From 1d83c596f86d98738e676f58f4e63f675f54a9a0 Mon Sep 17 00:00:00 2001 From: Nicolae Vartolomei Date: Fri, 5 Mar 2021 09:50:26 +0000 Subject: [PATCH 0001/1647] RFC: Throw exception if removing parts from ZooKeeper fails. This is used for removing part metadata from ZooKeeper when executing queue events like `DROP_RANGE` triggered when a user tries to drop a part or a partition. There are other uses but I'll focus only on this one. Before this change the method was giving up silently if it was unable to remove parts from ZooKeeper and this behaviour seems to be problematic. It could lead to operation being reported as successful at first but data reappearing later (very rarely) or "stuck" events in replication queue. Here is one particular scenario which I think we've hit: * Execute a DETACH PARTITION * DROP_RANGE event put in the queue * Replicas try to execute dropRange but some of them get disconnected from ZK and 5 retries aren't enough (ZK is miss-behaving), return code (false) is ignored and log pointer advances. * One of the replica where dropRange failed is restarted. * checkParts is executed and it finds parts that weren't removed from ZK, logs `Removing locally missing part from ZooKeeper and queueing a fetch` and puts GET_PART on the queue. * Few things can happen from here: * There is a lagging replica that din't execute DROP_RANGE yet: part will be fetched. The other replica will execute DROP_RANGE later and we'll get diverging set of parts on replicas. * Another replica also silently failed to remove parts from ZK: both of them are left with GET_PART in the queue and none of them can make progress, logging: `No active replica has part ... or covering part`. --- .../ReplicatedMergeTreeRestartingThread.cpp | 2 +- src/Storages/StorageReplicatedMergeTree.cpp | 21 ++++++++++--------- src/Storages/StorageReplicatedMergeTree.h | 5 +++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp index b3cb7c92def..ce73281cfe8 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp @@ -208,7 +208,7 @@ void ReplicatedMergeTreeRestartingThread::removeFailedQuorumParts() return; /// Firstly, remove parts from ZooKeeper - storage.tryRemovePartsFromZooKeeperWithRetries(failed_parts); + storage.removePartsFromZooKeeperWithRetries(failed_parts); for (const auto & part_name : failed_parts) { diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 68f3b6d80d1..d95c1f12aa8 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -1954,7 +1954,7 @@ void StorageReplicatedMergeTree::executeDropRange(const LogEntry & entry) } /// Forcibly remove parts from ZooKeeper - tryRemovePartsFromZooKeeperWithRetries(parts_to_remove); + removePartsFromZooKeeperWithRetries(parts_to_remove); if (entry.detach) LOG_DEBUG(log, "Detached {} parts inside {}.", parts_to_remove.size(), entry.new_part_name); @@ -2047,7 +2047,7 @@ bool StorageReplicatedMergeTree::executeReplaceRange(const LogEntry & entry) if (parts_to_add.empty()) { LOG_INFO(log, "All parts from REPLACE PARTITION command have been already attached"); - tryRemovePartsFromZooKeeperWithRetries(parts_to_remove); + removePartsFromZooKeeperWithRetries(parts_to_remove); return true; } @@ -2284,7 +2284,7 @@ bool StorageReplicatedMergeTree::executeReplaceRange(const LogEntry & entry) throw; } - tryRemovePartsFromZooKeeperWithRetries(parts_to_remove); + removePartsFromZooKeeperWithRetries(parts_to_remove); res_parts.clear(); parts_to_remove.clear(); cleanup_thread.wakeup(); @@ -2408,7 +2408,7 @@ void StorageReplicatedMergeTree::cloneReplica(const String & source_replica, Coo LOG_WARNING(log, "Source replica does not have part {}. Removing it from ZooKeeper.", part); } } - tryRemovePartsFromZooKeeperWithRetries(parts_to_remove_from_zk); + removePartsFromZooKeeperWithRetries(parts_to_remove_from_zk); auto local_active_parts = getDataParts(); DataPartsVector parts_to_remove_from_working_set; @@ -5470,16 +5470,16 @@ void StorageReplicatedMergeTree::clearOldPartsAndRemoveFromZK() } -bool StorageReplicatedMergeTree::tryRemovePartsFromZooKeeperWithRetries(DataPartsVector & parts, size_t max_retries) +void StorageReplicatedMergeTree::removePartsFromZooKeeperWithRetries(DataPartsVector & parts, size_t max_retries) { Strings part_names_to_remove; for (const auto & part : parts) part_names_to_remove.emplace_back(part->name); - return tryRemovePartsFromZooKeeperWithRetries(part_names_to_remove, max_retries); + return removePartsFromZooKeeperWithRetries(part_names_to_remove, max_retries); } -bool StorageReplicatedMergeTree::tryRemovePartsFromZooKeeperWithRetries(const Strings & part_names, size_t max_retries) +void StorageReplicatedMergeTree::removePartsFromZooKeeperWithRetries(const Strings & part_names, size_t max_retries) { size_t num_tries = 0; bool success = false; @@ -5544,7 +5544,8 @@ bool StorageReplicatedMergeTree::tryRemovePartsFromZooKeeperWithRetries(const St std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } - return success; + if (!success) + throw Exception(ErrorCodes::UNFINISHED, "Failed to remove parts from ZooKeeper after {} retries", num_tries); } void StorageReplicatedMergeTree::removePartsFromZooKeeper( @@ -5843,7 +5844,7 @@ void StorageReplicatedMergeTree::replacePartitionFrom( lock.assumeUnlocked(); /// Forcibly remove replaced parts from ZooKeeper - tryRemovePartsFromZooKeeperWithRetries(parts_to_remove); + removePartsFromZooKeeperWithRetries(parts_to_remove); /// Speedup removing of replaced parts from filesystem parts_to_remove.clear(); @@ -6028,7 +6029,7 @@ void StorageReplicatedMergeTree::movePartitionToTable(const StoragePtr & dest_ta for (auto & lock : ephemeral_locks) lock.assumeUnlocked(); - tryRemovePartsFromZooKeeperWithRetries(parts_to_remove); + removePartsFromZooKeeperWithRetries(parts_to_remove); parts_to_remove.clear(); cleanup_thread.wakeup(); diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index a1a70ada9b2..2591fa2d3dc 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -388,8 +388,9 @@ private: void removePartsFromZooKeeper(zkutil::ZooKeeperPtr & zookeeper, const Strings & part_names, NameSet * parts_should_be_retried = nullptr); - bool tryRemovePartsFromZooKeeperWithRetries(const Strings & part_names, size_t max_retries = 5); - bool tryRemovePartsFromZooKeeperWithRetries(DataPartsVector & parts, size_t max_retries = 5); + /// Remove parts from ZooKeeper, throw exception if unable to do so after max_retries. + void removePartsFromZooKeeperWithRetries(const Strings & part_names, size_t max_retries = 5); + void removePartsFromZooKeeperWithRetries(DataPartsVector & parts, size_t max_retries = 5); /// Removes a part from ZooKeeper and adds a task to the queue to download it. It is supposed to do this with broken parts. void removePartAndEnqueueFetch(const String & part_name); From f596906f47667c2160957540be4814d20672caee Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 31 Mar 2021 20:55:04 +0300 Subject: [PATCH 0002/1647] add transaction counters --- src/Common/ErrorCodes.cpp | 2 + src/Common/TransactionMetadata.cpp | 18 +++++ src/Common/TransactionMetadata.h | 41 +++++++++++ .../FunctionsTransactionCounters.cpp | 58 +++++++++++++++ .../registerFunctionsMiscellaneous.cpp | 2 + src/Interpreters/Context.cpp | 15 ++++ src/Interpreters/Context.h | 8 +++ src/Interpreters/InterpreterFactory.cpp | 6 ++ .../InterpreterTransactionControlQuery.cpp | 72 +++++++++++++++++++ .../InterpreterTransactionControlQuery.h | 31 ++++++++ src/Interpreters/MergeTreeTransaction.cpp | 14 ++++ src/Interpreters/MergeTreeTransaction.h | 35 +++++++++ src/Interpreters/QueryLog.cpp | 6 +- src/Interpreters/QueryLog.h | 3 + src/Interpreters/TransactionLog.cpp | 44 ++++++++++++ src/Interpreters/TransactionLog.h | 30 ++++++++ src/Interpreters/executeQuery.cpp | 7 ++ src/Parsers/ASTTransactionControl.cpp | 34 +++++++++ src/Parsers/ASTTransactionControl.h | 29 ++++++++ src/Parsers/ParserQuery.cpp | 5 +- src/Parsers/ParserTransactionControl.cpp | 25 +++++++ src/Parsers/ParserTransactionControl.h | 14 ++++ src/Storages/MergeTree/IMergeTreeDataPart.h | 14 ++++ src/Storages/MergeTree/MergeTreeData.cpp | 11 ++- src/Storages/System/StorageSystemParts.cpp | 22 +++++- 25 files changed, 542 insertions(+), 4 deletions(-) create mode 100644 src/Common/TransactionMetadata.cpp create mode 100644 src/Common/TransactionMetadata.h create mode 100644 src/Functions/FunctionsTransactionCounters.cpp create mode 100644 src/Interpreters/InterpreterTransactionControlQuery.cpp create mode 100644 src/Interpreters/InterpreterTransactionControlQuery.h create mode 100644 src/Interpreters/MergeTreeTransaction.cpp create mode 100644 src/Interpreters/MergeTreeTransaction.h create mode 100644 src/Interpreters/TransactionLog.cpp create mode 100644 src/Interpreters/TransactionLog.h create mode 100644 src/Parsers/ASTTransactionControl.cpp create mode 100644 src/Parsers/ASTTransactionControl.h create mode 100644 src/Parsers/ParserTransactionControl.cpp create mode 100644 src/Parsers/ParserTransactionControl.h diff --git a/src/Common/ErrorCodes.cpp b/src/Common/ErrorCodes.cpp index 0f85ad5c792..55745ffc211 100644 --- a/src/Common/ErrorCodes.cpp +++ b/src/Common/ErrorCodes.cpp @@ -548,6 +548,8 @@ M(578, INVALID_FORMAT_INSERT_QUERY_WITH_DATA) \ M(579, INCORRECT_PART_TYPE) \ M(580, CANNOT_SET_ROUNDING_MODE) \ + M(581, INVALID_TRANSACTION) \ + M(582, SERIALIZATION_ERROR) \ \ M(998, POSTGRESQL_CONNECTION_FAILURE) \ M(999, KEEPER_EXCEPTION) \ diff --git a/src/Common/TransactionMetadata.cpp b/src/Common/TransactionMetadata.cpp new file mode 100644 index 00000000000..430ea5cda29 --- /dev/null +++ b/src/Common/TransactionMetadata.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +namespace DB +{ + +DataTypePtr TransactionID::getDataType() +{ + DataTypes types; + types.push_back(std::make_shared()); + types.push_back(std::make_shared()); + types.push_back(std::make_shared()); + return std::make_shared(std::move(types)); +} + +} diff --git a/src/Common/TransactionMetadata.h b/src/Common/TransactionMetadata.h new file mode 100644 index 00000000000..34eaaf2d245 --- /dev/null +++ b/src/Common/TransactionMetadata.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include + +namespace DB +{ + +class IDataType; +using DataTypePtr = std::shared_ptr; + +/// FIXME Transactions: Sequential node numbers in ZooKeeper are Int32, but 31 bit is not enough +using CSN = UInt64; +using Snapshot = CSN; +using LocalTID = UInt64; + +struct TransactionID +{ + CSN start_csn = 0; + LocalTID local_tid = 0; + UUID host_id = UUIDHelpers::Nil; /// Depends on #17278, leave it Nil for now. + + static DataTypePtr getDataType(); +}; + +namespace Tx +{ + +const CSN UnknownCSN = 0; +const CSN PrehistoricCSN = 1; + +const LocalTID PrehistoricLocalTID = 1; + +const TransactionID EmptyTID = {0, 0, UUIDHelpers::Nil}; +const TransactionID PrehistoricTID = {0, PrehistoricLocalTID, UUIDHelpers::Nil}; + +/// So far, that changes will never become visible +const CSN RolledBackCSN = std::numeric_limits::max(); + +} + +} diff --git a/src/Functions/FunctionsTransactionCounters.cpp b/src/Functions/FunctionsTransactionCounters.cpp new file mode 100644 index 00000000000..93a2961fb74 --- /dev/null +++ b/src/Functions/FunctionsTransactionCounters.cpp @@ -0,0 +1,58 @@ +#include +#include +#include + + +namespace DB +{ + +namespace +{ + +class FunctionTransactionID : public IFunction +{ +public: + static constexpr auto name = "transactionID"; + + static FunctionPtr create(const Context & context) + { + return std::make_shared(context.getCurrentTransaction()); + } + + explicit FunctionTransactionID(MergeTreeTransactionPtr && txn_) : txn(txn_) + { + } + + String getName() const override { return name; } + + size_t getNumberOfArguments() const override { return 0; } + + DataTypePtr getReturnTypeImpl(const DataTypes &) const override + { + return TransactionID::getDataType(); + } + + bool isDeterministic() const override { return false; } + + ColumnPtr executeImpl(const ColumnsWithTypeAndName &, const DataTypePtr & result_type, size_t input_rows_count) const override + { + Tuple res; + if (txn) + res = {txn->tid.start_csn, txn->tid.local_tid, txn->tid.host_id}; + else + res = {UInt64(0), UInt64(0), UUIDHelpers::Nil}; + return result_type->createColumnConst(input_rows_count, res); + } + +private: + MergeTreeTransactionPtr txn; +}; + +} + +void registerFunctionsTransactionCounters(FunctionFactory & factory) +{ + factory.registerFunction(); +} + +} diff --git a/src/Functions/registerFunctionsMiscellaneous.cpp b/src/Functions/registerFunctionsMiscellaneous.cpp index d00d4a42db0..ab415740be3 100644 --- a/src/Functions/registerFunctionsMiscellaneous.cpp +++ b/src/Functions/registerFunctionsMiscellaneous.cpp @@ -73,6 +73,7 @@ void registerFunctionFile(FunctionFactory & factory); void registerFunctionConnectionId(FunctionFactory & factory); void registerFunctionPartitionId(FunctionFactory & factory); void registerFunctionIsIPAddressContainedIn(FunctionFactory &); +void registerFunctionsTransactionCounters(FunctionFactory & factory); #if USE_ICU void registerFunctionConvertCharset(FunctionFactory &); @@ -146,6 +147,7 @@ void registerFunctionsMiscellaneous(FunctionFactory & factory) registerFunctionConnectionId(factory); registerFunctionPartitionId(factory); registerFunctionIsIPAddressContainedIn(factory); + registerFunctionsTransactionCounters(factory); #if USE_ICU registerFunctionConvertCharset(factory); diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 8eec5d099e2..5c6afb38ff4 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2581,6 +2581,21 @@ ZooKeeperMetadataTransactionPtr Context::getZooKeeperMetadataTransaction() const return metadata_transaction; } +void Context::setCurrentTransaction(MergeTreeTransactionPtr txn) +{ + assert(!merge_tree_transaction || !txn); + assert(this == session_context || this == query_context); + int enable_mvcc_test_helper = getConfigRef().getInt("_enable_mvcc_test_helper_dev", 0); + if (enable_mvcc_test_helper != 42) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Transactions are not supported"); + merge_tree_transaction = std::move(txn); +} + +MergeTreeTransactionPtr Context::getCurrentTransaction() const +{ + return merge_tree_transaction; +} + PartUUIDsPtr Context::getPartUUIDs() { auto lock = getLock(); diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index 5c98a2ba64a..e035031f993 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -122,6 +122,8 @@ struct BackgroundTaskSchedulingSettings; class ZooKeeperMetadataTransaction; using ZooKeeperMetadataTransactionPtr = std::shared_ptr; +class MergeTreeTransaction; +using MergeTreeTransactionPtr = std::shared_ptr; #if USE_EMBEDDED_COMPILER class CompiledExpressionCache; @@ -290,6 +292,8 @@ private: /// thousands of signatures. /// And I hope it will be replaced with more common Transaction sometime. + MergeTreeTransactionPtr merge_tree_transaction; /// Current transaction context. Can be inside session or query context. + /// Use copy constructor or createGlobal() instead Context(); @@ -757,6 +761,10 @@ public: /// Returns context of current distributed DDL query or nullptr. ZooKeeperMetadataTransactionPtr getZooKeeperMetadataTransaction() const; + void setCurrentTransaction(MergeTreeTransactionPtr txn); + MergeTreeTransactionPtr getCurrentTransaction() const; + + struct MySQLWireContext { uint8_t sequence_id = 0; diff --git a/src/Interpreters/InterpreterFactory.cpp b/src/Interpreters/InterpreterFactory.cpp index 15e4c52f040..5684150fed1 100644 --- a/src/Interpreters/InterpreterFactory.cpp +++ b/src/Interpreters/InterpreterFactory.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -66,6 +67,7 @@ #include #include #include +#include #include #include @@ -264,6 +266,10 @@ std::unique_ptr InterpreterFactory::get(ASTPtr & query, Context & { return std::make_unique(query, context); } + else if (query->as()) + { + return std::make_unique(query, context); + } else { throw Exception("Unknown type of query: " + query->getID(), ErrorCodes::UNKNOWN_TYPE_OF_QUERY); diff --git a/src/Interpreters/InterpreterTransactionControlQuery.cpp b/src/Interpreters/InterpreterTransactionControlQuery.cpp new file mode 100644 index 00000000000..369f5edd1da --- /dev/null +++ b/src/Interpreters/InterpreterTransactionControlQuery.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; + extern const int INVALID_TRANSACTION; +} + +BlockIO InterpreterTransactionControlQuery::execute() +{ + if (!query_context.hasSessionContext()) + throw Exception(ErrorCodes::INVALID_TRANSACTION, "Transaction Control Language queries are allowed only inside session"); + + Context & session_context = query_context.getSessionContext(); + const auto & tcl = query_ptr->as(); + + switch (tcl.action) + { + case ASTTransactionControl::BEGIN: + return executeBegin(session_context); + case ASTTransactionControl::COMMIT: + return executeCommit(session_context); + case ASTTransactionControl::ROLLBACK: + return executeRollback(session_context); + } +} + +BlockIO InterpreterTransactionControlQuery::executeBegin(Context & context) +{ + if (context.getCurrentTransaction()) + throw Exception(ErrorCodes::INVALID_TRANSACTION, "Nested transactions are not supported"); + + auto txn = TransactionLog::instance().beginTransaction(); + context.setCurrentTransaction(txn); + query_context.setCurrentTransaction(txn); + return {}; +} + +BlockIO InterpreterTransactionControlQuery::executeCommit(Context & context) +{ + auto txn = context.getCurrentTransaction(); + if (!txn) + throw Exception(ErrorCodes::INVALID_TRANSACTION, "There is no current transaction"); + if (txn->getState() != MergeTreeTransaction::RUNNING) + throw Exception(ErrorCodes::INVALID_TRANSACTION, "Transaction is not in RUNNING state"); + + TransactionLog::instance().commitTransaction(txn); + context.setCurrentTransaction(nullptr); + return {}; +} + +BlockIO InterpreterTransactionControlQuery::executeRollback(Context & context) +{ + auto txn = context.getCurrentTransaction(); + if (!txn) + throw Exception(ErrorCodes::INVALID_TRANSACTION, "There is no current transaction"); + if (txn->getState() == MergeTreeTransaction::COMMITTED) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Transaction is in COMMITTED state"); + + if (txn->getState() == MergeTreeTransaction::RUNNING) + TransactionLog::instance().rollbackTransaction(txn); + context.getSessionContext().setCurrentTransaction(nullptr); + return {}; +} + +} diff --git a/src/Interpreters/InterpreterTransactionControlQuery.h b/src/Interpreters/InterpreterTransactionControlQuery.h new file mode 100644 index 00000000000..4c455d22a24 --- /dev/null +++ b/src/Interpreters/InterpreterTransactionControlQuery.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include + +namespace DB +{ + +class InterpreterTransactionControlQuery : public IInterpreter +{ +public: + InterpreterTransactionControlQuery(const ASTPtr & query_ptr_, Context & context_) + : query_ptr(query_ptr_) + , query_context(context_) + { + } + + BlockIO execute() override; + + bool ignoreQuota() const override { return true; } + bool ignoreLimits() const override { return true; } +private: + BlockIO executeBegin(Context & context); + BlockIO executeCommit(Context & context); + BlockIO executeRollback(Context & context); + +private: + ASTPtr query_ptr; + Context & query_context; +}; + +} diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp new file mode 100644 index 00000000000..d1af1d61ba7 --- /dev/null +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -0,0 +1,14 @@ +#include + +namespace DB +{ + +MergeTreeTransaction::MergeTreeTransaction(Snapshot snapshot_, LocalTID local_tid_, UUID host_id) + : tid({snapshot_, local_tid_, host_id}) + , snapshot(snapshot_) + , state(RUNNING) +{ +} + + +} diff --git a/src/Interpreters/MergeTreeTransaction.h b/src/Interpreters/MergeTreeTransaction.h new file mode 100644 index 00000000000..95c4a81c756 --- /dev/null +++ b/src/Interpreters/MergeTreeTransaction.h @@ -0,0 +1,35 @@ +#pragma once +#include + +namespace DB +{ + +class MergeTreeTransaction +{ + friend class TransactionLog; +public: + enum State + { + RUNNING, + COMMITTED, + ROLLED_BACK, + }; + + Snapshot getSnapshot() const { return snapshot; } + State getState() const { return state; } + + const TransactionID tid; + + MergeTreeTransaction() = delete; + MergeTreeTransaction(Snapshot snapshot_, LocalTID local_tid_, UUID host_id); + +private: + Snapshot snapshot; + State state; + + CSN csn = Tx::UnknownCSN; +}; + +using MergeTreeTransactionPtr = std::shared_ptr; + +} diff --git a/src/Interpreters/QueryLog.cpp b/src/Interpreters/QueryLog.cpp index b6902468242..cee1d2b5412 100644 --- a/src/Interpreters/QueryLog.cpp +++ b/src/Interpreters/QueryLog.cpp @@ -108,7 +108,9 @@ Block QueryLogElement::createBlock() {std::make_shared(std::make_shared()), "used_formats"}, {std::make_shared(std::make_shared()), "used_functions"}, {std::make_shared(std::make_shared()), "used_storages"}, - {std::make_shared(std::make_shared()), "used_table_functions"} + {std::make_shared(std::make_shared()), "used_table_functions"}, + + {TransactionID::getDataType(), "transaction_id"} }; } @@ -237,6 +239,8 @@ void QueryLogElement::appendToBlock(MutableColumns & columns) const fill_column(used_storages, column_storage_factory_objects); fill_column(used_table_functions, column_table_function_factory_objects); } + + columns[i++]->insert(Tuple{tid.start_csn, tid.local_tid, tid.host_id}); } void QueryLogElement::appendClientInfo(const ClientInfo & client_info, MutableColumns & columns, size_t & i) diff --git a/src/Interpreters/QueryLog.h b/src/Interpreters/QueryLog.h index 8617a8d1cbc..7532f2d14f2 100644 --- a/src/Interpreters/QueryLog.h +++ b/src/Interpreters/QueryLog.h @@ -2,6 +2,7 @@ #include #include +#include namespace ProfileEvents @@ -80,6 +81,8 @@ struct QueryLogElement std::shared_ptr profile_counters; std::shared_ptr query_settings; + TransactionID tid; + static std::string name() { return "QueryLog"; } static Block createBlock(); diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp new file mode 100644 index 00000000000..00dbb3352b4 --- /dev/null +++ b/src/Interpreters/TransactionLog.cpp @@ -0,0 +1,44 @@ +#include + +namespace DB +{ + +TransactionLog & TransactionLog::instance() +{ + static TransactionLog inst; + return inst; +} + +TransactionLog::TransactionLog() +{ + csn_counter = 1; + local_tid_counter = 1; +} + +Snapshot TransactionLog::getLatestSnapshot() const +{ + return csn_counter.load(); +} + +MergeTreeTransactionPtr TransactionLog::beginTransaction() +{ + Snapshot snapshot = csn_counter.load(); + LocalTID ltid = 1 + local_tid_counter.fetch_add(1); + return std::make_shared(snapshot, ltid, UUIDHelpers::Nil); +} + +CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) +{ + txn->csn = 1 + csn_counter.fetch_add(1); + /// TODO Transactions: reset local_tid_counter + txn->state = MergeTreeTransaction::COMMITTED; + return txn->csn; +} + +void TransactionLog::rollbackTransaction(const MergeTreeTransactionPtr & txn) +{ + txn->csn = Tx::RolledBackCSN; + txn->state = MergeTreeTransaction::ROLLED_BACK; +} + +} diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h new file mode 100644 index 00000000000..073d2a2929d --- /dev/null +++ b/src/Interpreters/TransactionLog.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + +namespace DB +{ + +class TransactionLog final : private boost::noncopyable +{ +public: + static TransactionLog & instance(); + + TransactionLog(); + + Snapshot getLatestSnapshot() const; + + /// Allocated TID, returns transaction object + MergeTreeTransactionPtr beginTransaction(); + + CSN commitTransaction(const MergeTreeTransactionPtr & txn); + + void rollbackTransaction(const MergeTreeTransactionPtr & txn); + +private: + std::atomic csn_counter; + std::atomic local_tid_counter; +}; + +} diff --git a/src/Interpreters/executeQuery.cpp b/src/Interpreters/executeQuery.cpp index a5c21405ff1..a00f8301622 100644 --- a/src/Interpreters/executeQuery.cpp +++ b/src/Interpreters/executeQuery.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include #include @@ -266,6 +267,9 @@ static void onExceptionBeforeStart(const String & query_for_logging, Context & c if (elem.log_comment.size() > settings.max_query_size) elem.log_comment.resize(settings.max_query_size); + if (auto txn = context.getCurrentTransaction()) + elem.tid = txn->tid; + if (settings.calculate_text_stack_trace) setExceptionStackTrace(elem); logException(context, elem); @@ -634,6 +638,9 @@ static std::tuple executeQueryImpl( elem.client_info = context.getClientInfo(); + if (auto txn = context.getCurrentTransaction()) + elem.tid = txn->tid; + bool log_queries = settings.log_queries && !internal; /// Log into system table start of query execution, if need. diff --git a/src/Parsers/ASTTransactionControl.cpp b/src/Parsers/ASTTransactionControl.cpp new file mode 100644 index 00000000000..d12c9d6d6e4 --- /dev/null +++ b/src/Parsers/ASTTransactionControl.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +void ASTTransactionControl::formatImpl(const FormatSettings & format /*state*/, FormatState &, FormatStateStacked /*frame*/) const +{ + switch (action) + { + case BEGIN: + format.ostr << (format.hilite ? hilite_keyword : "") << "BEGIN TRANSACTION" << (format.hilite ? hilite_none : ""); + break; + case COMMIT: + format.ostr << (format.hilite ? hilite_keyword : "") << "COMMIT" << (format.hilite ? hilite_none : ""); + break; + case ROLLBACK: + format.ostr << (format.hilite ? hilite_keyword : "") << "ROLLBACK" << (format.hilite ? hilite_none : ""); + break; + } +} + +void ASTTransactionControl::updateTreeHashImpl(SipHash & hash_state) const +{ + hash_state.update(action); +} + +} diff --git a/src/Parsers/ASTTransactionControl.h b/src/Parsers/ASTTransactionControl.h new file mode 100644 index 00000000000..c01c4172627 --- /dev/null +++ b/src/Parsers/ASTTransactionControl.h @@ -0,0 +1,29 @@ +#pragma once +#include + +namespace DB +{ + +/// Common AST for TCL queries +class ASTTransactionControl : public IAST +{ +public: + enum QueryType + { + BEGIN, + COMMIT, + ROLLBACK, + }; + + QueryType action; + + ASTTransactionControl(QueryType action_) : action(action_) {} + + String getID(char /*delimiter*/) const override { return "ASTTransactionControl"; } + ASTPtr clone() const override { return std::make_shared(*this); } + + void formatImpl(const FormatSettings & format, FormatState & /*state*/, FormatStateStacked /*frame*/) const override; + void updateTreeHashImpl(SipHash & hash_state) const override; +}; + +} diff --git a/src/Parsers/ParserQuery.cpp b/src/Parsers/ParserQuery.cpp index 4550bdc8a75..f9c11f1c870 100644 --- a/src/Parsers/ParserQuery.cpp +++ b/src/Parsers/ParserQuery.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace DB @@ -40,6 +41,7 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) ParserGrantQuery grant_p; ParserSetRoleQuery set_role_p; ParserExternalDDLQuery external_ddl_p; + ParserTransactionControl transaction_control_p; bool res = query_with_output_p.parse(pos, node, expected) || insert_p.parse(pos, node, expected) @@ -54,7 +56,8 @@ bool ParserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) || create_settings_profile_p.parse(pos, node, expected) || drop_access_entity_p.parse(pos, node, expected) || grant_p.parse(pos, node, expected) - || external_ddl_p.parse(pos, node, expected); + || external_ddl_p.parse(pos, node, expected) + || transaction_control_p.parse(pos, node, expected); return res; } diff --git a/src/Parsers/ParserTransactionControl.cpp b/src/Parsers/ParserTransactionControl.cpp new file mode 100644 index 00000000000..a5591a0447e --- /dev/null +++ b/src/Parsers/ParserTransactionControl.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +namespace DB +{ + +bool ParserTransactionControl::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) +{ + ASTTransactionControl::QueryType action; + + if (ParserKeyword("BEGIN TRANSACTION").ignore(pos, expected)) + action = ASTTransactionControl::BEGIN; + else if (ParserKeyword("COMMIT").ignore(pos, expected)) + action = ASTTransactionControl::COMMIT; + else if (ParserKeyword("ROLLBACK").ignore(pos, expected)) + action = ASTTransactionControl::ROLLBACK; + else + return false; + + node = std::make_shared(action); + return true; +} + +} diff --git a/src/Parsers/ParserTransactionControl.h b/src/Parsers/ParserTransactionControl.h new file mode 100644 index 00000000000..157c088624c --- /dev/null +++ b/src/Parsers/ParserTransactionControl.h @@ -0,0 +1,14 @@ +#pragma once +#include + +namespace DB +{ + +class ParserTransactionControl : public IParserBase +{ +public: + const char * getName() const override { return "TCL query"; } + bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; +}; + +} diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 03f6564788a..c990bdcc65b 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -301,6 +302,19 @@ public: CompressionCodecPtr default_codec; + struct VersionMetadata + { + TransactionID mintid = Tx::EmptyTID; + TransactionID maxtid = Tx::EmptyTID; + + bool maybe_visible = false; + + CSN mincsn = Tx::UnknownCSN; + CSN maxcsn = Tx::UnknownCSN; + }; + + mutable VersionMetadata versions; + /// For data in RAM ('index') UInt64 getIndexSizeInBytes() const; UInt64 getIndexSizeInAllocatedBytes() const; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index d61de13b604..7610239662e 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -993,6 +993,8 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) { (*it)->remove_time.store((*it)->modification_time, std::memory_order_relaxed); modifyPartState(it, DataPartState::Outdated); + (*it)->versions.maxtid = Tx::PrehistoricTID; + (*it)->versions.maxcsn = Tx::PrehistoricCSN; removePartContributionToDataVolume(*it); }; @@ -1000,6 +1002,14 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) while (curr_jt != data_parts_by_state_and_info.end() && (*curr_jt)->getState() == DataPartState::Committed) { + /// We do not have version metadata and transactions history for old parts, + /// so let's consider that such parts were created by some ancient transaction + /// and were committed with some prehistoric CSN. + /// TODO Transactions: distinguish "prehistoric" parts from uncommitted parts in case of hard restart + (*curr_jt)->versions.mintid = Tx::PrehistoricTID; + (*curr_jt)->versions.mincsn = Tx::PrehistoricCSN; + (*curr_jt)->versions.maybe_visible = true; + /// Don't consider data parts belonging to different partitions. if ((*curr_jt)->info.partition_id != (*prev_jt)->info.partition_id) { @@ -1030,7 +1040,6 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) calculateColumnSizesImpl(); - LOG_DEBUG(log, "Loaded data parts ({} items)", data_parts_indexes.size()); } diff --git a/src/Storages/System/StorageSystemParts.cpp b/src/Storages/System/StorageSystemParts.cpp index 6a643dbe1b9..f100ecdfa9b 100644 --- a/src/Storages/System/StorageSystemParts.cpp +++ b/src/Storages/System/StorageSystemParts.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace DB { @@ -75,7 +76,12 @@ StorageSystemParts::StorageSystemParts(const StorageID & table_id_) {"rows_where_ttl_info.expression", std::make_shared(std::make_shared())}, {"rows_where_ttl_info.min", std::make_shared(std::make_shared())}, - {"rows_where_ttl_info.max", std::make_shared(std::make_shared())} + {"rows_where_ttl_info.max", std::make_shared(std::make_shared())}, + + {"mintid", TransactionID::getDataType()}, + {"maxtid", TransactionID::getDataType()}, + {"mincsn", std::make_shared()}, + {"maxcsn", std::make_shared()}, } ) { @@ -257,6 +263,20 @@ void StorageSystemParts::processNextStorage( /// Do not use part->getState*, it can be changed from different thread if (has_state_column) columns[res_index++]->insert(IMergeTreeDataPart::stateToString(part_state)); + + auto get_tid_as_field = [](const TransactionID & tid) -> Field + { + return Tuple{tid.start_csn, tid.local_tid, tid.host_id}; + }; + + if (columns_mask[src_index++]) + columns[res_index++]->insert(get_tid_as_field(part->versions.mintid)); + if (columns_mask[src_index++]) + columns[res_index++]->insert(get_tid_as_field(part->versions.maxtid)); + if (columns_mask[src_index++]) + columns[res_index++]->insert(part->versions.mincsn); + if (columns_mask[src_index++]) + columns[res_index++]->insert(part->versions.maxcsn); } } From 3422bd1742238ec85e5328bbf6f0c53a13449515 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 8 Apr 2021 20:20:45 +0300 Subject: [PATCH 0003/1647] check parts visibility for select --- src/Common/TransactionMetadata.cpp | 154 ++++++++++++++++++ src/Common/TransactionMetadata.h | 60 +++++++ src/IO/WriteHelpers.h | 16 ++ src/Interpreters/InterpreterSelectQuery.cpp | 3 +- src/Interpreters/MergeTreeTransaction.cpp | 79 +++++++++ src/Interpreters/MergeTreeTransaction.h | 20 +++ src/Interpreters/TransactionLog.cpp | 81 ++++++++- src/Interpreters/TransactionLog.h | 14 ++ src/Storages/MergeTree/IMergeTreeDataPart.h | 12 +- .../MergeTree/MergeTreeBlockOutputStream.cpp | 2 +- .../MergeTree/MergeTreeBlockOutputStream.h | 9 +- src/Storages/MergeTree/MergeTreeData.cpp | 70 ++++++-- src/Storages/MergeTree/MergeTreeData.h | 12 +- .../MergeTree/MergeTreeDataMergerMutator.cpp | 2 +- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- .../ReplicatedMergeTreeBlockOutputStream.cpp | 2 +- src/Storages/StorageMergeTree.cpp | 10 +- src/Storages/StorageReplicatedMergeTree.cpp | 12 +- src/Storages/System/StorageSystemParts.cpp | 6 +- .../01174_select_insert_isolation.reference | 1 + .../01174_select_insert_isolation.sh | 60 +++++++ 21 files changed, 574 insertions(+), 53 deletions(-) create mode 100644 tests/queries/0_stateless/01174_select_insert_isolation.reference create mode 100755 tests/queries/0_stateless/01174_select_insert_isolation.sh diff --git a/src/Common/TransactionMetadata.cpp b/src/Common/TransactionMetadata.cpp index 430ea5cda29..a4dcebbf3ef 100644 --- a/src/Common/TransactionMetadata.cpp +++ b/src/Common/TransactionMetadata.cpp @@ -1,11 +1,19 @@ #include +#include #include #include #include +#include namespace DB { +namespace ErrorCodes +{ +extern const int SERIALIZATION_ERROR; +extern const int LOGICAL_ERROR; +} + DataTypePtr TransactionID::getDataType() { DataTypes types; @@ -15,4 +23,150 @@ DataTypePtr TransactionID::getDataType() return std::make_shared(std::move(types)); } +TIDHash TransactionID::getHash() const +{ + SipHash hash; + hash.update(start_csn); + hash.update(local_tid); + hash.update(host_id); + return hash.get64(); +} + +/// It can be used fro introspection purposes only +TransactionID VersionMetadata::getMaxTID() const +{ + TIDHash max_lock = maxtid_lock.load(); + if (max_lock) + { + if (auto txn = TransactionLog::instance().tryGetRunningTransaction(max_lock)) + return txn->tid; + } + + if (maxcsn.load(std::memory_order_relaxed)) + { + /// maxtid cannot be changed since we have maxcsn, so it's readonly + return maxtid; + } + + return Tx::EmptyTID; +} + +void VersionMetadata::lockMaxTID(const TransactionID & tid, const String & error_context) +{ + TIDHash max_lock_value = tid.getHash(); + TIDHash expected_max_lock_value = 0; + bool locked = maxtid_lock.compare_exchange_strong(expected_max_lock_value, max_lock_value); + if (!locked) + { + throw Exception(ErrorCodes::SERIALIZATION_ERROR, "Serialization error: " + "Transaction {} tried to remove data part, " + "but it's locked ({}) by another transaction {} which is currently removing this part. {}", + tid, expected_max_lock_value, getMaxTID(), error_context); + } + + maxtid = tid; +} + +void VersionMetadata::unlockMaxTID(const TransactionID & tid) +{ + TIDHash max_lock_value = tid.getHash(); + TIDHash locked_by = maxtid_lock.load(); + + auto throw_cannot_unlock = [&]() + { + auto locked_by_txn = TransactionLog::instance().tryGetRunningTransaction(locked_by); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot unlock maxtid, it's a bug. Current: {} {}, actual: {} {}", + max_lock_value, tid, locked_by, locked_by_txn ? locked_by_txn->tid : Tx::EmptyTID); + }; + + if (locked_by != max_lock_value) + throw_cannot_unlock(); + + maxtid = Tx::EmptyTID; + bool unlocked = maxtid_lock.compare_exchange_strong(locked_by, 0); + if (!unlocked) + throw_cannot_unlock(); +} + +void VersionMetadata::setMinTID(const TransactionID & tid) +{ + /// TODO Transactions: initialize it in constructor on part creation and remove this method + assert(!mintid); + const_cast(mintid) = tid; +} + +bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) +{ + Snapshot snapshot_version = txn.getSnapshot(); + assert(mintid); + CSN min = mincsn.load(std::memory_order_relaxed); + TIDHash max_lock = maxtid_lock.load(std::memory_order_relaxed); + CSN max = maxcsn.load(std::memory_order_relaxed); + + [[maybe_unused]] bool had_mincsn = min; + [[maybe_unused]] bool had_maxtid = max_lock; + [[maybe_unused]] bool had_maxcsn = max; + assert(!had_maxcsn || had_maxtid); + assert(!had_maxcsn || had_mincsn); + + /// Fast path: + + /// Part is definitely not visible if: + /// - creation was committed after we took the snapshot + /// - removal was committed before we took the snapshot + /// - current transaction is removing it + if (min && snapshot_version < min) + return false; + if (max && max <= snapshot_version) + return false; + if (max_lock && max_lock == txn.tid.getHash()) + return false; + + /// Otherwise, part is definitely visible if: + /// - creation was committed after we took the snapshot and nobody tried to remove the part + /// - current transaction is creating it + if (!max_lock && min && min <= snapshot_version) + return true; + if (mintid == txn.tid) + return true; + + /// End of fast path. + + /// Data part has mintid/maxtid, but does not have mincsn/maxcsn. + /// It means that some transaction is creating/removing the part right now or has done it recently + /// and we don't know if it was already committed ot not. + assert(!had_mincsn || (had_maxtid && !had_maxcsn)); + assert(mintid != txn.tid && max_lock != txn.tid.getHash()); + + /// Before doing CSN lookup, let's check some extra conditions. + /// If snapshot_version <= some_tid.start_csn, then changes of transaction with some_tid + /// are definitely not visible for us, so we don't need to check if it was committed. + if (snapshot_version <= mintid.start_csn) + return false; + + /// Check if mintid/maxtid transactions are committed and write CSNs + /// TODO Transactions: we probably need some optimizations here + /// to avoid some CSN lookups or make the lookups cheaper. + /// NOTE: Old enough committed parts always have written CSNs, + /// so we can determine their visibility through fast path. + /// But for long-running writing transactions we will always do + /// CNS lookup and get 0 (UnknownCSN) until the transaction is committer/rolled back. + min = TransactionLog::instance().getCSN(mintid); + if (!min) + return false; /// Part creation is not committed yet + + /// We don't need to check if CSNs are already writen or not, + /// because once writen CSN cannot be changed, so it's safe to overwrite it (with tha same value). + mincsn.store(min, std::memory_order_relaxed); + + if (max_lock) + { + max = TransactionLog::instance().getCSN(max_lock); + if (max) + maxcsn.store(max, std::memory_order_relaxed); + } + + return min <= snapshot_version && (!max || snapshot_version < max); +} + } diff --git a/src/Common/TransactionMetadata.h b/src/Common/TransactionMetadata.h index 34eaaf2d245..6abf7c2b4b3 100644 --- a/src/Common/TransactionMetadata.h +++ b/src/Common/TransactionMetadata.h @@ -1,17 +1,21 @@ #pragma once #include #include +#include +#include namespace DB { class IDataType; using DataTypePtr = std::shared_ptr; +class MergeTreeTransaction; /// FIXME Transactions: Sequential node numbers in ZooKeeper are Int32, but 31 bit is not enough using CSN = UInt64; using Snapshot = CSN; using LocalTID = UInt64; +using TIDHash = UInt64; struct TransactionID { @@ -20,6 +24,24 @@ struct TransactionID UUID host_id = UUIDHelpers::Nil; /// Depends on #17278, leave it Nil for now. static DataTypePtr getDataType(); + + bool operator == (const TransactionID & rhs) const + { + return start_csn == rhs.start_csn && local_tid == rhs.local_tid && host_id == rhs.host_id; + } + + bool operator != (const TransactionID & rhs) const + { + return !(*this == rhs); + } + + operator bool() const + { + assert(local_tid || (start_csn == 0 && host_id == UUIDHelpers::Nil)); + return local_tid; + } + + TIDHash getHash() const; }; namespace Tx @@ -38,4 +60,42 @@ const CSN RolledBackCSN = std::numeric_limits::max(); } +struct VersionMetadata +{ + const TransactionID mintid = Tx::EmptyTID; + TransactionID maxtid = Tx::EmptyTID; + + std::atomic maxtid_lock = 0; + + std::atomic mincsn = Tx::UnknownCSN; + std::atomic maxcsn = Tx::UnknownCSN; + + bool isVisible(const MergeTreeTransaction & txn); + + TransactionID getMinTID() const { return mintid; } + TransactionID getMaxTID() const; + + void lockMaxTID(const TransactionID & tid, const String & error_context = {}); + void unlockMaxTID(const TransactionID & tid); + + /// It can be called only from MergeTreeTransaction or on server startup + void setMinTID(const TransactionID & tid); +}; + } + +template<> +struct fmt::formatter +{ + template + constexpr auto parse(ParseContext & context) + { + return context.begin(); + } + + template + auto format(const DB::TransactionID & tid, FormatContext & context) + { + return fmt::format_to(context.out(), "({}, {}, {})", tid.start_csn, tid.local_tid, tid.host_id); + } +}; diff --git a/src/IO/WriteHelpers.h b/src/IO/WriteHelpers.h index b9497b6f87e..8400d676018 100644 --- a/src/IO/WriteHelpers.h +++ b/src/IO/WriteHelpers.h @@ -1182,3 +1182,19 @@ struct PcgSerializer void writePointerHex(const void * ptr, WriteBuffer & buf); } + +template<> +struct fmt::formatter +{ + template + constexpr auto parse(ParseContext & context) + { + return context.begin(); + } + + template + auto format(const DB::UUID & uuid, FormatContext & context) + { + return fmt::format_to(context.out(), "{}", toString(uuid)); + } +}; diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 1f6b0c37437..42103ec15b4 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -1370,7 +1370,8 @@ void InterpreterSelectQuery::executeFetchColumns(QueryProcessingStage::Enum proc && processing_stage == QueryProcessingStage::FetchColumns && query_analyzer->hasAggregation() && (query_analyzer->aggregates().size() == 1) - && typeid_cast(query_analyzer->aggregates()[0].function.get()); + && typeid_cast(query_analyzer->aggregates()[0].function.get()) + && !context->getCurrentTransaction(); if (optimize_trivial_count) { diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index d1af1d61ba7..5b267e45f83 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -1,4 +1,6 @@ #include +#include +#include namespace DB { @@ -10,5 +12,82 @@ MergeTreeTransaction::MergeTreeTransaction(Snapshot snapshot_, LocalTID local_ti { } +void MergeTreeTransaction::addNewPart(const DataPartPtr & new_part, MergeTreeTransaction * txn) +{ + TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; + + new_part->versions.setMinTID(tid); + if (txn) + txn->addNewPart(new_part); +} + +void MergeTreeTransaction::removeOldPart(const DataPartPtr & part_to_remove, MergeTreeTransaction * txn) +{ + TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; + String error_context = fmt::format("Table: {}, part name: {}", + part_to_remove->storage.getStorageID().getNameForLogs(), + part_to_remove->name); + part_to_remove->versions.lockMaxTID(tid, error_context); + if (txn) + txn->removeOldPart(part_to_remove); +} + +void MergeTreeTransaction::addNewPartAndRemoveCovered(const DataPartPtr & new_part, const DataPartsVector & covered_parts, MergeTreeTransaction * txn) +{ + TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; + + new_part->versions.setMinTID(tid); + if (txn) + txn->addNewPart(new_part); + + String error_context = fmt::format("Table: {}, covering part name: {}", + new_part->storage.getStorageID().getNameForLogs(), + new_part->name); + error_context += ", part_name: {}"; + for (auto covered : covered_parts) + { + covered->versions.lockMaxTID(tid, fmt::format(error_context, covered->name)); + if (txn) + txn->removeOldPart(covered); + } +} + +void MergeTreeTransaction::addNewPart(const DataPartPtr & new_part) +{ + creating_parts.push_back(new_part); +} + +void MergeTreeTransaction::removeOldPart(const DataPartPtr & part_to_remove) +{ + removing_parts.push_back(part_to_remove); +} + +bool MergeTreeTransaction::isReadOnly() const +{ + return creating_parts.empty() && removing_parts.empty(); +} + +void MergeTreeTransaction::beforeCommit() +{ + assert(state == RUNNING); +} + +void MergeTreeTransaction::afterCommit() +{ + assert(state == COMMITTED); + for (const auto & part : creating_parts) + part->versions.mincsn.store(csn); + for (const auto & part : removing_parts) + part->versions.maxcsn.store(csn); +} + +void MergeTreeTransaction::rollback() +{ + assert(state == RUNNING); + for (const auto & part : creating_parts) + part->versions.mincsn.store(Tx::RolledBackCSN); + for (const auto & part : removing_parts) + part->versions.unlockMaxTID(tid); +} } diff --git a/src/Interpreters/MergeTreeTransaction.h b/src/Interpreters/MergeTreeTransaction.h index 95c4a81c756..c085fa96b0f 100644 --- a/src/Interpreters/MergeTreeTransaction.h +++ b/src/Interpreters/MergeTreeTransaction.h @@ -4,6 +4,10 @@ namespace DB { +class IMergeTreeDataPart; +using DataPartPtr = std::shared_ptr; +using DataPartsVector = std::vector; + class MergeTreeTransaction { friend class TransactionLog; @@ -23,10 +27,26 @@ public: MergeTreeTransaction() = delete; MergeTreeTransaction(Snapshot snapshot_, LocalTID local_tid_, UUID host_id); + void addNewPart(const DataPartPtr & new_part); + void removeOldPart(const DataPartPtr & part_to_remove); + + static void addNewPart(const DataPartPtr & new_part, MergeTreeTransaction * txn); + static void removeOldPart(const DataPartPtr & part_to_remove, MergeTreeTransaction * txn); + static void addNewPartAndRemoveCovered(const DataPartPtr & new_part, const DataPartsVector & covered_parts, MergeTreeTransaction * txn); + + bool isReadOnly() const; + private: + void beforeCommit(); + void afterCommit(); + void rollback(); + Snapshot snapshot; State state; + DataPartsVector creating_parts; + DataPartsVector removing_parts; + CSN csn = Tx::UnknownCSN; }; diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 00dbb3352b4..4b4f893f791 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -1,8 +1,15 @@ #include +#include +#include namespace DB { +namespace ErrorCodes +{ +extern const int LOGICAL_ERROR; +} + TransactionLog & TransactionLog::instance() { static TransactionLog inst; @@ -11,27 +18,59 @@ TransactionLog & TransactionLog::instance() TransactionLog::TransactionLog() { + latest_snapshot = 1; csn_counter = 1; local_tid_counter = 1; } Snapshot TransactionLog::getLatestSnapshot() const { - return csn_counter.load(); + return latest_snapshot.load(); } MergeTreeTransactionPtr TransactionLog::beginTransaction() { - Snapshot snapshot = csn_counter.load(); + Snapshot snapshot = latest_snapshot.load(); LocalTID ltid = 1 + local_tid_counter.fetch_add(1); - return std::make_shared(snapshot, ltid, UUIDHelpers::Nil); + auto txn = std::make_shared(snapshot, ltid, UUIDHelpers::Nil); + { + std::lock_guard lock{running_list_mutex}; + bool inserted = running_list.try_emplace(txn->tid.getHash(), txn).second; /// Commit point + if (!inserted) + throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} exists", txn->tid.getHash(), txn->tid); + } + return txn; } CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) { - txn->csn = 1 + csn_counter.fetch_add(1); + txn->beforeCommit(); + + CSN new_csn; /// TODO Transactions: reset local_tid_counter + if (txn->isReadOnly()) + { + new_csn = txn->snapshot; + } + else + { + std::lock_guard lock{commit_mutex}; + new_csn = 1 + csn_counter.fetch_add(1); + bool inserted = tid_to_csn.try_emplace(txn->tid.getHash(), new_csn).second; /// Commit point + if (!inserted) + throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} exists", txn->tid.getHash(), txn->tid); + latest_snapshot.store(new_csn, std::memory_order_relaxed); + } + + txn->csn = new_csn; txn->state = MergeTreeTransaction::COMMITTED; + txn->afterCommit(); + { + std::lock_guard lock{running_list_mutex}; + bool removed = running_list.erase(txn->tid.getHash()); + if (!removed) + throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} doesn't exist", txn->tid.getHash(), txn->tid); + } return txn->csn; } @@ -39,6 +78,40 @@ void TransactionLog::rollbackTransaction(const MergeTreeTransactionPtr & txn) { txn->csn = Tx::RolledBackCSN; txn->state = MergeTreeTransaction::ROLLED_BACK; + { + std::lock_guard lock{running_list_mutex}; + bool removed = running_list.erase(txn->tid.getHash()); + if (!removed) + throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} doesn't exist", txn->tid.getHash(), txn->tid); + } +} + +MergeTreeTransactionPtr TransactionLog::tryGetRunningTransaction(const TIDHash & tid) +{ + std::lock_guard lock{running_list_mutex}; + auto it = running_list.find(tid); + if (it == running_list.end()) + return nullptr; + return it->second; +} + +CSN TransactionLog::getCSN(const TransactionID & tid) const +{ + return getCSN(tid.getHash()); +} + +CSN TransactionLog::getCSN(const TIDHash & tid) const +{ + assert(tid); + assert(tid != Tx::EmptyTID.getHash()); + if (tid == Tx::PrehistoricTID.getHash()) + return Tx::PrehistoricCSN; + + std::lock_guard lock{commit_mutex}; + auto it = tid_to_csn.find(tid); + if (it == tid_to_csn.end()) + return Tx::UnknownCSN; + return it->second; } } diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index 073d2a2929d..8766ab24251 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -2,6 +2,7 @@ #include #include #include +#include namespace DB { @@ -22,9 +23,22 @@ public: void rollbackTransaction(const MergeTreeTransactionPtr & txn); + CSN getCSN(const TransactionID & tid) const; + CSN getCSN(const TIDHash & tid) const; + + MergeTreeTransactionPtr tryGetRunningTransaction(const TIDHash & tid); + private: + std::atomic latest_snapshot; std::atomic csn_counter; std::atomic local_tid_counter; + + /// FIXME Transactions: it's probably a bad idea to use global mutex here + mutable std::mutex commit_mutex; + std::unordered_map tid_to_csn; + + mutable std::mutex running_list_mutex; + std::unordered_map running_list; }; } diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index c990bdcc65b..7b481a3719e 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -44,6 +44,7 @@ class IMergeTreeReader; class IMergeTreeDataPartWriter; class MarkCache; class UncompressedCache; +class MergeTreeTransaction; namespace ErrorCodes @@ -302,17 +303,6 @@ public: CompressionCodecPtr default_codec; - struct VersionMetadata - { - TransactionID mintid = Tx::EmptyTID; - TransactionID maxtid = Tx::EmptyTID; - - bool maybe_visible = false; - - CSN mincsn = Tx::UnknownCSN; - CSN maxcsn = Tx::UnknownCSN; - }; - mutable VersionMetadata versions; /// For data in RAM ('index') diff --git a/src/Storages/MergeTree/MergeTreeBlockOutputStream.cpp b/src/Storages/MergeTree/MergeTreeBlockOutputStream.cpp index bb5644567ae..81b67021651 100644 --- a/src/Storages/MergeTree/MergeTreeBlockOutputStream.cpp +++ b/src/Storages/MergeTree/MergeTreeBlockOutputStream.cpp @@ -35,7 +35,7 @@ void MergeTreeBlockOutputStream::write(const Block & block) if (!part) continue; - storage.renameTempPartAndAdd(part, &storage.increment); + storage.renameTempPartAndAdd(part, context.getCurrentTransaction().get(), &storage.increment); PartLog::addNewPart(storage.global_context, part, watch.elapsed()); diff --git a/src/Storages/MergeTree/MergeTreeBlockOutputStream.h b/src/Storages/MergeTree/MergeTreeBlockOutputStream.h index 8caa53382dc..0000696a2d4 100644 --- a/src/Storages/MergeTree/MergeTreeBlockOutputStream.h +++ b/src/Storages/MergeTree/MergeTreeBlockOutputStream.h @@ -14,11 +14,17 @@ class StorageMergeTree; class MergeTreeBlockOutputStream : public IBlockOutputStream { public: - MergeTreeBlockOutputStream(StorageMergeTree & storage_, const StorageMetadataPtr metadata_snapshot_, size_t max_parts_per_block_, bool optimize_on_insert_) + MergeTreeBlockOutputStream( + StorageMergeTree & storage_, + const StorageMetadataPtr metadata_snapshot_, + size_t max_parts_per_block_, + bool optimize_on_insert_, + const Context & context_) : storage(storage_) , metadata_snapshot(metadata_snapshot_) , max_parts_per_block(max_parts_per_block_) , optimize_on_insert(optimize_on_insert_) + , context(context_) { } @@ -31,6 +37,7 @@ private: StorageMetadataPtr metadata_snapshot; size_t max_parts_per_block; bool optimize_on_insert; + const Context & context; }; } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 7610239662e..b452f2a7c45 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -979,6 +980,16 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) part->renameToDetached(""); + for (auto it = data_parts_by_state_and_info.begin(); it != data_parts_by_state_and_info.end(); ++it) + { + /// We do not have version metadata and transactions history for old parts, + /// so let's consider that such parts were created by some ancient transaction + /// and were committed with some prehistoric CSN. + /// TODO Transactions: distinguish "prehistoric" parts from uncommitted parts in case of hard restart + (*it)->versions.setMinTID(Tx::PrehistoricTID); + (*it)->versions.mincsn.store(Tx::PrehistoricCSN, std::memory_order_relaxed); + } + /// Delete from the set of current parts those parts that are covered by another part (those parts that /// were merged), but that for some reason are still not deleted from the filesystem. /// Deletion of files will be performed later in the clearOldParts() method. @@ -994,7 +1005,7 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) (*it)->remove_time.store((*it)->modification_time, std::memory_order_relaxed); modifyPartState(it, DataPartState::Outdated); (*it)->versions.maxtid = Tx::PrehistoricTID; - (*it)->versions.maxcsn = Tx::PrehistoricCSN; + (*it)->versions.maxcsn.store(Tx::PrehistoricCSN, std::memory_order_relaxed); removePartContributionToDataVolume(*it); }; @@ -1002,14 +1013,6 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) while (curr_jt != data_parts_by_state_and_info.end() && (*curr_jt)->getState() == DataPartState::Committed) { - /// We do not have version metadata and transactions history for old parts, - /// so let's consider that such parts were created by some ancient transaction - /// and were committed with some prehistoric CSN. - /// TODO Transactions: distinguish "prehistoric" parts from uncommitted parts in case of hard restart - (*curr_jt)->versions.mintid = Tx::PrehistoricTID; - (*curr_jt)->versions.mincsn = Tx::PrehistoricCSN; - (*curr_jt)->versions.maybe_visible = true; - /// Don't consider data parts belonging to different partitions. if ((*curr_jt)->info.partition_id != (*prev_jt)->info.partition_id) { @@ -2031,7 +2034,7 @@ MergeTreeData::DataPartsVector MergeTreeData::getActivePartsToReplace( } -bool MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr & part, SimpleIncrement * increment, Transaction * out_transaction) +bool MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr & part, MergeTreeTransaction * txn, SimpleIncrement * increment, Transaction * out_transaction) { if (out_transaction && &out_transaction->data != this) throw Exception("MergeTreeData::Transaction for one table cannot be used with another. It is a bug.", @@ -2040,7 +2043,7 @@ bool MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr & part, SimpleIncrem DataPartsVector covered_parts; { auto lock = lockParts(); - if (!renameTempPartAndReplace(part, increment, out_transaction, lock, &covered_parts)) + if (!renameTempPartAndReplace(part, txn, increment, out_transaction, lock, &covered_parts)) return false; } if (!covered_parts.empty()) @@ -2052,7 +2055,7 @@ bool MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr & part, SimpleIncrem bool MergeTreeData::renameTempPartAndReplace( - MutableDataPartPtr & part, SimpleIncrement * increment, Transaction * out_transaction, + MutableDataPartPtr & part, MergeTreeTransaction * txn, SimpleIncrement * increment, Transaction * out_transaction, std::unique_lock & lock, DataPartsVector * out_covered_parts) { if (out_transaction && &out_transaction->data != this) @@ -2100,7 +2103,6 @@ bool MergeTreeData::renameTempPartAndReplace( DataPartPtr covering_part; DataPartsVector covered_parts = getActivePartsToReplace(part_info, part_name, covering_part, lock); - DataPartsVector covered_parts_in_memory; if (covering_part) { @@ -2108,6 +2110,10 @@ bool MergeTreeData::renameTempPartAndReplace( return false; } + /// FIXME Transactions: it's not the best place for checking and setting maxtid, + /// because it's too optimistic. We should lock maxtid of covered parts at the beginning of operation. + MergeTreeTransaction::addNewPartAndRemoveCovered(part, covered_parts, txn); + /// All checks are passed. Now we can rename the part on disk. /// So, we maintain invariant: if a non-temporary part in filesystem then it is in data_parts /// @@ -2164,7 +2170,7 @@ bool MergeTreeData::renameTempPartAndReplace( } MergeTreeData::DataPartsVector MergeTreeData::renameTempPartAndReplace( - MutableDataPartPtr & part, SimpleIncrement * increment, Transaction * out_transaction) + MutableDataPartPtr & part, MergeTreeTransaction * txn, SimpleIncrement * increment, Transaction * out_transaction) { if (out_transaction && &out_transaction->data != this) throw Exception("MergeTreeData::Transaction for one table cannot be used with another. It is a bug.", @@ -2173,7 +2179,7 @@ MergeTreeData::DataPartsVector MergeTreeData::renameTempPartAndReplace( DataPartsVector covered_parts; { auto lock = lockParts(); - renameTempPartAndReplace(part, increment, out_transaction, lock, &covered_parts); + renameTempPartAndReplace(part, txn, increment, out_transaction, lock, &covered_parts); } return covered_parts; } @@ -3072,6 +3078,40 @@ String MergeTreeData::getPartitionIDFromQuery(const ASTPtr & ast, const Context return partition_id; } +DataPartsVector MergeTreeData::getDataPartsVector(const Context & context) const +{ + if (auto txn = context.getCurrentTransaction()) + return getVisibleDataPartsVector(*txn); + else + return getDataPartsVector(); +} + +MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVector(const MergeTreeTransaction & txn) const +{ + DataPartsVector maybe_visible_parts = getDataPartsVector({DataPartState::PreCommitted, DataPartState::Committed, DataPartState::Outdated}); + if (maybe_visible_parts.empty()) + return maybe_visible_parts; + + auto it = maybe_visible_parts.begin(); + auto it_last = maybe_visible_parts.end() - 1; + while (it <= it_last) + { + if ((*it)->versions.isVisible(txn)) + { + ++it; + } + else + { + std::swap(*it, *it_last); + --it_last; + } + } + + size_t new_size = it_last - maybe_visible_parts.begin() + 1; + maybe_visible_parts.resize(new_size); + return maybe_visible_parts; +} + MergeTreeData::DataPartsVector MergeTreeData::getDataPartsVector(const DataPartStates & affordable_states, DataPartStateVector * out_states) const { DataPartsVector res; diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 63d776a838c..d9e9bb9bcec 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -40,6 +40,7 @@ class MergeTreePartsMover; class MutationCommands; class Context; struct JobAndPool; +class MergeTreeTransaction; /// Auxiliary struct holding information about the future merged or mutated part. struct EmergingPartInfo @@ -406,6 +407,9 @@ public: DataParts getDataParts() const; DataPartsVector getDataPartsVector() const; + DataPartsVector getDataPartsVector(const Context & context) const; + DataPartsVector getVisibleDataPartsVector(const MergeTreeTransaction & txn) const; + /// Returns a committed part with the given name or a part containing it. If there is no such part, returns nullptr. DataPartPtr getActiveContainingPart(const String & part_name) const; DataPartPtr getActiveContainingPart(const MergeTreePartInfo & part_info) const; @@ -447,17 +451,19 @@ public: /// active set later with out_transaction->commit()). /// Else, commits the part immediately. /// Returns true if part was added. Returns false if part is covered by bigger part. - bool renameTempPartAndAdd(MutableDataPartPtr & part, SimpleIncrement * increment = nullptr, Transaction * out_transaction = nullptr); + bool renameTempPartAndAdd(MutableDataPartPtr & part, MergeTreeTransaction * txn, + SimpleIncrement * increment = nullptr, Transaction * out_transaction = nullptr); /// The same as renameTempPartAndAdd but the block range of the part can contain existing parts. /// Returns all parts covered by the added part (in ascending order). /// If out_transaction == nullptr, marks covered parts as Outdated. DataPartsVector renameTempPartAndReplace( - MutableDataPartPtr & part, SimpleIncrement * increment = nullptr, Transaction * out_transaction = nullptr); + MutableDataPartPtr & part, MergeTreeTransaction * txn, SimpleIncrement * increment = nullptr, Transaction * out_transaction = nullptr); /// Low-level version of previous one, doesn't lock mutex bool renameTempPartAndReplace( - MutableDataPartPtr & part, SimpleIncrement * increment, Transaction * out_transaction, DataPartsLock & lock, + MutableDataPartPtr & part, MergeTreeTransaction * txn, + SimpleIncrement * increment, Transaction * out_transaction, DataPartsLock & lock, DataPartsVector * out_covered_parts = nullptr); diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 4269aa89ad1..7209053400f 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -1337,7 +1337,7 @@ MergeTreeData::DataPartPtr MergeTreeDataMergerMutator::renameMergedTemporaryPart MergeTreeData::Transaction * out_transaction) { /// Rename new part, add to the set and remove original parts. - auto replaced_parts = data.renameTempPartAndReplace(new_data_part, nullptr, out_transaction); + auto replaced_parts = data.renameTempPartAndReplace(new_data_part, nullptr, nullptr, out_transaction); /// Let's check that all original parts have been deleted and only them. if (replaced_parts.size() != parts.size()) diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index f3759107912..f62601b9924 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -158,7 +158,7 @@ QueryPlanPtr MergeTreeDataSelectExecutor::read( const PartitionIdToMaxBlock * max_block_numbers_to_read) const { return readFromParts( - data.getDataPartsVector(), column_names_to_return, metadata_snapshot, + data.getDataPartsVector(context), column_names_to_return, metadata_snapshot, query_info, context, max_block_size, num_streams, max_block_numbers_to_read); } diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp index 529e3d2ab49..ed1d5c09b35 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp @@ -380,7 +380,7 @@ void ReplicatedMergeTreeBlockOutputStream::commitPart( bool renamed = false; try { - renamed = storage.renameTempPartAndAdd(part, nullptr, &transaction); + renamed = storage.renameTempPartAndAdd(part, nullptr, nullptr, &transaction); } catch (const Exception & e) { diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 10790057ac9..fbfc5b54897 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -226,7 +226,7 @@ BlockOutputStreamPtr StorageMergeTree::write(const ASTPtr & /*query*/, const Sto const auto & settings = context.getSettingsRef(); return std::make_shared( - *this, metadata_snapshot, settings.max_partitions_per_insert_block, context.getSettingsRef().optimize_on_insert); + *this, metadata_snapshot, settings.max_partitions_per_insert_block, context.getSettingsRef().optimize_on_insert, context); } void StorageMergeTree::checkTableCanBeDropped() const @@ -933,7 +933,7 @@ bool StorageMergeTree::mutateSelectedPart(const StorageMetadataPtr & metadata_sn future_part, metadata_snapshot, merge_mutate_entry.commands, *(merge_list_entry), time(nullptr), global_context, merge_mutate_entry.tagger->reserved_space, table_lock_holder); - renameTempPartAndReplace(new_part); + renameTempPartAndReplace(new_part, nullptr); updateMutationEntriesErrors(future_part, true, ""); write_part_log({}); @@ -1231,7 +1231,7 @@ PartitionCommandsResultInfo StorageMergeTree::attachPartition( { LOG_INFO(log, "Attaching part {} from {}", loaded_parts[i]->name, renamed_parts.old_and_new_names[i].second); String old_name = renamed_parts.old_and_new_names[i].first; - renameTempPartAndAdd(loaded_parts[i], &increment); + renameTempPartAndAdd(loaded_parts[i], context.getCurrentTransaction().get(), &increment); renamed_parts.old_and_new_names[i].first.clear(); results.push_back(PartitionCommandResultInfo{ @@ -1303,7 +1303,7 @@ void StorageMergeTree::replacePartitionFrom(const StoragePtr & source_table, con /// Populate transaction for (MutableDataPartPtr & part : dst_parts) - renameTempPartAndReplace(part, &increment, &transaction, data_parts_lock); + renameTempPartAndReplace(part, context.getCurrentTransaction().get(), &increment, &transaction, data_parts_lock); transaction.commit(&data_parts_lock); @@ -1379,7 +1379,7 @@ void StorageMergeTree::movePartitionToTable(const StoragePtr & dest_table, const DataPartsLock lock(mutex); for (MutableDataPartPtr & part : dst_parts) - dest_table_storage->renameTempPartAndReplace(part, &dest_table_storage->increment, &transaction, lock); + dest_table_storage->renameTempPartAndReplace(part, context.getCurrentTransaction().get(), &dest_table_storage->increment, &transaction, lock); removePartsFromWorkingSet(src_parts, true, lock); transaction.commit(&lock); diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index f9d63132a1b..ff97ffc1411 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -1442,7 +1442,7 @@ bool StorageReplicatedMergeTree::executeLogEntry(LogEntry & entry) Transaction transaction(*this); - renameTempPartAndReplace(part, nullptr, &transaction); + renameTempPartAndReplace(part, nullptr, nullptr, &transaction); checkPartChecksumsAndCommit(transaction, part); writePartLog(PartLogElement::Type::NEW_PART, {}, 0 /** log entry is fake so we don't measure the time */, @@ -1810,7 +1810,7 @@ bool StorageReplicatedMergeTree::tryExecutePartMutation(const StorageReplicatedM new_part = merger_mutator.mutatePartToTemporaryPart( future_mutated_part, metadata_snapshot, commands, *merge_entry, entry.create_time, global_context, reserved_space, table_lock); - renameTempPartAndReplace(new_part, nullptr, &transaction); + renameTempPartAndReplace(new_part, nullptr, nullptr, &transaction); try { @@ -2433,7 +2433,7 @@ bool StorageReplicatedMergeTree::executeReplaceRange(const LogEntry & entry) Coordination::Requests ops; for (PartDescriptionPtr & part_desc : final_parts) { - renameTempPartAndReplace(part_desc->res_part, nullptr, &transaction); + renameTempPartAndReplace(part_desc->res_part, nullptr, nullptr, &transaction); getCommitPartOps(ops, part_desc->res_part); if (ops.size() > zkutil::MULTI_BATCH_SIZE) @@ -3802,7 +3802,7 @@ bool StorageReplicatedMergeTree::fetchPart(const String & part_name, const Stora if (!to_detached) { Transaction transaction(*this); - renameTempPartAndReplace(part, nullptr, &transaction); + renameTempPartAndReplace(part, nullptr, nullptr, &transaction); replaced_parts = checkPartChecksumsAndCommit(transaction, part); @@ -6131,7 +6131,7 @@ void StorageReplicatedMergeTree::replacePartitionFrom( auto data_parts_lock = lockParts(); for (MutableDataPartPtr & part : dst_parts) - renameTempPartAndReplace(part, nullptr, &transaction, data_parts_lock); + renameTempPartAndReplace(part, context.getCurrentTransaction().get(), nullptr, &transaction, data_parts_lock); } op_results = zookeeper->multi(ops); @@ -6322,7 +6322,7 @@ void StorageReplicatedMergeTree::movePartitionToTable(const StoragePtr & dest_ta DataPartsLock lock(mutex); for (MutableDataPartPtr & part : dst_parts) - dest_table_storage->renameTempPartAndReplace(part, nullptr, &transaction, lock); + dest_table_storage->renameTempPartAndReplace(part, query_context.getCurrentTransaction().get(), nullptr, &transaction, lock); op_results = zookeeper->multi(ops); diff --git a/src/Storages/System/StorageSystemParts.cpp b/src/Storages/System/StorageSystemParts.cpp index f100ecdfa9b..030226d86dc 100644 --- a/src/Storages/System/StorageSystemParts.cpp +++ b/src/Storages/System/StorageSystemParts.cpp @@ -272,11 +272,11 @@ void StorageSystemParts::processNextStorage( if (columns_mask[src_index++]) columns[res_index++]->insert(get_tid_as_field(part->versions.mintid)); if (columns_mask[src_index++]) - columns[res_index++]->insert(get_tid_as_field(part->versions.maxtid)); + columns[res_index++]->insert(get_tid_as_field(part->versions.getMaxTID())); if (columns_mask[src_index++]) - columns[res_index++]->insert(part->versions.mincsn); + columns[res_index++]->insert(part->versions.mincsn.load(std::memory_order_relaxed)); if (columns_mask[src_index++]) - columns[res_index++]->insert(part->versions.maxcsn); + columns[res_index++]->insert(part->versions.maxcsn.load(std::memory_order_relaxed)); } } diff --git a/tests/queries/0_stateless/01174_select_insert_isolation.reference b/tests/queries/0_stateless/01174_select_insert_isolation.reference new file mode 100644 index 00000000000..51d57dfa0d6 --- /dev/null +++ b/tests/queries/0_stateless/01174_select_insert_isolation.reference @@ -0,0 +1 @@ +200 0 100 100 0 diff --git a/tests/queries/0_stateless/01174_select_insert_isolation.sh b/tests/queries/0_stateless/01174_select_insert_isolation.sh new file mode 100755 index 00000000000..23fa9b4da32 --- /dev/null +++ b/tests/queries/0_stateless/01174_select_insert_isolation.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +set -e + +$CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS mt"; +$CLICKHOUSE_CLIENT --query "CREATE TABLE mt (n Int8, m Int8) ENGINE=MergeTree ORDER BY n PARTITION BY 0 < n"; +$CLICKHOUSE_CLIENT --query "SYSTEM STOP MERGES mt"; #FIXME + +function thread_insert_commit() +{ + for i in {1..50}; do + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + INSERT INTO mt VALUES ($i, $1); + INSERT INTO mt VALUES (-$i, $1); + COMMIT;"; + done +} + +function thread_insert_rollback() +{ + for _ in {1..50}; do + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + INSERT INTO mt VALUES (42, $1); + ROLLBACK;"; + done +} + +function thread_select() +{ + trap "exit 0" INT + while true; do + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + SELECT arraySort(groupArray(n)), arraySort(groupArray(m)) FROM mt; + SELECT throwIf((SELECT sum(n) FROM mt) != 0) FORMAT Null; + SELECT throwIf((SELECT count() FROM mt) % 2 != 0) FORMAT Null; + SELECT arraySort(groupArray(n)), arraySort(groupArray(m)) FROM mt; + COMMIT;" | uniq | wc -l | grep -v "^1$" ||:; # Must be 1 if the first and the last queries got the same result + done +} + +thread_insert_commit 1 & PID_1=$! +thread_insert_commit 2 & PID_2=$! +thread_insert_rollback 3 & PID_3=$! +thread_select & PID_4=$! +wait $PID_1 && wait $PID_2 && wait $PID_3 +kill -INT $PID_4 +wait + +$CLICKHOUSE_CLIENT --multiquery --query " +BEGIN TRANSACTION; +SELECT count(), sum(n), sum(m=1), sum(m=2), sum(m=3) FROM mt;"; + +$CLICKHOUSE_CLIENT --query "DROP TABLE mt"; From f270fa1843ef20fdc2bc16ab068d5b0a838067bf Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 9 Apr 2021 15:53:51 +0300 Subject: [PATCH 0004/1647] rollback on exception --- src/Interpreters/Context.cpp | 6 ++ src/Interpreters/Context.h | 6 +- .../InterpreterTransactionControlQuery.cpp | 2 +- src/Interpreters/MergeTreeTransaction.cpp | 32 ++++++-- src/Interpreters/MergeTreeTransaction.h | 14 ++-- .../MergeTreeTransactionHolder.cpp | 70 ++++++++++++++++++ src/Interpreters/MergeTreeTransactionHolder.h | 32 ++++++++ src/Interpreters/TransactionLog.cpp | 14 ++-- src/Interpreters/TransactionLog.h | 2 +- src/Interpreters/executeQuery.cpp | 8 +- .../01172_transaction_counters.reference | 10 +++ .../01172_transaction_counters.sql | 22 ++++++ ...1173_transaction_control_queries.reference | 9 +++ .../01173_transaction_control_queries.sql | 74 +++++++++++++++++++ 14 files changed, 276 insertions(+), 25 deletions(-) create mode 100644 src/Interpreters/MergeTreeTransactionHolder.cpp create mode 100644 src/Interpreters/MergeTreeTransactionHolder.h create mode 100644 tests/queries/0_stateless/01172_transaction_counters.reference create mode 100644 tests/queries/0_stateless/01172_transaction_counters.sql create mode 100644 tests/queries/0_stateless/01173_transaction_control_queries.reference create mode 100644 tests/queries/0_stateless/01173_transaction_control_queries.sql diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 5c6afb38ff4..933d613f676 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2581,6 +2581,12 @@ ZooKeeperMetadataTransactionPtr Context::getZooKeeperMetadataTransaction() const return metadata_transaction; } +void Context::initCurrentTransaction(MergeTreeTransactionPtr txn) +{ + merge_tree_transaction_holder = MergeTreeTransactionHolder(txn, false); + setCurrentTransaction(std::move(txn)); +} + void Context::setCurrentTransaction(MergeTreeTransactionPtr txn) { assert(!merge_tree_transaction || !txn); diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index e035031f993..3e621b61225 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -23,6 +23,7 @@ #include #include #include +#include #if !defined(ARCADIA_BUILD) # include "config_core.h" @@ -122,8 +123,6 @@ struct BackgroundTaskSchedulingSettings; class ZooKeeperMetadataTransaction; using ZooKeeperMetadataTransactionPtr = std::shared_ptr; -class MergeTreeTransaction; -using MergeTreeTransactionPtr = std::shared_ptr; #if USE_EMBEDDED_COMPILER class CompiledExpressionCache; @@ -293,6 +292,8 @@ private: /// And I hope it will be replaced with more common Transaction sometime. MergeTreeTransactionPtr merge_tree_transaction; /// Current transaction context. Can be inside session or query context. + /// It's shared with all children contexts. + MergeTreeTransactionHolder merge_tree_transaction_holder; /// It will rollback or commit transaction on Context destruction. /// Use copy constructor or createGlobal() instead Context(); @@ -761,6 +762,7 @@ public: /// Returns context of current distributed DDL query or nullptr. ZooKeeperMetadataTransactionPtr getZooKeeperMetadataTransaction() const; + void initCurrentTransaction(MergeTreeTransactionPtr txn); void setCurrentTransaction(MergeTreeTransactionPtr txn); MergeTreeTransactionPtr getCurrentTransaction() const; diff --git a/src/Interpreters/InterpreterTransactionControlQuery.cpp b/src/Interpreters/InterpreterTransactionControlQuery.cpp index 369f5edd1da..8038d53e118 100644 --- a/src/Interpreters/InterpreterTransactionControlQuery.cpp +++ b/src/Interpreters/InterpreterTransactionControlQuery.cpp @@ -37,7 +37,7 @@ BlockIO InterpreterTransactionControlQuery::executeBegin(Context & context) throw Exception(ErrorCodes::INVALID_TRANSACTION, "Nested transactions are not supported"); auto txn = TransactionLog::instance().beginTransaction(); - context.setCurrentTransaction(txn); + context.initCurrentTransaction(txn); query_context.setCurrentTransaction(txn); return {}; } diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index 5b267e45f83..2f8445530da 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace DB { @@ -8,10 +9,19 @@ namespace DB MergeTreeTransaction::MergeTreeTransaction(Snapshot snapshot_, LocalTID local_tid_, UUID host_id) : tid({snapshot_, local_tid_, host_id}) , snapshot(snapshot_) - , state(RUNNING) + , csn(Tx::UnknownCSN) { } +MergeTreeTransaction::State MergeTreeTransaction::getState() const +{ + if (csn == Tx::UnknownCSN) + return RUNNING; + if (csn == Tx::RolledBackCSN) + return ROLLED_BACK; + return COMMITTED; +} + void MergeTreeTransaction::addNewPart(const DataPartPtr & new_part, MergeTreeTransaction * txn) { TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; @@ -69,25 +79,35 @@ bool MergeTreeTransaction::isReadOnly() const void MergeTreeTransaction::beforeCommit() { - assert(state == RUNNING); + assert(csn == Tx::UnknownCSN); } -void MergeTreeTransaction::afterCommit() +void MergeTreeTransaction::afterCommit(CSN assigned_csn) noexcept { - assert(state == COMMITTED); + assert(csn == Tx::UnknownCSN); + csn = assigned_csn; for (const auto & part : creating_parts) part->versions.mincsn.store(csn); for (const auto & part : removing_parts) part->versions.maxcsn.store(csn); } -void MergeTreeTransaction::rollback() +void MergeTreeTransaction::rollback() noexcept { - assert(state == RUNNING); + assert(csn == Tx::UnknownCSN); + csn = Tx::RolledBackCSN; for (const auto & part : creating_parts) part->versions.mincsn.store(Tx::RolledBackCSN); for (const auto & part : removing_parts) part->versions.unlockMaxTID(tid); } +void MergeTreeTransaction::onException() +{ + if (csn) + return; + + TransactionLog::instance().rollbackTransaction(shared_from_this()); +} + } diff --git a/src/Interpreters/MergeTreeTransaction.h b/src/Interpreters/MergeTreeTransaction.h index c085fa96b0f..c9fad85f612 100644 --- a/src/Interpreters/MergeTreeTransaction.h +++ b/src/Interpreters/MergeTreeTransaction.h @@ -1,5 +1,6 @@ #pragma once #include +#include namespace DB { @@ -8,7 +9,7 @@ class IMergeTreeDataPart; using DataPartPtr = std::shared_ptr; using DataPartsVector = std::vector; -class MergeTreeTransaction +class MergeTreeTransaction : public std::enable_shared_from_this, private boost::noncopyable { friend class TransactionLog; public: @@ -20,7 +21,7 @@ public: }; Snapshot getSnapshot() const { return snapshot; } - State getState() const { return state; } + State getState() const; const TransactionID tid; @@ -36,18 +37,19 @@ public: bool isReadOnly() const; + void onException(); + private: void beforeCommit(); - void afterCommit(); - void rollback(); + void afterCommit(CSN assigned_csn) noexcept; + void rollback() noexcept; Snapshot snapshot; - State state; DataPartsVector creating_parts; DataPartsVector removing_parts; - CSN csn = Tx::UnknownCSN; + CSN csn; }; using MergeTreeTransactionPtr = std::shared_ptr; diff --git a/src/Interpreters/MergeTreeTransactionHolder.cpp b/src/Interpreters/MergeTreeTransactionHolder.cpp new file mode 100644 index 00000000000..1de0d9c75c4 --- /dev/null +++ b/src/Interpreters/MergeTreeTransactionHolder.cpp @@ -0,0 +1,70 @@ +#include +#include +#include + +namespace DB +{ + +MergeTreeTransactionHolder::MergeTreeTransactionHolder(const MergeTreeTransactionPtr & txn_, bool autocommit_ = false) + : txn(txn_) + , autocommit(autocommit_) +{ + assert(!txn || txn->getState() == MergeTreeTransaction::RUNNING); +} + +MergeTreeTransactionHolder::MergeTreeTransactionHolder(MergeTreeTransactionHolder && rhs) noexcept + : txn(std::move(rhs.txn)) + , autocommit(rhs.autocommit) +{ + rhs.txn = {}; +} + +MergeTreeTransactionHolder & MergeTreeTransactionHolder::operator=(MergeTreeTransactionHolder && rhs) noexcept +{ + onDestroy(); + txn = std::move(rhs.txn); + autocommit = rhs.autocommit; + rhs.txn = {}; + return *this; +} + +MergeTreeTransactionHolder::~MergeTreeTransactionHolder() +{ + onDestroy(); +} + +void MergeTreeTransactionHolder::onDestroy() noexcept +{ + if (!txn) + return; + if (txn->getState() != MergeTreeTransaction::RUNNING) + return; + + if (autocommit) + { + try + { + TransactionLog::instance().commitTransaction(txn); + } + catch (...) + { + tryLogCurrentException(__PRETTY_FUNCTION__); + } + } else + { + TransactionLog::instance().rollbackTransaction(txn); + } +} + +MergeTreeTransactionHolder::MergeTreeTransactionHolder(const MergeTreeTransactionHolder &) +{ + txn = nullptr; +} + +MergeTreeTransactionHolder & MergeTreeTransactionHolder::operator=(const MergeTreeTransactionHolder &) +{ + txn = nullptr; + return *this; +} + +} diff --git a/src/Interpreters/MergeTreeTransactionHolder.h b/src/Interpreters/MergeTreeTransactionHolder.h new file mode 100644 index 00000000000..6172beb2a69 --- /dev/null +++ b/src/Interpreters/MergeTreeTransactionHolder.h @@ -0,0 +1,32 @@ +#pragma once +#include + +namespace DB +{ + +class MergeTreeTransaction; +using MergeTreeTransactionPtr = std::shared_ptr; + +class MergeTreeTransactionHolder +{ +public: + MergeTreeTransactionHolder() = default; + MergeTreeTransactionHolder(const MergeTreeTransactionPtr & txn_, bool autocommit_); + MergeTreeTransactionHolder(MergeTreeTransactionHolder && rhs) noexcept; + MergeTreeTransactionHolder & operator=(MergeTreeTransactionHolder && rhs) noexcept; + ~MergeTreeTransactionHolder(); + + /// NOTE: We cannot make it noncopyable, because we use it as a filed of Context. + /// So the following copy constructor and operator does not copy anything, + /// they just leave txn nullptr. + MergeTreeTransactionHolder(const MergeTreeTransactionHolder & rhs); + MergeTreeTransactionHolder & operator=(const MergeTreeTransactionHolder & rhs); + +private: + void onDestroy() noexcept; + + MergeTreeTransactionPtr txn; + bool autocommit = false; +}; + +} diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 4b4f893f791..dac5f48abd4 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -62,27 +62,25 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) latest_snapshot.store(new_csn, std::memory_order_relaxed); } - txn->csn = new_csn; - txn->state = MergeTreeTransaction::COMMITTED; - txn->afterCommit(); + txn->afterCommit(new_csn); + { std::lock_guard lock{running_list_mutex}; bool removed = running_list.erase(txn->tid.getHash()); if (!removed) throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} doesn't exist", txn->tid.getHash(), txn->tid); } - return txn->csn; + return new_csn; } -void TransactionLog::rollbackTransaction(const MergeTreeTransactionPtr & txn) +void TransactionLog::rollbackTransaction(const MergeTreeTransactionPtr & txn) noexcept { - txn->csn = Tx::RolledBackCSN; - txn->state = MergeTreeTransaction::ROLLED_BACK; + txn->rollback(); { std::lock_guard lock{running_list_mutex}; bool removed = running_list.erase(txn->tid.getHash()); if (!removed) - throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} doesn't exist", txn->tid.getHash(), txn->tid); + abort(); } } diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index 8766ab24251..616cf62f242 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -21,7 +21,7 @@ public: CSN commitTransaction(const MergeTreeTransactionPtr & txn); - void rollbackTransaction(const MergeTreeTransactionPtr & txn); + void rollbackTransaction(const MergeTreeTransactionPtr & txn) noexcept; CSN getCSN(const TransactionID & tid) const; CSN getCSN(const TIDHash & tid) const; diff --git a/src/Interpreters/executeQuery.cpp b/src/Interpreters/executeQuery.cpp index a00f8301622..819c17af3ae 100644 --- a/src/Interpreters/executeQuery.cpp +++ b/src/Interpreters/executeQuery.cpp @@ -50,7 +50,7 @@ #include #include #include -#include +#include #include #include @@ -825,6 +825,9 @@ static std::tuple executeQueryImpl( log_queries_min_query_duration_ms = settings.log_queries_min_query_duration_ms.totalMilliseconds(), quota(quota), status_info_to_query_log] () mutable { + if (auto txn = context.getCurrentTransaction()) + txn->onException(); + if (quota) quota->used(Quota::ERRORS, 1, /* check_exceeded = */ false); @@ -888,6 +891,9 @@ static std::tuple executeQueryImpl( } catch (...) { + if (auto txn = context.getCurrentTransaction()) + txn->onException(); + if (!internal) { if (query_for_logging.empty()) diff --git a/tests/queries/0_stateless/01172_transaction_counters.reference b/tests/queries/0_stateless/01172_transaction_counters.reference new file mode 100644 index 00000000000..375f3540af9 --- /dev/null +++ b/tests/queries/0_stateless/01172_transaction_counters.reference @@ -0,0 +1,10 @@ +(0,0,'00000000-0000-0000-0000-000000000000') +all_1_1_0 0 +all_2_2_0 1 +all_1_1_0 1 (0,0,'00000000-0000-0000-0000-000000000000') 0 +all_2_2_0 0 (0,0,'00000000-0000-0000-0000-000000000000') 0 +all_1_1_0 0 +all_3_3_0 1 +all_1_1_0 1 (0,0,'00000000-0000-0000-0000-000000000000') 0 +all_2_2_0 18446744073709551615 (0,0,'00000000-0000-0000-0000-000000000000') 0 +all_3_3_0 0 (0,0,'00000000-0000-0000-0000-000000000000') 0 diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql new file mode 100644 index 00000000000..db7aa639f5d --- /dev/null +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -0,0 +1,22 @@ +drop table if exists txn_counters; + +create table txn_counters (n Int64, mintid DEFAULT transactionID()) engine=MergeTree order by n; + +system stop merges txn_counters; --FIXME + +insert into txn_counters(n) values (1); +select transactionID(); + +begin transaction; +insert into txn_counters(n) values (2); +select system.parts.name, txn_counters.mintid = system.parts.mintid from txn_counters join system.parts on txn_counters._part = system.parts.name where database=currentDatabase() and table='txn_counters' order by system.parts.name; +select name, mincsn, maxtid, maxcsn from system.parts where database=currentDatabase() and table='txn_counters' order by system.parts.name; +rollback; + +begin transaction; +insert into txn_counters(n) values (3); +select system.parts.name, txn_counters.mintid = system.parts.mintid from txn_counters join system.parts on txn_counters._part = system.parts.name where database=currentDatabase() and table='txn_counters' order by system.parts.name; +select name, mincsn, maxtid, maxcsn from system.parts where database=currentDatabase() and table='txn_counters' order by system.parts.name; +commit; + +drop table txn_counters; diff --git a/tests/queries/0_stateless/01173_transaction_control_queries.reference b/tests/queries/0_stateless/01173_transaction_control_queries.reference new file mode 100644 index 00000000000..a9cd56425f1 --- /dev/null +++ b/tests/queries/0_stateless/01173_transaction_control_queries.reference @@ -0,0 +1,9 @@ +commit [1,10] +rollback [1,2,10,20] +no nested [1,10] +on exception before start [1,3,10,30] +on exception while processing [1,4,10,40] +1 +on session close [1,6,10,60] +commit [1,7,10,70] +readonly [1,7,10,70] diff --git a/tests/queries/0_stateless/01173_transaction_control_queries.sql b/tests/queries/0_stateless/01173_transaction_control_queries.sql new file mode 100644 index 00000000000..780259dc158 --- /dev/null +++ b/tests/queries/0_stateless/01173_transaction_control_queries.sql @@ -0,0 +1,74 @@ +drop table if exists mt1; +drop table if exists mt2; + +create table mt1 (n Int64) engine=MergeTree order by n; +create table mt2 (n Int64) engine=MergeTree order by n; +system stop merges mt1; --FIXME +system stop merges mt2; --FIXME + +commit; -- { serverError 581 } +rollback; -- { serverError 581 } + +begin transaction; +insert into mt1 values (1); +insert into mt2 values (10); +select 'commit', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); +commit; + +begin transaction; +insert into mt1 values (2); +insert into mt2 values (20); +select 'rollback', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); +rollback; + +begin transaction; +select 'no nested', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); +begin transaction; -- { serverError 581 } +rollback; + +begin transaction; +insert into mt1 values (3); +insert into mt2 values (30); +select 'on exception before start', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); +-- rollback on exception before start +select functionThatDoesNotExist(); -- { serverError 46 } +-- cannot commit after exception +commit; -- { serverError 581 } +begin transaction; -- { serverError 581 } +rollback; + +begin transaction; +insert into mt1 values (4); +insert into mt2 values (40); +select 'on exception while processing', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); +-- rollback on exception while processing +select throwIf(100 < number) from numbers(1000); -- { serverError 395 } +-- cannot commit after exception +commit; -- { serverError 581 } +-- FIXME Transactions: do not allow queries after exception +insert into mt1 values (5); +insert into mt2 values (50); +select 1; +rollback; + +begin transaction; +insert into mt1 values (6); +insert into mt2 values (60); +select 'on session close', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); +-- trigger reconnection by error on client, check rollback on session close +insert into mt1 values ([1]); -- { clientError 43 } +commit; -- { serverError 581 } +rollback; -- { serverError 581 } + +begin transaction; +insert into mt1 values (7); +insert into mt2 values (70); +select 'commit', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); +commit; + +begin transaction; +select 'readonly', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); +commit; + +drop table mt1; +drop table mt2; From aa8d31dbca64447089fb73f558b9f78a3a8ef82b Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 23 Apr 2021 15:53:38 +0300 Subject: [PATCH 0005/1647] dynamic subcolumns: wip --- src/CMakeLists.txt | 16 +++ src/Columns/ColumnObject.cpp | 117 ++++++++++++++++++ src/Columns/ColumnObject.h | 94 ++++++++++++++ src/Common/CMakeLists.txt | 2 + src/Common/JSONParsers/CMakeLists.txt | 15 +++ .../JSONParsers}/DummyJSONParser.h | 0 .../JSONParsers}/RapidJSONParser.h | 2 +- .../JSONParsers}/SimdJSONParser.h | 2 +- .../JSONParsers/config_jsonparsers.h.in | 6 + src/Common/config.h.in | 3 + src/Core/config_core.h.in | 2 + src/DataTypes/CMakeLists.txt | 2 + src/DataTypes/DataTypeFactory.cpp | 1 + src/DataTypes/DataTypeFactory.h | 1 + src/DataTypes/DataTypeObject.cpp | 63 ++++++++++ src/DataTypes/DataTypeObject.h | 43 +++++++ src/DataTypes/Serializations/CMakeLists.txt | 3 + src/DataTypes/Serializations/JSONDataParser.h | 88 +++++++++++++ .../Serializations/SerializationObject.cpp | 115 +++++++++++++++++ .../Serializations/SerializationObject.h | 29 +++++ .../Serializations/tests/CMakeLists.txt | 0 .../tests/gtest_json_parser.cpp | 33 +++++ src/Functions/FunctionsJSON.h | 6 +- src/IO/ReadHelpers.cpp | 48 +++++++ src/IO/ReadHelpers.h | 3 + 25 files changed, 689 insertions(+), 5 deletions(-) create mode 100644 src/Columns/ColumnObject.cpp create mode 100644 src/Columns/ColumnObject.h create mode 100644 src/Common/JSONParsers/CMakeLists.txt rename src/{Functions => Common/JSONParsers}/DummyJSONParser.h (100%) rename src/{Functions => Common/JSONParsers}/RapidJSONParser.h (99%) rename src/{Functions => Common/JSONParsers}/SimdJSONParser.h (99%) create mode 100644 src/Common/JSONParsers/config_jsonparsers.h.in create mode 100644 src/DataTypes/DataTypeObject.cpp create mode 100644 src/DataTypes/DataTypeObject.h create mode 100644 src/DataTypes/Serializations/CMakeLists.txt create mode 100644 src/DataTypes/Serializations/JSONDataParser.h create mode 100644 src/DataTypes/Serializations/SerializationObject.cpp create mode 100644 src/DataTypes/Serializations/SerializationObject.h create mode 100644 src/DataTypes/Serializations/tests/CMakeLists.txt create mode 100644 src/DataTypes/Serializations/tests/gtest_json_parser.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 43f6ae8fea5..8fa33ace0fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -343,6 +343,14 @@ if(RE2_INCLUDE_DIR) target_include_directories(clickhouse_common_io SYSTEM BEFORE PUBLIC ${RE2_INCLUDE_DIR}) endif() +if(USE_SIMDJSON) + dbms_target_link_libraries(PUBLIC simdjson) +endif() + +if(USE_RAPIDJSON) + dbms_target_include_directories(SYSTEM BEFORE PUBLIC ${RAPIDJSON_INCLUDE_DIR}) +endif() + dbms_target_link_libraries ( PRIVATE boost::filesystem @@ -513,5 +521,13 @@ if (ENABLE_TESTS AND USE_GTEST) target_link_libraries(unit_tests_dbms PRIVATE gcc) endif () + if(USE_SIMDJSON) + target_link_libraries(unit_tests_dbms PRIVATE simdjson) + endif() + + if(USE_RAPIDJSON) + target_include_directories(unit_tests_dbms SYSTEM PRIVATE ${RAPIDJSON_INCLUDE_DIR}) + endif() + add_check(unit_tests_dbms) endif () diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp new file mode 100644 index 00000000000..0c3a89cfbf2 --- /dev/null +++ b/src/Columns/ColumnObject.cpp @@ -0,0 +1,117 @@ +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; + extern const int ILLEGAL_COLUMN; +} + +ColumnObject::ColumnObject(const SubcolumnsMap & subcolumns_) + : subcolumns(subcolumns_) +{ + checkConsistency(); +} + +ColumnObject::ColumnObject(const Names & keys, const Columns & subcolumns_) +{ + if (keys.size() != subcolumns_.size()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of keys ({}) and subcolumns ({}) are inconsistent"); + + for (size_t i = 0; i < keys.size(); ++i) + subcolumns[keys[i]] = subcolumns_[i]; + + checkConsistency(); +} + +void ColumnObject::checkConsistency() const +{ + if (subcolumns.empty()) + return; + + size_t first_size = subcolumns.begin()->second->size(); + for (const auto & [name, column] : subcolumns) + { + if (!column) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Null subcolumn passed to ColumnObject"); + + if (first_size != column->size()) + { + throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of subcolumns are inconsistent in ColumnObject." + " Subcolumn '{}' has {} rows, subcolumn '{}' has {} rows", + subcolumns.begin()->first, first_size, name, column->size()); + } + } +} + +MutableColumnPtr ColumnObject::cloneResized(size_t new_size) const +{ + SubcolumnsMap new_subcolumns; + for (const auto & [key, subcolumn] : subcolumns) + new_subcolumns[key] = subcolumn->cloneResized(new_size); + + return ColumnObject::create(new_subcolumns); +} + +size_t ColumnObject::byteSize() const +{ + size_t res = 0; + for (const auto & [_, column] : subcolumns) + res += column->byteSize(); + return res; +} + +size_t ColumnObject::allocatedBytes() const +{ + size_t res = 0; + for (const auto & [_, column] : subcolumns) + res += column->allocatedBytes(); + return res; +} + +// const ColumnPtr & ColumnObject::tryGetSubcolumn(const String & key) const +// { +// auto it = subcolumns.find(key); +// if (it == subcolumns.end()) +// return nullptr; + +// return it->second; +// } + +const IColumn & ColumnObject::getSubcolumn(const String & key) const +{ + auto it = subcolumns.find(key); + if (it != subcolumns.end()) + return *it->second; + + throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key); +} + +IColumn & ColumnObject::getSubcolumn(const String & key) +{ + auto it = subcolumns.find(key); + if (it != subcolumns.end()) + return *it->second; + + throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key); +} + +bool ColumnObject::hasSubcolumn(const String & key) const +{ + return subcolumns.count(key) != 0; +} + +void ColumnObject::addSubcolumn(const String & key, MutableColumnPtr && subcolumn, bool check_size) +{ + std::cerr << "adding subcolumn: " << key << ", subcolumn: " << subcolumn->dumpStructure() << "\n"; + if (check_size && subcolumn->size() != size()) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", + key, subcolumn->size(), size()); + + subcolumns[key] = std::move(subcolumn); +} + +} diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h new file mode 100644 index 00000000000..d462de304aa --- /dev/null +++ b/src/Columns/ColumnObject.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +class ColumnObject : public COWHelper +{ +private: + using SubcolumnsMap = std::unordered_map; + SubcolumnsMap subcolumns; + +public: + ColumnObject() = default; + ColumnObject(const SubcolumnsMap & subcolumns_); + ColumnObject(const Names & keys, const Columns & subcolumns_); + + bool hasSubcolumn(const String & key) const; + + const IColumn & getSubcolumn(const String & key) const; + IColumn & getSubcolumn(const String & key); + + void addSubcolumn(const String & key, MutableColumnPtr && subcolumn, bool check_size = true); + + const SubcolumnsMap & getSubcolumns() const { return subcolumns; } + SubcolumnsMap & getSubcolumns() { return subcolumns; } + + const char * getFamilyName() const override { return "Object"; } + + size_t size() const override { return subcolumns.empty() ? 0 : subcolumns.begin()->second->size(); } + + MutableColumnPtr cloneResized(size_t new_size) const override; + + size_t byteSize() const override; + size_t allocatedBytes() const override; + + /// All other methods throw exception. + + ColumnPtr decompress() const override { throwMustBeDecompressed(); } + + TypeIndex getDataType() const override { throwMustBeDecompressed(); } + Field operator[](size_t) const override { throwMustBeDecompressed(); } + void get(size_t, Field &) const override { throwMustBeDecompressed(); } + StringRef getDataAt(size_t) const override { throwMustBeDecompressed(); } + void insert(const Field &) override { throwMustBeDecompressed(); } + void insertRangeFrom(const IColumn &, size_t, size_t) override { throwMustBeDecompressed(); } + void insertData(const char *, size_t) override { throwMustBeDecompressed(); } + void insertDefault() override { throwMustBeDecompressed(); } + void popBack(size_t) override { throwMustBeDecompressed(); } + StringRef serializeValueIntoArena(size_t, Arena &, char const *&) const override { throwMustBeDecompressed(); } + const char * deserializeAndInsertFromArena(const char *) override { throwMustBeDecompressed(); } + const char * skipSerializedInArena(const char *) const override { throwMustBeDecompressed(); } + void updateHashWithValue(size_t, SipHash &) const override { throwMustBeDecompressed(); } + void updateWeakHash32(WeakHash32 &) const override { throwMustBeDecompressed(); } + void updateHashFast(SipHash &) const override { throwMustBeDecompressed(); } + ColumnPtr filter(const Filter &, ssize_t) const override { throwMustBeDecompressed(); } + ColumnPtr permute(const Permutation &, size_t) const override { throwMustBeDecompressed(); } + ColumnPtr index(const IColumn &, size_t) const override { throwMustBeDecompressed(); } + int compareAt(size_t, size_t, const IColumn &, int) const override { throwMustBeDecompressed(); } + void compareColumn(const IColumn &, size_t, PaddedPODArray *, PaddedPODArray &, int, int) const override + { + throwMustBeDecompressed(); + } + bool hasEqualValues() const override + { + throwMustBeDecompressed(); + } + void getPermutation(bool, size_t, int, Permutation &) const override { throwMustBeDecompressed(); } + void updatePermutation(bool, size_t, int, Permutation &, EqualRanges &) const override { throwMustBeDecompressed(); } + ColumnPtr replicate(const Offsets &) const override { throwMustBeDecompressed(); } + MutableColumns scatter(ColumnIndex, const Selector &) const override { throwMustBeDecompressed(); } + void gather(ColumnGathererStream &) override { throwMustBeDecompressed(); } + void getExtremes(Field &, Field &) const override { throwMustBeDecompressed(); } + size_t byteSizeAt(size_t) const override { throwMustBeDecompressed(); } + +private: + [[noreturn]] void throwMustBeDecompressed() const + { + throw Exception("ColumnCompressed must be decompressed before use", ErrorCodes::LOGICAL_ERROR); + } + + void checkConsistency() const; +}; + +} + diff --git a/src/Common/CMakeLists.txt b/src/Common/CMakeLists.txt index 61d9b9771a4..feee3bd9dd3 100644 --- a/src/Common/CMakeLists.txt +++ b/src/Common/CMakeLists.txt @@ -3,6 +3,8 @@ add_subdirectory(StringUtils) #add_subdirectory(ZooKeeper) #add_subdirectory(ConfigProcessor) +add_subdirectory(JSONParsers) + if (ENABLE_TESTS) add_subdirectory (tests) endif () diff --git a/src/Common/JSONParsers/CMakeLists.txt b/src/Common/JSONParsers/CMakeLists.txt new file mode 100644 index 00000000000..7b0c6f3ff07 --- /dev/null +++ b/src/Common/JSONParsers/CMakeLists.txt @@ -0,0 +1,15 @@ +# configure_file(config_jsonparsers.h.in ${ConfigIncludePath}/config_jsonparsers.h) + +# include(${ClickHouse_SOURCE_DIR}/cmake/dbms_glob_sources.cmake) + +# add_headers_and_sources(clickhouse_common_jsonparsers .) + +# add_library(clickhouse_common_jsonparsers ${clickhouse_common_jsonparsers_headers} ${clickhouse_common_jsonparsers_sources}) + +# if(USE_SIMDJSON) +# target_link_libraries(clickhouse_common_jsonparsers PRIVATE simdjson) +# endif() + +# if(USE_RAPIDJSON) +# target_include_directories(clickhouse_common_jsonparsers SYSTEM PRIVATE ${RAPIDJSON_INCLUDE_DIR}) +# endif() diff --git a/src/Functions/DummyJSONParser.h b/src/Common/JSONParsers/DummyJSONParser.h similarity index 100% rename from src/Functions/DummyJSONParser.h rename to src/Common/JSONParsers/DummyJSONParser.h diff --git a/src/Functions/RapidJSONParser.h b/src/Common/JSONParsers/RapidJSONParser.h similarity index 99% rename from src/Functions/RapidJSONParser.h rename to src/Common/JSONParsers/RapidJSONParser.h index 992480d64f7..c25bfefaea1 100644 --- a/src/Functions/RapidJSONParser.h +++ b/src/Common/JSONParsers/RapidJSONParser.h @@ -1,7 +1,7 @@ #pragma once #if !defined(ARCADIA_BUILD) -# include "config_functions.h" +# include "config_jsonparsers.h" #endif #if USE_RAPIDJSON diff --git a/src/Functions/SimdJSONParser.h b/src/Common/JSONParsers/SimdJSONParser.h similarity index 99% rename from src/Functions/SimdJSONParser.h rename to src/Common/JSONParsers/SimdJSONParser.h index 7ff3c45130d..974b506b4a7 100644 --- a/src/Functions/SimdJSONParser.h +++ b/src/Common/JSONParsers/SimdJSONParser.h @@ -1,7 +1,7 @@ #pragma once #if !defined(ARCADIA_BUILD) -# include "config_functions.h" +# include "config_jsonparsers.h" #endif #if USE_SIMDJSON diff --git a/src/Common/JSONParsers/config_jsonparsers.h.in b/src/Common/JSONParsers/config_jsonparsers.h.in new file mode 100644 index 00000000000..64352584342 --- /dev/null +++ b/src/Common/JSONParsers/config_jsonparsers.h.in @@ -0,0 +1,6 @@ +// #pragma once + +// // .h autogenerated by cmake! + +// #cmakedefine01 USE_SIMDJSON +// #cmakedefine01 USE_RAPIDJSON diff --git a/src/Common/config.h.in b/src/Common/config.h.in index ee2bfe3df53..64ac0d1f338 100644 --- a/src/Common/config.h.in +++ b/src/Common/config.h.in @@ -15,3 +15,6 @@ #cmakedefine01 USE_GRPC #cmakedefine01 USE_STATS #cmakedefine01 CLICKHOUSE_SPLIT_BINARY +#cmakedefine01 USE_SIMDJSON +#cmakedefine01 USE_RAPIDJSON + diff --git a/src/Core/config_core.h.in b/src/Core/config_core.h.in index e250e013913..c56e6cc28f8 100644 --- a/src/Core/config_core.h.in +++ b/src/Core/config_core.h.in @@ -15,3 +15,5 @@ #cmakedefine01 USE_LIBPQXX #cmakedefine01 USE_NURAFT #cmakedefine01 USE_KRB5 +#cmakedefine01 USE_SIMDJSON +#cmakedefine01 USE_RAPIDJSON diff --git a/src/DataTypes/CMakeLists.txt b/src/DataTypes/CMakeLists.txt index 65172356645..81a4c872b02 100644 --- a/src/DataTypes/CMakeLists.txt +++ b/src/DataTypes/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory (Serializations) + if (ENABLE_TESTS) add_subdirectory (tests) endif () diff --git a/src/DataTypes/DataTypeFactory.cpp b/src/DataTypes/DataTypeFactory.cpp index c28de15565c..6113385383f 100644 --- a/src/DataTypes/DataTypeFactory.cpp +++ b/src/DataTypes/DataTypeFactory.cpp @@ -211,6 +211,7 @@ DataTypeFactory::DataTypeFactory() registerDataTypeDomainSimpleAggregateFunction(*this); registerDataTypeDomainGeo(*this); registerDataTypeMap(*this); + registerDataTypeObject(*this); } DataTypeFactory & DataTypeFactory::instance() diff --git a/src/DataTypes/DataTypeFactory.h b/src/DataTypes/DataTypeFactory.h index 9fa3e30297b..e58d5ecaf07 100644 --- a/src/DataTypes/DataTypeFactory.h +++ b/src/DataTypes/DataTypeFactory.h @@ -86,5 +86,6 @@ void registerDataTypeLowCardinality(DataTypeFactory & factory); void registerDataTypeDomainIPv4AndIPv6(DataTypeFactory & factory); void registerDataTypeDomainSimpleAggregateFunction(DataTypeFactory & factory); void registerDataTypeDomainGeo(DataTypeFactory & factory); +void registerDataTypeObject(DataTypeFactory & factory); } diff --git a/src/DataTypes/DataTypeObject.cpp b/src/DataTypes/DataTypeObject.cpp new file mode 100644 index 00000000000..52a02e79f66 --- /dev/null +++ b/src/DataTypes/DataTypeObject.cpp @@ -0,0 +1,63 @@ +#include +#include +#include + +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; + extern const int UNEXPECTED_AST_STRUCTURE; +} + +DataTypeObject::DataTypeObject(const String & schema_format_) + : schema_format(Poco::toLower(schema_format_)) + , default_serialization(getObjectSerialization(schema_format)) +{ +} + +bool DataTypeObject::equals(const IDataType & rhs) const +{ + if (const auto * object = typeid_cast(&rhs)) + return schema_format == object->schema_format; + return false; +} + +SerializationPtr DataTypeObject::doGetDefaultSerialization() const +{ + return default_serialization; +} + +String DataTypeObject::doGetName() const +{ + WriteBufferFromOwnString out; + out << "Object(" << quote << schema_format << ")"; + return out.str(); +} + + +static DataTypePtr create(const ASTPtr & arguments) +{ + if (!arguments || arguments->children.size() != 1) + throw Exception("Object data type family must have exactly one argument - name of schema format", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + const auto * argument = arguments->children[0]->as(); + if (!argument || argument->value.getType() != Field::Types::String) + throw Exception("Object data type family must have a string as its argument", + ErrorCodes::UNEXPECTED_AST_STRUCTURE); + + return std::make_shared(argument->value.get()); +} + +void registerDataTypeObject(DataTypeFactory & factory) +{ + factory.registerDataType("Object", create); +} + +} diff --git a/src/DataTypes/DataTypeObject.h b/src/DataTypes/DataTypeObject.h new file mode 100644 index 00000000000..17d8dcc0f41 --- /dev/null +++ b/src/DataTypes/DataTypeObject.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int NOT_IMPLEMENTED; +} + +class DataTypeObject : public IDataType +{ +private: + String schema_format; + SerializationPtr default_serialization; + +public: + DataTypeObject(const String & schema_format_); + + const char * getFamilyName() const override { return "Object"; } + String doGetName() const override; + TypeIndex getTypeId() const override { return TypeIndex::Nothing; } + + MutableColumnPtr createColumn() const override { return ColumnObject::create(); } + + Field getDefault() const override + { + throw Exception("Method getDefault() is not implemented for data type " + getName(), ErrorCodes::NOT_IMPLEMENTED); + } + + bool haveSubtypes() const override { return false; } + bool equals(const IDataType & rhs) const override; + bool isParametric() const override { return true; } + + SerializationPtr doGetDefaultSerialization() const override; +}; + +} diff --git a/src/DataTypes/Serializations/CMakeLists.txt b/src/DataTypes/Serializations/CMakeLists.txt new file mode 100644 index 00000000000..65172356645 --- /dev/null +++ b/src/DataTypes/Serializations/CMakeLists.txt @@ -0,0 +1,3 @@ +if (ENABLE_TESTS) + add_subdirectory (tests) +endif () diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h new file mode 100644 index 00000000000..18996b7ff25 --- /dev/null +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -0,0 +1,88 @@ +#pragma once + +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +namespace +{ + +template +String getValueAsString(const Element & element) +{ + if (element.isBool()) return toString(element.getBool()); + if (element.isInt64()) return toString(element.getInt64()); + if (element.isUInt64()) return toString(element.getUInt64()); + if (element.isDouble()) return toString(element.getDouble()); + if (element.isString()) return String(element.getString()); + + throw Exception(ErrorCodes::LOGICAL_ERROR, "Unsupported type of JSON field"); +} + +} + +struct ParseResult +{ + Strings paths; + Strings values; +}; + +template +class JSONDataParser +{ +public: + using Element = typename ParserImpl::Element; + using Path = std::vector; + using Paths = std::vector; + + void readInto(String & s, ReadBuffer & buf) + { + readJSONObjectPossiblyInvalid(s, buf); + } + + std::optional parse(const char * begin, size_t length) + { + std::string_view json{begin, length}; + Element document; + if (!parser.parse(json, document)) + return {}; + + ParseResult result; + traverse(document, "", result); + return result; + } + +private: + void traverse(const Element & element, String current_path, ParseResult & result) + { + /// TODO: support arrays. + if (element.isObject()) + { + auto object = element.getObject(); + for (auto it = object.begin(); it != object.end(); ++it) + { + const auto & [key, value] = *it; + String next_path = current_path; + if (!next_path.empty()) + next_path += "."; + next_path += key; + traverse(value, next_path, result); + } + } + else + { + result.paths.push_back(current_path); + result.values.push_back(getValueAsString(element)); + } + } + + ParserImpl parser; +}; + +} diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp new file mode 100644 index 00000000000..69d245fdf23 --- /dev/null +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int NOT_IMPLEMENTED; + extern const int INCORRECT_DATA; +} + +template +void SerializationObject::serializeText(const IColumn & /*column*/, size_t /*row_num*/, WriteBuffer & /*ostr*/, const FormatSettings &) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::deserializeText(IColumn & column, ReadBuffer & istr, const FormatSettings &) const +{ + auto & column_object = assert_cast(column); + + String buf; + parser.readInto(buf, istr); + std::cerr << "buf: " << buf << "\n"; + auto result = parser.parse(buf.data(), buf.size()); + if (!result) + throw Exception(ErrorCodes::INCORRECT_DATA, "Cannot parse object"); + + const auto & [paths, values] = *result; + assert(paths.size() == values.size()); + + NameSet paths_set(paths.begin(), paths.end()); + + if (paths.size() != paths_set.size()) + throw Exception(ErrorCodes::INCORRECT_DATA, "Object has ambiguous paths"); + + size_t column_size = column_object.size(); + for (size_t i = 0; i < paths.size(); ++i) + { + if (!column_object.hasSubcolumn(paths[i])) + { + auto new_column = ColumnString::create()->cloneResized(column_size); + column_object.addSubcolumn(paths[i], std::move(new_column), false); + } + + column_object.getSubcolumn(paths[i]).insertData(values[i].data(), values[i].size()); + } + + for (auto & [key, subcolumn] : column_object.getSubcolumns()) + { + if (!paths_set.count(key)) + subcolumn->insertDefault(); + } +} + +template +void SerializationObject::serializeBinary(const Field & /*field*/, WriteBuffer & /*ostr*/) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::deserializeBinary(Field & /*field*/, ReadBuffer & /*istr*/) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::serializeBinary(const IColumn & /*column*/, size_t /*row_num*/, WriteBuffer & /*ostr*/) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::deserializeBinary(IColumn & /*column*/, ReadBuffer & /*istr*/) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::serializeBinaryBulk(const IColumn & /*column*/, WriteBuffer & /*ostr*/, size_t /*offset*/, size_t /*limit*/) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::deserializeBinaryBulk(IColumn & /*column*/, ReadBuffer & /*istr*/, size_t /*limit*/, double /*avg_value_size_hint*/) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +SerializationPtr getObjectSerialization(const String & schema_format) +{ + if (schema_format == "json") + { +#if USE_SIMDJSON + return std::make_shared>>(); +#elif USE_RAPIDJSON + return std::make_shared>>(); +#else + throw Exception(ErrorCodes::NOT_IMPLEMENTED, + "To use data type Object with JSON format, ClickHouse should be built with Simdjson or Rapidjson"); +#endif + } + + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Unknow schema_format '{}'", schema_format); +} + +} diff --git a/src/DataTypes/Serializations/SerializationObject.h b/src/DataTypes/Serializations/SerializationObject.h new file mode 100644 index 00000000000..4806c50ad8c --- /dev/null +++ b/src/DataTypes/Serializations/SerializationObject.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace DB +{ + +template +class SerializationObject : public SimpleTextSerialization +{ +public: + void serializeText(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings &) const override; + void deserializeText(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const override; + + /** Format is platform-dependent. */ + void serializeBinary(const Field & field, WriteBuffer & ostr) const override; + void deserializeBinary(Field & field, ReadBuffer & istr) const override; + void serializeBinary(const IColumn & column, size_t row_num, WriteBuffer & ostr) const override; + void deserializeBinary(IColumn & column, ReadBuffer & istr) const override; + void serializeBinaryBulk(const IColumn & column, WriteBuffer & ostr, size_t offset, size_t limit) const override; + void deserializeBinaryBulk(IColumn & column, ReadBuffer & istr, size_t limit, double avg_value_size_hint) const override; + +private: + mutable Parser parser; +}; + +SerializationPtr getObjectSerialization(const String & schema_format); + +} diff --git a/src/DataTypes/Serializations/tests/CMakeLists.txt b/src/DataTypes/Serializations/tests/CMakeLists.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/DataTypes/Serializations/tests/gtest_json_parser.cpp b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp new file mode 100644 index 00000000000..48a4a446c48 --- /dev/null +++ b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +#if USE_SIMDJSON + +using namespace DB; + +TEST(JSONDataParser, ReadInto) +{ + String json = R"({"k1" : 1, "k2" : {"k3" : "aa", "k4" : 2}})"; + String json_bad = json + "aaaaaaa"; + + JSONDataParser parser; + ReadBufferFromString buf(json_bad); + String res; + parser.readInto(res, buf); + ASSERT_EQ(json, res); +} + +TEST(JSONDataParser, Parse) +{ + String json = R"({"k1" : 1, "k2" : {"k3" : "aa", "k4" : 2}})"; + JSONDataParser parser; + auto res = parser.parse(json.data(), json.size()); + ASSERT_TRUE(res.has_value()); + + const auto & [paths, values] = *res; + ASSERT_EQ(paths, (Strings{"k1", "k2.k3", "k2.k4"})); + ASSERT_EQ(values, (Strings{"1", "aa", "2"})); +} + +#endif diff --git a/src/Functions/FunctionsJSON.h b/src/Functions/FunctionsJSON.h index c2fd44e2e2a..2427e8afae1 100644 --- a/src/Functions/FunctionsJSON.h +++ b/src/Functions/FunctionsJSON.h @@ -2,9 +2,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/src/IO/ReadHelpers.cpp b/src/IO/ReadHelpers.cpp index 72ffd74a42d..60dbe8cac51 100644 --- a/src/IO/ReadHelpers.cpp +++ b/src/IO/ReadHelpers.cpp @@ -755,6 +755,54 @@ template bool readJSONStringInto, bool>(PaddedPODArray(NullOutput & s, ReadBuffer & buf); template void readJSONStringInto(String & s, ReadBuffer & buf); +template +ReturnType readJSONObjectPossiblyInvalid(Vector & s, ReadBuffer & buf) +{ + static constexpr bool throw_exception = std::is_same_v; + + auto error = [](const char * message [[maybe_unused]], int code [[maybe_unused]]) + { + if constexpr (throw_exception) + throw ParsingException(message, code); + return ReturnType(false); + }; + + if (buf.eof() || *buf.position() != '{') + return error("JSON should starts from opening curly bracket", ErrorCodes::INCORRECT_DATA); + + s.push_back(*buf.position()); + ++buf.position(); + Int64 balance = 1; + while (!buf.eof()) + { + char * next_pos = find_first_symbols<'\\', '{', '}'>(buf.position(), buf.buffer().end()); + appendToStringOrVector(s, buf, next_pos); + buf.position() = next_pos; + + if (!buf.hasPendingData()) + continue; + + if (*buf.position() == '\\') + { + parseJSONEscapeSequence(s, buf); + continue; + } + + if (*buf.position() == '}') + --balance; + else if (*buf.position() == '{') + ++balance; + + s.push_back(*buf.position()); + ++buf.position(); + if (balance == 0) + return ReturnType(true); + } + + return error("JSON should have equal number of opening and closing brackets", ErrorCodes::INCORRECT_DATA); +} + +template void readJSONObjectPossiblyInvalid(String & s, ReadBuffer & buf); template ReturnType readDateTextFallback(LocalDate & date, ReadBuffer & buf) diff --git a/src/IO/ReadHelpers.h b/src/IO/ReadHelpers.h index cf25b819e6c..304e315c855 100644 --- a/src/IO/ReadHelpers.h +++ b/src/IO/ReadHelpers.h @@ -520,6 +520,9 @@ bool tryReadJSONStringInto(Vector & s, ReadBuffer & buf) return readJSONStringInto(s, buf); } +template +ReturnType readJSONObjectPossiblyInvalid(Vector & s, ReadBuffer & buf); + template void readStringUntilWhitespaceInto(Vector & s, ReadBuffer & buf); From e23784081022b12052cce619265903e8aeee3f5f Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 23 Apr 2021 17:34:53 +0300 Subject: [PATCH 0006/1647] dynamic subcolumns: wip --- src/Columns/ColumnObject.h | 8 +- .../Serializations/ISerialization.cpp | 8 + src/DataTypes/Serializations/ISerialization.h | 5 + .../Serializations/SerializationObject.cpp | 180 ++++++++++++++++-- .../Serializations/SerializationObject.h | 29 ++- 5 files changed, 210 insertions(+), 20 deletions(-) diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index d462de304aa..3fe6f316bff 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -28,11 +28,15 @@ public: const IColumn & getSubcolumn(const String & key) const; IColumn & getSubcolumn(const String & key); - void addSubcolumn(const String & key, MutableColumnPtr && subcolumn, bool check_size = true); + void addSubcolumn(const String & key, MutableColumnPtr && subcolumn, bool check_size = false); const SubcolumnsMap & getSubcolumns() const { return subcolumns; } SubcolumnsMap & getSubcolumns() { return subcolumns; } + void checkConsistency() const; + + /// Part of interface + const char * getFamilyName() const override { return "Object"; } size_t size() const override { return subcolumns.empty() ? 0 : subcolumns.begin()->second->size(); } @@ -86,8 +90,6 @@ private: { throw Exception("ColumnCompressed must be decompressed before use", ErrorCodes::LOGICAL_ERROR); } - - void checkConsistency() const; }; } diff --git a/src/DataTypes/Serializations/ISerialization.cpp b/src/DataTypes/Serializations/ISerialization.cpp index 68316ef7650..82dfa53c103 100644 --- a/src/DataTypes/Serializations/ISerialization.cpp +++ b/src/DataTypes/Serializations/ISerialization.cpp @@ -48,6 +48,10 @@ String ISerialization::Substream::toString() const return "SparseElements"; case SparseOffsets: return "SparseOffsets"; + case ObjectStructure: + return "ObjectStructure"; + case ObjectElement: + return "ObjectElement(" + object_key_name + ")"; } __builtin_unreachable(); @@ -143,6 +147,10 @@ static String getNameForSubstreamPath( stream_name += (escape_tuple_delimiter && elem.escape_tuple_delimiter ? escapeForFileName(".") : ".") + escapeForFileName(elem.tuple_element_name); } + else if (elem.type == Substream::ObjectElement) + { + stream_name += escapeForFileName(".") + escapeForFileName(elem.object_key_name); + } } return stream_name; diff --git a/src/DataTypes/Serializations/ISerialization.h b/src/DataTypes/Serializations/ISerialization.h index a6b780f3780..2a5d6f18ce4 100644 --- a/src/DataTypes/Serializations/ISerialization.h +++ b/src/DataTypes/Serializations/ISerialization.h @@ -87,12 +87,17 @@ public: SparseElements, SparseOffsets, + + ObjectStructure, + ObjectElement, }; Type type; /// Index of tuple element, starting at 1 or name. String tuple_element_name; + String object_key_name; + /// Do we need to escape a dot in filenames for tuple elements. bool escape_tuple_delimiter = true; diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 69d245fdf23..f88544e1b35 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -1,10 +1,18 @@ #include +#include +#include +#include +#include #include #include -#include +#include #include #include +#include +#include +#include + namespace DB { @@ -12,6 +20,7 @@ namespace ErrorCodes { extern const int NOT_IMPLEMENTED; extern const int INCORRECT_DATA; + extern const int CANNOT_READ_ALL_DATA; } template @@ -46,7 +55,7 @@ void SerializationObject::deserializeText(IColumn & column, ReadBuffer & if (!column_object.hasSubcolumn(paths[i])) { auto new_column = ColumnString::create()->cloneResized(column_size); - column_object.addSubcolumn(paths[i], std::move(new_column), false); + column_object.addSubcolumn(paths[i], std::move(new_column)); } column_object.getSubcolumn(paths[i]).insertData(values[i].data(), values[i].size()); @@ -59,6 +68,161 @@ void SerializationObject::deserializeText(IColumn & column, ReadBuffer & } } +namespace +{ + +struct SerializeStateObject : public ISerialization::SerializeBinaryBulkState +{ + std::unordered_map states; +}; + +struct DeserializeStateObject : public ISerialization::DeserializeBinaryBulkState +{ + std::unordered_map states; +}; + +DataTypePtr getDataTypeByColumn(const IColumn & column) +{ + if (column.empty()) + return std::make_shared(); + + return applyVisitor(FieldToDataType(), column[0]); +} + +} + +template +void SerializationObject::serializeBinaryBulkStatePrefix( + SerializeBinaryBulkSettings & settings, + SerializeBinaryBulkStatePtr &) const +{ + if (settings.position_independent_encoding) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, + "DataTypeObject cannot be serialized with position independent encoding"); +} + +template +void SerializationObject::serializeBinaryBulkStateSuffix( + SerializeBinaryBulkSettings & settings, + SerializeBinaryBulkStatePtr & state) const +{ + UNUSED(settings); + UNUSED(state); +} + +template +void SerializationObject::deserializeBinaryBulkStatePrefix( + DeserializeBinaryBulkSettings & settings, + DeserializeBinaryBulkStatePtr &) const +{ + if (settings.position_independent_encoding) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, + "DataTypeObject cannot be deserialized with position independent encoding"); +} + +template +void SerializationObject::serializeBinaryBulkWithMultipleStreams( + const IColumn & column, + size_t offset, + size_t limit, + SerializeBinaryBulkSettings & settings, + SerializeBinaryBulkStatePtr & state) const +{ + const auto & column_object = assert_cast(column); + assert(!settings.position_independent_encoding); + + settings.path.push_back(Substream::ObjectStructure); + if (auto * stream = settings.getter(settings.path)) + writeVarUInt(column_object.getSubcolumns().size(), *stream); + + settings.path.back() = Substream::ObjectElement; + for (const auto & [key, subcolumn] : column_object.getSubcolumns()) + { + settings.path.back().object_key_name = key; + + settings.path.back() = Substream::ObjectStructure; + if (auto * stream = settings.getter(settings.path)) + { + auto type = getDataTypeByColumn(*subcolumn); + writeStringBinary(key, *stream); + writeStringBinary(type->getName(), *stream); + } + + settings.path.back() = Substream::ObjectElement; + settings.path.back().object_key_name = key; + + if (auto * stream = settings.getter(settings.path)) + { + auto type = getDataTypeByColumn(*subcolumn); + auto serialization = type->getDefaultSerialization(); + serialization->serializeBinaryBulkWithMultipleStreams(*subcolumn, offset, limit, settings, state); + } + } + + settings.path.pop_back(); +} + +template +void SerializationObject::deserializeBinaryBulkWithMultipleStreams( + ColumnPtr & column, + size_t limit, + DeserializeBinaryBulkSettings & settings, + DeserializeBinaryBulkStatePtr & state, + SubstreamsCache * cache) const +{ + if (!column->empty()) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, + "DataTypeObject cannot be deserialized to non-empty column"); + + auto mutable_column = column->assumeMutable(); + auto & column_object = typeid_cast(*mutable_column); + + size_t num_subcolumns = 0; + settings.path.push_back(Substream::ObjectStructure); + if (auto * stream = settings.getter(settings.path)) + readVarUInt(num_subcolumns, *stream); + + settings.path.back() = Substream::ObjectElement; + for (size_t i = 0; i < num_subcolumns; ++i) + { + String key; + String type_name; + + settings.path.back() = Substream::ObjectStructure; + if (auto * stream = settings.getter(settings.path)) + { + readStringBinary(key, *stream); + readStringBinary(type_name, *stream); + } + else + { + throw Exception(ErrorCodes::CANNOT_READ_ALL_DATA, + "Cannot read structure of DataTypeObject, because its stream is missing"); + } + + settings.path.back() = Substream::ObjectElement; + settings.path.back().object_key_name = key; + + if (auto * stream = settings.getter(settings.path)) + { + auto type = DataTypeFactory::instance().get(type_name); + auto serialization = type->getDefaultSerialization(); + ColumnPtr subcolumn = type->createColumn(); + serialization->deserializeBinaryBulkWithMultipleStreams(subcolumn, limit, settings, state, cache); + column_object.addSubcolumn(key, subcolumn->assumeMutable()); + } + else + { + throw Exception(ErrorCodes::CANNOT_READ_ALL_DATA, + "Cannot read subcolumn '{}' of DataTypeObject, because its stream is missing", key); + } + } + + settings.path.pop_back(); + column_object.checkConsistency(); + column = std::move(mutable_column); +} + template void SerializationObject::serializeBinary(const Field & /*field*/, WriteBuffer & /*ostr*/) const { @@ -83,18 +247,6 @@ void SerializationObject::deserializeBinary(IColumn & /*column*/, ReadBu throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); } -template -void SerializationObject::serializeBinaryBulk(const IColumn & /*column*/, WriteBuffer & /*ostr*/, size_t /*offset*/, size_t /*limit*/) const -{ - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); -} - -template -void SerializationObject::deserializeBinaryBulk(IColumn & /*column*/, ReadBuffer & /*istr*/, size_t /*limit*/, double /*avg_value_size_hint*/) const -{ - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); -} - SerializationPtr getObjectSerialization(const String & schema_format) { if (schema_format == "json") diff --git a/src/DataTypes/Serializations/SerializationObject.h b/src/DataTypes/Serializations/SerializationObject.h index 4806c50ad8c..eeb64ca2f21 100644 --- a/src/DataTypes/Serializations/SerializationObject.h +++ b/src/DataTypes/Serializations/SerializationObject.h @@ -12,13 +12,36 @@ public: void serializeText(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings &) const override; void deserializeText(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const override; - /** Format is platform-dependent. */ + void serializeBinaryBulkStatePrefix( + SerializeBinaryBulkSettings & settings, + SerializeBinaryBulkStatePtr & state) const override; + + void serializeBinaryBulkStateSuffix( + SerializeBinaryBulkSettings & settings, + SerializeBinaryBulkStatePtr & state) const override; + + void deserializeBinaryBulkStatePrefix( + DeserializeBinaryBulkSettings & settings, + DeserializeBinaryBulkStatePtr & state) const override; + + void serializeBinaryBulkWithMultipleStreams( + const IColumn & column, + size_t offset, + size_t limit, + SerializeBinaryBulkSettings & settings, + SerializeBinaryBulkStatePtr & state) const override; + + void deserializeBinaryBulkWithMultipleStreams( + ColumnPtr & column, + size_t limit, + DeserializeBinaryBulkSettings & settings, + DeserializeBinaryBulkStatePtr & state, + SubstreamsCache * cache) const override; + void serializeBinary(const Field & field, WriteBuffer & ostr) const override; void deserializeBinary(Field & field, ReadBuffer & istr) const override; void serializeBinary(const IColumn & column, size_t row_num, WriteBuffer & ostr) const override; void deserializeBinary(IColumn & column, ReadBuffer & istr) const override; - void serializeBinaryBulk(const IColumn & column, WriteBuffer & ostr, size_t offset, size_t limit) const override; - void deserializeBinaryBulk(IColumn & column, ReadBuffer & istr, size_t limit, double avg_value_size_hint) const override; private: mutable Parser parser; From 6240169bbbbd6a0c6c7bea8ebbcd7497cf020a04 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 24 Apr 2021 02:56:26 +0300 Subject: [PATCH 0007/1647] dynamic subcolumns: wip --- src/Columns/ColumnObject.cpp | 9 +++ src/Columns/ColumnObject.h | 4 +- src/DataTypes/ObjectUtils.cpp | 70 +++++++++++++++++++ src/DataTypes/ObjectUtils.h | 12 ++++ .../Serializations/SerializationObject.cpp | 10 +-- src/Interpreters/InterpreterSelectQuery.cpp | 2 + .../MergeTree/MergeTreeDataWriter.cpp | 7 +- 7 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 src/DataTypes/ObjectUtils.cpp create mode 100644 src/DataTypes/ObjectUtils.h diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 0c3a89cfbf2..17594501db1 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -114,4 +114,13 @@ void ColumnObject::addSubcolumn(const String & key, MutableColumnPtr && subcolum subcolumns[key] = std::move(subcolumn); } +Names ColumnObject::getKeys() const +{ + Names keys; + keys.reserve(subcolumns.size()); + for (const auto & [key, _] : subcolumns) + keys.emplace_back(key); + return keys; +} + } diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 3fe6f316bff..6e3d96ceef0 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -23,6 +23,8 @@ public: ColumnObject(const SubcolumnsMap & subcolumns_); ColumnObject(const Names & keys, const Columns & subcolumns_); + void checkConsistency() const; + bool hasSubcolumn(const String & key) const; const IColumn & getSubcolumn(const String & key) const; @@ -33,7 +35,7 @@ public: const SubcolumnsMap & getSubcolumns() const { return subcolumns; } SubcolumnsMap & getSubcolumns() { return subcolumns; } - void checkConsistency() const; + Names getKeys() const; /// Part of interface diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp new file mode 100644 index 00000000000..7a9226c4762 --- /dev/null +++ b/src/DataTypes/ObjectUtils.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int TYPE_MISMATCH; + extern const int LOGICAL_ERROR; +} + +static const IDataType * getTypeObject(const DataTypePtr & type) +{ + return typeid_cast(type.get()); +} + +DataTypePtr getDataTypeByColumn(const IColumn & column) +{ + if (column.empty()) + return std::make_shared(); + + return applyVisitor(FieldToDataType(), column[0]); +} + +void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) +{ + for (auto & name_type : columns_list) + { + if (const auto * type_object = getTypeObject(name_type.type)) + { + auto & column = block.getByName(name_type.name); + + if (!getTypeObject(column.type)) + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Type for column '{}' mismatch in columns list and in block. In list: {}, in block: {}", + name_type.name, name_type.type->getName(), column.type->getName()); + + const auto & column_object = assert_cast(*column.column); + const auto & subcolumns_map = column_object.getSubcolumns(); + + Names tuple_names; + DataTypes tuple_types; + Columns tuple_columns; + + for (const auto & [key, subcolumn] : subcolumns_map) + { + tuple_names.push_back(key); + tuple_types.push_back(getDataTypeByColumn(*subcolumn)); + tuple_columns.push_back(subcolumn); + } + + auto type_tuple = std::make_shared(tuple_types, tuple_names); + auto column_tuple = ColumnTuple::create(tuple_columns); + + name_type.type = type_tuple; + column.type = type_tuple; + column.column = column_tuple; + } + } +} + +} diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h new file mode 100644 index 00000000000..cacb3abbb08 --- /dev/null +++ b/src/DataTypes/ObjectUtils.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +namespace DB +{ + +DataTypePtr getDataTypeByColumn(const IColumn & column); +void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block); + +} diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index f88544e1b35..a34a8c3b12a 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -1,8 +1,8 @@ #include -#include #include #include #include +#include #include #include #include @@ -81,14 +81,6 @@ struct DeserializeStateObject : public ISerialization::DeserializeBinaryBulkStat std::unordered_map states; }; -DataTypePtr getDataTypeByColumn(const IColumn & column) -{ - if (column.empty()) - return std::make_shared(); - - return applyVisitor(FieldToDataType(), column[0]); -} - } template diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index f44e55a3df9..cb8375cf798 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -375,6 +375,8 @@ InterpreterSelectQuery::InterpreterSelectQuery( if (view) view->replaceWithSubquery(getSelectQuery(), view_table, metadata_snapshot); + std::cerr << "source_header: " << source_header.dumpStructure() << "\n"; + syntax_analyzer_result = TreeRewriter(context).analyzeSelect( query_ptr, TreeRewriterResult(source_header.getNamesAndTypesList(), storage, metadata_snapshot), diff --git a/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/src/Storages/MergeTree/MergeTreeDataWriter.cpp index 55d42fed20c..b155225c1bb 100644 --- a/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -261,6 +262,9 @@ Block MergeTreeDataWriter::mergeBlock(const Block & block, SortDescription sort_ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithPartition & block_with_partition, const StorageMetadataPtr & metadata_snapshot, bool optimize_on_insert) { Block & block = block_with_partition.block; + auto columns = metadata_snapshot->getColumns().getAllPhysical().filter(block.getNames()); + + convertObjectsToTuples(columns, block); static const String TMP_PREFIX = "tmp_insert_"; @@ -337,7 +341,6 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithPa for (const auto & ttl_entry : move_ttl_entries) updateTTL(ttl_entry, move_ttl_infos, move_ttl_infos.moves_ttl[ttl_entry.result_column], block, false); - NamesAndTypesList columns = metadata_snapshot->getColumns().getAllPhysical().filter(block.getNames()); ReservationPtr reservation = data.reserveSpacePreferringTTLRules(metadata_snapshot, expected_size, move_ttl_infos, time(nullptr), 0, true); VolumePtr volume = data.getStoragePolicy()->getVolume(0); @@ -404,7 +407,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithPa serialization_info.add(block); const auto & index_factory = MergeTreeIndexFactory::instance(); - MergedBlockOutputStream out(new_data_part, metadata_snapshot,columns, + MergedBlockOutputStream out(new_data_part, metadata_snapshot, columns, index_factory.getMany(metadata_snapshot->getSecondaryIndices()), compression_codec, serialization_info); From 644df6be7d0e859e02f5bfb94f412a37eecd0239 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 24 Apr 2021 07:09:01 +0300 Subject: [PATCH 0008/1647] dynamic subcolumns: wip --- src/Core/Types.h | 2 + src/DataTypes/DataTypeObject.h | 2 +- src/DataTypes/DataTypeTuple.cpp | 4 +- src/DataTypes/IDataType.h | 2 + src/DataTypes/NestedUtils.cpp | 2 +- src/DataTypes/ObjectUtils.cpp | 31 +++++++ src/DataTypes/ObjectUtils.h | 1 + .../Serializations/ISerialization.cpp | 6 +- src/Interpreters/InterpreterOptimizeQuery.cpp | 2 +- src/Interpreters/InterpreterSelectQuery.cpp | 5 +- src/Interpreters/TreeRewriter.cpp | 6 ++ .../getHeaderForProcessingStage.cpp | 7 +- .../QueryPlan/ReadFromMergeTree.cpp | 2 +- src/Storages/ColumnsDescription.h | 1 + src/Storages/IStorage.cpp | 2 +- src/Storages/IStorage.h | 3 + .../MergeTree/MergeTreeBlockReadUtils.cpp | 12 ++- src/Storages/MergeTree/MergeTreeData.cpp | 93 +++++++++++++++++++ src/Storages/MergeTree/MergeTreeData.h | 4 + .../MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- src/Storages/MergeTree/MergeTreeReadPool.cpp | 2 +- .../MergeTreeReverseSelectProcessor.cpp | 2 +- .../MergeTree/MergeTreeSelectProcessor.cpp | 2 +- .../MergeTree/MergeTreeSequentialSource.cpp | 2 +- .../RocksDB/StorageEmbeddedRocksDB.cpp | 2 +- src/Storages/StorageGenerateRandom.cpp | 2 +- src/Storages/StorageInMemoryMetadata.cpp | 24 ++++- src/Storages/StorageInMemoryMetadata.h | 7 +- src/Storages/StorageJoin.cpp | 2 +- src/Storages/StorageLog.cpp | 2 +- src/Storages/StorageMemory.cpp | 2 +- src/Storages/StorageMongoDB.cpp | 2 +- src/Storages/StorageMySQL.cpp | 2 +- src/Storages/StoragePostgreSQL.cpp | 2 +- src/Storages/StorageS3Cluster.cpp | 2 +- src/Storages/StorageStripeLog.cpp | 2 +- src/Storages/StorageTinyLog.cpp | 2 +- src/Storages/StorageValues.cpp | 2 +- src/Storages/StorageXDBC.cpp | 2 +- src/Storages/System/IStorageSystemOneBlock.h | 2 +- src/Storages/System/StorageSystemColumns.cpp | 2 +- src/Storages/System/StorageSystemDisks.cpp | 2 +- src/Storages/System/StorageSystemNumbers.cpp | 2 +- src/Storages/System/StorageSystemOne.cpp | 2 +- .../System/StorageSystemPartsBase.cpp | 2 +- src/Storages/System/StorageSystemReplicas.cpp | 2 +- .../System/StorageSystemStoragePolicies.cpp | 2 +- src/Storages/System/StorageSystemTables.cpp | 2 +- src/Storages/System/StorageSystemZeros.cpp | 2 +- 49 files changed, 229 insertions(+), 45 deletions(-) diff --git a/src/Core/Types.h b/src/Core/Types.h index b9ecda4a46d..9eee46f8af2 100644 --- a/src/Core/Types.h +++ b/src/Core/Types.h @@ -58,6 +58,7 @@ enum class TypeIndex AggregateFunction, LowCardinality, Map, + Object, }; #if !defined(__clang__) #pragma GCC diagnostic pop @@ -273,6 +274,7 @@ inline constexpr const char * getTypeName(TypeIndex idx) case TypeIndex::AggregateFunction: return "AggregateFunction"; case TypeIndex::LowCardinality: return "LowCardinality"; case TypeIndex::Map: return "Map"; + case TypeIndex::Object: return "Object"; } __builtin_unreachable(); diff --git a/src/DataTypes/DataTypeObject.h b/src/DataTypes/DataTypeObject.h index 17d8dcc0f41..6ba666adf3f 100644 --- a/src/DataTypes/DataTypeObject.h +++ b/src/DataTypes/DataTypeObject.h @@ -24,7 +24,7 @@ public: const char * getFamilyName() const override { return "Object"; } String doGetName() const override; - TypeIndex getTypeId() const override { return TypeIndex::Nothing; } + TypeIndex getTypeId() const override { return TypeIndex::Object; } MutableColumnPtr createColumn() const override { return ColumnObject::create(); } diff --git a/src/DataTypes/DataTypeTuple.cpp b/src/DataTypes/DataTypeTuple.cpp index c8006a65a79..e07870c3c43 100644 --- a/src/DataTypes/DataTypeTuple.cpp +++ b/src/DataTypes/DataTypeTuple.cpp @@ -281,7 +281,9 @@ DataTypePtr DataTypeTuple::tryGetSubcolumnType(const String & subcolumn_name) co auto on_success = [&](size_t pos) { return elems[pos]; }; auto on_continue = [&](size_t pos, const String & next_subcolumn) { return elems[pos]->tryGetSubcolumnType(next_subcolumn); }; - return getSubcolumnEntity(subcolumn_name, on_success, on_continue); + auto kek = getSubcolumnEntity(subcolumn_name, on_success, on_continue); + std::cerr << "requested subcolumn: " << subcolumn_name << ", have: " << !!kek << "\n"; + return kek; } ColumnPtr DataTypeTuple::getSubcolumn(const String & subcolumn_name, const IColumn & column) const diff --git a/src/DataTypes/IDataType.h b/src/DataTypes/IDataType.h index bb59bc8b3c6..3ca82a40121 100644 --- a/src/DataTypes/IDataType.h +++ b/src/DataTypes/IDataType.h @@ -337,6 +337,7 @@ struct WhichDataType constexpr bool isMap() const {return idx == TypeIndex::Map; } constexpr bool isSet() const { return idx == TypeIndex::Set; } constexpr bool isInterval() const { return idx == TypeIndex::Interval; } + constexpr bool isObject() const { return idx == TypeIndex::Object; } constexpr bool isNothing() const { return idx == TypeIndex::Nothing; } constexpr bool isNullable() const { return idx == TypeIndex::Nullable; } @@ -362,6 +363,7 @@ inline bool isDecimal(const DataTypePtr & data_type) { return WhichDataType(data inline bool isTuple(const DataTypePtr & data_type) { return WhichDataType(data_type).isTuple(); } inline bool isArray(const DataTypePtr & data_type) { return WhichDataType(data_type).isArray(); } inline bool isMap(const DataTypePtr & data_type) {return WhichDataType(data_type).isMap(); } +inline bool isObject(const DataTypePtr & data_type) {return WhichDataType(data_type).isObject(); } template inline bool isUInt8(const T & data_type) diff --git a/src/DataTypes/NestedUtils.cpp b/src/DataTypes/NestedUtils.cpp index 6c13eea0a1b..b84f937c3ec 100644 --- a/src/DataTypes/NestedUtils.cpp +++ b/src/DataTypes/NestedUtils.cpp @@ -62,7 +62,7 @@ std::pair splitName(const std::string & name) ++pos; - while (pos < end && isWordCharASCII(*pos)) + while (pos < end && (isWordCharASCII(*pos) || *pos == '.')) ++pos; if (pos != end) diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 7a9226c4762..5e7dd24c0fd 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -67,4 +68,34 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) } } +DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) +{ + std::unordered_map subcolumns_types; + for (const auto & type : types) + { + const auto * type_tuple = typeid_cast(type.get()); + if (!type_tuple) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Least common type for object can be deduced only from tuples, but {} given", type->getName()); + + const auto & tuple_names = type_tuple->getElementNames(); + const auto & tuple_types = type_tuple->getElements(); + assert(tuple_names.size() == tuple_type.size()); + + for (size_t i = 0; i < tuple_names.size(); ++i) + subcolumns_types[tuple_names[i]].push_back(tuple_types[i]); + } + + Names tuple_names; + DataTypes tuple_types; + + for (const auto & [name, subtypes] : subcolumns_types) + { + tuple_names.push_back(name); + tuple_types.push_back(getLeastSupertype(subtypes)); + } + + return std::make_shared(tuple_types, tuple_names); +} + } diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index cacb3abbb08..509574f934d 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -8,5 +8,6 @@ namespace DB DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block); +DataTypePtr getLeastCommonTypeForObject(const DataTypes & types); } diff --git a/src/DataTypes/Serializations/ISerialization.cpp b/src/DataTypes/Serializations/ISerialization.cpp index 82dfa53c103..fa483d3c8d2 100644 --- a/src/DataTypes/Serializations/ISerialization.cpp +++ b/src/DataTypes/Serializations/ISerialization.cpp @@ -144,8 +144,10 @@ static String getNameForSubstreamPath( /// Because nested data may be represented not by Array of Tuple, /// but by separate Array columns with names in a form of a.b, /// and name is encoded as a whole. - stream_name += (escape_tuple_delimiter && elem.escape_tuple_delimiter ? - escapeForFileName(".") : ".") + escapeForFileName(elem.tuple_element_name); + if (escape_tuple_delimiter && elem.escape_tuple_delimiter) + stream_name += escapeForFileName(".") + escapeForFileName(elem.tuple_element_name); + else + stream_name += "." + elem.tuple_element_name; } else if (elem.type == Substream::ObjectElement) { diff --git a/src/Interpreters/InterpreterOptimizeQuery.cpp b/src/Interpreters/InterpreterOptimizeQuery.cpp index 64de5ee0479..b50ce8b85df 100644 --- a/src/Interpreters/InterpreterOptimizeQuery.cpp +++ b/src/Interpreters/InterpreterOptimizeQuery.cpp @@ -46,7 +46,7 @@ BlockIO InterpreterOptimizeQuery::execute() column_names.emplace_back(col->getColumnName()); } - metadata_snapshot->check(column_names, NamesAndTypesList{}, table_id); + metadata_snapshot->check(column_names, NamesAndTypesList{}, table_id, {}); Names required_columns; { required_columns = metadata_snapshot->getColumnsRequiredForSortingKey(); diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index cb8375cf798..6a6c5185441 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -475,7 +475,8 @@ InterpreterSelectQuery::InterpreterSelectQuery( } } - source_header = metadata_snapshot->getSampleBlockForColumns(required_columns, storage->getVirtuals(), storage->getStorageID()); + source_header = metadata_snapshot->getSampleBlockForColumns( + required_columns, storage->getVirtuals(), storage->getStorageID(), storage->getExpandedObjects()); } /// Calculate structure of the result. @@ -1736,7 +1737,7 @@ void InterpreterSelectQuery::executeFetchColumns(QueryProcessingStage::Enum proc if (!query_plan.isInitialized()) { auto header = metadata_snapshot->getSampleBlockForColumns( - required_columns, storage->getVirtuals(), storage->getStorageID()); + required_columns, storage->getVirtuals(), storage->getStorageID(), storage->getExpandedObjects()); addEmptySourceToQueryPlan(query_plan, header, query_info); } diff --git a/src/Interpreters/TreeRewriter.cpp b/src/Interpreters/TreeRewriter.cpp index 324a773fbc2..2deff98fd0c 100644 --- a/src/Interpreters/TreeRewriter.cpp +++ b/src/Interpreters/TreeRewriter.cpp @@ -641,6 +641,12 @@ void TreeRewriterResult::collectSourceColumns(bool add_special) else columns_from_storage = add_special ? columns.getAll() : columns.getAllPhysical(); + columns_from_storage = storage->expandObjectColumns(columns_from_storage, storage->supportsSubcolumns()); + + std::cerr << "columns_from_storage: "; + for (const auto & col : columns_from_storage) + std::cerr << col.dump() << "\n"; + if (source_columns.empty()) source_columns.swap(columns_from_storage); else diff --git a/src/Interpreters/getHeaderForProcessingStage.cpp b/src/Interpreters/getHeaderForProcessingStage.cpp index 9c7c86a0b88..3ed6642105f 100644 --- a/src/Interpreters/getHeaderForProcessingStage.cpp +++ b/src/Interpreters/getHeaderForProcessingStage.cpp @@ -47,7 +47,9 @@ Block getHeaderForProcessingStage( { case QueryProcessingStage::FetchColumns: { - Block header = metadata_snapshot->getSampleBlockForColumns(column_names, storage.getVirtuals(), storage.getStorageID()); + Block header = metadata_snapshot->getSampleBlockForColumns( + column_names, storage.getVirtuals(), storage.getStorageID(), storage.getExpandedObjects()); + if (query_info.prewhere_info) { auto & prewhere_info = *query_info.prewhere_info; @@ -75,7 +77,8 @@ Block getHeaderForProcessingStage( removeJoin(*query->as()); auto stream = std::make_shared( - metadata_snapshot->getSampleBlockForColumns(column_names, storage.getVirtuals(), storage.getStorageID())); + metadata_snapshot->getSampleBlockForColumns( + column_names, storage.getVirtuals(), storage.getStorageID(), storage.getExpandedObjects())); return InterpreterSelectQuery(query, context, stream, SelectQueryOptions(processed_stage).analyze()).getSampleBlock(); } } diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index ebf9c9e4121..6dccb2b7954 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -23,7 +23,7 @@ ReadFromMergeTree::ReadFromMergeTree( size_t num_streams_, ReadType read_type_) : ISourceStep(DataStream{.header = MergeTreeBaseSelectProcessor::transformHeader( - metadata_snapshot_->getSampleBlockForColumns(required_columns_, storage_.getVirtuals(), storage_.getStorageID()), + metadata_snapshot_->getSampleBlockForColumns(required_columns_, storage_.getVirtuals(), storage_.getStorageID(), storage_.getExpandedObjects()), prewhere_info_, virt_column_names_)}) , storage(storage_) diff --git a/src/Storages/ColumnsDescription.h b/src/Storages/ColumnsDescription.h index 7fff22abf71..f7886d0353a 100644 --- a/src/Storages/ColumnsDescription.h +++ b/src/Storages/ColumnsDescription.h @@ -31,6 +31,7 @@ struct ColumnDescription { String name; DataTypePtr type; + DataTypePtr expaneded_type; ColumnDefault default_desc; String comment; ASTPtr codec; diff --git a/src/Storages/IStorage.cpp b/src/Storages/IStorage.cpp index f7fb359432e..eb78bf4aa84 100644 --- a/src/Storages/IStorage.cpp +++ b/src/Storages/IStorage.cpp @@ -105,7 +105,7 @@ void IStorage::read( auto pipe = read(column_names, metadata_snapshot, query_info, context, processed_stage, max_block_size, num_streams); if (pipe.empty()) { - auto header = metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()); + auto header = metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID(), getExpandedObjects()); InterpreterSelectQuery::addEmptySourceToQueryPlan(query_plan, header, query_info); } else diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index e48e9e49919..c64a40ac0d2 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -524,6 +524,9 @@ public: /// Does not takes underlying Storage (if any) into account. virtual std::optional lifetimeBytes() const { return {}; } + virtual NamesAndTypesList expandObjectColumns(const NamesAndTypesList & columns_list, bool /*with_subcolumns*/) const { return columns_list; } + virtual NamesAndTypesList getExpandedObjects() const { return {}; } + private: /// Lock required for alter queries (lockForAlter). Always taken for write /// (actually can be replaced with std::mutex, but for consistency we use diff --git a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp index 10ce061a864..cf07f4e2ec9 100644 --- a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp +++ b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -90,6 +91,13 @@ NameSet injectRequiredColumns(const MergeTreeData & storage, const StorageMetada auto alter_conversions = storage.getAlterConversionsForPart(part); for (size_t i = 0; i < columns.size(); ++i) { + auto name_in_storage = Nested::extractTableName(columns[i]); + if (isObject(storage_columns.get(name_in_storage).type)) + { + have_at_least_one_physical_column = true; + continue; + } + /// We are going to fetch only physical columns if (!storage_columns.hasPhysicalOrSubcolumn(columns[i])) throw Exception("There is no physical column or subcolumn " + columns[i] + " in table.", ErrorCodes::NO_SUCH_COLUMN_IN_TABLE); @@ -308,7 +316,9 @@ MergeTreeReadTaskColumns getReadTaskColumns( if (check_columns) { - const NamesAndTypesList & physical_columns = metadata_snapshot->getColumns().getAllWithSubcolumns(); + auto physical_columns = metadata_snapshot->getColumns().getAllWithSubcolumns(); + physical_columns = storage.expandObjectColumns(physical_columns, true); + result.pre_columns = physical_columns.addTypes(pre_column_names); result.columns = physical_columns.addTypes(column_names); } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index f28d87bb9be..b02abf5da0c 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -4479,6 +4481,97 @@ ReservationPtr MergeTreeData::balancedReservation( return reserved_space; } +static NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list) +{ + NameSet res; + for (const auto & [name, type] : columns_list) + if (isObject(type)) + res.insert(name); + + return res; +} + +static NamesAndTypesList expandObjectColumnsImpl( + const MergeTreeData::DataPartsVector & parts, + const NamesAndTypesList & columns_list, + const NameSet & requested_to_expand, + bool with_subcolumns) +{ + std::unordered_map types_in_parts; + + for (const auto & part : parts) + { + const auto & part_columns = part->getColumns(); + for (const auto & [name, type] : part_columns) + { + if (requested_to_expand.count(name)) + types_in_parts[name].push_back(type); + } + } + + NamesAndTypesList result_columns; + for (const auto & column : columns_list) + { + auto it = types_in_parts.find(column.name); + if (it != types_in_parts.end()) + { + auto expanded_type = getLeastCommonTypeForObject(it->second); + result_columns.emplace_back(column.name, expanded_type); + + std::cerr << "expanded_type: " << expanded_type->getName() << "\n"; + + if (with_subcolumns) + { + for (const auto & subcolumn : expanded_type->getSubcolumnNames()) + { + result_columns.emplace_back(column.name, subcolumn, + expanded_type, expanded_type->getSubcolumnType(subcolumn)); + std::cerr << "adding subcolumn: " << subcolumn << "\n"; + } + } + } + else + { + result_columns.push_back(column); + } + } + + return result_columns; +} + +NamesAndTypesList MergeTreeData::expandObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns) const +{ + /// Firstly fast check if there are any Object columns. + NameSet requested_to_expand = getNamesOfObjectColumns(columns_list); + if (requested_to_expand.empty()) + return columns_list; + + return expandObjectColumnsImpl(parts, columns_list, requested_to_expand, with_subcolumns); +} + +NamesAndTypesList MergeTreeData::expandObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const +{ + /// Firstly fast check if there are any Object columns. + NameSet requested_to_expand = getNamesOfObjectColumns(columns_list); + if (requested_to_expand.empty()) + return columns_list; + + return expandObjectColumnsImpl(getDataPartsVector(), columns_list, requested_to_expand, with_subcolumns); +} + +NamesAndTypesList MergeTreeData::getExpandedObjects() const +{ + auto metadata_snapshot = getInMemoryMetadataPtr(); + auto columns = metadata_snapshot->getColumns().getAllPhysical(); + + NamesAndTypesList result_columns; + for (const auto & column : columns) + if (isObject(column.type)) + result_columns.push_back(column); + + return expandObjectColumns(result_columns, false); +} + CurrentlySubmergingEmergingTagger::~CurrentlySubmergingEmergingTagger() { std::lock_guard lock(storage.currently_submerging_emerging_mutex); diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 46c0014d9f7..c8adee236a0 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -617,6 +617,10 @@ public: return column_sizes; } + NamesAndTypesList expandObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const override; + NamesAndTypesList expandObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns) const; + NamesAndTypesList getExpandedObjects() const override; + /// For ATTACH/DETACH/DROP PARTITION. String getPartitionIDFromQuery(const ASTPtr & ast, ContextPtr context) const; diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index af72b3e53f2..8eeb349057a 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -236,7 +236,7 @@ QueryPlanPtr MergeTreeDataSelectExecutor::readFromParts( } // At this point, empty `part_values` means all parts. - metadata_snapshot->check(real_column_names, data.getVirtuals(), data.getStorageID()); + metadata_snapshot->check(real_column_names, data.getVirtuals(), data.getStorageID(), data.getExpandedObjects()); const Settings & settings = context->getSettingsRef(); const auto & primary_key = metadata_snapshot->getPrimaryKey(); diff --git a/src/Storages/MergeTree/MergeTreeReadPool.cpp b/src/Storages/MergeTree/MergeTreeReadPool.cpp index d9a250e3f7a..07173eb2118 100644 --- a/src/Storages/MergeTree/MergeTreeReadPool.cpp +++ b/src/Storages/MergeTree/MergeTreeReadPool.cpp @@ -168,7 +168,7 @@ MarkRanges MergeTreeReadPool::getRestMarks(const IMergeTreeDataPart & part, cons Block MergeTreeReadPool::getHeader() const { - return metadata_snapshot->getSampleBlockForColumns(column_names, data.getVirtuals(), data.getStorageID()); + return metadata_snapshot->getSampleBlockForColumns(column_names, data.getVirtuals(), data.getStorageID(), data.getExpandedObjects()); } void MergeTreeReadPool::profileFeedback(const ReadBufferFromFileBase::ProfileInfo info) diff --git a/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp b/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp index e9527efaa4a..251dc4d5467 100644 --- a/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp @@ -30,7 +30,7 @@ MergeTreeReverseSelectProcessor::MergeTreeReverseSelectProcessor( bool quiet) : MergeTreeBaseSelectProcessor{ - metadata_snapshot_->getSampleBlockForColumns(required_columns_, storage_.getVirtuals(), storage_.getStorageID()), + metadata_snapshot_->getSampleBlockForColumns(required_columns_, storage_.getVirtuals(), storage_.getStorageID(), storage_.getExpandedObjects()), storage_, metadata_snapshot_, prewhere_info_, max_block_size_rows_, preferred_block_size_bytes_, preferred_max_column_in_block_size_bytes_, reader_settings_, use_uncompressed_cache_, virt_column_names_}, diff --git a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp index 980afa170e9..932595b42cd 100644 --- a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp @@ -30,7 +30,7 @@ MergeTreeSelectProcessor::MergeTreeSelectProcessor( bool quiet) : MergeTreeBaseSelectProcessor{ - metadata_snapshot_->getSampleBlockForColumns(required_columns_, storage_.getVirtuals(), storage_.getStorageID()), + metadata_snapshot_->getSampleBlockForColumns(required_columns_, storage_.getVirtuals(), storage_.getStorageID(), storage_.getExpandedObjects()), storage_, metadata_snapshot_, prewhere_info_, max_block_size_rows_, preferred_block_size_bytes_, preferred_max_column_in_block_size_bytes_, reader_settings_, use_uncompressed_cache_, virt_column_names_}, diff --git a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp index e82b1966461..972bbce193d 100644 --- a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp +++ b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp @@ -17,7 +17,7 @@ MergeTreeSequentialSource::MergeTreeSequentialSource( bool read_with_direct_io_, bool take_column_types_from_storage, bool quiet) - : SourceWithProgress(metadata_snapshot_->getSampleBlockForColumns(columns_to_read_, storage_.getVirtuals(), storage_.getStorageID())) + : SourceWithProgress(metadata_snapshot_->getSampleBlockForColumns(columns_to_read_, storage_.getVirtuals(), storage_.getStorageID(), storage_.getExpandedObjects())) , storage(storage_) , metadata_snapshot(metadata_snapshot_) , data_part(std::move(data_part_)) diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index 9173c23ec5a..744d91b0f54 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -289,7 +289,7 @@ Pipe StorageEmbeddedRocksDB::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); FieldVectorPtr keys; bool all_scan = false; diff --git a/src/Storages/StorageGenerateRandom.cpp b/src/Storages/StorageGenerateRandom.cpp index bc158c38f37..167d74cc51d 100644 --- a/src/Storages/StorageGenerateRandom.cpp +++ b/src/Storages/StorageGenerateRandom.cpp @@ -447,7 +447,7 @@ Pipe StorageGenerateRandom::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); Pipes pipes; pipes.reserve(num_streams); diff --git a/src/Storages/StorageInMemoryMetadata.cpp b/src/Storages/StorageInMemoryMetadata.cpp index 2f4a24a5c60..9a35250723a 100644 --- a/src/Storages/StorageInMemoryMetadata.cpp +++ b/src/Storages/StorageInMemoryMetadata.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -287,7 +288,7 @@ Block StorageInMemoryMetadata::getSampleBlock() const } Block StorageInMemoryMetadata::getSampleBlockForColumns( - const Names & column_names, const NamesAndTypesList & virtuals, const StorageID & storage_id) const + const Names & column_names, const NamesAndTypesList & virtuals, const StorageID & storage_id, const NamesAndTypesList & expanded_objects) const { Block res; @@ -303,6 +304,17 @@ Block StorageInMemoryMetadata::getSampleBlockForColumns( for (const auto & column : virtuals) columns_map.emplace(column.name, column.type); + for (const auto & column : expanded_objects) + { + columns_map.emplace(column.name, column.type); + for (const auto & subcolumn : column.type->getSubcolumnNames()) + columns_map.emplace(Nested::concatenateName(column.name, subcolumn), column.type->getSubcolumnType(subcolumn)); + } + + std::cerr << "expanded objects: "; + for (const auto & col : expanded_objects) + std::cerr << col.dump() << "\n"; + for (const auto & name : column_names) { auto it = columns_map.find(name); @@ -477,11 +489,19 @@ namespace } } -void StorageInMemoryMetadata::check(const Names & column_names, const NamesAndTypesList & virtuals, const StorageID & storage_id) const +void StorageInMemoryMetadata::check( + const Names & column_names, const NamesAndTypesList & virtuals, const StorageID & storage_id, const NamesAndTypesList & expanded_objects) const { NamesAndTypesList available_columns = getColumns().getAllPhysicalWithSubcolumns(); available_columns.insert(available_columns.end(), virtuals.begin(), virtuals.end()); + for (const auto & column : expanded_objects) + { + available_columns.push_back(column); + for (const auto & subcolumn : column.type->getSubcolumnNames()) + available_columns.emplace_back(column.name, subcolumn, column.type, column.type->getSubcolumnType(subcolumn)); + } + const String list_of_columns = listOfColumns(available_columns); if (column_names.empty()) diff --git a/src/Storages/StorageInMemoryMetadata.h b/src/Storages/StorageInMemoryMetadata.h index 00fb944c0b5..053dba62341 100644 --- a/src/Storages/StorageInMemoryMetadata.h +++ b/src/Storages/StorageInMemoryMetadata.h @@ -147,7 +147,9 @@ struct StorageInMemoryMetadata /// Storage metadata. StorageID required only for more clear exception /// message. Block getSampleBlockForColumns( - const Names & column_names, const NamesAndTypesList & virtuals = {}, const StorageID & storage_id = StorageID::createEmpty()) const; + const Names & column_names, const NamesAndTypesList & virtuals = {}, + const StorageID & storage_id = StorageID::createEmpty(), const NamesAndTypesList & expanded_objects = {}) const; + /// Returns structure with partition key. const KeyDescription & getPartitionKey() const; /// Returns ASTExpressionList of partition key expression for storage or nullptr if there is none. @@ -212,7 +214,8 @@ struct StorageInMemoryMetadata /// Verify that all the requested names are in the table and are set correctly: /// list of names is not empty and the names do not repeat. - void check(const Names & column_names, const NamesAndTypesList & virtuals, const StorageID & storage_id) const; + void check(const Names & column_names, const NamesAndTypesList & virtuals, + const StorageID & storage_id, const NamesAndTypesList & expanded_objects) const; /// Check that all the requested names are in the table and have the correct types. void check(const NamesAndTypesList & columns) const; diff --git a/src/Storages/StorageJoin.cpp b/src/Storages/StorageJoin.cpp index 983b9213a35..c029ebc9b31 100644 --- a/src/Storages/StorageJoin.cpp +++ b/src/Storages/StorageJoin.cpp @@ -497,7 +497,7 @@ Pipe StorageJoin::read( size_t max_block_size, unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); Block source_sample_block = metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()); return Pipe(std::make_shared(join, rwlock, max_block_size, source_sample_block)); diff --git a/src/Storages/StorageLog.cpp b/src/Storages/StorageLog.cpp index 8ed68e0b44d..14d4ee71c46 100644 --- a/src/Storages/StorageLog.cpp +++ b/src/Storages/StorageLog.cpp @@ -652,7 +652,7 @@ Pipe StorageLog::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); auto lock_timeout = getLockTimeout(context); loadMarks(lock_timeout); diff --git a/src/Storages/StorageMemory.cpp b/src/Storages/StorageMemory.cpp index 4cae7367606..7a0ba624891 100644 --- a/src/Storages/StorageMemory.cpp +++ b/src/Storages/StorageMemory.cpp @@ -183,7 +183,7 @@ Pipe StorageMemory::read( size_t /*max_block_size*/, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); if (delay_read_for_global_subqueries) { diff --git a/src/Storages/StorageMongoDB.cpp b/src/Storages/StorageMongoDB.cpp index 2b0200f3643..1e516c71d2d 100644 --- a/src/Storages/StorageMongoDB.cpp +++ b/src/Storages/StorageMongoDB.cpp @@ -81,7 +81,7 @@ Pipe StorageMongoDB::read( { connectIfNotConnected(); - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); Block sample_block; for (const String & column_name : column_names) diff --git a/src/Storages/StorageMySQL.cpp b/src/Storages/StorageMySQL.cpp index bc07679a0de..a3f7ce51fb4 100644 --- a/src/Storages/StorageMySQL.cpp +++ b/src/Storages/StorageMySQL.cpp @@ -74,7 +74,7 @@ Pipe StorageMySQL::read( size_t /*max_block_size*/, unsigned) { - metadata_snapshot->check(column_names_, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names_, getVirtuals(), getStorageID(), getExpandedObjects()); String query = transformQueryForExternalDatabase( query_info_, metadata_snapshot->getColumns().getOrdinary(), diff --git a/src/Storages/StoragePostgreSQL.cpp b/src/Storages/StoragePostgreSQL.cpp index a99568c0036..3549d5700b1 100644 --- a/src/Storages/StoragePostgreSQL.cpp +++ b/src/Storages/StoragePostgreSQL.cpp @@ -70,7 +70,7 @@ Pipe StoragePostgreSQL::read( size_t max_block_size_, unsigned) { - metadata_snapshot->check(column_names_, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names_, getVirtuals(), getStorageID(), getExpandedObjects()); /// Connection is already made to the needed database, so it should not be present in the query; /// remote_table_schema is empty if it is not specified, will access only table_name. diff --git a/src/Storages/StorageS3Cluster.cpp b/src/Storages/StorageS3Cluster.cpp index 8afc0e44023..a9698c0545a 100644 --- a/src/Storages/StorageS3Cluster.cpp +++ b/src/Storages/StorageS3Cluster.cpp @@ -135,7 +135,7 @@ Pipe StorageS3Cluster::read( } } - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); return Pipe::unitePipes(std::move(pipes)); } diff --git a/src/Storages/StorageStripeLog.cpp b/src/Storages/StorageStripeLog.cpp index d845dfb71f2..2db0cc45cbb 100644 --- a/src/Storages/StorageStripeLog.cpp +++ b/src/Storages/StorageStripeLog.cpp @@ -330,7 +330,7 @@ Pipe StorageStripeLog::read( if (!lock) throw Exception("Lock timeout exceeded", ErrorCodes::TIMEOUT_EXCEEDED); - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); NameSet column_names_set(column_names.begin(), column_names.end()); diff --git a/src/Storages/StorageTinyLog.cpp b/src/Storages/StorageTinyLog.cpp index 41c2961e929..e62a35317a7 100644 --- a/src/Storages/StorageTinyLog.cpp +++ b/src/Storages/StorageTinyLog.cpp @@ -484,7 +484,7 @@ Pipe StorageTinyLog::read( const size_t max_block_size, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); auto all_columns = metadata_snapshot->getColumns().getAllWithSubcolumns().addTypes(column_names); diff --git a/src/Storages/StorageValues.cpp b/src/Storages/StorageValues.cpp index ace5ca3667c..ccdd715d8b3 100644 --- a/src/Storages/StorageValues.cpp +++ b/src/Storages/StorageValues.cpp @@ -29,7 +29,7 @@ Pipe StorageValues::read( size_t /*max_block_size*/, unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); /// Get only required columns. Block block; diff --git a/src/Storages/StorageXDBC.cpp b/src/Storages/StorageXDBC.cpp index f94696c716b..16a134de84a 100644 --- a/src/Storages/StorageXDBC.cpp +++ b/src/Storages/StorageXDBC.cpp @@ -106,7 +106,7 @@ Pipe StorageXDBC::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); bridge_helper->startBridgeSync(); return IStorageURLBase::read(column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); diff --git a/src/Storages/System/IStorageSystemOneBlock.h b/src/Storages/System/IStorageSystemOneBlock.h index 36e92d5a1f4..a1e139d0705 100644 --- a/src/Storages/System/IStorageSystemOneBlock.h +++ b/src/Storages/System/IStorageSystemOneBlock.h @@ -41,7 +41,7 @@ public: size_t /*max_block_size*/, unsigned /*num_streams*/) override { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); Block sample_block = metadata_snapshot->getSampleBlock(); MutableColumns res_columns = sample_block.cloneEmptyColumns(); diff --git a/src/Storages/System/StorageSystemColumns.cpp b/src/Storages/System/StorageSystemColumns.cpp index 8f65147bb11..81c048f8e2d 100644 --- a/src/Storages/System/StorageSystemColumns.cpp +++ b/src/Storages/System/StorageSystemColumns.cpp @@ -248,7 +248,7 @@ Pipe StorageSystemColumns::read( const size_t max_block_size, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); /// Create a mask of what columns are needed in the result. diff --git a/src/Storages/System/StorageSystemDisks.cpp b/src/Storages/System/StorageSystemDisks.cpp index 5d7628acb2a..64ea8209b90 100644 --- a/src/Storages/System/StorageSystemDisks.cpp +++ b/src/Storages/System/StorageSystemDisks.cpp @@ -35,7 +35,7 @@ Pipe StorageSystemDisks::read( const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); MutableColumnPtr col_name = ColumnString::create(); MutableColumnPtr col_path = ColumnString::create(); diff --git a/src/Storages/System/StorageSystemNumbers.cpp b/src/Storages/System/StorageSystemNumbers.cpp index f8a0e94bf98..6c6d8352c6e 100644 --- a/src/Storages/System/StorageSystemNumbers.cpp +++ b/src/Storages/System/StorageSystemNumbers.cpp @@ -131,7 +131,7 @@ Pipe StorageSystemNumbers::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); if (limit && *limit < max_block_size) { diff --git a/src/Storages/System/StorageSystemOne.cpp b/src/Storages/System/StorageSystemOne.cpp index 7c28f897121..9237d5568e4 100644 --- a/src/Storages/System/StorageSystemOne.cpp +++ b/src/Storages/System/StorageSystemOne.cpp @@ -29,7 +29,7 @@ Pipe StorageSystemOne::read( const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); Block header{ColumnWithTypeAndName( DataTypeUInt8().createColumn(), diff --git a/src/Storages/System/StorageSystemPartsBase.cpp b/src/Storages/System/StorageSystemPartsBase.cpp index f1c82aa4c63..2ac2f6a9dba 100644 --- a/src/Storages/System/StorageSystemPartsBase.cpp +++ b/src/Storages/System/StorageSystemPartsBase.cpp @@ -41,7 +41,7 @@ bool StorageSystemPartsBase::hasStateColumn(const Names & column_names, const St /// Do not check if only _state column is requested if (!(has_state_column && real_column_names.empty())) - metadata_snapshot->check(real_column_names, {}, getStorageID()); + metadata_snapshot->check(real_column_names, {}, getStorageID(), {}); return has_state_column; } diff --git a/src/Storages/System/StorageSystemReplicas.cpp b/src/Storages/System/StorageSystemReplicas.cpp index fc33c6b421b..79862e3e383 100644 --- a/src/Storages/System/StorageSystemReplicas.cpp +++ b/src/Storages/System/StorageSystemReplicas.cpp @@ -65,7 +65,7 @@ Pipe StorageSystemReplicas::read( const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); const auto access = context->getAccess(); const bool check_access_for_databases = !access->isGranted(AccessType::SHOW_TABLES); diff --git a/src/Storages/System/StorageSystemStoragePolicies.cpp b/src/Storages/System/StorageSystemStoragePolicies.cpp index 48dfadd2b3c..7d117212275 100644 --- a/src/Storages/System/StorageSystemStoragePolicies.cpp +++ b/src/Storages/System/StorageSystemStoragePolicies.cpp @@ -44,7 +44,7 @@ Pipe StorageSystemStoragePolicies::read( const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); MutableColumnPtr col_policy_name = ColumnString::create(); MutableColumnPtr col_volume_name = ColumnString::create(); diff --git a/src/Storages/System/StorageSystemTables.cpp b/src/Storages/System/StorageSystemTables.cpp index 9602339f381..6dd6fa96a05 100644 --- a/src/Storages/System/StorageSystemTables.cpp +++ b/src/Storages/System/StorageSystemTables.cpp @@ -506,7 +506,7 @@ Pipe StorageSystemTables::read( const size_t max_block_size, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); /// Create a mask of what columns are needed in the result. diff --git a/src/Storages/System/StorageSystemZeros.cpp b/src/Storages/System/StorageSystemZeros.cpp index d1456d72685..f3642d3c66a 100644 --- a/src/Storages/System/StorageSystemZeros.cpp +++ b/src/Storages/System/StorageSystemZeros.cpp @@ -99,7 +99,7 @@ Pipe StorageSystemZeros::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); bool use_multiple_streams = multithreaded; From 28a9a5c277bc4632f999e4a19f26922687c47d1f Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 26 Apr 2021 00:57:21 +0300 Subject: [PATCH 0009/1647] dynamic subcolumns: support arrays --- src/Columns/ColumnObject.cpp | 1 - src/DataTypes/DataTypeTuple.cpp | 1 - src/DataTypes/FieldToDataType.cpp | 3 + src/DataTypes/FieldToDataType.h | 8 ++ src/DataTypes/ObjectUtils.cpp | 2 +- src/DataTypes/Serializations/JSONDataParser.h | 68 +++++++++++++++-- .../Serializations/SerializationObject.cpp | 60 ++++++++------- .../Serializations/SerializationObject.h | 3 + .../tests/gtest_json_parser.cpp | 73 +++++++++++++++---- src/Storages/MergeTree/MergeTreeData.cpp | 3 - .../0_stateless/01825_type_json.reference | 10 +++ tests/queries/0_stateless/01825_type_json.sh | 40 ++++++++++ 12 files changed, 217 insertions(+), 55 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json.reference create mode 100644 tests/queries/0_stateless/01825_type_json.sh diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 17594501db1..fdd12c6f617 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -105,7 +105,6 @@ bool ColumnObject::hasSubcolumn(const String & key) const void ColumnObject::addSubcolumn(const String & key, MutableColumnPtr && subcolumn, bool check_size) { - std::cerr << "adding subcolumn: " << key << ", subcolumn: " << subcolumn->dumpStructure() << "\n"; if (check_size && subcolumn->size() != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", diff --git a/src/DataTypes/DataTypeTuple.cpp b/src/DataTypes/DataTypeTuple.cpp index e07870c3c43..f203514d2bb 100644 --- a/src/DataTypes/DataTypeTuple.cpp +++ b/src/DataTypes/DataTypeTuple.cpp @@ -282,7 +282,6 @@ DataTypePtr DataTypeTuple::tryGetSubcolumnType(const String & subcolumn_name) co auto on_continue = [&](size_t pos, const String & next_subcolumn) { return elems[pos]->tryGetSubcolumnType(next_subcolumn); }; auto kek = getSubcolumnEntity(subcolumn_name, on_success, on_continue); - std::cerr << "requested subcolumn: " << subcolumn_name << ", have: " << !!kek << "\n"; return kek; } diff --git a/src/DataTypes/FieldToDataType.cpp b/src/DataTypes/FieldToDataType.cpp index 95f03385121..aca3918d74f 100644 --- a/src/DataTypes/FieldToDataType.cpp +++ b/src/DataTypes/FieldToDataType.cpp @@ -95,6 +95,9 @@ DataTypePtr FieldToDataType::operator() (const DecimalField & x) con DataTypePtr FieldToDataType::operator() (const Array & x) const { + if (assume_array_elements_have_equal_types && !x.empty()) + return std::make_shared(applyVisitor(FieldToDataType(), x[0])); + DataTypes element_types; element_types.reserve(x.size()); diff --git a/src/DataTypes/FieldToDataType.h b/src/DataTypes/FieldToDataType.h index 39457564a2f..3f55937fc67 100644 --- a/src/DataTypes/FieldToDataType.h +++ b/src/DataTypes/FieldToDataType.h @@ -17,6 +17,11 @@ using DataTypePtr = std::shared_ptr; class FieldToDataType : public StaticVisitor { public: + FieldToDataType(bool assume_array_elements_have_equal_types_ = false) + : assume_array_elements_have_equal_types(assume_array_elements_have_equal_types_) + { + } + DataTypePtr operator() (const Null & x) const; DataTypePtr operator() (const UInt64 & x) const; DataTypePtr operator() (const UInt128 & x) const; @@ -34,6 +39,9 @@ public: DataTypePtr operator() (const AggregateFunctionStateData & x) const; DataTypePtr operator() (const UInt256 & x) const; DataTypePtr operator() (const Int256 & x) const; + +private: + bool assume_array_elements_have_equal_types; }; } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 5e7dd24c0fd..f6beb3fdf4d 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -28,7 +28,7 @@ DataTypePtr getDataTypeByColumn(const IColumn & column) if (column.empty()) return std::make_shared(); - return applyVisitor(FieldToDataType(), column[0]); + return applyVisitor(FieldToDataType(true), column[0]); } void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index 18996b7ff25..38634d5d984 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -25,12 +25,21 @@ String getValueAsString(const Element & element) throw Exception(ErrorCodes::LOGICAL_ERROR, "Unsupported type of JSON field"); } +String getNextPath(const String & current_path, const std::string_view & key) +{ + String next_path = current_path; + if (!next_path.empty()) + next_path += "."; + next_path += key; + return next_path; +} + } struct ParseResult { Strings paths; - Strings values; + std::vector values; }; template @@ -61,18 +70,63 @@ public: private: void traverse(const Element & element, String current_path, ParseResult & result) { - /// TODO: support arrays. if (element.isObject()) { auto object = element.getObject(); + result.paths.reserve(result.paths.size() + object.size()); + result.values.reserve(result.values.size() + object.size()); + for (auto it = object.begin(); it != object.end(); ++it) { const auto & [key, value] = *it; - String next_path = current_path; - if (!next_path.empty()) - next_path += "."; - next_path += key; - traverse(value, next_path, result); + traverse(value, getNextPath(current_path, key), result); + } + } + else if (element.isArray()) + { + auto array = element.getArray(); + std::unordered_map arrays_by_path; + size_t current_size = 0; + + for (auto it = array.begin(); it != array.end(); ++it) + { + ParseResult element_result; + traverse(*it, "", element_result); + + NameSet inserted_paths; + const auto & [paths, values] = element_result; + for (size_t i = 0; i < paths.size(); ++i) + { + inserted_paths.insert(paths[i]); + + auto & path_array = arrays_by_path[paths[i]]; + assert(path_array.size() == 0 || path_array.size() == current_size); + + if (path_array.size() == 0) + { + path_array.reserve(paths.size()); + path_array.resize(current_size); + } + + path_array.push_back(values[i]); + } + + for (auto & [path, path_array] : arrays_by_path) + { + if (!inserted_paths.count(path)) + path_array.push_back(Field()); + } + + ++current_size; + } + + result.paths.reserve(result.paths.size() + array.size()); + result.values.reserve(result.paths.size() + array.size()); + + for (const auto & [path, path_array] : arrays_by_path) + { + result.paths.push_back(getNextPath(current_path, path)); + result.values.push_back(path_array); } } else diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index a34a8c3b12a..bfaead33f9d 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -21,6 +21,7 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; extern const int INCORRECT_DATA; extern const int CANNOT_READ_ALL_DATA; + extern const int TYPE_MISMATCH; } template @@ -36,7 +37,6 @@ void SerializationObject::deserializeText(IColumn & column, ReadBuffer & String buf; parser.readInto(buf, istr); - std::cerr << "buf: " << buf << "\n"; auto result = parser.parse(buf.data(), buf.size()); if (!result) throw Exception(ErrorCodes::INCORRECT_DATA, "Cannot parse object"); @@ -52,13 +52,25 @@ void SerializationObject::deserializeText(IColumn & column, ReadBuffer & size_t column_size = column_object.size(); for (size_t i = 0; i < paths.size(); ++i) { + auto value_type = applyVisitor(FieldToDataType(true), values[i]); if (!column_object.hasSubcolumn(paths[i])) { - auto new_column = ColumnString::create()->cloneResized(column_size); + auto new_column = value_type->createColumn()->cloneResized(column_size); + new_column->insert(values[i]); column_object.addSubcolumn(paths[i], std::move(new_column)); } + else + { + auto & subcolumn = column_object.getSubcolumn(paths[i]); + auto column_type = getDataTypeByColumn(subcolumn); - column_object.getSubcolumn(paths[i]).insertData(values[i].data(), values[i].size()); + if (!value_type->equals(*column_type)) + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Type mismatch beetwen value and column. Type of value: {}. Type of column: {}", + value_type->getName(), column_type->getName()); + + subcolumn.insert(values[i]); + } } for (auto & [key, subcolumn] : column_object.getSubcolumns()) @@ -68,29 +80,25 @@ void SerializationObject::deserializeText(IColumn & column, ReadBuffer & } } -namespace +template +template +void SerializationObject::checkSerializationIsSupported(Settings & settings, StatePtr & state) const { + if (settings.position_independent_encoding) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, + "DataTypeObject doesn't support serialization with position independent encoding"); -struct SerializeStateObject : public ISerialization::SerializeBinaryBulkState -{ - std::unordered_map states; -}; - -struct DeserializeStateObject : public ISerialization::DeserializeBinaryBulkState -{ - std::unordered_map states; -}; - + if (state) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, + "DataTypeObject doesn't support serialization with non-trivial state"); } template void SerializationObject::serializeBinaryBulkStatePrefix( SerializeBinaryBulkSettings & settings, - SerializeBinaryBulkStatePtr &) const + SerializeBinaryBulkStatePtr & state) const { - if (settings.position_independent_encoding) - throw Exception(ErrorCodes::NOT_IMPLEMENTED, - "DataTypeObject cannot be serialized with position independent encoding"); + checkSerializationIsSupported(settings, state); } template @@ -98,18 +106,15 @@ void SerializationObject::serializeBinaryBulkStateSuffix( SerializeBinaryBulkSettings & settings, SerializeBinaryBulkStatePtr & state) const { - UNUSED(settings); - UNUSED(state); + checkSerializationIsSupported(settings, state); } template void SerializationObject::deserializeBinaryBulkStatePrefix( DeserializeBinaryBulkSettings & settings, - DeserializeBinaryBulkStatePtr &) const + DeserializeBinaryBulkStatePtr & state) const { - if (settings.position_independent_encoding) - throw Exception(ErrorCodes::NOT_IMPLEMENTED, - "DataTypeObject cannot be deserialized with position independent encoding"); + checkSerializationIsSupported(settings, state); } template @@ -120,8 +125,8 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( SerializeBinaryBulkSettings & settings, SerializeBinaryBulkStatePtr & state) const { + checkSerializationIsSupported(settings, state); const auto & column_object = assert_cast(column); - assert(!settings.position_independent_encoding); settings.path.push_back(Substream::ObjectStructure); if (auto * stream = settings.getter(settings.path)) @@ -130,9 +135,9 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( settings.path.back() = Substream::ObjectElement; for (const auto & [key, subcolumn] : column_object.getSubcolumns()) { + settings.path.back() = Substream::ObjectStructure; settings.path.back().object_key_name = key; - settings.path.back() = Substream::ObjectStructure; if (auto * stream = settings.getter(settings.path)) { auto type = getDataTypeByColumn(*subcolumn); @@ -162,6 +167,7 @@ void SerializationObject::deserializeBinaryBulkWithMultipleStreams( DeserializeBinaryBulkStatePtr & state, SubstreamsCache * cache) const { + checkSerializationIsSupported(settings, state); if (!column->empty()) throw Exception(ErrorCodes::NOT_IMPLEMENTED, "DataTypeObject cannot be deserialized to non-empty column"); @@ -253,7 +259,7 @@ SerializationPtr getObjectSerialization(const String & schema_format) #endif } - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Unknow schema_format '{}'", schema_format); + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Unknown schema format '{}'", schema_format); } } diff --git a/src/DataTypes/Serializations/SerializationObject.h b/src/DataTypes/Serializations/SerializationObject.h index eeb64ca2f21..c78c8d0f921 100644 --- a/src/DataTypes/Serializations/SerializationObject.h +++ b/src/DataTypes/Serializations/SerializationObject.h @@ -44,6 +44,9 @@ public: void deserializeBinary(IColumn & column, ReadBuffer & istr) const override; private: + template + void checkSerializationIsSupported(Settings & settings, StatePtr & state) const; + mutable Parser parser; }; diff --git a/src/DataTypes/Serializations/tests/gtest_json_parser.cpp b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp index 48a4a446c48..dfcf43b1215 100644 --- a/src/DataTypes/Serializations/tests/gtest_json_parser.cpp +++ b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include @@ -6,28 +7,70 @@ using namespace DB; +const String json1 = R"({"k1" : 1, "k2" : {"k3" : "aa", "k4" : 2}})"; + +const String json2 = +R"({"k1" : [ + { + "k2" : "aaa", + "k3" : [{ "k4" : "bbb" }, { "k4" : "ccc" }] + }, + { + "k2" : "ddd", + "k3" : [{ "k4" : "eee" }, { "k4" : "fff" }] + } + ] +})"; + TEST(JSONDataParser, ReadInto) { - String json = R"({"k1" : 1, "k2" : {"k3" : "aa", "k4" : 2}})"; - String json_bad = json + "aaaaaaa"; - JSONDataParser parser; - ReadBufferFromString buf(json_bad); - String res; - parser.readInto(res, buf); - ASSERT_EQ(json, res); + { + String json_bad = json1 + "aaaaaaa"; + + JSONDataParser parser; + ReadBufferFromString buf(json_bad); + String res; + parser.readInto(res, buf); + ASSERT_EQ(json1, res); + } + + { + String json_bad = json2 + "aaaaaaa"; + + JSONDataParser parser; + ReadBufferFromString buf(json_bad); + String res; + parser.readInto(res, buf); + ASSERT_EQ(json2, res); + } } TEST(JSONDataParser, Parse) { - String json = R"({"k1" : 1, "k2" : {"k3" : "aa", "k4" : 2}})"; - JSONDataParser parser; - auto res = parser.parse(json.data(), json.size()); - ASSERT_TRUE(res.has_value()); + { + JSONDataParser parser; + auto res = parser.parse(json1.data(), json1.size()); + ASSERT_TRUE(res.has_value()); + + const auto & [paths, values] = *res; + ASSERT_EQ(paths, (Strings{"k1", "k2.k3", "k2.k4"})); + ASSERT_EQ(values, (std::vector{"1", "aa", "2"})); + } + + { + JSONDataParser parser; + auto res = parser.parse(json2.data(), json2.size()); + ASSERT_TRUE(res.has_value()); + + const auto & [paths, values] = *res; + ASSERT_EQ(paths, (Strings{"k1.k3.k4", "k1.k2"})); + + auto k1k3k4 = Array{Array{"bbb", "ccc"}, Array{"eee", "fff"}}; + auto k1k2 = Array{"aaa", "ddd"}; + ASSERT_EQ(values, (std::vector{k1k3k4, k1k2})); + } - const auto & [paths, values] = *res; - ASSERT_EQ(paths, (Strings{"k1", "k2.k3", "k2.k4"})); - ASSERT_EQ(values, (Strings{"1", "aa", "2"})); } #endif diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index b02abf5da0c..ba376d47b16 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -4518,15 +4518,12 @@ static NamesAndTypesList expandObjectColumnsImpl( auto expanded_type = getLeastCommonTypeForObject(it->second); result_columns.emplace_back(column.name, expanded_type); - std::cerr << "expanded_type: " << expanded_type->getName() << "\n"; - if (with_subcolumns) { for (const auto & subcolumn : expanded_type->getSubcolumnNames()) { result_columns.emplace_back(column.name, subcolumn, expanded_type, expanded_type->getSubcolumnType(subcolumn)); - std::cerr << "adding subcolumn: " << subcolumn << "\n"; } } } diff --git a/tests/queries/0_stateless/01825_type_json.reference b/tests/queries/0_stateless/01825_type_json.reference new file mode 100644 index 00000000000..f29a150ecf0 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json.reference @@ -0,0 +1,10 @@ +1 aa bb c +2 ee ff +3 foo +all_1_1_0 id UInt64 +all_1_1_0 data Tuple(k1 String, `k2.k4` String, `k2.k3` String, k5 String) +all_2_2_0 id UInt64 +all_2_2_0 data Tuple(k5 String) +1 ['aaa','ddd'] [['bbb','ccc'],['eee','fff']] +all_1_1_0 id UInt64 +all_1_1_0 data Tuple(`k1.k3.k4` Array(Array(String)), `k1.k2` Array(String)) diff --git a/tests/queries/0_stateless/01825_type_json.sh b/tests/queries/0_stateless/01825_type_json.sh new file mode 100644 index 00000000000..59cce8b925c --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +set -e + +$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json" +$CLICKHOUSE_CLIENT -q "CREATE TABLE t_json(id UInt64, data Object('JSON')) \ + ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part = 0" + +cat << EOF | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json FORMAT CSV" +1,{"k1":"aa","k2":{"k3":"bb","k4":"c"}} +2,{"k1":"ee","k5":"ff"} +EOF + +cat << EOF | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json FORMAT CSV" +3,{"k5":"foo"} +EOF + +$CLICKHOUSE_CLIENT -q "SELECT id, data.k1, data.k2.k3, data.k2.k4, data.k5 FROM t_json ORDER BY id" +$CLICKHOUSE_CLIENT -q "SELECT name, column, type \ + FROM system.parts_columns WHERE table = 't_json' AND database = '$CLICKHOUSE_DATABASE' ORDER BY name" + +$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json" + +$CLICKHOUSE_CLIENT -q "CREATE TABLE t_json(id UInt64, data Object('JSON')) \ + ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part = 0" + +cat << EOF | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json FORMAT CSV" +1,{"k1":[{"k2":"aaa","k3":[{"k4":"bbb"},{"k4":"ccc"}]},{"k2":"ddd","k3":[{"k4":"eee"},{"k4":"fff"}]}]} +EOF + +$CLICKHOUSE_CLIENT -q "SELECT id, data.k1.k2, data.k1.k3.k4 FROM t_json ORDER BY id" + +$CLICKHOUSE_CLIENT -q "SELECT name, column, type \ + FROM system.parts_columns WHERE table = 't_json' AND database = '$CLICKHOUSE_DATABASE' ORDER BY name" + +$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json" From 3bc2a0820c8b41fe391a8df8e5267ae8e4322e01 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 3 May 2021 03:56:19 +0300 Subject: [PATCH 0010/1647] dynamic subcolumns: support merges --- src/DataTypes/Serializations/JSONDataParser.h | 4 +- .../Serializations/SerializationObject.cpp | 1 - src/Functions/FunctionsConversion.h | 63 ++++++++++++++++--- src/IO/ReadHelpers.cpp | 6 +- src/Storages/MergeTree/MergeTreeData.cpp | 14 +++-- src/Storages/MergeTree/MergeTreeData.h | 3 +- .../MergeTree/MergeTreeDataMergerMutator.cpp | 2 + .../MergeTree/MergeTreeSequentialSource.cpp | 3 +- src/Storages/StorageInMemoryMetadata.cpp | 11 ++-- 9 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index 38634d5d984..f778a1c26f6 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -120,8 +120,8 @@ private: ++current_size; } - result.paths.reserve(result.paths.size() + array.size()); - result.values.reserve(result.paths.size() + array.size()); + result.paths.reserve(result.paths.size() + arrays_by_path.size()); + result.values.reserve(result.paths.size() + arrays_by_path.size()); for (const auto & [path, path_array] : arrays_by_path) { diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index bfaead33f9d..16c9425d601 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -132,7 +132,6 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( if (auto * stream = settings.getter(settings.path)) writeVarUInt(column_object.getSubcolumns().size(), *stream); - settings.path.back() = Substream::ObjectElement; for (const auto & [key, subcolumn] : column_object.getSubcolumns()) { settings.path.back() = Substream::ObjectStructure; diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 9b40be58862..52d6d19af53 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -2580,20 +2580,57 @@ private: throw Exception{"CAST AS Tuple can only be performed between tuple types or from String.\nLeft type: " + from_type_untyped->getName() + ", right type: " + to_type->getName(), ErrorCodes::TYPE_MISMATCH}; - if (from_type->getElements().size() != to_type->getElements().size()) - throw Exception{"CAST AS Tuple can only be performed between tuple types with the same number of elements or from String.\n" - "Left type: " + from_type->getName() + ", right type: " + to_type->getName(), ErrorCodes::TYPE_MISMATCH}; - const auto & from_element_types = from_type->getElements(); const auto & to_element_types = to_type->getElements(); - auto element_wrappers = getElementWrappers(from_element_types, to_element_types); - return [element_wrappers, from_element_types, to_element_types] + std::vector element_wrappers; + std::vector> to_reverse_index; + + if (from_type->haveExplicitNames() && to_type->haveExplicitNames()) + { + const auto & from_names = from_type->getElementNames(); + std::unordered_map from_positions; + from_positions.reserve(from_names.size()); + for (size_t i = 0; i < from_names.size(); ++i) + from_positions[from_names[i]] = i; + + const auto & to_names = to_type->getElementNames(); + element_wrappers.reserve(to_names.size()); + to_reverse_index.reserve(from_names.size()); + + for (size_t i = 0; i < to_names.size(); ++i) + { + auto it = from_positions.find(to_names[i]); + if (it != from_positions.end()) + { + element_wrappers.emplace_back(prepareUnpackDictionaries(from_element_types[it->second], to_element_types[i])); + to_reverse_index.emplace_back(it->second); + } + else + { + element_wrappers.emplace_back(); + to_reverse_index.emplace_back(); + } + } + } + else + { + if (from_element_types.size() != to_element_types.size()) + throw Exception{"CAST AS Tuple can only be performed between tuple types with the same number of elements or from String.\n" + "Left type: " + from_type->getName() + ", right type: " + to_type->getName(), ErrorCodes::TYPE_MISMATCH}; + + element_wrappers = getElementWrappers(from_element_types, to_element_types); + to_reverse_index.reserve(to_element_types.size()); + for (size_t i = 0; i < to_element_types.size(); ++i) + to_reverse_index.emplace_back(i); + } + + return [element_wrappers, from_element_types, to_element_types, to_reverse_index] (ColumnsWithTypeAndName & arguments, const DataTypePtr &, const ColumnNullable * nullable_source, size_t input_rows_count) -> ColumnPtr { const auto * col = arguments.front().column.get(); - size_t tuple_size = from_element_types.size(); + size_t tuple_size = to_element_types.size(); const ColumnTuple & column_tuple = typeid_cast(*col); Columns converted_columns(tuple_size); @@ -2601,8 +2638,16 @@ private: /// invoke conversion for each element for (size_t i = 0; i < tuple_size; ++i) { - ColumnsWithTypeAndName element = {{column_tuple.getColumns()[i], from_element_types[i], "" }}; - converted_columns[i] = element_wrappers[i](element, to_element_types[i], nullable_source, input_rows_count); + if (to_reverse_index[i]) + { + size_t from_idx = *to_reverse_index[i]; + ColumnsWithTypeAndName element = {{column_tuple.getColumns()[from_idx], from_element_types[from_idx], "" }}; + converted_columns[i] = element_wrappers[i](element, to_element_types[i], nullable_source, input_rows_count); + } + else + { + converted_columns[i] = to_element_types[i]->createColumn()->cloneResized(input_rows_count); + } } return ColumnTuple::create(converted_columns); diff --git a/src/IO/ReadHelpers.cpp b/src/IO/ReadHelpers.cpp index 60dbe8cac51..cb6d43f859f 100644 --- a/src/IO/ReadHelpers.cpp +++ b/src/IO/ReadHelpers.cpp @@ -768,7 +768,7 @@ ReturnType readJSONObjectPossiblyInvalid(Vector & s, ReadBuffer & buf) }; if (buf.eof() || *buf.position() != '{') - return error("JSON should starts from opening curly bracket", ErrorCodes::INCORRECT_DATA); + return error("JSON should start from opening curly bracket", ErrorCodes::INCORRECT_DATA); s.push_back(*buf.position()); ++buf.position(); @@ -795,8 +795,12 @@ ReturnType readJSONObjectPossiblyInvalid(Vector & s, ReadBuffer & buf) s.push_back(*buf.position()); ++buf.position(); + if (balance == 0) return ReturnType(true); + + if (balance < 0) + break; } return error("JSON should have equal number of opening and closing brackets", ErrorCodes::INCORRECT_DATA); diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index ba376d47b16..9baeb6e20fd 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -4536,9 +4536,9 @@ static NamesAndTypesList expandObjectColumnsImpl( return result_columns; } -NamesAndTypesList MergeTreeData::expandObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns) const +NamesAndTypesList MergeTreeData::expandObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns) { - /// Firstly fast check if there are any Object columns. + /// Firstly fast check without locking parts, if there are any Object columns. NameSet requested_to_expand = getNamesOfObjectColumns(columns_list); if (requested_to_expand.empty()) return columns_list; @@ -4548,7 +4548,7 @@ NamesAndTypesList MergeTreeData::expandObjectColumns(const DataPartsVector & par NamesAndTypesList MergeTreeData::expandObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const { - /// Firstly fast check if there are any Object columns. + /// Firstly fast check without locking parts, if there are any Object columns. NameSet requested_to_expand = getNamesOfObjectColumns(columns_list); if (requested_to_expand.empty()) return columns_list; @@ -4556,7 +4556,13 @@ NamesAndTypesList MergeTreeData::expandObjectColumns(const NamesAndTypesList & c return expandObjectColumnsImpl(getDataPartsVector(), columns_list, requested_to_expand, with_subcolumns); } +/// TODO: bad code. NamesAndTypesList MergeTreeData::getExpandedObjects() const +{ + return getExpandedObjects(getDataPartsVector()); +} + +NamesAndTypesList MergeTreeData::getExpandedObjects(const DataPartsVector & parts) const { auto metadata_snapshot = getInMemoryMetadataPtr(); auto columns = metadata_snapshot->getColumns().getAllPhysical(); @@ -4566,7 +4572,7 @@ NamesAndTypesList MergeTreeData::getExpandedObjects() const if (isObject(column.type)) result_columns.push_back(column); - return expandObjectColumns(result_columns, false); + return expandObjectColumns(parts, result_columns, false); } CurrentlySubmergingEmergingTagger::~CurrentlySubmergingEmergingTagger() diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index c8adee236a0..3ec20a12687 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -618,8 +618,9 @@ public: } NamesAndTypesList expandObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const override; - NamesAndTypesList expandObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns) const; + static NamesAndTypesList expandObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns); NamesAndTypesList getExpandedObjects() const override; + NamesAndTypesList getExpandedObjects(const DataPartsVector & parts) const; /// For ATTACH/DETACH/DROP PARTITION. String getPartitionIDFromQuery(const ASTPtr & ast, ContextPtr context) const; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index c97594b0d62..dd38f9cd30e 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -686,6 +686,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor Names all_column_names = metadata_snapshot->getColumns().getNamesOfPhysical(); NamesAndTypesList storage_columns = metadata_snapshot->getColumns().getAllPhysical(); + storage_columns = MergeTreeData::expandObjectColumns(parts, storage_columns, false); const auto data_settings = data.getSettings(); NamesAndTypesList gathering_columns; @@ -919,6 +920,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor } const auto & index_factory = MergeTreeIndexFactory::instance(); + MergedBlockOutputStream to{ new_data_part, metadata_snapshot, diff --git a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp index 972bbce193d..7cc4ade9ffa 100644 --- a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp +++ b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp @@ -43,7 +43,8 @@ MergeTreeSequentialSource::MergeTreeSequentialSource( NamesAndTypesList columns_for_reader; if (take_column_types_from_storage) { - const NamesAndTypesList & physical_columns = metadata_snapshot->getColumns().getAllPhysical(); + auto physical_columns = metadata_snapshot->getColumns().getAllPhysical(); + physical_columns = storage.expandObjectColumns(physical_columns, false); columns_for_reader = physical_columns.addTypes(columns_to_read); } else diff --git a/src/Storages/StorageInMemoryMetadata.cpp b/src/Storages/StorageInMemoryMetadata.cpp index 9a35250723a..327c2bf0d0a 100644 --- a/src/Storages/StorageInMemoryMetadata.cpp +++ b/src/Storages/StorageInMemoryMetadata.cpp @@ -306,15 +306,14 @@ Block StorageInMemoryMetadata::getSampleBlockForColumns( for (const auto & column : expanded_objects) { - columns_map.emplace(column.name, column.type); + columns_map[column.name] = column.type; for (const auto & subcolumn : column.type->getSubcolumnNames()) - columns_map.emplace(Nested::concatenateName(column.name, subcolumn), column.type->getSubcolumnType(subcolumn)); + { + auto full_name = Nested::concatenateName(column.name, subcolumn); + columns_map[full_name] = column.type->getSubcolumnType(subcolumn); + } } - std::cerr << "expanded objects: "; - for (const auto & col : expanded_objects) - std::cerr << col.dump() << "\n"; - for (const auto & name : column_names) { auto it = columns_map.find(name); From e44706911ee225e46426369f2a068caed33d5d58 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 5 May 2021 02:02:54 +0300 Subject: [PATCH 0011/1647] dynamic columns: better getting of sample block --- src/Interpreters/InterpreterOptimizeQuery.cpp | 2 +- src/Interpreters/InterpreterSelectQuery.cpp | 6 +- src/Interpreters/TreeRewriter.cpp | 15 +-- .../getHeaderForProcessingStage.cpp | 6 +- .../QueryPlan/ReadFromMergeTree.cpp | 2 +- src/Storages/ColumnsDescription.cpp | 54 ++++++++--- src/Storages/ColumnsDescription.h | 37 ++++++++ src/Storages/IStorage.cpp | 91 ++++++++++++++++++- src/Storages/IStorage.h | 11 ++- src/Storages/Kafka/KafkaBlockInputStream.cpp | 4 +- .../MergeTree/MergeTreeBlockReadUtils.cpp | 8 +- src/Storages/MergeTree/MergeTreeData.cpp | 19 ---- src/Storages/MergeTree/MergeTreeData.h | 2 - .../MergeTree/MergeTreeDataMergerMutator.cpp | 1 + .../MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- src/Storages/MergeTree/MergeTreeReadPool.cpp | 2 +- .../MergeTreeReverseSelectProcessor.cpp | 2 +- .../MergeTree/MergeTreeSelectProcessor.cpp | 2 +- .../MergeTree/MergeTreeSequentialSource.cpp | 6 +- .../RabbitMQ/RabbitMQBlockInputStream.cpp | 5 +- src/Storages/RabbitMQ/StorageRabbitMQ.cpp | 4 +- .../RocksDB/StorageEmbeddedRocksDB.cpp | 2 +- src/Storages/StorageBuffer.cpp | 3 +- src/Storages/StorageFile.cpp | 8 +- src/Storages/StorageGenerateRandom.cpp | 2 +- src/Storages/StorageInMemoryMetadata.cpp | 89 ++---------------- src/Storages/StorageInMemoryMetadata.h | 16 +--- src/Storages/StorageJoin.cpp | 4 +- src/Storages/StorageLog.cpp | 2 +- src/Storages/StorageMemory.cpp | 4 +- src/Storages/StorageMongoDB.cpp | 2 +- src/Storages/StorageMySQL.cpp | 2 +- src/Storages/StorageNull.h | 2 +- src/Storages/StoragePostgreSQL.cpp | 2 +- src/Storages/StorageS3Cluster.cpp | 2 +- src/Storages/StorageStripeLog.cpp | 6 +- src/Storages/StorageTinyLog.cpp | 2 +- src/Storages/StorageValues.cpp | 2 +- src/Storages/StorageView.cpp | 2 +- src/Storages/StorageXDBC.cpp | 4 +- src/Storages/System/IStorageSystemOneBlock.h | 2 +- src/Storages/System/StorageSystemColumns.cpp | 2 +- src/Storages/System/StorageSystemDisks.cpp | 2 +- src/Storages/System/StorageSystemNumbers.cpp | 2 +- src/Storages/System/StorageSystemOne.cpp | 2 +- .../System/StorageSystemPartsBase.cpp | 2 +- src/Storages/System/StorageSystemReplicas.cpp | 2 +- .../System/StorageSystemStoragePolicies.cpp | 2 +- src/Storages/System/StorageSystemTables.cpp | 2 +- src/Storages/System/StorageSystemZeros.cpp | 2 +- 50 files changed, 250 insertions(+), 207 deletions(-) diff --git a/src/Interpreters/InterpreterOptimizeQuery.cpp b/src/Interpreters/InterpreterOptimizeQuery.cpp index b50ce8b85df..099e23fbcc5 100644 --- a/src/Interpreters/InterpreterOptimizeQuery.cpp +++ b/src/Interpreters/InterpreterOptimizeQuery.cpp @@ -46,7 +46,7 @@ BlockIO InterpreterOptimizeQuery::execute() column_names.emplace_back(col->getColumnName()); } - metadata_snapshot->check(column_names, NamesAndTypesList{}, table_id, {}); + table->check(metadata_snapshot, column_names); Names required_columns; { required_columns = metadata_snapshot->getColumnsRequiredForSortingKey(); diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 6a6c5185441..8ff91427de3 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -475,8 +475,7 @@ InterpreterSelectQuery::InterpreterSelectQuery( } } - source_header = metadata_snapshot->getSampleBlockForColumns( - required_columns, storage->getVirtuals(), storage->getStorageID(), storage->getExpandedObjects()); + source_header = storage->getSampleBlockForColumns(metadata_snapshot, required_columns); } /// Calculate structure of the result. @@ -1736,8 +1735,7 @@ void InterpreterSelectQuery::executeFetchColumns(QueryProcessingStage::Enum proc /// Create step which reads from empty source if storage has no data. if (!query_plan.isInitialized()) { - auto header = metadata_snapshot->getSampleBlockForColumns( - required_columns, storage->getVirtuals(), storage->getStorageID(), storage->getExpandedObjects()); + auto header = storage->getSampleBlockForColumns(metadata_snapshot, required_columns); addEmptySourceToQueryPlan(query_plan, header, query_info); } diff --git a/src/Interpreters/TreeRewriter.cpp b/src/Interpreters/TreeRewriter.cpp index 2deff98fd0c..f44ff468245 100644 --- a/src/Interpreters/TreeRewriter.cpp +++ b/src/Interpreters/TreeRewriter.cpp @@ -633,19 +633,12 @@ void TreeRewriterResult::collectSourceColumns(bool add_special) { if (storage) { - const ColumnsDescription & columns = metadata_snapshot->getColumns(); - - NamesAndTypesList columns_from_storage; + auto options = GetColumnsOptions(add_special ? GetColumnsOptions::All : GetColumnsOptions::AllPhysical); + options.withExtendedObjects(); if (storage->supportsSubcolumns()) - columns_from_storage = add_special ? columns.getAllWithSubcolumns() : columns.getAllPhysicalWithSubcolumns(); - else - columns_from_storage = add_special ? columns.getAll() : columns.getAllPhysical(); + options.withSubcolumns(); - columns_from_storage = storage->expandObjectColumns(columns_from_storage, storage->supportsSubcolumns()); - - std::cerr << "columns_from_storage: "; - for (const auto & col : columns_from_storage) - std::cerr << col.dump() << "\n"; + auto columns_from_storage = storage->getColumns(metadata_snapshot, options); if (source_columns.empty()) source_columns.swap(columns_from_storage); diff --git a/src/Interpreters/getHeaderForProcessingStage.cpp b/src/Interpreters/getHeaderForProcessingStage.cpp index 3ed6642105f..6f446aa1b71 100644 --- a/src/Interpreters/getHeaderForProcessingStage.cpp +++ b/src/Interpreters/getHeaderForProcessingStage.cpp @@ -47,8 +47,7 @@ Block getHeaderForProcessingStage( { case QueryProcessingStage::FetchColumns: { - Block header = metadata_snapshot->getSampleBlockForColumns( - column_names, storage.getVirtuals(), storage.getStorageID(), storage.getExpandedObjects()); + Block header = storage.getSampleBlockForColumns(metadata_snapshot, column_names); if (query_info.prewhere_info) { @@ -77,8 +76,7 @@ Block getHeaderForProcessingStage( removeJoin(*query->as()); auto stream = std::make_shared( - metadata_snapshot->getSampleBlockForColumns( - column_names, storage.getVirtuals(), storage.getStorageID(), storage.getExpandedObjects())); + storage.getSampleBlockForColumns(metadata_snapshot, column_names)); return InterpreterSelectQuery(query, context, stream, SelectQueryOptions(processed_stage).analyze()).getSampleBlock(); } } diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index 6dccb2b7954..87d24343bff 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -23,7 +23,7 @@ ReadFromMergeTree::ReadFromMergeTree( size_t num_streams_, ReadType read_type_) : ISourceStep(DataStream{.header = MergeTreeBaseSelectProcessor::transformHeader( - metadata_snapshot_->getSampleBlockForColumns(required_columns_, storage_.getVirtuals(), storage_.getStorageID(), storage_.getExpandedObjects()), + storage_.getSampleBlockForColumns(metadata_snapshot_, required_columns_), prewhere_info_, virt_column_names_)}) , storage(storage_) diff --git a/src/Storages/ColumnsDescription.cpp b/src/Storages/ColumnsDescription.cpp index 545911f1465..3dbc210d4aa 100644 --- a/src/Storages/ColumnsDescription.cpp +++ b/src/Storages/ColumnsDescription.cpp @@ -340,6 +340,47 @@ NamesAndTypesList ColumnsDescription::getAll() const return ret; } +static NamesAndTypesList getWithSubcolumns(NamesAndTypesList && source_list) +{ + NamesAndTypesList ret; + for (const auto & col : source_list) + { + ret.emplace_back(col.name, col.type); + for (const auto & subcolumn : col.type->getSubcolumnNames()) + ret.emplace_back(col.name, subcolumn, col.type, col.type->getSubcolumnType(subcolumn)); + } + + return ret; +} + +NamesAndTypesList ColumnsDescription::get(const GetColumnsOptions & options) const +{ + NamesAndTypesList res; + switch (options.kind) + { + case GetColumnsOptions::All: + res = getAll(); + break; + case GetColumnsOptions::AllPhysical: + res = getAllPhysical(); + break; + case GetColumnsOptions::Ordinary: + res = getOrdinary(); + break; + case GetColumnsOptions::Materialized: + res = getMaterialized(); + break; + case GetColumnsOptions::Aliases: + res = getAliases(); + break; + } + + if (options.with_subcolumns) + res = getWithSubcolumns(std::move(res)); + + return res; +} + bool ColumnsDescription::has(const String & column_name) const { return columns.get<1>().find(column_name) != columns.get<1>().end(); @@ -426,19 +467,6 @@ bool ColumnsDescription::hasPhysicalOrSubcolumn(const String & column_name) cons return hasPhysical(column_name) || subcolumns.find(column_name) != subcolumns.end(); } -static NamesAndTypesList getWithSubcolumns(NamesAndTypesList && source_list) -{ - NamesAndTypesList ret; - for (const auto & col : source_list) - { - ret.emplace_back(col.name, col.type); - for (const auto & subcolumn : col.type->getSubcolumnNames()) - ret.emplace_back(col.name, subcolumn, col.type, col.type->getSubcolumnType(subcolumn)); - } - - return ret; -} - NamesAndTypesList ColumnsDescription::getAllWithSubcolumns() const { return getWithSubcolumns(getAll()); diff --git a/src/Storages/ColumnsDescription.h b/src/Storages/ColumnsDescription.h index f7886d0353a..a8707cf60a3 100644 --- a/src/Storages/ColumnsDescription.h +++ b/src/Storages/ColumnsDescription.h @@ -25,6 +25,42 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } +struct GetColumnsOptions +{ + enum Kind : UInt8 + { + All = 0, + AllPhysical = 1, + Ordinary = 2, + Materialized = 3, + Aliases = 4, + }; + + GetColumnsOptions(Kind kind_) : kind(kind_) {} + + GetColumnsOptions & withSubcolumns(bool value = true) + { + with_subcolumns = value; + return *this; + } + + GetColumnsOptions & withVirtuals(bool value = true) + { + with_virtuals = value; + return *this; + } + + GetColumnsOptions & withExtendedObjects(bool value = true) + { + with_extended_objects = value; + return *this; + } + + Kind kind; + bool with_subcolumns = false; + bool with_virtuals = false; + bool with_extended_objects = false; +}; /// Description of a single table column (in CREATE TABLE for example). struct ColumnDescription @@ -75,6 +111,7 @@ public: auto begin() const { return columns.begin(); } auto end() const { return columns.end(); } + NamesAndTypesList get(const GetColumnsOptions & options) const; NamesAndTypesList getOrdinary() const; NamesAndTypesList getMaterialized() const; NamesAndTypesList getAliases() const; diff --git a/src/Storages/IStorage.cpp b/src/Storages/IStorage.cpp index eb78bf4aa84..8bde51e93aa 100644 --- a/src/Storages/IStorage.cpp +++ b/src/Storages/IStorage.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace DB @@ -21,6 +22,10 @@ namespace ErrorCodes extern const int TABLE_IS_DROPPED; extern const int NOT_IMPLEMENTED; extern const int DEADLOCK_AVOIDED; + extern const int NOT_FOUND_COLUMN_IN_BLOCK; + extern const int EMPTY_LIST_OF_COLUMNS_QUERIED; + extern const int NO_SUCH_COLUMN_IN_TABLE; + extern const int COLUMN_QUERIED_MORE_THAN_ONCE; } bool IStorage::isVirtualColumn(const String & column_name, const StorageMetadataPtr & metadata_snapshot) const @@ -105,7 +110,7 @@ void IStorage::read( auto pipe = read(column_names, metadata_snapshot, query_info, context, processed_stage, max_block_size, num_streams); if (pipe.empty()) { - auto header = metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID(), getExpandedObjects()); + auto header = getSampleBlockForColumns(metadata_snapshot, column_names); InterpreterSelectQuery::addEmptySourceToQueryPlan(query_plan, header, query_info); } else @@ -197,6 +202,90 @@ NameDependencies IStorage::getDependentViewsByColumn(ContextPtr context) const return name_deps; } +NamesAndTypesList IStorage::getColumns(const StorageMetadataPtr & metadata_snapshot, const GetColumnsOptions & options) const +{ + auto all_columns = metadata_snapshot->getColumns().get(options); + + if (options.with_virtuals) + { + /// Virtual columns must be appended after ordinary, + /// because user can override them. + auto virtuals = getVirtuals(); + if (!virtuals.empty()) + { + NameSet column_names; + for (const auto & column : all_columns) + column_names.insert(column.name); + for (auto && column : virtuals) + if (!column_names.count(column.name)) + all_columns.push_back(std::move(column)); + } + } + + if (options.with_extended_objects) + all_columns = expandObjectColumns(all_columns, options.with_subcolumns); + + return all_columns; +} + +Block IStorage::getSampleBlockForColumns(const StorageMetadataPtr & metadata_snapshot, const Names & column_names) const +{ + Block res; + + auto all_columns = getColumns( + metadata_snapshot, + GetColumnsOptions(GetColumnsOptions::All) + .withSubcolumns().withVirtuals().withExtendedObjects()); + + std::unordered_map columns_map; + columns_map.reserve(all_columns.size()); + + for (const auto & elem : all_columns) + columns_map.emplace(elem.name, elem.type); + + for (const auto & name : column_names) + { + auto it = columns_map.find(name); + if (it != columns_map.end()) + res.insert({it->second->createColumn(), it->second, it->first}); + else + throw Exception(ErrorCodes::NOT_FOUND_COLUMN_IN_BLOCK, + "Column {} not found in table {}", backQuote(name), getStorageID().getNameForLogs()); + } + + return res; +} + +void IStorage::check(const StorageMetadataPtr & metadata_snapshot, const Names & column_names) const +{ + auto available_columns = getColumns( + metadata_snapshot, + GetColumnsOptions(GetColumnsOptions::AllPhysical) + .withSubcolumns().withVirtuals().withExtendedObjects()).getNames(); + + if (column_names.empty()) + throw Exception(ErrorCodes::EMPTY_LIST_OF_COLUMNS_QUERIED, + "Empty list of columns queried. There are columns: {}", + boost::algorithm::join(available_columns, ",")); + + std::unordered_set columns_set(available_columns.begin(), available_columns.end()); + std::unordered_set unique_names; + + for (const auto & name : column_names) + { + if (columns_set.end() == columns_set.find(name)) + throw Exception(ErrorCodes::NO_SUCH_COLUMN_IN_TABLE, + "There is no column with name {} in table {}. There are columns: ", + backQuote(name), getStorageID().getNameForLogs(), boost::algorithm::join(available_columns, ",")); + + if (unique_names.end() != unique_names.find(name)) + throw Exception(ErrorCodes::COLUMN_QUERIED_MORE_THAN_ONCE, "Column {} queried more than once", name); + + unique_names.insert(name); + } +} + + std::string PrewhereDAGInfo::dump() const { WriteBufferFromOwnString ss; diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index c64a40ac0d2..f3611ea94b4 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -157,6 +157,16 @@ public: /// used without any locks. StorageMetadataPtr getInMemoryMetadataPtr() const { return metadata.get(); } + NamesAndTypesList getColumns(const StorageMetadataPtr & metadata_snapshot, const GetColumnsOptions & options) const; + + /// Block with ordinary + materialized + aliases + virtuals + subcolumns. + /// message. + Block getSampleBlockForColumns(const StorageMetadataPtr & metadata_snapshot, const Names & column_names) const; + + /// Verify that all the requested names are in the table and are set correctly: + /// list of names is not empty and the names do not repeat. + void check(const StorageMetadataPtr & metadata_snapshot, const Names & column_names) const; + /// Update storage metadata. Used in ALTER or initialization of Storage. /// Metadata object is multiversion, so this method can be called without /// any locks. @@ -525,7 +535,6 @@ public: virtual std::optional lifetimeBytes() const { return {}; } virtual NamesAndTypesList expandObjectColumns(const NamesAndTypesList & columns_list, bool /*with_subcolumns*/) const { return columns_list; } - virtual NamesAndTypesList getExpandedObjects() const { return {}; } private: /// Lock required for alter queries (lockForAlter). Always taken for write diff --git a/src/Storages/Kafka/KafkaBlockInputStream.cpp b/src/Storages/Kafka/KafkaBlockInputStream.cpp index 5d9b19b1972..b75f7a4e5be 100644 --- a/src/Storages/Kafka/KafkaBlockInputStream.cpp +++ b/src/Storages/Kafka/KafkaBlockInputStream.cpp @@ -35,7 +35,7 @@ KafkaBlockInputStream::KafkaBlockInputStream( , max_block_size(max_block_size_) , commit_in_suffix(commit_in_suffix_) , non_virtual_header(metadata_snapshot->getSampleBlockNonMaterialized()) - , virtual_header(metadata_snapshot->getSampleBlockForColumns(storage.getVirtualColumnNames(), storage.getVirtuals(), storage.getStorageID())) + , virtual_header(storage.getSampleBlockForColumns(metadata_snapshot, storage.getVirtualColumnNames())) , handle_error_mode(storage.getHandleKafkaErrorMode()) { } @@ -53,7 +53,7 @@ KafkaBlockInputStream::~KafkaBlockInputStream() Block KafkaBlockInputStream::getHeader() const { - return metadata_snapshot->getSampleBlockForColumns(column_names, storage.getVirtuals(), storage.getStorageID()); + return storage.getSampleBlockForColumns(metadata_snapshot, column_names); } void KafkaBlockInputStream::readPrefixImpl() diff --git a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp index cf07f4e2ec9..797f70d2e4c 100644 --- a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp +++ b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp @@ -316,11 +316,11 @@ MergeTreeReadTaskColumns getReadTaskColumns( if (check_columns) { - auto physical_columns = metadata_snapshot->getColumns().getAllWithSubcolumns(); - physical_columns = storage.expandObjectColumns(physical_columns, true); + auto all_columns = storage.getColumns(metadata_snapshot, + GetColumnsOptions(GetColumnsOptions::All).withSubcolumns().withExtendedObjects()); - result.pre_columns = physical_columns.addTypes(pre_column_names); - result.columns = physical_columns.addTypes(column_names); + result.pre_columns = all_columns.addTypes(pre_column_names); + result.columns = all_columns.addTypes(column_names); } else { diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 9baeb6e20fd..2bf41a503a2 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -4556,25 +4556,6 @@ NamesAndTypesList MergeTreeData::expandObjectColumns(const NamesAndTypesList & c return expandObjectColumnsImpl(getDataPartsVector(), columns_list, requested_to_expand, with_subcolumns); } -/// TODO: bad code. -NamesAndTypesList MergeTreeData::getExpandedObjects() const -{ - return getExpandedObjects(getDataPartsVector()); -} - -NamesAndTypesList MergeTreeData::getExpandedObjects(const DataPartsVector & parts) const -{ - auto metadata_snapshot = getInMemoryMetadataPtr(); - auto columns = metadata_snapshot->getColumns().getAllPhysical(); - - NamesAndTypesList result_columns; - for (const auto & column : columns) - if (isObject(column.type)) - result_columns.push_back(column); - - return expandObjectColumns(parts, result_columns, false); -} - CurrentlySubmergingEmergingTagger::~CurrentlySubmergingEmergingTagger() { std::lock_guard lock(storage.currently_submerging_emerging_mutex); diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 3ec20a12687..dab03a6571e 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -619,8 +619,6 @@ public: NamesAndTypesList expandObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const override; static NamesAndTypesList expandObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns); - NamesAndTypesList getExpandedObjects() const override; - NamesAndTypesList getExpandedObjects(const DataPartsVector & parts) const; /// For ATTACH/DETACH/DROP PARTITION. String getPartitionIDFromQuery(const ASTPtr & ast, ContextPtr context) const; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index dd38f9cd30e..87ff7d53c75 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -942,6 +942,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor Block block; while (!is_cancelled() && (block = merged_stream->read())) { + std::cerr << "read block: " << block.dumpStructure() << "\n"; rows_written += block.rows(); to.write(block); diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 8eeb349057a..4ffc99a4d80 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -236,7 +236,7 @@ QueryPlanPtr MergeTreeDataSelectExecutor::readFromParts( } // At this point, empty `part_values` means all parts. - metadata_snapshot->check(real_column_names, data.getVirtuals(), data.getStorageID(), data.getExpandedObjects()); + data.check(metadata_snapshot, real_column_names); const Settings & settings = context->getSettingsRef(); const auto & primary_key = metadata_snapshot->getPrimaryKey(); diff --git a/src/Storages/MergeTree/MergeTreeReadPool.cpp b/src/Storages/MergeTree/MergeTreeReadPool.cpp index 07173eb2118..1d7ae3eb923 100644 --- a/src/Storages/MergeTree/MergeTreeReadPool.cpp +++ b/src/Storages/MergeTree/MergeTreeReadPool.cpp @@ -168,7 +168,7 @@ MarkRanges MergeTreeReadPool::getRestMarks(const IMergeTreeDataPart & part, cons Block MergeTreeReadPool::getHeader() const { - return metadata_snapshot->getSampleBlockForColumns(column_names, data.getVirtuals(), data.getStorageID(), data.getExpandedObjects()); + return data.getSampleBlockForColumns(metadata_snapshot, column_names); } void MergeTreeReadPool::profileFeedback(const ReadBufferFromFileBase::ProfileInfo info) diff --git a/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp b/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp index 251dc4d5467..7d546e43e2f 100644 --- a/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp @@ -30,7 +30,7 @@ MergeTreeReverseSelectProcessor::MergeTreeReverseSelectProcessor( bool quiet) : MergeTreeBaseSelectProcessor{ - metadata_snapshot_->getSampleBlockForColumns(required_columns_, storage_.getVirtuals(), storage_.getStorageID(), storage_.getExpandedObjects()), + storage_.getSampleBlockForColumns(metadata_snapshot_, required_columns_), storage_, metadata_snapshot_, prewhere_info_, max_block_size_rows_, preferred_block_size_bytes_, preferred_max_column_in_block_size_bytes_, reader_settings_, use_uncompressed_cache_, virt_column_names_}, diff --git a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp index 932595b42cd..43f1e440c24 100644 --- a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp @@ -30,7 +30,7 @@ MergeTreeSelectProcessor::MergeTreeSelectProcessor( bool quiet) : MergeTreeBaseSelectProcessor{ - metadata_snapshot_->getSampleBlockForColumns(required_columns_, storage_.getVirtuals(), storage_.getStorageID(), storage_.getExpandedObjects()), + storage_.getSampleBlockForColumns(metadata_snapshot_, required_columns_), storage_, metadata_snapshot_, prewhere_info_, max_block_size_rows_, preferred_block_size_bytes_, preferred_max_column_in_block_size_bytes_, reader_settings_, use_uncompressed_cache_, virt_column_names_}, diff --git a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp index 7cc4ade9ffa..9d27adac6d2 100644 --- a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp +++ b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp @@ -17,7 +17,7 @@ MergeTreeSequentialSource::MergeTreeSequentialSource( bool read_with_direct_io_, bool take_column_types_from_storage, bool quiet) - : SourceWithProgress(metadata_snapshot_->getSampleBlockForColumns(columns_to_read_, storage_.getVirtuals(), storage_.getStorageID(), storage_.getExpandedObjects())) + : SourceWithProgress(storage_.getSampleBlockForColumns(metadata_snapshot_, columns_to_read_)) , storage(storage_) , metadata_snapshot(metadata_snapshot_) , data_part(std::move(data_part_)) @@ -43,8 +43,8 @@ MergeTreeSequentialSource::MergeTreeSequentialSource( NamesAndTypesList columns_for_reader; if (take_column_types_from_storage) { - auto physical_columns = metadata_snapshot->getColumns().getAllPhysical(); - physical_columns = storage.expandObjectColumns(physical_columns, false); + auto physical_columns = storage.getColumns(metadata_snapshot, + GetColumnsOptions(GetColumnsOptions::AllPhysical).withExtendedObjects()); columns_for_reader = physical_columns.addTypes(columns_to_read); } else diff --git a/src/Storages/RabbitMQ/RabbitMQBlockInputStream.cpp b/src/Storages/RabbitMQ/RabbitMQBlockInputStream.cpp index 6c3d3a53c21..ec12f84c341 100644 --- a/src/Storages/RabbitMQ/RabbitMQBlockInputStream.cpp +++ b/src/Storages/RabbitMQ/RabbitMQBlockInputStream.cpp @@ -28,9 +28,8 @@ RabbitMQBlockInputStream::RabbitMQBlockInputStream( , ack_in_suffix(ack_in_suffix_) , non_virtual_header(metadata_snapshot->getSampleBlockNonMaterialized()) , sample_block(non_virtual_header) - , virtual_header(metadata_snapshot->getSampleBlockForColumns( - {"_exchange_name", "_channel_id", "_delivery_tag", "_redelivered", "_message_id", "_timestamp"}, - storage.getVirtuals(), storage.getStorageID())) + , virtual_header(storage.getSampleBlockForColumns(metadata_snapshot, + {"_exchange_name", "_channel_id", "_delivery_tag", "_redelivered", "_message_id", "_timestamp"})) { for (const auto & column : virtual_header) sample_block.insert(column); diff --git a/src/Storages/RabbitMQ/StorageRabbitMQ.cpp b/src/Storages/RabbitMQ/StorageRabbitMQ.cpp index 55629f2a205..0a77f237d24 100644 --- a/src/Storages/RabbitMQ/StorageRabbitMQ.cpp +++ b/src/Storages/RabbitMQ/StorageRabbitMQ.cpp @@ -573,7 +573,7 @@ Pipe StorageRabbitMQ::read( if (num_created_consumers == 0) return {}; - auto sample_block = metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()); + auto sample_block = getSampleBlockForColumns(metadata_snapshot, column_names); auto modified_context = addSettings(local_context); auto block_size = getMaxBlockSize(); @@ -812,7 +812,7 @@ bool StorageRabbitMQ::streamToViews() auto metadata_snapshot = getInMemoryMetadataPtr(); auto column_names = block_io.out->getHeader().getNames(); - auto sample_block = metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()); + auto sample_block = getSampleBlockForColumns(metadata_snapshot, column_names); auto block_size = getMaxBlockSize(); diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index 744d91b0f54..47e8220b74d 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -289,7 +289,7 @@ Pipe StorageEmbeddedRocksDB::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); FieldVectorPtr keys; bool all_scan = false; diff --git a/src/Storages/StorageBuffer.cpp b/src/Storages/StorageBuffer.cpp index afe37d0bcbe..6497a02a661 100644 --- a/src/Storages/StorageBuffer.cpp +++ b/src/Storages/StorageBuffer.cpp @@ -132,8 +132,7 @@ class BufferSource : public SourceWithProgress { public: BufferSource(const Names & column_names_, StorageBuffer::Buffer & buffer_, const StorageBuffer & storage, const StorageMetadataPtr & metadata_snapshot) - : SourceWithProgress( - metadata_snapshot->getSampleBlockForColumns(column_names_, storage.getVirtuals(), storage.getStorageID())) + : SourceWithProgress(storage.getSampleBlockForColumns(metadata_snapshot, column_names_)) , column_names_and_types(metadata_snapshot->getColumns().getAllWithSubcolumns().addTypes(column_names_)) , buffer(buffer_) {} diff --git a/src/Storages/StorageFile.cpp b/src/Storages/StorageFile.cpp index 14b91d29805..7a89e083bc8 100644 --- a/src/Storages/StorageFile.cpp +++ b/src/Storages/StorageFile.cpp @@ -273,7 +273,7 @@ public: const FilesInfoPtr & files_info) { if (storage->isColumnOriented()) - return metadata_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical(), storage->getVirtuals(), storage->getStorageID()); + return storage->getSampleBlockForColumns(metadata_snapshot, columns_description.getNamesOfPhysical()); else return getHeader(metadata_snapshot, files_info->need_path_column, files_info->need_file_column); } @@ -369,7 +369,7 @@ public: auto get_block_for_format = [&]() -> Block { if (storage->isColumnOriented()) - return metadata_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); + return storage->getSampleBlockForColumns(metadata_snapshot, columns_description.getNamesOfPhysical()); return metadata_snapshot->getSampleBlock(); }; @@ -458,7 +458,7 @@ Pipe StorageFile::read( if (paths.size() == 1 && !Poco::File(paths[0]).exists()) { if (context->getSettingsRef().engine_file_empty_if_not_exists) - return Pipe(std::make_shared(metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()))); + return Pipe(std::make_shared(getSampleBlockForColumns(metadata_snapshot, column_names))); else throw Exception("File " + paths[0] + " doesn't exist", ErrorCodes::FILE_DOESNT_EXIST); } @@ -489,7 +489,7 @@ Pipe StorageFile::read( { if (isColumnOriented()) return ColumnsDescription{ - metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()).getNamesAndTypesList()}; + getSampleBlockForColumns(metadata_snapshot, column_names).getNamesAndTypesList()}; else return metadata_snapshot->getColumns(); }; diff --git a/src/Storages/StorageGenerateRandom.cpp b/src/Storages/StorageGenerateRandom.cpp index 167d74cc51d..6ad16b139ba 100644 --- a/src/Storages/StorageGenerateRandom.cpp +++ b/src/Storages/StorageGenerateRandom.cpp @@ -447,7 +447,7 @@ Pipe StorageGenerateRandom::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); Pipes pipes; pipes.reserve(num_streams); diff --git a/src/Storages/StorageInMemoryMetadata.cpp b/src/Storages/StorageInMemoryMetadata.cpp index 327c2bf0d0a..0f291a3dedb 100644 --- a/src/Storages/StorageInMemoryMetadata.cpp +++ b/src/Storages/StorageInMemoryMetadata.cpp @@ -287,47 +287,6 @@ Block StorageInMemoryMetadata::getSampleBlock() const return res; } -Block StorageInMemoryMetadata::getSampleBlockForColumns( - const Names & column_names, const NamesAndTypesList & virtuals, const StorageID & storage_id, const NamesAndTypesList & expanded_objects) const -{ - Block res; - - auto all_columns = getColumns().getAllWithSubcolumns(); - std::unordered_map columns_map; - columns_map.reserve(all_columns.size()); - - for (const auto & elem : all_columns) - columns_map.emplace(elem.name, elem.type); - - /// Virtual columns must be appended after ordinary, because user can - /// override them. - for (const auto & column : virtuals) - columns_map.emplace(column.name, column.type); - - for (const auto & column : expanded_objects) - { - columns_map[column.name] = column.type; - for (const auto & subcolumn : column.type->getSubcolumnNames()) - { - auto full_name = Nested::concatenateName(column.name, subcolumn); - columns_map[full_name] = column.type->getSubcolumnType(subcolumn); - } - } - - for (const auto & name : column_names) - { - auto it = columns_map.find(name); - if (it != columns_map.end()) - res.insert({it->second->createColumn(), it->second, it->first}); - else - throw Exception( - "Column " + backQuote(name) + " not found in table " + (storage_id.empty() ? "" : storage_id.getNameForLogs()), - ErrorCodes::NOT_FOUND_COLUMN_IN_BLOCK); - } - - return res; -} - const KeyDescription & StorageInMemoryMetadata::getPartitionKey() const { return partition_key; @@ -457,18 +416,6 @@ namespace using UniqueStrings = google::sparsehash::dense_hash_set; #endif - String listOfColumns(const NamesAndTypesList & available_columns) - { - WriteBufferFromOwnString ss; - for (auto it = available_columns.begin(); it != available_columns.end(); ++it) - { - if (it != available_columns.begin()) - ss << ", "; - ss << it->name; - } - return ss.str(); - } - NamesAndTypesMap getColumnsMap(const NamesAndTypesList & columns) { NamesAndTypesMap res; @@ -488,38 +435,16 @@ namespace } } -void StorageInMemoryMetadata::check( - const Names & column_names, const NamesAndTypesList & virtuals, const StorageID & storage_id, const NamesAndTypesList & expanded_objects) const +String listOfColumns(const NamesAndTypesList & available_columns) { - NamesAndTypesList available_columns = getColumns().getAllPhysicalWithSubcolumns(); - available_columns.insert(available_columns.end(), virtuals.begin(), virtuals.end()); - - for (const auto & column : expanded_objects) + WriteBufferFromOwnString ss; + for (auto it = available_columns.begin(); it != available_columns.end(); ++it) { - available_columns.push_back(column); - for (const auto & subcolumn : column.type->getSubcolumnNames()) - available_columns.emplace_back(column.name, subcolumn, column.type, column.type->getSubcolumnType(subcolumn)); - } - - const String list_of_columns = listOfColumns(available_columns); - - if (column_names.empty()) - throw Exception("Empty list of columns queried. There are columns: " + list_of_columns, ErrorCodes::EMPTY_LIST_OF_COLUMNS_QUERIED); - - const auto columns_map = getColumnsMap(available_columns); - - auto unique_names = initUniqueStrings(); - for (const auto & name : column_names) - { - if (columns_map.end() == columns_map.find(name)) - throw Exception( - "There is no column with name " + backQuote(name) + " in table " + storage_id.getNameForLogs() + ". There are columns: " + list_of_columns, - ErrorCodes::NO_SUCH_COLUMN_IN_TABLE); - - if (unique_names.end() != unique_names.find(name)) - throw Exception("Column " + name + " queried more than once", ErrorCodes::COLUMN_QUERIED_MORE_THAN_ONCE); - unique_names.insert(name); + if (it != available_columns.begin()) + ss << ", "; + ss << it->name; } + return ss.str(); } void StorageInMemoryMetadata::check(const NamesAndTypesList & provided_columns) const diff --git a/src/Storages/StorageInMemoryMetadata.h b/src/Storages/StorageInMemoryMetadata.h index 053dba62341..cd63d1bee60 100644 --- a/src/Storages/StorageInMemoryMetadata.h +++ b/src/Storages/StorageInMemoryMetadata.h @@ -141,15 +141,6 @@ struct StorageInMemoryMetadata /// Storage metadata. Block getSampleBlockWithVirtuals(const NamesAndTypesList & virtuals) const; - - /// Block with ordinary + materialized + aliases + virtuals. Virtuals have - /// to be explicitly specified, because they are part of Storage type, not - /// Storage metadata. StorageID required only for more clear exception - /// message. - Block getSampleBlockForColumns( - const Names & column_names, const NamesAndTypesList & virtuals = {}, - const StorageID & storage_id = StorageID::createEmpty(), const NamesAndTypesList & expanded_objects = {}) const; - /// Returns structure with partition key. const KeyDescription & getPartitionKey() const; /// Returns ASTExpressionList of partition key expression for storage or nullptr if there is none. @@ -212,11 +203,6 @@ struct StorageInMemoryMetadata const SelectQueryDescription & getSelectQuery() const; bool hasSelectQuery() const; - /// Verify that all the requested names are in the table and are set correctly: - /// list of names is not empty and the names do not repeat. - void check(const Names & column_names, const NamesAndTypesList & virtuals, - const StorageID & storage_id, const NamesAndTypesList & expanded_objects) const; - /// Check that all the requested names are in the table and have the correct types. void check(const NamesAndTypesList & columns) const; @@ -232,4 +218,6 @@ struct StorageInMemoryMetadata using StorageMetadataPtr = std::shared_ptr; using MultiVersionStorageMetadataPtr = MultiVersion; +String listOfColumns(const NamesAndTypesList & available_columns); + } diff --git a/src/Storages/StorageJoin.cpp b/src/Storages/StorageJoin.cpp index c029ebc9b31..040f8897092 100644 --- a/src/Storages/StorageJoin.cpp +++ b/src/Storages/StorageJoin.cpp @@ -497,9 +497,9 @@ Pipe StorageJoin::read( size_t max_block_size, unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); - Block source_sample_block = metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()); + Block source_sample_block = getSampleBlockForColumns(metadata_snapshot, column_names); return Pipe(std::make_shared(join, rwlock, max_block_size, source_sample_block)); } diff --git a/src/Storages/StorageLog.cpp b/src/Storages/StorageLog.cpp index 14d4ee71c46..4beb1a0bbfa 100644 --- a/src/Storages/StorageLog.cpp +++ b/src/Storages/StorageLog.cpp @@ -652,7 +652,7 @@ Pipe StorageLog::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); auto lock_timeout = getLockTimeout(context); loadMarks(lock_timeout); diff --git a/src/Storages/StorageMemory.cpp b/src/Storages/StorageMemory.cpp index 7a0ba624891..d37f018df9f 100644 --- a/src/Storages/StorageMemory.cpp +++ b/src/Storages/StorageMemory.cpp @@ -34,7 +34,7 @@ public: std::shared_ptr data_, std::shared_ptr> parallel_execution_index_, InitializerFunc initializer_func_ = {}) - : SourceWithProgress(metadata_snapshot->getSampleBlockForColumns(column_names_, storage.getVirtuals(), storage.getStorageID())) + : SourceWithProgress(storage.getSampleBlockForColumns(metadata_snapshot, column_names_)) , column_names_and_types(metadata_snapshot->getColumns().getAllWithSubcolumns().addTypes(std::move(column_names_))) , data(data_) , parallel_execution_index(parallel_execution_index_) @@ -183,7 +183,7 @@ Pipe StorageMemory::read( size_t /*max_block_size*/, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); if (delay_read_for_global_subqueries) { diff --git a/src/Storages/StorageMongoDB.cpp b/src/Storages/StorageMongoDB.cpp index 1e516c71d2d..58b6560848b 100644 --- a/src/Storages/StorageMongoDB.cpp +++ b/src/Storages/StorageMongoDB.cpp @@ -81,7 +81,7 @@ Pipe StorageMongoDB::read( { connectIfNotConnected(); - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); Block sample_block; for (const String & column_name : column_names) diff --git a/src/Storages/StorageMySQL.cpp b/src/Storages/StorageMySQL.cpp index a3f7ce51fb4..0a080328dd8 100644 --- a/src/Storages/StorageMySQL.cpp +++ b/src/Storages/StorageMySQL.cpp @@ -74,7 +74,7 @@ Pipe StorageMySQL::read( size_t /*max_block_size*/, unsigned) { - metadata_snapshot->check(column_names_, getVirtuals(), getStorageID(), getExpandedObjects()); + check(metadata_snapshot, column_names_); String query = transformQueryForExternalDatabase( query_info_, metadata_snapshot->getColumns().getOrdinary(), diff --git a/src/Storages/StorageNull.h b/src/Storages/StorageNull.h index 7fe65eb25dc..422d7cfd8e0 100644 --- a/src/Storages/StorageNull.h +++ b/src/Storages/StorageNull.h @@ -31,7 +31,7 @@ public: unsigned) override { return Pipe( - std::make_shared(metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()))); + std::make_shared(getSampleBlockForColumns(metadata_snapshot, column_names))); } bool supportsParallelInsert() const override { return true; } diff --git a/src/Storages/StoragePostgreSQL.cpp b/src/Storages/StoragePostgreSQL.cpp index 3549d5700b1..82d8785bd48 100644 --- a/src/Storages/StoragePostgreSQL.cpp +++ b/src/Storages/StoragePostgreSQL.cpp @@ -70,7 +70,7 @@ Pipe StoragePostgreSQL::read( size_t max_block_size_, unsigned) { - metadata_snapshot->check(column_names_, getVirtuals(), getStorageID(), getExpandedObjects()); + check(metadata_snapshot, column_names_); /// Connection is already made to the needed database, so it should not be present in the query; /// remote_table_schema is empty if it is not specified, will access only table_name. diff --git a/src/Storages/StorageS3Cluster.cpp b/src/Storages/StorageS3Cluster.cpp index a9698c0545a..cffa63030b8 100644 --- a/src/Storages/StorageS3Cluster.cpp +++ b/src/Storages/StorageS3Cluster.cpp @@ -135,7 +135,7 @@ Pipe StorageS3Cluster::read( } } - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); return Pipe::unitePipes(std::move(pipes)); } diff --git a/src/Storages/StorageStripeLog.cpp b/src/Storages/StorageStripeLog.cpp index 2db0cc45cbb..66197864849 100644 --- a/src/Storages/StorageStripeLog.cpp +++ b/src/Storages/StorageStripeLog.cpp @@ -59,7 +59,7 @@ public: IndexForNativeFormat::Blocks::const_iterator index_end) { if (index_begin == index_end) - return metadata_snapshot->getSampleBlockForColumns(column_names, storage.getVirtuals(), storage.getStorageID()); + return storage.getSampleBlockForColumns(metadata_snapshot, column_names); /// TODO: check if possible to always return storage.getSampleBlock() @@ -330,7 +330,7 @@ Pipe StorageStripeLog::read( if (!lock) throw Exception("Lock timeout exceeded", ErrorCodes::TIMEOUT_EXCEEDED); - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); NameSet column_names_set(column_names.begin(), column_names.end()); @@ -339,7 +339,7 @@ Pipe StorageStripeLog::read( String index_file = table_path + "index.mrk"; if (!disk->exists(index_file)) { - return Pipe(std::make_shared(metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()))); + return Pipe(std::make_shared(getSampleBlockForColumns(metadata_snapshot, column_names))); } CompressedReadBufferFromFile index_in(disk->readFile(index_file, 4096)); diff --git a/src/Storages/StorageTinyLog.cpp b/src/Storages/StorageTinyLog.cpp index e62a35317a7..fd317180837 100644 --- a/src/Storages/StorageTinyLog.cpp +++ b/src/Storages/StorageTinyLog.cpp @@ -484,7 +484,7 @@ Pipe StorageTinyLog::read( const size_t max_block_size, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); auto all_columns = metadata_snapshot->getColumns().getAllWithSubcolumns().addTypes(column_names); diff --git a/src/Storages/StorageValues.cpp b/src/Storages/StorageValues.cpp index ccdd715d8b3..0c9de61783a 100644 --- a/src/Storages/StorageValues.cpp +++ b/src/Storages/StorageValues.cpp @@ -29,7 +29,7 @@ Pipe StorageValues::read( size_t /*max_block_size*/, unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); /// Get only required columns. Block block; diff --git a/src/Storages/StorageView.cpp b/src/Storages/StorageView.cpp index 75bd4b2967f..740702b16c0 100644 --- a/src/Storages/StorageView.cpp +++ b/src/Storages/StorageView.cpp @@ -98,7 +98,7 @@ void StorageView::read( query_plan.addStep(std::move(materializing)); /// And also convert to expected structure. - auto header = metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()); + auto header = getSampleBlockForColumns(metadata_snapshot, column_names); auto convert_actions_dag = ActionsDAG::makeConvertingActions( query_plan.getCurrentDataStream().header.getColumnsWithTypeAndName(), header.getColumnsWithTypeAndName(), diff --git a/src/Storages/StorageXDBC.cpp b/src/Storages/StorageXDBC.cpp index 16a134de84a..8053bbd3446 100644 --- a/src/Storages/StorageXDBC.cpp +++ b/src/Storages/StorageXDBC.cpp @@ -106,7 +106,7 @@ Pipe StorageXDBC::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); bridge_helper->startBridgeSync(); return IStorageURLBase::read(column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); @@ -140,7 +140,7 @@ BlockOutputStreamPtr StorageXDBC::write(const ASTPtr & /*query*/, const StorageM Block StorageXDBC::getHeaderBlock(const Names & column_names, const StorageMetadataPtr & metadata_snapshot) const { - return metadata_snapshot->getSampleBlockForColumns(column_names, getVirtuals(), getStorageID()); + return getSampleBlockForColumns(metadata_snapshot, column_names); } std::string StorageXDBC::getName() const diff --git a/src/Storages/System/IStorageSystemOneBlock.h b/src/Storages/System/IStorageSystemOneBlock.h index a1e139d0705..5ce2f37bb29 100644 --- a/src/Storages/System/IStorageSystemOneBlock.h +++ b/src/Storages/System/IStorageSystemOneBlock.h @@ -41,7 +41,7 @@ public: size_t /*max_block_size*/, unsigned /*num_streams*/) override { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); Block sample_block = metadata_snapshot->getSampleBlock(); MutableColumns res_columns = sample_block.cloneEmptyColumns(); diff --git a/src/Storages/System/StorageSystemColumns.cpp b/src/Storages/System/StorageSystemColumns.cpp index 81c048f8e2d..424ba6347c2 100644 --- a/src/Storages/System/StorageSystemColumns.cpp +++ b/src/Storages/System/StorageSystemColumns.cpp @@ -248,7 +248,7 @@ Pipe StorageSystemColumns::read( const size_t max_block_size, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); /// Create a mask of what columns are needed in the result. diff --git a/src/Storages/System/StorageSystemDisks.cpp b/src/Storages/System/StorageSystemDisks.cpp index 64ea8209b90..51c62a1614d 100644 --- a/src/Storages/System/StorageSystemDisks.cpp +++ b/src/Storages/System/StorageSystemDisks.cpp @@ -35,7 +35,7 @@ Pipe StorageSystemDisks::read( const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); MutableColumnPtr col_name = ColumnString::create(); MutableColumnPtr col_path = ColumnString::create(); diff --git a/src/Storages/System/StorageSystemNumbers.cpp b/src/Storages/System/StorageSystemNumbers.cpp index 6c6d8352c6e..01bf75f6914 100644 --- a/src/Storages/System/StorageSystemNumbers.cpp +++ b/src/Storages/System/StorageSystemNumbers.cpp @@ -131,7 +131,7 @@ Pipe StorageSystemNumbers::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); if (limit && *limit < max_block_size) { diff --git a/src/Storages/System/StorageSystemOne.cpp b/src/Storages/System/StorageSystemOne.cpp index 9237d5568e4..09b4c438422 100644 --- a/src/Storages/System/StorageSystemOne.cpp +++ b/src/Storages/System/StorageSystemOne.cpp @@ -29,7 +29,7 @@ Pipe StorageSystemOne::read( const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); Block header{ColumnWithTypeAndName( DataTypeUInt8().createColumn(), diff --git a/src/Storages/System/StorageSystemPartsBase.cpp b/src/Storages/System/StorageSystemPartsBase.cpp index 2ac2f6a9dba..15f39b272e0 100644 --- a/src/Storages/System/StorageSystemPartsBase.cpp +++ b/src/Storages/System/StorageSystemPartsBase.cpp @@ -41,7 +41,7 @@ bool StorageSystemPartsBase::hasStateColumn(const Names & column_names, const St /// Do not check if only _state column is requested if (!(has_state_column && real_column_names.empty())) - metadata_snapshot->check(real_column_names, {}, getStorageID(), {}); + check(metadata_snapshot, real_column_names); return has_state_column; } diff --git a/src/Storages/System/StorageSystemReplicas.cpp b/src/Storages/System/StorageSystemReplicas.cpp index 79862e3e383..2d4c90b5954 100644 --- a/src/Storages/System/StorageSystemReplicas.cpp +++ b/src/Storages/System/StorageSystemReplicas.cpp @@ -65,7 +65,7 @@ Pipe StorageSystemReplicas::read( const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); const auto access = context->getAccess(); const bool check_access_for_databases = !access->isGranted(AccessType::SHOW_TABLES); diff --git a/src/Storages/System/StorageSystemStoragePolicies.cpp b/src/Storages/System/StorageSystemStoragePolicies.cpp index 7d117212275..8f20ab63f25 100644 --- a/src/Storages/System/StorageSystemStoragePolicies.cpp +++ b/src/Storages/System/StorageSystemStoragePolicies.cpp @@ -44,7 +44,7 @@ Pipe StorageSystemStoragePolicies::read( const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); MutableColumnPtr col_policy_name = ColumnString::create(); MutableColumnPtr col_volume_name = ColumnString::create(); diff --git a/src/Storages/System/StorageSystemTables.cpp b/src/Storages/System/StorageSystemTables.cpp index 6dd6fa96a05..6b30cdefe35 100644 --- a/src/Storages/System/StorageSystemTables.cpp +++ b/src/Storages/System/StorageSystemTables.cpp @@ -506,7 +506,7 @@ Pipe StorageSystemTables::read( const size_t max_block_size, const unsigned /*num_streams*/) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); /// Create a mask of what columns are needed in the result. diff --git a/src/Storages/System/StorageSystemZeros.cpp b/src/Storages/System/StorageSystemZeros.cpp index f3642d3c66a..6292ff4d414 100644 --- a/src/Storages/System/StorageSystemZeros.cpp +++ b/src/Storages/System/StorageSystemZeros.cpp @@ -99,7 +99,7 @@ Pipe StorageSystemZeros::read( size_t max_block_size, unsigned num_streams) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID(), getExpandedObjects() ); + check(metadata_snapshot, column_names); bool use_multiple_streams = multithreaded; From f22f22c4febba2c2af7fd9e819205d1035342d82 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 6 May 2021 03:40:17 +0300 Subject: [PATCH 0012/1647] dynamic columns: support of different types --- src/Columns/ColumnArray.cpp | 10 ++ src/Columns/ColumnArray.h | 2 + src/Columns/ColumnObject.cpp | 100 ++++++++++++------ src/Columns/ColumnObject.h | 38 +++++-- src/DataTypes/FieldToDataType.cpp | 3 - src/DataTypes/FieldToDataType.h | 8 -- src/DataTypes/ObjectUtils.cpp | 6 +- src/DataTypes/Serializations/ColumnVariant | 0 src/DataTypes/Serializations/JSONDataParser.h | 14 +-- .../Serializations/SerializationObject.cpp | 75 +++++++++---- 10 files changed, 171 insertions(+), 85 deletions(-) create mode 100644 src/DataTypes/Serializations/ColumnVariant diff --git a/src/Columns/ColumnArray.cpp b/src/Columns/ColumnArray.cpp index 0b22a7fa7a0..10fe4f04b34 100644 --- a/src/Columns/ColumnArray.cpp +++ b/src/Columns/ColumnArray.cpp @@ -1216,6 +1216,16 @@ void ColumnArray::gather(ColumnGathererStream & gatherer) gatherer.gather(*this); } +size_t ColumnArray::getNumberOfDimensions() const +{ + const auto * nested_array = checkAndGetColumn(*data); + if (!nested_array) + return 1; + return 1 + nested_array->getNumberOfDimensions(); /// Every modern C++ compiler optimizes tail recursion. +} + + + // size_t ColumnArray::getNumberOfDefaultRows(size_t step) const // { // const auto & offsets_data = getOffsets(); diff --git a/src/Columns/ColumnArray.h b/src/Columns/ColumnArray.h index cc686855e37..6194b331812 100644 --- a/src/Columns/ColumnArray.h +++ b/src/Columns/ColumnArray.h @@ -145,6 +145,8 @@ public: bool isCollationSupported() const override { return getData().isCollationSupported(); } + size_t getNumberOfDimensions() const; + private: WrappedPtr data; WrappedPtr offsets; diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index fdd12c6f617..249523c3021 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -7,22 +7,45 @@ namespace ErrorCodes { extern const int LOGICAL_ERROR; extern const int ILLEGAL_COLUMN; + extern const int DUPLICATE_COLUMN; } -ColumnObject::ColumnObject(const SubcolumnsMap & subcolumns_) - : subcolumns(subcolumns_) +static TypeId getLeastSuperTypeId(const PaddedPODArray & type_ids) { - checkConsistency(); + } -ColumnObject::ColumnObject(const Names & keys, const Columns & subcolumns_) +ColumnObject::Subcolumn::Subcolumn(const Subcolumn & other) + : data(other.data), type_ids(other.type_ids.begin(), other.type_ids.end()) { - if (keys.size() != subcolumns_.size()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of keys ({}) and subcolumns ({}) are inconsistent"); +} - for (size_t i = 0; i < keys.size(); ++i) - subcolumns[keys[i]] = subcolumns_[i]; +ColumnObject::Subcolumn::Subcolumn(MutableColumnPtr && data_) + : data(std::move(data_)) +{ +} +void ColumnObject::Subcolumn::insert(const Field & field, TypeIndex type_id) +{ + data->insert(field); + type_ids.push_back(type_id); +} + +void ColumnObject::Subcolumn::insertDefault() +{ + data->insertDefault(); + type_ids.push_back(TypeIndex::Nothing); +} + +void ColumnObject::Subcolumn::resize(size_t new_size) +{ + data = data->cloneResized(new_size); + type_ids.resize_fill(new_size, TypeIndex::Nothing); +} + +ColumnObject::ColumnObject(SubcolumnsMap && subcolumns_) + : subcolumns(std::move(subcolumns_)) +{ checkConsistency(); } @@ -31,17 +54,17 @@ void ColumnObject::checkConsistency() const if (subcolumns.empty()) return; - size_t first_size = subcolumns.begin()->second->size(); + size_t first_size = subcolumns.begin()->second.size(); for (const auto & [name, column] : subcolumns) { - if (!column) + if (!column.data) throw Exception(ErrorCodes::LOGICAL_ERROR, "Null subcolumn passed to ColumnObject"); - if (first_size != column->size()) + if (first_size != column.data->size()) { throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of subcolumns are inconsistent in ColumnObject." " Subcolumn '{}' has {} rows, subcolumn '{}' has {} rows", - subcolumns.begin()->first, first_size, name, column->size()); + subcolumns.begin()->first, first_size, name, column.data->size()); } } } @@ -50,16 +73,16 @@ MutableColumnPtr ColumnObject::cloneResized(size_t new_size) const { SubcolumnsMap new_subcolumns; for (const auto & [key, subcolumn] : subcolumns) - new_subcolumns[key] = subcolumn->cloneResized(new_size); + new_subcolumns[key].resize(new_size); - return ColumnObject::create(new_subcolumns); + return ColumnObject::create(std::move(new_subcolumns)); } size_t ColumnObject::byteSize() const { size_t res = 0; for (const auto & [_, column] : subcolumns) - res += column->byteSize(); + res += column.data->byteSize(); return res; } @@ -67,33 +90,24 @@ size_t ColumnObject::allocatedBytes() const { size_t res = 0; for (const auto & [_, column] : subcolumns) - res += column->allocatedBytes(); + res += column.data->allocatedBytes(); return res; } -// const ColumnPtr & ColumnObject::tryGetSubcolumn(const String & key) const -// { -// auto it = subcolumns.find(key); -// if (it == subcolumns.end()) -// return nullptr; - -// return it->second; -// } - -const IColumn & ColumnObject::getSubcolumn(const String & key) const +const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const String & key) const { auto it = subcolumns.find(key); if (it != subcolumns.end()) - return *it->second; + return it->second; throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key); } -IColumn & ColumnObject::getSubcolumn(const String & key) +ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const String & key) { auto it = subcolumns.find(key); if (it != subcolumns.end()) - return *it->second; + return it->second; throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key); } @@ -103,12 +117,34 @@ bool ColumnObject::hasSubcolumn(const String & key) const return subcolumns.count(key) != 0; } -void ColumnObject::addSubcolumn(const String & key, MutableColumnPtr && subcolumn, bool check_size) +void ColumnObject::addSubcolumn(const String & key, MutableColumnPtr && column_sample, size_t new_size, bool check_size) { - if (check_size && subcolumn->size() != size()) + if (subcolumns.count(key)) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key); + + if (!column_sample->empty()) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Cannot add subcolumn '{}' with non-empty sample column", key); + + if (check_size && new_size != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", - key, subcolumn->size(), size()); + key, new_size, size()); + + auto & subcolumn = subcolumns[key]; + subcolumn = std::move(column_sample); + subcolumn.resize(new_size); +} + +void ColumnObject::addSubcolumn(const String & key, Subcolumn && subcolumn, bool check_size) +{ + if (subcolumns.count(key)) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key); + + if (check_size && subcolumn.size() != size()) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", + key, subcolumn.size(), size()); subcolumns[key] = std::move(subcolumn); } diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 6e3d96ceef0..363a7114a06 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -1,8 +1,10 @@ #pragma once #include -#include #include +#include +#include +#include namespace DB { @@ -12,25 +14,43 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -class ColumnObject : public COWHelper +class ColumnObject final : public COWHelper { +public: + struct Subcolumn + { + Subcolumn() = default; + Subcolumn(const Subcolumn & other); + Subcolumn(MutableColumnPtr && data_); + Subcolumn & operator=(Subcolumn && other) = default; + + WrappedPtr data; + PaddedPODArray type_ids; + + size_t size() const { return data->size(); } + void insert(const Field & field, TypeIndex type_id); + void insertDefault(); + void resize(size_t new_size); + }; + + using SubcolumnsMap = std::unordered_map; + private: - using SubcolumnsMap = std::unordered_map; SubcolumnsMap subcolumns; public: ColumnObject() = default; - ColumnObject(const SubcolumnsMap & subcolumns_); - ColumnObject(const Names & keys, const Columns & subcolumns_); + ColumnObject(SubcolumnsMap && subcolumns_); void checkConsistency() const; bool hasSubcolumn(const String & key) const; - const IColumn & getSubcolumn(const String & key) const; - IColumn & getSubcolumn(const String & key); + const Subcolumn & getSubcolumn(const String & key) const; + Subcolumn & getSubcolumn(const String & key); - void addSubcolumn(const String & key, MutableColumnPtr && subcolumn, bool check_size = false); + void addSubcolumn(const String & key, MutableColumnPtr && column_sample, size_t new_size, bool check_size = false); + void addSubcolumn(const String & key, Subcolumn && subcolumn, bool check_size = false); const SubcolumnsMap & getSubcolumns() const { return subcolumns; } SubcolumnsMap & getSubcolumns() { return subcolumns; } @@ -41,7 +61,7 @@ public: const char * getFamilyName() const override { return "Object"; } - size_t size() const override { return subcolumns.empty() ? 0 : subcolumns.begin()->second->size(); } + size_t size() const override { return subcolumns.empty() ? 0 : subcolumns.begin()->second.size(); } MutableColumnPtr cloneResized(size_t new_size) const override; diff --git a/src/DataTypes/FieldToDataType.cpp b/src/DataTypes/FieldToDataType.cpp index aca3918d74f..95f03385121 100644 --- a/src/DataTypes/FieldToDataType.cpp +++ b/src/DataTypes/FieldToDataType.cpp @@ -95,9 +95,6 @@ DataTypePtr FieldToDataType::operator() (const DecimalField & x) con DataTypePtr FieldToDataType::operator() (const Array & x) const { - if (assume_array_elements_have_equal_types && !x.empty()) - return std::make_shared(applyVisitor(FieldToDataType(), x[0])); - DataTypes element_types; element_types.reserve(x.size()); diff --git a/src/DataTypes/FieldToDataType.h b/src/DataTypes/FieldToDataType.h index 3f55937fc67..39457564a2f 100644 --- a/src/DataTypes/FieldToDataType.h +++ b/src/DataTypes/FieldToDataType.h @@ -17,11 +17,6 @@ using DataTypePtr = std::shared_ptr; class FieldToDataType : public StaticVisitor { public: - FieldToDataType(bool assume_array_elements_have_equal_types_ = false) - : assume_array_elements_have_equal_types(assume_array_elements_have_equal_types_) - { - } - DataTypePtr operator() (const Null & x) const; DataTypePtr operator() (const UInt64 & x) const; DataTypePtr operator() (const UInt128 & x) const; @@ -39,9 +34,6 @@ public: DataTypePtr operator() (const AggregateFunctionStateData & x) const; DataTypePtr operator() (const UInt256 & x) const; DataTypePtr operator() (const Int256 & x) const; - -private: - bool assume_array_elements_have_equal_types; }; } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index f6beb3fdf4d..f1950e2f8b5 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -28,7 +28,7 @@ DataTypePtr getDataTypeByColumn(const IColumn & column) if (column.empty()) return std::make_shared(); - return applyVisitor(FieldToDataType(true), column[0]); + return applyVisitor(FieldToDataType(), column[0]); } void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) @@ -54,8 +54,8 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) for (const auto & [key, subcolumn] : subcolumns_map) { tuple_names.push_back(key); - tuple_types.push_back(getDataTypeByColumn(*subcolumn)); - tuple_columns.push_back(subcolumn); + tuple_types.push_back(getDataTypeByColumn(*subcolumn.data)); + tuple_columns.push_back(subcolumn.data); } auto type_tuple = std::make_shared(tuple_types, tuple_names); diff --git a/src/DataTypes/Serializations/ColumnVariant b/src/DataTypes/Serializations/ColumnVariant new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index f778a1c26f6..2fb3a65236f 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -14,13 +14,13 @@ namespace { template -String getValueAsString(const Element & element) +Field getValueAsField(const Element & element) { - if (element.isBool()) return toString(element.getBool()); - if (element.isInt64()) return toString(element.getInt64()); - if (element.isUInt64()) return toString(element.getUInt64()); - if (element.isDouble()) return toString(element.getDouble()); - if (element.isString()) return String(element.getString()); + if (element.isBool()) return element.getBool(); + if (element.isInt64()) return element.getInt64(); + if (element.isUInt64()) return element.getUInt64(); + if (element.isDouble()) return element.getDouble(); + if (element.isString()) return element.getString(); throw Exception(ErrorCodes::LOGICAL_ERROR, "Unsupported type of JSON field"); } @@ -132,7 +132,7 @@ private: else { result.paths.push_back(current_path); - result.values.push_back(getValueAsString(element)); + result.values.push_back(getValueAsField(element)); } } diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 16c9425d601..5d720de4111 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -8,6 +10,8 @@ #include #include #include +#include +#include #include #include @@ -30,6 +34,33 @@ void SerializationObject::serializeText(const IColumn & /*column*/, size throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); } +namespace +{ + +DataTypePtr createArrayOfStrings(size_t dim) +{ + DataTypePtr type = std::make_shared(); + for (size_t i = 0; i < dim; ++i) + type = std::make_shared(type); + return type; +} + +size_t getNumberOfDimensions(const IDataType & type) +{ + if (const auto * type_array = typeid_cast(&type)) + return type_array->getNumberOfDimensions(); + return 0; +} + +size_t getNumberOfDimensions(const IColumn & column) +{ + if (const auto * column_array = checkAndGetColumn(column)) + return column_array->getNumberOfDimensions(); + return 0; +} + +} + template void SerializationObject::deserializeText(IColumn & column, ReadBuffer & istr, const FormatSettings &) const { @@ -52,31 +83,29 @@ void SerializationObject::deserializeText(IColumn & column, ReadBuffer & size_t column_size = column_object.size(); for (size_t i = 0; i < paths.size(); ++i) { - auto value_type = applyVisitor(FieldToDataType(true), values[i]); + auto value_type = applyVisitor(FieldToDataType(), values[i]); + size_t value_dim = getNumberOfDimensions(*value_type); + auto array_type = createArrayOfStrings(value_dim); + auto converted_value = convertFieldToTypeOrThrow(values[i], *array_type, value_type.get()); + if (!column_object.hasSubcolumn(paths[i])) - { - auto new_column = value_type->createColumn()->cloneResized(column_size); - new_column->insert(values[i]); - column_object.addSubcolumn(paths[i], std::move(new_column)); - } - else - { - auto & subcolumn = column_object.getSubcolumn(paths[i]); - auto column_type = getDataTypeByColumn(subcolumn); + column_object.addSubcolumn(paths[i], array_type->createColumn(), column_size); - if (!value_type->equals(*column_type)) - throw Exception(ErrorCodes::TYPE_MISMATCH, - "Type mismatch beetwen value and column. Type of value: {}. Type of column: {}", - value_type->getName(), column_type->getName()); + auto & subcolumn = column_object.getSubcolumn(paths[i]); + size_t column_dim = getNumberOfDimensions(*subcolumn.data); - subcolumn.insert(values[i]); - } + if (value_dim != column_dim) + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Dimension of types mismatched beetwen value and column. Dimension of value: {}. Dimension of column: {}", + value_dim, column_dim); + + subcolumn.insert(converted_value, value_type->getTypeId()); } for (auto & [key, subcolumn] : column_object.getSubcolumns()) { if (!paths_set.count(key)) - subcolumn->insertDefault(); + subcolumn.insertDefault(); } } @@ -139,7 +168,7 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( if (auto * stream = settings.getter(settings.path)) { - auto type = getDataTypeByColumn(*subcolumn); + auto type = getDataTypeByColumn(*subcolumn.data); writeStringBinary(key, *stream); writeStringBinary(type->getName(), *stream); } @@ -149,9 +178,9 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( if (auto * stream = settings.getter(settings.path)) { - auto type = getDataTypeByColumn(*subcolumn); + auto type = getDataTypeByColumn(*subcolumn.data); auto serialization = type->getDefaultSerialization(); - serialization->serializeBinaryBulkWithMultipleStreams(*subcolumn, offset, limit, settings, state); + serialization->serializeBinaryBulkWithMultipleStreams(*subcolumn.data, offset, limit, settings, state); } } @@ -204,9 +233,9 @@ void SerializationObject::deserializeBinaryBulkWithMultipleStreams( { auto type = DataTypeFactory::instance().get(type_name); auto serialization = type->getDefaultSerialization(); - ColumnPtr subcolumn = type->createColumn(); - serialization->deserializeBinaryBulkWithMultipleStreams(subcolumn, limit, settings, state, cache); - column_object.addSubcolumn(key, subcolumn->assumeMutable()); + ColumnPtr subcolumn_data = type->createColumn(); + serialization->deserializeBinaryBulkWithMultipleStreams(subcolumn_data, limit, settings, state, cache); + column_object.addSubcolumn(key, subcolumn_data->assumeMutable()); } else { From 0dea7d2e4b20a3a18418afd45e83e676b6e0cb08 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 6 May 2021 08:33:06 +0300 Subject: [PATCH 0013/1647] dynamic columns: support of different types --- src/Columns/ColumnArray.cpp | 34 +++++---- src/Columns/ColumnArray.h | 4 +- src/Columns/ColumnObject.cpp | 72 +++++++++++++------ src/Columns/ColumnObject.h | 12 ++-- src/DataTypes/FieldToDataType.cpp | 6 +- src/DataTypes/FieldToDataType.h | 8 +++ src/DataTypes/IDataType.h | 2 + src/DataTypes/ObjectUtils.cpp | 53 ++++++++++++-- src/DataTypes/ObjectUtils.h | 5 ++ .../Serializations/SerializationObject.cpp | 37 ++-------- src/DataTypes/getLeastSupertype.cpp | 38 ++++++---- src/DataTypes/getLeastSupertype.h | 2 +- src/Interpreters/convertFieldToType.cpp | 2 + src/Processors/Formats/IRowInputFormat.cpp | 2 + 14 files changed, 177 insertions(+), 100 deletions(-) diff --git a/src/Columns/ColumnArray.cpp b/src/Columns/ColumnArray.cpp index 10fe4f04b34..4e1533acef6 100644 --- a/src/Columns/ColumnArray.cpp +++ b/src/Columns/ColumnArray.cpp @@ -1224,25 +1224,23 @@ size_t ColumnArray::getNumberOfDimensions() const return 1 + nested_array->getNumberOfDimensions(); /// Every modern C++ compiler optimizes tail recursion. } +size_t ColumnArray::getNumberOfDefaultRows(size_t step) const +{ + const auto & offsets_data = getOffsets(); + size_t res = 0; + for (size_t i = 0; i < offsets_data.size(); i += step) + res += (offsets_data[i] != offsets_data[i - 1]); + return res; +} -// size_t ColumnArray::getNumberOfDefaultRows(size_t step) const -// { -// const auto & offsets_data = getOffsets(); -// size_t res = 0; -// for (size_t i = 0; i < offsets_data.size(); i += step) -// res += (offsets_data[i] != offsets_data[i - 1]); - -// return res; -// } - -// void ColumnArray::getIndicesOfNonDefaultValues(IColumn::Offsets & indices, size_t from, size_t limit) const -// { -// const auto & offsets_data = getOffsets(); -// size_t to = limit && from + limit < size() ? from + limit : size(); -// for (size_t i = from; i < to; ++i) -// if (offsets_data[i] != offsets_data[i - 1]) -// indices.push_back(i); -// } +void ColumnArray::getIndicesOfNonDefaultValues(IColumn::Offsets & indices, size_t from, size_t limit) const +{ + const auto & offsets_data = getOffsets(); + size_t to = limit && from + limit < size() ? from + limit : size(); + for (size_t i = from; i < to; ++i) + if (offsets_data[i] != offsets_data[i - 1]) + indices.push_back(i); +} } diff --git a/src/Columns/ColumnArray.h b/src/Columns/ColumnArray.h index 6194b331812..ede1aeb8405 100644 --- a/src/Columns/ColumnArray.h +++ b/src/Columns/ColumnArray.h @@ -140,8 +140,8 @@ public: return false; } - // size_t getNumberOfDefaultRows(size_t step) const override; - // void getIndicesOfNonDefaultValues(IColumn::Offsets & indices, size_t from, size_t limit) const override; + size_t getNumberOfDefaultRows(size_t step) const override; + void getIndicesOfNonDefaultValues(IColumn::Offsets & indices, size_t from, size_t limit) const override; bool isCollationSupported() const override { return getData().isCollationSupported(); } diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 249523c3021..a8181d925ba 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -1,4 +1,10 @@ #include +#include +#include +#include +#include +#include +#include namespace DB { @@ -10,37 +16,25 @@ namespace ErrorCodes extern const int DUPLICATE_COLUMN; } -static TypeId getLeastSuperTypeId(const PaddedPODArray & type_ids) -{ - -} - ColumnObject::Subcolumn::Subcolumn(const Subcolumn & other) - : data(other.data), type_ids(other.type_ids.begin(), other.type_ids.end()) + : data(other.data), least_common_type(other.least_common_type) { } ColumnObject::Subcolumn::Subcolumn(MutableColumnPtr && data_) - : data(std::move(data_)) + : data(std::move(data_)), least_common_type(getDataTypeByColumn(*data)) { } -void ColumnObject::Subcolumn::insert(const Field & field, TypeIndex type_id) +void ColumnObject::Subcolumn::insert(const Field & field, const DataTypePtr & value_type) { data->insert(field); - type_ids.push_back(type_id); + least_common_type = getLeastSupertype({least_common_type, value_type}, true); } void ColumnObject::Subcolumn::insertDefault() { data->insertDefault(); - type_ids.push_back(TypeIndex::Nothing); -} - -void ColumnObject::Subcolumn::resize(size_t new_size) -{ - data = data->cloneResized(new_size); - type_ids.resize_fill(new_size, TypeIndex::Nothing); } ColumnObject::ColumnObject(SubcolumnsMap && subcolumns_) @@ -71,11 +65,11 @@ void ColumnObject::checkConsistency() const MutableColumnPtr ColumnObject::cloneResized(size_t new_size) const { - SubcolumnsMap new_subcolumns; - for (const auto & [key, subcolumn] : subcolumns) - new_subcolumns[key].resize(new_size); + if (new_size != 0) + throw Exception(ErrorCodes::NOT_IMPLEMENTED, + "ColumnObject doesn't support resize to non-zero length"); - return ColumnObject::create(std::move(new_subcolumns)); + return ColumnObject::create(); } size_t ColumnObject::byteSize() const @@ -117,7 +111,7 @@ bool ColumnObject::hasSubcolumn(const String & key) const return subcolumns.count(key) != 0; } -void ColumnObject::addSubcolumn(const String & key, MutableColumnPtr && column_sample, size_t new_size, bool check_size) +void ColumnObject::addSubcolumn(const String & key, const ColumnPtr & column_sample, size_t new_size, bool check_size) { if (subcolumns.count(key)) throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key); @@ -132,8 +126,8 @@ void ColumnObject::addSubcolumn(const String & key, MutableColumnPtr && column_s key, new_size, size()); auto & subcolumn = subcolumns[key]; - subcolumn = std::move(column_sample); - subcolumn.resize(new_size); + subcolumn.data = column_sample->cloneResized(new_size); + subcolumn.least_common_type = std::make_shared(); } void ColumnObject::addSubcolumn(const String & key, Subcolumn && subcolumn, bool check_size) @@ -158,4 +152,36 @@ Names ColumnObject::getKeys() const return keys; } +void ColumnObject::optimizeTypesOfSubcolumns() +{ + if (optimized_subcolumn_types) + return; + + for (auto & [_, subcolumn] : subcolumns) + { + auto from_type = getDataTypeByColumn(*subcolumn.data); + if (subcolumn.least_common_type->equals(*from_type)) + continue; + + size_t subcolumn_size = subcolumn.size(); + if (subcolumn.data->getNumberOfDefaultRows(1) == 0) + { + subcolumn.data = castColumn({subcolumn.data, from_type, ""}, subcolumn.least_common_type); + } + else + { + /// TODO: wrong code. + auto offsets = ColumnUInt64::create(); + auto & offsets_data = offsets->getData(); + + subcolumn.data->getIndicesOfNonDefaultValues(offsets_data, 0, subcolumn_size); + auto values = subcolumn.data->index(*offsets, subcolumn_size); + values = castColumn({subcolumn.data, from_type, ""}, subcolumn.least_common_type); + subcolumn.data = values->createWithOffsets(offsets_data, subcolumn_size); + } + } + + optimized_subcolumn_types = true; +} + } diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 363a7114a06..d924b4c0e84 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -6,6 +6,8 @@ #include #include +#include + namespace DB { @@ -25,18 +27,18 @@ public: Subcolumn & operator=(Subcolumn && other) = default; WrappedPtr data; - PaddedPODArray type_ids; + DataTypePtr least_common_type; size_t size() const { return data->size(); } - void insert(const Field & field, TypeIndex type_id); + void insert(const Field & field, const DataTypePtr & value_type); void insertDefault(); - void resize(size_t new_size); }; using SubcolumnsMap = std::unordered_map; private: SubcolumnsMap subcolumns; + bool optimized_subcolumn_types = false; public: ColumnObject() = default; @@ -49,7 +51,7 @@ public: const Subcolumn & getSubcolumn(const String & key) const; Subcolumn & getSubcolumn(const String & key); - void addSubcolumn(const String & key, MutableColumnPtr && column_sample, size_t new_size, bool check_size = false); + void addSubcolumn(const String & key, const ColumnPtr & column_sample, size_t new_size, bool check_size = false); void addSubcolumn(const String & key, Subcolumn && subcolumn, bool check_size = false); const SubcolumnsMap & getSubcolumns() const { return subcolumns; } @@ -57,6 +59,8 @@ public: Names getKeys() const; + void optimizeTypesOfSubcolumns(); + /// Part of interface const char * getFamilyName() const override { return "Object"; } diff --git a/src/DataTypes/FieldToDataType.cpp b/src/DataTypes/FieldToDataType.cpp index 95f03385121..7d58def5146 100644 --- a/src/DataTypes/FieldToDataType.cpp +++ b/src/DataTypes/FieldToDataType.cpp @@ -101,7 +101,7 @@ DataTypePtr FieldToDataType::operator() (const Array & x) const for (const Field & elem : x) element_types.emplace_back(applyVisitor(FieldToDataType(), elem)); - return std::make_shared(getLeastSupertype(element_types)); + return std::make_shared(getLeastSupertype(element_types, allow_convertion_to_string)); } @@ -134,7 +134,9 @@ DataTypePtr FieldToDataType::operator() (const Map & map) const value_types.push_back(applyVisitor(FieldToDataType(), tuple[1])); } - return std::make_shared(getLeastSupertype(key_types), getLeastSupertype(value_types)); + return std::make_shared( + getLeastSupertype(key_types, allow_convertion_to_string), + getLeastSupertype(value_types, allow_convertion_to_string)); } DataTypePtr FieldToDataType::operator() (const AggregateFunctionStateData & x) const diff --git a/src/DataTypes/FieldToDataType.h b/src/DataTypes/FieldToDataType.h index 39457564a2f..bb8b997a767 100644 --- a/src/DataTypes/FieldToDataType.h +++ b/src/DataTypes/FieldToDataType.h @@ -17,6 +17,11 @@ using DataTypePtr = std::shared_ptr; class FieldToDataType : public StaticVisitor { public: + FieldToDataType(bool allow_convertion_to_string_ = false) + : allow_convertion_to_string(allow_convertion_to_string_) + { + } + DataTypePtr operator() (const Null & x) const; DataTypePtr operator() (const UInt64 & x) const; DataTypePtr operator() (const UInt128 & x) const; @@ -34,6 +39,9 @@ public: DataTypePtr operator() (const AggregateFunctionStateData & x) const; DataTypePtr operator() (const UInt256 & x) const; DataTypePtr operator() (const Int256 & x) const; + +private: + bool allow_convertion_to_string; }; } diff --git a/src/DataTypes/IDataType.h b/src/DataTypes/IDataType.h index 3ca82a40121..23854e0746d 100644 --- a/src/DataTypes/IDataType.h +++ b/src/DataTypes/IDataType.h @@ -345,6 +345,8 @@ struct WhichDataType constexpr bool isAggregateFunction() const { return idx == TypeIndex::AggregateFunction; } constexpr bool IsBigIntOrDeimal() const { return isInt128() || isInt256() || isUInt256() || isDecimal256(); } + + constexpr bool isSimple() const { return isInt() || isUInt() || isFloat() || isString(); } }; /// IDataType helpers (alternative for IDataType virtual methods with single point of truth) diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index f1950e2f8b5..7e63165149e 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -2,10 +2,13 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include @@ -23,12 +26,38 @@ static const IDataType * getTypeObject(const DataTypePtr & type) return typeid_cast(type.get()); } +size_t getNumberOfDimensions(const IDataType & type) +{ + if (const auto * type_array = typeid_cast(&type)) + return type_array->getNumberOfDimensions(); + return 0; +} + +size_t getNumberOfDimensions(const IColumn & column) +{ + if (const auto * column_array = checkAndGetColumn(column)) + return column_array->getNumberOfDimensions(); + return 0; +} + +DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension) +{ + for (size_t i = 0; i < dimension; ++i) + type = std::make_shared(type); + return type; +} + DataTypePtr getDataTypeByColumn(const IColumn & column) { - if (column.empty()) - return std::make_shared(); + auto idx = column.getDataType(); + if (WhichDataType(column.getDataType()).isSimple()) + return DataTypeFactory::instance().get(getTypeName(idx)); - return applyVisitor(FieldToDataType(), column[0]); + if (const auto * column_array = typeid_cast(&column)) + return std::make_shared(getDataTypeByColumn(column_array->getData())); + + /// TODO: add more types. + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot get data type of column {}", column.getFamilyName()); } void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) @@ -91,11 +120,27 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) for (const auto & [name, subtypes] : subcolumns_types) { + assert(!subtypes.empty()); + + size_t first_dim = getNumberOfDimensions(*subtypes[0]); + for (size_t i = 1; i < subtypes.size(); ++i) + if (first_dim != getNumberOfDimensions(*subtypes[i])) + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Uncompatible types of subcolumn '{}': {} and {}", + name, subtypes[0]->getName(), subtypes[i]->getName()); + tuple_names.push_back(name); - tuple_types.push_back(getLeastSupertype(subtypes)); + tuple_types.push_back(getLeastSupertype(subtypes, /*allow_conversion_to_string=*/ true)); } return std::make_shared(tuple_types, tuple_names); } +void optimizeTypesOfObjectColumns(MutableColumns & columns) +{ + for (auto & column : columns) + if (auto * column_object = typeid_cast(column.get())) + column_object->optimizeTypesOfSubcolumns(); +} + } diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 509574f934d..b8b508530ba 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -6,8 +6,13 @@ namespace DB { +size_t getNumberOfDimensions(const IDataType & type); +size_t getNumberOfDimensions(const IColumn & column); +DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); + DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block); DataTypePtr getLeastCommonTypeForObject(const DataTypes & types); +void optimizeTypesOfObjectColumns(MutableColumns & columns); } diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 5d720de4111..fcbf1e11775 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -34,33 +34,6 @@ void SerializationObject::serializeText(const IColumn & /*column*/, size throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); } -namespace -{ - -DataTypePtr createArrayOfStrings(size_t dim) -{ - DataTypePtr type = std::make_shared(); - for (size_t i = 0; i < dim; ++i) - type = std::make_shared(type); - return type; -} - -size_t getNumberOfDimensions(const IDataType & type) -{ - if (const auto * type_array = typeid_cast(&type)) - return type_array->getNumberOfDimensions(); - return 0; -} - -size_t getNumberOfDimensions(const IColumn & column) -{ - if (const auto * column_array = checkAndGetColumn(column)) - return column_array->getNumberOfDimensions(); - return 0; -} - -} - template void SerializationObject::deserializeText(IColumn & column, ReadBuffer & istr, const FormatSettings &) const { @@ -83,9 +56,10 @@ void SerializationObject::deserializeText(IColumn & column, ReadBuffer & size_t column_size = column_object.size(); for (size_t i = 0; i < paths.size(); ++i) { - auto value_type = applyVisitor(FieldToDataType(), values[i]); - size_t value_dim = getNumberOfDimensions(*value_type); - auto array_type = createArrayOfStrings(value_dim); + auto value_type = applyVisitor(FieldToDataType(/*allow_conversion_to_string=*/true), values[i]); + + auto value_dim = getNumberOfDimensions(*value_type); + auto array_type = createArrayOfType(std::make_shared(), value_dim); auto converted_value = convertFieldToTypeOrThrow(values[i], *array_type, value_type.get()); if (!column_object.hasSubcolumn(paths[i])) @@ -99,7 +73,7 @@ void SerializationObject::deserializeText(IColumn & column, ReadBuffer & "Dimension of types mismatched beetwen value and column. Dimension of value: {}. Dimension of column: {}", value_dim, column_dim); - subcolumn.insert(converted_value, value_type->getTypeId()); + subcolumn.insert(converted_value, value_type); } for (auto & [key, subcolumn] : column_object.getSubcolumns()) @@ -246,6 +220,7 @@ void SerializationObject::deserializeBinaryBulkWithMultipleStreams( settings.path.pop_back(); column_object.checkConsistency(); + column_object.optimizeTypesOfSubcolumns(); column = std::move(mutable_column); } diff --git a/src/DataTypes/getLeastSupertype.cpp b/src/DataTypes/getLeastSupertype.cpp index 6710313349b..2b46d779293 100644 --- a/src/DataTypes/getLeastSupertype.cpp +++ b/src/DataTypes/getLeastSupertype.cpp @@ -50,8 +50,16 @@ namespace } -DataTypePtr getLeastSupertype(const DataTypes & types) +DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_string) { + auto throw_or_return = [&](std::string_view message, int error_code) + { + if (allow_conversion_to_string) + return std::make_shared(); + + throw Exception(String(message), error_code); + }; + /// Trivial cases if (types.empty()) @@ -113,7 +121,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) if (have_array) { if (!all_arrays) - throw Exception(getExceptionMessagePrefix(types) + " because some of them are Array and some of them are not", ErrorCodes::NO_COMMON_TYPE); + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are Array and some of them are not", ErrorCodes::NO_COMMON_TYPE); return std::make_shared(getLeastSupertype(nested_types)); } @@ -139,7 +147,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) nested_types[elem_idx].reserve(types.size()); } else if (tuple_size != type_tuple->getElements().size()) - throw Exception(getExceptionMessagePrefix(types) + " because Tuples have different sizes", ErrorCodes::NO_COMMON_TYPE); + return throw_or_return(getExceptionMessagePrefix(types) + " because Tuples have different sizes", ErrorCodes::NO_COMMON_TYPE); have_tuple = true; @@ -153,7 +161,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) if (have_tuple) { if (!all_tuples) - throw Exception(getExceptionMessagePrefix(types) + " because some of them are Tuple and some of them are not", ErrorCodes::NO_COMMON_TYPE); + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are Tuple and some of them are not", ErrorCodes::NO_COMMON_TYPE); DataTypes common_tuple_types(tuple_size); for (size_t elem_idx = 0; elem_idx < tuple_size; ++elem_idx) @@ -187,7 +195,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) if (have_maps) { if (!all_maps) - throw Exception(getExceptionMessagePrefix(types) + " because some of them are Maps and some of them are not", ErrorCodes::NO_COMMON_TYPE); + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are Maps and some of them are not", ErrorCodes::NO_COMMON_TYPE); return std::make_shared(getLeastSupertype(key_types), getLeastSupertype(value_types)); } @@ -268,7 +276,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) { bool all_strings = type_ids.size() == (have_string + have_fixed_string); if (!all_strings) - throw Exception(getExceptionMessagePrefix(types) + " because some of them are String/FixedString and some of them are not", ErrorCodes::NO_COMMON_TYPE); + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are String/FixedString and some of them are not", ErrorCodes::NO_COMMON_TYPE); return std::make_shared(); } @@ -284,7 +292,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) { bool all_date_or_datetime = type_ids.size() == (have_date + have_datetime + have_datetime64); if (!all_date_or_datetime) - throw Exception(getExceptionMessagePrefix(types) + " because some of them are Date/DateTime/DateTime64 and some of them are not", + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are Date/DateTime/DateTime64 and some of them are not", ErrorCodes::NO_COMMON_TYPE); if (have_datetime64 == 0) @@ -331,7 +339,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) } if (num_supported != type_ids.size()) - throw Exception(getExceptionMessagePrefix(types) + " because some of them have no lossless conversion to Decimal", + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them have no lossless conversion to Decimal", ErrorCodes::NO_COMMON_TYPE); UInt32 max_scale = 0; @@ -354,7 +362,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) } if (min_precision > DataTypeDecimal::maxPrecision()) - throw Exception(getExceptionMessagePrefix(types) + " because the least supertype is Decimal(" + return throw_or_return(getExceptionMessagePrefix(types) + " because the least supertype is Decimal(" + toString(min_precision) + ',' + toString(max_scale) + ')', ErrorCodes::NO_COMMON_TYPE); @@ -415,7 +423,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) if (max_bits_of_signed_integer || max_bits_of_unsigned_integer || max_mantissa_bits_of_floating) { if (!all_numbers) - throw Exception(getExceptionMessagePrefix(types) + " because some of them are numbers and some of them are not", ErrorCodes::NO_COMMON_TYPE); + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are numbers and some of them are not", ErrorCodes::NO_COMMON_TYPE); /// If there are signed and unsigned types of same bit-width, the result must be signed number with at least one more bit. /// Example, common of Int32, UInt32 = Int64. @@ -430,7 +438,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) if (min_bit_width_of_integer != 64) ++min_bit_width_of_integer; else - throw Exception( + return throw_or_return( getExceptionMessagePrefix(types) + " because some of them are signed integers and some are unsigned integers," " but there is no signed integer type, that can exactly represent all required unsigned integer values", @@ -446,7 +454,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) else if (min_mantissa_bits <= 53) return std::make_shared(); else - throw Exception(getExceptionMessagePrefix(types) + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are integers and some are floating point," " but there is no floating point type, that can exactly represent all required integers", ErrorCodes::NO_COMMON_TYPE); } @@ -467,7 +475,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types) else if (min_bit_width_of_integer <= 256) return std::make_shared(); else - throw Exception(getExceptionMessagePrefix(types) + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are signed integers and some are unsigned integers," " but there is no signed integer type, that can exactly represent all required unsigned integer values", ErrorCodes::NO_COMMON_TYPE); } @@ -485,14 +493,14 @@ DataTypePtr getLeastSupertype(const DataTypes & types) else if (min_bit_width_of_integer <= 256) return std::make_shared(); else - throw Exception("Logical error: " + getExceptionMessagePrefix(types) + return throw_or_return("Logical error: " + getExceptionMessagePrefix(types) + " but as all data types are unsigned integers, we must have found maximum unsigned integer type", ErrorCodes::NO_COMMON_TYPE); } } } /// All other data types (UUID, AggregateFunction, Enum...) are compatible only if they are the same (checked in trivial cases). - throw Exception(getExceptionMessagePrefix(types), ErrorCodes::NO_COMMON_TYPE); + return throw_or_return(getExceptionMessagePrefix(types), ErrorCodes::NO_COMMON_TYPE); } } diff --git a/src/DataTypes/getLeastSupertype.h b/src/DataTypes/getLeastSupertype.h index 57e011a0529..da84e5eac60 100644 --- a/src/DataTypes/getLeastSupertype.h +++ b/src/DataTypes/getLeastSupertype.h @@ -12,6 +12,6 @@ namespace DB * Examples: least common supertype for UInt8, Int8 - Int16. * Examples: there is no least common supertype for Array(UInt8), Int8. */ -DataTypePtr getLeastSupertype(const DataTypes & types); +DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_string = false); } diff --git a/src/Interpreters/convertFieldToType.cpp b/src/Interpreters/convertFieldToType.cpp index ed920539bea..ca9b7d6cdd8 100644 --- a/src/Interpreters/convertFieldToType.cpp +++ b/src/Interpreters/convertFieldToType.cpp @@ -210,6 +210,8 @@ Field convertFieldToTypeImpl(const Field & src, const IDataType & type, const ID } return src; } + + return applyVisitor(FieldVisitorToString(), src); } else if (const DataTypeArray * type_array = typeid_cast(&type)) { diff --git a/src/Processors/Formats/IRowInputFormat.cpp b/src/Processors/Formats/IRowInputFormat.cpp index 52e64a9d90d..ecca6714aa0 100644 --- a/src/Processors/Formats/IRowInputFormat.cpp +++ b/src/Processors/Formats/IRowInputFormat.cpp @@ -1,4 +1,5 @@ #include +#include #include // toString #include @@ -197,6 +198,7 @@ Chunk IRowInputFormat::generate() return {}; } + optimizeTypesOfObjectColumns(columns); Chunk chunk(std::move(columns), num_rows); //chunk.setChunkInfo(std::move(chunk_missing_values)); return chunk; From 5150c3b9b5dfae5a38fa398411fc70c50030b8ac Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 7 May 2021 05:19:54 +0300 Subject: [PATCH 0014/1647] dynamic columns: better input formats of json type --- src/DataTypes/Serializations/JSONDataParser.h | 2 +- .../Serializations/SerializationObject.cpp | 83 +++++++++++++++---- .../Serializations/SerializationObject.h | 20 ++++- src/IO/ReadHelpers.cpp | 2 +- .../0_stateless/01825_type_json.reference | 25 +++++- tests/queries/0_stateless/01825_type_json.sql | 81 ++++++++++++++++++ 6 files changed, 188 insertions(+), 25 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json.sql diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index 2fb3a65236f..edb6838a70d 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -50,7 +50,7 @@ public: using Path = std::vector; using Paths = std::vector; - void readInto(String & s, ReadBuffer & buf) + void readJSON(String & s, ReadBuffer & buf) { readJSONObjectPossiblyInvalid(s, buf); } diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index fcbf1e11775..5f3a78f5041 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -8,9 +7,7 @@ #include #include #include -#include #include -#include #include #include @@ -29,18 +26,14 @@ namespace ErrorCodes } template -void SerializationObject::serializeText(const IColumn & /*column*/, size_t /*row_num*/, WriteBuffer & /*ostr*/, const FormatSettings &) const -{ - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); -} - -template -void SerializationObject::deserializeText(IColumn & column, ReadBuffer & istr, const FormatSettings &) const +template +void SerializationObject::deserializeTextImpl(IColumn & column, Reader && reader) const { auto & column_object = assert_cast(column); String buf; - parser.readInto(buf, istr); + reader(buf); + auto result = parser.parse(buf.data(), buf.size()); if (!result) throw Exception(ErrorCodes::INCORRECT_DATA, "Cannot parse object"); @@ -83,6 +76,36 @@ void SerializationObject::deserializeText(IColumn & column, ReadBuffer & } } +template +void SerializationObject::deserializeWholeText(IColumn & column, ReadBuffer & istr, const FormatSettings &) const +{ + deserializeTextImpl(column, [&](String & s) { readStringInto(s, istr); }); +} + +template +void SerializationObject::deserializeTextEscaped(IColumn & column, ReadBuffer & istr, const FormatSettings &) const +{ + deserializeTextImpl(column, [&](String & s) { readEscapedStringInto(s, istr); }); +} + +template +void SerializationObject::deserializeTextQuoted(IColumn & column, ReadBuffer & istr, const FormatSettings &) const +{ + deserializeTextImpl(column, [&](String & s) { readQuotedStringInto(s, istr); }); +} + +template +void SerializationObject::deserializeTextJSON(IColumn & column, ReadBuffer & istr, const FormatSettings &) const +{ + deserializeTextImpl(column, [&](String & s) { parser.readJSON(s, istr); }); +} + +template +void SerializationObject::deserializeTextCSV(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const +{ + deserializeTextImpl(column, [&](String & s) { readCSVStringInto(s, istr, settings.csv); }); +} + template template void SerializationObject::checkSerializationIsSupported(Settings & settings, StatePtr & state) const @@ -225,25 +248,55 @@ void SerializationObject::deserializeBinaryBulkWithMultipleStreams( } template -void SerializationObject::serializeBinary(const Field & /*field*/, WriteBuffer & /*ostr*/) const +void SerializationObject::serializeBinary(const Field &, WriteBuffer &) const { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); } template -void SerializationObject::deserializeBinary(Field & /*field*/, ReadBuffer & /*istr*/) const +void SerializationObject::deserializeBinary(Field &, ReadBuffer &) const { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); } template -void SerializationObject::serializeBinary(const IColumn & /*column*/, size_t /*row_num*/, WriteBuffer & /*ostr*/) const +void SerializationObject::serializeBinary(const IColumn &, size_t, WriteBuffer &) const { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); } template -void SerializationObject::deserializeBinary(IColumn & /*column*/, ReadBuffer & /*istr*/) const +void SerializationObject::deserializeBinary(IColumn &, ReadBuffer &) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::serializeText(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::serializeTextEscaped(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::serializeTextQuoted(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::serializeTextJSON(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const +{ + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); +} + +template +void SerializationObject::serializeTextCSV(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); } diff --git a/src/DataTypes/Serializations/SerializationObject.h b/src/DataTypes/Serializations/SerializationObject.h index c78c8d0f921..eba013b1664 100644 --- a/src/DataTypes/Serializations/SerializationObject.h +++ b/src/DataTypes/Serializations/SerializationObject.h @@ -6,12 +6,9 @@ namespace DB { template -class SerializationObject : public SimpleTextSerialization +class SerializationObject : public ISerialization { public: - void serializeText(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings &) const override; - void deserializeText(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const override; - void serializeBinaryBulkStatePrefix( SerializeBinaryBulkSettings & settings, SerializeBinaryBulkStatePtr & state) const override; @@ -43,10 +40,25 @@ public: void serializeBinary(const IColumn & column, size_t row_num, WriteBuffer & ostr) const override; void deserializeBinary(IColumn & column, ReadBuffer & istr) const override; + void serializeText(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings &) const override; + void serializeTextEscaped(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const override; + void serializeTextQuoted(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const override; + void serializeTextJSON(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const override; + void serializeTextCSV(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const override; + + void deserializeWholeText(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const override; + void deserializeTextEscaped(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const override; + void deserializeTextQuoted(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const override; + void deserializeTextJSON(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const override; + void deserializeTextCSV(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const override; + private: template void checkSerializationIsSupported(Settings & settings, StatePtr & state) const; + template + void deserializeTextImpl(IColumn & column, Reader && reader) const; + mutable Parser parser; }; diff --git a/src/IO/ReadHelpers.cpp b/src/IO/ReadHelpers.cpp index cb6d43f859f..f8c52594d27 100644 --- a/src/IO/ReadHelpers.cpp +++ b/src/IO/ReadHelpers.cpp @@ -244,7 +244,7 @@ void readString(String & s, ReadBuffer & buf) } template void readStringInto>(PaddedPODArray & s, ReadBuffer & buf); - +template void readStringInto(String & s, ReadBuffer & buf); template void readStringUntilEOFInto(Vector & s, ReadBuffer & buf) diff --git a/tests/queries/0_stateless/01825_type_json.reference b/tests/queries/0_stateless/01825_type_json.reference index f29a150ecf0..5e957434033 100644 --- a/tests/queries/0_stateless/01825_type_json.reference +++ b/tests/queries/0_stateless/01825_type_json.reference @@ -1,10 +1,27 @@ 1 aa bb c 2 ee ff 3 foo -all_1_1_0 id UInt64 all_1_1_0 data Tuple(k1 String, `k2.k4` String, `k2.k3` String, k5 String) -all_2_2_0 id UInt64 all_2_2_0 data Tuple(k5 String) +all_1_2_1 data Tuple(k5 String, `k2.k3` String, `k2.k4` String, k1 String) +============ 1 ['aaa','ddd'] [['bbb','ccc'],['eee','fff']] -all_1_1_0 id UInt64 -all_1_1_0 data Tuple(`k1.k3.k4` Array(Array(String)), `k1.k2` Array(String)) +all_3_3_0 data Tuple(`k1.k3.k4` Array(Array(String)), `k1.k2` Array(String)) +============ +1 a 42 +2 b 4200 +4242 +all_4_4_0 data Tuple(name String, value Int16) +1 a 42 +2 b 4200 +3 a 42.123 +all_4_4_0 data Tuple(name String, value Int16) +all_5_5_0 data Tuple(name String, value Float64) +1 a 42 +2 b 4200 +3 a 42.123 +4 a some +all_4_4_0 data Tuple(name String, value Int16) +all_5_5_0 data Tuple(name String, value Float64) +all_6_6_0 data Tuple(name String, value String) +all_4_6_1 data Tuple(value String, name String) diff --git a/tests/queries/0_stateless/01825_type_json.sql b/tests/queries/0_stateless/01825_type_json.sql new file mode 100644 index 00000000000..668b39db70f --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json.sql @@ -0,0 +1,81 @@ +DROP TABLE IF EXISTS t_json; + +CREATE TABLE t_json(id UInt64, data Object('JSON')) +ENGINE = MergeTree ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0; + +SYSTEM STOP MERGES t_json; + +INSERT INTO t_json FORMAT JSONEachRow {"id": 1, "data": {"k1": "aa", "k2": {"k3": "bb", "k4": "c"}}} {"id": 2, "data": {"k1": "ee", "k5": "ff"}}; +INSERT INTO t_json FORMAT JSONEachRow {"id": 3, "data": {"k5":"foo"}}; + +SELECT id, data.k1, data.k2.k3, data.k2.k4, data.k5 FROM t_json ORDER BY id; + +SELECT name, column, type +FROM system.parts_columns +WHERE table = 't_json' AND database = currentDatabase() AND active AND column = 'data' +ORDER BY name; + +SYSTEM START MERGES t_json; + +OPTIMIZE TABLE t_json FINAL; + +SELECT name, column, type +FROM system.parts_columns +WHERE table = 't_json' AND database = currentDatabase() AND active AND column = 'data' +ORDER BY name; + + +SELECT '============'; +TRUNCATE TABLE t_json; + +INSERT INTO t_json FORMAT JSONEachRow {"id": 1, "data": {"k1":[{"k2":"aaa","k3":[{"k4":"bbb"},{"k4":"ccc"}]},{"k2":"ddd","k3":[{"k4":"eee"},{"k4":"fff"}]}]}}; +SELECT id, data.k1.k2, data.k1.k3.k4 FROM t_json ORDER BY id; + +SELECT name, column, type +FROM system.parts_columns +WHERE table = 't_json' AND database = currentDatabase() AND active AND column = 'data' +ORDER BY name; + +SELECT '============'; +TRUNCATE TABLE t_json; + +SYSTEM STOP MERGES t_json; + +INSERT INTO t_json FORMAT JSONEachRow {"id": 1, "data": {"name": "a", "value": 42 }}, {"id": 2, "data": {"name": "b", "value": 4200 }}; + +SELECT id, data.name, data.value FROM t_json ORDER BY id; +SELECT sum(data.value) FROM t_json; + +SELECT name, column, type +FROM system.parts_columns +WHERE table = 't_json' AND database = currentDatabase() AND active AND column = 'data' +ORDER BY name; + +INSERT INTO t_json FORMAT JSONEachRow {"id": 3, "data": {"name": "a", "value": 42.123 }}; + +SELECT id, data.name, data.value FROM t_json ORDER BY id; + +SELECT name, column, type +FROM system.parts_columns +WHERE table = 't_json' AND database = currentDatabase() AND active AND column = 'data' +ORDER BY name; + +INSERT INTO t_json FORMAT JSONEachRow {"id": 4, "data": {"name": "a", "value": "some" }}; + +SELECT id, data.name, data.value FROM t_json ORDER BY id; + +SELECT name, column, type +FROM system.parts_columns +WHERE table = 't_json' AND database = currentDatabase() AND active AND column = 'data' +ORDER BY name; + +SYSTEM START MERGES t_json; +OPTIMIZE TABLE t_json FINAL; + +SELECT name, column, type +FROM system.parts_columns +WHERE table = 't_json' AND database = currentDatabase() AND active AND column = 'data' +ORDER BY name; + +DROP TABLE IF EXISTS t_json; From 1823ea6efd3590224ae04f6d865334a3e8e75b7f Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 7 May 2021 05:21:24 +0300 Subject: [PATCH 0015/1647] disable sparse columns --- 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 b9d9f1473ef..4e877980f1d 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -33,7 +33,7 @@ struct Settings; M(UInt64, min_rows_for_compact_part, 0, "Experimental. Minimal number of rows to create part in compact format instead of saving it in RAM", 0) \ M(Bool, in_memory_parts_enable_wal, true, "Whether to write blocks in Native format to write-ahead-log before creation in-memory part", 0) \ M(UInt64, write_ahead_log_max_bytes, 1024 * 1024 * 1024, "Rotate WAL, if it exceeds that amount of bytes", 0) \ - M(Float, ratio_for_sparse_serialization, 0, "", 0) \ + M(Float, ratio_for_sparse_serialization, 1.1, "", 0) \ \ /** Merge settings. */ \ M(UInt64, merge_max_block_size, DEFAULT_MERGE_BLOCK_SIZE, "How many rows in blocks should be formed for merge operations.", 0) \ From 012009ceaa2d1dd6228082dc9fdbd50a0fea165d Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 7 May 2021 06:12:41 +0300 Subject: [PATCH 0016/1647] fix build --- src/Common/CMakeLists.txt | 2 -- src/Common/JSONParsers/CMakeLists.txt | 15 --------------- src/Common/JSONParsers/RapidJSONParser.h | 2 +- src/Common/JSONParsers/SimdJSONParser.h | 2 +- src/Common/JSONParsers/config_jsonparsers.h.in | 6 ------ 5 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 src/Common/JSONParsers/CMakeLists.txt delete mode 100644 src/Common/JSONParsers/config_jsonparsers.h.in diff --git a/src/Common/CMakeLists.txt b/src/Common/CMakeLists.txt index feee3bd9dd3..61d9b9771a4 100644 --- a/src/Common/CMakeLists.txt +++ b/src/Common/CMakeLists.txt @@ -3,8 +3,6 @@ add_subdirectory(StringUtils) #add_subdirectory(ZooKeeper) #add_subdirectory(ConfigProcessor) -add_subdirectory(JSONParsers) - if (ENABLE_TESTS) add_subdirectory (tests) endif () diff --git a/src/Common/JSONParsers/CMakeLists.txt b/src/Common/JSONParsers/CMakeLists.txt deleted file mode 100644 index 7b0c6f3ff07..00000000000 --- a/src/Common/JSONParsers/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# configure_file(config_jsonparsers.h.in ${ConfigIncludePath}/config_jsonparsers.h) - -# include(${ClickHouse_SOURCE_DIR}/cmake/dbms_glob_sources.cmake) - -# add_headers_and_sources(clickhouse_common_jsonparsers .) - -# add_library(clickhouse_common_jsonparsers ${clickhouse_common_jsonparsers_headers} ${clickhouse_common_jsonparsers_sources}) - -# if(USE_SIMDJSON) -# target_link_libraries(clickhouse_common_jsonparsers PRIVATE simdjson) -# endif() - -# if(USE_RAPIDJSON) -# target_include_directories(clickhouse_common_jsonparsers SYSTEM PRIVATE ${RAPIDJSON_INCLUDE_DIR}) -# endif() diff --git a/src/Common/JSONParsers/RapidJSONParser.h b/src/Common/JSONParsers/RapidJSONParser.h index c25bfefaea1..e4e7c6082b5 100644 --- a/src/Common/JSONParsers/RapidJSONParser.h +++ b/src/Common/JSONParsers/RapidJSONParser.h @@ -1,7 +1,7 @@ #pragma once #if !defined(ARCADIA_BUILD) -# include "config_jsonparsers.h" +# include #endif #if USE_RAPIDJSON diff --git a/src/Common/JSONParsers/SimdJSONParser.h b/src/Common/JSONParsers/SimdJSONParser.h index 974b506b4a7..08ccbe705eb 100644 --- a/src/Common/JSONParsers/SimdJSONParser.h +++ b/src/Common/JSONParsers/SimdJSONParser.h @@ -1,7 +1,7 @@ #pragma once #if !defined(ARCADIA_BUILD) -# include "config_jsonparsers.h" +# include #endif #if USE_SIMDJSON diff --git a/src/Common/JSONParsers/config_jsonparsers.h.in b/src/Common/JSONParsers/config_jsonparsers.h.in deleted file mode 100644 index 64352584342..00000000000 --- a/src/Common/JSONParsers/config_jsonparsers.h.in +++ /dev/null @@ -1,6 +0,0 @@ -// #pragma once - -// // .h autogenerated by cmake! - -// #cmakedefine01 USE_SIMDJSON -// #cmakedefine01 USE_RAPIDJSON From 37989cd2ae805d7a608dd76b6dcc3d4eb4598b08 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 11 May 2021 15:01:41 +0300 Subject: [PATCH 0017/1647] fix reading of nested --- src/Interpreters/InterpreterSelectQuery.cpp | 2 -- src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp | 2 +- src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 8ff91427de3..e3dbd5f0ba2 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -375,8 +375,6 @@ InterpreterSelectQuery::InterpreterSelectQuery( if (view) view->replaceWithSubquery(getSelectQuery(), view_table, metadata_snapshot); - std::cerr << "source_header: " << source_header.dumpStructure() << "\n"; - syntax_analyzer_result = TreeRewriter(context).analyzeSelect( query_ptr, TreeRewriterResult(source_header.getNamesAndTypesList(), storage, metadata_snapshot), diff --git a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp index 797f70d2e4c..8c949d8d6e0 100644 --- a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp +++ b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp @@ -92,7 +92,7 @@ NameSet injectRequiredColumns(const MergeTreeData & storage, const StorageMetada for (size_t i = 0; i < columns.size(); ++i) { auto name_in_storage = Nested::extractTableName(columns[i]); - if (isObject(storage_columns.get(name_in_storage).type)) + if (storage_columns.has(name_in_storage) && isObject(storage_columns.get(name_in_storage).type)) { have_at_least_one_physical_column = true; continue; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 87ff7d53c75..dd38f9cd30e 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -942,7 +942,6 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor Block block; while (!is_cancelled() && (block = merged_stream->read())) { - std::cerr << "read block: " << block.dumpStructure() << "\n"; rows_written += block.rows(); to.write(block); From f7582bf6e823720fb6971d646a9cc972c81ba851 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 14 May 2021 01:27:06 +0300 Subject: [PATCH 0018/1647] remove unused code --- src/DataTypes/Serializations/JSONDataParser.h | 2 -- src/Storages/ColumnsDescription.h | 1 - 2 files changed, 3 deletions(-) diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index edb6838a70d..cac13aab4e8 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -47,8 +47,6 @@ class JSONDataParser { public: using Element = typename ParserImpl::Element; - using Path = std::vector; - using Paths = std::vector; void readJSON(String & s, ReadBuffer & buf) { diff --git a/src/Storages/ColumnsDescription.h b/src/Storages/ColumnsDescription.h index a8707cf60a3..d03b701b27c 100644 --- a/src/Storages/ColumnsDescription.h +++ b/src/Storages/ColumnsDescription.h @@ -67,7 +67,6 @@ struct ColumnDescription { String name; DataTypePtr type; - DataTypePtr expaneded_type; ColumnDefault default_desc; String comment; ASTPtr codec; From 1fd6142c1a1242cb2ae2cd47dcc431979d3fc55e Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 17 May 2021 14:14:09 +0300 Subject: [PATCH 0019/1647] try enable merges --- src/Interpreters/MergeTreeTransaction.cpp | 2 ++ .../MergeTreeTransactionHolder.cpp | 10 +++--- src/Interpreters/MergeTreeTransactionHolder.h | 4 ++- src/Interpreters/TransactionLog.h | 1 + src/Interpreters/executeQuery.cpp | 9 +++++ src/Storages/MergeTree/MergeTreeData.cpp | 12 +++---- src/Storages/MergeTree/MergeTreeData.h | 2 +- .../MergeTree/MergeTreeDataMergerMutator.cpp | 16 +++++++-- .../MergeTree/MergeTreeDataMergerMutator.h | 2 ++ src/Storages/StorageMergeTree.cpp | 35 ++++++++++++++----- src/Storages/StorageMergeTree.h | 18 ++++++++-- src/Storages/StorageReplicatedMergeTree.cpp | 6 ++-- ...1173_transaction_control_queries.reference | 1 - .../01173_transaction_control_queries.sql | 11 +++--- .../01174_select_insert_isolation.sh | 12 ++++--- 15 files changed, 101 insertions(+), 40 deletions(-) diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index 2f8445530da..93f7524123a 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -64,11 +64,13 @@ void MergeTreeTransaction::addNewPartAndRemoveCovered(const DataPartPtr & new_pa void MergeTreeTransaction::addNewPart(const DataPartPtr & new_part) { + assert(csn == Tx::UnknownCSN); creating_parts.push_back(new_part); } void MergeTreeTransaction::removeOldPart(const DataPartPtr & part_to_remove) { + assert(csn == Tx::UnknownCSN); removing_parts.push_back(part_to_remove); } diff --git a/src/Interpreters/MergeTreeTransactionHolder.cpp b/src/Interpreters/MergeTreeTransactionHolder.cpp index 1de0d9c75c4..088f6ef8f05 100644 --- a/src/Interpreters/MergeTreeTransactionHolder.cpp +++ b/src/Interpreters/MergeTreeTransactionHolder.cpp @@ -23,8 +23,8 @@ MergeTreeTransactionHolder & MergeTreeTransactionHolder::operator=(MergeTreeTran { onDestroy(); txn = std::move(rhs.txn); - autocommit = rhs.autocommit; rhs.txn = {}; + autocommit = rhs.autocommit; return *this; } @@ -40,20 +40,20 @@ void MergeTreeTransactionHolder::onDestroy() noexcept if (txn->getState() != MergeTreeTransaction::RUNNING) return; - if (autocommit) + if (autocommit && std::uncaught_exceptions() == 0) { try { TransactionLog::instance().commitTransaction(txn); + return; } catch (...) { tryLogCurrentException(__PRETTY_FUNCTION__); } - } else - { - TransactionLog::instance().rollbackTransaction(txn); } + + TransactionLog::instance().rollbackTransaction(txn); } MergeTreeTransactionHolder::MergeTreeTransactionHolder(const MergeTreeTransactionHolder &) diff --git a/src/Interpreters/MergeTreeTransactionHolder.h b/src/Interpreters/MergeTreeTransactionHolder.h index 6172beb2a69..ec9cf1e1636 100644 --- a/src/Interpreters/MergeTreeTransactionHolder.h +++ b/src/Interpreters/MergeTreeTransactionHolder.h @@ -16,12 +16,14 @@ public: MergeTreeTransactionHolder & operator=(MergeTreeTransactionHolder && rhs) noexcept; ~MergeTreeTransactionHolder(); - /// NOTE: We cannot make it noncopyable, because we use it as a filed of Context. + /// NOTE: We cannot make it noncopyable, because we use it as a field of Context. /// So the following copy constructor and operator does not copy anything, /// they just leave txn nullptr. MergeTreeTransactionHolder(const MergeTreeTransactionHolder & rhs); MergeTreeTransactionHolder & operator=(const MergeTreeTransactionHolder & rhs); + MergeTreeTransactionPtr getTransaction() const { return txn; } + private: void onDestroy() noexcept; diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index 616cf62f242..11e896b0a2d 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include diff --git a/src/Interpreters/executeQuery.cpp b/src/Interpreters/executeQuery.cpp index ec725566a64..77ebe1322ec 100644 --- a/src/Interpreters/executeQuery.cpp +++ b/src/Interpreters/executeQuery.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #if !defined(ARCADIA_BUILD) @@ -78,6 +79,7 @@ namespace ErrorCodes { extern const int INTO_OUTFILE_NOT_ALLOWED; extern const int QUERY_WAS_CANCELLED; + extern const int INVALID_TRANSACTION; } @@ -396,6 +398,13 @@ static std::tuple executeQueryImpl( ast = parseQuery(parser, begin, end, "", max_query_size, settings.max_parser_depth); #endif + if (auto txn = context->getCurrentTransaction()) + { + assert(txn->getState() != MergeTreeTransaction::COMMITTED); + if (txn->getState() == MergeTreeTransaction::ROLLED_BACK && !ast->as()) + throw Exception(ErrorCodes::INVALID_TRANSACTION, "Cannot execute query: transaction is rolled back"); + } + /// Interpret SETTINGS clauses as early as possible (before invoking the corresponding interpreter), /// to allow settings to take effect. if (const auto * select_query = ast->as()) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 1119715bbd1..2b1d5bf5e83 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -3218,14 +3218,14 @@ String MergeTreeData::getPartitionIDFromQuery(const ASTPtr & ast, ContextPtr loc DataPartsVector MergeTreeData::getDataPartsVector(ContextPtr local_context) const { - if (auto txn = local_context->getCurrentTransaction()) - return getVisibleDataPartsVector(*txn); - else - return getDataPartsVector(); + return getVisibleDataPartsVector(local_context->getCurrentTransaction()); } -MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVector(const MergeTreeTransaction & txn) const +MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVector(const MergeTreeTransactionPtr & txn) const { + if (!txn) + return getDataPartsVector(); + DataPartsVector maybe_visible_parts = getDataPartsVector({DataPartState::PreCommitted, DataPartState::Committed, DataPartState::Outdated}); if (maybe_visible_parts.empty()) return maybe_visible_parts; @@ -3234,7 +3234,7 @@ MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVector(const Me auto it_last = maybe_visible_parts.end() - 1; while (it <= it_last) { - if ((*it)->versions.isVisible(txn)) + if ((*it)->versions.isVisible(*txn)) { ++it; } diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index eab330babb9..21fb24379aa 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -423,7 +423,7 @@ public: DataPartsVector getDataPartsVector() const; DataPartsVector getDataPartsVector(ContextPtr local_context) const; - DataPartsVector getVisibleDataPartsVector(const MergeTreeTransaction & txn) const; + DataPartsVector getVisibleDataPartsVector(const MergeTreeTransactionPtr & txn) const; /// Returns a committed part with the given name or a part containing it. If there is no such part, returns nullptr. DataPartPtr getActiveContainingPart(const String & part_name) const; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index cd186c0d38d..7a4de0811a5 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -213,9 +213,13 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( size_t max_total_size_to_merge, const AllowedMergingPredicate & can_merge_callback, bool merge_with_ttl_allowed, + const MergeTreeTransactionPtr & txn, String * out_disable_reason) { - MergeTreeData::DataPartsVector data_parts = data.getDataPartsVector(); + + MergeTreeData::DataPartsVector data_parts = data.getVisibleDataPartsVector(txn); + //FIXME get rid of sorting + std::sort(data_parts.begin(), data_parts.end(), MergeTreeData::LessDataPart()); const auto data_settings = data.getSettings(); auto metadata_snapshot = data.getInMemoryMetadataPtr(); @@ -1461,10 +1465,18 @@ MergeAlgorithm MergeTreeDataMergerMutator::chooseMergeAlgorithm( MergeTreeData::DataPartPtr MergeTreeDataMergerMutator::renameMergedTemporaryPart( MergeTreeData::MutableDataPartPtr & new_data_part, const MergeTreeData::DataPartsVector & parts, + const MergeTreeTransactionPtr & txn, MergeTreeData::Transaction * out_transaction) { /// Rename new part, add to the set and remove original parts. - auto replaced_parts = data.renameTempPartAndReplace(new_data_part, nullptr, nullptr, out_transaction); + auto replaced_parts = data.renameTempPartAndReplace(new_data_part, txn.get(), nullptr, out_transaction); + //String parts_str; + //for (const auto & p : parts) + // parts_str += "\t" + p->name; + //String parts_str2; + //for (const auto & p : replaced_parts) + // parts_str2 += "\t" + p->name; + //LOG_ERROR(log, "WTF {}: source {}, replaced {}", new_data_part->name, parts_str, parts_str2); /// Let's check that all original parts have been deleted and only them. if (replaced_parts.size() != parts.size()) diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h index b082d063dcf..c76179ce435 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h @@ -93,6 +93,7 @@ public: size_t max_total_size_to_merge, const AllowedMergingPredicate & can_merge, bool merge_with_ttl_allowed, + const MergeTreeTransactionPtr & txn, String * out_disable_reason = nullptr); /** Select all the parts in the specified partition for merge, if possible. @@ -148,6 +149,7 @@ public: MergeTreeData::DataPartPtr renameMergedTemporaryPart( MergeTreeData::MutableDataPartPtr & new_data_part, const MergeTreeData::DataPartsVector & parts, + const MergeTreeTransactionPtr & txn, MergeTreeData::Transaction * out_transaction = nullptr); diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index a6342818ac1..ef85abad126 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -676,7 +677,14 @@ void StorageMergeTree::loadMutations() } std::shared_ptr StorageMergeTree::selectPartsToMerge( - const StorageMetadataPtr & metadata_snapshot, bool aggressive, const String & partition_id, bool final, String * out_disable_reason, TableLockHolder & /* table_lock_holder */, bool optimize_skip_merged_partitions, SelectPartsDecision * select_decision_out) + const StorageMetadataPtr & metadata_snapshot, + bool aggressive, const String & partition_id, + bool final, + String * out_disable_reason, + TableLockHolder & /* table_lock_holder */, + const MergeTreeTransactionPtr & txn, + bool optimize_skip_merged_partitions, + SelectPartsDecision * select_decision_out) { std::unique_lock lock(currently_processing_in_background_mutex); auto data_settings = getSettings(); @@ -718,6 +726,7 @@ std::shared_ptr StorageMergeTree::se max_source_parts_size, can_merge, merge_with_ttl_allowed, + txn, out_disable_reason); } else if (out_disable_reason) @@ -787,6 +796,7 @@ bool StorageMergeTree::merge( bool final, bool deduplicate, const Names & deduplicate_by_columns, + const MergeTreeTransactionPtr & txn, String * out_disable_reason, bool optimize_skip_merged_partitions) { @@ -795,7 +805,7 @@ bool StorageMergeTree::merge( SelectPartsDecision select_decision; - auto merge_mutate_entry = selectPartsToMerge(metadata_snapshot, aggressive, partition_id, final, out_disable_reason, table_lock_holder, optimize_skip_merged_partitions, &select_decision); + auto merge_mutate_entry = selectPartsToMerge(metadata_snapshot, aggressive, partition_id, final, out_disable_reason, table_lock_holder, txn, optimize_skip_merged_partitions, &select_decision); /// If there is nothing to merge then we treat this merge as successful (needed for optimize final optimization) if (select_decision == SelectPartsDecision::NOTHING_TO_MERGE) @@ -804,7 +814,7 @@ bool StorageMergeTree::merge( if (!merge_mutate_entry) return false; - return mergeSelectedParts(metadata_snapshot, deduplicate, deduplicate_by_columns, *merge_mutate_entry, table_lock_holder); + return mergeSelectedParts(metadata_snapshot, deduplicate, deduplicate_by_columns, *merge_mutate_entry, table_lock_holder, txn); } bool StorageMergeTree::mergeSelectedParts( @@ -812,7 +822,8 @@ bool StorageMergeTree::mergeSelectedParts( bool deduplicate, const Names & deduplicate_by_columns, MergeMutateSelectedEntry & merge_mutate_entry, - TableLockHolder & table_lock_holder) + TableLockHolder & table_lock_holder, + const MergeTreeTransactionPtr & txn) { auto & future_part = merge_mutate_entry.future_part; Stopwatch stopwatch; @@ -847,7 +858,7 @@ bool StorageMergeTree::mergeSelectedParts( deduplicate_by_columns, merging_params); - merger_mutator.renameMergedTemporaryPart(new_part, future_part.parts, nullptr); + merger_mutator.renameMergedTemporaryPart(new_part, future_part.parts, txn, nullptr); write_part_log({}); } catch (...) @@ -1000,20 +1011,24 @@ std::optional StorageMergeTree::getDataProcessingJob() //-V657 if (merger_mutator.merges_blocker.isCancelled()) return {}; + /// FIXME Transactions: do not begin transaction if we don't need it + auto txn = TransactionLog::instance().beginTransaction(); + MergeTreeTransactionHolder autocommit{txn, true}; + auto metadata_snapshot = getInMemoryMetadataPtr(); std::shared_ptr merge_entry, mutate_entry; auto share_lock = lockForShare(RWLockImpl::NO_QUERY, getSettings()->lock_acquire_timeout_for_background_operations); - merge_entry = selectPartsToMerge(metadata_snapshot, false, {}, false, nullptr, share_lock); + merge_entry = selectPartsToMerge(metadata_snapshot, false, {}, false, nullptr, share_lock, txn); if (!merge_entry) mutate_entry = selectPartsToMutate(metadata_snapshot, nullptr, share_lock); if (merge_entry || mutate_entry) { - return JobAndPool{[this, metadata_snapshot, merge_entry, mutate_entry, share_lock] () mutable + return JobAndPool{[this, metadata_snapshot, merge_entry, mutate_entry, share_lock, holder = std::move(autocommit)] () mutable { if (merge_entry) - return mergeSelectedParts(metadata_snapshot, false, {}, *merge_entry, share_lock); + return mergeSelectedParts(metadata_snapshot, false, {}, *merge_entry, share_lock, holder.getTransaction()); else if (mutate_entry) return mutateSelectedPart(metadata_snapshot, *mutate_entry, share_lock); @@ -1110,6 +1125,8 @@ bool StorageMergeTree::optimize( LOG_DEBUG(log, "DEDUPLICATE BY ('{}')", fmt::join(deduplicate_by_columns, "', '")); } + auto txn = local_context->getCurrentTransaction(); + String disable_reason; if (!partition && final) { @@ -1127,6 +1144,7 @@ bool StorageMergeTree::optimize( true, deduplicate, deduplicate_by_columns, + txn, &disable_reason, local_context->getSettingsRef().optimize_skip_merged_partitions)) { @@ -1153,6 +1171,7 @@ bool StorageMergeTree::optimize( final, deduplicate, deduplicate_by_columns, + txn, &disable_reason, local_context->getSettingsRef().optimize_skip_merged_partitions)) { diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index 53199e1595a..8881008bc89 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -142,7 +142,14 @@ private: * If aggressive - when selects parts don't takes into account their ratio size and novelty (used for OPTIMIZE query). * Returns true if merge is finished successfully. */ - bool merge(bool aggressive, const String & partition_id, bool final, bool deduplicate, const Names & deduplicate_by_columns, String * out_disable_reason = nullptr, bool optimize_skip_merged_partitions = false); + bool merge( + bool aggressive, + const String & partition_id, + bool final, bool deduplicate, + const Names & deduplicate_by_columns, + const MergeTreeTransactionPtr & txn, + String * out_disable_reason = nullptr, + bool optimize_skip_merged_partitions = false); /// Make part state outdated and queue it to remove without timeout /// If force, then stop merges and block them until part state became outdated. Throw exception if part doesn't exists @@ -196,10 +203,17 @@ private: bool final, String * disable_reason, TableLockHolder & table_lock_holder, + const MergeTreeTransactionPtr & txn, bool optimize_skip_merged_partitions = false, SelectPartsDecision * select_decision_out = nullptr); - bool mergeSelectedParts(const StorageMetadataPtr & metadata_snapshot, bool deduplicate, const Names & deduplicate_by_columns, MergeMutateSelectedEntry & entry, TableLockHolder & table_lock_holder); + bool mergeSelectedParts( + const StorageMetadataPtr & metadata_snapshot, + bool deduplicate, + const Names & deduplicate_by_columns, + MergeMutateSelectedEntry & entry, + TableLockHolder & table_lock_holder, + const MergeTreeTransactionPtr & txn); std::shared_ptr selectPartsToMutate(const StorageMetadataPtr & metadata_snapshot, String * disable_reason, TableLockHolder & table_lock_holder); bool mutateSelectedPart(const StorageMetadataPtr & metadata_snapshot, MergeMutateSelectedEntry & entry, TableLockHolder & table_lock_holder); diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 5059a3bf171..46c7ffd2274 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -1690,7 +1690,7 @@ bool StorageReplicatedMergeTree::tryExecuteMerge(const LogEntry & entry) entry.deduplicate_by_columns, merging_params); - merger_mutator.renameMergedTemporaryPart(part, parts, &transaction); + merger_mutator.renameMergedTemporaryPart(part, parts, nullptr, &transaction); try { @@ -3078,7 +3078,7 @@ void StorageReplicatedMergeTree::mergeSelectingTask() future_merged_part.uuid = UUIDHelpers::generateV4(); if (max_source_parts_size_for_merge > 0 && - merger_mutator.selectPartsToMerge(future_merged_part, false, max_source_parts_size_for_merge, merge_pred, merge_with_ttl_allowed, nullptr) == SelectPartsDecision::SELECTED) + merger_mutator.selectPartsToMerge(future_merged_part, false, max_source_parts_size_for_merge, merge_pred, merge_with_ttl_allowed, nullptr, nullptr) == SelectPartsDecision::SELECTED) { create_result = createLogEntryToMergeParts( zookeeper, @@ -4446,7 +4446,7 @@ bool StorageReplicatedMergeTree::optimize( if (!partition) { select_decision = merger_mutator.selectPartsToMerge( - future_merged_part, true, storage_settings_ptr->max_bytes_to_merge_at_max_space_in_pool, can_merge, false, &disable_reason); + future_merged_part, true, storage_settings_ptr->max_bytes_to_merge_at_max_space_in_pool, can_merge, false, nullptr, &disable_reason); } else { diff --git a/tests/queries/0_stateless/01173_transaction_control_queries.reference b/tests/queries/0_stateless/01173_transaction_control_queries.reference index a9cd56425f1..6e27f29012b 100644 --- a/tests/queries/0_stateless/01173_transaction_control_queries.reference +++ b/tests/queries/0_stateless/01173_transaction_control_queries.reference @@ -3,7 +3,6 @@ rollback [1,2,10,20] no nested [1,10] on exception before start [1,3,10,30] on exception while processing [1,4,10,40] -1 on session close [1,6,10,60] commit [1,7,10,70] readonly [1,7,10,70] diff --git a/tests/queries/0_stateless/01173_transaction_control_queries.sql b/tests/queries/0_stateless/01173_transaction_control_queries.sql index 486b738e724..02397b85cb8 100644 --- a/tests/queries/0_stateless/01173_transaction_control_queries.sql +++ b/tests/queries/0_stateless/01173_transaction_control_queries.sql @@ -3,8 +3,8 @@ drop table if exists mt2; create table mt1 (n Int64) engine=MergeTree order by n; create table mt2 (n Int64) engine=MergeTree order by n; -system stop merges mt1; --FIXME -system stop merges mt2; --FIXME +--system stop merges mt1; --FIXME +--system stop merges mt2; --FIXME commit; -- { serverError 585 } rollback; -- { serverError 585 } @@ -45,10 +45,9 @@ select 'on exception while processing', arraySort(groupArray(n)) from (select n select throwIf(100 < number) from numbers(1000); -- { serverError 395 } -- cannot commit after exception commit; -- { serverError 585 } --- FIXME Transactions: do not allow queries after exception -insert into mt1 values (5); -insert into mt2 values (50); -select 1; +insert into mt1 values (5); -- { serverError 585 } +insert into mt2 values (50); -- { serverError 585 } +select 1; -- { serverError 585 } rollback; begin transaction; diff --git a/tests/queries/0_stateless/01174_select_insert_isolation.sh b/tests/queries/0_stateless/01174_select_insert_isolation.sh index 23fa9b4da32..5697a81bedd 100755 --- a/tests/queries/0_stateless/01174_select_insert_isolation.sh +++ b/tests/queries/0_stateless/01174_select_insert_isolation.sh @@ -8,7 +8,7 @@ set -e $CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS mt"; $CLICKHOUSE_CLIENT --query "CREATE TABLE mt (n Int8, m Int8) ENGINE=MergeTree ORDER BY n PARTITION BY 0 < n"; -$CLICKHOUSE_CLIENT --query "SYSTEM STOP MERGES mt"; #FIXME +#$CLICKHOUSE_CLIENT --query "SYSTEM STOP MERGES mt"; #FIXME function thread_insert_commit() { @@ -35,13 +35,15 @@ function thread_select() { trap "exit 0" INT while true; do + # Result of `uniq | wc -l` must be 1 if the first and the last queries got the same result $CLICKHOUSE_CLIENT --multiquery --query " BEGIN TRANSACTION; - SELECT arraySort(groupArray(n)), arraySort(groupArray(m)) FROM mt; + SELECT arraySort(groupArray(n)), arraySort(groupArray(m)), arraySort(groupArray(_part)) FROM mt; SELECT throwIf((SELECT sum(n) FROM mt) != 0) FORMAT Null; SELECT throwIf((SELECT count() FROM mt) % 2 != 0) FORMAT Null; - SELECT arraySort(groupArray(n)), arraySort(groupArray(m)) FROM mt; - COMMIT;" | uniq | wc -l | grep -v "^1$" ||:; # Must be 1 if the first and the last queries got the same result + SELECT arraySort(groupArray(n)), arraySort(groupArray(m)), arraySort(groupArray(_part)) FROM mt; + COMMIT;" | tee -a ./wtf.log | uniq | wc -l | grep -v "^1$" && $CLICKHOUSE_CLIENT -q "SELECT * FROM system.parts + WHERE database='$CLICKHOUSE_DATABASE' AND table='mt'" ||:; done } @@ -57,4 +59,4 @@ $CLICKHOUSE_CLIENT --multiquery --query " BEGIN TRANSACTION; SELECT count(), sum(n), sum(m=1), sum(m=2), sum(m=3) FROM mt;"; -$CLICKHOUSE_CLIENT --query "DROP TABLE mt"; +#$CLICKHOUSE_CLIENT --query "DROP TABLE mt"; From 529d1aeb197b72e46a01aeb1c6d48a48983648e4 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 18 May 2021 20:07:29 +0300 Subject: [PATCH 0020/1647] fix merges of uncommitted parts --- base/daemon/BaseDaemon.cpp | 2 +- src/Common/TransactionMetadata.cpp | 7 ++-- src/Interpreters/MergeTreeTransaction.cpp | 21 +++++++++++ src/Interpreters/MergeTreeTransaction.h | 2 ++ src/Interpreters/TransactionLog.cpp | 8 +++++ src/Interpreters/TransactionLog.h | 2 ++ src/Storages/MergeTree/MergeTreeData.cpp | 6 +++- .../MergeTree/MergeTreeDataMergerMutator.cpp | 13 ++++--- .../MergeTree/MergeTreeDataMergerMutator.h | 5 ++- .../MergeTree/ReplicatedMergeTreeQueue.cpp | 1 + .../MergeTree/ReplicatedMergeTreeQueue.h | 1 + src/Storages/StorageMergeTree.cpp | 35 ++++++++++++++----- .../01172_transaction_counters.sql | 2 -- .../01173_transaction_control_queries.sql | 2 -- .../01174_select_insert_isolation.sh | 3 +- 15 files changed, 83 insertions(+), 27 deletions(-) diff --git a/base/daemon/BaseDaemon.cpp b/base/daemon/BaseDaemon.cpp index 83384038b7c..48f8bd30c4b 100644 --- a/base/daemon/BaseDaemon.cpp +++ b/base/daemon/BaseDaemon.cpp @@ -787,7 +787,7 @@ void BaseDaemon::initializeTerminationAndSignalProcessing() /// Setup signal handlers. /// SIGTSTP is added for debugging purposes. To output a stack trace of any running thread at anytime. - addSignalHandler({SIGABRT, SIGSEGV, SIGILL, SIGBUS, SIGSYS, SIGFPE, SIGPIPE, SIGTSTP, SIGTRAP}, signalHandler, &handled_signals); + addSignalHandler({SIGABRT, SIGSEGV, SIGILL, SIGBUS, SIGSYS, SIGFPE, SIGPIPE, SIGTSTP}, signalHandler, &handled_signals); addSignalHandler({SIGHUP, SIGUSR1}, closeLogsSignalHandler, &handled_signals); addSignalHandler({SIGINT, SIGQUIT, SIGTERM}, terminateRequestedSignalHandler, &handled_signals); diff --git a/src/Common/TransactionMetadata.cpp b/src/Common/TransactionMetadata.cpp index a4dcebbf3ef..7116ede8e9a 100644 --- a/src/Common/TransactionMetadata.cpp +++ b/src/Common/TransactionMetadata.cpp @@ -123,9 +123,12 @@ bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) return false; /// Otherwise, part is definitely visible if: - /// - creation was committed after we took the snapshot and nobody tried to remove the part + /// - creation was committed before we took the snapshot and nobody tried to remove the part + /// - creation was committed before and removal was committed after /// - current transaction is creating it - if (!max_lock && min && min <= snapshot_version) + if (min && min <= snapshot_version && !max_lock) + return true; + if (min && min <= snapshot_version && max && snapshot_version < max) return true; if (mintid == txn.tid) return true; diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index 93f7524123a..726a92a6d68 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -112,4 +112,25 @@ void MergeTreeTransaction::onException() TransactionLog::instance().rollbackTransaction(shared_from_this()); } +String MergeTreeTransaction::dumpDescription() const +{ + String res = "\ncreating parts:\n"; + for (const auto & part : creating_parts) + { + res += part->name; + res += "\n"; + } + + res += "removing parts:\n"; + for (const auto & part : removing_parts) + { + res += part->name; + res += fmt::format(" (created by {}, {})\n", part->versions.getMinTID(), part->versions.mincsn); + assert(!part->versions.mincsn || part->versions.mincsn <= snapshot); + assert(!part->versions.maxcsn); + } + + return res; +} + } diff --git a/src/Interpreters/MergeTreeTransaction.h b/src/Interpreters/MergeTreeTransaction.h index c9fad85f612..832ee92b230 100644 --- a/src/Interpreters/MergeTreeTransaction.h +++ b/src/Interpreters/MergeTreeTransaction.h @@ -39,6 +39,8 @@ public: void onException(); + String dumpDescription() const; + private: void beforeCommit(); void afterCommit(CSN assigned_csn) noexcept; diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index dac5f48abd4..7e7357c68cc 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace DB { @@ -17,6 +18,7 @@ TransactionLog & TransactionLog::instance() } TransactionLog::TransactionLog() + : log(&Poco::Logger::get("TransactionLog")) { latest_snapshot = 1; csn_counter = 1; @@ -39,6 +41,7 @@ MergeTreeTransactionPtr TransactionLog::beginTransaction() if (!inserted) throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} exists", txn->tid.getHash(), txn->tid); } + LOG_TRACE(log, "Beginning transaction {}", txn->tid); return txn; } @@ -50,10 +53,12 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) /// TODO Transactions: reset local_tid_counter if (txn->isReadOnly()) { + LOG_TRACE(log, "Closing readonly transaction {}", txn->tid); new_csn = txn->snapshot; } else { + LOG_TRACE(log, "Committing transaction {}{}", txn->tid, txn->dumpDescription()); std::lock_guard lock{commit_mutex}; new_csn = 1 + csn_counter.fetch_add(1); bool inserted = tid_to_csn.try_emplace(txn->tid.getHash(), new_csn).second; /// Commit point @@ -62,6 +67,8 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) latest_snapshot.store(new_csn, std::memory_order_relaxed); } + LOG_INFO(log, "Transaction {} committed with CSN={}", txn->tid, new_csn); + txn->afterCommit(new_csn); { @@ -75,6 +82,7 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) void TransactionLog::rollbackTransaction(const MergeTreeTransactionPtr & txn) noexcept { + LOG_TRACE(log, "Rolling back transaction {}", txn->tid); txn->rollback(); { std::lock_guard lock{running_list_mutex}; diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index 11e896b0a2d..d4894deac5c 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -30,6 +30,8 @@ public: MergeTreeTransactionPtr tryGetRunningTransaction(const TIDHash & tid); private: + Poco::Logger * log; + std::atomic latest_snapshot; std::atomic csn_counter; std::atomic local_tid_counter; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 09f4811fe71..29396bdf213 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1084,7 +1084,7 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) { (*it)->remove_time.store((*it)->modification_time, std::memory_order_relaxed); modifyPartState(it, DataPartState::Outdated); - (*it)->versions.maxtid = Tx::PrehistoricTID; + (*it)->versions.lockMaxTID(Tx::PrehistoricTID); (*it)->versions.maxcsn.store(Tx::PrehistoricCSN, std::memory_order_relaxed); removePartContributionToDataVolume(*it); }; @@ -3225,10 +3225,13 @@ MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVector(const Me auto it = maybe_visible_parts.begin(); auto it_last = maybe_visible_parts.end() - 1; + String visible_parts_str; while (it <= it_last) { if ((*it)->versions.isVisible(*txn)) { + visible_parts_str += (*it)->name; + visible_parts_str += " "; ++it; } else @@ -3239,6 +3242,7 @@ MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVector(const Me } size_t new_size = it_last - maybe_visible_parts.begin() + 1; + LOG_TRACE(log, "Got {} parts visible for {}: {}", new_size, txn->tid, visible_parts_str); maybe_visible_parts.resize(new_size); return maybe_visible_parts; } diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 2a3f8608dee..289ba50e434 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -216,10 +216,9 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( const MergeTreeTransactionPtr & txn, String * out_disable_reason) { - - MergeTreeData::DataPartsVector data_parts = data.getVisibleDataPartsVector(txn); - //FIXME get rid of sorting - std::sort(data_parts.begin(), data_parts.end(), MergeTreeData::LessDataPart()); + /// NOTE It will contain uncommitted parts and future parts. + /// But It's ok since merge predicate allows to include in range visible parts only. + MergeTreeData::DataPartsVector data_parts = data.getDataPartsVector(); const auto data_settings = data.getSettings(); auto metadata_snapshot = data.getInMemoryMetadataPtr(); @@ -265,7 +264,7 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( * So we have to check if this part is currently being inserted with quorum and so on and so forth. * Obviously we have to check it manually only for the first part * of each partition because it will be automatically checked for a pair of parts. */ - if (!can_merge_callback(nullptr, part, nullptr)) + if (!can_merge_callback(nullptr, part, txn.get(), nullptr)) continue; } @@ -273,7 +272,7 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( { /// If we cannot merge with previous part we had to start new parts /// interval (in the same partition) - if (!can_merge_callback(*prev_part, part, nullptr)) + if (!can_merge_callback(*prev_part, part, txn.get(), nullptr)) { /// Starting new interval in the same partition assert(!parts_ranges.back().empty()); @@ -415,7 +414,7 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectAllPartsToMergeWithinParti while (it != parts.end()) { /// For the case of one part, we check that it can be merged "with itself". - if ((it != parts.begin() || parts.size() == 1) && !can_merge(*prev_it, *it, out_disable_reason)) + if ((it != parts.begin() || parts.size() == 1) && !can_merge(*prev_it, *it, nullptr, out_disable_reason)) { return SelectPartsDecision::CANNOT_SELECT; } diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h index c76179ce435..7340a86f4a7 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h @@ -61,7 +61,10 @@ struct FutureMergedMutatedPart class MergeTreeDataMergerMutator { public: - using AllowedMergingPredicate = std::function; + using AllowedMergingPredicate = std::function; MergeTreeDataMergerMutator(MergeTreeData & data_, size_t background_pool_size); diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp index b81b5712594..1dd4962453e 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.cpp @@ -1811,6 +1811,7 @@ ReplicatedMergeTreeMergePredicate::ReplicatedMergeTreeMergePredicate( bool ReplicatedMergeTreeMergePredicate::operator()( const MergeTreeData::DataPartPtr & left, const MergeTreeData::DataPartPtr & right, + const MergeTreeTransaction *, String * out_reason) const { if (left) diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h index 8b1028623b2..111168d198e 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h +++ b/src/Storages/MergeTree/ReplicatedMergeTreeQueue.h @@ -451,6 +451,7 @@ public: /// Depending on the existence of left part checks a merge predicate for two parts or for single part. bool operator()(const MergeTreeData::DataPartPtr & left, const MergeTreeData::DataPartPtr & right, + const MergeTreeTransaction * txn, String * out_reason = nullptr) const; /// Can we assign a merge with these two parts? diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 4e56076a28a..f21d039d937 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -698,8 +698,18 @@ std::shared_ptr StorageMergeTree::se CurrentlyMergingPartsTaggerPtr merging_tagger; MergeList::EntryPtr merge_entry; - auto can_merge = [this, &lock](const DataPartPtr & left, const DataPartPtr & right, String *) -> bool + auto can_merge = [this, &lock](const DataPartPtr & left, const DataPartPtr & right, const MergeTreeTransaction * tx, String *) -> bool { + if (tx) + { + /// Cannot merge parts if some of them is not visible in current snapshot + /// TODO We can use simplified visibility rules (without CSN lookup) here + if (left && !left->versions.isVisible(*tx)) + return false; + if (right && !right->versions.isVisible(*tx)) + return false; + } + /// This predicate is checked for the first part of each range. /// (left = nullptr, right = "first part of partition") if (!left) @@ -1022,28 +1032,35 @@ std::optional StorageMergeTree::getDataProcessingJob() //-V657 if (merger_mutator.merges_blocker.isCancelled()) return {}; - /// FIXME Transactions: do not begin transaction if we don't need it - auto txn = TransactionLog::instance().beginTransaction(); - MergeTreeTransactionHolder autocommit{txn, true}; - auto metadata_snapshot = getInMemoryMetadataPtr(); std::shared_ptr merge_entry, mutate_entry; auto share_lock = lockForShare(RWLockImpl::NO_QUERY, getSettings()->lock_acquire_timeout_for_background_operations); - merge_entry = selectPartsToMerge(metadata_snapshot, false, {}, false, nullptr, share_lock, txn); + merge_entry = selectPartsToMerge(metadata_snapshot, false, {}, false, nullptr, share_lock, nullptr); if (!merge_entry) mutate_entry = selectPartsToMutate(metadata_snapshot, nullptr, share_lock); if (merge_entry || mutate_entry) { - return JobAndPool{[this, metadata_snapshot, merge_entry, mutate_entry, share_lock, holder = std::move(autocommit)] () mutable + return JobAndPool{[this, metadata_snapshot, merge_entry, mutate_entry, share_lock] () mutable { + merge_entry = {}; + mutate_entry = {}; + /// FIXME Transactions: do not begin transaction if we don't need it + auto txn = TransactionLog::instance().beginTransaction(); + MergeTreeTransactionHolder autocommit{txn, true}; + + merge_entry = selectPartsToMerge(metadata_snapshot, false, {}, false, nullptr, share_lock, txn); + if (!merge_entry) + mutate_entry = selectPartsToMutate(metadata_snapshot, nullptr, share_lock); + if (merge_entry) - return mergeSelectedParts(metadata_snapshot, false, {}, *merge_entry, share_lock, holder.getTransaction()); + return mergeSelectedParts(metadata_snapshot, false, {}, *merge_entry, share_lock, txn); else if (mutate_entry) return mutateSelectedPart(metadata_snapshot, *mutate_entry, share_lock); - __builtin_unreachable(); + return true; + //__builtin_unreachable(); }, PoolType::MERGE_MUTATE}; } else if (auto lock = time_after_previous_cleanup.compareAndRestartDeferred(1)) diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql index db7aa639f5d..2637041bcf7 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.sql +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -2,8 +2,6 @@ drop table if exists txn_counters; create table txn_counters (n Int64, mintid DEFAULT transactionID()) engine=MergeTree order by n; -system stop merges txn_counters; --FIXME - insert into txn_counters(n) values (1); select transactionID(); diff --git a/tests/queries/0_stateless/01173_transaction_control_queries.sql b/tests/queries/0_stateless/01173_transaction_control_queries.sql index 02397b85cb8..b85d34f667b 100644 --- a/tests/queries/0_stateless/01173_transaction_control_queries.sql +++ b/tests/queries/0_stateless/01173_transaction_control_queries.sql @@ -3,8 +3,6 @@ drop table if exists mt2; create table mt1 (n Int64) engine=MergeTree order by n; create table mt2 (n Int64) engine=MergeTree order by n; ---system stop merges mt1; --FIXME ---system stop merges mt2; --FIXME commit; -- { serverError 585 } rollback; -- { serverError 585 } diff --git a/tests/queries/0_stateless/01174_select_insert_isolation.sh b/tests/queries/0_stateless/01174_select_insert_isolation.sh index 5697a81bedd..e692b36e1af 100755 --- a/tests/queries/0_stateless/01174_select_insert_isolation.sh +++ b/tests/queries/0_stateless/01174_select_insert_isolation.sh @@ -8,7 +8,6 @@ set -e $CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS mt"; $CLICKHOUSE_CLIENT --query "CREATE TABLE mt (n Int8, m Int8) ENGINE=MergeTree ORDER BY n PARTITION BY 0 < n"; -#$CLICKHOUSE_CLIENT --query "SYSTEM STOP MERGES mt"; #FIXME function thread_insert_commit() { @@ -59,4 +58,4 @@ $CLICKHOUSE_CLIENT --multiquery --query " BEGIN TRANSACTION; SELECT count(), sum(n), sum(m=1), sum(m=2), sum(m=3) FROM mt;"; -#$CLICKHOUSE_CLIENT --query "DROP TABLE mt"; +$CLICKHOUSE_CLIENT --query "DROP TABLE mt"; From 19337dc227338258b3c8e71a08d78cc0f3963645 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 18 May 2021 22:34:49 +0300 Subject: [PATCH 0021/1647] add config --- src/Interpreters/Context.cpp | 2 +- src/Storages/System/StorageSystemParts.cpp | 10 +++++----- tests/config/install.sh | 1 + tests/config/transactions.xml | 3 +++ 4 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 tests/config/transactions.xml diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 88cba971f39..ec4c4625f95 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2656,7 +2656,7 @@ void Context::setCurrentTransaction(MergeTreeTransactionPtr txn) { assert(!merge_tree_transaction || !txn); assert(this == session_context.lock().get() || this == query_context.lock().get()); - int enable_mvcc_test_helper = getConfigRef().getInt("_enable_mvcc_test_helper_dev", 0); + int enable_mvcc_test_helper = getConfigRef().getInt("_enable_experimental_mvcc_prototype_test_helper_dev", 0); if (enable_mvcc_test_helper != 42) throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Transactions are not supported"); merge_tree_transaction = std::move(txn); diff --git a/src/Storages/System/StorageSystemParts.cpp b/src/Storages/System/StorageSystemParts.cpp index 030226d86dc..d3d1f4c85ee 100644 --- a/src/Storages/System/StorageSystemParts.cpp +++ b/src/Storages/System/StorageSystemParts.cpp @@ -259,11 +259,6 @@ void StorageSystemParts::processNextStorage( add_ttl_info_map(part->ttl_infos.group_by_ttl); add_ttl_info_map(part->ttl_infos.rows_where_ttl); - /// _state column should be the latest. - /// Do not use part->getState*, it can be changed from different thread - if (has_state_column) - columns[res_index++]->insert(IMergeTreeDataPart::stateToString(part_state)); - auto get_tid_as_field = [](const TransactionID & tid) -> Field { return Tuple{tid.start_csn, tid.local_tid, tid.host_id}; @@ -277,6 +272,11 @@ void StorageSystemParts::processNextStorage( columns[res_index++]->insert(part->versions.mincsn.load(std::memory_order_relaxed)); if (columns_mask[src_index++]) columns[res_index++]->insert(part->versions.maxcsn.load(std::memory_order_relaxed)); + + /// _state column should be the latest. + /// Do not use part->getState*, it can be changed from different thread + if (has_state_column) + columns[res_index++]->insert(IMergeTreeDataPart::stateToString(part_state)); } } diff --git a/tests/config/install.sh b/tests/config/install.sh index 7e01860e241..43e04a8109e 100755 --- a/tests/config/install.sh +++ b/tests/config/install.sh @@ -46,6 +46,7 @@ ln -sf $SRC_PATH/strings_dictionary.xml $DEST_SERVER_PATH/ ln -sf $SRC_PATH/decimals_dictionary.xml $DEST_SERVER_PATH/ ln -sf $SRC_PATH/executable_dictionary.xml $DEST_SERVER_PATH/ ln -sf $SRC_PATH/executable_pool_dictionary.xml $DEST_SERVER_PATH/ +ln -sf $SRC_PATH/transactions.xml $DEST_SERVER_PATH/ ln -sf $SRC_PATH/top_level_domains $DEST_SERVER_PATH/ diff --git a/tests/config/transactions.xml b/tests/config/transactions.xml new file mode 100644 index 00000000000..731a312ed58 --- /dev/null +++ b/tests/config/transactions.xml @@ -0,0 +1,3 @@ + + <_enable_experimental_mvcc_prototype_test_helper_dev>42 + From 13ae56985f488173d1a8c8e5eca31554ac245a71 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 26 May 2021 05:41:38 +0300 Subject: [PATCH 0022/1647] dynamic subcolumns: better handling of missed values --- src/Columns/ColumnNullable.cpp | 7 +- src/Columns/ColumnNullable.h | 2 +- src/Columns/ColumnObject.cpp | 17 +++-- src/Columns/ColumnObject.h | 71 +++++++++---------- src/Columns/ColumnSparse.cpp | 2 +- src/Columns/ColumnVector.cpp | 5 +- src/Columns/ColumnVector.h | 2 +- src/Columns/IColumn.cpp | 6 +- src/Columns/IColumn.h | 11 ++- src/DataTypes/ObjectUtils.cpp | 58 ++++++++++----- src/DataTypes/ObjectUtils.h | 2 + src/DataTypes/Serializations/JSONDataParser.h | 10 ++- .../Serializations/SerializationObject.cpp | 54 ++++++++++++-- .../tests/gtest_json_parser.cpp | 2 +- src/DataTypes/getLeastSupertype.cpp | 16 +++-- src/Functions/FunctionsConversion.h | 1 + src/Functions/IFunction.cpp | 2 +- 17 files changed, 178 insertions(+), 90 deletions(-) diff --git a/src/Columns/ColumnNullable.cpp b/src/Columns/ColumnNullable.cpp index 2b6dd1106b3..5541e2bf681 100644 --- a/src/Columns/ColumnNullable.cpp +++ b/src/Columns/ColumnNullable.cpp @@ -707,10 +707,11 @@ void ColumnNullable::getIndicesOfNonDefaultValues(Offsets & indices, size_t from indices.push_back(i); } -ColumnPtr ColumnNullable::createWithOffsets(const IColumn::Offsets & offsets, size_t total_rows, size_t shift) const +ColumnPtr ColumnNullable::createWithOffsets(const IColumn::Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const { - auto new_values = nested_column->createWithOffsets(offsets, total_rows, shift); - auto new_null_map = null_map->createWithOffsets(offsets, total_rows, shift); + /// TODO: check if default_field is null. + auto new_values = nested_column->createWithOffsets(offsets, default_field, total_rows, shift); + auto new_null_map = null_map->createWithOffsets(offsets, Field(1), total_rows, shift); return ColumnNullable::create(new_values, new_null_map); } diff --git a/src/Columns/ColumnNullable.h b/src/Columns/ColumnNullable.h index 55282f4569b..79a4ca12ea0 100644 --- a/src/Columns/ColumnNullable.h +++ b/src/Columns/ColumnNullable.h @@ -174,7 +174,7 @@ public: size_t getNumberOfDefaultRows(size_t step) const override; void getIndicesOfNonDefaultValues(Offsets & indices, size_t from, size_t limit) const override; - ColumnPtr createWithOffsets(const IColumn::Offsets & offsets, size_t total_rows, size_t shift) const override; + ColumnPtr createWithOffsets(const IColumn::Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const override; private: WrappedPtr nested_column; diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 0091454ca11..523ea15ad5c 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -6,6 +6,8 @@ #include #include +#include + namespace DB { @@ -154,7 +156,7 @@ Names ColumnObject::getKeys() const void ColumnObject::optimizeTypesOfSubcolumns() { - if (optimized_subcolumn_types) + if (optimized_types_of_subcolumns) return; for (auto & [_, subcolumn] : subcolumns) @@ -164,7 +166,7 @@ void ColumnObject::optimizeTypesOfSubcolumns() continue; size_t subcolumn_size = subcolumn.size(); - if (subcolumn.data->getNumberOfDefaultRows(1) == 0) + if (subcolumn.data->getNumberOfDefaultRows(/*step=*/ 1) == 0) { subcolumn.data = castColumn({subcolumn.data, from_type, ""}, subcolumn.least_common_type); } @@ -174,13 +176,16 @@ void ColumnObject::optimizeTypesOfSubcolumns() auto & offsets_data = offsets->getData(); subcolumn.data->getIndicesOfNonDefaultValues(offsets_data, 0, subcolumn_size); - auto values = subcolumn.data->index(*offsets, subcolumn_size); - values = castColumn({subcolumn.data, from_type, ""}, subcolumn.least_common_type); - subcolumn.data = values->createWithOffsets(offsets_data, subcolumn_size, /*shift=*/ 0); + + auto values = subcolumn.data->index(*offsets, offsets->size()); + values = castColumn({values, from_type, ""}, subcolumn.least_common_type); + + subcolumn.data = values->createWithOffsets( + offsets_data, subcolumn.least_common_type->getDefault(), subcolumn_size, /*shift=*/ 0); } } - optimized_subcolumn_types = true; + optimized_types_of_subcolumns = true; } } diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index d924b4c0e84..07412cbf4bc 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -38,7 +38,7 @@ public: private: SubcolumnsMap subcolumns; - bool optimized_subcolumn_types = false; + bool optimized_types_of_subcolumns = false; public: ColumnObject() = default; @@ -74,47 +74,40 @@ public: /// All other methods throw exception. - ColumnPtr decompress() const override { throwMustBeDecompressed(); } - - TypeIndex getDataType() const override { throwMustBeDecompressed(); } - Field operator[](size_t) const override { throwMustBeDecompressed(); } - void get(size_t, Field &) const override { throwMustBeDecompressed(); } - StringRef getDataAt(size_t) const override { throwMustBeDecompressed(); } - void insert(const Field &) override { throwMustBeDecompressed(); } - void insertRangeFrom(const IColumn &, size_t, size_t) override { throwMustBeDecompressed(); } - void insertData(const char *, size_t) override { throwMustBeDecompressed(); } - void insertDefault() override { throwMustBeDecompressed(); } - void popBack(size_t) override { throwMustBeDecompressed(); } - StringRef serializeValueIntoArena(size_t, Arena &, char const *&) const override { throwMustBeDecompressed(); } - const char * deserializeAndInsertFromArena(const char *) override { throwMustBeDecompressed(); } - const char * skipSerializedInArena(const char *) const override { throwMustBeDecompressed(); } - void updateHashWithValue(size_t, SipHash &) const override { throwMustBeDecompressed(); } - void updateWeakHash32(WeakHash32 &) const override { throwMustBeDecompressed(); } - void updateHashFast(SipHash &) const override { throwMustBeDecompressed(); } - ColumnPtr filter(const Filter &, ssize_t) const override { throwMustBeDecompressed(); } - ColumnPtr permute(const Permutation &, size_t) const override { throwMustBeDecompressed(); } - ColumnPtr index(const IColumn &, size_t) const override { throwMustBeDecompressed(); } - int compareAt(size_t, size_t, const IColumn &, int) const override { throwMustBeDecompressed(); } - void compareColumn(const IColumn &, size_t, PaddedPODArray *, PaddedPODArray &, int, int) const override - { - throwMustBeDecompressed(); - } - bool hasEqualValues() const override - { - throwMustBeDecompressed(); - } - void getPermutation(bool, size_t, int, Permutation &) const override { throwMustBeDecompressed(); } - void updatePermutation(bool, size_t, int, Permutation &, EqualRanges &) const override { throwMustBeDecompressed(); } - ColumnPtr replicate(const Offsets &) const override { throwMustBeDecompressed(); } - MutableColumns scatter(ColumnIndex, const Selector &) const override { throwMustBeDecompressed(); } - void gather(ColumnGathererStream &) override { throwMustBeDecompressed(); } - void getExtremes(Field &, Field &) const override { throwMustBeDecompressed(); } - size_t byteSizeAt(size_t) const override { throwMustBeDecompressed(); } + ColumnPtr decompress() const override { throwMustBeConcrete(); } + TypeIndex getDataType() const override { throwMustBeConcrete(); } + Field operator[](size_t) const override { throwMustBeConcrete(); } + void get(size_t, Field &) const override { throwMustBeConcrete(); } + StringRef getDataAt(size_t) const override { throwMustBeConcrete(); } + void insert(const Field &) override { throwMustBeConcrete(); } + void insertRangeFrom(const IColumn &, size_t, size_t) override { throwMustBeConcrete(); } + void insertData(const char *, size_t) override { throwMustBeConcrete(); } + void insertDefault() override { throwMustBeConcrete(); } + void popBack(size_t) override { throwMustBeConcrete(); } + StringRef serializeValueIntoArena(size_t, Arena &, char const *&) const override { throwMustBeConcrete(); } + const char * deserializeAndInsertFromArena(const char *) override { throwMustBeConcrete(); } + const char * skipSerializedInArena(const char *) const override { throwMustBeConcrete(); } + void updateHashWithValue(size_t, SipHash &) const override { throwMustBeConcrete(); } + void updateWeakHash32(WeakHash32 &) const override { throwMustBeConcrete(); } + void updateHashFast(SipHash &) const override { throwMustBeConcrete(); } + ColumnPtr filter(const Filter &, ssize_t) const override { throwMustBeConcrete(); } + ColumnPtr permute(const Permutation &, size_t) const override { throwMustBeConcrete(); } + ColumnPtr index(const IColumn &, size_t) const override { throwMustBeConcrete(); } + int compareAt(size_t, size_t, const IColumn &, int) const override { throwMustBeConcrete(); } + void compareColumn(const IColumn &, size_t, PaddedPODArray *, PaddedPODArray &, int, int) const override { throwMustBeConcrete(); } + bool hasEqualValues() const override { throwMustBeConcrete(); } + void getPermutation(bool, size_t, int, Permutation &) const override { throwMustBeConcrete(); } + void updatePermutation(bool, size_t, int, Permutation &, EqualRanges &) const override { throwMustBeConcrete(); } + ColumnPtr replicate(const Offsets &) const override { throwMustBeConcrete(); } + MutableColumns scatter(ColumnIndex, const Selector &) const override { throwMustBeConcrete(); } + void gather(ColumnGathererStream &) override { throwMustBeConcrete(); } + void getExtremes(Field &, Field &) const override { throwMustBeConcrete(); } + size_t byteSizeAt(size_t) const override { throwMustBeConcrete(); } private: - [[noreturn]] void throwMustBeDecompressed() const + [[noreturn]] void throwMustBeConcrete() const { - throw Exception("ColumnCompressed must be decompressed before use", ErrorCodes::LOGICAL_ERROR); + throw Exception("ColumnObject must be converted to ColumnTuple before use", ErrorCodes::LOGICAL_ERROR); } }; diff --git a/src/Columns/ColumnSparse.cpp b/src/Columns/ColumnSparse.cpp index 5aeddd6e2dd..ec37f136374 100644 --- a/src/Columns/ColumnSparse.cpp +++ b/src/Columns/ColumnSparse.cpp @@ -118,7 +118,7 @@ StringRef ColumnSparse::getDataAt(size_t n) const ColumnPtr ColumnSparse::convertToFullColumnIfSparse() const { - return values->createWithOffsets(getOffsetsData(), _size, 1); + return values->createWithOffsets(getOffsetsData(), (*values)[0], _size, /*shift=*/ 1); } void ColumnSparse::insertData(const char * pos, size_t length) diff --git a/src/Columns/ColumnVector.cpp b/src/Columns/ColumnVector.cpp index 44c6132f23c..72bcdc499d6 100644 --- a/src/Columns/ColumnVector.cpp +++ b/src/Columns/ColumnVector.cpp @@ -583,7 +583,7 @@ void ColumnVector::getIndicesOfNonDefaultValues(IColumn::Offsets & indices, s } template -ColumnPtr ColumnVector::createWithOffsets(const IColumn::Offsets & offsets, size_t total_rows, size_t shift) const +ColumnPtr ColumnVector::createWithOffsets(const IColumn::Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const { if (offsets.size() + shift != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, @@ -592,7 +592,8 @@ ColumnPtr ColumnVector::createWithOffsets(const IColumn::Offsets & offsets, s auto res = this->create(); auto & res_data = res->getData(); - res_data.resize_fill(total_rows, data[0]); + T default_value = safeGet(default_field); + res_data.resize_fill(total_rows, default_value); for (size_t i = 0; i < offsets.size(); ++i) res_data[offsets[i]] = data[i + shift]; diff --git a/src/Columns/ColumnVector.h b/src/Columns/ColumnVector.h index 3001c88a516..6e5578d9994 100644 --- a/src/Columns/ColumnVector.h +++ b/src/Columns/ColumnVector.h @@ -323,7 +323,7 @@ public: size_t getNumberOfDefaultRows(size_t step) const override; void getIndicesOfNonDefaultValues(IColumn::Offsets & indices, size_t from, size_t limit) const override; - ColumnPtr createWithOffsets(const IColumn::Offsets & offsets, size_t total_rows, size_t shift) const override; + ColumnPtr createWithOffsets(const IColumn::Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const override; ColumnPtr compress() const override; diff --git a/src/Columns/IColumn.cpp b/src/Columns/IColumn.cpp index 45c7967072f..ebed783cdd3 100644 --- a/src/Columns/IColumn.cpp +++ b/src/Columns/IColumn.cpp @@ -44,7 +44,7 @@ void IColumn::getIndicesOfNonDefaultValues(Offsets & indices, size_t from, size_ indices.push_back(i); } -ColumnPtr IColumn::createWithOffsets(const Offsets & offsets, size_t total_rows, size_t shift) const +ColumnPtr IColumn::createWithOffsets(const Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const { if (offsets.size() + shift != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, @@ -60,14 +60,14 @@ ColumnPtr IColumn::createWithOffsets(const Offsets & offsets, size_t total_rows, current_offset = offsets[i]; if (offsets_diff > 1) - res->insertManyFrom(*this, 0, offsets_diff - 1); + res->insertMany(default_field, offsets_diff - 1); res->insertFrom(*this, i + shift); } ssize_t offsets_diff = static_cast(total_rows) - current_offset; if (offsets_diff > 1) - res->insertManyFrom(*this, 0, offsets_diff - 1); + res->insertMany(default_field, offsets_diff - 1); return res; } diff --git a/src/Columns/IColumn.h b/src/Columns/IColumn.h index 7c0b6647a23..968ffe99a61 100644 --- a/src/Columns/IColumn.h +++ b/src/Columns/IColumn.h @@ -182,6 +182,13 @@ public: insertFrom(src, position); } + /// Appends one field multiple times. Can be optimized in inherited classes. + virtual void insertMany(const Field & field, size_t length) + { + for (size_t i = 0; i < length; ++i) + insert(field); + } + /// Appends data located in specified memory chunk if it is possible (throws an exception if it cannot be implemented). /// Is used to optimize some computations (in aggregation, for example). /// Parameter length could be ignored if column values have fixed size. @@ -390,10 +397,10 @@ public: /// Returns column with @total_size elements. /// In result column values from current column are at positions from @offsets. - /// Other values are filled by defaults. + /// Other values are filled by @default_value. /// @shift means how much rows to skip from the beginning of current column. /// Used to create full column from sparse. - virtual Ptr createWithOffsets(const Offsets & offsets, size_t total_rows, size_t shift) const; + virtual Ptr createWithOffsets(const Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) 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. diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 7e63165149e..c134920e732 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -3,12 +3,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -40,6 +42,13 @@ size_t getNumberOfDimensions(const IColumn & column) return 0; } +DataTypePtr getBaseTypeOfArray(DataTypePtr type) +{ + while (const auto * type_array = typeid_cast(type.get())) + type = type_array->getNestedType(); + return type; +} + DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension) { for (size_t i = 0; i < dimension; ++i) @@ -50,16 +59,30 @@ DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension) DataTypePtr getDataTypeByColumn(const IColumn & column) { auto idx = column.getDataType(); - if (WhichDataType(column.getDataType()).isSimple()) + if (WhichDataType(idx).isSimple()) return DataTypeFactory::instance().get(getTypeName(idx)); - if (const auto * column_array = typeid_cast(&column)) + if (const auto * column_array = checkAndGetColumn(&column)) return std::make_shared(getDataTypeByColumn(column_array->getData())); + if (const auto * column_nullable = checkAndGetColumn(&column)) + return makeNullable(getDataTypeByColumn(column_nullable->getNestedColumn())); + /// TODO: add more types. throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot get data type of column {}", column.getFamilyName()); } +template +static auto extractVector(const std::vector & vec) +{ + static_assert(I < std::tuple_size_v); + std::vector> res; + res.reserve(vec.size()); + for (const auto & elem : vec) + res.emplace_back(std::get(elem)); + return res; +} + void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) { for (auto & name_type : columns_list) @@ -76,16 +99,16 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) const auto & column_object = assert_cast(*column.column); const auto & subcolumns_map = column_object.getSubcolumns(); - Names tuple_names; - DataTypes tuple_types; - Columns tuple_columns; - + std::vector> tuple_elements; for (const auto & [key, subcolumn] : subcolumns_map) - { - tuple_names.push_back(key); - tuple_types.push_back(getDataTypeByColumn(*subcolumn.data)); - tuple_columns.push_back(subcolumn.data); - } + tuple_elements.emplace_back(key, getDataTypeByColumn(*subcolumn.data), subcolumn.data); + + std::sort(tuple_elements.begin(), tuple_elements.end(), + [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); } ); + + auto tuple_names = extractVector<0>(tuple_elements); + auto tuple_types = extractVector<1>(tuple_elements); + auto tuple_columns = extractVector<2>(tuple_elements); auto type_tuple = std::make_shared(tuple_types, tuple_names); auto column_tuple = ColumnTuple::create(tuple_columns); @@ -115,9 +138,7 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) subcolumns_types[tuple_names[i]].push_back(tuple_types[i]); } - Names tuple_names; - DataTypes tuple_types; - + std::vector> tuple_elements; for (const auto & [name, subtypes] : subcolumns_types) { assert(!subtypes.empty()); @@ -129,10 +150,15 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) "Uncompatible types of subcolumn '{}': {} and {}", name, subtypes[0]->getName(), subtypes[i]->getName()); - tuple_names.push_back(name); - tuple_types.push_back(getLeastSupertype(subtypes, /*allow_conversion_to_string=*/ true)); + tuple_elements.emplace_back(name, getLeastSupertype(subtypes, /*allow_conversion_to_string=*/ true)); } + std::sort(tuple_elements.begin(), tuple_elements.end(), + [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); } ); + + auto tuple_names = extractVector<0>(tuple_elements); + auto tuple_types = extractVector<1>(tuple_elements); + return std::make_shared(tuple_types, tuple_names); } diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index b8b508530ba..a143dd009fb 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -2,12 +2,14 @@ #include #include +#include namespace DB { size_t getNumberOfDimensions(const IDataType & type); size_t getNumberOfDimensions(const IColumn & column); +DataTypePtr getBaseTypeOfArray(DataTypePtr type); DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); DataTypePtr getDataTypeByColumn(const IColumn & column); diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index cac13aab4e8..059ed592464 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -28,9 +28,13 @@ Field getValueAsField(const Element & element) String getNextPath(const String & current_path, const std::string_view & key) { String next_path = current_path; - if (!next_path.empty()) - next_path += "."; - next_path += key; + if (!key.empty()) + { + if (!next_path.empty()) + next_path += "."; + next_path += key; + } + return next_path; } diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 5f3a78f5041..ada3a909b46 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,40 @@ namespace ErrorCodes extern const int TYPE_MISMATCH; } +namespace +{ + +class FieldVisitorReplaceNull : public StaticVisitor +{ +public: + FieldVisitorReplaceNull(const Field & replacement_) + : replacement(replacement_) + { + } + + Field operator() (const Null &) const { return replacement; } + + template + Field operator() (const T & x) const + { + if constexpr (std::is_base_of_v) + { + const size_t size = x.size(); + T res(size); + for (size_t i = 0; i < size; ++i) + res[i] = applyVisitor(*this, x[i]); + return res; + } + else + return x; + } + +private: + Field replacement; +}; + +} + template template void SerializationObject::deserializeTextImpl(IColumn & column, Reader && reader) const @@ -49,11 +84,21 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & size_t column_size = column_object.size(); for (size_t i = 0; i < paths.size(); ++i) { - auto value_type = applyVisitor(FieldToDataType(/*allow_conversion_to_string=*/true), values[i]); + Field value = std::move(values[i]); + auto value_type = applyVisitor(FieldToDataType(/*allow_conversion_to_string=*/ true), value); auto value_dim = getNumberOfDimensions(*value_type); + auto base_type = getBaseTypeOfArray(value_type); + + if (base_type->isNullable()) + { + base_type = removeNullable(base_type); + value = applyVisitor(FieldVisitorReplaceNull(base_type->getDefault()), value); + value_type = createArrayOfType(base_type, value_dim); + } + auto array_type = createArrayOfType(std::make_shared(), value_dim); - auto converted_value = convertFieldToTypeOrThrow(values[i], *array_type, value_type.get()); + auto converted_value = convertFieldToTypeOrThrow(value, *array_type); if (!column_object.hasSubcolumn(paths[i])) column_object.addSubcolumn(paths[i], array_type->createColumn(), column_size); @@ -63,8 +108,9 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & if (value_dim != column_dim) throw Exception(ErrorCodes::TYPE_MISMATCH, - "Dimension of types mismatched beetwen value and column. Dimension of value: {}. Dimension of column: {}", - value_dim, column_dim); + "Dimension of types mismatched beetwen inserted value and column at key '{}'. " + "Dimension of value: {}. Dimension of column: {}", + paths[i], value_dim, column_dim); subcolumn.insert(converted_value, value_type); } diff --git a/src/DataTypes/Serializations/tests/gtest_json_parser.cpp b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp index 2eece7f4fef..f7d84db5e7f 100644 --- a/src/DataTypes/Serializations/tests/gtest_json_parser.cpp +++ b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp @@ -55,7 +55,7 @@ TEST(JSONDataParser, Parse) const auto & [paths, values] = *res; ASSERT_EQ(paths, (Strings{"k1", "k2.k3", "k2.k4"})); - ASSERT_EQ(values, (std::vector{"1", "aa", "2"})); + ASSERT_EQ(values, (std::vector{1, "aa", 2})); } { diff --git a/src/DataTypes/getLeastSupertype.cpp b/src/DataTypes/getLeastSupertype.cpp index 764dd7c04d4..82dbe4f5cd9 100644 --- a/src/DataTypes/getLeastSupertype.cpp +++ b/src/DataTypes/getLeastSupertype.cpp @@ -96,7 +96,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_ non_nothing_types.emplace_back(type); if (non_nothing_types.size() < types.size()) - return getLeastSupertype(non_nothing_types); + return getLeastSupertype(non_nothing_types, allow_conversion_to_string); } /// For Arrays @@ -123,7 +123,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_ if (!all_arrays) return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are Array and some of them are not", ErrorCodes::NO_COMMON_TYPE); - return std::make_shared(getLeastSupertype(nested_types)); + return std::make_shared(getLeastSupertype(nested_types, allow_conversion_to_string)); } } @@ -165,7 +165,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_ DataTypes common_tuple_types(tuple_size); for (size_t elem_idx = 0; elem_idx < tuple_size; ++elem_idx) - common_tuple_types[elem_idx] = getLeastSupertype(nested_types[elem_idx]); + common_tuple_types[elem_idx] = getLeastSupertype(nested_types[elem_idx], allow_conversion_to_string); return std::make_shared(common_tuple_types); } @@ -197,7 +197,9 @@ DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_ if (!all_maps) return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are Maps and some of them are not", ErrorCodes::NO_COMMON_TYPE); - return std::make_shared(getLeastSupertype(key_types), getLeastSupertype(value_types)); + return std::make_shared( + getLeastSupertype(key_types, allow_conversion_to_string), + getLeastSupertype(value_types, allow_conversion_to_string)); } } @@ -228,9 +230,9 @@ DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_ if (have_low_cardinality) { if (have_not_low_cardinality) - return getLeastSupertype(nested_types); + return getLeastSupertype(nested_types, allow_conversion_to_string); else - return std::make_shared(getLeastSupertype(nested_types)); + return std::make_shared(getLeastSupertype(nested_types, allow_conversion_to_string)); } } @@ -256,7 +258,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_ if (have_nullable) { - return std::make_shared(getLeastSupertype(nested_types)); + return std::make_shared(getLeastSupertype(nested_types, allow_conversion_to_string)); } } diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 6168b723281..7e26e479f42 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -2599,6 +2599,7 @@ private: if (from_type->haveExplicitNames() && to_type->haveExplicitNames()) { + std::cerr << "converting tuples...\n"; const auto & from_names = from_type->getElementNames(); std::unordered_map from_positions; from_positions.reserve(from_names.size()); diff --git a/src/Functions/IFunction.cpp b/src/Functions/IFunction.cpp index a4f40a383c7..b38fca694cf 100644 --- a/src/Functions/IFunction.cpp +++ b/src/Functions/IFunction.cpp @@ -323,7 +323,7 @@ ColumnPtr IExecutableFunction::execute(const ColumnsWithTypeAndName & arguments, if (!res->isDefaultAt(0)) { const auto & offsets_data = assert_cast &>(*sparse_offsets).getData(); - return res->createWithOffsets(offsets_data, input_rows_count, /*shift=*/ 1); + return res->createWithOffsets(offsets_data, (*res)[0], input_rows_count, /*shift=*/ 1); } return ColumnSparse::create(res, sparse_offsets, input_rows_count); From b0cc45c361a3d0dff1e10966d963e1ba47bcdfb0 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 26 May 2021 05:43:22 +0300 Subject: [PATCH 0023/1647] dynamic subcolumns: add test --- .../0_stateless/01825_type_json.reference | 8 ++-- .../0_stateless/01825_type_json_2.reference | 24 ++++++++++++ .../queries/0_stateless/01825_type_json_2.sql | 38 +++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_2.reference create mode 100644 tests/queries/0_stateless/01825_type_json_2.sql diff --git a/tests/queries/0_stateless/01825_type_json.reference b/tests/queries/0_stateless/01825_type_json.reference index 5e957434033..4c21f14d4e9 100644 --- a/tests/queries/0_stateless/01825_type_json.reference +++ b/tests/queries/0_stateless/01825_type_json.reference @@ -1,12 +1,12 @@ 1 aa bb c 2 ee ff 3 foo -all_1_1_0 data Tuple(k1 String, `k2.k4` String, `k2.k3` String, k5 String) +all_1_1_0 data Tuple(k1 String, `k2.k3` String, `k2.k4` String, k5 String) all_2_2_0 data Tuple(k5 String) -all_1_2_1 data Tuple(k5 String, `k2.k3` String, `k2.k4` String, k1 String) +all_1_2_1 data Tuple(k1 String, `k2.k3` String, `k2.k4` String, k5 String) ============ 1 ['aaa','ddd'] [['bbb','ccc'],['eee','fff']] -all_3_3_0 data Tuple(`k1.k3.k4` Array(Array(String)), `k1.k2` Array(String)) +all_3_3_0 data Tuple(`k1.k2` Array(String), `k1.k3.k4` Array(Array(String))) ============ 1 a 42 2 b 4200 @@ -24,4 +24,4 @@ all_5_5_0 data Tuple(name String, value Float64) all_4_4_0 data Tuple(name String, value Int16) all_5_5_0 data Tuple(name String, value Float64) all_6_6_0 data Tuple(name String, value String) -all_4_6_1 data Tuple(value String, name String) +all_4_6_1 data Tuple(name String, value String) diff --git a/tests/queries/0_stateless/01825_type_json_2.reference b/tests/queries/0_stateless/01825_type_json_2.reference new file mode 100644 index 00000000000..6afe2ebcb60 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_2.reference @@ -0,0 +1,24 @@ +1 (1,2,0) Tuple(k1 Int8, k2 Int8, k3 Int8) +2 (0,3,4) Tuple(k1 Int8, k2 Int8, k3 Int8) +1 1 2 0 +2 0 3 4 +1 (1,2,'0') Tuple(k1 Int8, k2 Int8, k3 String) +2 (0,3,'4') Tuple(k1 Int8, k2 Int8, k3 String) +3 (0,0,'10') Tuple(k1 Int8, k2 Int8, k3 String) +4 (0,5,'str') Tuple(k1 Int8, k2 Int8, k3 String) +1 1 2 0 +2 0 3 4 +3 0 0 10 +4 0 5 str +============ +1 ([1,2,3.3]) Tuple(k1 Array(Float64)) +1 [1,2,3.3] +1 (['1','2','3.3']) Tuple(k1 Array(String)) +2 (['a','4','b']) Tuple(k1 Array(String)) +1 ['1','2','3.3'] +2 ['a','4','b'] +============ +1 ([11,0],[0,22],[]) Tuple(`k1.k2` Array(Int8), `k1.k3` Array(Int8), `k1.k4` Array(Int8)) +2 ([],[33,0,55],[0,44,66]) Tuple(`k1.k2` Array(Int8), `k1.k3` Array(Int8), `k1.k4` Array(Int8)) +1 [11,0] [0,22] [] +2 [] [33,0,55] [0,44,66] diff --git a/tests/queries/0_stateless/01825_type_json_2.sql b/tests/queries/0_stateless/01825_type_json_2.sql new file mode 100644 index 00000000000..fbea36978c6 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_2.sql @@ -0,0 +1,38 @@ +DROP TABLE IF EXISTS t_json_2; + +CREATE TABLE t_json_2(id UInt64, data Object('JSON')) +ENGINE = MergeTree ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0; + +INSERT INTO t_json_2 FORMAT JSONEachRow {"id": 1, "data": {"k1": 1, "k2" : 2}} {"id": 2, "data": {"k2": 3, "k3" : 4}}; + +SELECT id, data, toTypeName(data) FROM t_json_2 ORDER BY id; +SELECT id, data.k1, data.k2, data.k3 FROM t_json_2 ORDER BY id; + +INSERT INTO t_json_2 FORMAT JSONEachRow {"id": 3, "data": {"k3" : 10}} {"id": 4, "data": {"k2": 5, "k3" : "str"}}; + +SELECT id, data, toTypeName(data) FROM t_json_2 ORDER BY id; +SELECT id, data.k1, data.k2, data.k3 FROM t_json_2 ORDER BY id; + +SELECT '============'; +TRUNCATE TABLE t_json_2; + +INSERT INTO TABLE t_json_2 FORMAT JSONEachRow {"id": 1, "data": {"k1" : [1, 2, 3.3]}}; + +SELECT id, data, toTypeName(data) FROM t_json_2 ORDER BY id; +SELECT id, data.k1 FROM t_json_2 ORDEr BY id; + +INSERT INTO TABLE t_json_2 FORMAT JSONEachRow {"id": 2, "data": {"k1" : ["a", 4, "b"]}}; + +SELECT id, data, toTypeName(data) FROM t_json_2 ORDER BY id; +SELECT id, data.k1 FROM t_json_2 ORDER BY id; + +SELECT '============'; +TRUNCATE TABLE t_json_2; + +INSERT INTO TABLE t_json_2 FORMAT JSONEachRow {"id": 1, "data": {"k1" : [{"k2" : 11}, {"k3" : 22}]}} {"id": 2, "data": {"k1" : [{"k3" : 33}, {"k4" : 44}, {"k3" : 55, "k4" : 66}]}}; + +SELECT id, data, toTypeName(data) FROM t_json_2 ORDER BY id; +SELECT id, data.k1.k2, data.k1.k3, data.k1.k4 FROM t_json_2 ORDER BY id; + +DROP TABLE t_json_2; From d83819fcaeaeae9906caeabff692d1556040e4d2 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 26 May 2021 18:34:34 +0300 Subject: [PATCH 0024/1647] fix build --- src/Columns/IColumn.h | 2 +- src/DataTypes/Serializations/SerializationObject.cpp | 3 +-- src/Functions/FunctionsConversion.h | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Columns/IColumn.h b/src/Columns/IColumn.h index 968ffe99a61..e8e58888c54 100644 --- a/src/Columns/IColumn.h +++ b/src/Columns/IColumn.h @@ -397,7 +397,7 @@ public: /// Returns column with @total_size elements. /// In result column values from current column are at positions from @offsets. - /// Other values are filled by @default_value. + /// Other values are filled by @default_field. /// @shift means how much rows to skip from the beginning of current column. /// Used to create full column from sparse. virtual Ptr createWithOffsets(const Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const; diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index ada3a909b46..5810b2f57b5 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -28,11 +28,10 @@ namespace ErrorCodes namespace { - class FieldVisitorReplaceNull : public StaticVisitor { public: - FieldVisitorReplaceNull(const Field & replacement_) + [[maybe_unused]] explicit FieldVisitorReplaceNull(const Field & replacement_) : replacement(replacement_) { } diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 7e26e479f42..6168b723281 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -2599,7 +2599,6 @@ private: if (from_type->haveExplicitNames() && to_type->haveExplicitNames()) { - std::cerr << "converting tuples...\n"; const auto & from_names = from_type->getElementNames(); std::unordered_map from_positions; from_positions.reserve(from_names.size()); From 7e5f784597bd3fc1e2c848f8dad0a9c3f859572b Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 27 May 2021 19:54:27 +0300 Subject: [PATCH 0025/1647] dynamic subcolumns: better handling of nulls and empty arrays --- src/Columns/ColumnObject.cpp | 35 ++++++++++--- src/Columns/ColumnObject.h | 7 ++- src/DataTypes/DataTypeObject.cpp | 3 ++ src/DataTypes/ObjectUtils.cpp | 6 +++ src/DataTypes/Serializations/JSONDataParser.h | 21 +++++--- .../Serializations/SerializationObject.cpp | 7 ++- .../0_stateless/01825_type_json_3.reference | 31 +++++++++++ .../queries/0_stateless/01825_type_json_3.sql | 51 +++++++++++++++++++ 8 files changed, 144 insertions(+), 17 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_3.reference create mode 100644 tests/queries/0_stateless/01825_type_json_3.sql diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 523ea15ad5c..d0ac443c248 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -90,6 +91,12 @@ size_t ColumnObject::allocatedBytes() const return res; } +void ColumnObject::forEachSubcolumn(ColumnCallback callback) +{ + for (auto & [_, column] : subcolumns) + callback(column.data); +} + const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const String & key) const { auto it = subcolumns.find(key); @@ -159,16 +166,26 @@ void ColumnObject::optimizeTypesOfSubcolumns() if (optimized_types_of_subcolumns) return; - for (auto & [_, subcolumn] : subcolumns) + size_t old_size = size(); + SubcolumnsMap new_subcolumns; + for (auto && [name, subcolumn] : subcolumns) { auto from_type = getDataTypeByColumn(*subcolumn.data); - if (subcolumn.least_common_type->equals(*from_type)) + auto to_type = subcolumn.least_common_type; + + if (isNothing(getBaseTypeOfArray(to_type))) continue; + if (to_type->equals(*from_type)) + { + new_subcolumns[name] = std::move(subcolumn); + continue; + } + size_t subcolumn_size = subcolumn.size(); if (subcolumn.data->getNumberOfDefaultRows(/*step=*/ 1) == 0) { - subcolumn.data = castColumn({subcolumn.data, from_type, ""}, subcolumn.least_common_type); + subcolumn.data = castColumn({subcolumn.data, from_type, ""}, to_type); } else { @@ -176,15 +193,19 @@ void ColumnObject::optimizeTypesOfSubcolumns() auto & offsets_data = offsets->getData(); subcolumn.data->getIndicesOfNonDefaultValues(offsets_data, 0, subcolumn_size); - auto values = subcolumn.data->index(*offsets, offsets->size()); - values = castColumn({values, from_type, ""}, subcolumn.least_common_type); - subcolumn.data = values->createWithOffsets( - offsets_data, subcolumn.least_common_type->getDefault(), subcolumn_size, /*shift=*/ 0); + values = castColumn({values, from_type, ""}, to_type); + subcolumn.data = values->createWithOffsets(offsets_data, to_type->getDefault(), subcolumn_size, /*shift=*/ 0); } + + new_subcolumns[name] = std::move(subcolumn); } + if (new_subcolumns.empty()) + new_subcolumns[COLUMN_NAME_DUMMY] = Subcolumn{ColumnUInt8::create(old_size)}; + + std::swap(subcolumns, new_subcolumns); optimized_types_of_subcolumns = true; } diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 07412cbf4bc..c02418e092d 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -39,8 +39,11 @@ public: private: SubcolumnsMap subcolumns; bool optimized_types_of_subcolumns = false; + size_t size_with_no_subcolumns = 0; public: + static constexpr auto COLUMN_NAME_DUMMY = "_dummy"; + ColumnObject() = default; ColumnObject(SubcolumnsMap && subcolumns_); @@ -65,12 +68,12 @@ public: const char * getFamilyName() const override { return "Object"; } - size_t size() const override { return subcolumns.empty() ? 0 : subcolumns.begin()->second.size(); } + size_t size() const override { return subcolumns.empty() ? size_with_no_subcolumns : subcolumns.begin()->second.size(); } MutableColumnPtr cloneResized(size_t new_size) const override; - size_t byteSize() const override; size_t allocatedBytes() const override; + void forEachSubcolumn(ColumnCallback callback) override; /// All other methods throw exception. diff --git a/src/DataTypes/DataTypeObject.cpp b/src/DataTypes/DataTypeObject.cpp index 52a02e79f66..5f07d9905f4 100644 --- a/src/DataTypes/DataTypeObject.cpp +++ b/src/DataTypes/DataTypeObject.cpp @@ -58,6 +58,9 @@ static DataTypePtr create(const ASTPtr & arguments) void registerDataTypeObject(DataTypeFactory & factory) { factory.registerDataType("Object", create); + factory.registerSimpleDataType("JSON", + [] { return std::make_shared("JSON"); }, + DataTypeFactory::CaseInsensitive); } } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index c134920e732..de330b7e2b2 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -142,6 +143,8 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) for (const auto & [name, subtypes] : subcolumns_types) { assert(!subtypes.empty()); + if (name == ColumnObject::COLUMN_NAME_DUMMY) + continue; size_t first_dim = getNumberOfDimensions(*subtypes[0]); for (size_t i = 1; i < subtypes.size(); ++i) @@ -153,6 +156,9 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) tuple_elements.emplace_back(name, getLeastSupertype(subtypes, /*allow_conversion_to_string=*/ true)); } + if (tuple_elements.empty()) + tuple_elements.emplace_back(ColumnObject::COLUMN_NAME_DUMMY, std::make_shared()); + std::sort(tuple_elements.begin(), tuple_elements.end(), [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); } ); diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index 059ed592464..aab4a5b5c99 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -21,6 +21,7 @@ Field getValueAsField(const Element & element) if (element.isUInt64()) return element.getUInt64(); if (element.isDouble()) return element.getDouble(); if (element.isString()) return element.getString(); + if (element.isNull()) return Field(); throw Exception(ErrorCodes::LOGICAL_ERROR, "Unsupported type of JSON field"); } @@ -122,13 +123,21 @@ private: ++current_size; } - result.paths.reserve(result.paths.size() + arrays_by_path.size()); - result.values.reserve(result.paths.size() + arrays_by_path.size()); - - for (const auto & [path, path_array] : arrays_by_path) + if (arrays_by_path.empty()) { - result.paths.push_back(getNextPath(current_path, path)); - result.values.push_back(path_array); + result.paths.push_back(current_path); + result.values.push_back(Array()); + } + else + { + result.paths.reserve(result.paths.size() + arrays_by_path.size()); + result.values.reserve(result.paths.size() + arrays_by_path.size()); + + for (const auto & [path, path_array] : arrays_by_path) + { + result.paths.push_back(getNextPath(current_path, path)); + result.values.push_back(path_array); + } } } else diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 5810b2f57b5..d38bfd5c769 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -92,12 +92,15 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & if (base_type->isNullable()) { base_type = removeNullable(base_type); - value = applyVisitor(FieldVisitorReplaceNull(base_type->getDefault()), value); + auto default_field = isNothing(base_type) ? Field(String()) : base_type->getDefault(); + value = applyVisitor(FieldVisitorReplaceNull(default_field), value); value_type = createArrayOfType(base_type, value_dim); } auto array_type = createArrayOfType(std::make_shared(), value_dim); - auto converted_value = convertFieldToTypeOrThrow(value, *array_type); + auto converted_value = isNothing(base_type) + ? std::move(value) + : convertFieldToTypeOrThrow(value, *array_type); if (!column_object.hasSubcolumn(paths[i])) column_object.addSubcolumn(paths[i], array_type->createColumn(), column_size); diff --git a/tests/queries/0_stateless/01825_type_json_3.reference b/tests/queries/0_stateless/01825_type_json_3.reference new file mode 100644 index 00000000000..830d44d779c --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_3.reference @@ -0,0 +1,31 @@ +1 ('',0) Tuple(k1 String, k2 Int8) +2 ('v1',2) Tuple(k1 String, k2 Int8) +1 0 +2 v1 2 +======== +1 ([],[]) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +2 (['v1','v4'],['v3','']) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +1 [] [] +2 ['v1','v4'] ['v3',''] +1 ([],[]) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +2 (['v1','v4'],['v3','']) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +3 ([],[]) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +4 ([],[]) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +all_2_2_0 data Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +all_3_3_0 data Tuple(_dummy UInt8) +1 [] [] +2 ['v1','v4'] ['v3',''] +3 [] [] +4 [] [] +data Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +1 [] [] +2 ['v1','v4'] ['v3',''] +3 [] [] +4 [] [] +======== +1 (1,'foo',[]) Tuple(`k1.k2` Int8, `k1.k3` String, k4 Array(Int8)) +2 (0,'',[1,2,3]) Tuple(`k1.k2` Int8, `k1.k3` String, k4 Array(Int8)) +3 (10,'',[]) Tuple(`k1.k2` Int8, `k1.k3` String, k4 Array(Int8)) +1 1 foo [] +2 0 [1,2,3] +3 10 [] diff --git a/tests/queries/0_stateless/01825_type_json_3.sql b/tests/queries/0_stateless/01825_type_json_3.sql new file mode 100644 index 00000000000..cb30f1002fd --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_3.sql @@ -0,0 +1,51 @@ +DROP TABLE IF EXISTS t_json_3; + +CREATE TABLE t_json_3(id UInt64, data JSON) +ENGINE = MergeTree ORDER BY tuple() +SETTINGS min_bytes_for_wide_part = 0; + +SYSTEM STOP MERGES t_json_3; + +INSERT INTO t_json_3 FORMAT JSONEachRow {"id": 1, "data": {"k1": null}}, {"id": 2, "data": {"k1": "v1", "k2" : 2}}; + +SELECT id, data, toTypeName(data) FROM t_json_3 ORDER BY id; +SELECT id, data.k1, data.k2 FROM t_json_3 ORDER BY id; + +SELECT '========'; +TRUNCATE TABLE t_json_3; + +INSERT INTO t_json_3 FORMAT JSONEachRow {"id": 1, "data": {"k1" : []}} {"id": 2, "data": {"k1" : [{"k2" : "v1", "k3" : "v3"}, {"k2" : "v4"}]}}; + +SELECT id, data, toTypeName(data) FROM t_json_3 ORDER BY id; +SELECT id, `data.k1.k2`, `data.k1.k3` FROM t_json_3 ORDER BY id; + +INSERT INTO t_json_3 FORMAT JSONEachRow {"id": 3, "data": {"k1" : []}} {"id": 4, "data": {"k1" : []}}; + +SELECT id, data, toTypeName(data) FROM t_json_3 ORDER BY id; + +SELECT name, column, type +FROM system.parts_columns +WHERE table = 't_json_3' AND database = currentDatabase() AND active AND column = 'data' +ORDER BY name; + +SELECT id, data.k1.k2, data.k1.k3 FROM t_json_3 ORDER BY id; + +SYSTEM START MERGES t_json_3; +OPTIMIZE TABLE t_json_3 FINAL; + +SELECT column, type +FROM system.parts_columns +WHERE table = 't_json_3' AND database = currentDatabase() AND active AND column = 'data' +ORDER BY name; + +SELECT id, data.k1.k2, data.k1.k3 FROM t_json_3 ORDER BY id; + +SELECT '========'; +TRUNCATE TABLE t_json_3; + +INSERT INTO t_json_3 FORMAT JSONEachRow {"id": 1, "data": {"k1" : {"k2" : 1, "k3" : "foo"}}} {"id": 2, "data": {"k1" : null, "k4" : [1, 2, 3]}}, {"id" : 3, "data": {"k1" : {"k2" : 10}, "k4" : []}}; + +SELECT id, data, toTypeName(data) FROM t_json_3 ORDER BY id; +SELECT id, data.k1.k2, data.k1.k3, data.k4 FROM t_json_3 ORDER BY id; + +DROP TABLE t_json_3; From 041e2def3ebb605f0e38dd5afe1378944aeeebf9 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 28 May 2021 03:52:58 +0300 Subject: [PATCH 0026/1647] fix conversion of tuples --- src/DataTypes/DataTypeTuple.cpp | 3 +-- src/DataTypes/Serializations/ColumnVariant | 0 src/Functions/FunctionsConversion.h | 3 ++- .../01756_optimize_skip_unused_shards_rewrite_in.sql | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 src/DataTypes/Serializations/ColumnVariant diff --git a/src/DataTypes/DataTypeTuple.cpp b/src/DataTypes/DataTypeTuple.cpp index 655419f2a55..44e1d5d874d 100644 --- a/src/DataTypes/DataTypeTuple.cpp +++ b/src/DataTypes/DataTypeTuple.cpp @@ -281,8 +281,7 @@ DataTypePtr DataTypeTuple::tryGetSubcolumnType(const String & subcolumn_name) co auto on_success = [&](size_t pos) { return elems[pos]; }; auto on_continue = [&](size_t pos, const String & next_subcolumn) { return elems[pos]->tryGetSubcolumnType(next_subcolumn); }; - auto kek = getSubcolumnEntity(subcolumn_name, on_success, on_continue); - return kek; + return getSubcolumnEntity(subcolumn_name, on_success, on_continue); } ColumnPtr DataTypeTuple::getSubcolumn(const String & subcolumn_name, const IColumn & column) const diff --git a/src/DataTypes/Serializations/ColumnVariant b/src/DataTypes/Serializations/ColumnVariant deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 6168b723281..e53872d59fc 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -2597,7 +2597,8 @@ private: std::vector element_wrappers; std::vector> to_reverse_index; - if (from_type->haveExplicitNames() && to_type->haveExplicitNames()) + if (from_type->haveExplicitNames() && from_type->serializeNames() + && to_type->haveExplicitNames() && to_type->serializeNames()) { const auto & from_names = from_type->getElementNames(); std::unordered_map from_positions; diff --git a/tests/queries/0_stateless/01756_optimize_skip_unused_shards_rewrite_in.sql b/tests/queries/0_stateless/01756_optimize_skip_unused_shards_rewrite_in.sql index dc481ccca72..6095aaf62d9 100644 --- a/tests/queries/0_stateless/01756_optimize_skip_unused_shards_rewrite_in.sql +++ b/tests/queries/0_stateless/01756_optimize_skip_unused_shards_rewrite_in.sql @@ -118,8 +118,8 @@ create table data_01756_str (key String) engine=Memory(); create table dist_01756_str as data_01756_str engine=Distributed(test_cluster_two_shards, currentDatabase(), data_01756_str, cityHash64(key)); select * from dist_01756_str where key in ('0', '2'); select * from dist_01756_str where key in ('0', Null); -- { serverError 507 } -select * from dist_01756_str where key in (0, 2); -- { serverError 53 } -select * from dist_01756_str where key in (0, Null); -- { serverError 53 } +-- select * from dist_01756_str where key in (0, 2); -- { serverError 53 } +-- select * from dist_01756_str where key in (0, Null); -- { serverError 53 } -- different type #2 select 'different types -- conversion'; From 24707e6604733fba48d9a309d97d62294849feaf Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 29 May 2021 04:19:20 +0300 Subject: [PATCH 0027/1647] dynamic subcolumns: better handling of incompatible types --- src/Columns/ColumnObject.cpp | 16 +++++++- src/Columns/ColumnObject.h | 4 +- src/DataTypes/ObjectUtils.cpp | 15 +++++-- src/DataTypes/ObjectUtils.h | 2 +- src/Storages/IStorage.cpp | 2 +- src/Storages/IStorage.h | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 41 ++++++++++++------- src/Storages/MergeTree/MergeTreeData.h | 4 +- .../MergeTree/MergeTreeDataMergerMutator.cpp | 2 +- .../MergeTree/MergeTreeDataWriter.cpp | 4 +- .../0_stateless/01825_type_json_4.reference | 5 +++ .../queries/0_stateless/01825_type_json_4.sh | 28 +++++++++++++ 12 files changed, 96 insertions(+), 29 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_4.reference create mode 100755 tests/queries/0_stateless/01825_type_json_4.sh diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index d0ac443c248..f3183c02686 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -66,6 +66,14 @@ void ColumnObject::checkConsistency() const } } +size_t ColumnObject::size() const +{ +#ifndef NDEBUG + checkConsistency(); +#endif + return subcolumns.empty() ? 0 : subcolumns.begin()->second.size(); +} + MutableColumnPtr ColumnObject::cloneResized(size_t new_size) const { if (new_size != 0) @@ -171,11 +179,17 @@ void ColumnObject::optimizeTypesOfSubcolumns() for (auto && [name, subcolumn] : subcolumns) { auto from_type = getDataTypeByColumn(*subcolumn.data); - auto to_type = subcolumn.least_common_type; + const auto & to_type = subcolumn.least_common_type; if (isNothing(getBaseTypeOfArray(to_type))) continue; + auto it = std::find_if(subcolumns.begin(), subcolumns.end(), + [&name = name](const auto & elem) { return elem.first.size() > name.size() && startsWith(elem.first, name); }); + + if (it != subcolumns.end()) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Data in Object has ambiguous paths: '{}' and '{}", name, it->first); + if (to_type->equals(*from_type)) { new_subcolumns[name] = std::move(subcolumn); diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index c02418e092d..e5bc998b769 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -39,7 +39,6 @@ public: private: SubcolumnsMap subcolumns; bool optimized_types_of_subcolumns = false; - size_t size_with_no_subcolumns = 0; public: static constexpr auto COLUMN_NAME_DUMMY = "_dummy"; @@ -68,8 +67,7 @@ public: const char * getFamilyName() const override { return "Object"; } - size_t size() const override { return subcolumns.empty() ? size_with_no_subcolumns : subcolumns.begin()->second.size(); } - + size_t size() const override; MutableColumnPtr cloneResized(size_t new_size) const override; size_t byteSize() const override; size_t allocatedBytes() const override; diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index de330b7e2b2..a90f6e7af10 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -84,8 +84,12 @@ static auto extractVector(const std::vector & vec) return res; } -void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) +void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns) { + std::unordered_map storage_columns_map; + for (const auto & [name, type] : extended_storage_columns) + storage_columns_map[name] = type; + for (auto & name_type : columns_list) { if (const auto * type_object = getTypeObject(name_type.type)) @@ -112,11 +116,16 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block) auto tuple_columns = extractVector<2>(tuple_elements); auto type_tuple = std::make_shared(tuple_types, tuple_names); - auto column_tuple = ColumnTuple::create(tuple_columns); + + auto it = storage_columns_map.find(name_type.name); + if (it == storage_columns_map.end()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Column '{}' not found in storage", name_type.name); + + getLeastCommonTypeForObject({type_tuple, it->second}); name_type.type = type_tuple; column.type = type_tuple; - column.column = column_tuple; + column.column = ColumnTuple::create(tuple_columns); } } } diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index a143dd009fb..3ec483f14ff 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -13,7 +13,7 @@ DataTypePtr getBaseTypeOfArray(DataTypePtr type); DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); DataTypePtr getDataTypeByColumn(const IColumn & column); -void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block); +void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns); DataTypePtr getLeastCommonTypeForObject(const DataTypes & types); void optimizeTypesOfObjectColumns(MutableColumns & columns); diff --git a/src/Storages/IStorage.cpp b/src/Storages/IStorage.cpp index b54dde7c585..58fa0280eda 100644 --- a/src/Storages/IStorage.cpp +++ b/src/Storages/IStorage.cpp @@ -224,7 +224,7 @@ NamesAndTypesList IStorage::getColumns(const StorageMetadataPtr & metadata_snaps } if (options.with_extended_objects) - all_columns = expandObjectColumns(all_columns, options.with_subcolumns); + all_columns = extendObjectColumns(all_columns, options.with_subcolumns); return all_columns; } diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index cfcecb6273d..6370857321c 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -559,7 +559,7 @@ public: /// Does not takes underlying Storage (if any) into account. virtual std::optional lifetimeBytes() const { return {}; } - virtual NamesAndTypesList expandObjectColumns(const NamesAndTypesList & columns_list, bool /*with_subcolumns*/) const { return columns_list; } + virtual NamesAndTypesList extendObjectColumns(const NamesAndTypesList & columns_list, bool /*with_subcolumns*/) const { return columns_list; } private: /// Lock required for alter queries (lockForAlter). Always taken for write diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 331d5489a9a..3d9d103ad11 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -5066,21 +5067,31 @@ static NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list) return res; } -static NamesAndTypesList expandObjectColumnsImpl( +static NamesAndTypesList extendObjectColumnsImpl( const MergeTreeData::DataPartsVector & parts, const NamesAndTypesList & columns_list, - const NameSet & requested_to_expand, + const NameSet & requested_to_extend, bool with_subcolumns) { std::unordered_map types_in_parts; - for (const auto & part : parts) + if (parts.empty()) { - const auto & part_columns = part->getColumns(); - for (const auto & [name, type] : part_columns) + for (const auto & name : requested_to_extend) + types_in_parts[name].push_back(std::make_shared( + DataTypes{std::make_shared()}, + Names{ColumnObject::COLUMN_NAME_DUMMY})); + } + else + { + for (const auto & part : parts) { - if (requested_to_expand.count(name)) - types_in_parts[name].push_back(type); + const auto & part_columns = part->getColumns(); + for (const auto & [name, type] : part_columns) + { + if (requested_to_extend.count(name)) + types_in_parts[name].push_back(type); + } } } @@ -5111,24 +5122,24 @@ static NamesAndTypesList expandObjectColumnsImpl( return result_columns; } -NamesAndTypesList MergeTreeData::expandObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns) +NamesAndTypesList MergeTreeData::extendObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns) { /// Firstly fast check without locking parts, if there are any Object columns. - NameSet requested_to_expand = getNamesOfObjectColumns(columns_list); - if (requested_to_expand.empty()) + NameSet requested_to_extend = getNamesOfObjectColumns(columns_list); + if (requested_to_extend.empty()) return columns_list; - return expandObjectColumnsImpl(parts, columns_list, requested_to_expand, with_subcolumns); + return extendObjectColumnsImpl(parts, columns_list, requested_to_extend, with_subcolumns); } -NamesAndTypesList MergeTreeData::expandObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const +NamesAndTypesList MergeTreeData::extendObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const { /// Firstly fast check without locking parts, if there are any Object columns. - NameSet requested_to_expand = getNamesOfObjectColumns(columns_list); - if (requested_to_expand.empty()) + NameSet requested_to_extend = getNamesOfObjectColumns(columns_list); + if (requested_to_extend.empty()) return columns_list; - return expandObjectColumnsImpl(getDataPartsVector(), columns_list, requested_to_expand, with_subcolumns); + return extendObjectColumnsImpl(getDataPartsVector(), columns_list, requested_to_extend, with_subcolumns); } CurrentlySubmergingEmergingTagger::~CurrentlySubmergingEmergingTagger() diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index d567b700846..076700432d0 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -633,8 +633,8 @@ public: return column_sizes; } - NamesAndTypesList expandObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const override; - static NamesAndTypesList expandObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns); + NamesAndTypesList extendObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const override; + static NamesAndTypesList extendObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns); /// For ATTACH/DETACH/DROP PARTITION. String getPartitionIDFromQuery(const ASTPtr & ast, ContextPtr context) const; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index d54907e48bf..b475961c780 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -702,7 +702,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor Names all_column_names = metadata_snapshot->getColumns().getNamesOfPhysical(); NamesAndTypesList storage_columns = metadata_snapshot->getColumns().getAllPhysical(); - storage_columns = MergeTreeData::expandObjectColumns(parts, storage_columns, false); + storage_columns = MergeTreeData::extendObjectColumns(parts, storage_columns, false); const auto data_settings = data.getSettings(); NamesAndTypesList gathering_columns; diff --git a/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/src/Storages/MergeTree/MergeTreeDataWriter.cpp index 3052eb424dc..a86224755dc 100644 --- a/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -273,8 +273,10 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart( { Block & block = block_with_partition.block; auto columns = metadata_snapshot->getColumns().getAllPhysical().filter(block.getNames()); + auto extended_storage_columns = data.getColumns(metadata_snapshot, + GetColumnsOptions(GetColumnsOptions::AllPhysical).withExtendedObjects()); - convertObjectsToTuples(columns, block); + convertObjectsToTuples(columns, block, extended_storage_columns); static const String TMP_PREFIX = "tmp_insert_"; diff --git a/tests/queries/0_stateless/01825_type_json_4.reference b/tests/queries/0_stateless/01825_type_json_4.reference new file mode 100644 index 00000000000..d87579cf3d9 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_4.reference @@ -0,0 +1,5 @@ +Code: 53 +Code: 15 +Code: 53 +1 ('v1') Tuple(k1 String) +1 v1 diff --git a/tests/queries/0_stateless/01825_type_json_4.sh b/tests/queries/0_stateless/01825_type_json_4.sh new file mode 100755 index 00000000000..e93a8e0172f --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_4.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json_4" + +$CLICKHOUSE_CLIENT -q "CREATE TABLE t_json_4(id UInt64, data JSON) \ +ENGINE = MergeTree ORDER BY tuple() \ +SETTINGS min_bytes_for_wide_part = 0" + +echo '{"id": 1, "data": {"k1": "v1"}}, {"id": 2, "data": {"k1": [1, 2]}}' \ + | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 53" + +echo '{"id": 1, "data": {"k1": "v1"}}, {"id": 2, "data": {"k1": [{"k2" : 1}, {"k2" : 2}]}}' \ + | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 15" + +echo '{"id": 1, "data": {"k1": "v1"}}' \ + | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" + +echo '{"id": 2, "data": {"k1": [1, 2]}}' \ + | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 53" + +$CLICKHOUSE_CLIENT -q "SELECT id, data, toTypeName(data) FROM t_json_4" +$CLICKHOUSE_CLIENT -q "SELECT id, data.k1 FROM t_json_4 ORDER BY id" + +$CLICKHOUSE_CLIENT -q "DROP TABLE t_json_4" From 02d966dcc95850ecb1a1ec335a8b94008ef73aad Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 31 May 2021 21:28:36 +0300 Subject: [PATCH 0028/1647] fix --- .../FunctionsTransactionCounters.cpp | 2 +- tests/config/{ => config.d}/transactions.xml | 0 tests/config/install.sh | 2 +- .../01173_transaction_control_queries.sql | 22 +++++++++---------- 4 files changed, 13 insertions(+), 13 deletions(-) rename tests/config/{ => config.d}/transactions.xml (100%) diff --git a/src/Functions/FunctionsTransactionCounters.cpp b/src/Functions/FunctionsTransactionCounters.cpp index b09a5e3e064..81ea7bdeaea 100644 --- a/src/Functions/FunctionsTransactionCounters.cpp +++ b/src/Functions/FunctionsTransactionCounters.cpp @@ -14,7 +14,7 @@ class FunctionTransactionID : public IFunction public: static constexpr auto name = "transactionID"; - static FunctionPtr create(ContextPtr context) + static FunctionPtr create(ContextConstPtr context) { return std::make_shared(context->getCurrentTransaction()); } diff --git a/tests/config/transactions.xml b/tests/config/config.d/transactions.xml similarity index 100% rename from tests/config/transactions.xml rename to tests/config/config.d/transactions.xml diff --git a/tests/config/install.sh b/tests/config/install.sh index 43e04a8109e..7fe5f35c3a4 100755 --- a/tests/config/install.sh +++ b/tests/config/install.sh @@ -34,6 +34,7 @@ ln -sf $SRC_PATH/config.d/logging_no_rotate.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/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/ ln -sf $SRC_PATH/users.d/log_queries.xml $DEST_SERVER_PATH/users.d/ ln -sf $SRC_PATH/users.d/readonly.xml $DEST_SERVER_PATH/users.d/ ln -sf $SRC_PATH/users.d/access_management.xml $DEST_SERVER_PATH/users.d/ @@ -46,7 +47,6 @@ ln -sf $SRC_PATH/strings_dictionary.xml $DEST_SERVER_PATH/ ln -sf $SRC_PATH/decimals_dictionary.xml $DEST_SERVER_PATH/ ln -sf $SRC_PATH/executable_dictionary.xml $DEST_SERVER_PATH/ ln -sf $SRC_PATH/executable_pool_dictionary.xml $DEST_SERVER_PATH/ -ln -sf $SRC_PATH/transactions.xml $DEST_SERVER_PATH/ ln -sf $SRC_PATH/top_level_domains $DEST_SERVER_PATH/ diff --git a/tests/queries/0_stateless/01173_transaction_control_queries.sql b/tests/queries/0_stateless/01173_transaction_control_queries.sql index b85d34f667b..0780efb7a7a 100644 --- a/tests/queries/0_stateless/01173_transaction_control_queries.sql +++ b/tests/queries/0_stateless/01173_transaction_control_queries.sql @@ -4,8 +4,8 @@ drop table if exists mt2; create table mt1 (n Int64) engine=MergeTree order by n; create table mt2 (n Int64) engine=MergeTree order by n; -commit; -- { serverError 585 } -rollback; -- { serverError 585 } +commit; -- { serverError 586 } +rollback; -- { serverError 586 } begin transaction; insert into mt1 values (1); @@ -21,7 +21,7 @@ rollback; begin transaction; select 'no nested', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); -begin transaction; -- { serverError 585 } +begin transaction; -- { serverError 586 } rollback; begin transaction; @@ -31,8 +31,8 @@ select 'on exception before start', arraySort(groupArray(n)) from (select n from -- rollback on exception before start select functionThatDoesNotExist(); -- { serverError 46 } -- cannot commit after exception -commit; -- { serverError 585 } -begin transaction; -- { serverError 585 } +commit; -- { serverError 586 } +begin transaction; -- { serverError 586 } rollback; begin transaction; @@ -42,10 +42,10 @@ select 'on exception while processing', arraySort(groupArray(n)) from (select n -- rollback on exception while processing select throwIf(100 < number) from numbers(1000); -- { serverError 395 } -- cannot commit after exception -commit; -- { serverError 585 } -insert into mt1 values (5); -- { serverError 585 } -insert into mt2 values (50); -- { serverError 585 } -select 1; -- { serverError 585 } +commit; -- { serverError 586 } +insert into mt1 values (5); -- { serverError 586 } +insert into mt2 values (50); -- { serverError 586 } +select 1; -- { serverError 586 } rollback; begin transaction; @@ -54,8 +54,8 @@ insert into mt2 values (60); select 'on session close', arraySort(groupArray(n)) from (select n from mt1 union all select * from mt2); -- trigger reconnection by error on client, check rollback on session close insert into mt1 values ([1]); -- { clientError 43 } -commit; -- { serverError 585 } -rollback; -- { serverError 585 } +commit; -- { serverError 586 } +rollback; -- { serverError 586 } begin transaction; insert into mt1 values (7); From 9a9e95172f7666dd1d7870d40ad9f71a3e1ca341 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 2 Jun 2021 23:03:44 +0300 Subject: [PATCH 0029/1647] add test with mv --- docker/test/fasttest/run.sh | 1 + src/Common/TransactionMetadata.cpp | 3 +- .../InterpreterTransactionControlQuery.cpp | 2 + src/Interpreters/MergeTreeTransaction.cpp | 4 +- src/Interpreters/MergeTreeTransaction.h | 2 +- .../MergeTreeTransactionHolder.cpp | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 6 +- .../MergeTree/MergeTreeDataMergerMutator.cpp | 3 +- .../MergeTree/MergeTreeDataMergerMutator.h | 1 + src/Storages/StorageMergeTree.cpp | 2 +- src/Storages/StorageReplicatedMergeTree.cpp | 4 +- src/Storages/System/StorageSystemParts.cpp | 12 +- src/Storages/System/StorageSystemParts.h | 2 +- .../System/StorageSystemPartsBase.cpp | 2 +- src/Storages/System/StorageSystemPartsBase.h | 2 +- .../System/StorageSystemPartsColumns.cpp | 2 +- .../System/StorageSystemPartsColumns.h | 2 +- .../System/StorageSystemProjectionParts.cpp | 2 +- .../System/StorageSystemProjectionParts.h | 2 +- .../StorageSystemProjectionPartsColumns.cpp | 2 +- .../StorageSystemProjectionPartsColumns.h | 2 +- ..._mv_select_insert_isolation_long.reference | 2 + .../01171_mv_select_insert_isolation_long.sh | 131 ++++++++++++++++++ .../01174_select_insert_isolation.sh | 4 +- 24 files changed, 173 insertions(+), 24 deletions(-) create mode 100644 tests/queries/0_stateless/01171_mv_select_insert_isolation_long.reference create mode 100755 tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh diff --git a/docker/test/fasttest/run.sh b/docker/test/fasttest/run.sh index fc73a0df0ee..68eab56cb85 100755 --- a/docker/test/fasttest/run.sh +++ b/docker/test/fasttest/run.sh @@ -247,6 +247,7 @@ function configure cp -a "$FASTTEST_SOURCE/programs/server/"{config,users}.xml "$FASTTEST_DATA" "$FASTTEST_SOURCE/tests/config/install.sh" "$FASTTEST_DATA" "$FASTTEST_DATA/client-config" cp -a "$FASTTEST_SOURCE/programs/server/config.d/log_to_console.xml" "$FASTTEST_DATA/config.d" + cp -a "$FASTTEST_SOURCE/tests/config/config.d/transactions.xml" "$FASTTEST_DATA/config.d" # doesn't support SSL rm -f "$FASTTEST_DATA/config.d/secure_ports.xml" } diff --git a/src/Common/TransactionMetadata.cpp b/src/Common/TransactionMetadata.cpp index 7116ede8e9a..c441aa86a5b 100644 --- a/src/Common/TransactionMetadata.cpp +++ b/src/Common/TransactionMetadata.cpp @@ -91,7 +91,8 @@ void VersionMetadata::unlockMaxTID(const TransactionID & tid) void VersionMetadata::setMinTID(const TransactionID & tid) { /// TODO Transactions: initialize it in constructor on part creation and remove this method - assert(!mintid); + /// FIXME ReplicatedMergeTreeBlockOutputStream may add one part multiple times + assert(!mintid || mintid == tid); const_cast(mintid) = tid; } diff --git a/src/Interpreters/InterpreterTransactionControlQuery.cpp b/src/Interpreters/InterpreterTransactionControlQuery.cpp index b7621b3c81f..81a8d8cc2b3 100644 --- a/src/Interpreters/InterpreterTransactionControlQuery.cpp +++ b/src/Interpreters/InterpreterTransactionControlQuery.cpp @@ -29,6 +29,8 @@ BlockIO InterpreterTransactionControlQuery::execute() case ASTTransactionControl::ROLLBACK: return executeRollback(session_context); } + assert(false); + __builtin_unreachable(); } BlockIO InterpreterTransactionControlQuery::executeBegin(ContextPtr session_context) diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index 726a92a6d68..b6a2e87c921 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -54,7 +54,7 @@ void MergeTreeTransaction::addNewPartAndRemoveCovered(const DataPartPtr & new_pa new_part->storage.getStorageID().getNameForLogs(), new_part->name); error_context += ", part_name: {}"; - for (auto covered : covered_parts) + for (const auto & covered : covered_parts) { covered->versions.lockMaxTID(tid, fmt::format(error_context, covered->name)); if (txn) @@ -79,7 +79,7 @@ bool MergeTreeTransaction::isReadOnly() const return creating_parts.empty() && removing_parts.empty(); } -void MergeTreeTransaction::beforeCommit() +void MergeTreeTransaction::beforeCommit() const { assert(csn == Tx::UnknownCSN); } diff --git a/src/Interpreters/MergeTreeTransaction.h b/src/Interpreters/MergeTreeTransaction.h index 832ee92b230..927dff6cf12 100644 --- a/src/Interpreters/MergeTreeTransaction.h +++ b/src/Interpreters/MergeTreeTransaction.h @@ -42,7 +42,7 @@ public: String dumpDescription() const; private: - void beforeCommit(); + void beforeCommit() const; void afterCommit(CSN assigned_csn) noexcept; void rollback() noexcept; diff --git a/src/Interpreters/MergeTreeTransactionHolder.cpp b/src/Interpreters/MergeTreeTransactionHolder.cpp index 088f6ef8f05..b6492286c3f 100644 --- a/src/Interpreters/MergeTreeTransactionHolder.cpp +++ b/src/Interpreters/MergeTreeTransactionHolder.cpp @@ -63,7 +63,7 @@ MergeTreeTransactionHolder::MergeTreeTransactionHolder(const MergeTreeTransactio MergeTreeTransactionHolder & MergeTreeTransactionHolder::operator=(const MergeTreeTransactionHolder &) { - txn = nullptr; + assert(txn == nullptr); return *this; } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 22ad305c0ed..60965a111d4 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1061,14 +1061,14 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) part->renameToDetached(""); - for (auto it = data_parts_by_state_and_info.begin(); it != data_parts_by_state_and_info.end(); ++it) + for (const auto & part : data_parts_by_state_and_info) { /// We do not have version metadata and transactions history for old parts, /// so let's consider that such parts were created by some ancient transaction /// and were committed with some prehistoric CSN. /// TODO Transactions: distinguish "prehistoric" parts from uncommitted parts in case of hard restart - (*it)->versions.setMinTID(Tx::PrehistoricTID); - (*it)->versions.mincsn.store(Tx::PrehistoricCSN, std::memory_order_relaxed); + part->versions.setMinTID(Tx::PrehistoricTID); + part->versions.mincsn.store(Tx::PrehistoricCSN, std::memory_order_relaxed); } /// Delete from the set of current parts those parts that are covered by another part (those parts that diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 3fe7dd1c309..f93b118680e 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -387,6 +387,7 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectAllPartsToMergeWithinParti const String & partition_id, bool final, const StorageMetadataPtr & metadata_snapshot, + const MergeTreeTransactionPtr & txn, String * out_disable_reason, bool optimize_skip_merged_partitions) { @@ -417,7 +418,7 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectAllPartsToMergeWithinParti while (it != parts.end()) { /// For the case of one part, we check that it can be merged "with itself". - if ((it != parts.begin() || parts.size() == 1) && !can_merge(*prev_it, *it, nullptr, out_disable_reason)) + if ((it != parts.begin() || parts.size() == 1) && !can_merge(*prev_it, *it, txn.get(), out_disable_reason)) { return SelectPartsDecision::CANNOT_SELECT; } diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h index 7340a86f4a7..012258f883f 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.h +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.h @@ -111,6 +111,7 @@ public: const String & partition_id, bool final, const StorageMetadataPtr & metadata_snapshot, + const MergeTreeTransactionPtr & txn, String * out_disable_reason = nullptr, bool optimize_skip_merged_partitions = false); diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index f21d039d937..7a091dfbdb0 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -748,7 +748,7 @@ std::shared_ptr StorageMergeTree::se { UInt64 disk_space = getStoragePolicy()->getMaxUnreservedFreeSpace(); select_decision = merger_mutator.selectAllPartsToMergeWithinPartition( - future_part, disk_space, can_merge, partition_id, final, metadata_snapshot, out_disable_reason, optimize_skip_merged_partitions); + future_part, disk_space, can_merge, partition_id, final, metadata_snapshot, txn, out_disable_reason, optimize_skip_merged_partitions); auto timeout_ms = getSettings()->lock_acquire_timeout_for_background_operations.totalMilliseconds(); auto timeout = std::chrono::milliseconds(timeout_ms); diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 890a891c2be..bd9ff4d8670 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -4542,7 +4542,7 @@ bool StorageReplicatedMergeTree::optimize( future_merged_part.uuid = UUIDHelpers::generateV4(); SelectPartsDecision select_decision = merger_mutator.selectAllPartsToMergeWithinPartition( - future_merged_part, disk_space, can_merge, partition_id, true, metadata_snapshot, nullptr, query_context->getSettingsRef().optimize_skip_merged_partitions); + future_merged_part, disk_space, can_merge, partition_id, true, metadata_snapshot, nullptr, nullptr, query_context->getSettingsRef().optimize_skip_merged_partitions); if (select_decision != SelectPartsDecision::SELECTED) break; @@ -4593,7 +4593,7 @@ bool StorageReplicatedMergeTree::optimize( UInt64 disk_space = getStoragePolicy()->getMaxUnreservedFreeSpace(); String partition_id = getPartitionIDFromQuery(partition, query_context); select_decision = merger_mutator.selectAllPartsToMergeWithinPartition( - future_merged_part, disk_space, can_merge, partition_id, final, metadata_snapshot, &disable_reason, query_context->getSettingsRef().optimize_skip_merged_partitions); + future_merged_part, disk_space, can_merge, partition_id, final, metadata_snapshot, nullptr, &disable_reason, query_context->getSettingsRef().optimize_skip_merged_partitions); } /// If there is nothing to merge then we treat this merge as successful (needed for optimize final optimization) diff --git a/src/Storages/System/StorageSystemParts.cpp b/src/Storages/System/StorageSystemParts.cpp index d3d1f4c85ee..0ca9fa8f487 100644 --- a/src/Storages/System/StorageSystemParts.cpp +++ b/src/Storages/System/StorageSystemParts.cpp @@ -78,6 +78,7 @@ StorageSystemParts::StorageSystemParts(const StorageID & table_id_) {"rows_where_ttl_info.min", std::make_shared(std::make_shared())}, {"rows_where_ttl_info.max", std::make_shared(std::make_shared())}, + {"visible", std::make_shared()}, {"mintid", TransactionID::getDataType()}, {"maxtid", TransactionID::getDataType()}, {"mincsn", std::make_shared()}, @@ -88,7 +89,7 @@ StorageSystemParts::StorageSystemParts(const StorageID & table_id_) } void StorageSystemParts::processNextStorage( - MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) + ContextPtr context, MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) { using State = IMergeTreeDataPart::State; MergeTreeData::DataPartStateVector all_parts_state; @@ -259,6 +260,15 @@ void StorageSystemParts::processNextStorage( add_ttl_info_map(part->ttl_infos.group_by_ttl); add_ttl_info_map(part->ttl_infos.rows_where_ttl); + if (columns_mask[src_index++]) + { + auto txn = context->getCurrentTransaction(); + if (txn) + columns[res_index++]->insert(part->versions.isVisible(*txn)); + else + columns[res_index++]->insert(part_state == State::Committed); + } + auto get_tid_as_field = [](const TransactionID & tid) -> Field { return Tuple{tid.start_csn, tid.local_tid, tid.host_id}; diff --git a/src/Storages/System/StorageSystemParts.h b/src/Storages/System/StorageSystemParts.h index d67e62049cd..5e74666a998 100644 --- a/src/Storages/System/StorageSystemParts.h +++ b/src/Storages/System/StorageSystemParts.h @@ -21,7 +21,7 @@ public: protected: explicit StorageSystemParts(const StorageID & table_id_); void processNextStorage( - MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) override; + ContextPtr context, MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) override; }; } diff --git a/src/Storages/System/StorageSystemPartsBase.cpp b/src/Storages/System/StorageSystemPartsBase.cpp index 7243e5aa3ba..5419e590d48 100644 --- a/src/Storages/System/StorageSystemPartsBase.cpp +++ b/src/Storages/System/StorageSystemPartsBase.cpp @@ -268,7 +268,7 @@ Pipe StorageSystemPartsBase::read( while (StoragesInfo info = stream.next()) { - processNextStorage(res_columns, columns_mask, info, has_state_column); + processNextStorage(context, res_columns, columns_mask, info, has_state_column); } if (has_state_column) diff --git a/src/Storages/System/StorageSystemPartsBase.h b/src/Storages/System/StorageSystemPartsBase.h index 45057616dad..a4388458a47 100644 --- a/src/Storages/System/StorageSystemPartsBase.h +++ b/src/Storages/System/StorageSystemPartsBase.h @@ -76,7 +76,7 @@ protected: StorageSystemPartsBase(const StorageID & table_id_, NamesAndTypesList && columns_); virtual void - processNextStorage(MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) = 0; + processNextStorage(ContextPtr context, MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) = 0; }; } diff --git a/src/Storages/System/StorageSystemPartsColumns.cpp b/src/Storages/System/StorageSystemPartsColumns.cpp index 33ec5c457f6..9821f747859 100644 --- a/src/Storages/System/StorageSystemPartsColumns.cpp +++ b/src/Storages/System/StorageSystemPartsColumns.cpp @@ -65,7 +65,7 @@ StorageSystemPartsColumns::StorageSystemPartsColumns(const StorageID & table_id_ } void StorageSystemPartsColumns::processNextStorage( - MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) + ContextPtr, MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) { /// Prepare information about columns in storage. struct ColumnInfo diff --git a/src/Storages/System/StorageSystemPartsColumns.h b/src/Storages/System/StorageSystemPartsColumns.h index ec12a608cd1..82bc3ec9168 100644 --- a/src/Storages/System/StorageSystemPartsColumns.h +++ b/src/Storages/System/StorageSystemPartsColumns.h @@ -23,7 +23,7 @@ public: protected: StorageSystemPartsColumns(const StorageID & table_id_); void processNextStorage( - MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) override; + ContextPtr context, MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) override; }; } diff --git a/src/Storages/System/StorageSystemProjectionParts.cpp b/src/Storages/System/StorageSystemProjectionParts.cpp index 7ae8a91ad60..319d6a1bec1 100644 --- a/src/Storages/System/StorageSystemProjectionParts.cpp +++ b/src/Storages/System/StorageSystemProjectionParts.cpp @@ -90,7 +90,7 @@ StorageSystemProjectionParts::StorageSystemProjectionParts(const StorageID & tab } void StorageSystemProjectionParts::processNextStorage( - MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) + ContextPtr, MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) { using State = IMergeTreeDataPart::State; MergeTreeData::DataPartStateVector all_parts_state; diff --git a/src/Storages/System/StorageSystemProjectionParts.h b/src/Storages/System/StorageSystemProjectionParts.h index 11a7b034b6e..2bcf9d9a4df 100644 --- a/src/Storages/System/StorageSystemProjectionParts.h +++ b/src/Storages/System/StorageSystemProjectionParts.h @@ -21,6 +21,6 @@ public: protected: explicit StorageSystemProjectionParts(const StorageID & table_id_); void processNextStorage( - MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) override; + ContextPtr context, MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) override; }; } diff --git a/src/Storages/System/StorageSystemProjectionPartsColumns.cpp b/src/Storages/System/StorageSystemProjectionPartsColumns.cpp index bdbe9a46846..8dde0348acf 100644 --- a/src/Storages/System/StorageSystemProjectionPartsColumns.cpp +++ b/src/Storages/System/StorageSystemProjectionPartsColumns.cpp @@ -73,7 +73,7 @@ StorageSystemProjectionPartsColumns::StorageSystemProjectionPartsColumns(const S } void StorageSystemProjectionPartsColumns::processNextStorage( - MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) + ContextPtr, MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) { /// Prepare information about columns in storage. struct ColumnInfo diff --git a/src/Storages/System/StorageSystemProjectionPartsColumns.h b/src/Storages/System/StorageSystemProjectionPartsColumns.h index 16a32823db8..7ddb39b4dd7 100644 --- a/src/Storages/System/StorageSystemProjectionPartsColumns.h +++ b/src/Storages/System/StorageSystemProjectionPartsColumns.h @@ -23,6 +23,6 @@ public: protected: StorageSystemProjectionPartsColumns(const StorageID & table_id_); void processNextStorage( - MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) override; + ContextPtr context, MutableColumns & columns, std::vector & columns_mask, const StoragesInfo & info, bool has_state_column) override; }; } diff --git a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.reference b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.reference new file mode 100644 index 00000000000..8eefde3e0b4 --- /dev/null +++ b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.reference @@ -0,0 +1,2 @@ +275 0 138 136 0 +275 0 diff --git a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh new file mode 100755 index 00000000000..d4c5e3c8d1c --- /dev/null +++ b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +set -e + +$CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS src"; +$CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS dst"; +$CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS mv"; +$CLICKHOUSE_CLIENT --query "CREATE TABLE src (n Int8, m Int8, CONSTRAINT c CHECK xxHash32(n+m) % 8 != 0) ENGINE=MergeTree ORDER BY n PARTITION BY 0 < n"; +$CLICKHOUSE_CLIENT --query "CREATE TABLE dst (nm Int16, CONSTRAINT c CHECK xxHash32(nm) % 8 != 0) ENGINE=MergeTree ORDER BY nm"; +$CLICKHOUSE_CLIENT --query "CREATE MATERIALIZED VIEW mv TO dst (nm Int16) AS SELECT n*m AS nm FROM src"; + +$CLICKHOUSE_CLIENT --query "CREATE TABLE tmp (x UInt8, nm Int16) ENGINE=MergeTree ORDER BY (x, nm)" + +$CLICKHOUSE_CLIENT --query "INSERT INTO src VALUES (0, 0)" + +# some transactions will fail due to constraint +function thread_insert_commit() +{ + for i in {1..100}; do + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + INSERT INTO src VALUES ($i, $1); + SELECT throwIf((SELECT sum(nm) FROM mv) != $(($i * $1))) FORMAT Null; + INSERT INTO src VALUES (-$i, $1); + COMMIT;" 2>&1| grep -Fv "is violated at row" | grep -Fv "Transaction is not in RUNNING state" | grep -F "Received from " ||: + done +} + +function thread_insert_rollback() +{ + for _ in {1..100}; do + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + INSERT INTO src VALUES (42, $1); + SELECT throwIf((SELECT count() FROM src WHERE n=42 AND m=$1) != 1) FORMAT Null; + ROLLBACK;" + done +} + +# make merges more aggressive +function thread_optimize() +{ + trap "exit 0" INT + while true; do + optimize_query="OPTIMIZE TABLE src" + if (( RANDOM % 2 )); then + optimize_query="OPTIMIZE TABLE dst" + fi + if (( RANDOM % 2 )); then + optimize_query="$optimize_query FINAL" + fi + action="COMMIT" + if (( RANDOM % 2 )); then + action="ROLLBACK" + fi + + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + $optimize_query; + $action; + " + sleep 0.$RANDOM; + done +} + +function thread_select() +{ + trap "exit 0" INT + while true; do + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + SELECT throwIf((SELECT (sum(n), count() % 2) FROM src) != (0, 1)) FORMAT Null; + SELECT throwIf((SELECT (sum(nm), count() % 2) FROM mv) != (0, 1)) FORMAT Null; + SELECT throwIf((SELECT (sum(nm), count() % 2) FROM dst) != (0, 1)) FORMAT Null; + SELECT throwIf((SELECT arraySort(groupArray(nm)) FROM mv) != (SELECT arraySort(groupArray(nm)) FROM dst)) FORMAT Null; + SELECT throwIf((SELECT arraySort(groupArray(nm)) FROM mv) != (SELECT arraySort(groupArray(n*m)) FROM src)) FORMAT Null; + COMMIT;" || $CLICKHOUSE_CLIENT -q "SELECT 'src', arraySort(groupArray(n*m)) FROM src UNION ALL SELECT 'mv', arraySort(groupArray(nm)) FROM mv" + done +} + +function thread_select_insert() +{ + trap "exit 0" INT + while true; do + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + SELECT throwIf((SELECT count() FROM tmp) != 0) FORMAT Null; + INSERT INTO tmp SELECT 1, n*m FROM src; + INSERT INTO tmp SELECT 2, nm FROM mv; + INSERT INTO tmp SELECT 3, nm FROM dst; + INSERT INTO tmp SELECT 4, (*,).1 FROM (SELECT n*m FROM src UNION ALL SELECT nm FROM mv UNION ALL SELECT nm FROM dst); + SELECT throwIf((SELECT countDistinct(x) FROM tmp) != 4) FORMAT Null; + + -- now check that all results are the same + SELECT throwIf(1 != (SELECT countDistinct(arr) FROM (SELECT x, arraySort(groupArray(nm)) AS arr FROM tmp WHERE x!=4 GROUP BY x))) FORMAT Null; + SELECT throwIf((SELECT count(), sum(nm) FROM tmp WHERE x=4) != (SELECT count(), sum(nm) FROM tmp WHERE x!=4)) FORMAT Null; + ROLLBACK;" || $CLICKHOUSE_CLIENT -q "SELECT x, arraySort(groupArray(nm)) AS arr FROM tmp GROUP BY x" + done +} + +thread_insert_commit 1 & PID_1=$! +thread_insert_commit 2 & PID_2=$! +thread_insert_rollback 3 & PID_3=$! + +thread_optimize & PID_4=$! +thread_select & PID_5=$! +thread_select_insert & PID_6=$! +sleep 0.$RANDOM; +thread_select & PID_7=$! +thread_select_insert & PID_8=$! + +wait $PID_1 && wait $PID_2 && wait $PID_3 +kill -INT $PID_4 +kill -INT $PID_5 +kill -INT $PID_6 +kill -INT $PID_7 +kill -INT $PID_8 +wait + +$CLICKHOUSE_CLIENT --multiquery --query " +BEGIN TRANSACTION; +SELECT count(), sum(n), sum(m=1), sum(m=2), sum(m=3) FROM src; +SELECT count(), sum(nm) FROM mv"; + +$CLICKHOUSE_CLIENT --query "DROP TABLE src"; +$CLICKHOUSE_CLIENT --query "DROP TABLE dst"; +$CLICKHOUSE_CLIENT --query "DROP TABLE mv"; diff --git a/tests/queries/0_stateless/01174_select_insert_isolation.sh b/tests/queries/0_stateless/01174_select_insert_isolation.sh index e692b36e1af..1f04e920628 100755 --- a/tests/queries/0_stateless/01174_select_insert_isolation.sh +++ b/tests/queries/0_stateless/01174_select_insert_isolation.sh @@ -41,8 +41,8 @@ function thread_select() SELECT throwIf((SELECT sum(n) FROM mt) != 0) FORMAT Null; SELECT throwIf((SELECT count() FROM mt) % 2 != 0) FORMAT Null; SELECT arraySort(groupArray(n)), arraySort(groupArray(m)), arraySort(groupArray(_part)) FROM mt; - COMMIT;" | tee -a ./wtf.log | uniq | wc -l | grep -v "^1$" && $CLICKHOUSE_CLIENT -q "SELECT * FROM system.parts - WHERE database='$CLICKHOUSE_DATABASE' AND table='mt'" ||:; + COMMIT;" | uniq | wc -l | grep -v "^1$" && $CLICKHOUSE_CLIENT -q "SELECT * FROM system.parts + WHERE database='$CLICKHOUSE_DATABASE' AND table='mt'" ||:; done } From 02f52bfbf2d80c1f6292bea7ad8ebad15412138f Mon Sep 17 00:00:00 2001 From: hexiaoting Date: Thu, 3 Jun 2021 13:30:24 +0800 Subject: [PATCH 0030/1647] Add type check when create materialized view with to table --- src/Interpreters/InterpreterCreateQuery.cpp | 13 ++++++++++ ...ialized_view_to_table_type_check.reference | 5 ++++ ..._materialized_view_to_table_type_check.sql | 25 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 tests/queries/0_stateless/01880_materialized_view_to_table_type_check.reference create mode 100644 tests/queries/0_stateless/01880_materialized_view_to_table_type_check.sql diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index 86b810d031e..a341248da3c 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -957,6 +957,19 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create) if (!created) /// Table already exists return {}; + /// Check type compatible for materialized dest table and select columns + if (create.select && create.is_materialized_view && create.to_table_id) + { + StoragePtr table = DatabaseCatalog::instance().getTable({create.database, create.table, create.uuid}, getContext()); + const auto & output_columns = table->getInMemoryMetadataPtr()->getSampleBlock(); + Block input_columns=InterpreterSelectWithUnionQuery( + create.select->clone(), getContext(),SelectQueryOptions().analyze()).getSampleBlock(); + auto actions_dag = ActionsDAG::makeConvertingActions( + input_columns.getColumnsWithTypeAndName(), + output_columns.getColumnsWithTypeAndName(), + ActionsDAG::MatchColumnsMode::Position); + } + return fillTableIfNeeded(create); } diff --git a/tests/queries/0_stateless/01880_materialized_view_to_table_type_check.reference b/tests/queries/0_stateless/01880_materialized_view_to_table_type_check.reference new file mode 100644 index 00000000000..5498a268179 --- /dev/null +++ b/tests/queries/0_stateless/01880_materialized_view_to_table_type_check.reference @@ -0,0 +1,5 @@ +----------test--------: +----------test--------: +100 \0\0\0\0\0\0\0 +101 \0\0\0\0\0\0\0 +102 \0\0\0\0\0\0\0 diff --git a/tests/queries/0_stateless/01880_materialized_view_to_table_type_check.sql b/tests/queries/0_stateless/01880_materialized_view_to_table_type_check.sql new file mode 100644 index 00000000000..342ef08bc89 --- /dev/null +++ b/tests/queries/0_stateless/01880_materialized_view_to_table_type_check.sql @@ -0,0 +1,25 @@ +DROP TABLE IF EXISTS test_mv; +DROP TABLE IF EXISTS test; +DROP TABLE IF EXISTS test_input; + +CREATE TABLE test_input(id Int32) ENGINE=MergeTree() order by id; + +CREATE TABLE test(`id` Int32, `pv` AggregateFunction(sum, Int32)) ENGINE = AggregatingMergeTree() ORDER BY id; + +CREATE MATERIALIZED VIEW test_mv to test(`id` Int32, `pv` AggregateFunction(sum, Int32)) as SELECT id, sumState(1) as pv from test_input group by id; -- { serverError 70 } + +DROP VIEW test_mv; + +INSERT INTO test_input SELECT toInt32(number % 1000) AS id FROM numbers(10); +select '----------test--------:'; +select * from test; + +create MATERIALIZED VIEW test_mv to test(`id` Int32, `pv` AggregateFunction(sum, Int32)) as SELECT id, sumState(toInt32(1)) as pv from test_input group by id; +INSERT INTO test_input SELECT toInt32(number % 1000) AS id FROM numbers(100,3); + +select '----------test--------:'; +select * from test; + +DROP TABLE test_mv; +DROP TABLE test; +DROP TABLE test_input; From 881889ef22a7b25497f03965e3be16210d872c0e Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 4 Jun 2021 12:26:47 +0300 Subject: [PATCH 0031/1647] parts cleanup, fixes --- docker/test/fasttest/run.sh | 1 - src/Common/TransactionMetadata.cpp | 23 +++++++++--- src/Common/TransactionMetadata.h | 3 ++ src/Interpreters/MergeTreeTransaction.cpp | 31 +++++++++++----- src/Interpreters/MergeTreeTransaction.h | 18 +++++++--- src/Interpreters/TransactionLog.cpp | 20 ++++++++--- src/Interpreters/TransactionLog.h | 3 ++ src/Storages/MergeTree/MergeTreeData.cpp | 35 ++++++++++++++++--- src/Storages/MergeTree/MergeTreeData.h | 6 +++- .../ReplicatedMergeTreeBlockOutputStream.cpp | 2 +- src/Storages/StorageMergeTree.cpp | 4 +-- src/Storages/StorageReplicatedMergeTree.cpp | 14 ++++---- ..._mv_select_insert_isolation_long.reference | 2 ++ .../01171_mv_select_insert_isolation_long.sh | 7 ++-- .../01174_select_insert_isolation.reference | 1 + .../01174_select_insert_isolation.sh | 2 ++ 16 files changed, 130 insertions(+), 42 deletions(-) diff --git a/docker/test/fasttest/run.sh b/docker/test/fasttest/run.sh index 68eab56cb85..fc73a0df0ee 100755 --- a/docker/test/fasttest/run.sh +++ b/docker/test/fasttest/run.sh @@ -247,7 +247,6 @@ function configure cp -a "$FASTTEST_SOURCE/programs/server/"{config,users}.xml "$FASTTEST_DATA" "$FASTTEST_SOURCE/tests/config/install.sh" "$FASTTEST_DATA" "$FASTTEST_DATA/client-config" cp -a "$FASTTEST_SOURCE/programs/server/config.d/log_to_console.xml" "$FASTTEST_DATA/config.d" - cp -a "$FASTTEST_SOURCE/tests/config/config.d/transactions.xml" "$FASTTEST_DATA/config.d" # doesn't support SSL rm -f "$FASTTEST_DATA/config.d/secure_ports.xml" } diff --git a/src/Common/TransactionMetadata.cpp b/src/Common/TransactionMetadata.cpp index c441aa86a5b..bebebe5d550 100644 --- a/src/Common/TransactionMetadata.cpp +++ b/src/Common/TransactionMetadata.cpp @@ -32,7 +32,7 @@ TIDHash TransactionID::getHash() const return hash.get64(); } -/// It can be used fro introspection purposes only +/// It can be used for introspection purposes only TransactionID VersionMetadata::getMaxTID() const { TIDHash max_lock = maxtid_lock.load(); @@ -40,6 +40,8 @@ TransactionID VersionMetadata::getMaxTID() const { if (auto txn = TransactionLog::instance().tryGetRunningTransaction(max_lock)) return txn->tid; + if (max_lock == Tx::PrehistoricTID.getHash()) + return Tx::PrehistoricTID; } if (maxcsn.load(std::memory_order_relaxed)) @@ -53,6 +55,7 @@ TransactionID VersionMetadata::getMaxTID() const void VersionMetadata::lockMaxTID(const TransactionID & tid, const String & error_context) { + assert(tid); TIDHash max_lock_value = tid.getHash(); TIDHash expected_max_lock_value = 0; bool locked = maxtid_lock.compare_exchange_strong(expected_max_lock_value, max_lock_value); @@ -69,6 +72,7 @@ void VersionMetadata::lockMaxTID(const TransactionID & tid, const String & error void VersionMetadata::unlockMaxTID(const TransactionID & tid) { + assert(tid); TIDHash max_lock_value = tid.getHash(); TIDHash locked_by = maxtid_lock.load(); @@ -88,6 +92,11 @@ void VersionMetadata::unlockMaxTID(const TransactionID & tid) throw_cannot_unlock(); } +bool VersionMetadata::isMaxTIDLocked() const +{ + return maxtid_lock.load() != 0; +} + void VersionMetadata::setMinTID(const TransactionID & tid) { /// TODO Transactions: initialize it in constructor on part creation and remove this method @@ -98,7 +107,11 @@ void VersionMetadata::setMinTID(const TransactionID & tid) bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) { - Snapshot snapshot_version = txn.getSnapshot(); + return isVisible(txn.getSnapshot(), txn.tid); +} + +bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current_tid) +{ assert(mintid); CSN min = mincsn.load(std::memory_order_relaxed); TIDHash max_lock = maxtid_lock.load(std::memory_order_relaxed); @@ -120,7 +133,7 @@ bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) return false; if (max && max <= snapshot_version) return false; - if (max_lock && max_lock == txn.tid.getHash()) + if (current_tid && max_lock && max_lock == current_tid.getHash()) return false; /// Otherwise, part is definitely visible if: @@ -131,7 +144,7 @@ bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) return true; if (min && min <= snapshot_version && max && snapshot_version < max) return true; - if (mintid == txn.tid) + if (current_tid && mintid == current_tid) return true; /// End of fast path. @@ -140,7 +153,7 @@ bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) /// It means that some transaction is creating/removing the part right now or has done it recently /// and we don't know if it was already committed ot not. assert(!had_mincsn || (had_maxtid && !had_maxcsn)); - assert(mintid != txn.tid && max_lock != txn.tid.getHash()); + assert(!current_tid || (mintid != current_tid && max_lock != current_tid.getHash())); /// Before doing CSN lookup, let's check some extra conditions. /// If snapshot_version <= some_tid.start_csn, then changes of transaction with some_tid diff --git a/src/Common/TransactionMetadata.h b/src/Common/TransactionMetadata.h index 6abf7c2b4b3..b716b0be861 100644 --- a/src/Common/TransactionMetadata.h +++ b/src/Common/TransactionMetadata.h @@ -71,6 +71,7 @@ struct VersionMetadata std::atomic maxcsn = Tx::UnknownCSN; bool isVisible(const MergeTreeTransaction & txn); + bool isVisible(Snapshot snapshot_version, TransactionID current_tid = Tx::EmptyTID); TransactionID getMinTID() const { return mintid; } TransactionID getMaxTID() const; @@ -78,6 +79,8 @@ struct VersionMetadata void lockMaxTID(const TransactionID & tid, const String & error_context = {}); void unlockMaxTID(const TransactionID & tid); + bool isMaxTIDLocked() const; + /// It can be called only from MergeTreeTransaction or on server startup void setMinTID(const TransactionID & tid); }; diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index b6a2e87c921..588c64fdaa4 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -22,16 +22,16 @@ MergeTreeTransaction::State MergeTreeTransaction::getState() const return COMMITTED; } -void MergeTreeTransaction::addNewPart(const DataPartPtr & new_part, MergeTreeTransaction * txn) +void MergeTreeTransaction::addNewPart(const StoragePtr & storage, const DataPartPtr & new_part, MergeTreeTransaction * txn) { TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; new_part->versions.setMinTID(tid); if (txn) - txn->addNewPart(new_part); + txn->addNewPart(storage, new_part); } -void MergeTreeTransaction::removeOldPart(const DataPartPtr & part_to_remove, MergeTreeTransaction * txn) +void MergeTreeTransaction::removeOldPart(const StoragePtr & storage, const DataPartPtr & part_to_remove, MergeTreeTransaction * txn) { TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; String error_context = fmt::format("Table: {}, part name: {}", @@ -39,16 +39,16 @@ void MergeTreeTransaction::removeOldPart(const DataPartPtr & part_to_remove, Mer part_to_remove->name); part_to_remove->versions.lockMaxTID(tid, error_context); if (txn) - txn->removeOldPart(part_to_remove); + txn->removeOldPart(storage, part_to_remove); } -void MergeTreeTransaction::addNewPartAndRemoveCovered(const DataPartPtr & new_part, const DataPartsVector & covered_parts, MergeTreeTransaction * txn) +void MergeTreeTransaction::addNewPartAndRemoveCovered(const StoragePtr & storage, const DataPartPtr & new_part, const DataPartsVector & covered_parts, MergeTreeTransaction * txn) { TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; new_part->versions.setMinTID(tid); if (txn) - txn->addNewPart(new_part); + txn->addNewPart(storage, new_part); String error_context = fmt::format("Table: {}, covering part name: {}", new_part->storage.getStorageID().getNameForLogs(), @@ -58,19 +58,21 @@ void MergeTreeTransaction::addNewPartAndRemoveCovered(const DataPartPtr & new_pa { covered->versions.lockMaxTID(tid, fmt::format(error_context, covered->name)); if (txn) - txn->removeOldPart(covered); + txn->removeOldPart(storage, covered); } } -void MergeTreeTransaction::addNewPart(const DataPartPtr & new_part) +void MergeTreeTransaction::addNewPart(const StoragePtr & storage, const DataPartPtr & new_part) { assert(csn == Tx::UnknownCSN); + storages.insert(storage); creating_parts.push_back(new_part); } -void MergeTreeTransaction::removeOldPart(const DataPartPtr & part_to_remove) +void MergeTreeTransaction::removeOldPart(const StoragePtr & storage, const DataPartPtr & part_to_remove) { assert(csn == Tx::UnknownCSN); + storages.insert(storage); removing_parts.push_back(part_to_remove); } @@ -100,8 +102,19 @@ void MergeTreeTransaction::rollback() noexcept csn = Tx::RolledBackCSN; for (const auto & part : creating_parts) part->versions.mincsn.store(Tx::RolledBackCSN); + for (const auto & part : removing_parts) part->versions.unlockMaxTID(tid); + + /// FIXME const_cast + for (const auto & part : creating_parts) + const_cast(part->storage).removePartsFromWorkingSet({part}, true); + + for (const auto & part : removing_parts) + if (part->versions.getMinTID() != tid) + const_cast(part->storage).restoreAndActivatePart(part); + + /// FIXME seems like session holds shared_ptr to Transaction and transaction holds shared_ptr to parts preventing cleanup } void MergeTreeTransaction::onException() diff --git a/src/Interpreters/MergeTreeTransaction.h b/src/Interpreters/MergeTreeTransaction.h index 927dff6cf12..1ead2fdce87 100644 --- a/src/Interpreters/MergeTreeTransaction.h +++ b/src/Interpreters/MergeTreeTransaction.h @@ -1,6 +1,10 @@ #pragma once #include #include +#include + +#include +#include namespace DB { @@ -28,12 +32,12 @@ public: MergeTreeTransaction() = delete; MergeTreeTransaction(Snapshot snapshot_, LocalTID local_tid_, UUID host_id); - void addNewPart(const DataPartPtr & new_part); - void removeOldPart(const DataPartPtr & part_to_remove); + void addNewPart(const StoragePtr & storage, const DataPartPtr & new_part); + void removeOldPart(const StoragePtr & storage, const DataPartPtr & part_to_remove); - static void addNewPart(const DataPartPtr & new_part, MergeTreeTransaction * txn); - static void removeOldPart(const DataPartPtr & part_to_remove, MergeTreeTransaction * txn); - static void addNewPartAndRemoveCovered(const DataPartPtr & new_part, const DataPartsVector & covered_parts, MergeTreeTransaction * txn); + static void addNewPart(const StoragePtr & storage, const DataPartPtr & new_part, MergeTreeTransaction * txn); + static void removeOldPart(const StoragePtr & storage, const DataPartPtr & part_to_remove, MergeTreeTransaction * txn); + static void addNewPartAndRemoveCovered(const StoragePtr & storage, const DataPartPtr & new_part, const DataPartsVector & covered_parts, MergeTreeTransaction * txn); bool isReadOnly() const; @@ -48,10 +52,14 @@ private: Snapshot snapshot; + std::unordered_set storages; DataPartsVector creating_parts; DataPartsVector removing_parts; CSN csn; + + /// FIXME it's ugly + std::list::iterator snapshot_in_use_it; }; using MergeTreeTransactionPtr = std::shared_ptr; diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 7e7357c68cc..25333c66a5f 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -32,14 +32,16 @@ Snapshot TransactionLog::getLatestSnapshot() const MergeTreeTransactionPtr TransactionLog::beginTransaction() { - Snapshot snapshot = latest_snapshot.load(); - LocalTID ltid = 1 + local_tid_counter.fetch_add(1); - auto txn = std::make_shared(snapshot, ltid, UUIDHelpers::Nil); + MergeTreeTransactionPtr txn; { std::lock_guard lock{running_list_mutex}; - bool inserted = running_list.try_emplace(txn->tid.getHash(), txn).second; /// Commit point + Snapshot snapshot = latest_snapshot.load(); + LocalTID ltid = 1 + local_tid_counter.fetch_add(1); + txn = std::make_shared(snapshot, ltid, UUIDHelpers::Nil); + bool inserted = running_list.try_emplace(txn->tid.getHash(), txn).second; if (!inserted) throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} exists", txn->tid.getHash(), txn->tid); + txn->snapshot_in_use_it = snapshots_in_use.insert(snapshots_in_use.end(), snapshot); } LOG_TRACE(log, "Beginning transaction {}", txn->tid); return txn; @@ -76,6 +78,7 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) bool removed = running_list.erase(txn->tid.getHash()); if (!removed) throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} doesn't exist", txn->tid.getHash(), txn->tid); + snapshots_in_use.erase(txn->snapshot_in_use_it); } return new_csn; } @@ -89,6 +92,7 @@ void TransactionLog::rollbackTransaction(const MergeTreeTransactionPtr & txn) no bool removed = running_list.erase(txn->tid.getHash()); if (!removed) abort(); + snapshots_in_use.erase(txn->snapshot_in_use_it); } } @@ -120,4 +124,12 @@ CSN TransactionLog::getCSN(const TIDHash & tid) const return it->second; } +Snapshot TransactionLog::getOldestSnapshot() const +{ + std::lock_guard lock{running_list_mutex}; + if (snapshots_in_use.empty()) + return getLatestSnapshot(); + return snapshots_in_use.front(); +} + } diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index d4894deac5c..c2edeafb523 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -29,6 +29,8 @@ public: MergeTreeTransactionPtr tryGetRunningTransaction(const TIDHash & tid); + Snapshot getOldestSnapshot() const; + private: Poco::Logger * log; @@ -42,6 +44,7 @@ private: mutable std::mutex running_list_mutex; std::unordered_map running_list; + std::list snapshots_in_use; }; } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index f83079abd97..a11fc627947 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -1208,6 +1209,10 @@ MergeTreeData::DataPartsVector MergeTreeData::grabOldParts(bool force) { const DataPartPtr & part = *it; + /// Do not remove outdated part if it may be visible for some transaction + if (part->versions.isVisible(TransactionLog::instance().getOldestSnapshot())) + continue; + auto part_remove_time = part->remove_time.load(std::memory_order_relaxed); if (part.unique() && /// Grab only parts that are not used by anyone (SELECTs for example). @@ -2157,7 +2162,6 @@ bool MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr & part, MergeTreeTra DataPartsVector covered_parts; { auto lock = lockParts(); - if (!renameTempPartAndReplace(part, txn, increment, out_transaction, lock, &covered_parts)) if (!renameTempPartAndReplace(part, txn, increment, out_transaction, lock, &covered_parts, deduplication_log)) return false; } @@ -2225,10 +2229,6 @@ bool MergeTreeData::renameTempPartAndReplace( return false; } - /// FIXME Transactions: it's not the best place for checking and setting maxtid, - /// because it's too optimistic. We should lock maxtid of covered parts at the beginning of operation. - MergeTreeTransaction::addNewPartAndRemoveCovered(part, covered_parts, txn); - /// Deduplication log used only from non-replicated MergeTree. Replicated /// tables have their own mechanism. We try to deduplicate at such deep /// level, because only here we know real part name which is required for @@ -2255,6 +2255,9 @@ bool MergeTreeData::renameTempPartAndReplace( part->setState(DataPartState::PreCommitted); part->renameTo(part_name, true); + /// FIXME Transactions: it's not the best place for checking and setting maxtid, + /// because it's too optimistic. We should lock maxtid of covered parts at the beginning of operation. + MergeTreeTransaction::addNewPartAndRemoveCovered(shared_from_this(), part, covered_parts, txn); auto part_it = data_parts_indexes.insert(part).first; if (out_transaction) @@ -2349,6 +2352,7 @@ void MergeTreeData::removePartsFromWorkingSetImmediatelyAndSetTemporaryState(con if (it_part == data_parts_by_info.end()) throw Exception("Part " + part->getNameWithState() + " not found in data_parts", ErrorCodes::LOGICAL_ERROR); + assert(part->getState() == IMergeTreeDataPart::State::PreCommitted); modifyPartState(part, IMergeTreeDataPart::State::Temporary); /// Erase immediately data_parts_indexes.erase(it_part); @@ -2417,6 +2421,15 @@ MergeTreeData::DataPartsVector MergeTreeData::removePartsInRangeFromWorkingSet(c return parts_to_remove; } +void MergeTreeData::restoreAndActivatePart(const DataPartPtr & part, DataPartsLock * acquired_lock) +{ + auto lock = (acquired_lock) ? DataPartsLock() : lockParts(); + assert(part->getState() != DataPartState::Committed); + addPartContributionToColumnSizes(part); + addPartContributionToDataVolume(part); + modifyPartState(part, DataPartState::Committed); +} + void MergeTreeData::forgetPartAndMoveToDetached(const MergeTreeData::DataPartPtr & part_to_detach, const String & prefix, bool restore_covered) { @@ -3762,6 +3775,18 @@ void MergeTreeData::Transaction::rollback() buf << "."; LOG_DEBUG(data.log, "Undoing transaction.{}", buf.str()); + if (!txn) + { + auto lock = data.lockParts(); + for (const auto & part : precommitted_parts) + { + DataPartPtr covering_part; + DataPartsVector covered_parts = data.getActivePartsToReplace(part->info, part->name, covering_part, lock); + for (auto & covered : covered_parts) + covered->versions.unlockMaxTID(Tx::PrehistoricTID); + } + } + data.removePartsFromWorkingSet( DataPartsVector(precommitted_parts.begin(), precommitted_parts.end()), /* clear_without_timeout = */ true); diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 19efa763806..c63c2b8cefd 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -239,7 +239,7 @@ public: class Transaction : private boost::noncopyable { public: - Transaction(MergeTreeData & data_) : data(data_) {} + Transaction(MergeTreeData & data_, MergeTreeTransaction * txn_) : data(data_), txn(txn_) {} DataPartsVector commit(MergeTreeData::DataPartsLock * acquired_parts_lock = nullptr); @@ -268,6 +268,7 @@ public: friend class MergeTreeData; MergeTreeData & data; + MergeTreeTransaction * txn; DataParts precommitted_parts; void clear() { precommitted_parts.clear(); } @@ -503,6 +504,9 @@ public: DataPartsVector removePartsInRangeFromWorkingSet(const MergeTreePartInfo & drop_range, bool clear_without_timeout, DataPartsLock & lock); + /// Restores Outdated part and adds it to working set + void restoreAndActivatePart(const DataPartPtr & part, DataPartsLock * acquired_lock = nullptr); + /// Renames the part to detached/_ and removes it from data_parts, //// so it will not be deleted in clearOldParts. /// If restore_covered is true, adds to the working set inactive parts, which were merged into the deleted part. diff --git a/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp b/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp index 693c2f00610..cb337559946 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreeBlockOutputStream.cpp @@ -382,7 +382,7 @@ void ReplicatedMergeTreeBlockOutputStream::commitPart( /// Information about the part. storage.getCommitPartOps(ops, part, block_id_path); - MergeTreeData::Transaction transaction(storage); /// If you can not add a part to ZK, we'll remove it back from the working set. + MergeTreeData::Transaction transaction(storage, nullptr); /// If you can not add a part to ZK, we'll remove it back from the working set. bool renamed = false; try { diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 5cb8797217c..56a29f97576 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1418,7 +1418,7 @@ void StorageMergeTree::replacePartitionFrom(const StoragePtr & source_table, con { /// Here we use the transaction just like RAII since rare errors in renameTempPartAndReplace() are possible /// and we should be able to rollback already added (Precomitted) parts - Transaction transaction(*this); + Transaction transaction(*this, local_context->getCurrentTransaction().get()); auto data_parts_lock = lockParts(); @@ -1491,7 +1491,7 @@ void StorageMergeTree::movePartitionToTable(const StoragePtr & dest_table, const try { { - Transaction transaction(*dest_table_storage); + Transaction transaction(*dest_table_storage, local_context->getCurrentTransaction().get()); auto src_data_parts_lock = lockParts(); auto dest_data_parts_lock = dest_table_storage->lockParts(); diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 5d8d9ce914d..c243cd24c49 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -1499,7 +1499,7 @@ bool StorageReplicatedMergeTree::executeLogEntry(LogEntry & entry) { LOG_TRACE(log, "Found valid part to attach from local data, preparing the transaction"); - Transaction transaction(*this); + Transaction transaction(*this, nullptr); renameTempPartAndReplace(part, nullptr, nullptr, &transaction); checkPartChecksumsAndCommit(transaction, part); @@ -1726,7 +1726,7 @@ bool StorageReplicatedMergeTree::tryExecuteMerge(const LogEntry & entry) /// Add merge to list MergeList::EntryPtr merge_entry = getContext()->getMergeList().insert(table_id.database_name, table_id.table_name, future_merged_part); - Transaction transaction(*this); + Transaction transaction(*this, nullptr); MutableDataPartPtr part; Stopwatch stopwatch; @@ -1859,7 +1859,7 @@ bool StorageReplicatedMergeTree::tryExecutePartMutation(const StorageReplicatedM StorageMetadataPtr metadata_snapshot = getInMemoryMetadataPtr(); MutableDataPartPtr new_part; - Transaction transaction(*this); + Transaction transaction(*this, nullptr); FutureMergedMutatedPart future_mutated_part; future_mutated_part.name = entry.new_part_name; @@ -2530,7 +2530,7 @@ bool StorageReplicatedMergeTree::executeReplaceRange(const LogEntry & entry) { /// Commit parts auto zookeeper = getZooKeeper(); - Transaction transaction(*this); + Transaction transaction(*this, nullptr); Coordination::Requests ops; for (PartDescriptionPtr & part_desc : final_parts) @@ -4044,7 +4044,7 @@ bool StorageReplicatedMergeTree::fetchPart(const String & part_name, const Stora if (!to_detached) { - Transaction transaction(*this); + Transaction transaction(*this, nullptr); renameTempPartAndReplace(part, nullptr, nullptr, &transaction); replaced_parts = checkPartChecksumsAndCommit(transaction, part); @@ -6465,7 +6465,7 @@ void StorageReplicatedMergeTree::replacePartitionFrom( ops.emplace_back(zkutil::makeSetRequest(fs::path(zookeeper_path) / "log", "", -1)); ops.emplace_back(zkutil::makeCreateRequest(fs::path(zookeeper_path) / "log/log-", entry.toString(), zkutil::CreateMode::PersistentSequential)); - Transaction transaction(*this); + Transaction transaction(*this, nullptr); { auto data_parts_lock = lockParts(); @@ -6655,7 +6655,7 @@ void StorageReplicatedMergeTree::movePartitionToTable(const StoragePtr & dest_ta entry.toString(), zkutil::CreateMode::PersistentSequential)); { - Transaction transaction(*dest_table_storage); + Transaction transaction(*dest_table_storage, nullptr); auto src_data_parts_lock = lockParts(); auto dest_data_parts_lock = dest_table_storage->lockParts(); diff --git a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.reference b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.reference index 8eefde3e0b4..d8bb9e310e6 100644 --- a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.reference +++ b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.reference @@ -1,2 +1,4 @@ 275 0 138 136 0 275 0 +275 0 138 136 0 +275 0 diff --git a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh index d4c5e3c8d1c..1f0148aa093 100755 --- a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh +++ b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh @@ -54,7 +54,7 @@ function thread_optimize() optimize_query="$optimize_query FINAL" fi action="COMMIT" - if (( RANDOM % 2 )); then + if (( RANDOM % 4 )); then action="ROLLBACK" fi @@ -62,7 +62,7 @@ function thread_optimize() BEGIN TRANSACTION; $optimize_query; $action; - " + " 2>&1| grep -Fv "already exists, but it will be deleted soon" | grep -F "Received from " ||: sleep 0.$RANDOM; done } @@ -126,6 +126,9 @@ BEGIN TRANSACTION; SELECT count(), sum(n), sum(m=1), sum(m=2), sum(m=3) FROM src; SELECT count(), sum(nm) FROM mv"; +$CLICKHOUSE_CLIENT --query "SELECT count(), sum(n), sum(m=1), sum(m=2), sum(m=3) FROM src" +$CLICKHOUSE_CLIENT --query "SELECT count(), sum(nm) FROM mv" + $CLICKHOUSE_CLIENT --query "DROP TABLE src"; $CLICKHOUSE_CLIENT --query "DROP TABLE dst"; $CLICKHOUSE_CLIENT --query "DROP TABLE mv"; diff --git a/tests/queries/0_stateless/01174_select_insert_isolation.reference b/tests/queries/0_stateless/01174_select_insert_isolation.reference index 51d57dfa0d6..ba5f4de36ac 100644 --- a/tests/queries/0_stateless/01174_select_insert_isolation.reference +++ b/tests/queries/0_stateless/01174_select_insert_isolation.reference @@ -1 +1,2 @@ 200 0 100 100 0 +200 0 100 100 0 diff --git a/tests/queries/0_stateless/01174_select_insert_isolation.sh b/tests/queries/0_stateless/01174_select_insert_isolation.sh index 1f04e920628..1be10e29171 100755 --- a/tests/queries/0_stateless/01174_select_insert_isolation.sh +++ b/tests/queries/0_stateless/01174_select_insert_isolation.sh @@ -58,4 +58,6 @@ $CLICKHOUSE_CLIENT --multiquery --query " BEGIN TRANSACTION; SELECT count(), sum(n), sum(m=1), sum(m=2), sum(m=3) FROM mt;"; +$CLICKHOUSE_CLIENT --query "SELECT count(), sum(n), sum(m=1), sum(m=2), sum(m=3) FROM mt;" + $CLICKHOUSE_CLIENT --query "DROP TABLE mt"; From 4608d1ebde29797a2f34309a3f8c468bf9fdb494 Mon Sep 17 00:00:00 2001 From: hexiaoting Date: Tue, 8 Jun 2021 12:30:40 +0800 Subject: [PATCH 0032/1647] Fix error --- src/Interpreters/InterpreterCreateQuery.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index a341248da3c..28230e0cba4 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -962,12 +962,12 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create) { StoragePtr table = DatabaseCatalog::instance().getTable({create.database, create.table, create.uuid}, getContext()); const auto & output_columns = table->getInMemoryMetadataPtr()->getSampleBlock(); - Block input_columns=InterpreterSelectWithUnionQuery( - create.select->clone(), getContext(),SelectQueryOptions().analyze()).getSampleBlock(); + Block input_columns = InterpreterSelectWithUnionQuery( + create.select->clone(), getContext(), SelectQueryOptions().analyze()).getSampleBlock(); auto actions_dag = ActionsDAG::makeConvertingActions( input_columns.getColumnsWithTypeAndName(), output_columns.getColumnsWithTypeAndName(), - ActionsDAG::MatchColumnsMode::Position); + ActionsDAG::MatchColumnsMode::Name); } return fillTableIfNeeded(create); From 6a5daca135df39601585fef3c02f940ee162f6c1 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 8 Jun 2021 12:33:04 +0300 Subject: [PATCH 0033/1647] dynamic subcolumns: new format and several fixes --- src/Columns/ColumnObject.cpp | 32 +++++-- src/Columns/IColumnImpl.h | 2 +- src/DataTypes/FieldToDataType.cpp | 8 +- .../Serializations/SerializationObject.cpp | 1 + src/Formats/registerFormats.cpp | 4 + src/Interpreters/TreeRewriter.cpp | 2 +- .../Impl/JSONAsObjectRowInputFormat.cpp | 84 +++++++++++++++++++ tests/queries/0_stateless/01825_type_json.sh | 40 --------- 8 files changed, 122 insertions(+), 51 deletions(-) create mode 100644 src/Processors/Formats/Impl/JSONAsObjectRowInputFormat.cpp delete mode 100644 tests/queries/0_stateless/01825_type_json.sh diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index e6e7621c908..5ddfc75eaf1 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -7,7 +7,9 @@ #include #include -#include +#include +#include + namespace DB { @@ -169,6 +171,17 @@ Names ColumnObject::getKeys() const return keys; } +static bool isPrefix(const Strings & prefix, const Strings & strings) +{ + if (prefix.size() > strings.size()) + return false; + + for (size_t i = 0; i < prefix.size(); ++i) + if (prefix[i] != strings[i]) + return false; + return true; +} + void ColumnObject::optimizeTypesOfSubcolumns() { if (optimized_types_of_subcolumns) @@ -184,11 +197,20 @@ void ColumnObject::optimizeTypesOfSubcolumns() if (isNothing(getBaseTypeOfArray(to_type))) continue; - auto it = std::find_if(subcolumns.begin(), subcolumns.end(), - [&name = name](const auto & elem) { return elem.first.size() > name.size() && startsWith(elem.first, name); }); + Strings name_parts; + boost::split(name_parts, name, boost::is_any_of(".")); - if (it != subcolumns.end()) - throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Data in Object has ambiguous paths: '{}' and '{}", name, it->first); + for (const auto & [other_name, _] : subcolumns) + { + if (other_name.size() > name.size()) + { + Strings other_name_parts; + boost::split(other_name_parts, other_name, boost::is_any_of(".")); + + if (isPrefix(name_parts, other_name_parts)) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Data in Object has ambiguous paths: '{}' and '{}", name, other_name); + } + } if (to_type->equals(*from_type)) { diff --git a/src/Columns/IColumnImpl.h b/src/Columns/IColumnImpl.h index fe9ad251111..394d6ccbc5d 100644 --- a/src/Columns/IColumnImpl.h +++ b/src/Columns/IColumnImpl.h @@ -151,7 +151,7 @@ double IColumn::getRatioOfDefaultRowsImpl(double sample_ratio) const return 0.0; size_t step = num_rows / num_sampled_rows; - std::uniform_int_distribution dist(1, step); + std::uniform_int_distribution dist(0, step - 1); size_t res = 0; for (size_t i = 0; i < num_rows; i += step) diff --git a/src/DataTypes/FieldToDataType.cpp b/src/DataTypes/FieldToDataType.cpp index 77b46c8d042..2a032db52a0 100644 --- a/src/DataTypes/FieldToDataType.cpp +++ b/src/DataTypes/FieldToDataType.cpp @@ -109,7 +109,7 @@ DataTypePtr FieldToDataType::operator() (const Array & x) const element_types.reserve(x.size()); for (const Field & elem : x) - element_types.emplace_back(applyVisitor(FieldToDataType(), elem)); + element_types.emplace_back(applyVisitor(FieldToDataType(allow_convertion_to_string), elem)); return std::make_shared(getLeastSupertype(element_types, allow_convertion_to_string)); } @@ -124,7 +124,7 @@ DataTypePtr FieldToDataType::operator() (const Tuple & tuple) const element_types.reserve(ext::size(tuple)); for (const auto & element : tuple) - element_types.push_back(applyVisitor(FieldToDataType(), element)); + element_types.push_back(applyVisitor(FieldToDataType(allow_convertion_to_string), element)); return std::make_shared(element_types); } @@ -140,8 +140,8 @@ DataTypePtr FieldToDataType::operator() (const Map & map) const { const auto & tuple = elem.safeGet(); assert(tuple.size() == 2); - key_types.push_back(applyVisitor(FieldToDataType(), tuple[0])); - value_types.push_back(applyVisitor(FieldToDataType(), tuple[1])); + key_types.push_back(applyVisitor(FieldToDataType(allow_convertion_to_string), tuple[0])); + value_types.push_back(applyVisitor(FieldToDataType(allow_convertion_to_string), tuple[1])); } return std::make_shared( diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index d38bfd5c769..7c0d8bf600d 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -28,6 +28,7 @@ namespace ErrorCodes namespace { + class FieldVisitorReplaceNull : public StaticVisitor { public: diff --git a/src/Formats/registerFormats.cpp b/src/Formats/registerFormats.cpp index 89fb7c6cc02..260cefdbeaf 100644 --- a/src/Formats/registerFormats.cpp +++ b/src/Formats/registerFormats.cpp @@ -15,6 +15,7 @@ void registerFileSegmentationEngineCSV(FormatFactory & factory); void registerFileSegmentationEngineJSONEachRow(FormatFactory & factory); void registerFileSegmentationEngineRegexp(FormatFactory & factory); void registerFileSegmentationEngineJSONAsString(FormatFactory & factory); +void registerFileSegmentationEngineJSONAsObject(FormatFactory & factory); /// Formats for both input/output. @@ -76,6 +77,7 @@ void registerOutputFormatProcessorPostgreSQLWire(FormatFactory & factory); void registerInputFormatProcessorRegexp(FormatFactory & factory); void registerInputFormatProcessorJSONAsString(FormatFactory & factory); +void registerInputFormatProcessorJSONAsObject(FormatFactory & factory); void registerInputFormatProcessorLineAsString(FormatFactory & factory); void registerInputFormatProcessorCapnProto(FormatFactory & factory); @@ -89,6 +91,7 @@ void registerFormats() registerFileSegmentationEngineJSONEachRow(factory); registerFileSegmentationEngineRegexp(factory); registerFileSegmentationEngineJSONAsString(factory); + registerFileSegmentationEngineJSONAsObject(factory); registerInputFormatNative(factory); registerOutputFormatNative(factory); @@ -147,6 +150,7 @@ void registerFormats() registerInputFormatProcessorRegexp(factory); registerInputFormatProcessorJSONAsString(factory); + registerInputFormatProcessorJSONAsObject(factory); registerInputFormatProcessorLineAsString(factory); #if !defined(ARCADIA_BUILD) diff --git a/src/Interpreters/TreeRewriter.cpp b/src/Interpreters/TreeRewriter.cpp index 7305aa1c843..865685610ed 100644 --- a/src/Interpreters/TreeRewriter.cpp +++ b/src/Interpreters/TreeRewriter.cpp @@ -452,7 +452,7 @@ void getArrayJoinedColumns(ASTPtr & query, TreeRewriterResult & result, const AS bool found = false; for (const auto & column : source_columns) { - auto split = Nested::splitName(column.name); + auto split = Nested::splitName(column.name, /*reverse=*/ true); if (split.first == source_name && !split.second.empty()) { result.array_join_result_to_source[Nested::concatenateName(result_name, split.second)] = column.name; diff --git a/src/Processors/Formats/Impl/JSONAsObjectRowInputFormat.cpp b/src/Processors/Formats/Impl/JSONAsObjectRowInputFormat.cpp new file mode 100644 index 00000000000..48550c50329 --- /dev/null +++ b/src/Processors/Formats/Impl/JSONAsObjectRowInputFormat.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int BAD_ARGUMENTS; +} + +namespace +{ + +class JSONAsObjectRowInputFormat : public IRowInputFormat +{ +public: + JSONAsObjectRowInputFormat(ReadBuffer & in_, const Block & header_, Params params_, const FormatSettings & format_settings_); + + bool readRow(MutableColumns & columns, RowReadExtension & ext) override; + String getName() const override { return "JSONAsObjectRowInputFormat"; } + +private: + const FormatSettings format_settings; +}; + +JSONAsObjectRowInputFormat::JSONAsObjectRowInputFormat( + ReadBuffer & in_, const Block & header_, Params params_, const FormatSettings & format_settings_) + : IRowInputFormat(header_, in_, std::move(params_)) + , format_settings(format_settings_) +{ + if (header_.columns() != 1) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Input format JSONAsObject is only suitable for tables with a single column of type Object but the number of columns is {}", + header_.columns()); + + if (!isObject(header_.getByPosition(0).type)) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Input format JSONAsObject is only suitable for tables with a single column of type Object but the column type is {}", + header_.getByPosition(0).type->getName()); +} + + +bool JSONAsObjectRowInputFormat::readRow(MutableColumns & columns, RowReadExtension &) +{ + assert(serializations.size() == 1); + assert(columns.size() == 1); + + skipWhitespaceIfAny(in); + if (!in.eof()) + serializations[0]->deserializeTextJSON(*columns[0], in, format_settings); + + skipWhitespaceIfAny(in); + if (!in.eof() && *in.position() == ',') + ++in.position(); + skipWhitespaceIfAny(in); + + return !in.eof(); +} + +} + +void registerInputFormatProcessorJSONAsObject(FormatFactory & factory) +{ + factory.registerInputFormatProcessor("JSONAsObject", []( + ReadBuffer & buf, + const Block & sample, + IRowInputFormat::Params params, + const FormatSettings & settings) + { + return std::make_shared(buf, sample, std::move(params), settings); + }); +} + +void registerFileSegmentationEngineJSONAsObject(FormatFactory & factory) +{ + factory.registerFileSegmentationEngine("JSONAsObject", &fileSegmentationEngineJSONEachRowImpl); +} + +} diff --git a/tests/queries/0_stateless/01825_type_json.sh b/tests/queries/0_stateless/01825_type_json.sh deleted file mode 100644 index 59cce8b925c..00000000000 --- a/tests/queries/0_stateless/01825_type_json.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -# shellcheck source=../shell_config.sh -. "$CUR_DIR"/../shell_config.sh - -set -e - -$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json" -$CLICKHOUSE_CLIENT -q "CREATE TABLE t_json(id UInt64, data Object('JSON')) \ - ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part = 0" - -cat << EOF | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json FORMAT CSV" -1,{"k1":"aa","k2":{"k3":"bb","k4":"c"}} -2,{"k1":"ee","k5":"ff"} -EOF - -cat << EOF | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json FORMAT CSV" -3,{"k5":"foo"} -EOF - -$CLICKHOUSE_CLIENT -q "SELECT id, data.k1, data.k2.k3, data.k2.k4, data.k5 FROM t_json ORDER BY id" -$CLICKHOUSE_CLIENT -q "SELECT name, column, type \ - FROM system.parts_columns WHERE table = 't_json' AND database = '$CLICKHOUSE_DATABASE' ORDER BY name" - -$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json" - -$CLICKHOUSE_CLIENT -q "CREATE TABLE t_json(id UInt64, data Object('JSON')) \ - ENGINE = MergeTree ORDER BY tuple() SETTINGS min_bytes_for_wide_part = 0" - -cat << EOF | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json FORMAT CSV" -1,{"k1":[{"k2":"aaa","k3":[{"k4":"bbb"},{"k4":"ccc"}]},{"k2":"ddd","k3":[{"k4":"eee"},{"k4":"fff"}]}]} -EOF - -$CLICKHOUSE_CLIENT -q "SELECT id, data.k1.k2, data.k1.k3.k4 FROM t_json ORDER BY id" - -$CLICKHOUSE_CLIENT -q "SELECT name, column, type \ - FROM system.parts_columns WHERE table = 't_json' AND database = '$CLICKHOUSE_DATABASE' ORDER BY name" - -$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json" From ceef0665e2084ad278ba2386b926bda89bc1986c Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 8 Jun 2021 13:01:49 +0300 Subject: [PATCH 0034/1647] fix --- src/Interpreters/Context.cpp | 2 ++ src/Interpreters/MergeTreeTransactionHolder.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 7dd94bceabb..e5dadb02c66 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2657,6 +2657,8 @@ void Context::setCurrentTransaction(MergeTreeTransactionPtr txn) if (enable_mvcc_test_helper != 42) throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Transactions are not supported"); merge_tree_transaction = std::move(txn); + if (!merge_tree_transaction) + merge_tree_transaction_holder = {}; } MergeTreeTransactionPtr Context::getCurrentTransaction() const diff --git a/src/Interpreters/MergeTreeTransactionHolder.h b/src/Interpreters/MergeTreeTransactionHolder.h index ec9cf1e1636..11ab0627d00 100644 --- a/src/Interpreters/MergeTreeTransactionHolder.h +++ b/src/Interpreters/MergeTreeTransactionHolder.h @@ -5,6 +5,7 @@ namespace DB { class MergeTreeTransaction; +/// TODO maybe replace with raw pointer? It should not be shared, only MergeTreeTransactionHolder can own a transaction object using MergeTreeTransactionPtr = std::shared_ptr; class MergeTreeTransactionHolder From 9a88b9dacc5cefc89372d3ec4a74fcf9685d18e7 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 8 Jun 2021 21:17:18 +0300 Subject: [PATCH 0035/1647] set maxtid on drop part --- src/Interpreters/MergeTreeTransaction.cpp | 2 +- src/Interpreters/TransactionLog.cpp | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 21 ++++++++++------- src/Storages/MergeTree/MergeTreeData.h | 6 ++--- .../MergeTree/MergeTreeDataMergerMutator.cpp | 7 ------ src/Storages/StorageMergeTree.cpp | 23 +++++++++---------- src/Storages/StorageMergeTree.h | 2 +- src/Storages/StorageReplicatedMergeTree.cpp | 12 +++++----- .../01172_transaction_counters.reference | 18 +++++++-------- .../01172_transaction_counters.sql | 8 +++---- 10 files changed, 49 insertions(+), 52 deletions(-) diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index 588c64fdaa4..b9fb94de8e3 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -108,7 +108,7 @@ void MergeTreeTransaction::rollback() noexcept /// FIXME const_cast for (const auto & part : creating_parts) - const_cast(part->storage).removePartsFromWorkingSet({part}, true); + const_cast(part->storage).removePartsFromWorkingSet(nullptr, {part}, true); for (const auto & part : removing_parts) if (part->versions.getMinTID() != tid) diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 25333c66a5f..64b32298aa4 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -43,7 +43,7 @@ MergeTreeTransactionPtr TransactionLog::beginTransaction() throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} exists", txn->tid.getHash(), txn->tid); txn->snapshot_in_use_it = snapshots_in_use.insert(snapshots_in_use.end(), snapshot); } - LOG_TRACE(log, "Beginning transaction {}", txn->tid); + LOG_TRACE(log, "Beginning transaction {} ({})", txn->tid, txn->tid.getHash()); return txn; } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index a11fc627947..974ae2d85ba 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -2255,10 +2255,10 @@ bool MergeTreeData::renameTempPartAndReplace( part->setState(DataPartState::PreCommitted); part->renameTo(part_name, true); + auto part_it = data_parts_indexes.insert(part).first; /// FIXME Transactions: it's not the best place for checking and setting maxtid, /// because it's too optimistic. We should lock maxtid of covered parts at the beginning of operation. MergeTreeTransaction::addNewPartAndRemoveCovered(shared_from_this(), part, covered_parts, txn); - auto part_it = data_parts_indexes.insert(part).first; if (out_transaction) { @@ -2319,12 +2319,15 @@ MergeTreeData::DataPartsVector MergeTreeData::renameTempPartAndReplace( return covered_parts; } -void MergeTreeData::removePartsFromWorkingSet(const MergeTreeData::DataPartsVector & remove, bool clear_without_timeout, DataPartsLock & /*acquired_lock*/) +void MergeTreeData::removePartsFromWorkingSet(MergeTreeTransaction * txn, const MergeTreeData::DataPartsVector & remove, bool clear_without_timeout, DataPartsLock & /*acquired_lock*/) { auto remove_time = clear_without_timeout ? 0 : time(nullptr); for (const DataPartPtr & part : remove) { + if (part->versions.mincsn != Tx::RolledBackCSN) + MergeTreeTransaction::removeOldPart(shared_from_this(), part, txn); + if (part->getState() == IMergeTreeDataPart::State::Committed) { removePartContributionToColumnSizes(part); @@ -2359,7 +2362,8 @@ void MergeTreeData::removePartsFromWorkingSetImmediatelyAndSetTemporaryState(con } } -void MergeTreeData::removePartsFromWorkingSet(const DataPartsVector & remove, bool clear_without_timeout, DataPartsLock * acquired_lock) +void MergeTreeData::removePartsFromWorkingSet( + MergeTreeTransaction * txn, const DataPartsVector & remove, bool clear_without_timeout, DataPartsLock * acquired_lock) { auto lock = (acquired_lock) ? DataPartsLock() : lockParts(); @@ -2371,11 +2375,12 @@ void MergeTreeData::removePartsFromWorkingSet(const DataPartsVector & remove, bo part->assertState({DataPartState::PreCommitted, DataPartState::Committed, DataPartState::Outdated}); } - removePartsFromWorkingSet(remove, clear_without_timeout, lock); + removePartsFromWorkingSet(txn, remove, clear_without_timeout, lock); } -MergeTreeData::DataPartsVector MergeTreeData::removePartsInRangeFromWorkingSet(const MergeTreePartInfo & drop_range, bool clear_without_timeout, - DataPartsLock & lock) +MergeTreeData::DataPartsVector MergeTreeData::removePartsInRangeFromWorkingSet( + MergeTreeTransaction * txn, const MergeTreePartInfo & drop_range, + bool clear_without_timeout, DataPartsLock & lock) { DataPartsVector parts_to_remove; @@ -2416,7 +2421,7 @@ MergeTreeData::DataPartsVector MergeTreeData::removePartsInRangeFromWorkingSet(c parts_to_remove.emplace_back(part); } - removePartsFromWorkingSet(parts_to_remove, clear_without_timeout, lock); + removePartsFromWorkingSet(txn, parts_to_remove, clear_without_timeout, lock); return parts_to_remove; } @@ -3787,7 +3792,7 @@ void MergeTreeData::Transaction::rollback() } } - data.removePartsFromWorkingSet( + data.removePartsFromWorkingSet(txn, DataPartsVector(precommitted_parts.begin(), precommitted_parts.end()), /* clear_without_timeout = */ true); } diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index c63c2b8cefd..fd257a1e343 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -495,13 +495,13 @@ public: /// Parts in add must already be in data_parts with PreCommitted, Committed, or Outdated states. /// If clear_without_timeout is true, the parts will be deleted at once, or during the next call to /// clearOldParts (ignoring old_parts_lifetime). - void removePartsFromWorkingSet(const DataPartsVector & remove, bool clear_without_timeout, DataPartsLock * acquired_lock = nullptr); - void removePartsFromWorkingSet(const DataPartsVector & remove, bool clear_without_timeout, DataPartsLock & acquired_lock); + void removePartsFromWorkingSet(MergeTreeTransaction * txn, const DataPartsVector & remove, bool clear_without_timeout, DataPartsLock * acquired_lock = nullptr); + void removePartsFromWorkingSet(MergeTreeTransaction * txn, const DataPartsVector & remove, bool clear_without_timeout, DataPartsLock & acquired_lock); /// Removes all parts from the working set parts /// for which (partition_id = drop_range.partition_id && min_block >= drop_range.min_block && max_block <= drop_range.max_block). /// Used in REPLACE PARTITION command; - DataPartsVector removePartsInRangeFromWorkingSet(const MergeTreePartInfo & drop_range, bool clear_without_timeout, + DataPartsVector removePartsInRangeFromWorkingSet(MergeTreeTransaction * txn, const MergeTreePartInfo & drop_range, bool clear_without_timeout, DataPartsLock & lock); /// Restores Outdated part and adds it to working set diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index c704bd7a857..1209598c2da 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -1477,13 +1477,6 @@ MergeTreeData::DataPartPtr MergeTreeDataMergerMutator::renameMergedTemporaryPart { /// Rename new part, add to the set and remove original parts. auto replaced_parts = data.renameTempPartAndReplace(new_data_part, txn.get(), nullptr, out_transaction); - //String parts_str; - //for (const auto & p : parts) - // parts_str += "\t" + p->name; - //String parts_str2; - //for (const auto & p : replaced_parts) - // parts_str2 += "\t" + p->name; - //LOG_ERROR(log, "WTF {}: source {}, replaced {}", new_data_part->name, parts_str, parts_str2); /// Let's check that all original parts have been deleted and only them. if (replaced_parts.size() != parts.size()) diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 56a29f97576..4fb510a9b97 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -243,7 +243,7 @@ void StorageMergeTree::drop() dropAllData(); } -void StorageMergeTree::truncate(const ASTPtr &, const StorageMetadataPtr &, ContextPtr, TableExclusiveLockHolder &) +void StorageMergeTree::truncate(const ASTPtr &, const StorageMetadataPtr &, ContextPtr local_context, TableExclusiveLockHolder &) { { /// Asks to complete merges and does not allow them to start. @@ -251,7 +251,7 @@ void StorageMergeTree::truncate(const ASTPtr &, const StorageMetadataPtr &, Cont auto merge_blocker = stopMergesAndWait(); auto parts_to_remove = getDataPartsVector(); - removePartsFromWorkingSet(parts_to_remove, true); + removePartsFromWorkingSet(local_context->getCurrentTransaction().get(), parts_to_remove, true); LOG_INFO(log, "Removed {} parts.", parts_to_remove.size()); } @@ -1058,7 +1058,6 @@ std::optional StorageMergeTree::getDataProcessingJob() //-V657 return mergeSelectedParts(metadata_snapshot, false, {}, *merge_entry, share_lock, txn); else if (mutate_entry) return mutateSelectedPart(metadata_snapshot, *mutate_entry, share_lock); - return true; //__builtin_unreachable(); }, PoolType::MERGE_MUTATE}; @@ -1242,7 +1241,7 @@ ActionLock StorageMergeTree::stopMergesAndWait() } -MergeTreeDataPartPtr StorageMergeTree::outdatePart(const String & part_name, bool force) +MergeTreeDataPartPtr StorageMergeTree::outdatePart(MergeTreeTransaction * txn, const String & part_name, bool force) { if (force) @@ -1252,7 +1251,7 @@ MergeTreeDataPartPtr StorageMergeTree::outdatePart(const String & part_name, boo auto part = getPartIfExists(part_name, {MergeTreeDataPartState::Committed}); if (!part) throw Exception("Part " + part_name + " not found, won't try to drop it.", ErrorCodes::NO_SUCH_DATA_PART); - removePartsFromWorkingSet({part}, true); + removePartsFromWorkingSet(txn, {part}, true); return part; } else @@ -1271,22 +1270,22 @@ MergeTreeDataPartPtr StorageMergeTree::outdatePart(const String & part_name, boo if (currently_merging_mutating_parts.count(part)) return nullptr; - removePartsFromWorkingSet({part}, true); + removePartsFromWorkingSet(txn, {part}, true); return part; } } void StorageMergeTree::dropPartNoWaitNoThrow(const String & part_name) { - if (auto part = outdatePart(part_name, /*force=*/ false)) + if (auto part = outdatePart(nullptr, part_name, /*force=*/ false)) dropPartsImpl({part}, /*detach=*/ false); /// Else nothing to do, part was removed in some different way } -void StorageMergeTree::dropPart(const String & part_name, bool detach, ContextPtr /*query_context*/) +void StorageMergeTree::dropPart(const String & part_name, bool detach, ContextPtr query_context) { - if (auto part = outdatePart(part_name, /*force=*/ true)) + if (auto part = outdatePart(query_context->getCurrentTransaction().get(), part_name, /*force=*/ true)) dropPartsImpl({part}, detach); } @@ -1302,7 +1301,7 @@ void StorageMergeTree::dropPartition(const ASTPtr & partition, bool detach, Cont parts_to_remove = getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); /// TODO should we throw an exception if parts_to_remove is empty? - removePartsFromWorkingSet(parts_to_remove, true); + removePartsFromWorkingSet(local_context->getCurrentTransaction().get(), parts_to_remove, true); } dropPartsImpl(std::move(parts_to_remove), detach); @@ -1430,7 +1429,7 @@ void StorageMergeTree::replacePartitionFrom(const StoragePtr & source_table, con /// If it is REPLACE (not ATTACH), remove all parts which max_block_number less then min_block_number of the first new block if (replace) - removePartsInRangeFromWorkingSet(drop_range, true, data_parts_lock); + removePartsInRangeFromWorkingSet(local_context->getCurrentTransaction().get(), drop_range, true, data_parts_lock); } PartLog::addNewParts(getContext(), dst_parts, watch.elapsed()); @@ -1502,7 +1501,7 @@ void StorageMergeTree::movePartitionToTable(const StoragePtr & dest_table, const for (MutableDataPartPtr & part : dst_parts) dest_table_storage->renameTempPartAndReplace(part, local_context->getCurrentTransaction().get(), &dest_table_storage->increment, &transaction, lock); - removePartsFromWorkingSet(src_parts, true, lock); + removePartsFromWorkingSet(local_context->getCurrentTransaction().get(), src_parts, true, lock); transaction.commit(&lock); } diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index d8585b967f0..24c0b46e5d6 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -154,7 +154,7 @@ private: /// Make part state outdated and queue it to remove without timeout /// If force, then stop merges and block them until part state became outdated. Throw exception if part doesn't exists /// If not force, then take merges selector and check that part is not participating in background operations. - MergeTreeDataPartPtr outdatePart(const String & part_name, bool force); + MergeTreeDataPartPtr outdatePart(MergeTreeTransaction * txn, const String & part_name, bool force); ActionLock stopMergesAndWait(); /// Allocate block number for new mutation, write mutation to disk diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index c243cd24c49..6dbf7d270ea 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -2195,7 +2195,7 @@ void StorageReplicatedMergeTree::executeDropRange(const LogEntry & entry) DataPartsVector parts_to_remove; { auto data_parts_lock = lockParts(); - parts_to_remove = removePartsInRangeFromWorkingSet(drop_range_info, true, data_parts_lock); + parts_to_remove = removePartsInRangeFromWorkingSet(nullptr, drop_range_info, true, data_parts_lock); } if (entry.detach) @@ -2311,7 +2311,7 @@ bool StorageReplicatedMergeTree::executeReplaceRange(const LogEntry & entry) if (parts_to_add.empty() && replace) { - parts_to_remove = removePartsInRangeFromWorkingSet(drop_range, true, data_parts_lock); + parts_to_remove = removePartsInRangeFromWorkingSet(nullptr, drop_range, true, data_parts_lock); String parts_to_remove_str; for (const auto & part : parts_to_remove) { @@ -2554,7 +2554,7 @@ bool StorageReplicatedMergeTree::executeReplaceRange(const LogEntry & entry) transaction.commit(&data_parts_lock); if (replace) { - parts_to_remove = removePartsInRangeFromWorkingSet(drop_range, true, data_parts_lock); + parts_to_remove = removePartsInRangeFromWorkingSet(nullptr, drop_range, true, data_parts_lock); String parts_to_remove_str; for (const auto & part : parts_to_remove) { @@ -2777,7 +2777,7 @@ void StorageReplicatedMergeTree::cloneReplica(const String & source_replica, Coo } } - removePartsFromWorkingSet(parts_to_remove_from_working_set, true); + removePartsFromWorkingSet(nullptr, parts_to_remove_from_working_set, true); for (const String & name : active_parts) { @@ -6486,7 +6486,7 @@ void StorageReplicatedMergeTree::replacePartitionFrom( transaction.commit(&data_parts_lock); if (replace) - parts_to_remove = removePartsInRangeFromWorkingSet(drop_range, true, data_parts_lock); + parts_to_remove = removePartsInRangeFromWorkingSet(nullptr, drop_range, true, data_parts_lock); } PartLog::addNewParts(getContext(), dst_parts, watch.elapsed()); @@ -6672,7 +6672,7 @@ void StorageReplicatedMergeTree::movePartitionToTable(const StoragePtr & dest_ta else zkutil::KeeperMultiException::check(code, ops, op_results); - parts_to_remove = removePartsInRangeFromWorkingSet(drop_range, true, lock); + parts_to_remove = removePartsInRangeFromWorkingSet(nullptr, drop_range, true, lock); transaction.commit(&lock); } diff --git a/tests/queries/0_stateless/01172_transaction_counters.reference b/tests/queries/0_stateless/01172_transaction_counters.reference index 375f3540af9..fe055805d93 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.reference +++ b/tests/queries/0_stateless/01172_transaction_counters.reference @@ -1,10 +1,10 @@ (0,0,'00000000-0000-0000-0000-000000000000') -all_1_1_0 0 -all_2_2_0 1 -all_1_1_0 1 (0,0,'00000000-0000-0000-0000-000000000000') 0 -all_2_2_0 0 (0,0,'00000000-0000-0000-0000-000000000000') 0 -all_1_1_0 0 -all_3_3_0 1 -all_1_1_0 1 (0,0,'00000000-0000-0000-0000-000000000000') 0 -all_2_2_0 18446744073709551615 (0,0,'00000000-0000-0000-0000-000000000000') 0 -all_3_3_0 0 (0,0,'00000000-0000-0000-0000-000000000000') 0 +1 all_1_1_0 0 +1 all_2_2_0 1 +2 all_1_1_0 1 (0,0,'00000000-0000-0000-0000-000000000000') 0 +2 all_2_2_0 0 (0,0,'00000000-0000-0000-0000-000000000000') 0 +3 all_1_1_0 0 +3 all_3_3_0 1 +4 all_1_1_0 1 (0,0,'00000000-0000-0000-0000-000000000000') 0 +4 all_2_2_0 18446744073709551615 (0,0,'00000000-0000-0000-0000-000000000000') 0 +4 all_3_3_0 0 (0,0,'00000000-0000-0000-0000-000000000000') 0 diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql index 2637041bcf7..f36b2c683ba 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.sql +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -7,14 +7,14 @@ select transactionID(); begin transaction; insert into txn_counters(n) values (2); -select system.parts.name, txn_counters.mintid = system.parts.mintid from txn_counters join system.parts on txn_counters._part = system.parts.name where database=currentDatabase() and table='txn_counters' order by system.parts.name; -select name, mincsn, maxtid, maxcsn from system.parts where database=currentDatabase() and table='txn_counters' order by system.parts.name; +select 1, system.parts.name, txn_counters.mintid = system.parts.mintid from txn_counters join system.parts on txn_counters._part = system.parts.name where database=currentDatabase() and table='txn_counters' order by system.parts.name; +select 2, name, mincsn, maxtid, maxcsn from system.parts where database=currentDatabase() and table='txn_counters' order by system.parts.name; rollback; begin transaction; insert into txn_counters(n) values (3); -select system.parts.name, txn_counters.mintid = system.parts.mintid from txn_counters join system.parts on txn_counters._part = system.parts.name where database=currentDatabase() and table='txn_counters' order by system.parts.name; -select name, mincsn, maxtid, maxcsn from system.parts where database=currentDatabase() and table='txn_counters' order by system.parts.name; +select 3, system.parts.name, txn_counters.mintid = system.parts.mintid from txn_counters join system.parts on txn_counters._part = system.parts.name where database=currentDatabase() and table='txn_counters' order by system.parts.name; +select 4, name, mincsn, maxtid, maxcsn from system.parts where database=currentDatabase() and table='txn_counters' order by system.parts.name; commit; drop table txn_counters; From ca5a07a5f563449177f88396858f730568c13367 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 10 Jun 2021 16:57:31 +0300 Subject: [PATCH 0036/1647] dynamic subcolumns: better hash tables while parsing --- src/Columns/ColumnObject.cpp | 12 +++--- src/DataTypes/ObjectUtils.cpp | 16 +++++--- src/DataTypes/ObjectUtils.h | 2 +- src/DataTypes/Serializations/JSONDataParser.h | 40 ++++++++++++------- .../Serializations/SerializationObject.cpp | 9 +++-- 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 5ddfc75eaf1..c08af9ca9cb 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -219,18 +219,18 @@ void ColumnObject::optimizeTypesOfSubcolumns() } size_t subcolumn_size = subcolumn.size(); - if (subcolumn.data->getRatioOfDefaultRows() == 1.0) + auto offsets = ColumnUInt64::create(); + auto & offsets_data = offsets->getData(); + + subcolumn.data->getIndicesOfNonDefaultRows(offsets_data, 0, subcolumn_size); + + if (offsets->size() == subcolumn.data->size()) { subcolumn.data = castColumn({subcolumn.data, from_type, ""}, to_type); } else { - auto offsets = ColumnUInt64::create(); - auto & offsets_data = offsets->getData(); - - subcolumn.data->getIndicesOfNonDefaultRows(offsets_data, 0, subcolumn_size); auto values = subcolumn.data->index(*offsets, offsets->size()); - values = castColumn({values, from_type, ""}, to_type); subcolumn.data = values->createWithOffsets(offsets_data, to_type->getDefault(), subcolumn_size, /*shift=*/ 0); } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index d0ef8ced022..b9bb0f033d2 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -43,17 +43,23 @@ size_t getNumberOfDimensions(const IColumn & column) return 0; } -DataTypePtr getBaseTypeOfArray(DataTypePtr type) +DataTypePtr getBaseTypeOfArray(const DataTypePtr & type) { - while (const auto * type_array = typeid_cast(type.get())) - type = type_array->getNestedType(); - return type; + const DataTypeArray * last_array = nullptr; + const IDataType * current_type = type.get(); + while (const auto * type_array = typeid_cast(current_type)) + { + current_type = type_array->getNestedType().get(); + last_array = type_array; + } + + return last_array ? last_array->getNestedType() : type; } DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension) { for (size_t i = 0; i < dimension; ++i) - type = std::make_shared(type); + type = std::make_shared(std::move(type)); return type; } diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 3ec483f14ff..42562e506e6 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -9,7 +9,7 @@ namespace DB size_t getNumberOfDimensions(const IDataType & type); size_t getNumberOfDimensions(const IColumn & column); -DataTypePtr getBaseTypeOfArray(DataTypePtr type); +DataTypePtr getBaseTypeOfArray(const DataTypePtr & type); DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); DataTypePtr getDataTypeByColumn(const IColumn & column); diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index aab4a5b5c99..daefb6a9b9a 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace DB { @@ -88,7 +89,11 @@ private: else if (element.isArray()) { auto array = element.getArray(); - std::unordered_map arrays_by_path; + + using PathToArray = HashMap; + PathToArray arrays_by_path; + Arena strings_pool; + size_t current_size = 0; for (auto it = array.begin(); it != array.end(); ++it) @@ -96,27 +101,32 @@ private: ParseResult element_result; traverse(*it, "", element_result); - NameSet inserted_paths; - const auto & [paths, values] = element_result; + auto && [paths, values] = element_result; for (size_t i = 0; i < paths.size(); ++i) { - inserted_paths.insert(paths[i]); + const auto & path = paths[i]; - auto & path_array = arrays_by_path[paths[i]]; - assert(path_array.size() == 0 || path_array.size() == current_size); - - if (path_array.size() == 0) + if (auto found = arrays_by_path.find(path)) { - path_array.reserve(paths.size()); - path_array.resize(current_size); + auto & path_array = found->getMapped(); + assert(path_array.size() == current_size); + path_array.push_back(std::move(values[i])); } + else + { + StringRef ref{strings_pool.insert(path.data(), path.size()), path.size()}; + auto & path_array = arrays_by_path[ref]; - path_array.push_back(values[i]); + path_array.reserve(array.size()); + path_array.resize(current_size); + path_array.push_back(std::move(values[i])); + } } for (auto & [path, path_array] : arrays_by_path) { - if (!inserted_paths.count(path)) + assert(path_array.size() == current_size || path_array.size() == current_size + 1); + if (path_array.size() == current_size) path_array.push_back(Field()); } @@ -133,10 +143,10 @@ private: result.paths.reserve(result.paths.size() + arrays_by_path.size()); result.values.reserve(result.paths.size() + arrays_by_path.size()); - for (const auto & [path, path_array] : arrays_by_path) + for (auto && [path, path_array] : arrays_by_path) { - result.paths.push_back(getNextPath(current_path, path)); - result.values.push_back(path_array); + result.paths.push_back(getNextPath(current_path, static_cast(path))); + result.values.push_back(std::move(path_array)); } } } diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 7c0d8bf600d..947eb1fc7a7 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -73,10 +74,12 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & if (!result) throw Exception(ErrorCodes::INCORRECT_DATA, "Cannot parse object"); - const auto & [paths, values] = *result; + auto && [paths, values] = *result; assert(paths.size() == values.size()); - NameSet paths_set(paths.begin(), paths.end()); + HashSet paths_set; + for (const auto & path : paths) + paths_set.insert(path); if (paths.size() != paths_set.size()) throw Exception(ErrorCodes::INCORRECT_DATA, "Object has ambiguous paths"); @@ -120,7 +123,7 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & for (auto & [key, subcolumn] : column_object.getSubcolumns()) { - if (!paths_set.count(key)) + if (!paths_set.has(key)) subcolumn.insertDefault(); } } From e5d8a97546765ad4cb194f55bc0b61fb3a4f0adc Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 11 Jun 2021 15:14:38 +0300 Subject: [PATCH 0037/1647] fix --- src/Common/TransactionMetadata.cpp | 35 +++++++++++++++++++ src/Common/TransactionMetadata.h | 4 ++- src/Interpreters/Context.h | 1 + src/Storages/MergeTree/MergeTreeData.cpp | 13 +++++-- ...034_move_partition_from_table_zookeeper.sh | 4 +-- .../01171_mv_select_insert_isolation_long.sh | 6 ++-- .../01172_transaction_counters.sql | 3 ++ .../01174_select_insert_isolation.sh | 2 +- 8 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/Common/TransactionMetadata.cpp b/src/Common/TransactionMetadata.cpp index bebebe5d550..c3513380a49 100644 --- a/src/Common/TransactionMetadata.cpp +++ b/src/Common/TransactionMetadata.cpp @@ -186,4 +186,39 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current return min <= snapshot_version && (!max || snapshot_version < max); } +bool VersionMetadata::canBeRemoved(Snapshot oldest_snapshot_version) +{ + CSN min = mincsn.load(std::memory_order_relaxed); + if (min == Tx::RolledBackCSN) + return true; + + if (!min) + { + min = TransactionLog::instance().getCSN(mintid); + if (min) + mincsn.store(min, std::memory_order_relaxed); + else + return false; + } + + if (oldest_snapshot_version < min) + return false; + + TIDHash max_lock = maxtid_lock.load(std::memory_order_relaxed); + if (!max_lock) + return false; + + CSN max = maxcsn.load(std::memory_order_relaxed); + if (!max) + { + max = TransactionLog::instance().getCSN(max_lock); + if (max) + maxcsn.store(max, std::memory_order_relaxed); + else + return false; + } + + return max <= oldest_snapshot_version; +} + } diff --git a/src/Common/TransactionMetadata.h b/src/Common/TransactionMetadata.h index b716b0be861..8aa1488f1a6 100644 --- a/src/Common/TransactionMetadata.h +++ b/src/Common/TransactionMetadata.h @@ -53,7 +53,7 @@ const CSN PrehistoricCSN = 1; const LocalTID PrehistoricLocalTID = 1; const TransactionID EmptyTID = {0, 0, UUIDHelpers::Nil}; -const TransactionID PrehistoricTID = {0, PrehistoricLocalTID, UUIDHelpers::Nil}; +const TransactionID PrehistoricTID = {PrehistoricCSN, PrehistoricLocalTID, UUIDHelpers::Nil}; /// So far, that changes will never become visible const CSN RolledBackCSN = std::numeric_limits::max(); @@ -83,6 +83,8 @@ struct VersionMetadata /// It can be called only from MergeTreeTransaction or on server startup void setMinTID(const TransactionID & tid); + + bool canBeRemoved(Snapshot oldest_snapshot_version); }; } diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index b72439b9cd3..0c9f21556b7 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -844,6 +844,7 @@ struct NamedSession NamedSession(NamedSessionKey key_, ContextPtr context_, std::chrono::steady_clock::duration timeout_, NamedSessions & parent_) : key(key_), context(Context::createCopy(context_)), timeout(timeout_), parent(parent_) { + context->setSessionContext(context); } void release(); diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index ee3a81ee88e..6f9bcead9fe 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1210,7 +1210,7 @@ MergeTreeData::DataPartsVector MergeTreeData::grabOldParts(bool force) const DataPartPtr & part = *it; /// Do not remove outdated part if it may be visible for some transaction - if (part->versions.isVisible(TransactionLog::instance().getOldestSnapshot())) + if (!part->versions.canBeRemoved(TransactionLog::instance().getOldestSnapshot())) continue; auto part_remove_time = part->remove_time.load(std::memory_order_relaxed); @@ -2417,8 +2417,15 @@ MergeTreeData::DataPartsVector MergeTreeData::removePartsInRangeFromWorkingSet( part->name, drop_range.getPartName()); } - if (part->getState() != DataPartState::Deleting) - parts_to_remove.emplace_back(part); + if (part->getState() == DataPartState::Deleting) + continue; + + /// FIXME refactor removePartsFromWorkingSet(...), do not remove parts twice + TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; + if (!part->versions.isVisible(tid.start_csn, tid)) + continue; + + parts_to_remove.emplace_back(part); } removePartsFromWorkingSet(txn, parts_to_remove, clear_without_timeout, lock); diff --git a/tests/queries/0_stateless/01034_move_partition_from_table_zookeeper.sh b/tests/queries/0_stateless/01034_move_partition_from_table_zookeeper.sh index ae3dd7851c8..af4969d77c8 100755 --- a/tests/queries/0_stateless/01034_move_partition_from_table_zookeeper.sh +++ b/tests/queries/0_stateless/01034_move_partition_from_table_zookeeper.sh @@ -75,8 +75,8 @@ $CLICKHOUSE_CLIENT --query="DROP TABLE dst;" $CLICKHOUSE_CLIENT --query="SELECT 'MOVE incompatible schema different order by';" -$CLICKHOUSE_CLIENT --query="CREATE TABLE src (p UInt64, k String, d UInt64) ENGINE = ReplicatedMergeTree('/clickhouse/$CURR_DATABASE/src3', '1') PARTITION BY p ORDER BY (p, k, d);" -$CLICKHOUSE_CLIENT --query="CREATE TABLE dst (p UInt64, k String, d UInt64) ENGINE = ReplicatedMergeTree('/clickhouse/$CURR_DATABASE/dst3', '1') PARTITION BY p ORDER BY (d, k, p);" +$CLICKHOUSE_CLIENT --query="CREATE TABLE src (p UInt64, k String, d UInt64) ENGINE = ReplicatedMergeTree('/clickhouse/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX/src3', '1') PARTITION BY p ORDER BY (p, k, d);" +$CLICKHOUSE_CLIENT --query="CREATE TABLE dst (p UInt64, k String, d UInt64) ENGINE = ReplicatedMergeTree('/clickhouse/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX/dst3', '1') PARTITION BY p ORDER BY (d, k, p);" $CLICKHOUSE_CLIENT --query="INSERT INTO src VALUES (0, '0', 1);" diff --git a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh index 1f0148aa093..f506962a36a 100755 --- a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh +++ b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh @@ -9,11 +9,11 @@ set -e $CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS src"; $CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS dst"; $CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS mv"; -$CLICKHOUSE_CLIENT --query "CREATE TABLE src (n Int8, m Int8, CONSTRAINT c CHECK xxHash32(n+m) % 8 != 0) ENGINE=MergeTree ORDER BY n PARTITION BY 0 < n"; -$CLICKHOUSE_CLIENT --query "CREATE TABLE dst (nm Int16, CONSTRAINT c CHECK xxHash32(nm) % 8 != 0) ENGINE=MergeTree ORDER BY nm"; +$CLICKHOUSE_CLIENT --query "CREATE TABLE src (n Int8, m Int8, CONSTRAINT c CHECK xxHash32(n+m) % 8 != 0) ENGINE=MergeTree ORDER BY n PARTITION BY 0 < n SETTINGS old_parts_lifetime=0"; +$CLICKHOUSE_CLIENT --query "CREATE TABLE dst (nm Int16, CONSTRAINT c CHECK xxHash32(nm) % 8 != 0) ENGINE=MergeTree ORDER BY nm SETTINGS old_parts_lifetime=0"; $CLICKHOUSE_CLIENT --query "CREATE MATERIALIZED VIEW mv TO dst (nm Int16) AS SELECT n*m AS nm FROM src"; -$CLICKHOUSE_CLIENT --query "CREATE TABLE tmp (x UInt8, nm Int16) ENGINE=MergeTree ORDER BY (x, nm)" +$CLICKHOUSE_CLIENT --query "CREATE TABLE tmp (x UInt8, nm Int16) ENGINE=MergeTree ORDER BY (x, nm) SETTINGS old_parts_lifetime=0" $CLICKHOUSE_CLIENT --query "INSERT INTO src VALUES (0, 0)" diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql index f36b2c683ba..1d9311e4e86 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.sql +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -5,6 +5,9 @@ create table txn_counters (n Int64, mintid DEFAULT transactionID()) engine=Merge insert into txn_counters(n) values (1); select transactionID(); +-- stop background cleanup +system stop merges txn_counters; + begin transaction; insert into txn_counters(n) values (2); select 1, system.parts.name, txn_counters.mintid = system.parts.mintid from txn_counters join system.parts on txn_counters._part = system.parts.name where database=currentDatabase() and table='txn_counters' order by system.parts.name; diff --git a/tests/queries/0_stateless/01174_select_insert_isolation.sh b/tests/queries/0_stateless/01174_select_insert_isolation.sh index 1be10e29171..2bc7185e3fa 100755 --- a/tests/queries/0_stateless/01174_select_insert_isolation.sh +++ b/tests/queries/0_stateless/01174_select_insert_isolation.sh @@ -7,7 +7,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) set -e $CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS mt"; -$CLICKHOUSE_CLIENT --query "CREATE TABLE mt (n Int8, m Int8) ENGINE=MergeTree ORDER BY n PARTITION BY 0 < n"; +$CLICKHOUSE_CLIENT --query "CREATE TABLE mt (n Int8, m Int8) ENGINE=MergeTree ORDER BY n PARTITION BY 0 < n SETTINGS old_parts_lifetime=0"; function thread_insert_commit() { From 7e9b13199a44e59cd35761906776229826d59c9a Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 12 Jun 2021 18:10:25 +0300 Subject: [PATCH 0038/1647] dynamic columns: improve performance of parsing json data --- src/Columns/ColumnObject.cpp | 393 +++++++++++++++--- src/Columns/ColumnObject.h | 37 +- src/Common/ErrorCodes.cpp | 1 + src/Core/Types.h | 1 + src/DataTypes/FieldToDataType.h | 29 +- src/DataTypes/ObjectUtils.cpp | 13 +- src/DataTypes/ObjectUtils.h | 2 +- .../Serializations/SerializationObject.cpp | 81 +--- src/DataTypes/getLeastSupertype.cpp | 350 +++++++++------- src/DataTypes/getLeastSupertype.h | 3 + src/Functions/FunctionsComparison.h | 2 +- src/Functions/FunctionsRound.h | 2 +- src/Functions/array/arrayIndex.h | 4 +- src/Functions/array/arrayResize.cpp | 2 +- src/Functions/if.cpp | 4 +- src/Functions/ifNull.cpp | 2 +- src/Functions/neighbor.cpp | 2 +- src/Functions/transform.cpp | 2 +- src/Interpreters/TableJoin.cpp | 2 +- src/Processors/Formats/IRowInputFormat.cpp | 2 +- src/Processors/Transforms/WindowTransform.cpp | 2 +- src/Storages/MergeTree/KeyCondition.cpp | 2 +- .../0_stateless/01825_type_json_4.reference | 2 +- .../queries/0_stateless/01825_type_json_4.sh | 2 +- 24 files changed, 635 insertions(+), 307 deletions(-) diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index c08af9ca9cb..5e31d296d97 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,7 +6,11 @@ #include #include #include +#include +#include +#include #include +#include #include #include @@ -19,27 +24,327 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; extern const int ILLEGAL_COLUMN; extern const int DUPLICATE_COLUMN; + extern const int NUMBER_OF_DIMENSIONS_MISMATHED; +} + +namespace +{ + +class FieldVisitorReplaceNull : public StaticVisitor +{ +public: + [[maybe_unused]] explicit FieldVisitorReplaceNull(const Field & replacement_) + : replacement(replacement_) + { + } + + Field operator()(const Null &) const { return replacement; } + + template + Field operator()(const T & x) const + { + if constexpr (std::is_base_of_v) + { + const size_t size = x.size(); + T res(size); + for (size_t i = 0; i < size; ++i) + res[i] = applyVisitor(*this, x[i]); + return res; + } + else + return x; + } + +private: + Field replacement; +}; + +class FieldVisitorToNumberOfDimensions : public StaticVisitor +{ +public: + size_t operator()(const Array & x) const + { + if (x.empty()) + return 1; + + const size_t size = x.size(); + size_t dimensions = applyVisitor(*this, x[0]); + for (size_t i = 1; i < size; ++i) + { + size_t current_dimensions = applyVisitor(*this, x[i]); + if (current_dimensions != dimensions) + throw Exception(ErrorCodes::NUMBER_OF_DIMENSIONS_MISMATHED, + "Number of dimensions mismatched among array elements"); + } + + return 1 + dimensions; + } + + template + size_t operator()(const T &) const { return 0; } +}; + +class FieldVisitorToScalarType: public StaticVisitor<> +{ +public: + using FieldType = Field::Types::Which; + + void operator()(const Array & x) + { + size_t size = x.size(); + for (size_t i = 0; i < size; ++i) + applyVisitor(*this, x[i]); + } + + void operator()(const UInt64 & x) + { + field_types.insert(FieldType::UInt64); + if (x <= std::numeric_limits::max()) + type_indexes.insert(TypeIndex::UInt8); + else if (x <= std::numeric_limits::max()) + type_indexes.insert(TypeIndex::UInt16); + else if (x <= std::numeric_limits::max()) + type_indexes.insert(TypeIndex::UInt32); + else + type_indexes.insert(TypeIndex::UInt64); + } + + void operator()(const Int64 & x) + { + field_types.insert(FieldType::Int64); + if (x <= std::numeric_limits::max() && x >= std::numeric_limits::min()) + type_indexes.insert(TypeIndex::Int8); + else if (x <= std::numeric_limits::max() && x >= std::numeric_limits::min()) + type_indexes.insert(TypeIndex::Int16); + else if (x <= std::numeric_limits::max() && x >= std::numeric_limits::min()) + type_indexes.insert(TypeIndex::Int32); + else + type_indexes.insert(TypeIndex::Int64); + } + + void operator()(const Null &) + { + have_nulls = true; + } + + template + void operator()(const T &) + { + auto field_type = Field::TypeToEnum>::value; + field_types.insert(field_type); + type_indexes.insert(TypeId>); + } + + DataTypePtr getScalarType() const + { + + auto res = getLeastSupertype(type_indexes, true); + if (have_nulls) + return makeNullable(res); + + return res; + } + + bool needConvertField() const { return field_types.size() > 1; } + +private: + TypeIndexSet type_indexes; + std::unordered_set field_types; + bool have_nulls = false; +}; + } ColumnObject::Subcolumn::Subcolumn(const Subcolumn & other) - : data(other.data), least_common_type(other.least_common_type) + : least_common_type(other.least_common_type) + , data(other.data) + , num_of_defaults_in_prefix(other.num_of_defaults_in_prefix) { } ColumnObject::Subcolumn::Subcolumn(MutableColumnPtr && data_) - : data(std::move(data_)), least_common_type(getDataTypeByColumn(*data)) + : least_common_type(getDataTypeByColumn(*data_)) +{ + data.push_back(std::move(data_)); +} + +ColumnObject::Subcolumn::Subcolumn(size_t size_) + : least_common_type(std::make_shared()) + , num_of_defaults_in_prefix(size_) { } -void ColumnObject::Subcolumn::insert(const Field & field, const DataTypePtr & value_type) +size_t ColumnObject::Subcolumn::Subcolumn::size() const { - data->insert(field); - least_common_type = getLeastSupertype({least_common_type, value_type}, true); + size_t res = num_of_defaults_in_prefix; + for (const auto & part : data) + res += part->size(); + return res; +} + +size_t ColumnObject::Subcolumn::Subcolumn::byteSize() const +{ + size_t res = 0; + for (const auto & part : data) + res += part->byteSize(); + return res; +} + +size_t ColumnObject::Subcolumn::Subcolumn::allocatedBytes() const +{ + size_t res = 0; + for (const auto & part : data) + res += part->allocatedBytes(); + return res; +} + +void ColumnObject::Subcolumn::checkTypes() const +{ + DataTypes prefix_types; + prefix_types.reserve(data.size()); + for (size_t i = 0; i < data.size(); ++i) + { + auto current_type = getDataTypeByColumn(*data[i]); + prefix_types.push_back(current_type); + auto prefix_common_type = getLeastSupertype(prefix_types); + if (!prefix_common_type->equals(*current_type)) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Data type {} of column at postion {} cannot represent all columns from i-th prefix", + current_type->getName(), i); + } +} + +void ColumnObject::Subcolumn::insert(Field && field) +{ + auto value_dim = applyVisitor(FieldVisitorToNumberOfDimensions(), field); + auto column_dim = getNumberOfDimensions(*least_common_type); + + if (!isNothing(least_common_type) && value_dim != column_dim) + throw Exception(ErrorCodes::NUMBER_OF_DIMENSIONS_MISMATHED, + "Dimension of types mismatched beetwen inserted value and column." + "Dimension of value: {}. Dimension of column: {}", + value_dim, column_dim); + + FieldVisitorToScalarType to_scalar_type_visitor; + applyVisitor(to_scalar_type_visitor, field); + + auto base_type = to_scalar_type_visitor.getScalarType(); + if (isNothing(base_type)) + { + insertDefault(); + return; + } + + DataTypePtr value_type; + if (base_type->isNullable()) + { + base_type = removeNullable(base_type); + if (isNothing(base_type)) + { + insertDefault(); + return; + } + + field = applyVisitor(FieldVisitorReplaceNull(base_type->getDefault()), std::move(field)); + value_type = createArrayOfType(base_type, value_dim); + } + else + { + value_type = createArrayOfType(base_type, value_dim); + } + + bool type_changed = false; + if (data.empty()) + { + data.push_back(value_type->createColumn()); + least_common_type = value_type; + } + else if (!least_common_type->equals(*value_type)) + { + value_type = getLeastSupertype(DataTypes{value_type, least_common_type}, true); + type_changed = true; + if (!least_common_type->equals(*value_type)) + { + data.push_back(value_type->createColumn()); + least_common_type = value_type; + } + } + + if (type_changed || to_scalar_type_visitor.needConvertField()) + { + auto converted_field = convertFieldToTypeOrThrow(std::move(field), *value_type); + data.back()->insert(std::move(converted_field)); + } + else + data.back()->insert(std::move(field)); +} + +void ColumnObject::Subcolumn::finalize() +{ + if (isFinalized()) + return; + + const auto & to_type = least_common_type; + auto result_column = to_type->createColumn(); + + if (num_of_defaults_in_prefix) + result_column->insertManyDefaults(num_of_defaults_in_prefix); + + for (auto & part : data) + { + auto from_type = getDataTypeByColumn(*part); + size_t part_size = part->size(); + + if (!from_type->equals(*to_type)) + { + auto offsets = ColumnUInt64::create(); + auto & offsets_data = offsets->getData(); + + part->getIndicesOfNonDefaultRows(offsets_data, 0, part_size); + + if (offsets->size() == part_size) + { + part = castColumn({part, from_type, ""}, to_type); + } + else + { + auto values = part->index(*offsets, offsets->size()); + values = castColumn({values, from_type, ""}, to_type); + part = values->createWithOffsets(offsets_data, to_type->getDefault(), part_size, /*shift=*/ 0); + } + } + + result_column->insertRangeFrom(*part, 0, part_size); + } + + data = { std::move(result_column) }; + num_of_defaults_in_prefix = 0; } void ColumnObject::Subcolumn::insertDefault() { - data->insertDefault(); + if (data.empty()) + ++num_of_defaults_in_prefix; + else + data.back()->insertDefault(); +} + +IColumn & ColumnObject::Subcolumn::getFinalizedColumn() +{ + assert(isFinalized()); + return *data[0]; +} + +const IColumn & ColumnObject::Subcolumn::getFinalizedColumn() const +{ + assert(isFinalized()); + return *data[0]; +} + +const ColumnPtr & ColumnObject::Subcolumn::getFinalizedColumnPtr() const +{ + assert(isFinalized()); + return data[0]; } ColumnObject::ColumnObject(SubcolumnsMap && subcolumns_) @@ -56,14 +361,11 @@ void ColumnObject::checkConsistency() const size_t first_size = subcolumns.begin()->second.size(); for (const auto & [name, column] : subcolumns) { - if (!column.data) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Null subcolumn passed to ColumnObject"); - - if (first_size != column.data->size()) + if (first_size != column.size()) { throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of subcolumns are inconsistent in ColumnObject." " Subcolumn '{}' has {} rows, subcolumn '{}' has {} rows", - subcolumns.begin()->first, first_size, name, column.data->size()); + subcolumns.begin()->first, first_size, name, column.size()); } } } @@ -89,7 +391,7 @@ size_t ColumnObject::byteSize() const { size_t res = 0; for (const auto & [_, column] : subcolumns) - res += column.data->byteSize(); + res += column.byteSize(); return res; } @@ -97,14 +399,14 @@ size_t ColumnObject::allocatedBytes() const { size_t res = 0; for (const auto & [_, column] : subcolumns) - res += column.data->allocatedBytes(); + res += column.allocatedBytes(); return res; } -void ColumnObject::forEachSubcolumn(ColumnCallback callback) +void ColumnObject::forEachSubcolumn(ColumnCallback) { - for (auto & [_, column] : subcolumns) - callback(column.data); + // for (auto & [_, column] : subcolumns) + // callback(column.data); } const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const String & key) const @@ -130,23 +432,17 @@ bool ColumnObject::hasSubcolumn(const String & key) const return subcolumns.count(key) != 0; } -void ColumnObject::addSubcolumn(const String & key, const ColumnPtr & column_sample, size_t new_size, bool check_size) +void ColumnObject::addSubcolumn(const String & key, size_t new_size, bool check_size) { if (subcolumns.count(key)) throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key); - if (!column_sample->empty()) - throw Exception(ErrorCodes::LOGICAL_ERROR, - "Cannot add subcolumn '{}' with non-empty sample column", key); - if (check_size && new_size != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", key, new_size, size()); - auto & subcolumn = subcolumns[key]; - subcolumn.data = column_sample->cloneResized(new_size); - subcolumn.least_common_type = std::make_shared(); + subcolumns[key] = Subcolumn(new_size); } void ColumnObject::addSubcolumn(const String & key, Subcolumn && subcolumn, bool check_size) @@ -162,15 +458,6 @@ void ColumnObject::addSubcolumn(const String & key, Subcolumn && subcolumn, bool subcolumns[key] = std::move(subcolumn); } -Names ColumnObject::getKeys() const -{ - Names keys; - keys.reserve(subcolumns.size()); - for (const auto & [key, _] : subcolumns) - keys.emplace_back(key); - return keys; -} - static bool isPrefix(const Strings & prefix, const Strings & strings) { if (prefix.size() > strings.size()) @@ -182,19 +469,20 @@ static bool isPrefix(const Strings & prefix, const Strings & strings) return true; } -void ColumnObject::optimizeTypesOfSubcolumns() +bool ColumnObject::isFinalized() const { - if (optimized_types_of_subcolumns) - return; + return std::all_of(subcolumns.begin(), subcolumns.end(), + [](const auto & elem) { return elem.second.isFinalized(); }); +} +void ColumnObject::finalize() +{ size_t old_size = size(); SubcolumnsMap new_subcolumns; for (auto && [name, subcolumn] : subcolumns) { - auto from_type = getDataTypeByColumn(*subcolumn.data); - const auto & to_type = subcolumn.least_common_type; - - if (isNothing(getBaseTypeOfArray(to_type))) + const auto & least_common_type = subcolumn.getLeastCommonType(); + if (isNothing(getBaseTypeOfArray(least_common_type))) continue; Strings name_parts; @@ -212,29 +500,7 @@ void ColumnObject::optimizeTypesOfSubcolumns() } } - if (to_type->equals(*from_type)) - { - new_subcolumns[name] = std::move(subcolumn); - continue; - } - - size_t subcolumn_size = subcolumn.size(); - auto offsets = ColumnUInt64::create(); - auto & offsets_data = offsets->getData(); - - subcolumn.data->getIndicesOfNonDefaultRows(offsets_data, 0, subcolumn_size); - - if (offsets->size() == subcolumn.data->size()) - { - subcolumn.data = castColumn({subcolumn.data, from_type, ""}, to_type); - } - else - { - auto values = subcolumn.data->index(*offsets, offsets->size()); - values = castColumn({values, from_type, ""}, to_type); - subcolumn.data = values->createWithOffsets(offsets_data, to_type->getDefault(), subcolumn_size, /*shift=*/ 0); - } - + subcolumn.finalize(); new_subcolumns[name] = std::move(subcolumn); } @@ -242,7 +508,6 @@ void ColumnObject::optimizeTypesOfSubcolumns() new_subcolumns[COLUMN_NAME_DUMMY] = Subcolumn{ColumnUInt8::create(old_size)}; std::swap(subcolumns, new_subcolumns); - optimized_types_of_subcolumns = true; } } diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 7e9abd3f1f9..a54b9fcbdb5 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -19,26 +19,41 @@ namespace ErrorCodes class ColumnObject final : public COWHelper { public: - struct Subcolumn + class Subcolumn { + public: Subcolumn() = default; - Subcolumn(const Subcolumn & other); + Subcolumn(size_t size_); Subcolumn(MutableColumnPtr && data_); + Subcolumn(const Subcolumn & other); Subcolumn & operator=(Subcolumn && other) = default; - WrappedPtr data; - DataTypePtr least_common_type; + size_t size() const; + size_t byteSize() const; + size_t allocatedBytes() const; - size_t size() const { return data->size(); } - void insert(const Field & field, const DataTypePtr & value_type); + bool isFinalized() const { return data.size() == 1 && num_of_defaults_in_prefix == 0; } + const DataTypePtr & getLeastCommonType() const { return least_common_type; } + void checkTypes() const; + + void insert(Field && field); void insertDefault(); + void finalize(); + + IColumn & getFinalizedColumn(); + const IColumn & getFinalizedColumn() const; + const ColumnPtr & getFinalizedColumnPtr() const; + + private: + DataTypePtr least_common_type; + std::vector data; + size_t num_of_defaults_in_prefix = 0; }; using SubcolumnsMap = std::unordered_map; private: SubcolumnsMap subcolumns; - bool optimized_types_of_subcolumns = false; public: static constexpr auto COLUMN_NAME_DUMMY = "_dummy"; @@ -53,15 +68,14 @@ public: const Subcolumn & getSubcolumn(const String & key) const; Subcolumn & getSubcolumn(const String & key); - void addSubcolumn(const String & key, const ColumnPtr & column_sample, size_t new_size, bool check_size = false); + void addSubcolumn(const String & key, size_t new_size, bool check_size = false); void addSubcolumn(const String & key, Subcolumn && subcolumn, bool check_size = false); const SubcolumnsMap & getSubcolumns() const { return subcolumns; } SubcolumnsMap & getSubcolumns() { return subcolumns; } - Names getKeys() const; - - void optimizeTypesOfSubcolumns(); + bool isFinalized() const; + void finalize(); /// Part of interface @@ -116,4 +130,3 @@ private: }; } - diff --git a/src/Common/ErrorCodes.cpp b/src/Common/ErrorCodes.cpp index d840830bf28..cb20ced71b0 100644 --- a/src/Common/ErrorCodes.cpp +++ b/src/Common/ErrorCodes.cpp @@ -554,6 +554,7 @@ M(584, PROJECTION_NOT_USED) \ M(585, CANNOT_PARSE_YAML) \ M(586, CANNOT_CREATE_FILE) \ + M(587, NUMBER_OF_DIMENSIONS_MISMATHED) \ \ M(998, POSTGRESQL_CONNECTION_FAILURE) \ M(999, KEEPER_EXCEPTION) \ diff --git a/src/Core/Types.h b/src/Core/Types.h index 3f9b04f2f4f..afce0c3a0b7 100644 --- a/src/Core/Types.h +++ b/src/Core/Types.h @@ -109,6 +109,7 @@ template <> inline constexpr TypeIndex TypeId = TypeIndex::Int128; template <> inline constexpr TypeIndex TypeId = TypeIndex::Int256; template <> inline constexpr TypeIndex TypeId = TypeIndex::Float32; template <> inline constexpr TypeIndex TypeId = TypeIndex::Float64; +template <> inline constexpr TypeIndex TypeId = TypeIndex::String; template <> inline constexpr TypeIndex TypeId = TypeIndex::UUID; diff --git a/src/DataTypes/FieldToDataType.h b/src/DataTypes/FieldToDataType.h index 03681bb1d61..a8270715b74 100644 --- a/src/DataTypes/FieldToDataType.h +++ b/src/DataTypes/FieldToDataType.h @@ -48,5 +48,32 @@ private: bool allow_convertion_to_string; }; -} +// template struct FieldTypeToTypeIndex; + +// #define DEFINE_FOR_TYPE(T) \ +// template <> struct FieldTypeToTypeIndex { static constexpr auto value = TypeIndex::T; }; + +// DEFINE_FOR_TYPE(UInt64) +// DEFINE_FOR_TYPE(UInt128) +// DEFINE_FOR_TYPE(UInt256) +// DEFINE_FOR_TYPE(Int64) +// DEFINE_FOR_TYPE(Int128) +// DEFINE_FOR_TYPE(Int256) +// DEFINE_FOR_TYPE(UUID) +// DEFINE_FOR_TYPE(Float64) +// DEFINE_FOR_TYPE(String) +// DEFINE_FOR_TYPE(Array) +// DEFINE_FOR_TYPE(Tuple) +// DEFINE_FOR_TYPE(Map) +// DEFINE_FOR_TYPE(Decimal32) +// DEFINE_FOR_TYPE(Decimal64) +// DEFINE_FOR_TYPE(Decimal128) +// DEFINE_FOR_TYPE(Decimal256) + +// template <> struct FieldTypeToTypeIndex { static constexpr auto value = TypeIndex::Nothing; }; +// template <> struct FieldTypeToTypeIndex { static constexpr auto value = TypeIndex::AggregateFunction; }; + +// #undef DEFINE_FOR_TYPE + +} diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index b9bb0f033d2..8cde1f14819 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -110,9 +110,16 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con const auto & column_object = assert_cast(*column.column); const auto & subcolumns_map = column_object.getSubcolumns(); + if (!column_object.isFinalized()) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Cannot convert to tuple column '{}' from type {}. Column should be finalized firts", + name_type.name, name_type.type->getName()); + std::vector> tuple_elements; for (const auto & [key, subcolumn] : subcolumns_map) - tuple_elements.emplace_back(key, getDataTypeByColumn(*subcolumn.data), subcolumn.data); + tuple_elements.emplace_back(key, + subcolumn.getLeastCommonType(), + subcolumn.getFinalizedColumnPtr()); std::sort(tuple_elements.begin(), tuple_elements.end(), [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); } ); @@ -183,11 +190,11 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) return std::make_shared(tuple_types, tuple_names); } -void optimizeTypesOfObjectColumns(MutableColumns & columns) +void finalizeObjectColumns(MutableColumns & columns) { for (auto & column : columns) if (auto * column_object = typeid_cast(column.get())) - column_object->optimizeTypesOfSubcolumns(); + column_object->finalize(); } } diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 42562e506e6..379ff2baa58 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -15,6 +15,6 @@ DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns); DataTypePtr getLeastCommonTypeForObject(const DataTypes & types); -void optimizeTypesOfObjectColumns(MutableColumns & columns); +void finalizeObjectColumns(MutableColumns & columns); } diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 947eb1fc7a7..6277f58bef9 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -2,15 +2,12 @@ #include #include #include -#include -#include #include +#include #include #include -#include #include #include -#include #include #include @@ -27,40 +24,6 @@ namespace ErrorCodes extern const int TYPE_MISMATCH; } -namespace -{ - -class FieldVisitorReplaceNull : public StaticVisitor -{ -public: - [[maybe_unused]] explicit FieldVisitorReplaceNull(const Field & replacement_) - : replacement(replacement_) - { - } - - Field operator() (const Null &) const { return replacement; } - - template - Field operator() (const T & x) const - { - if constexpr (std::is_base_of_v) - { - const size_t size = x.size(); - T res(size); - for (size_t i = 0; i < size; ++i) - res[i] = applyVisitor(*this, x[i]); - return res; - } - else - return x; - } - -private: - Field replacement; -}; - -} - template template void SerializationObject::deserializeTextImpl(IColumn & column, Reader && reader) const @@ -87,38 +50,13 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & size_t column_size = column_object.size(); for (size_t i = 0; i < paths.size(); ++i) { - Field value = std::move(values[i]); - - auto value_type = applyVisitor(FieldToDataType(/*allow_conversion_to_string=*/ true), value); - auto value_dim = getNumberOfDimensions(*value_type); - auto base_type = getBaseTypeOfArray(value_type); - - if (base_type->isNullable()) - { - base_type = removeNullable(base_type); - auto default_field = isNothing(base_type) ? Field(String()) : base_type->getDefault(); - value = applyVisitor(FieldVisitorReplaceNull(default_field), value); - value_type = createArrayOfType(base_type, value_dim); - } - - auto array_type = createArrayOfType(std::make_shared(), value_dim); - auto converted_value = isNothing(base_type) - ? std::move(value) - : convertFieldToTypeOrThrow(value, *array_type); - if (!column_object.hasSubcolumn(paths[i])) - column_object.addSubcolumn(paths[i], array_type->createColumn(), column_size); + column_object.addSubcolumn(paths[i], column_size); auto & subcolumn = column_object.getSubcolumn(paths[i]); - size_t column_dim = getNumberOfDimensions(*subcolumn.data); + assert(subcolumn.size() == column_size); - if (value_dim != column_dim) - throw Exception(ErrorCodes::TYPE_MISMATCH, - "Dimension of types mismatched beetwen inserted value and column at key '{}'. " - "Dimension of value: {}. Dimension of column: {}", - paths[i], value_dim, column_dim); - - subcolumn.insert(converted_value, value_type); + subcolumn.insert(std::move(values[i])); } for (auto & [key, subcolumn] : column_object.getSubcolumns()) @@ -206,6 +144,9 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( checkSerializationIsSupported(settings, state); const auto & column_object = assert_cast(column); + if (!column_object.isFinalized()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot write non-finalized ColumnObject"); + settings.path.push_back(Substream::ObjectStructure); if (auto * stream = settings.getter(settings.path)) writeVarUInt(column_object.getSubcolumns().size(), *stream); @@ -215,9 +156,9 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( settings.path.back() = Substream::ObjectStructure; settings.path.back().object_key_name = key; + auto type = getDataTypeByColumn(subcolumn.getFinalizedColumn()); if (auto * stream = settings.getter(settings.path)) { - auto type = getDataTypeByColumn(*subcolumn.data); writeStringBinary(key, *stream); writeStringBinary(type->getName(), *stream); } @@ -227,9 +168,9 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( if (auto * stream = settings.getter(settings.path)) { - auto type = getDataTypeByColumn(*subcolumn.data); auto serialization = type->getDefaultSerialization(); - serialization->serializeBinaryBulkWithMultipleStreams(*subcolumn.data, offset, limit, settings, state); + serialization->serializeBinaryBulkWithMultipleStreams( + subcolumn.getFinalizedColumn(), offset, limit, settings, state); } } @@ -295,7 +236,7 @@ void SerializationObject::deserializeBinaryBulkWithMultipleStreams( settings.path.pop_back(); column_object.checkConsistency(); - column_object.optimizeTypesOfSubcolumns(); + column_object.finalize(); column = std::move(mutable_column); } diff --git a/src/DataTypes/getLeastSupertype.cpp b/src/DataTypes/getLeastSupertype.cpp index 82dbe4f5cd9..6c88092b176 100644 --- a/src/DataTypes/getLeastSupertype.cpp +++ b/src/DataTypes/getLeastSupertype.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace DB @@ -30,25 +31,173 @@ namespace ErrorCodes namespace { - String getExceptionMessagePrefix(const DataTypes & types) + +template +String typeToString(const DataType & type); + +template<> String typeToString(const DataTypePtr & type) { return type->getName(); } +template<> String typeToString(const TypeIndex & type) { return getTypeName(type); } + +template +String getExceptionMessagePrefix(const DataTypes & types) +{ + WriteBufferFromOwnString res; + res << "There is no supertype for types "; + + bool first = true; + for (const auto & type : types) { - WriteBufferFromOwnString res; - res << "There is no supertype for types "; + if (!first) + res << ", "; + first = false; - bool first = true; - for (const auto & type : types) - { - if (!first) - res << ", "; - first = false; - - res << type->getName(); - } - - return res.str(); + res << typeToString(type); } + + return res.str(); } +DataTypePtr getNumericType(const TypeIndexSet & types, bool allow_conversion_to_string) +{ + auto throw_or_return = [&](std::string_view message, int error_code) + { + if (allow_conversion_to_string) + return std::make_shared(); + + throw Exception(String(message), error_code); + }; + + bool all_numbers = true; + + size_t max_bits_of_signed_integer = 0; + size_t max_bits_of_unsigned_integer = 0; + size_t max_mantissa_bits_of_floating = 0; + + auto maximize = [](size_t & what, size_t value) + { + if (value > what) + what = value; + }; + + for (const auto & type : types) + { + if (type == TypeIndex::UInt8) + maximize(max_bits_of_unsigned_integer, 8); + else if (type == TypeIndex::UInt16) + maximize(max_bits_of_unsigned_integer, 16); + else if (type == TypeIndex::UInt32) + maximize(max_bits_of_unsigned_integer, 32); + else if (type == TypeIndex::UInt64) + maximize(max_bits_of_unsigned_integer, 64); + else if (type == TypeIndex::UInt128) + maximize(max_bits_of_unsigned_integer, 128); + else if (type == TypeIndex::UInt256) + maximize(max_bits_of_unsigned_integer, 256); + else if (type == TypeIndex::Int8 || type == TypeIndex::Enum8) + maximize(max_bits_of_signed_integer, 8); + else if (type == TypeIndex::Int16 || type == TypeIndex::Enum16) + maximize(max_bits_of_signed_integer, 16); + else if (type == TypeIndex::Int32) + maximize(max_bits_of_signed_integer, 32); + else if (type == TypeIndex::Int64) + maximize(max_bits_of_signed_integer, 64); + else if (type == TypeIndex::Int128) + maximize(max_bits_of_signed_integer, 128); + else if (type == TypeIndex::Int256) + maximize(max_bits_of_signed_integer, 256); + else if (type == TypeIndex::Float32) + maximize(max_mantissa_bits_of_floating, 24); + else if (type == TypeIndex::Float64) + maximize(max_mantissa_bits_of_floating, 53); + else + all_numbers = false; + } + + if (max_bits_of_signed_integer || max_bits_of_unsigned_integer || max_mantissa_bits_of_floating) + { + if (!all_numbers) + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are numbers and some of them are not", ErrorCodes::NO_COMMON_TYPE); + + /// If there are signed and unsigned types of same bit-width, the result must be signed number with at least one more bit. + /// Example, common of Int32, UInt32 = Int64. + + size_t min_bit_width_of_integer = std::max(max_bits_of_signed_integer, max_bits_of_unsigned_integer); + + /// If unsigned is not covered by signed. + if (max_bits_of_signed_integer && max_bits_of_unsigned_integer >= max_bits_of_signed_integer) //-V1051 + { + // Because 128 and 256 bit integers are significantly slower, we should not promote to them. + // But if we already have wide numbers, promotion is necessary. + if (min_bit_width_of_integer != 64) + ++min_bit_width_of_integer; + else + return throw_or_return( + getExceptionMessagePrefix(types) + + " because some of them are signed integers and some are unsigned integers," + " but there is no signed integer type, that can exactly represent all required unsigned integer values", + ErrorCodes::NO_COMMON_TYPE); + } + + /// If the result must be floating. + if (max_mantissa_bits_of_floating) + { + size_t min_mantissa_bits = std::max(min_bit_width_of_integer, max_mantissa_bits_of_floating); + if (min_mantissa_bits <= 24) + return std::make_shared(); + else if (min_mantissa_bits <= 53) + return std::make_shared(); + else + return throw_or_return(getExceptionMessagePrefix(types) + + " because some of them are integers and some are floating point," + " but there is no floating point type, that can exactly represent all required integers", ErrorCodes::NO_COMMON_TYPE); + } + + /// If the result must be signed integer. + if (max_bits_of_signed_integer) + { + if (min_bit_width_of_integer <= 8) + return std::make_shared(); + else if (min_bit_width_of_integer <= 16) + return std::make_shared(); + else if (min_bit_width_of_integer <= 32) + return std::make_shared(); + else if (min_bit_width_of_integer <= 64) + return std::make_shared(); + else if (min_bit_width_of_integer <= 128) + return std::make_shared(); + else if (min_bit_width_of_integer <= 256) + return std::make_shared(); + else + return throw_or_return(getExceptionMessagePrefix(types) + + " because some of them are signed integers and some are unsigned integers," + " but there is no signed integer type, that can exactly represent all required unsigned integer values", ErrorCodes::NO_COMMON_TYPE); + } + + /// All unsigned. + { + if (min_bit_width_of_integer <= 8) + return std::make_shared(); + else if (min_bit_width_of_integer <= 16) + return std::make_shared(); + else if (min_bit_width_of_integer <= 32) + return std::make_shared(); + else if (min_bit_width_of_integer <= 64) + return std::make_shared(); + else if (min_bit_width_of_integer <= 128) + return std::make_shared(); + else if (min_bit_width_of_integer <= 256) + return std::make_shared(); + else + return throw_or_return("Logical error: " + getExceptionMessagePrefix(types) + + " but as all data types are unsigned integers, we must have found maximum unsigned integer type", ErrorCodes::NO_COMMON_TYPE); + + } + } + + return {}; +} + +} DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_string) { @@ -264,7 +413,7 @@ DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_ /// Non-recursive rules - std::unordered_set type_ids; + TypeIndexSet type_ids; for (const auto & type : types) type_ids.insert(type->getTypeId()); @@ -378,135 +527,56 @@ DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_ /// For numeric types, the most complicated part. { - bool all_numbers = true; - - size_t max_bits_of_signed_integer = 0; - size_t max_bits_of_unsigned_integer = 0; - size_t max_mantissa_bits_of_floating = 0; - - auto maximize = [](size_t & what, size_t value) - { - if (value > what) - what = value; - }; - - for (const auto & type : types) - { - if (typeid_cast(type.get())) - maximize(max_bits_of_unsigned_integer, 8); - else if (typeid_cast(type.get())) - maximize(max_bits_of_unsigned_integer, 16); - else if (typeid_cast(type.get())) - maximize(max_bits_of_unsigned_integer, 32); - else if (typeid_cast(type.get())) - maximize(max_bits_of_unsigned_integer, 64); - else if (typeid_cast(type.get())) - maximize(max_bits_of_unsigned_integer, 128); - else if (typeid_cast(type.get())) - maximize(max_bits_of_unsigned_integer, 256); - else if (typeid_cast(type.get()) || typeid_cast(type.get())) - maximize(max_bits_of_signed_integer, 8); - else if (typeid_cast(type.get()) || typeid_cast(type.get())) - maximize(max_bits_of_signed_integer, 16); - else if (typeid_cast(type.get())) - maximize(max_bits_of_signed_integer, 32); - else if (typeid_cast(type.get())) - maximize(max_bits_of_signed_integer, 64); - else if (typeid_cast(type.get())) - maximize(max_bits_of_signed_integer, 128); - else if (typeid_cast(type.get())) - maximize(max_bits_of_signed_integer, 256); - else if (typeid_cast(type.get())) - maximize(max_mantissa_bits_of_floating, 24); - else if (typeid_cast(type.get())) - maximize(max_mantissa_bits_of_floating, 53); - else - all_numbers = false; - } - - if (max_bits_of_signed_integer || max_bits_of_unsigned_integer || max_mantissa_bits_of_floating) - { - if (!all_numbers) - return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are numbers and some of them are not", ErrorCodes::NO_COMMON_TYPE); - - /// If there are signed and unsigned types of same bit-width, the result must be signed number with at least one more bit. - /// Example, common of Int32, UInt32 = Int64. - - size_t min_bit_width_of_integer = std::max(max_bits_of_signed_integer, max_bits_of_unsigned_integer); - - /// If unsigned is not covered by signed. - if (max_bits_of_signed_integer && max_bits_of_unsigned_integer >= max_bits_of_signed_integer) //-V1051 - { - // Because 128 and 256 bit integers are significantly slower, we should not promote to them. - // But if we already have wide numbers, promotion is necessary. - if (min_bit_width_of_integer != 64) - ++min_bit_width_of_integer; - else - return throw_or_return( - getExceptionMessagePrefix(types) - + " because some of them are signed integers and some are unsigned integers," - " but there is no signed integer type, that can exactly represent all required unsigned integer values", - ErrorCodes::NO_COMMON_TYPE); - } - - /// If the result must be floating. - if (max_mantissa_bits_of_floating) - { - size_t min_mantissa_bits = std::max(min_bit_width_of_integer, max_mantissa_bits_of_floating); - if (min_mantissa_bits <= 24) - return std::make_shared(); - else if (min_mantissa_bits <= 53) - return std::make_shared(); - else - return throw_or_return(getExceptionMessagePrefix(types) - + " because some of them are integers and some are floating point," - " but there is no floating point type, that can exactly represent all required integers", ErrorCodes::NO_COMMON_TYPE); - } - - /// If the result must be signed integer. - if (max_bits_of_signed_integer) - { - if (min_bit_width_of_integer <= 8) - return std::make_shared(); - else if (min_bit_width_of_integer <= 16) - return std::make_shared(); - else if (min_bit_width_of_integer <= 32) - return std::make_shared(); - else if (min_bit_width_of_integer <= 64) - return std::make_shared(); - else if (min_bit_width_of_integer <= 128) - return std::make_shared(); - else if (min_bit_width_of_integer <= 256) - return std::make_shared(); - else - return throw_or_return(getExceptionMessagePrefix(types) - + " because some of them are signed integers and some are unsigned integers," - " but there is no signed integer type, that can exactly represent all required unsigned integer values", ErrorCodes::NO_COMMON_TYPE); - } - - /// All unsigned. - { - if (min_bit_width_of_integer <= 8) - return std::make_shared(); - else if (min_bit_width_of_integer <= 16) - return std::make_shared(); - else if (min_bit_width_of_integer <= 32) - return std::make_shared(); - else if (min_bit_width_of_integer <= 64) - return std::make_shared(); - else if (min_bit_width_of_integer <= 128) - return std::make_shared(); - else if (min_bit_width_of_integer <= 256) - return std::make_shared(); - else - return throw_or_return("Logical error: " + getExceptionMessagePrefix(types) - + " but as all data types are unsigned integers, we must have found maximum unsigned integer type", ErrorCodes::NO_COMMON_TYPE); - } - } + auto numeric_type = getNumericType(type_ids, allow_conversion_to_string); + if (numeric_type) + return numeric_type; } /// All other data types (UUID, AggregateFunction, Enum...) are compatible only if they are the same (checked in trivial cases). return throw_or_return(getExceptionMessagePrefix(types), ErrorCodes::NO_COMMON_TYPE); } +DataTypePtr getLeastSupertype(const TypeIndexSet & types, bool allow_conversion_to_string) +{ + auto throw_or_return = [&](std::string_view message, int error_code) + { + if (allow_conversion_to_string) + return std::make_shared(); + + throw Exception(String(message), error_code); + }; + + TypeIndexSet types_set; + for (const auto & type : types) + { + if (WhichDataType(type).isNothing()) + continue; + + if (!WhichDataType(type).isSimple()) + throw Exception(ErrorCodes::NO_COMMON_TYPE, + "Cannot get common type by type ids with parametric type {}", getTypeName(type)); + + types_set.insert(type); + } + + if (types_set.empty()) + return std::make_shared(); + + if (types.count(TypeIndex::String)) + { + if (types.size() != 1) + return throw_or_return(getExceptionMessagePrefix(types) + " because some of them are String and some of them are not", ErrorCodes::NO_COMMON_TYPE); + + return std::make_shared(); + } + + /// For numeric types, the most complicated part. + auto numeric_type = getNumericType(types, allow_conversion_to_string); + if (numeric_type) + return numeric_type; + + /// All other data types (UUID, AggregateFunction, Enum...) are compatible only if they are the same (checked in trivial cases). + return throw_or_return(getExceptionMessagePrefix(types), ErrorCodes::NO_COMMON_TYPE); +} + } diff --git a/src/DataTypes/getLeastSupertype.h b/src/DataTypes/getLeastSupertype.h index c407a0e3a63..b1f67452606 100644 --- a/src/DataTypes/getLeastSupertype.h +++ b/src/DataTypes/getLeastSupertype.h @@ -15,4 +15,7 @@ namespace DB */ DataTypePtr getLeastSupertype(const DataTypes & types, bool allow_conversion_to_string = false); +using TypeIndexSet = std::unordered_set; +DataTypePtr getLeastSupertype(const TypeIndexSet & types, bool allow_conversion_to_string = false); + } diff --git a/src/Functions/FunctionsComparison.h b/src/Functions/FunctionsComparison.h index d8f36f17210..ef520cbca7c 100644 --- a/src/Functions/FunctionsComparison.h +++ b/src/Functions/FunctionsComparison.h @@ -1055,7 +1055,7 @@ private: ColumnPtr executeGeneric(const ColumnWithTypeAndName & c0, const ColumnWithTypeAndName & c1) const { - DataTypePtr common_type = getLeastSupertype({c0.type, c1.type}); + DataTypePtr common_type = getLeastSupertype(DataTypes{c0.type, c1.type}); ColumnPtr c0_converted = castColumn(c0, common_type); ColumnPtr c1_converted = castColumn(c1, common_type); diff --git a/src/Functions/FunctionsRound.h b/src/Functions/FunctionsRound.h index 98f35e52a4c..01dc41577e2 100644 --- a/src/Functions/FunctionsRound.h +++ b/src/Functions/FunctionsRound.h @@ -660,7 +660,7 @@ public: throw Exception{"Elements of array of second argument of function " + getName() + " must be numeric type.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; } - return getLeastSupertype({type_x, type_arr_nested}); + return getLeastSupertype(DataTypes{type_x, type_arr_nested}); } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t) const override diff --git a/src/Functions/array/arrayIndex.h b/src/Functions/array/arrayIndex.h index fbd3501298e..3bfe134d6e5 100644 --- a/src/Functions/array/arrayIndex.h +++ b/src/Functions/array/arrayIndex.h @@ -500,7 +500,7 @@ private: auto arg_decayed = removeNullable(removeLowCardinality(arg)); return ((isNativeNumber(inner_type_decayed) || isEnum(inner_type_decayed)) && isNativeNumber(arg_decayed)) - || getLeastSupertype({inner_type_decayed, arg_decayed}); + || getLeastSupertype(DataTypes{inner_type_decayed, arg_decayed}); } #define INTEGRAL_TPL_PACK UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64, Float32, Float64 @@ -974,7 +974,7 @@ private: DataTypePtr array_elements_type = assert_cast(*arguments[0].type).getNestedType(); const DataTypePtr & index_type = arguments[1].type; - DataTypePtr common_type = getLeastSupertype({array_elements_type, index_type}); + DataTypePtr common_type = getLeastSupertype(DataTypes{array_elements_type, index_type}); ColumnPtr col_nested = castColumn({ col->getDataPtr(), array_elements_type, "" }, common_type); diff --git a/src/Functions/array/arrayResize.cpp b/src/Functions/array/arrayResize.cpp index 03b0430d6ec..453c8977467 100644 --- a/src/Functions/array/arrayResize.cpp +++ b/src/Functions/array/arrayResize.cpp @@ -60,7 +60,7 @@ public: if (number_of_arguments == 2) return arguments[0]; else /* if (number_of_arguments == 3) */ - return std::make_shared(getLeastSupertype({array_type->getNestedType(), arguments[2]})); + return std::make_shared(getLeastSupertype(DataTypes{array_type->getNestedType(), arguments[2]})); } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & return_type, size_t input_rows_count) const override diff --git a/src/Functions/if.cpp b/src/Functions/if.cpp index dbe36d4c728..60e9d47c0bb 100644 --- a/src/Functions/if.cpp +++ b/src/Functions/if.cpp @@ -584,7 +584,7 @@ private: const ColumnWithTypeAndName & arg1 = arguments[1]; const ColumnWithTypeAndName & arg2 = arguments[2]; - DataTypePtr common_type = getLeastSupertype({arg1.type, arg2.type}); + DataTypePtr common_type = getLeastSupertype(DataTypes{arg1.type, arg2.type}); ColumnPtr col_then = castColumn(arg1, common_type); ColumnPtr col_else = castColumn(arg2, common_type); @@ -921,7 +921,7 @@ public: throw Exception("Illegal type " + arguments[0]->getName() + " of first argument (condition) of function if. Must be UInt8.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - return getLeastSupertype({arguments[1], arguments[2]}); + return getLeastSupertype(DataTypes{arguments[1], arguments[2]}); } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override diff --git a/src/Functions/ifNull.cpp b/src/Functions/ifNull.cpp index 69c7e9346f2..6c858dc324e 100644 --- a/src/Functions/ifNull.cpp +++ b/src/Functions/ifNull.cpp @@ -46,7 +46,7 @@ public: if (!arguments[0]->isNullable()) return arguments[0]; - return getLeastSupertype({removeNullable(arguments[0]), arguments[1]}); + return getLeastSupertype(DataTypes{removeNullable(arguments[0]), arguments[1]}); } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override diff --git a/src/Functions/neighbor.cpp b/src/Functions/neighbor.cpp index 53c47d3972d..ad7d2d1ef85 100644 --- a/src/Functions/neighbor.cpp +++ b/src/Functions/neighbor.cpp @@ -76,7 +76,7 @@ public: // check that default value column has supertype with first argument if (number_of_arguments == 3) - return getLeastSupertype({arguments[0], arguments[2]}); + return getLeastSupertype(DataTypes{arguments[0], arguments[2]}); return arguments[0]; } diff --git a/src/Functions/transform.cpp b/src/Functions/transform.cpp index e9305304ac0..d0621346f0b 100644 --- a/src/Functions/transform.cpp +++ b/src/Functions/transform.cpp @@ -138,7 +138,7 @@ public: if (type_arr_to_nested->isValueRepresentedByNumber() && type_default->isValueRepresentedByNumber()) { /// We take the smallest common type for the elements of the array of values `to` and for `default`. - return getLeastSupertype({type_arr_to_nested, type_default}); + return getLeastSupertype(DataTypes{type_arr_to_nested, type_default}); } /// TODO More checks. diff --git a/src/Interpreters/TableJoin.cpp b/src/Interpreters/TableJoin.cpp index 122e2cd6479..0597b6e1d90 100644 --- a/src/Interpreters/TableJoin.cpp +++ b/src/Interpreters/TableJoin.cpp @@ -407,7 +407,7 @@ bool TableJoin::inferJoinKeyCommonType(const NamesAndTypesList & left, const Nam DataTypePtr supertype; try { - supertype = DB::getLeastSupertype({ltype->second, rtype->second}); + supertype = DB::getLeastSupertype(DataTypes{ltype->second, rtype->second}); } catch (DB::Exception & ex) { diff --git a/src/Processors/Formats/IRowInputFormat.cpp b/src/Processors/Formats/IRowInputFormat.cpp index ecca6714aa0..5b6146265aa 100644 --- a/src/Processors/Formats/IRowInputFormat.cpp +++ b/src/Processors/Formats/IRowInputFormat.cpp @@ -198,7 +198,7 @@ Chunk IRowInputFormat::generate() return {}; } - optimizeTypesOfObjectColumns(columns); + finalizeObjectColumns(columns); Chunk chunk(std::move(columns), num_rows); //chunk.setChunkInfo(std::move(chunk_missing_values)); return chunk; diff --git a/src/Processors/Transforms/WindowTransform.cpp b/src/Processors/Transforms/WindowTransform.cpp index 03e7d958edd..72b3c927fea 100644 --- a/src/Processors/Transforms/WindowTransform.cpp +++ b/src/Processors/Transforms/WindowTransform.cpp @@ -1474,7 +1474,7 @@ struct WindowFunctionLagLeadInFrame final : public WindowFunction return; } - if (!getLeastSupertype({argument_types[0], argument_types[2]})) + if (!getLeastSupertype(DataTypes{argument_types[0], argument_types[2]})) { throw Exception(ErrorCodes::BAD_ARGUMENTS, "The default value type '{}' is not convertible to the argument type '{}'", diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 268c45c305f..b68973f45af 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -1248,7 +1248,7 @@ bool KeyCondition::tryParseAtomFromAST(const ASTPtr & node, ContextPtr context, } else { - DataTypePtr common_type = getLeastSupertype({key_expr_type, const_type}); + DataTypePtr common_type = getLeastSupertype(DataTypes{key_expr_type, const_type}); if (!const_type->equals(*common_type)) { castValueToType(common_type, const_value, const_type, node); diff --git a/tests/queries/0_stateless/01825_type_json_4.reference b/tests/queries/0_stateless/01825_type_json_4.reference index d87579cf3d9..2b9550ea267 100644 --- a/tests/queries/0_stateless/01825_type_json_4.reference +++ b/tests/queries/0_stateless/01825_type_json_4.reference @@ -1,4 +1,4 @@ -Code: 53 +Code: 587 Code: 15 Code: 53 1 ('v1') Tuple(k1 String) diff --git a/tests/queries/0_stateless/01825_type_json_4.sh b/tests/queries/0_stateless/01825_type_json_4.sh index e93a8e0172f..4533dc018cd 100755 --- a/tests/queries/0_stateless/01825_type_json_4.sh +++ b/tests/queries/0_stateless/01825_type_json_4.sh @@ -11,7 +11,7 @@ ENGINE = MergeTree ORDER BY tuple() \ SETTINGS min_bytes_for_wide_part = 0" echo '{"id": 1, "data": {"k1": "v1"}}, {"id": 2, "data": {"k1": [1, 2]}}' \ - | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 53" + | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 587" echo '{"id": 1, "data": {"k1": "v1"}}, {"id": 2, "data": {"k1": [{"k2" : 1}, {"k2" : 2}]}}' \ | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 15" From 21028ea93667f5a81072a6173e452c48cf1c33bd Mon Sep 17 00:00:00 2001 From: hexiaoting Date: Tue, 15 Jun 2021 16:40:34 +0800 Subject: [PATCH 0039/1647] Add more type checks --- src/Interpreters/InterpreterCreateQuery.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index 28230e0cba4..99315295514 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -960,13 +960,23 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create) /// Check type compatible for materialized dest table and select columns if (create.select && create.is_materialized_view && create.to_table_id) { - StoragePtr table = DatabaseCatalog::instance().getTable({create.database, create.table, create.uuid}, getContext()); - const auto & output_columns = table->getInMemoryMetadataPtr()->getSampleBlock(); + StoragePtr to_table = DatabaseCatalog::instance().getTable({create.to_table_id.database_name, + create.to_table_id.table_name, + create.to_table_id.uuid}, + getContext()); + const auto & to_output_columns = to_table->getInMemoryMetadataPtr()->getSampleBlock(); + StoragePtr view_table = DatabaseCatalog::instance().getTable({create.database, create.table, create.uuid}, getContext()); + const auto & view_output_columns = view_table->getInMemoryMetadataPtr()->getSampleBlock(); + Block input_columns = InterpreterSelectWithUnionQuery( create.select->clone(), getContext(), SelectQueryOptions().analyze()).getSampleBlock(); - auto actions_dag = ActionsDAG::makeConvertingActions( + ActionsDAG::makeConvertingActions( input_columns.getColumnsWithTypeAndName(), - output_columns.getColumnsWithTypeAndName(), + to_output_columns.getColumnsWithTypeAndName(), + ActionsDAG::MatchColumnsMode::Position); + ActionsDAG::makeConvertingActions( + input_columns.getColumnsWithTypeAndName(), + view_output_columns.getColumnsWithTypeAndName(), ActionsDAG::MatchColumnsMode::Name); } From 693685f2c237d3ae0b17a7820fb0dd4732fddd8a Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 7 Jul 2021 18:33:44 +0300 Subject: [PATCH 0040/1647] fix build --- src/Functions/FunctionSQLJSON.h | 6 +++--- src/Interpreters/convertFieldToType.cpp | 2 +- src/Storages/System/StorageSystemDataSkippingIndices.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Functions/FunctionSQLJSON.h b/src/Functions/FunctionSQLJSON.h index 497909b5242..0b6eda5e7bc 100644 --- a/src/Functions/FunctionSQLJSON.h +++ b/src/Functions/FunctionSQLJSON.h @@ -8,13 +8,13 @@ #include #include #include -#include +#include #include #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/Interpreters/convertFieldToType.cpp b/src/Interpreters/convertFieldToType.cpp index b67f5b86925..0d6f41da39f 100644 --- a/src/Interpreters/convertFieldToType.cpp +++ b/src/Interpreters/convertFieldToType.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/Storages/System/StorageSystemDataSkippingIndices.cpp b/src/Storages/System/StorageSystemDataSkippingIndices.cpp index 7a6ce4ec519..27fceb97a31 100644 --- a/src/Storages/System/StorageSystemDataSkippingIndices.cpp +++ b/src/Storages/System/StorageSystemDataSkippingIndices.cpp @@ -153,7 +153,7 @@ Pipe StorageSystemDataSkippingIndices::read( size_t max_block_size, unsigned int /* num_streams */) { - metadata_snapshot->check(column_names, getVirtuals(), getStorageID()); + check(metadata_snapshot, column_names); NameSet names_set(column_names.begin(), column_names.end()); From 3ed7f5a6cce2daa7aa0996f8211a0291f7fc13f5 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 9 Jul 2021 06:15:41 +0300 Subject: [PATCH 0041/1647] dynamic subcolumns: add snapshot for storage --- src/DataStreams/RemoteQueryExecutor.cpp | 5 +- src/DataTypes/ObjectUtils.cpp | 39 ++++++++ src/DataTypes/ObjectUtils.h | 5 + src/Interpreters/InterpreterOptimizeQuery.cpp | 3 +- src/Interpreters/InterpreterSelectQuery.cpp | 19 ++-- src/Interpreters/InterpreterSelectQuery.h | 1 + src/Interpreters/MutationsInterpreter.cpp | 2 +- src/Interpreters/TreeOptimizer.cpp | 7 +- src/Interpreters/TreeRewriter.cpp | 18 ++-- src/Interpreters/TreeRewriter.h | 8 +- .../getHeaderForProcessingStage.cpp | 7 +- .../getHeaderForProcessingStage.h | 7 +- .../QueryPlan/ReadFromMergeTree.cpp | 12 ++- src/Processors/QueryPlan/ReadFromMergeTree.h | 2 + src/Storages/HDFS/StorageHDFS.cpp | 4 +- src/Storages/HDFS/StorageHDFS.h | 2 +- src/Storages/IStorage.cpp | 94 ++---------------- src/Storages/IStorage.h | 20 ++-- src/Storages/Kafka/KafkaBlockInputStream.cpp | 10 +- src/Storages/Kafka/KafkaBlockInputStream.h | 4 +- src/Storages/Kafka/StorageKafka.cpp | 11 ++- src/Storages/Kafka/StorageKafka.h | 2 +- src/Storages/LiveView/StorageBlocks.h | 4 +- src/Storages/LiveView/StorageLiveView.cpp | 2 +- src/Storages/LiveView/StorageLiveView.h | 2 +- .../MergeTreeBaseSelectProcessor.cpp | 4 +- .../MergeTree/MergeTreeBaseSelectProcessor.h | 4 +- .../MergeTree/MergeTreeBlockReadUtils.cpp | 8 +- .../MergeTree/MergeTreeBlockReadUtils.h | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 92 +++++------------- src/Storages/MergeTree/MergeTreeData.h | 9 +- .../MergeTree/MergeTreeDataMergerMutator.cpp | 12 ++- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 13 ++- .../MergeTree/MergeTreeDataSelectExecutor.h | 6 +- .../MergeTree/MergeTreeDataWriter.cpp | 3 +- src/Storages/MergeTree/MergeTreeReadPool.cpp | 10 +- src/Storages/MergeTree/MergeTreeReadPool.h | 4 +- .../MergeTreeReverseSelectProcessor.cpp | 14 +-- .../MergeTreeReverseSelectProcessor.h | 2 +- .../MergeTree/MergeTreeSelectProcessor.cpp | 9 +- .../MergeTree/MergeTreeSelectProcessor.h | 2 +- .../MergeTree/MergeTreeSequentialSource.cpp | 13 +-- .../MergeTree/MergeTreeSequentialSource.h | 4 +- ...rgeTreeThreadSelectBlockInputProcessor.cpp | 5 +- ...MergeTreeThreadSelectBlockInputProcessor.h | 2 +- .../StorageFromBasePartsOfProjection.h | 3 +- .../MergeTree/StorageFromMergeTreeDataPart.h | 7 +- .../StorageMaterializedPostgreSQL.cpp | 4 +- .../StorageMaterializedPostgreSQL.h | 2 +- .../RabbitMQ/RabbitMQBlockInputStream.cpp | 8 +- .../RabbitMQ/RabbitMQBlockInputStream.h | 4 +- src/Storages/RabbitMQ/StorageRabbitMQ.cpp | 12 +-- src/Storages/RabbitMQ/StorageRabbitMQ.h | 2 +- .../ReadFinalForExternalReplicaStorage.cpp | 6 +- .../ReadFinalForExternalReplicaStorage.h | 1 - .../RocksDB/StorageEmbeddedRocksDB.cpp | 10 +- src/Storages/RocksDB/StorageEmbeddedRocksDB.h | 2 +- src/Storages/StorageBuffer.cpp | 26 ++--- src/Storages/StorageBuffer.h | 6 +- src/Storages/StorageDictionary.cpp | 2 +- src/Storages/StorageDictionary.h | 2 +- src/Storages/StorageDistributed.cpp | 14 +-- src/Storages/StorageDistributed.h | 6 +- src/Storages/StorageExternalDistributed.cpp | 4 +- src/Storages/StorageExternalDistributed.h | 2 +- src/Storages/StorageFile.cpp | 28 +++--- src/Storages/StorageFile.h | 2 +- src/Storages/StorageGenerateRandom.cpp | 6 +- src/Storages/StorageGenerateRandom.h | 2 +- src/Storages/StorageInput.cpp | 4 +- src/Storages/StorageInput.h | 2 +- src/Storages/StorageJoin.cpp | 6 +- src/Storages/StorageJoin.h | 2 +- src/Storages/StorageLog.cpp | 8 +- src/Storages/StorageLog.h | 2 +- src/Storages/StorageMaterializeMySQL.cpp | 4 +- src/Storages/StorageMaterializeMySQL.h | 2 +- src/Storages/StorageMaterializedView.cpp | 16 ++-- src/Storages/StorageMaterializedView.h | 6 +- src/Storages/StorageMemory.cpp | 16 ++-- src/Storages/StorageMemory.h | 2 +- src/Storages/StorageMerge.cpp | 29 +++--- src/Storages/StorageMerge.h | 6 +- src/Storages/StorageMergeTree.cpp | 8 +- src/Storages/StorageMergeTree.h | 4 +- src/Storages/StorageMongoDB.cpp | 6 +- src/Storages/StorageMongoDB.h | 2 +- src/Storages/StorageMySQL.cpp | 8 +- src/Storages/StorageMySQL.h | 2 +- src/Storages/StorageNull.h | 4 +- src/Storages/StoragePostgreSQL.cpp | 8 +- src/Storages/StoragePostgreSQL.h | 2 +- src/Storages/StorageProxy.h | 9 +- src/Storages/StorageReplicatedMergeTree.cpp | 10 +- src/Storages/StorageReplicatedMergeTree.h | 4 +- src/Storages/StorageS3.cpp | 6 +- src/Storages/StorageS3.h | 2 +- src/Storages/StorageS3Cluster.cpp | 6 +- src/Storages/StorageS3Cluster.h | 4 +- src/Storages/StorageSnapshot.cpp | 95 +++++++++++++++++++ src/Storages/StorageSnapshot.h | 58 +++++++++++ src/Storages/StorageStripeLog.cpp | 21 ++-- src/Storages/StorageStripeLog.h | 2 +- src/Storages/StorageTableFunction.h | 8 +- src/Storages/StorageTinyLog.cpp | 6 +- src/Storages/StorageTinyLog.h | 2 +- src/Storages/StorageURL.cpp | 24 ++--- src/Storages/StorageURL.h | 14 +-- src/Storages/StorageValues.cpp | 4 +- src/Storages/StorageValues.h | 2 +- src/Storages/StorageView.cpp | 10 +- src/Storages/StorageView.h | 4 +- src/Storages/StorageXDBC.cpp | 18 ++-- src/Storages/StorageXDBC.h | 8 +- src/Storages/System/IStorageSystemOneBlock.h | 6 +- src/Storages/System/StorageSystemColumns.cpp | 6 +- src/Storages/System/StorageSystemColumns.h | 2 +- .../StorageSystemDataSkippingIndices.cpp | 6 +- .../System/StorageSystemDataSkippingIndices.h | 2 +- .../System/StorageSystemDetachedParts.cpp | 4 +- .../System/StorageSystemDetachedParts.h | 2 +- src/Storages/System/StorageSystemDisks.cpp | 6 +- src/Storages/System/StorageSystemDisks.h | 2 +- src/Storages/System/StorageSystemNumbers.cpp | 4 +- src/Storages/System/StorageSystemNumbers.h | 2 +- src/Storages/System/StorageSystemOne.cpp | 4 +- src/Storages/System/StorageSystemOne.h | 2 +- .../System/StorageSystemPartsBase.cpp | 10 +- src/Storages/System/StorageSystemPartsBase.h | 4 +- src/Storages/System/StorageSystemReplicas.cpp | 10 +- src/Storages/System/StorageSystemReplicas.h | 2 +- .../System/StorageSystemStoragePolicies.cpp | 6 +- .../System/StorageSystemStoragePolicies.h | 2 +- src/Storages/System/StorageSystemTables.cpp | 6 +- src/Storages/System/StorageSystemTables.h | 2 +- src/Storages/System/StorageSystemZeros.cpp | 4 +- src/Storages/System/StorageSystemZeros.h | 2 +- src/Storages/tests/gtest_storage_log.cpp | 5 +- ...01825_type_json_schema_race_long.reference | 1 + .../01825_type_json_schema_race_long.sh | 34 +++++++ 140 files changed, 707 insertions(+), 564 deletions(-) create mode 100644 src/Storages/StorageSnapshot.cpp create mode 100644 src/Storages/StorageSnapshot.h create mode 100644 tests/queries/0_stateless/01825_type_json_schema_race_long.reference create mode 100755 tests/queries/0_stateless/01825_type_json_schema_race_long.sh diff --git a/src/DataStreams/RemoteQueryExecutor.cpp b/src/DataStreams/RemoteQueryExecutor.cpp index 0c60bfdbfdb..279c820be04 100644 --- a/src/DataStreams/RemoteQueryExecutor.cpp +++ b/src/DataStreams/RemoteQueryExecutor.cpp @@ -479,12 +479,13 @@ void RemoteQueryExecutor::sendExternalTables() { SelectQueryInfo query_info; auto metadata_snapshot = cur->getInMemoryMetadataPtr(); + auto storage_snapshot = cur->getStorageSnapshot(metadata_snapshot); QueryProcessingStage::Enum read_from_table_stage = cur->getQueryProcessingStage( - context, QueryProcessingStage::Complete, metadata_snapshot, query_info); + context, QueryProcessingStage::Complete, storage_snapshot, query_info); Pipe pipe = cur->read( metadata_snapshot->getColumns().getNamesOfPhysical(), - metadata_snapshot, query_info, context, + storage_snapshot, query_info, context, read_from_table_stage, DEFAULT_BLOCK_SIZE, 1); if (pipe.empty()) diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 8cde1f14819..c2b13fd8db3 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -190,6 +190,45 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) return std::make_shared(tuple_types, tuple_names); } +NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list) +{ + NameSet res; + for (const auto & [name, type] : columns_list) + if (isObject(type)) + res.insert(name); + + return res; +} + +NamesAndTypesList extendObjectColumns(const NamesAndTypesList & columns_list, const NameToTypeMap & object_types, bool with_subcolumns) +{ + NamesAndTypesList result_columns; + for (const auto & column : columns_list) + { + auto it = object_types.find(column.name); + if (it != object_types.end()) + { + const auto & object_type = it->second; + result_columns.emplace_back(column.name, object_type); + + if (with_subcolumns) + { + for (const auto & subcolumn : object_type->getSubcolumnNames()) + { + result_columns.emplace_back(column.name, subcolumn, + object_type, object_type->getSubcolumnType(subcolumn)); + } + } + } + else + { + result_columns.push_back(column); + } + } + + return result_columns; +} + void finalizeObjectColumns(MutableColumns & columns) { for (auto & column : columns) diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 379ff2baa58..cd0922c6817 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -7,6 +7,8 @@ namespace DB { +using NameToTypeMap = std::unordered_map; + size_t getNumberOfDimensions(const IDataType & type); size_t getNumberOfDimensions(const IColumn & column); DataTypePtr getBaseTypeOfArray(const DataTypePtr & type); @@ -15,6 +17,9 @@ DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns); DataTypePtr getLeastCommonTypeForObject(const DataTypes & types); +NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list); +NamesAndTypesList extendObjectColumns(const NamesAndTypesList & columns_list, const NameToTypeMap & object_types, bool with_subcolumns); + void finalizeObjectColumns(MutableColumns & columns); } diff --git a/src/Interpreters/InterpreterOptimizeQuery.cpp b/src/Interpreters/InterpreterOptimizeQuery.cpp index 099e23fbcc5..987de683639 100644 --- a/src/Interpreters/InterpreterOptimizeQuery.cpp +++ b/src/Interpreters/InterpreterOptimizeQuery.cpp @@ -32,6 +32,7 @@ BlockIO InterpreterOptimizeQuery::execute() auto table_id = getContext()->resolveStorageID(ast, Context::ResolveOrdinary); StoragePtr table = DatabaseCatalog::instance().getTable(table_id, getContext()); auto metadata_snapshot = table->getInMemoryMetadataPtr(); + auto storage_snapshot = table->getStorageSnapshot(metadata_snapshot); // Empty list of names means we deduplicate by all columns, but user can explicitly state which columns to use. Names column_names; @@ -46,7 +47,7 @@ BlockIO InterpreterOptimizeQuery::execute() column_names.emplace_back(col->getColumnName()); } - table->check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); Names required_columns; { required_columns = metadata_snapshot->getColumnsRequiredForSortingKey(); diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index af27addacf4..6d641b539af 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -139,7 +139,7 @@ String InterpreterSelectQuery::generateFilterActions(ActionsDAGPtr & actions, co table_expr->children.push_back(table_expr->database_and_table_name); /// Using separate expression analyzer to prevent any possible alias injection - auto syntax_result = TreeRewriter(context).analyzeSelect(query_ast, TreeRewriterResult({}, storage, metadata_snapshot)); + auto syntax_result = TreeRewriter(context).analyzeSelect(query_ast, TreeRewriterResult({}, storage, storage_snapshot)); SelectQueryExpressionAnalyzer analyzer(query_ast, syntax_result, context, metadata_snapshot); actions = analyzer.simpleSelectActions(); @@ -328,6 +328,8 @@ InterpreterSelectQuery::InterpreterSelectQuery( table_id = storage->getStorageID(); if (!metadata_snapshot) metadata_snapshot = storage->getInMemoryMetadataPtr(); + + storage_snapshot = storage->getStorageSnapshot(metadata_snapshot); } if (has_input || !joined_tables.resolveTables()) @@ -384,7 +386,7 @@ InterpreterSelectQuery::InterpreterSelectQuery( syntax_analyzer_result = TreeRewriter(context).analyzeSelect( query_ptr, - TreeRewriterResult(source_header.getNamesAndTypesList(), storage, metadata_snapshot), + TreeRewriterResult(source_header.getNamesAndTypesList(), storage, storage_snapshot), options, joined_tables.tablesWithColumns(), required_result_column_names, table_join); query_info.syntax_analyzer_result = syntax_analyzer_result; @@ -499,7 +501,7 @@ InterpreterSelectQuery::InterpreterSelectQuery( } } - source_header = storage->getSampleBlockForColumns(metadata_snapshot, required_columns); + source_header = storage_snapshot->getSampleBlockForColumns(required_columns); } /// Calculate structure of the result. @@ -613,7 +615,7 @@ Block InterpreterSelectQuery::getSampleBlockImpl() if (storage && !options.only_analyze) { - from_stage = storage->getQueryProcessingStage(context, options.to_stage, metadata_snapshot, query_info); + from_stage = storage->getQueryProcessingStage(context, options.to_stage, storage_snapshot, query_info); /// TODO how can we make IN index work if we cache parts before selecting a projection? /// XXX Used for IN set index analysis. Is this a proper way? @@ -1678,7 +1680,7 @@ void InterpreterSelectQuery::addPrewhereAliasActions() } auto syntax_result - = TreeRewriter(context).analyze(required_columns_all_expr, required_columns_after_prewhere, storage, metadata_snapshot); + = TreeRewriter(context).analyze(required_columns_all_expr, required_columns_after_prewhere, storage, storage_snapshot); alias_actions = ExpressionAnalyzer(required_columns_all_expr, syntax_result, context).getActionsDAG(true); /// The set of required columns could be added as a result of adding an action to calculate ALIAS. @@ -1948,7 +1950,7 @@ void InterpreterSelectQuery::executeFetchColumns(QueryProcessingStage::Enum proc if (!options.ignore_quota && (options.to_stage == QueryProcessingStage::Complete)) quota = context->getQuota(); - storage->read(query_plan, required_columns, metadata_snapshot, query_info, context, processing_stage, max_block_size, max_streams); + storage->read(query_plan, required_columns, storage_snapshot, query_info, context, processing_stage, max_block_size, max_streams); if (context->hasQueryContext() && !options.is_internal) { @@ -1963,8 +1965,9 @@ void InterpreterSelectQuery::executeFetchColumns(QueryProcessingStage::Enum proc /// Create step which reads from empty source if storage has no data. if (!query_plan.isInitialized()) { - const auto & metadata = query_info.projection ? query_info.projection->desc->metadata : metadata_snapshot; - auto header = storage->getSampleBlockForColumns(metadata, required_columns); + /// TODO: fix. + // const auto & metadata = query_info.projection ? query_info.projection->desc->metadata : metadata_snapshot; + auto header = storage_snapshot->getSampleBlockForColumns(required_columns); addEmptySourceToQueryPlan(query_plan, header, query_info, context); } diff --git a/src/Interpreters/InterpreterSelectQuery.h b/src/Interpreters/InterpreterSelectQuery.h index aec3b0b8bd3..d8a866f00f8 100644 --- a/src/Interpreters/InterpreterSelectQuery.h +++ b/src/Interpreters/InterpreterSelectQuery.h @@ -203,6 +203,7 @@ private: Poco::Logger * log; StorageMetadataPtr metadata_snapshot; + StorageSnapshotPtr storage_snapshot; }; } diff --git a/src/Interpreters/MutationsInterpreter.cpp b/src/Interpreters/MutationsInterpreter.cpp index 03a2a4da1d1..0d5d03b676a 100644 --- a/src/Interpreters/MutationsInterpreter.cpp +++ b/src/Interpreters/MutationsInterpreter.cpp @@ -732,7 +732,7 @@ ASTPtr MutationsInterpreter::prepareInterpreterSelectQuery(std::vector & for (const String & column : stage.output_columns) all_asts->children.push_back(std::make_shared(column)); - auto syntax_result = TreeRewriter(context).analyze(all_asts, all_columns, storage, metadata_snapshot); + auto syntax_result = TreeRewriter(context).analyze(all_asts, all_columns, storage, storage->getStorageSnapshot(metadata_snapshot)); if (context->hasQueryContext()) for (const auto & it : syntax_result->getScalars()) context->getQueryContext()->addScalar(it.first, it.second); diff --git a/src/Interpreters/TreeOptimizer.cpp b/src/Interpreters/TreeOptimizer.cpp index c1a265d9a06..8724ac55802 100644 --- a/src/Interpreters/TreeOptimizer.cpp +++ b/src/Interpreters/TreeOptimizer.cpp @@ -607,9 +607,8 @@ void TreeOptimizer::apply(ASTPtr & query, TreeRewriterResult & result, if (!select_query) throw Exception("Select analyze for not select asts.", ErrorCodes::LOGICAL_ERROR); - if (settings.optimize_functions_to_subcolumns && result.storage - && result.storage->supportsSubcolumns() && result.metadata_snapshot) - optimizeFunctionsToSubcolumns(query, result.metadata_snapshot); + if (settings.optimize_functions_to_subcolumns && result.storage_snapshot && result.storage->supportsSubcolumns()) + optimizeFunctionsToSubcolumns(query, result.storage_snapshot->metadata); optimizeIf(query, result.aliases, settings.optimize_if_chain_to_multiif); @@ -668,7 +667,7 @@ void TreeOptimizer::apply(ASTPtr & query, TreeRewriterResult & result, /// Replace monotonous functions with its argument if (settings.optimize_monotonous_functions_in_order_by) optimizeMonotonousFunctionsInOrderBy(select_query, context, tables_with_columns, - result.metadata_snapshot ? result.metadata_snapshot->getSortingKeyColumns() : Names{}); + result.storage_snapshot ? result.storage_snapshot->metadata->getSortingKeyColumns() : Names{}); /// Remove duplicate items from ORDER BY. /// Execute it after all order by optimizations, diff --git a/src/Interpreters/TreeRewriter.cpp b/src/Interpreters/TreeRewriter.cpp index 7f0f53d5b26..2e9ddaf5c54 100644 --- a/src/Interpreters/TreeRewriter.cpp +++ b/src/Interpreters/TreeRewriter.cpp @@ -617,10 +617,10 @@ std::vector getWindowFunctions(ASTPtr & query, const ASTSel TreeRewriterResult::TreeRewriterResult( const NamesAndTypesList & source_columns_, ConstStoragePtr storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, bool add_special) : storage(storage_) - , metadata_snapshot(metadata_snapshot_) + , storage_snapshot(storage_snapshot_) , source_columns(source_columns_) { collectSourceColumns(add_special); @@ -638,7 +638,7 @@ void TreeRewriterResult::collectSourceColumns(bool add_special) if (storage->supportsSubcolumns()) options.withSubcolumns(); - auto columns_from_storage = storage->getColumns(metadata_snapshot, options); + auto columns_from_storage = storage_snapshot->getColumns(options); if (source_columns.empty()) source_columns.swap(columns_from_storage); @@ -745,9 +745,9 @@ void TreeRewriterResult::collectUsedColumns(const ASTPtr & query, bool is_select /// If we have no information about columns sizes, choose a column of minimum size of its data type. required.insert(ExpressionActions::getSmallestColumn(source_columns)); } - else if (is_select && metadata_snapshot && !columns_context.has_array_join) + else if (is_select && storage_snapshot && !columns_context.has_array_join) { - const auto & partition_desc = metadata_snapshot->getPartitionKey(); + const auto & partition_desc = storage_snapshot->metadata->getPartitionKey(); if (partition_desc.expression) { auto partition_source_columns = partition_desc.expression->getRequiredColumns(); @@ -939,9 +939,9 @@ TreeRewriterResultPtr TreeRewriter::analyzeSelect( result.required_source_columns_before_expanding_alias_columns = result.required_source_columns.getNames(); /// rewrite filters for select query, must go after getArrayJoinedColumns - if (settings.optimize_respect_aliases && result.metadata_snapshot) + if (settings.optimize_respect_aliases && result.storage_snapshot) { - replaceAliasColumnsInQuery(query, result.metadata_snapshot->getColumns(), result.array_join_result_to_source, getContext()); + replaceAliasColumnsInQuery(query, result.storage_snapshot->metadata->getColumns(), result.array_join_result_to_source, getContext()); result.collectUsedColumns(query, true); } @@ -960,7 +960,7 @@ TreeRewriterResultPtr TreeRewriter::analyze( ASTPtr & query, const NamesAndTypesList & source_columns, ConstStoragePtr storage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, bool allow_aggregations, bool allow_self_aliases) const { @@ -969,7 +969,7 @@ TreeRewriterResultPtr TreeRewriter::analyze( const auto & settings = getContext()->getSettingsRef(); - TreeRewriterResult result(source_columns, storage, metadata_snapshot, false); + TreeRewriterResult result(source_columns, storage, storage_snapshot, false); normalize(query, result.aliases, result.source_columns_set, false, settings, allow_self_aliases); diff --git a/src/Interpreters/TreeRewriter.h b/src/Interpreters/TreeRewriter.h index 0dca00c285e..e5d69f41811 100644 --- a/src/Interpreters/TreeRewriter.h +++ b/src/Interpreters/TreeRewriter.h @@ -19,11 +19,13 @@ struct SelectQueryOptions; using Scalars = std::map; struct StorageInMemoryMetadata; using StorageMetadataPtr = std::shared_ptr; +struct StorageSnapshot; +using StorageSnapshotPtr = std::shared_ptr; struct TreeRewriterResult { ConstStoragePtr storage; - StorageMetadataPtr metadata_snapshot; + StorageSnapshotPtr storage_snapshot; std::shared_ptr analyzed_join; const ASTTablesInSelectQueryElement * ast_join = nullptr; @@ -76,7 +78,7 @@ struct TreeRewriterResult TreeRewriterResult( const NamesAndTypesList & source_columns_, ConstStoragePtr storage_ = {}, - const StorageMetadataPtr & metadata_snapshot_ = {}, + const StorageSnapshotPtr & storage_snapshot_ = {}, bool add_special = true); void collectSourceColumns(bool add_special); @@ -109,7 +111,7 @@ public: ASTPtr & query, const NamesAndTypesList & source_columns_, ConstStoragePtr storage = {}, - const StorageMetadataPtr & metadata_snapshot = {}, + const StorageSnapshotPtr & storage_snapshot = {}, bool allow_aggregations = false, bool allow_self_aliases = true) const; diff --git a/src/Interpreters/getHeaderForProcessingStage.cpp b/src/Interpreters/getHeaderForProcessingStage.cpp index a98ce9eec0a..3cf5c977e8b 100644 --- a/src/Interpreters/getHeaderForProcessingStage.cpp +++ b/src/Interpreters/getHeaderForProcessingStage.cpp @@ -80,9 +80,8 @@ bool removeJoin(ASTSelectQuery & select, TreeRewriterResult & rewriter_result, C } Block getHeaderForProcessingStage( - const IStorage & storage, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage) @@ -91,7 +90,7 @@ Block getHeaderForProcessingStage( { case QueryProcessingStage::FetchColumns: { - Block header = storage.getSampleBlockForColumns(metadata_snapshot, column_names); + Block header = storage_snapshot->getSampleBlockForColumns(column_names); if (query_info.prewhere_info) { @@ -122,7 +121,7 @@ Block getHeaderForProcessingStage( removeJoin(*query->as(), new_rewriter_result, context); auto stream = std::make_shared( - storage.getSampleBlockForColumns(metadata_snapshot, column_names)); + storage_snapshot->getSampleBlockForColumns(column_names)); return InterpreterSelectQuery(query, context, stream, SelectQueryOptions(processed_stage).analyze()).getSampleBlock(); } } diff --git a/src/Interpreters/getHeaderForProcessingStage.h b/src/Interpreters/getHeaderForProcessingStage.h index 54a1126a3df..6ada136030e 100644 --- a/src/Interpreters/getHeaderForProcessingStage.h +++ b/src/Interpreters/getHeaderForProcessingStage.h @@ -10,8 +10,8 @@ namespace DB { class IStorage; -struct StorageInMemoryMetadata; -using StorageMetadataPtr = std::shared_ptr; +struct StorageSnapshot; +using StorageSnapshotPtr = std::shared_ptr; struct SelectQueryInfo; struct TreeRewriterResult; class ASTSelectQuery; @@ -20,9 +20,8 @@ bool hasJoin(const ASTSelectQuery & select); bool removeJoin(ASTSelectQuery & select, TreeRewriterResult & rewriter_result, ContextPtr context); Block getHeaderForProcessingStage( - const IStorage & storage, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage); diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index b772662c29e..a3c4067000b 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -74,6 +74,7 @@ ReadFromMergeTree::ReadFromMergeTree( Names virt_column_names_, const MergeTreeData & data_, const SelectQueryInfo & query_info_, + StorageSnapshotPtr storage_snapshot_, StorageMetadataPtr metadata_snapshot_, StorageMetadataPtr metadata_snapshot_base_, ContextPtr context_, @@ -83,7 +84,7 @@ ReadFromMergeTree::ReadFromMergeTree( std::shared_ptr max_block_numbers_to_read_, Poco::Logger * log_) : ISourceStep(DataStream{.header = MergeTreeBaseSelectProcessor::transformHeader( - data_.getSampleBlockForColumns(metadata_snapshot_, real_column_names_), + storage_snapshot_->getSampleBlockForColumns(real_column_names_), getPrewhereInfo(query_info_), data_.getPartitionValueType(), virt_column_names_)}) @@ -95,6 +96,7 @@ ReadFromMergeTree::ReadFromMergeTree( , query_info(query_info_) , prewhere_info(getPrewhereInfo(query_info)) , actions_settings(ExpressionActionsSettings::fromContext(context_)) + , storage_snapshot(std::move(storage_snapshot_)) , metadata_snapshot(std::move(metadata_snapshot_)) , metadata_snapshot_base(std::move(metadata_snapshot_base_)) , context(std::move(context_)) @@ -141,7 +143,7 @@ Pipe ReadFromMergeTree::readFromPool( min_marks_for_concurrent_read, std::move(parts_with_range), data, - metadata_snapshot, + storage_snapshot, prewhere_info, true, required_columns, @@ -157,7 +159,7 @@ Pipe ReadFromMergeTree::readFromPool( auto source = std::make_shared( i, pool, min_marks_for_concurrent_read, max_block_size, settings.preferred_block_size_bytes, settings.preferred_max_column_in_block_size_bytes, - data, metadata_snapshot, use_uncompressed_cache, + data, storage_snapshot, use_uncompressed_cache, prewhere_info, actions_settings, reader_settings, virt_column_names); if (i == 0) @@ -179,7 +181,7 @@ ProcessorPtr ReadFromMergeTree::createSource( bool use_uncompressed_cache) { return std::make_shared( - data, metadata_snapshot, part.data_part, max_block_size, preferred_block_size_bytes, + data, storage_snapshot, part.data_part, max_block_size, preferred_block_size_bytes, preferred_max_column_in_block_size_bytes, required_columns, part.ranges, use_uncompressed_cache, prewhere_info, actions_settings, true, reader_settings, virt_column_names, part.part_index_in_query); } @@ -784,7 +786,7 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre result.column_names_to_read.push_back(ExpressionActions::getSmallestColumn(available_real_columns)); } - data.check(metadata_snapshot, result.column_names_to_read); + storage_snapshot->check(result.column_names_to_read); // Build and check if primary key is used when necessary const auto & primary_key = metadata_snapshot->getPrimaryKey(); diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.h b/src/Processors/QueryPlan/ReadFromMergeTree.h index a5184d28593..764227ef304 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.h +++ b/src/Processors/QueryPlan/ReadFromMergeTree.h @@ -60,6 +60,7 @@ public: Names virt_column_names_, const MergeTreeData & data_, const SelectQueryInfo & query_info_, + StorageSnapshotPtr storage_snapshot, StorageMetadataPtr metadata_snapshot_, StorageMetadataPtr metadata_snapshot_base_, ContextPtr context_, @@ -92,6 +93,7 @@ private: PrewhereInfoPtr prewhere_info; ExpressionActionsSettings actions_settings; + StorageSnapshotPtr storage_snapshot; StorageMetadataPtr metadata_snapshot; StorageMetadataPtr metadata_snapshot_base; diff --git a/src/Storages/HDFS/StorageHDFS.cpp b/src/Storages/HDFS/StorageHDFS.cpp index 578da239c20..a8ab5c00ba2 100644 --- a/src/Storages/HDFS/StorageHDFS.cpp +++ b/src/Storages/HDFS/StorageHDFS.cpp @@ -274,7 +274,7 @@ Strings LSWithRegexpMatching(const String & path_for_ls, const HDFSFSPtr & fs, c Pipe StorageHDFS::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context_, QueryProcessingStage::Enum /*processed_stage*/, @@ -309,7 +309,7 @@ Pipe StorageHDFS::read( for (size_t i = 0; i < num_streams; ++i) pipes.emplace_back(std::make_shared( - sources_info, uri_without_path, format_name, compression_method, metadata_snapshot->getSampleBlock(), context_, max_block_size)); + sources_info, uri_without_path, format_name, compression_method, storage_snapshot->metadata->getSampleBlock(), context_, max_block_size)); return Pipe::unitePipes(std::move(pipes)); } diff --git a/src/Storages/HDFS/StorageHDFS.h b/src/Storages/HDFS/StorageHDFS.h index 4a6614be2e0..f24992ba412 100644 --- a/src/Storages/HDFS/StorageHDFS.h +++ b/src/Storages/HDFS/StorageHDFS.h @@ -25,7 +25,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/IStorage.cpp b/src/Storages/IStorage.cpp index 8a9a2f5f76f..2ccaf9b2c1c 100644 --- a/src/Storages/IStorage.cpp +++ b/src/Storages/IStorage.cpp @@ -90,7 +90,7 @@ TableExclusiveLockHolder IStorage::lockExclusively(const String & query_id, cons Pipe IStorage::read( const Names & /*column_names*/, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, @@ -103,18 +103,19 @@ Pipe IStorage::read( void IStorage::read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, size_t max_block_size, unsigned num_streams) { - auto pipe = read(column_names, metadata_snapshot, query_info, context, processed_stage, max_block_size, num_streams); + auto pipe = read(column_names, storage_snapshot, query_info, context, processed_stage, max_block_size, num_streams); if (pipe.empty()) { - const auto & metadata_for_query = query_info.projection ? query_info.projection->desc->metadata : metadata_snapshot; - auto header = getSampleBlockForColumns(metadata_for_query, column_names); + /// TODO: fix + // const auto & metadata_for_query = query_info.projection ? query_info.projection->desc->metadata : storage_snapshot->metadata; + auto header = storage_snapshot->getSampleBlockForColumns(column_names); InterpreterSelectQuery::addEmptySourceToQueryPlan(query_plan, header, query_info, context); } else @@ -206,90 +207,11 @@ NameDependencies IStorage::getDependentViewsByColumn(ContextPtr context) const return name_deps; } -NamesAndTypesList IStorage::getColumns(const StorageMetadataPtr & metadata_snapshot, const GetColumnsOptions & options) const +StorageSnapshotPtr IStorage::getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const { - auto all_columns = metadata_snapshot->getColumns().get(options); - - if (options.with_virtuals) - { - /// Virtual columns must be appended after ordinary, - /// because user can override them. - auto virtuals = getVirtuals(); - if (!virtuals.empty()) - { - NameSet column_names; - for (const auto & column : all_columns) - column_names.insert(column.name); - for (auto && column : virtuals) - if (!column_names.count(column.name)) - all_columns.push_back(std::move(column)); - } - } - - if (options.with_extended_objects) - all_columns = extendObjectColumns(all_columns, options.with_subcolumns); - - return all_columns; + return std::make_shared(*this, metadata_snapshot); } -Block IStorage::getSampleBlockForColumns(const StorageMetadataPtr & metadata_snapshot, const Names & column_names) const -{ - Block res; - - auto all_columns = getColumns( - metadata_snapshot, - GetColumnsOptions(GetColumnsOptions::All) - .withSubcolumns().withVirtuals().withExtendedObjects()); - - std::unordered_map columns_map; - columns_map.reserve(all_columns.size()); - - for (const auto & elem : all_columns) - columns_map.emplace(elem.name, elem.type); - - for (const auto & name : column_names) - { - auto it = columns_map.find(name); - if (it != columns_map.end()) - res.insert({it->second->createColumn(), it->second, it->first}); - else - throw Exception(ErrorCodes::NOT_FOUND_COLUMN_IN_BLOCK, - "Column {} not found in table {}", backQuote(name), getStorageID().getNameForLogs()); - } - - return res; -} - -void IStorage::check(const StorageMetadataPtr & metadata_snapshot, const Names & column_names) const -{ - auto available_columns = getColumns( - metadata_snapshot, - GetColumnsOptions(GetColumnsOptions::AllPhysical) - .withSubcolumns().withVirtuals().withExtendedObjects()).getNames(); - - if (column_names.empty()) - throw Exception(ErrorCodes::EMPTY_LIST_OF_COLUMNS_QUERIED, - "Empty list of columns queried. There are columns: {}", - boost::algorithm::join(available_columns, ",")); - - std::unordered_set columns_set(available_columns.begin(), available_columns.end()); - std::unordered_set unique_names; - - for (const auto & name : column_names) - { - if (columns_set.end() == columns_set.find(name)) - throw Exception(ErrorCodes::NO_SUCH_COLUMN_IN_TABLE, - "There is no column with name {} in table {}. There are columns: ", - backQuote(name), getStorageID().getNameForLogs(), boost::algorithm::join(available_columns, ",")); - - if (unique_names.end() != unique_names.find(name)) - throw Exception(ErrorCodes::COLUMN_QUERIED_MORE_THAN_ONCE, "Column {} queried more than once", name); - - unique_names.insert(name); - } -} - - std::string PrewhereInfo::dump() const { WriteBufferFromOwnString ss; diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index 8dc1277cc33..fa7e58b6d0b 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -160,16 +161,6 @@ public: /// used without any locks. StorageMetadataPtr getInMemoryMetadataPtr() const { return metadata.get(); } - NamesAndTypesList getColumns(const StorageMetadataPtr & metadata_snapshot, const GetColumnsOptions & options) const; - - /// Block with ordinary + materialized + aliases + virtuals + subcolumns. - /// message. - Block getSampleBlockForColumns(const StorageMetadataPtr & metadata_snapshot, const Names & column_names) const; - - /// Verify that all the requested names are in the table and are set correctly: - /// list of names is not empty and the names do not repeat. - void check(const StorageMetadataPtr & metadata_snapshot, const Names & column_names) const; - /// Update storage metadata. Used in ALTER or initialization of Storage. /// Metadata object is multiversion, so this method can be called without /// any locks. @@ -246,8 +237,7 @@ public: * QueryProcessingStage::Enum required for Distributed over Distributed, * since it cannot return Complete for intermediate queries never. */ - virtual QueryProcessingStage::Enum - getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageMetadataPtr &, SelectQueryInfo &) const + virtual QueryProcessingStage::Enum getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageSnapshotPtr &, SelectQueryInfo &) const { return QueryProcessingStage::FetchColumns; } @@ -304,7 +294,7 @@ public: */ virtual Pipe read( const Names & /*column_names*/, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, @@ -316,7 +306,7 @@ public: virtual void read( QueryPlan & query_plan, const Names & /*column_names*/, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, @@ -564,6 +554,8 @@ public: virtual NamesAndTypesList extendObjectColumns(const NamesAndTypesList & columns_list, bool /*with_subcolumns*/) const { return columns_list; } + virtual StorageSnapshotPtr getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const; + private: /// Lock required for alter queries (lockForAlter). Always taken for write /// (actually can be replaced with std::mutex, but for consistency we use diff --git a/src/Storages/Kafka/KafkaBlockInputStream.cpp b/src/Storages/Kafka/KafkaBlockInputStream.cpp index b75f7a4e5be..b4146b634e5 100644 --- a/src/Storages/Kafka/KafkaBlockInputStream.cpp +++ b/src/Storages/Kafka/KafkaBlockInputStream.cpp @@ -21,21 +21,21 @@ const auto MAX_FAILED_POLL_ATTEMPTS = 10; KafkaBlockInputStream::KafkaBlockInputStream( StorageKafka & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const std::shared_ptr & context_, const Names & columns, Poco::Logger * log_, size_t max_block_size_, bool commit_in_suffix_) : storage(storage_) - , metadata_snapshot(metadata_snapshot_) + , storage_snapshot(storage_snapshot_) , context(context_) , column_names(columns) , log(log_) , max_block_size(max_block_size_) , commit_in_suffix(commit_in_suffix_) - , non_virtual_header(metadata_snapshot->getSampleBlockNonMaterialized()) - , virtual_header(storage.getSampleBlockForColumns(metadata_snapshot, storage.getVirtualColumnNames())) + , non_virtual_header(storage_snapshot->metadata->getSampleBlockNonMaterialized()) + , virtual_header(storage_snapshot->getSampleBlockForColumns(storage.getVirtualColumnNames())) , handle_error_mode(storage.getHandleKafkaErrorMode()) { } @@ -53,7 +53,7 @@ KafkaBlockInputStream::~KafkaBlockInputStream() Block KafkaBlockInputStream::getHeader() const { - return storage.getSampleBlockForColumns(metadata_snapshot, column_names); + return storage_snapshot->getSampleBlockForColumns(column_names); } void KafkaBlockInputStream::readPrefixImpl() diff --git a/src/Storages/Kafka/KafkaBlockInputStream.h b/src/Storages/Kafka/KafkaBlockInputStream.h index 98e4b8982e0..88b757bd2ef 100644 --- a/src/Storages/Kafka/KafkaBlockInputStream.h +++ b/src/Storages/Kafka/KafkaBlockInputStream.h @@ -18,7 +18,7 @@ class KafkaBlockInputStream : public IBlockInputStream public: KafkaBlockInputStream( StorageKafka & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const std::shared_ptr & context_, const Names & columns, Poco::Logger * log_, @@ -38,7 +38,7 @@ public: private: StorageKafka & storage; - StorageMetadataPtr metadata_snapshot; + StorageSnapshotPtr storage_snapshot; ContextPtr context; Names column_names; Poco::Logger * log; diff --git a/src/Storages/Kafka/StorageKafka.cpp b/src/Storages/Kafka/StorageKafka.cpp index 15dd5b553b0..af006abd249 100644 --- a/src/Storages/Kafka/StorageKafka.cpp +++ b/src/Storages/Kafka/StorageKafka.cpp @@ -258,7 +258,7 @@ String StorageKafka::getDefaultClientId(const StorageID & table_id_) Pipe StorageKafka::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /* query_info */, ContextPtr local_context, QueryProcessingStage::Enum /* processed_stage */, @@ -281,7 +281,8 @@ Pipe StorageKafka::read( /// TODO: probably that leads to awful performance. /// FIXME: seems that doesn't help with extra reading and committing unprocessed messages. /// TODO: rewrite KafkaBlockInputStream to KafkaSource. Now it is used in other place. - pipes.emplace_back(std::make_shared(std::make_shared(*this, metadata_snapshot, modified_context, column_names, log, 1))); + pipes.emplace_back(std::make_shared(std::make_shared( + *this, storage_snapshot, modified_context, column_names, log, 1))); } LOG_DEBUG(log, "Starting reading {} streams", pipes.size()); @@ -592,7 +593,8 @@ bool StorageKafka::streamToViews() auto table = DatabaseCatalog::instance().getTable(table_id, getContext()); if (!table) throw Exception("Engine table " + table_id.getNameForLogs() + " doesn't exist.", ErrorCodes::LOGICAL_ERROR); - auto metadata_snapshot = getInMemoryMetadataPtr(); + + auto storage_snapshot = getStorageSnapshot(getInMemoryMetadataPtr()); // Create an INSERT query for streaming data auto insert = std::make_shared(); @@ -616,7 +618,8 @@ bool StorageKafka::streamToViews() streams.reserve(stream_count); for (size_t i = 0; i < stream_count; ++i) { - auto stream = std::make_shared(*this, metadata_snapshot, kafka_context, block_io.out->getHeader().getNames(), log, block_size, false); + auto stream = std::make_shared( + *this, storage_snapshot, kafka_context, block_io.out->getHeader().getNames(), log, block_size, false); streams.emplace_back(stream); // Limit read batch to maximum block size to allow DDL diff --git a/src/Storages/Kafka/StorageKafka.h b/src/Storages/Kafka/StorageKafka.h index 805fa9d510c..9ba190611ec 100644 --- a/src/Storages/Kafka/StorageKafka.h +++ b/src/Storages/Kafka/StorageKafka.h @@ -43,7 +43,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/LiveView/StorageBlocks.h b/src/Storages/LiveView/StorageBlocks.h index 6cf7ce59fa2..388a94462ee 100644 --- a/src/Storages/LiveView/StorageBlocks.h +++ b/src/Storages/LiveView/StorageBlocks.h @@ -34,14 +34,14 @@ public: bool supportsFinal() const override { return true; } QueryProcessingStage::Enum - getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageMetadataPtr &, SelectQueryInfo &) const override + getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageSnapshotPtr &, SelectQueryInfo &) const override { return to_stage; } Pipe read( const Names & /*column_names*/, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, diff --git a/src/Storages/LiveView/StorageLiveView.cpp b/src/Storages/LiveView/StorageLiveView.cpp index f54abda6d7f..f7022cf6d5b 100644 --- a/src/Storages/LiveView/StorageLiveView.cpp +++ b/src/Storages/LiveView/StorageLiveView.cpp @@ -497,7 +497,7 @@ void StorageLiveView::refresh(bool grab_lock) Pipe StorageLiveView::read( const Names & /*column_names*/, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, diff --git a/src/Storages/LiveView/StorageLiveView.h b/src/Storages/LiveView/StorageLiveView.h index 23a9c84cb9e..deaf74bed41 100644 --- a/src/Storages/LiveView/StorageLiveView.h +++ b/src/Storages/LiveView/StorageLiveView.h @@ -146,7 +146,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/MergeTree/MergeTreeBaseSelectProcessor.cpp b/src/Storages/MergeTree/MergeTreeBaseSelectProcessor.cpp index 549a8a4f772..a756618dc67 100644 --- a/src/Storages/MergeTree/MergeTreeBaseSelectProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeBaseSelectProcessor.cpp @@ -25,7 +25,7 @@ namespace ErrorCodes MergeTreeBaseSelectProcessor::MergeTreeBaseSelectProcessor( Block header, const MergeTreeData & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const PrewhereInfoPtr & prewhere_info_, ExpressionActionsSettings actions_settings, UInt64 max_block_size_rows_, @@ -36,7 +36,7 @@ MergeTreeBaseSelectProcessor::MergeTreeBaseSelectProcessor( const Names & virt_column_names_) : SourceWithProgress(transformHeader(std::move(header), prewhere_info_, storage_.getPartitionValueType(), virt_column_names_)) , storage(storage_) - , metadata_snapshot(metadata_snapshot_) + , storage_snapshot(storage_snapshot_) , prewhere_info(prewhere_info_) , max_block_size_rows(max_block_size_rows_) , preferred_block_size_bytes(preferred_block_size_bytes_) diff --git a/src/Storages/MergeTree/MergeTreeBaseSelectProcessor.h b/src/Storages/MergeTree/MergeTreeBaseSelectProcessor.h index 8da9b002e16..d8127e041d5 100644 --- a/src/Storages/MergeTree/MergeTreeBaseSelectProcessor.h +++ b/src/Storages/MergeTree/MergeTreeBaseSelectProcessor.h @@ -22,7 +22,7 @@ public: MergeTreeBaseSelectProcessor( Block header, const MergeTreeData & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const PrewhereInfoPtr & prewhere_info_, ExpressionActionsSettings actions_settings, UInt64 max_block_size_rows_, @@ -57,7 +57,7 @@ protected: protected: const MergeTreeData & storage; - StorageMetadataPtr metadata_snapshot; + StorageSnapshotPtr storage_snapshot; PrewhereInfoPtr prewhere_info; std::unique_ptr prewhere_actions; diff --git a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp index 12c96f3eb02..88a596dd1ae 100644 --- a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp +++ b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp @@ -265,7 +265,7 @@ void MergeTreeBlockSizePredictor::update(const Block & sample_block, const Colum MergeTreeReadTaskColumns getReadTaskColumns( const MergeTreeData & storage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const MergeTreeData::DataPartPtr & data_part, const Names & required_columns, const PrewhereInfoPtr & prewhere_info, @@ -275,7 +275,7 @@ MergeTreeReadTaskColumns getReadTaskColumns( Names pre_column_names; /// inject columns required for defaults evaluation - bool should_reorder = !injectRequiredColumns(storage, metadata_snapshot, data_part, column_names).empty(); + bool should_reorder = !injectRequiredColumns(storage, storage_snapshot->metadata, data_part, column_names).empty(); if (prewhere_info) { @@ -300,7 +300,7 @@ MergeTreeReadTaskColumns getReadTaskColumns( if (pre_column_names.empty()) pre_column_names.push_back(column_names[0]); - const auto injected_pre_columns = injectRequiredColumns(storage, metadata_snapshot, data_part, pre_column_names); + const auto injected_pre_columns = injectRequiredColumns(storage, storage_snapshot->metadata, data_part, pre_column_names); if (!injected_pre_columns.empty()) should_reorder = true; @@ -318,7 +318,7 @@ MergeTreeReadTaskColumns getReadTaskColumns( if (check_columns) { - auto all_columns = storage.getColumns(metadata_snapshot, + auto all_columns = storage_snapshot->getColumns( GetColumnsOptions(GetColumnsOptions::All).withSubcolumns().withExtendedObjects()); result.pre_columns = all_columns.addTypes(pre_column_names); diff --git a/src/Storages/MergeTree/MergeTreeBlockReadUtils.h b/src/Storages/MergeTree/MergeTreeBlockReadUtils.h index 31d609e4242..4eee4ccd8d0 100644 --- a/src/Storages/MergeTree/MergeTreeBlockReadUtils.h +++ b/src/Storages/MergeTree/MergeTreeBlockReadUtils.h @@ -75,7 +75,7 @@ struct MergeTreeReadTaskColumns MergeTreeReadTaskColumns getReadTaskColumns( const MergeTreeData & storage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const MergeTreeData::DataPartPtr & data_part, const Names & required_columns, const PrewhereInfoPtr & prewhere_info, diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 730626ba8e5..74976c057ef 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -3861,7 +3861,7 @@ using PartitionIdToMaxBlock = std::unordered_map; static void selectBestProjection( const MergeTreeDataSelectExecutor & reader, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, const Names & required_columns, ProjectionCandidate & candidate, @@ -3890,7 +3890,8 @@ static void selectBestProjection( auto sum_marks = reader.estimateNumMarksToRead( projection_parts, candidate.required_columns, - metadata_snapshot, + storage_snapshot, + storage_snapshot->metadata, candidate.desc->metadata, query_info, // TODO syntax_analysis_result set in index query_context, @@ -3908,8 +3909,9 @@ static void selectBestProjection( sum_marks += reader.estimateNumMarksToRead( normal_parts, required_columns, - metadata_snapshot, - metadata_snapshot, + storage_snapshot, + storage_snapshot->metadata, + storage_snapshot->metadata, query_info, // TODO syntax_analysis_result set in index query_context, settings.max_threads, @@ -3926,8 +3928,9 @@ static void selectBestProjection( bool MergeTreeData::getQueryProcessingStageWithAggregateProjection( - ContextPtr query_context, const StorageMetadataPtr & metadata_snapshot, SelectQueryInfo & query_info) const + ContextPtr query_context, const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info) const { + const auto & metadata_snapshot = storage_snapshot->metadata; const auto & settings = query_context->getSettingsRef(); if (!settings.allow_experimental_projection_optimization || query_info.ignore_projections) return false; @@ -4160,7 +4163,7 @@ bool MergeTreeData::getQueryProcessingStageWithAggregateProjection( { selectBestProjection( reader, - metadata_snapshot, + storage_snapshot, query_info, analysis_result.required_columns, candidate, @@ -4181,6 +4184,7 @@ bool MergeTreeData::getQueryProcessingStageWithAggregateProjection( min_sum_marks = reader.estimateNumMarksToRead( parts, analysis_result.required_columns, + storage_snapshot, metadata_snapshot, metadata_snapshot, query_info, // TODO syntax_analysis_result set in index @@ -4198,7 +4202,7 @@ bool MergeTreeData::getQueryProcessingStageWithAggregateProjection( { selectBestProjection( reader, - metadata_snapshot, + storage_snapshot, query_info, analysis_result.required_columns, candidate, @@ -4232,12 +4236,12 @@ bool MergeTreeData::getQueryProcessingStageWithAggregateProjection( QueryProcessingStage::Enum MergeTreeData::getQueryProcessingStage( ContextPtr query_context, QueryProcessingStage::Enum to_stage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info) const { if (to_stage >= QueryProcessingStage::Enum::WithMergeableState) { - if (getQueryProcessingStageWithAggregateProjection(query_context, metadata_snapshot, query_info)) + if (getQueryProcessingStageWithAggregateProjection(query_context, storage_snapshot, query_info)) { if (query_info.projection->desc->type == ProjectionDescription::Type::Aggregate) return QueryProcessingStage::Enum::WithMergeableState; @@ -5101,27 +5105,13 @@ ReservationPtr MergeTreeData::balancedReservation( return reserved_space; } -static NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list) -{ - NameSet res; - for (const auto & [name, type] : columns_list) - if (isObject(type)) - res.insert(name); - - return res; -} - -static NamesAndTypesList extendObjectColumnsImpl( - const MergeTreeData::DataPartsVector & parts, - const NamesAndTypesList & columns_list, - const NameSet & requested_to_extend, - bool with_subcolumns) +StorageSnapshot::NameToTypeMap MergeTreeData::getObjectTypes(const DataPartsVector & parts, const NameSet & object_names) { std::unordered_map types_in_parts; if (parts.empty()) { - for (const auto & name : requested_to_extend) + for (const auto & name : object_names) types_in_parts[name].push_back(std::make_shared( DataTypes{std::make_shared()}, Names{ColumnObject::COLUMN_NAME_DUMMY})); @@ -5133,57 +5123,27 @@ static NamesAndTypesList extendObjectColumnsImpl( const auto & part_columns = part->getColumns(); for (const auto & [name, type] : part_columns) { - if (requested_to_extend.count(name)) + if (object_names.count(name)) types_in_parts[name].push_back(type); } } } - NamesAndTypesList result_columns; - for (const auto & column : columns_list) - { - auto it = types_in_parts.find(column.name); - if (it != types_in_parts.end()) - { - auto expanded_type = getLeastCommonTypeForObject(it->second); - result_columns.emplace_back(column.name, expanded_type); + StorageSnapshot::NameToTypeMap object_types; + for (const auto & [name, types] : types_in_parts) + object_types.emplace(name, getLeastCommonTypeForObject(types)); - if (with_subcolumns) - { - for (const auto & subcolumn : expanded_type->getSubcolumnNames()) - { - result_columns.emplace_back(column.name, subcolumn, - expanded_type, expanded_type->getSubcolumnType(subcolumn)); - } - } - } - else - { - result_columns.push_back(column); - } - } - - return result_columns; + return object_types; } -NamesAndTypesList MergeTreeData::extendObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns) +StorageSnapshotPtr MergeTreeData::getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const { - /// Firstly fast check without locking parts, if there are any Object columns. - NameSet requested_to_extend = getNamesOfObjectColumns(columns_list); - if (requested_to_extend.empty()) - return columns_list; + auto parts = getDataPartsVector(); + auto object_types = getObjectTypes( + parts, + getNamesOfObjectColumns(metadata_snapshot->getColumns().getAll())); - return extendObjectColumnsImpl(parts, columns_list, requested_to_extend, with_subcolumns); -} - -NamesAndTypesList MergeTreeData::extendObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const -{ - /// Firstly fast check without locking parts, if there are any Object columns. - NameSet requested_to_extend = getNamesOfObjectColumns(columns_list); - if (requested_to_extend.empty()) - return columns_list; - - return extendObjectColumnsImpl(getDataPartsVector(), columns_list, requested_to_extend, with_subcolumns); + return std::make_shared(*this, metadata_snapshot, object_types, parts); } CurrentlySubmergingEmergingTagger::~CurrentlySubmergingEmergingTagger() diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 0c24e16b10e..79f1ebaa6d0 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -363,12 +363,12 @@ public: BrokenPartCallback broken_part_callback_ = [](const String &){}); bool getQueryProcessingStageWithAggregateProjection( - ContextPtr query_context, const StorageMetadataPtr & metadata_snapshot, SelectQueryInfo & query_info) const; + ContextPtr query_context, const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info) const; QueryProcessingStage::Enum getQueryProcessingStage( ContextPtr query_context, QueryProcessingStage::Enum to_stage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & info) const override; ReservationPtr reserveSpace(UInt64 expected_size, VolumePtr & volume) const; @@ -394,6 +394,8 @@ public: bool mayBenefitFromIndexForIn(const ASTPtr & left_in_operand, ContextPtr, const StorageMetadataPtr & metadata_snapshot) const override; + StorageSnapshotPtr getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const override; + /// Load the set of data parts from disk. Call once - immediately after the object is created. void loadDataParts(bool skip_sanity_checks); @@ -635,8 +637,7 @@ public: return column_sizes; } - NamesAndTypesList extendObjectColumns(const NamesAndTypesList & columns_list, bool with_subcolumns) const override; - static NamesAndTypesList extendObjectColumns(const DataPartsVector & parts, const NamesAndTypesList & columns_list, bool with_subcolumns); + static StorageSnapshot::NameToTypeMap getObjectTypes(const DataPartsVector & parts, const NameSet & object_names); /// For ATTACH/DETACH/DROP PARTITION. String getPartitionIDFromQuery(const ASTPtr & ast, ContextPtr context) const; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 45c3027c638..708b8c10c08 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -718,7 +719,11 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor Names all_column_names = metadata_snapshot->getColumns().getNamesOfPhysical(); NamesAndTypesList storage_columns = metadata_snapshot->getColumns().getAllPhysical(); - storage_columns = MergeTreeData::extendObjectColumns(parts, storage_columns, false); + + auto object_types = MergeTreeData::getObjectTypes(parts, getNamesOfObjectColumns(storage_columns)); + auto storage_snapshot = std::make_shared(data, metadata_snapshot, object_types, parts); + storage_columns = extendObjectColumns(storage_columns, object_types, false); + const auto data_settings = data.getSettings(); NamesAndTypesList gathering_columns; @@ -858,8 +863,9 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor for (const auto & part : parts) { + /// TODO: Fix auto input = std::make_unique( - data, metadata_snapshot, part, merging_column_names, read_with_direct_io, true); + data, storage_snapshot, part, merging_column_names, read_with_direct_io, true); input->setProgressCallback( MergeProgressCallback(merge_entry, watch_prev_elapsed, horizontal_stage_progress)); @@ -1061,7 +1067,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor for (size_t part_num = 0; part_num < parts.size(); ++part_num) { auto column_part_source = std::make_shared( - data, metadata_snapshot, parts[part_num], column_names, read_with_direct_io, true); + data, storage_snapshot, parts[part_num], column_names, read_with_direct_io, true); column_part_source->setProgressCallback( MergeProgressCallback(merge_entry, watch_prev_elapsed, column_progress)); diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index bd3153034b3..6c2bb157c4f 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -124,7 +124,7 @@ static RelativeSize convertAbsoluteSampleSizeToRelative(const ASTPtr & node, siz QueryPlanPtr MergeTreeDataSelectExecutor::read( const Names & column_names_to_return, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, ContextPtr context, const UInt64 max_block_size, @@ -133,12 +133,15 @@ QueryPlanPtr MergeTreeDataSelectExecutor::read( std::shared_ptr max_block_numbers_to_read) const { const auto & settings = context->getSettingsRef(); - auto parts = data.getDataPartsVector(); + const auto & parts = storage_snapshot->parts; + const auto & metadata_snapshot = storage_snapshot->metadata; + if (!query_info.projection) { auto plan = readFromParts( parts, column_names_to_return, + storage_snapshot, metadata_snapshot, metadata_snapshot, query_info, @@ -185,6 +188,7 @@ QueryPlanPtr MergeTreeDataSelectExecutor::read( auto plan = readFromParts( projection_parts, query_info.projection->required_columns, + storage_snapshot, metadata_snapshot, query_info.projection->desc->metadata, query_info, @@ -1086,6 +1090,7 @@ static void selectColumnNames( size_t MergeTreeDataSelectExecutor::estimateNumMarksToRead( MergeTreeData::DataPartsVector parts, const Names & column_names_to_return, + const StorageSnapshotPtr & storage_snapshot, const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, @@ -1116,7 +1121,7 @@ size_t MergeTreeDataSelectExecutor::estimateNumMarksToRead( real_column_names.push_back(ExpressionActions::getSmallestColumn(available_real_columns)); } - data.check(metadata_snapshot, real_column_names); + storage_snapshot->check(real_column_names); const auto & primary_key = metadata_snapshot->getPrimaryKey(); Names primary_key_columns = primary_key.column_names; @@ -1166,6 +1171,7 @@ size_t MergeTreeDataSelectExecutor::estimateNumMarksToRead( QueryPlanPtr MergeTreeDataSelectExecutor::readFromParts( MergeTreeData::DataPartsVector parts, const Names & column_names_to_return, + const StorageSnapshotPtr & storage_snapshot, const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, @@ -1192,6 +1198,7 @@ QueryPlanPtr MergeTreeDataSelectExecutor::readFromParts( virt_column_names, data, query_info, + storage_snapshot, metadata_snapshot, metadata_snapshot_base, context, diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h index bd2a79f0aee..88443c0febc 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h @@ -37,7 +37,7 @@ public: QueryPlanPtr read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, ContextPtr context, UInt64 max_block_size, @@ -49,6 +49,7 @@ public: QueryPlanPtr readFromParts( MergeTreeData::DataPartsVector parts, const Names & column_names, + const StorageSnapshotPtr & storage_snapshot, const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, @@ -63,7 +64,8 @@ public: size_t estimateNumMarksToRead( MergeTreeData::DataPartsVector parts, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot_base, + const StorageSnapshotPtr & storage_snapshot, + const StorageMetadataPtr & storage_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, ContextPtr context, diff --git a/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/src/Storages/MergeTree/MergeTreeDataWriter.cpp index a36d3ab144d..f6c5fa40580 100644 --- a/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -273,7 +273,8 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart( { Block & block = block_with_partition.block; auto columns = metadata_snapshot->getColumns().getAllPhysical().filter(block.getNames()); - auto extended_storage_columns = data.getColumns(metadata_snapshot, + auto storage_snapshot = data.getStorageSnapshot(metadata_snapshot); + auto extended_storage_columns = storage_snapshot->getColumns( GetColumnsOptions(GetColumnsOptions::AllPhysical).withExtendedObjects()); convertObjectsToTuples(columns, block, extended_storage_columns); diff --git a/src/Storages/MergeTree/MergeTreeReadPool.cpp b/src/Storages/MergeTree/MergeTreeReadPool.cpp index 2c525f506ce..1e26d6abe1b 100644 --- a/src/Storages/MergeTree/MergeTreeReadPool.cpp +++ b/src/Storages/MergeTree/MergeTreeReadPool.cpp @@ -23,7 +23,7 @@ MergeTreeReadPool::MergeTreeReadPool( const size_t min_marks_for_concurrent_read_, RangesInDataParts && parts_, const MergeTreeData & data_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const PrewhereInfoPtr & prewhere_info_, const bool check_columns_, const Names & column_names_, @@ -33,7 +33,7 @@ MergeTreeReadPool::MergeTreeReadPool( : backoff_settings{backoff_settings_} , backoff_state{threads_} , data{data_} - , metadata_snapshot{metadata_snapshot_} + , storage_snapshot{storage_snapshot_} , column_names{column_names_} , do_not_steal_tasks{do_not_steal_tasks_} , predict_block_size_bytes{preferred_block_size_bytes_ > 0} @@ -168,7 +168,7 @@ MarkRanges MergeTreeReadPool::getRestMarks(const IMergeTreeDataPart & part, cons Block MergeTreeReadPool::getHeader() const { - return data.getSampleBlockForColumns(metadata_snapshot, column_names); + return storage_snapshot->getSampleBlockForColumns(column_names); } void MergeTreeReadPool::profileFeedback(const ReadBufferFromFileBase::ProfileInfo info) @@ -215,7 +215,7 @@ std::vector MergeTreeReadPool::fillPerPartInfo( const RangesInDataParts & parts, const bool check_columns) { std::vector per_part_sum_marks; - Block sample_block = metadata_snapshot->getSampleBlock(); + Block sample_block = storage_snapshot->metadata->getSampleBlock(); for (const auto i : collections::range(0, parts.size())) { @@ -229,7 +229,7 @@ std::vector MergeTreeReadPool::fillPerPartInfo( per_part_sum_marks.push_back(sum_marks); auto [required_columns, required_pre_columns, should_reorder] = - getReadTaskColumns(data, metadata_snapshot, part.data_part, column_names, prewhere_info, check_columns); + getReadTaskColumns(data, storage_snapshot, part.data_part, column_names, prewhere_info, check_columns); /// will be used to distinguish between PREWHERE and WHERE columns when applying filter const auto & required_column_names = required_columns.getNames(); diff --git a/src/Storages/MergeTree/MergeTreeReadPool.h b/src/Storages/MergeTree/MergeTreeReadPool.h index 9949bdf86f8..8fefcdeffaf 100644 --- a/src/Storages/MergeTree/MergeTreeReadPool.h +++ b/src/Storages/MergeTree/MergeTreeReadPool.h @@ -71,7 +71,7 @@ private: public: MergeTreeReadPool( const size_t threads_, const size_t sum_marks_, const size_t min_marks_for_concurrent_read_, - RangesInDataParts && parts_, const MergeTreeData & data_, const StorageMetadataPtr & metadata_snapshot_, + RangesInDataParts && parts_, const MergeTreeData & data_, const StorageSnapshotPtr & storage_snapshot_, const PrewhereInfoPtr & prewhere_info_, const bool check_columns_, const Names & column_names_, const BackoffSettings & backoff_settings_, size_t preferred_block_size_bytes_, @@ -99,7 +99,7 @@ private: const RangesInDataParts & parts, const size_t min_marks_for_concurrent_read); const MergeTreeData & data; - StorageMetadataPtr metadata_snapshot; + StorageSnapshotPtr storage_snapshot; const Names column_names; bool do_not_steal_tasks; bool predict_block_size_bytes; diff --git a/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp b/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp index b4abf7de222..b537c1286e1 100644 --- a/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.cpp @@ -14,7 +14,7 @@ namespace ErrorCodes MergeTreeReverseSelectProcessor::MergeTreeReverseSelectProcessor( const MergeTreeData & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const MergeTreeData::DataPartPtr & owned_data_part_, UInt64 max_block_size_rows_, size_t preferred_block_size_bytes_, @@ -31,8 +31,8 @@ MergeTreeReverseSelectProcessor::MergeTreeReverseSelectProcessor( bool quiet) : MergeTreeBaseSelectProcessor{ - storage_.getSampleBlockForColumns(metadata_snapshot_, required_columns_), - storage_, metadata_snapshot_, prewhere_info_, std::move(actions_settings), max_block_size_rows_, + storage_snapshot_->getSampleBlockForColumns(required_columns_), + storage_, storage_snapshot_, prewhere_info_, std::move(actions_settings), max_block_size_rows_, preferred_block_size_bytes_, preferred_max_column_in_block_size_bytes_, reader_settings_, use_uncompressed_cache_, virt_column_names_}, required_columns{std::move(required_columns_)}, @@ -56,7 +56,7 @@ MergeTreeReverseSelectProcessor::MergeTreeReverseSelectProcessor( ordered_names = header_without_virtual_columns.getNames(); - task_columns = getReadTaskColumns(storage, metadata_snapshot, data_part, required_columns, prewhere_info, check_columns); + task_columns = getReadTaskColumns(storage, storage_snapshot_, data_part, required_columns, prewhere_info, check_columns); /// will be used to distinguish between PREWHERE and WHERE columns when applying filter const auto & column_names = task_columns.columns.getNames(); @@ -67,12 +67,12 @@ MergeTreeReverseSelectProcessor::MergeTreeReverseSelectProcessor( owned_mark_cache = storage.getContext()->getMarkCache(); - reader = data_part->getReader(task_columns.columns, metadata_snapshot, + reader = data_part->getReader(task_columns.columns, storage_snapshot_->metadata, all_mark_ranges, owned_uncompressed_cache.get(), owned_mark_cache.get(), reader_settings); if (prewhere_info) - pre_reader = data_part->getReader(task_columns.pre_columns, metadata_snapshot, all_mark_ranges, + pre_reader = data_part->getReader(task_columns.pre_columns, storage_snapshot_->metadata, all_mark_ranges, owned_uncompressed_cache.get(), owned_mark_cache.get(), reader_settings); } @@ -96,7 +96,7 @@ try auto size_predictor = (preferred_block_size_bytes == 0) ? nullptr - : std::make_unique(data_part, ordered_names, metadata_snapshot->getSampleBlock()); + : std::make_unique(data_part, ordered_names, storage_snapshot->metadata->getSampleBlock()); task = std::make_unique( data_part, mark_ranges_for_task, part_index_in_query, ordered_names, column_name_set, diff --git a/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.h b/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.h index b807c2d912c..53138450629 100644 --- a/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.h +++ b/src/Storages/MergeTree/MergeTreeReverseSelectProcessor.h @@ -18,7 +18,7 @@ class MergeTreeReverseSelectProcessor : public MergeTreeBaseSelectProcessor public: MergeTreeReverseSelectProcessor( const MergeTreeData & storage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot_, const MergeTreeData::DataPartPtr & owned_data_part, UInt64 max_block_size_rows, size_t preferred_block_size_bytes, diff --git a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp index b663ab5220f..09b863d3808 100644 --- a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp @@ -14,7 +14,7 @@ namespace ErrorCodes MergeTreeSelectProcessor::MergeTreeSelectProcessor( const MergeTreeData & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const MergeTreeData::DataPartPtr & owned_data_part_, UInt64 max_block_size_rows_, size_t preferred_block_size_bytes_, @@ -31,8 +31,8 @@ MergeTreeSelectProcessor::MergeTreeSelectProcessor( bool quiet) : MergeTreeBaseSelectProcessor{ - storage_.getSampleBlockForColumns(metadata_snapshot_, required_columns_), - storage_, metadata_snapshot_, prewhere_info_, std::move(actions_settings), max_block_size_rows_, + storage_snapshot_->getSampleBlockForColumns(required_columns_), + storage_, storage_snapshot_, prewhere_info_, std::move(actions_settings), max_block_size_rows_, preferred_block_size_bytes_, preferred_max_column_in_block_size_bytes_, reader_settings_, use_uncompressed_cache_, virt_column_names_}, required_columns{std::move(required_columns_)}, @@ -69,9 +69,10 @@ try is_first_task = false; task_columns = getReadTaskColumns( - storage, metadata_snapshot, data_part, + storage, storage_snapshot, data_part, required_columns, prewhere_info, check_columns); + const auto & metadata_snapshot = storage_snapshot->metadata; auto size_predictor = (preferred_block_size_bytes == 0) ? nullptr : std::make_unique(data_part, ordered_names, metadata_snapshot->getSampleBlock()); diff --git a/src/Storages/MergeTree/MergeTreeSelectProcessor.h b/src/Storages/MergeTree/MergeTreeSelectProcessor.h index b63107b6dbf..15df4d067fa 100644 --- a/src/Storages/MergeTree/MergeTreeSelectProcessor.h +++ b/src/Storages/MergeTree/MergeTreeSelectProcessor.h @@ -18,7 +18,7 @@ class MergeTreeSelectProcessor : public MergeTreeBaseSelectProcessor public: MergeTreeSelectProcessor( const MergeTreeData & storage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot_, const MergeTreeData::DataPartPtr & owned_data_part, UInt64 max_block_size_rows, size_t preferred_block_size_bytes, diff --git a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp index 9d27adac6d2..e2c70d191d3 100644 --- a/src/Storages/MergeTree/MergeTreeSequentialSource.cpp +++ b/src/Storages/MergeTree/MergeTreeSequentialSource.cpp @@ -11,15 +11,15 @@ namespace ErrorCodes MergeTreeSequentialSource::MergeTreeSequentialSource( const MergeTreeData & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, MergeTreeData::DataPartPtr data_part_, Names columns_to_read_, bool read_with_direct_io_, bool take_column_types_from_storage, bool quiet) - : SourceWithProgress(storage_.getSampleBlockForColumns(metadata_snapshot_, columns_to_read_)) + : SourceWithProgress(storage_snapshot_->getSampleBlockForColumns(columns_to_read_)) , storage(storage_) - , metadata_snapshot(metadata_snapshot_) + , storage_snapshot(storage_snapshot_) , data_part(std::move(data_part_)) , columns_to_read(std::move(columns_to_read_)) , read_with_direct_io(read_with_direct_io_) @@ -39,12 +39,13 @@ MergeTreeSequentialSource::MergeTreeSequentialSource( addTotalRowsApprox(data_part->rows_count); /// Add columns because we don't want to read empty blocks - injectRequiredColumns(storage, metadata_snapshot, data_part, columns_to_read); + injectRequiredColumns(storage, storage_snapshot->metadata, data_part, columns_to_read); NamesAndTypesList columns_for_reader; if (take_column_types_from_storage) { - auto physical_columns = storage.getColumns(metadata_snapshot, + auto physical_columns = storage_snapshot->getColumns( GetColumnsOptions(GetColumnsOptions::AllPhysical).withExtendedObjects()); + columns_for_reader = physical_columns.addTypes(columns_to_read); } else @@ -61,7 +62,7 @@ MergeTreeSequentialSource::MergeTreeSequentialSource( .save_marks_in_cache = false }; - reader = data_part->getReader(columns_for_reader, metadata_snapshot, + reader = data_part->getReader(columns_for_reader, storage_snapshot->metadata, MarkRanges{MarkRange(0, data_part->getMarksCount())}, /* uncompressed_cache = */ nullptr, mark_cache.get(), reader_settings); } diff --git a/src/Storages/MergeTree/MergeTreeSequentialSource.h b/src/Storages/MergeTree/MergeTreeSequentialSource.h index 7eefdd9335b..8e2b3b81c7c 100644 --- a/src/Storages/MergeTree/MergeTreeSequentialSource.h +++ b/src/Storages/MergeTree/MergeTreeSequentialSource.h @@ -14,7 +14,7 @@ class MergeTreeSequentialSource : public SourceWithProgress public: MergeTreeSequentialSource( const MergeTreeData & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, MergeTreeData::DataPartPtr data_part_, Names columns_to_read_, bool read_with_direct_io_, @@ -35,7 +35,7 @@ protected: private: const MergeTreeData & storage; - StorageMetadataPtr metadata_snapshot; + StorageSnapshotPtr storage_snapshot; /// Data part will not be removed if the pointer owns it MergeTreeData::DataPartPtr data_part; diff --git a/src/Storages/MergeTree/MergeTreeThreadSelectBlockInputProcessor.cpp b/src/Storages/MergeTree/MergeTreeThreadSelectBlockInputProcessor.cpp index daefb17038a..af01a22f80d 100644 --- a/src/Storages/MergeTree/MergeTreeThreadSelectBlockInputProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeThreadSelectBlockInputProcessor.cpp @@ -16,7 +16,7 @@ MergeTreeThreadSelectBlockInputProcessor::MergeTreeThreadSelectBlockInputProcess size_t preferred_block_size_bytes_, size_t preferred_max_column_in_block_size_bytes_, const MergeTreeData & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const bool use_uncompressed_cache_, const PrewhereInfoPtr & prewhere_info_, ExpressionActionsSettings actions_settings, @@ -24,7 +24,7 @@ MergeTreeThreadSelectBlockInputProcessor::MergeTreeThreadSelectBlockInputProcess const Names & virt_column_names_) : MergeTreeBaseSelectProcessor{ - pool_->getHeader(), storage_, metadata_snapshot_, prewhere_info_, std::move(actions_settings), max_block_size_rows_, + pool_->getHeader(), storage_, storage_snapshot_, prewhere_info_, std::move(actions_settings), max_block_size_rows_, preferred_block_size_bytes_, preferred_max_column_in_block_size_bytes_, reader_settings_, use_uncompressed_cache_, virt_column_names_}, thread{thread_}, @@ -65,6 +65,7 @@ bool MergeTreeThreadSelectBlockInputProcessor::getNewTask() /// Allows pool to reduce number of threads in case of too slow reads. auto profile_callback = [this](ReadBufferFromFileBase::ProfileInfo info_) { pool->profileFeedback(info_); }; + const auto & metadata_snapshot = storage_snapshot->metadata; if (!reader) { diff --git a/src/Storages/MergeTree/MergeTreeThreadSelectBlockInputProcessor.h b/src/Storages/MergeTree/MergeTreeThreadSelectBlockInputProcessor.h index 30c551eede0..38bd544ca22 100644 --- a/src/Storages/MergeTree/MergeTreeThreadSelectBlockInputProcessor.h +++ b/src/Storages/MergeTree/MergeTreeThreadSelectBlockInputProcessor.h @@ -22,7 +22,7 @@ public: size_t preferred_block_size_bytes_, size_t preferred_max_column_in_block_size_bytes_, const MergeTreeData & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const bool use_uncompressed_cache_, const PrewhereInfoPtr & prewhere_info_, ExpressionActionsSettings actions_settings, diff --git a/src/Storages/MergeTree/StorageFromBasePartsOfProjection.h b/src/Storages/MergeTree/StorageFromBasePartsOfProjection.h index 5d82716af11..9664af21541 100644 --- a/src/Storages/MergeTree/StorageFromBasePartsOfProjection.h +++ b/src/Storages/MergeTree/StorageFromBasePartsOfProjection.h @@ -24,7 +24,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, @@ -35,6 +35,7 @@ public: QueryPlan query_plan = std::move(*MergeTreeDataSelectExecutor(storage).readFromParts( {}, column_names, + storage_snapshot, metadata_snapshot, metadata_snapshot, query_info, diff --git a/src/Storages/MergeTree/StorageFromMergeTreeDataPart.h b/src/Storages/MergeTree/StorageFromMergeTreeDataPart.h index 9cc2787697d..c14964c9e23 100644 --- a/src/Storages/MergeTree/StorageFromMergeTreeDataPart.h +++ b/src/Storages/MergeTree/StorageFromMergeTreeDataPart.h @@ -24,7 +24,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, @@ -36,8 +36,9 @@ public: .readFromParts( parts, column_names, - metadata_snapshot, - metadata_snapshot, + storage_snapshot, + storage_snapshot->metadata, + storage_snapshot->metadata, query_info, context, max_block_size, diff --git a/src/Storages/PostgreSQL/StorageMaterializedPostgreSQL.cpp b/src/Storages/PostgreSQL/StorageMaterializedPostgreSQL.cpp index 70251a940cc..341d654216e 100644 --- a/src/Storages/PostgreSQL/StorageMaterializedPostgreSQL.cpp +++ b/src/Storages/PostgreSQL/StorageMaterializedPostgreSQL.cpp @@ -258,7 +258,7 @@ NamesAndTypesList StorageMaterializedPostgreSQL::getVirtuals() const Pipe StorageMaterializedPostgreSQL::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & query_info, ContextPtr context_, QueryProcessingStage::Enum processed_stage, @@ -267,7 +267,7 @@ Pipe StorageMaterializedPostgreSQL::read( { auto materialized_table_lock = lockForShare(String(), context_->getSettingsRef().lock_acquire_timeout); auto nested_table = getNested(); - return readFinalFromNestedStorage(nested_table, column_names, metadata_snapshot, + return readFinalFromNestedStorage(nested_table, column_names, query_info, context_, processed_stage, max_block_size, num_streams); } diff --git a/src/Storages/PostgreSQL/StorageMaterializedPostgreSQL.h b/src/Storages/PostgreSQL/StorageMaterializedPostgreSQL.h index 5d18a0b16b7..051de71ed5e 100644 --- a/src/Storages/PostgreSQL/StorageMaterializedPostgreSQL.h +++ b/src/Storages/PostgreSQL/StorageMaterializedPostgreSQL.h @@ -84,7 +84,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context_, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/RabbitMQ/RabbitMQBlockInputStream.cpp b/src/Storages/RabbitMQ/RabbitMQBlockInputStream.cpp index ec12f84c341..5657fc4c041 100644 --- a/src/Storages/RabbitMQ/RabbitMQBlockInputStream.cpp +++ b/src/Storages/RabbitMQ/RabbitMQBlockInputStream.cpp @@ -15,20 +15,20 @@ namespace DB RabbitMQBlockInputStream::RabbitMQBlockInputStream( StorageRabbitMQ & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, ContextPtr context_, const Names & columns, size_t max_block_size_, bool ack_in_suffix_) : storage(storage_) - , metadata_snapshot(metadata_snapshot_) + , storage_snapshot(storage_snapshot_) , context(context_) , column_names(columns) , max_block_size(max_block_size_) , ack_in_suffix(ack_in_suffix_) - , non_virtual_header(metadata_snapshot->getSampleBlockNonMaterialized()) + , non_virtual_header(storage_snapshot->metadata->getSampleBlockNonMaterialized()) , sample_block(non_virtual_header) - , virtual_header(storage.getSampleBlockForColumns(metadata_snapshot, + , virtual_header(storage_snapshot->getSampleBlockForColumns( {"_exchange_name", "_channel_id", "_delivery_tag", "_redelivered", "_message_id", "_timestamp"})) { for (const auto & column : virtual_header) diff --git a/src/Storages/RabbitMQ/RabbitMQBlockInputStream.h b/src/Storages/RabbitMQ/RabbitMQBlockInputStream.h index 5ce1c96bf33..e6a7d16572f 100644 --- a/src/Storages/RabbitMQ/RabbitMQBlockInputStream.h +++ b/src/Storages/RabbitMQ/RabbitMQBlockInputStream.h @@ -14,7 +14,7 @@ class RabbitMQBlockInputStream : public IBlockInputStream public: RabbitMQBlockInputStream( StorageRabbitMQ & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, ContextPtr context_, const Names & columns, size_t max_block_size_, @@ -37,7 +37,7 @@ public: private: StorageRabbitMQ & storage; - StorageMetadataPtr metadata_snapshot; + StorageSnapshotPtr storage_snapshot; ContextPtr context; Names column_names; const size_t max_block_size; diff --git a/src/Storages/RabbitMQ/StorageRabbitMQ.cpp b/src/Storages/RabbitMQ/StorageRabbitMQ.cpp index 293edb0711a..b6263f398c6 100644 --- a/src/Storages/RabbitMQ/StorageRabbitMQ.cpp +++ b/src/Storages/RabbitMQ/StorageRabbitMQ.cpp @@ -595,7 +595,7 @@ void StorageRabbitMQ::unbindExchange() Pipe StorageRabbitMQ::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /* query_info */, ContextPtr local_context, QueryProcessingStage::Enum /* processed_stage */, @@ -608,7 +608,7 @@ Pipe StorageRabbitMQ::read( if (num_created_consumers == 0) return {}; - auto sample_block = getSampleBlockForColumns(metadata_snapshot, column_names); + auto sample_block = storage_snapshot->getSampleBlockForColumns(column_names); auto modified_context = addSettings(local_context); auto block_size = getMaxBlockSize(); @@ -625,7 +625,7 @@ Pipe StorageRabbitMQ::read( for (size_t i = 0; i < num_created_consumers; ++i) { auto rabbit_stream = std::make_shared( - *this, metadata_snapshot, modified_context, column_names, block_size); + *this, storage_snapshot, modified_context, column_names, block_size); auto converting_stream = std::make_shared( rabbit_stream, sample_block, ConvertingBlockInputStream::MatchColumnsMode::Name); @@ -903,9 +903,9 @@ bool StorageRabbitMQ::streamToViews() InterpreterInsertQuery interpreter(insert, rabbitmq_context, false, true, true); auto block_io = interpreter.execute(); - auto metadata_snapshot = getInMemoryMetadataPtr(); + auto storage_snapshot = getStorageSnapshot(getInMemoryMetadataPtr()); auto column_names = block_io.out->getHeader().getNames(); - auto sample_block = getSampleBlockForColumns(metadata_snapshot, column_names); + auto sample_block = storage_snapshot->getSampleBlockForColumns(column_names); auto block_size = getMaxBlockSize(); @@ -916,7 +916,7 @@ bool StorageRabbitMQ::streamToViews() for (size_t i = 0; i < num_created_consumers; ++i) { auto stream = std::make_shared( - *this, metadata_snapshot, rabbitmq_context, column_names, block_size, false); + *this, storage_snapshot, rabbitmq_context, column_names, block_size, false); streams.emplace_back(stream); // Limit read batch to maximum block size to allow DDL diff --git a/src/Storages/RabbitMQ/StorageRabbitMQ.h b/src/Storages/RabbitMQ/StorageRabbitMQ.h index 1935dcaee0e..09dc46d004b 100644 --- a/src/Storages/RabbitMQ/StorageRabbitMQ.h +++ b/src/Storages/RabbitMQ/StorageRabbitMQ.h @@ -43,7 +43,7 @@ public: /// Always return virtual columns in addition to required columns Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/ReadFinalForExternalReplicaStorage.cpp b/src/Storages/ReadFinalForExternalReplicaStorage.cpp index fb96bb01936..9a66bb25630 100644 --- a/src/Storages/ReadFinalForExternalReplicaStorage.cpp +++ b/src/Storages/ReadFinalForExternalReplicaStorage.cpp @@ -19,7 +19,6 @@ namespace DB Pipe readFinalFromNestedStorage( StoragePtr nested_storage, const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -28,7 +27,7 @@ Pipe readFinalFromNestedStorage( { NameSet column_names_set = NameSet(column_names.begin(), column_names.end()); auto lock = nested_storage->lockForShare(context->getCurrentQueryId(), context->getSettingsRef().lock_acquire_timeout); - const StorageMetadataPtr & nested_metadata = nested_storage->getInMemoryMetadataPtr(); + const auto & nested_metadata = nested_storage->getInMemoryMetadataPtr(); Block nested_header = nested_metadata->getSampleBlock(); ColumnWithTypeAndName & sign_column = nested_header.getByPosition(nested_header.columns() - 2); @@ -64,7 +63,8 @@ Pipe readFinalFromNestedStorage( expressions->children.emplace_back(std::make_shared(column_name)); } - Pipe pipe = nested_storage->read(require_columns_name, nested_metadata, query_info, context, processed_stage, max_block_size, num_streams); + auto nested_snapshot = nested_storage->getStorageSnapshot(nested_metadata); + Pipe pipe = nested_storage->read(require_columns_name, nested_snapshot, query_info, context, processed_stage, max_block_size, num_streams); pipe.addTableLock(lock); if (!expressions->children.empty() && !pipe.empty()) diff --git a/src/Storages/ReadFinalForExternalReplicaStorage.h b/src/Storages/ReadFinalForExternalReplicaStorage.h index b54592159ef..0870149c719 100644 --- a/src/Storages/ReadFinalForExternalReplicaStorage.h +++ b/src/Storages/ReadFinalForExternalReplicaStorage.h @@ -16,7 +16,6 @@ namespace DB Pipe readFinalFromNestedStorage( StoragePtr nested_storage, const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp index c29f74893ef..808d80d25dd 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.cpp @@ -285,24 +285,24 @@ void StorageEmbeddedRocksDB::initDb() Pipe StorageEmbeddedRocksDB::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, size_t max_block_size, unsigned num_streams) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); FieldVectorPtr keys; bool all_scan = false; - auto primary_key_data_type = metadata_snapshot->getSampleBlock().getByName(primary_key).type; + auto primary_key_data_type = storage_snapshot->metadata->getSampleBlock().getByName(primary_key).type; std::tie(keys, all_scan) = getFilterKeys(primary_key, primary_key_data_type, query_info); if (all_scan) { auto reader = std::make_shared( - *this, metadata_snapshot, max_block_size); + *this, storage_snapshot->metadata, max_block_size); return Pipe(std::make_shared(reader)); } else @@ -327,7 +327,7 @@ Pipe StorageEmbeddedRocksDB::read( size_t end = num_keys * (thread_idx + 1) / num_threads; pipes.emplace_back(std::make_shared( - *this, metadata_snapshot, keys, keys->begin() + begin, keys->begin() + end, max_block_size)); + *this, storage_snapshot->metadata, keys, keys->begin() + begin, keys->begin() + end, max_block_size)); } return Pipe::unitePipes(std::move(pipes)); } diff --git a/src/Storages/RocksDB/StorageEmbeddedRocksDB.h b/src/Storages/RocksDB/StorageEmbeddedRocksDB.h index aa81bc4d35f..ff6ffad3b31 100644 --- a/src/Storages/RocksDB/StorageEmbeddedRocksDB.h +++ b/src/Storages/RocksDB/StorageEmbeddedRocksDB.h @@ -27,7 +27,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageBuffer.cpp b/src/Storages/StorageBuffer.cpp index a63eef282d3..42f2d4dd80e 100644 --- a/src/Storages/StorageBuffer.cpp +++ b/src/Storages/StorageBuffer.cpp @@ -134,9 +134,9 @@ StorageBuffer::StorageBuffer( class BufferSource : public SourceWithProgress { public: - BufferSource(const Names & column_names_, StorageBuffer::Buffer & buffer_, const StorageBuffer & storage, const StorageMetadataPtr & metadata_snapshot) - : SourceWithProgress(storage.getSampleBlockForColumns(metadata_snapshot, column_names_)) - , column_names_and_types(metadata_snapshot->getColumns().getAllWithSubcolumns().addTypes(column_names_)) + BufferSource(const Names & column_names_, StorageBuffer::Buffer & buffer_, const StorageSnapshotPtr & storage_snapshot) + : SourceWithProgress(storage_snapshot->getSampleBlockForColumns(column_names_)) + , column_names_and_types(storage_snapshot->metadata->getColumns().getAllWithSubcolumns().addTypes(column_names_)) , buffer(buffer_) {} String getName() const override { return "Buffer"; } @@ -183,7 +183,7 @@ private: QueryProcessingStage::Enum StorageBuffer::getQueryProcessingStage( ContextPtr local_context, QueryProcessingStage::Enum to_stage, - const StorageMetadataPtr &, + const StorageSnapshotPtr &, SelectQueryInfo & query_info) const { if (destination_id) @@ -193,7 +193,8 @@ QueryProcessingStage::Enum StorageBuffer::getQueryProcessingStage( if (destination.get() == this) throw Exception("Destination table is myself. Read will cause infinite loop.", ErrorCodes::INFINITE_LOOP); - return destination->getQueryProcessingStage(local_context, to_stage, destination->getInMemoryMetadataPtr(), query_info); + const auto & destination_metadata = destination->getInMemoryMetadataPtr(); + return destination->getQueryProcessingStage(local_context, to_stage, destination->getStorageSnapshot(destination_metadata), query_info); } return QueryProcessingStage::FetchColumns; @@ -202,7 +203,7 @@ QueryProcessingStage::Enum StorageBuffer::getQueryProcessingStage( Pipe StorageBuffer::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -210,7 +211,7 @@ Pipe StorageBuffer::read( const unsigned num_streams) { QueryPlan plan; - read(plan, column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); + read(plan, column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); return plan.convertToPipe( QueryPlanOptimizationSettings::fromContext(local_context), BuildQueryPipelineSettings::fromContext(local_context)); @@ -219,13 +220,15 @@ Pipe StorageBuffer::read( void StorageBuffer::read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, size_t max_block_size, unsigned num_streams) { + const auto & metadata_snapshot = storage_snapshot->metadata; + if (destination_id) { auto destination = DatabaseCatalog::instance().getTable(destination_id, local_context); @@ -236,6 +239,7 @@ void StorageBuffer::read( auto destination_lock = destination->lockForShare(local_context->getCurrentQueryId(), local_context->getSettingsRef().lock_acquire_timeout); auto destination_metadata_snapshot = destination->getInMemoryMetadataPtr(); + auto destination_snapshot = destination->getStorageSnapshot(destination_metadata_snapshot); const bool dst_has_same_structure = std::all_of(column_names.begin(), column_names.end(), [metadata_snapshot, destination_metadata_snapshot](const String& column_name) { @@ -252,7 +256,7 @@ void StorageBuffer::read( /// The destination table has the same structure of the requested columns and we can simply read blocks from there. destination->read( - query_plan, column_names, destination_metadata_snapshot, query_info, + query_plan, column_names, destination_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); } else @@ -287,7 +291,7 @@ void StorageBuffer::read( else { destination->read( - query_plan, columns_intersection, destination_metadata_snapshot, query_info, + query_plan, columns_intersection, destination_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); if (query_plan.isInitialized()) @@ -344,7 +348,7 @@ void StorageBuffer::read( Pipes pipes_from_buffers; pipes_from_buffers.reserve(num_shards); for (auto & buf : buffers) - pipes_from_buffers.emplace_back(std::make_shared(column_names, buf, *this, metadata_snapshot)); + pipes_from_buffers.emplace_back(std::make_shared(column_names, buf, storage_snapshot)); pipe_from_buffers = Pipe::unitePipes(std::move(pipes_from_buffers)); } diff --git a/src/Storages/StorageBuffer.h b/src/Storages/StorageBuffer.h index b4fcdf6cbc9..884efe68d9d 100644 --- a/src/Storages/StorageBuffer.h +++ b/src/Storages/StorageBuffer.h @@ -59,11 +59,11 @@ public: std::string getName() const override { return "Buffer"; } QueryProcessingStage::Enum - getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageMetadataPtr &, SelectQueryInfo &) const override; + getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageSnapshotPtr &, SelectQueryInfo &) const override; Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -73,7 +73,7 @@ public: void read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageDictionary.cpp b/src/Storages/StorageDictionary.cpp index 4c31f62b21f..e28964eeeaa 100644 --- a/src/Storages/StorageDictionary.cpp +++ b/src/Storages/StorageDictionary.cpp @@ -160,7 +160,7 @@ void StorageDictionary::checkTableCanBeDetached() const Pipe StorageDictionary::read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & /*query_info*/, ContextPtr local_context, QueryProcessingStage::Enum /*processed_stage*/, diff --git a/src/Storages/StorageDictionary.h b/src/Storages/StorageDictionary.h index d074dec2c34..1f0b337222f 100644 --- a/src/Storages/StorageDictionary.h +++ b/src/Storages/StorageDictionary.h @@ -26,7 +26,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageDistributed.cpp b/src/Storages/StorageDistributed.cpp index f4d6ec5c6f7..c1983993d96 100644 --- a/src/Storages/StorageDistributed.cpp +++ b/src/Storages/StorageDistributed.cpp @@ -276,7 +276,7 @@ void replaceConstantExpressions( ConstStoragePtr storage, const StorageMetadataPtr & metadata_snapshot) { - auto syntax_result = TreeRewriter(context).analyze(node, columns, storage, metadata_snapshot); + auto syntax_result = TreeRewriter(context).analyze(node, columns, storage, storage->getStorageSnapshot(metadata_snapshot)); Block block_with_constants = KeyCondition::getBlockWithConstants(node, syntax_result, context); InDepthNodeVisitor visitor(block_with_constants); @@ -485,7 +485,7 @@ StorageDistributed::StorageDistributed( QueryProcessingStage::Enum StorageDistributed::getQueryProcessingStage( ContextPtr local_context, QueryProcessingStage::Enum to_stage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info) const { const auto & settings = local_context->getSettingsRef(); @@ -497,7 +497,7 @@ QueryProcessingStage::Enum StorageDistributed::getQueryProcessingStage( /// (Anyway it will be calculated in the read()) if (getClusterQueriedNodes(settings, cluster) > 1 && settings.optimize_skip_unused_shards) { - ClusterPtr optimized_cluster = getOptimizedCluster(local_context, metadata_snapshot, query_info.query); + ClusterPtr optimized_cluster = getOptimizedCluster(local_context, storage_snapshot->metadata, query_info.query); if (optimized_cluster) { LOG_DEBUG(log, "Skipping irrelevant shards - the query will be sent to the following shards of the cluster (shard numbers): {}", @@ -561,7 +561,7 @@ QueryProcessingStage::Enum StorageDistributed::getQueryProcessingStage( Pipe StorageDistributed::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -569,7 +569,7 @@ Pipe StorageDistributed::read( const unsigned num_streams) { QueryPlan plan; - read(plan, column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); + read(plan, column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); return plan.convertToPipe( QueryPlanOptimizationSettings::fromContext(local_context), BuildQueryPipelineSettings::fromContext(local_context)); @@ -578,7 +578,7 @@ Pipe StorageDistributed::read( void StorageDistributed::read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -605,7 +605,7 @@ void StorageDistributed::read( const Scalars & scalars = local_context->hasQueryContext() ? local_context->getQueryContext()->getScalars() : Scalars{}; bool has_virtual_shard_num_column = std::find(column_names.begin(), column_names.end(), "_shard_num") != column_names.end(); - if (has_virtual_shard_num_column && !isVirtualColumn("_shard_num", metadata_snapshot)) + if (has_virtual_shard_num_column && !isVirtualColumn("_shard_num", storage_snapshot->metadata)) has_virtual_shard_num_column = false; ClusterProxy::SelectStreamFactory select_stream_factory = remote_table_function_ptr diff --git a/src/Storages/StorageDistributed.h b/src/Storages/StorageDistributed.h index c63abbc6aa4..6cd1115e9a9 100644 --- a/src/Storages/StorageDistributed.h +++ b/src/Storages/StorageDistributed.h @@ -57,11 +57,11 @@ public: bool isRemote() const override { return true; } QueryProcessingStage::Enum - getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageMetadataPtr &, SelectQueryInfo &) const override; + getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageSnapshotPtr &, SelectQueryInfo &) const override; Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -71,7 +71,7 @@ public: void read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageExternalDistributed.cpp b/src/Storages/StorageExternalDistributed.cpp index 32b9c7e9245..12bd809ac8f 100644 --- a/src/Storages/StorageExternalDistributed.cpp +++ b/src/Storages/StorageExternalDistributed.cpp @@ -177,7 +177,7 @@ StorageExternalDistributed::StorageExternalDistributed( Pipe StorageExternalDistributed::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -189,7 +189,7 @@ Pipe StorageExternalDistributed::read( { pipes.emplace_back(shard->read( column_names, - metadata_snapshot, + storage_snapshot, query_info, context, processed_stage, diff --git a/src/Storages/StorageExternalDistributed.h b/src/Storages/StorageExternalDistributed.h index c85276c09dd..1e0947d43b5 100644 --- a/src/Storages/StorageExternalDistributed.h +++ b/src/Storages/StorageExternalDistributed.h @@ -32,7 +32,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageFile.cpp b/src/Storages/StorageFile.cpp index ea64f46919e..01d26ec1f42 100644 --- a/src/Storages/StorageFile.cpp +++ b/src/Storages/StorageFile.cpp @@ -271,26 +271,26 @@ public: static Block getBlockForSource( const StorageFilePtr & storage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const ColumnsDescription & columns_description, const FilesInfoPtr & files_info) { if (storage->isColumnOriented()) - return storage->getSampleBlockForColumns(metadata_snapshot, columns_description.getNamesOfPhysical()); + return storage_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); else - return getHeader(metadata_snapshot, files_info->need_path_column, files_info->need_file_column); + return getHeader(storage_snapshot->metadata, files_info->need_path_column, files_info->need_file_column); } StorageFileSource( std::shared_ptr storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, ContextPtr context_, UInt64 max_block_size_, FilesInfoPtr files_info_, ColumnsDescription columns_description_) - : SourceWithProgress(getBlockForSource(storage_, metadata_snapshot_, columns_description_, files_info_)) + : SourceWithProgress(getBlockForSource(storage_, storage_snapshot_, columns_description_, files_info_)) , storage(std::move(storage_)) - , metadata_snapshot(metadata_snapshot_) + , storage_snapshot(storage_snapshot_) , files_info(std::move(files_info_)) , columns_description(std::move(columns_description_)) , context(context_) @@ -379,8 +379,8 @@ public: auto get_block_for_format = [&]() -> Block { if (storage->isColumnOriented()) - return storage->getSampleBlockForColumns(metadata_snapshot, columns_description.getNamesOfPhysical()); - return metadata_snapshot->getSampleBlock(); + return storage_snapshot->getSampleBlockForColumns(columns_description.getNamesOfPhysical()); + return storage_snapshot->metadata->getSampleBlock(); }; auto format = FormatFactory::instance().getInput( @@ -434,7 +434,7 @@ public: private: std::shared_ptr storage; - StorageMetadataPtr metadata_snapshot; + StorageSnapshotPtr storage_snapshot; FilesInfoPtr files_info; String current_path; Block sample_block; @@ -454,7 +454,7 @@ private: Pipe StorageFile::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, @@ -469,7 +469,7 @@ Pipe StorageFile::read( if (paths.size() == 1 && !fs::exists(paths[0])) { if (context->getSettingsRef().engine_file_empty_if_not_exists) - return Pipe(std::make_shared(getSampleBlockForColumns(metadata_snapshot, column_names))); + return Pipe(std::make_shared(storage_snapshot->getSampleBlockForColumns(column_names))); else throw Exception("File " + paths[0] + " doesn't exist", ErrorCodes::FILE_DOESNT_EXIST); } @@ -505,12 +505,12 @@ Pipe StorageFile::read( { if (isColumnOriented()) return ColumnsDescription{ - getSampleBlockForColumns(metadata_snapshot, column_names).getNamesAndTypesList()}; + storage_snapshot->getSampleBlockForColumns(column_names).getNamesAndTypesList()}; else - return metadata_snapshot->getColumns(); + return storage_snapshot->metadata->getColumns(); }; pipes.emplace_back(std::make_shared( - this_ptr, metadata_snapshot, context, max_block_size, files_info, get_columns_for_format())); + this_ptr, storage_snapshot, context, max_block_size, files_info, get_columns_for_format())); } return Pipe::unitePipes(std::move(pipes)); diff --git a/src/Storages/StorageFile.h b/src/Storages/StorageFile.h index 843cd405828..cbd4169562b 100644 --- a/src/Storages/StorageFile.h +++ b/src/Storages/StorageFile.h @@ -22,7 +22,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageGenerateRandom.cpp b/src/Storages/StorageGenerateRandom.cpp index 702eaa59c3e..982e194da87 100644 --- a/src/Storages/StorageGenerateRandom.cpp +++ b/src/Storages/StorageGenerateRandom.cpp @@ -479,19 +479,19 @@ void registerStorageGenerateRandom(StorageFactory & factory) Pipe StorageGenerateRandom::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, size_t max_block_size, unsigned num_streams) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); Pipes pipes; pipes.reserve(num_streams); - const ColumnsDescription & our_columns = metadata_snapshot->getColumns(); + const ColumnsDescription & our_columns = storage_snapshot->metadata->getColumns(); Block block_header; for (const auto & name : column_names) { diff --git a/src/Storages/StorageGenerateRandom.h b/src/Storages/StorageGenerateRandom.h index 61fd68cb80c..390b2cef46a 100644 --- a/src/Storages/StorageGenerateRandom.h +++ b/src/Storages/StorageGenerateRandom.h @@ -17,7 +17,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageInput.cpp b/src/Storages/StorageInput.cpp index 63b440aff08..e779de731b5 100644 --- a/src/Storages/StorageInput.cpp +++ b/src/Storages/StorageInput.cpp @@ -54,7 +54,7 @@ void StorageInput::setInputStream(BlockInputStreamPtr input_stream_) Pipe StorageInput::read( const Names & /*column_names*/, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, @@ -68,7 +68,7 @@ Pipe StorageInput::read( { /// Send structure to the client. query_context->initializeInput(shared_from_this()); - return Pipe(std::make_shared(query_context, metadata_snapshot->getSampleBlock())); + return Pipe(std::make_shared(query_context, storage_snapshot->metadata->getSampleBlock())); } if (!input_stream) diff --git a/src/Storages/StorageInput.h b/src/Storages/StorageInput.h index 4b04907bea2..c780edd36b9 100644 --- a/src/Storages/StorageInput.h +++ b/src/Storages/StorageInput.h @@ -19,7 +19,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageJoin.cpp b/src/Storages/StorageJoin.cpp index 48464d611ba..7fc814104b7 100644 --- a/src/Storages/StorageJoin.cpp +++ b/src/Storages/StorageJoin.cpp @@ -567,16 +567,16 @@ private: // TODO: multiple stream read and index read Pipe StorageJoin::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, size_t max_block_size, unsigned /*num_streams*/) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); - Block source_sample_block = getSampleBlockForColumns(metadata_snapshot, column_names); + Block source_sample_block = storage_snapshot->getSampleBlockForColumns(column_names); return Pipe(std::make_shared(join, rwlock, max_block_size, source_sample_block)); } diff --git a/src/Storages/StorageJoin.h b/src/Storages/StorageJoin.h index cf28e9d4dd0..402e8af3f73 100644 --- a/src/Storages/StorageJoin.h +++ b/src/Storages/StorageJoin.h @@ -49,7 +49,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageLog.cpp b/src/Storages/StorageLog.cpp index 8b146a8568c..2d7560cba2c 100644 --- a/src/Storages/StorageLog.cpp +++ b/src/Storages/StorageLog.cpp @@ -648,19 +648,19 @@ static std::chrono::seconds getLockTimeout(ContextPtr context) Pipe StorageLog::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, size_t max_block_size, unsigned num_streams) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); auto lock_timeout = getLockTimeout(context); loadMarks(lock_timeout); - auto all_columns = metadata_snapshot->getColumns().getAllWithSubcolumns().addTypes(column_names); + auto all_columns = storage_snapshot->metadata->getColumns().getAllWithSubcolumns().addTypes(column_names); all_columns = Nested::convertToSubcolumns(all_columns); std::shared_lock lock(rwlock, lock_timeout); @@ -669,7 +669,7 @@ Pipe StorageLog::read( Pipes pipes; - const Marks & marks = getMarksWithRealRowCount(metadata_snapshot); + const Marks & marks = getMarksWithRealRowCount(storage_snapshot->metadata); size_t marks_size = marks.size(); if (num_streams > marks_size) diff --git a/src/Storages/StorageLog.h b/src/Storages/StorageLog.h index 6fea00edefd..87ac5376955 100644 --- a/src/Storages/StorageLog.h +++ b/src/Storages/StorageLog.h @@ -27,7 +27,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageMaterializeMySQL.cpp b/src/Storages/StorageMaterializeMySQL.cpp index 5b371fe3fb8..a05c0c5dd2b 100644 --- a/src/Storages/StorageMaterializeMySQL.cpp +++ b/src/Storages/StorageMaterializeMySQL.cpp @@ -38,7 +38,7 @@ StorageMaterializeMySQL::StorageMaterializeMySQL(const StoragePtr & nested_stora Pipe StorageMaterializeMySQL::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -47,7 +47,7 @@ Pipe StorageMaterializeMySQL::read( { /// If the background synchronization thread has exception. rethrowSyncExceptionIfNeed(database); - return readFinalFromNestedStorage(nested_storage, column_names, metadata_snapshot, + return readFinalFromNestedStorage(nested_storage, column_names, query_info, context, processed_stage, max_block_size, num_streams); } diff --git a/src/Storages/StorageMaterializeMySQL.h b/src/Storages/StorageMaterializeMySQL.h index 45221ed5b76..aa5fe393c98 100644 --- a/src/Storages/StorageMaterializeMySQL.h +++ b/src/Storages/StorageMaterializeMySQL.h @@ -25,7 +25,7 @@ public: StorageMaterializeMySQL(const StoragePtr & nested_storage_, const IDatabase * database_); Pipe read( - const Names & column_names, const StorageMetadataPtr & metadata_snapshot, SelectQueryInfo & query_info, + const Names & column_names, const StorageSnapshotPtr & metadata_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, size_t max_block_size, unsigned num_streams) override; BlockOutputStreamPtr write(const ASTPtr &, const StorageMetadataPtr &, ContextPtr) override { throwNotAllowed(); } diff --git a/src/Storages/StorageMaterializedView.cpp b/src/Storages/StorageMaterializedView.cpp index 76fa4b8e20b..74a6e4c679d 100644 --- a/src/Storages/StorageMaterializedView.cpp +++ b/src/Storages/StorageMaterializedView.cpp @@ -131,15 +131,16 @@ StorageMaterializedView::StorageMaterializedView( QueryProcessingStage::Enum StorageMaterializedView::getQueryProcessingStage( ContextPtr local_context, QueryProcessingStage::Enum to_stage, - const StorageMetadataPtr &, + const StorageSnapshotPtr &, SelectQueryInfo & query_info) const { - return getTargetTable()->getQueryProcessingStage(local_context, to_stage, getTargetTable()->getInMemoryMetadataPtr(), query_info); + auto target_metadata = getTargetTable()->getInMemoryMetadataPtr(); + return getTargetTable()->getQueryProcessingStage(local_context, to_stage, getTargetTable()->getStorageSnapshot(target_metadata), query_info); } Pipe StorageMaterializedView::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -147,7 +148,7 @@ Pipe StorageMaterializedView::read( const unsigned num_streams) { QueryPlan plan; - read(plan, column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); + read(plan, column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); return plan.convertToPipe( QueryPlanOptimizationSettings::fromContext(local_context), BuildQueryPipelineSettings::fromContext(local_context)); @@ -156,7 +157,7 @@ Pipe StorageMaterializedView::read( void StorageMaterializedView::read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -166,15 +167,16 @@ void StorageMaterializedView::read( auto storage = getTargetTable(); auto lock = storage->lockForShare(local_context->getCurrentQueryId(), local_context->getSettingsRef().lock_acquire_timeout); auto target_metadata_snapshot = storage->getInMemoryMetadataPtr(); + auto target_storage_snapshot = storage->getStorageSnapshot(target_metadata_snapshot); if (query_info.order_optimizer) query_info.input_order_info = query_info.order_optimizer->getInputOrder(target_metadata_snapshot, local_context); - storage->read(query_plan, column_names, target_metadata_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); + storage->read(query_plan, column_names, target_storage_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); if (query_plan.isInitialized()) { - auto mv_header = getHeaderForProcessingStage(*this, column_names, metadata_snapshot, query_info, local_context, processed_stage); + auto mv_header = getHeaderForProcessingStage(column_names, storage_snapshot, query_info, local_context, processed_stage); auto target_header = query_plan.getCurrentDataStream().header; /// No need to convert columns that does not exists in MV diff --git a/src/Storages/StorageMaterializedView.h b/src/Storages/StorageMaterializedView.h index 1f1729b05f7..69d06ce1063 100644 --- a/src/Storages/StorageMaterializedView.h +++ b/src/Storages/StorageMaterializedView.h @@ -67,7 +67,7 @@ public: void shutdown() override; QueryProcessingStage::Enum - getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageMetadataPtr &, SelectQueryInfo &) const override; + getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageSnapshotPtr &, SelectQueryInfo &) const override; StoragePtr getTargetTable() const; StoragePtr tryGetTargetTable() const; @@ -76,7 +76,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -86,7 +86,7 @@ public: void read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageMemory.cpp b/src/Storages/StorageMemory.cpp index a8f563d69ca..fd09898e28c 100644 --- a/src/Storages/StorageMemory.cpp +++ b/src/Storages/StorageMemory.cpp @@ -29,13 +29,12 @@ public: MemorySource( Names column_names_, - const StorageMemory & storage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, std::shared_ptr data_, std::shared_ptr> parallel_execution_index_, InitializerFunc initializer_func_ = {}) - : SourceWithProgress(storage.getSampleBlockForColumns(metadata_snapshot, column_names_)) - , column_names_and_types(metadata_snapshot->getColumns().getAllWithSubcolumns().addTypes(std::move(column_names_))) + : SourceWithProgress(storage_snapshot->getSampleBlockForColumns(column_names_)) + , column_names_and_types(storage_snapshot->metadata->getColumns().getAllWithSubcolumns().addTypes(std::move(column_names_))) , data(data_) , parallel_execution_index(parallel_execution_index_) , initializer_func(std::move(initializer_func_)) @@ -178,14 +177,14 @@ StorageMemory::StorageMemory( Pipe StorageMemory::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, size_t /*max_block_size*/, unsigned num_streams) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); if (delay_read_for_global_subqueries) { @@ -199,8 +198,7 @@ Pipe StorageMemory::read( return Pipe(std::make_shared( column_names, - *this, - metadata_snapshot, + storage_snapshot, nullptr /* data */, nullptr /* parallel execution index */, [this](std::shared_ptr & data_to_initialize) @@ -221,7 +219,7 @@ Pipe StorageMemory::read( for (size_t stream = 0; stream < num_streams; ++stream) { - pipes.emplace_back(std::make_shared(column_names, *this, metadata_snapshot, current_data, parallel_execution_index)); + pipes.emplace_back(std::make_shared(column_names, storage_snapshot, current_data, parallel_execution_index)); } return Pipe::unitePipes(std::move(pipes)); diff --git a/src/Storages/StorageMemory.h b/src/Storages/StorageMemory.h index 47f9a942b82..7910c419374 100644 --- a/src/Storages/StorageMemory.h +++ b/src/Storages/StorageMemory.h @@ -32,7 +32,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 2d5bbfc712d..12b2e69e7a1 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -139,7 +139,7 @@ bool StorageMerge::mayBenefitFromIndexForIn(const ASTPtr & left_in_operand, Cont QueryProcessingStage::Enum StorageMerge::getQueryProcessingStage( ContextPtr local_context, QueryProcessingStage::Enum to_stage, - const StorageMetadataPtr &, + const StorageSnapshotPtr &, SelectQueryInfo & query_info) const { /// In case of JOIN the first stage (which includes JOIN) @@ -168,7 +168,8 @@ QueryProcessingStage::Enum StorageMerge::getQueryProcessingStage( ++selected_table_size; stage_in_source_tables = std::max( stage_in_source_tables, - table->getQueryProcessingStage(local_context, to_stage, table->getInMemoryMetadataPtr(), query_info)); + table->getQueryProcessingStage(local_context, to_stage, + table->getStorageSnapshot(table->getInMemoryMetadataPtr()), query_info)); } iterator->next(); @@ -181,7 +182,7 @@ QueryProcessingStage::Enum StorageMerge::getQueryProcessingStage( Pipe StorageMerge::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -197,9 +198,9 @@ Pipe StorageMerge::read( for (const auto & column_name : column_names) { - if (column_name == "_database" && isVirtualColumn(column_name, metadata_snapshot)) + if (column_name == "_database" && isVirtualColumn(column_name, storage_snapshot->metadata)) has_database_virtual_column = true; - else if (column_name == "_table" && isVirtualColumn(column_name, metadata_snapshot)) + else if (column_name == "_table" && isVirtualColumn(column_name, storage_snapshot->metadata)) has_table_virtual_column = true; else real_column_names.push_back(column_name); @@ -212,7 +213,7 @@ Pipe StorageMerge::read( modified_context->setSetting("optimize_move_to_prewhere", Field{false}); /// What will be result structure depending on query processed stage in source tables? - Block header = getHeaderForProcessingStage(*this, column_names, metadata_snapshot, query_info, local_context, processed_stage); + Block header = getHeaderForProcessingStage(column_names, storage_snapshot, query_info, local_context, 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. @@ -284,7 +285,7 @@ Pipe StorageMerge::read( if (processed_stage == QueryProcessingStage::FetchColumns && !storage_columns.getAliases().empty()) { - auto syntax_result = TreeRewriter(local_context).analyzeSelect(query_info.query, TreeRewriterResult({}, storage, storage_metadata_snapshot)); + auto syntax_result = TreeRewriter(local_context).analyzeSelect(query_info.query, TreeRewriterResult({}, storage, storage->getStorageSnapshot(storage_metadata_snapshot))); ASTPtr required_columns_expr_list = std::make_shared(); ASTPtr column_expr; @@ -314,13 +315,13 @@ Pipe StorageMerge::read( } syntax_result = TreeRewriter(local_context).analyze(required_columns_expr_list, storage_columns.getAllPhysical(), - storage, storage_metadata_snapshot); + storage, storage->getStorageSnapshot(storage_metadata_snapshot)); auto alias_actions = ExpressionAnalyzer(required_columns_expr_list, syntax_result, local_context).getActionsDAG(true); required_columns = alias_actions->getRequiredColumns().getNames(); } auto source_pipe = createSources( - storage_metadata_snapshot, + storage_snapshot, query_info, processed_stage, max_block_size, @@ -347,7 +348,7 @@ Pipe StorageMerge::read( } Pipe StorageMerge::createSources( - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, const QueryProcessingStage::Enum & processed_stage, const UInt64 max_block_size, @@ -389,16 +390,16 @@ Pipe StorageMerge::createSources( } auto storage_stage - = storage->getQueryProcessingStage(modified_context, QueryProcessingStage::Complete, metadata_snapshot, modified_query_info); + = storage->getQueryProcessingStage(modified_context, QueryProcessingStage::Complete, storage_snapshot, modified_query_info); if (processed_stage <= storage_stage) { /// If there are only virtual columns in query, you must request at least one other column. if (real_column_names.empty()) - real_column_names.push_back(ExpressionActions::getSmallestColumn(metadata_snapshot->getColumns().getAllPhysical())); + real_column_names.push_back(ExpressionActions::getSmallestColumn(storage_snapshot->metadata->getColumns().getAllPhysical())); pipe = storage->read( real_column_names, - metadata_snapshot, + storage_snapshot, modified_query_info, modified_context, processed_stage, @@ -470,7 +471,7 @@ 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, metadata_snapshot, aliases, modified_context, modified_query_info.query, pipe, processed_stage); + convertingSourceStream(header, storage_snapshot->metadata, aliases, modified_context, modified_query_info.query, pipe, processed_stage); pipe.addTableLock(struct_lock); pipe.addStorageHolder(storage); diff --git a/src/Storages/StorageMerge.h b/src/Storages/StorageMerge.h index 20460e95156..5d9863515c4 100644 --- a/src/Storages/StorageMerge.h +++ b/src/Storages/StorageMerge.h @@ -28,11 +28,11 @@ public: bool supportsSubcolumns() const override { return true; } QueryProcessingStage::Enum - getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageMetadataPtr &, SelectQueryInfo &) const override; + getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageSnapshotPtr &, SelectQueryInfo &) const override; Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -108,7 +108,7 @@ protected: using Aliases = std::vector; Pipe createSources( - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, const QueryProcessingStage::Enum & processed_stage, UInt64 max_block_size, diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 55ccd60ea38..66c959d2cc5 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -180,20 +180,20 @@ StorageMergeTree::~StorageMergeTree() void StorageMergeTree::read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, size_t max_block_size, unsigned num_streams) { - if (auto plan = reader.read(column_names, metadata_snapshot, query_info, local_context, max_block_size, num_streams, processed_stage)) + if (auto plan = reader.read(column_names, storage_snapshot, query_info, local_context, max_block_size, num_streams, processed_stage)) query_plan = std::move(*plan); } Pipe StorageMergeTree::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -201,7 +201,7 @@ Pipe StorageMergeTree::read( const unsigned num_streams) { QueryPlan plan; - read(plan, column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); + read(plan, column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); return plan.convertToPipe( QueryPlanOptimizationSettings::fromContext(local_context), BuildQueryPipelineSettings::fromContext(local_context)); diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index fa2b2e9f08b..f39f842a858 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -40,7 +40,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -50,7 +50,7 @@ public: void read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageMongoDB.cpp b/src/Storages/StorageMongoDB.cpp index fc3da2343c5..61bc5dd4b5f 100644 --- a/src/Storages/StorageMongoDB.cpp +++ b/src/Storages/StorageMongoDB.cpp @@ -74,7 +74,7 @@ void StorageMongoDB::connectIfNotConnected() Pipe StorageMongoDB::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, @@ -83,12 +83,12 @@ Pipe StorageMongoDB::read( { connectIfNotConnected(); - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); Block sample_block; for (const String & column_name : column_names) { - auto column_data = metadata_snapshot->getColumns().getPhysical(column_name); + auto column_data = storage_snapshot->metadata->getColumns().getPhysical(column_name); sample_block.insert({ column_data.type, column_data.name }); } diff --git a/src/Storages/StorageMongoDB.h b/src/Storages/StorageMongoDB.h index 2553acdd40c..4572bc13074 100644 --- a/src/Storages/StorageMongoDB.h +++ b/src/Storages/StorageMongoDB.h @@ -34,7 +34,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageMySQL.cpp b/src/Storages/StorageMySQL.cpp index 116e713a8c9..4afcc492055 100644 --- a/src/Storages/StorageMySQL.cpp +++ b/src/Storages/StorageMySQL.cpp @@ -72,17 +72,17 @@ StorageMySQL::StorageMySQL( Pipe StorageMySQL::read( const Names & column_names_, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info_, ContextPtr context_, QueryProcessingStage::Enum /*processed_stage*/, size_t /*max_block_size*/, unsigned) { - check(metadata_snapshot, column_names_); + storage_snapshot->check(column_names_); String query = transformQueryForExternalDatabase( query_info_, - metadata_snapshot->getColumns().getOrdinary(), + storage_snapshot->metadata->getColumns().getOrdinary(), IdentifierQuotingStyle::BackticksMySQL, remote_database_name, remote_table_name, @@ -91,7 +91,7 @@ Pipe StorageMySQL::read( Block sample_block; for (const String & column_name : column_names_) { - auto column_data = metadata_snapshot->getColumns().getPhysical(column_name); + auto column_data = storage_snapshot->metadata->getColumns().getPhysical(column_name); WhichDataType which(column_data.type); /// Convert enum to string. diff --git a/src/Storages/StorageMySQL.h b/src/Storages/StorageMySQL.h index 2bcc7af5f2b..7bfccc1db94 100644 --- a/src/Storages/StorageMySQL.h +++ b/src/Storages/StorageMySQL.h @@ -41,7 +41,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageNull.h b/src/Storages/StorageNull.h index 2f1d9cfb4e5..d4fbe6f581d 100644 --- a/src/Storages/StorageNull.h +++ b/src/Storages/StorageNull.h @@ -23,7 +23,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo &, ContextPtr /*context*/, QueryProcessingStage::Enum /*processing_stage*/, @@ -31,7 +31,7 @@ public: unsigned) override { return Pipe( - std::make_shared(getSampleBlockForColumns(metadata_snapshot, column_names))); + std::make_shared(storage_snapshot->getSampleBlockForColumns(column_names))); } bool supportsParallelInsert() const override { return true; } diff --git a/src/Storages/StoragePostgreSQL.cpp b/src/Storages/StoragePostgreSQL.cpp index 79f9452b206..67b7dc6f372 100644 --- a/src/Storages/StoragePostgreSQL.cpp +++ b/src/Storages/StoragePostgreSQL.cpp @@ -64,25 +64,25 @@ StoragePostgreSQL::StoragePostgreSQL( Pipe StoragePostgreSQL::read( const Names & column_names_, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info_, ContextPtr context_, QueryProcessingStage::Enum /*processed_stage*/, size_t max_block_size_, unsigned) { - check(metadata_snapshot, column_names_); + storage_snapshot->check(column_names_); /// Connection is already made to the needed database, so it should not be present in the query; /// remote_table_schema is empty if it is not specified, will access only table_name. String query = transformQueryForExternalDatabase( - query_info_, metadata_snapshot->getColumns().getOrdinary(), + query_info_, storage_snapshot->metadata->getColumns().getOrdinary(), IdentifierQuotingStyle::DoubleQuotes, remote_table_schema, remote_table_name, context_); Block sample_block; for (const String & column_name : column_names_) { - auto column_data = metadata_snapshot->getColumns().getPhysical(column_name); + auto column_data = storage_snapshot->metadata->getColumns().getPhysical(column_name); WhichDataType which(column_data.type); if (which.isEnum()) column_data.type = std::make_shared(); diff --git a/src/Storages/StoragePostgreSQL.h b/src/Storages/StoragePostgreSQL.h index 5a8ecf5598f..13d74977ab5 100644 --- a/src/Storages/StoragePostgreSQL.h +++ b/src/Storages/StoragePostgreSQL.h @@ -34,7 +34,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageProxy.h b/src/Storages/StorageProxy.h index 60e7bf24046..94a9a1cb7d6 100644 --- a/src/Storages/StorageProxy.h +++ b/src/Storages/StorageProxy.h @@ -34,10 +34,11 @@ public: QueryProcessingStage::Enum getQueryProcessingStage( ContextPtr context, QueryProcessingStage::Enum to_stage, - const StorageMetadataPtr &, + const StorageSnapshotPtr &, SelectQueryInfo & info) const override { - return getNested()->getQueryProcessingStage(context, to_stage, getNested()->getInMemoryMetadataPtr(), info); + const auto & nested_metadata = getNested()->getInMemoryMetadataPtr(); + return getNested()->getQueryProcessingStage(context, to_stage, getNested()->getStorageSnapshot(nested_metadata), info); } BlockInputStreams watch( @@ -53,14 +54,14 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, size_t max_block_size, unsigned num_streams) override { - return getNested()->read(column_names, metadata_snapshot, query_info, context, processed_stage, max_block_size, num_streams); + return getNested()->read(column_names, storage_snapshot, query_info, context, processed_stage, max_block_size, num_streams); } BlockOutputStreamPtr write(const ASTPtr & query, const StorageMetadataPtr & metadata_snapshot, ContextPtr context) override diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 87befd2c407..e4a6f79ad87 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -4447,7 +4447,7 @@ ReplicatedMergeTreeQuorumAddedParts::PartitionIdToMaxBlock StorageReplicatedMerg void StorageReplicatedMergeTree::read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -4463,18 +4463,18 @@ void StorageReplicatedMergeTree::read( { auto max_added_blocks = std::make_shared(getMaxAddedBlocks()); if (auto plan = reader.read( - column_names, metadata_snapshot, query_info, local_context, max_block_size, num_streams, processed_stage, std::move(max_added_blocks))) + column_names, storage_snapshot, query_info, local_context, max_block_size, num_streams, processed_stage, std::move(max_added_blocks))) query_plan = std::move(*plan); return; } - if (auto plan = reader.read(column_names, metadata_snapshot, query_info, local_context, max_block_size, num_streams, processed_stage)) + if (auto plan = reader.read(column_names, storage_snapshot, query_info, local_context, max_block_size, num_streams, processed_stage)) query_plan = std::move(*plan); } Pipe StorageReplicatedMergeTree::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -4482,7 +4482,7 @@ Pipe StorageReplicatedMergeTree::read( const unsigned num_streams) { QueryPlan plan; - read(plan, column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); + read(plan, column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); return plan.convertToPipe( QueryPlanOptimizationSettings::fromContext(local_context), BuildQueryPipelineSettings::fromContext(local_context)); diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index 28dd3c760a8..8c79f0eb6f4 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -95,7 +95,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -105,7 +105,7 @@ public: void read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageS3.cpp b/src/Storages/StorageS3.cpp index b4fec69e075..88f7558d38f 100644 --- a/src/Storages/StorageS3.cpp +++ b/src/Storages/StorageS3.cpp @@ -360,7 +360,7 @@ StorageS3::StorageS3( Pipe StorageS3::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr local_context, QueryProcessingStage::Enum /*processed_stage*/, @@ -405,9 +405,9 @@ Pipe StorageS3::read( need_file_column, format_name, getName(), - metadata_snapshot->getSampleBlock(), + storage_snapshot->metadata->getSampleBlock(), local_context, - metadata_snapshot->getColumns(), + storage_snapshot->metadata->getColumns(), max_block_size, max_single_read_retries, compression_method, diff --git a/src/Storages/StorageS3.h b/src/Storages/StorageS3.h index 37ada9e8eb7..8ade1f0b533 100644 --- a/src/Storages/StorageS3.h +++ b/src/Storages/StorageS3.h @@ -121,7 +121,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageS3Cluster.cpp b/src/Storages/StorageS3Cluster.cpp index 8985ff314f6..311910a8b28 100644 --- a/src/Storages/StorageS3Cluster.cpp +++ b/src/Storages/StorageS3Cluster.cpp @@ -80,7 +80,7 @@ StorageS3Cluster::StorageS3Cluster( /// The code executes on initiator Pipe StorageS3Cluster::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -133,12 +133,12 @@ Pipe StorageS3Cluster::read( } } - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); return Pipe::unitePipes(std::move(pipes)); } QueryProcessingStage::Enum StorageS3Cluster::getQueryProcessingStage( - ContextPtr context, QueryProcessingStage::Enum to_stage, const StorageMetadataPtr &, SelectQueryInfo &) const + ContextPtr context, QueryProcessingStage::Enum to_stage, const StorageSnapshotPtr &, SelectQueryInfo &) const { /// Initiator executes query on remote node. if (context->getClientInfo().query_kind == ClientInfo::QueryKind::INITIAL_QUERY) diff --git a/src/Storages/StorageS3Cluster.h b/src/Storages/StorageS3Cluster.h index 821765a3780..a40edf34e27 100644 --- a/src/Storages/StorageS3Cluster.h +++ b/src/Storages/StorageS3Cluster.h @@ -27,11 +27,11 @@ class StorageS3Cluster : public shared_ptr_helper, public ISto public: std::string getName() const override { return "S3Cluster"; } - Pipe read(const Names &, const StorageMetadataPtr &, SelectQueryInfo &, + Pipe read(const Names &, const StorageSnapshotPtr &, SelectQueryInfo &, ContextPtr, QueryProcessingStage::Enum, size_t /*max_block_size*/, unsigned /*num_streams*/) override; QueryProcessingStage::Enum - getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageMetadataPtr &, SelectQueryInfo &) const override; + getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageSnapshotPtr &, SelectQueryInfo &) const override; NamesAndTypesList getVirtuals() const override; diff --git a/src/Storages/StorageSnapshot.cpp b/src/Storages/StorageSnapshot.cpp new file mode 100644 index 00000000000..cd1e516c7fe --- /dev/null +++ b/src/Storages/StorageSnapshot.cpp @@ -0,0 +1,95 @@ +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int NOT_FOUND_COLUMN_IN_BLOCK; + extern const int EMPTY_LIST_OF_COLUMNS_QUERIED; + extern const int NO_SUCH_COLUMN_IN_TABLE; + extern const int COLUMN_QUERIED_MORE_THAN_ONCE; +} + +NamesAndTypesList StorageSnapshot::getColumns(const GetColumnsOptions & options) const +{ + auto all_columns = metadata->getColumns().get(options); + + if (options.with_virtuals) + { + /// Virtual columns must be appended after ordinary, + /// because user can override them. + auto virtuals = storage.getVirtuals(); + if (!virtuals.empty()) + { + NameSet column_names; + for (const auto & column : all_columns) + column_names.insert(column.name); + for (auto && column : virtuals) + if (!column_names.count(column.name)) + all_columns.push_back(std::move(column)); + } + } + + if (options.with_extended_objects) + all_columns = extendObjectColumns(all_columns, object_types, options.with_subcolumns); + + return all_columns; +} + +Block StorageSnapshot::getSampleBlockForColumns(const Names & column_names) const +{ + Block res; + + auto all_columns = getColumns(GetColumnsOptions(GetColumnsOptions::All) + .withSubcolumns().withVirtuals().withExtendedObjects()); + + std::unordered_map columns_map; + columns_map.reserve(all_columns.size()); + + for (const auto & elem : all_columns) + columns_map.emplace(elem.name, elem.type); + + for (const auto & name : column_names) + { + auto it = columns_map.find(name); + if (it != columns_map.end()) + res.insert({it->second->createColumn(), it->second, it->first}); + else + throw Exception(ErrorCodes::NOT_FOUND_COLUMN_IN_BLOCK, + "Column {} not found in table {}", backQuote(name), storage.getStorageID().getNameForLogs()); + } + + return res; +} + +void StorageSnapshot::check(const Names & column_names) const +{ + auto available_columns = getColumns(GetColumnsOptions(GetColumnsOptions::AllPhysical) + .withSubcolumns().withVirtuals().withExtendedObjects()).getNames(); + + if (column_names.empty()) + throw Exception(ErrorCodes::EMPTY_LIST_OF_COLUMNS_QUERIED, + "Empty list of columns queried. There are columns: {}", + boost::algorithm::join(available_columns, ",")); + + std::unordered_set columns_set(available_columns.begin(), available_columns.end()); + std::unordered_set unique_names; + + for (const auto & name : column_names) + { + if (columns_set.end() == columns_set.find(name)) + throw Exception(ErrorCodes::NO_SUCH_COLUMN_IN_TABLE, + "There is no column with name {} in table {}. There are columns: ", + backQuote(name), storage.getStorageID().getNameForLogs(), boost::algorithm::join(available_columns, ",")); + + if (unique_names.end() != unique_names.find(name)) + throw Exception(ErrorCodes::COLUMN_QUERIED_MORE_THAN_ONCE, "Column {} queried more than once", name); + + unique_names.insert(name); + } +} + +} diff --git a/src/Storages/StorageSnapshot.h b/src/Storages/StorageSnapshot.h new file mode 100644 index 00000000000..a34eef8116a --- /dev/null +++ b/src/Storages/StorageSnapshot.h @@ -0,0 +1,58 @@ +#pragma once +#include + +namespace DB +{ + +class IStorage; + +class IMergeTreeDataPart; +using DataPartPtr = std::shared_ptr; +using DataPartsVector = std::vector; + +struct StorageSnapshot +{ + using NameToTypeMap = std::unordered_map; + + const IStorage & storage; + const StorageMetadataPtr metadata; + const NameToTypeMap object_types; + const DataPartsVector parts; + + StorageSnapshot( + const IStorage & storage_, + const StorageMetadataPtr & metadata_) + : storage(storage_), metadata(metadata_) + { + } + + StorageSnapshot( + const IStorage & storage_, + const StorageMetadataPtr & metadata_, + const NameToTypeMap & object_types_) + : storage(storage_), metadata(metadata_), object_types(object_types_) + { + } + + StorageSnapshot( + const IStorage & storage_, + const StorageMetadataPtr & metadata_, + const NameToTypeMap & object_types_, + const DataPartsVector & parts_) + : storage(storage_), metadata(metadata_), object_types(object_types_), parts(parts_) + { + } + + NamesAndTypesList getColumns(const GetColumnsOptions & options) const; + + /// Block with ordinary + materialized + aliases + virtuals + subcolumns. + Block getSampleBlockForColumns(const Names & column_names) const; + + /// Verify that all the requested names are in the table and are set correctly: + /// list of names is not empty and the names do not repeat. + void check(const Names & column_names) const; +}; + +using StorageSnapshotPtr = std::shared_ptr; + +} diff --git a/src/Storages/StorageStripeLog.cpp b/src/Storages/StorageStripeLog.cpp index b0f4bbe484e..2ffd2d40870 100644 --- a/src/Storages/StorageStripeLog.cpp +++ b/src/Storages/StorageStripeLog.cpp @@ -52,14 +52,13 @@ class StripeLogSource final : public SourceWithProgress { public: static Block getHeader( - StorageStripeLog & storage, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const Names & column_names, IndexForNativeFormat::Blocks::const_iterator index_begin, IndexForNativeFormat::Blocks::const_iterator index_end) { if (index_begin == index_end) - return storage.getSampleBlockForColumns(metadata_snapshot, column_names); + return storage_snapshot->getSampleBlockForColumns(column_names); /// TODO: check if possible to always return storage.getSampleBlock() @@ -76,16 +75,16 @@ public: StripeLogSource( StorageStripeLog & storage_, - const StorageMetadataPtr & metadata_snapshot_, + const StorageSnapshotPtr & storage_snapshot_, const Names & column_names, size_t max_read_buffer_size_, std::shared_ptr & index_, IndexForNativeFormat::Blocks::const_iterator index_begin_, IndexForNativeFormat::Blocks::const_iterator index_end_) : SourceWithProgress( - getHeader(storage_, metadata_snapshot_, column_names, index_begin_, index_end_)) + getHeader(storage_snapshot_, column_names, index_begin_, index_end_)) , storage(storage_) - , metadata_snapshot(metadata_snapshot_) + , storage_snapshot(storage_snapshot_) , max_read_buffer_size(max_read_buffer_size_) , index(index_) , index_begin(index_begin_) @@ -122,7 +121,7 @@ protected: private: StorageStripeLog & storage; - StorageMetadataPtr metadata_snapshot; + StorageSnapshotPtr storage_snapshot; size_t max_read_buffer_size; std::shared_ptr index; @@ -321,7 +320,7 @@ static std::chrono::seconds getLockTimeout(ContextPtr context) Pipe StorageStripeLog::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, @@ -332,7 +331,7 @@ Pipe StorageStripeLog::read( if (!lock) throw Exception("Lock timeout exceeded", ErrorCodes::TIMEOUT_EXCEEDED); - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); NameSet column_names_set(column_names.begin(), column_names.end()); @@ -341,7 +340,7 @@ Pipe StorageStripeLog::read( String index_file = table_path + "index.mrk"; if (!disk->exists(index_file)) { - return Pipe(std::make_shared(getSampleBlockForColumns(metadata_snapshot, column_names))); + return Pipe(std::make_shared(storage_snapshot->getSampleBlockForColumns(column_names))); } CompressedReadBufferFromFile index_in(disk->readFile(index_file, 4096)); @@ -360,7 +359,7 @@ Pipe StorageStripeLog::read( std::advance(end, (stream + 1) * size / num_streams); pipes.emplace_back(std::make_shared( - *this, metadata_snapshot, column_names, context->getSettingsRef().max_read_buffer_size, index, begin, end)); + *this, storage_snapshot, column_names, context->getSettingsRef().max_read_buffer_size, index, begin, end)); } /// We do not keep read lock directly at the time of reading, because we read ranges of data that do not change. diff --git a/src/Storages/StorageStripeLog.h b/src/Storages/StorageStripeLog.h index 0749273043f..8ae8dda41b2 100644 --- a/src/Storages/StorageStripeLog.h +++ b/src/Storages/StorageStripeLog.h @@ -27,7 +27,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageTableFunction.h b/src/Storages/StorageTableFunction.h index 859735fec5b..dd886b700a8 100644 --- a/src/Storages/StorageTableFunction.h +++ b/src/Storages/StorageTableFunction.h @@ -78,7 +78,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -89,12 +89,12 @@ public: for (const auto & c : column_names) cnames += c + " "; auto storage = getNested(); - auto nested_metadata = storage->getInMemoryMetadataPtr(); - auto pipe = storage->read(column_names, nested_metadata, query_info, context, + auto nested_snapshot = storage->getStorageSnapshot(storage->getInMemoryMetadataPtr()); + auto pipe = storage->read(column_names, nested_snapshot, query_info, context, processed_stage, max_block_size, num_streams); if (!pipe.empty() && add_conversion) { - auto to_header = getHeaderForProcessingStage(*this, column_names, metadata_snapshot, + auto to_header = getHeaderForProcessingStage(column_names, storage_snapshot, query_info, context, processed_stage); auto convert_actions_dag = ActionsDAG::makeConvertingActions( diff --git a/src/Storages/StorageTinyLog.cpp b/src/Storages/StorageTinyLog.cpp index f02a2610db2..e69c485c119 100644 --- a/src/Storages/StorageTinyLog.cpp +++ b/src/Storages/StorageTinyLog.cpp @@ -480,16 +480,16 @@ static std::chrono::seconds getLockTimeout(ContextPtr context) Pipe StorageTinyLog::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, const size_t max_block_size, const unsigned /*num_streams*/) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); - auto all_columns = metadata_snapshot->getColumns().getAllWithSubcolumns().addTypes(column_names); + auto all_columns = storage_snapshot->metadata->getColumns().getAllWithSubcolumns().addTypes(column_names); // When reading, we lock the entire storage, because we only have one file // per column and can't modify it concurrently. diff --git a/src/Storages/StorageTinyLog.h b/src/Storages/StorageTinyLog.h index 71763a6403e..96955d7119f 100644 --- a/src/Storages/StorageTinyLog.h +++ b/src/Storages/StorageTinyLog.h @@ -26,7 +26,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageURL.cpp b/src/Storages/StorageURL.cpp index fd9453e632c..f154b83673d 100644 --- a/src/Storages/StorageURL.cpp +++ b/src/Storages/StorageURL.cpp @@ -182,7 +182,7 @@ std::string IStorageURLBase::getReadMethod() const std::vector> IStorageURLBase::getReadURIParams( const Names & /*column_names*/, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, const SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum & /*processed_stage*/, @@ -193,7 +193,7 @@ std::vector> IStorageURLBase::getReadURIPara std::function IStorageURLBase::getReadPOSTDataCallback( const Names & /*column_names*/, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, const SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum & /*processed_stage*/, @@ -205,7 +205,7 @@ std::function IStorageURLBase::getReadPOSTDataCallback( Pipe IStorageURLBase::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, @@ -213,7 +213,7 @@ Pipe IStorageURLBase::read( unsigned /*num_streams*/) { auto request_uri = uri; - auto params = getReadURIParams(column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size); + auto params = getReadURIParams(column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size); for (const auto & [param, value] : params) request_uri.addQueryParameter(param, value); @@ -221,14 +221,14 @@ Pipe IStorageURLBase::read( request_uri, getReadMethod(), getReadPOSTDataCallback( - column_names, metadata_snapshot, query_info, + column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size), format_name, format_settings, getName(), - getHeaderBlock(column_names, metadata_snapshot), + getHeaderBlock(column_names, storage_snapshot), local_context, - metadata_snapshot->getColumns(), + storage_snapshot->metadata->getColumns(), max_block_size, ConnectionTimeouts::getHTTPTimeouts(local_context), chooseCompressionMethod(request_uri.getPath(), compression_method))); @@ -237,14 +237,14 @@ Pipe IStorageURLBase::read( Pipe StorageURLWithFailover::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, size_t max_block_size, unsigned /*num_streams*/) { - auto params = getReadURIParams(column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size); + auto params = getReadURIParams(column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size); WriteBufferFromOwnString error_message; error_message << "Detailed description:"; @@ -260,14 +260,14 @@ Pipe StorageURLWithFailover::read( request_uri, getReadMethod(), getReadPOSTDataCallback( - column_names, metadata_snapshot, query_info, + column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size), format_name, format_settings, getName(), - getHeaderBlock(column_names, metadata_snapshot), + getHeaderBlock(column_names, storage_snapshot), local_context, - metadata_snapshot->getColumns(), + storage_snapshot->metadata->getColumns(), max_block_size, ConnectionTimeouts::getHTTPTimeouts(local_context), chooseCompressionMethod(request_uri.getPath(), compression_method)); diff --git a/src/Storages/StorageURL.h b/src/Storages/StorageURL.h index 78306292044..c86f57cb269 100644 --- a/src/Storages/StorageURL.h +++ b/src/Storages/StorageURL.h @@ -25,7 +25,7 @@ class IStorageURLBase : public IStorage public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -59,7 +59,7 @@ protected: virtual std::vector> getReadURIParams( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum & processed_stage, @@ -67,14 +67,14 @@ protected: virtual std::function getReadPOSTDataCallback( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum & processed_stage, size_t max_block_size) const; private: - virtual Block getHeaderBlock(const Names & column_names, const StorageMetadataPtr & metadata_snapshot) const = 0; + virtual Block getHeaderBlock(const Names & column_names, const StorageSnapshotPtr & storage_snapshot) const = 0; }; class StorageURLBlockOutputStream : public IBlockOutputStream @@ -124,9 +124,9 @@ public: return "URL"; } - Block getHeaderBlock(const Names & /*column_names*/, const StorageMetadataPtr & metadata_snapshot) const override + Block getHeaderBlock(const Names & /*column_names*/, const StorageSnapshotPtr & storage_snapshot) const override { - return metadata_snapshot->getSampleBlock(); + return storage_snapshot->metadata->getSampleBlock(); } static FormatSettings getFormatSettingsFromArgs(const StorageFactory::Arguments & args); @@ -149,7 +149,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageValues.cpp b/src/Storages/StorageValues.cpp index 0c9de61783a..d0acb191e8d 100644 --- a/src/Storages/StorageValues.cpp +++ b/src/Storages/StorageValues.cpp @@ -22,14 +22,14 @@ StorageValues::StorageValues( Pipe StorageValues::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, size_t /*max_block_size*/, unsigned /*num_streams*/) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); /// Get only required columns. Block block; diff --git a/src/Storages/StorageValues.h b/src/Storages/StorageValues.h index 4d6d168441c..c74f8eae3ed 100644 --- a/src/Storages/StorageValues.h +++ b/src/Storages/StorageValues.h @@ -17,7 +17,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageView.cpp b/src/Storages/StorageView.cpp index 26996e4da6e..d89a9499e60 100644 --- a/src/Storages/StorageView.cpp +++ b/src/Storages/StorageView.cpp @@ -53,7 +53,7 @@ StorageView::StorageView( Pipe StorageView::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -61,7 +61,7 @@ Pipe StorageView::read( const unsigned num_streams) { QueryPlan plan; - read(plan, column_names, metadata_snapshot, query_info, context, processed_stage, max_block_size, num_streams); + read(plan, column_names, storage_snapshot, query_info, context, processed_stage, max_block_size, num_streams); return plan.convertToPipe( QueryPlanOptimizationSettings::fromContext(context), BuildQueryPipelineSettings::fromContext(context)); @@ -70,14 +70,14 @@ Pipe StorageView::read( void StorageView::read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - ASTPtr current_inner_query = metadata_snapshot->getSelectQuery().inner_query; + ASTPtr current_inner_query = storage_snapshot->metadata->getSelectQuery().inner_query; if (query_info.view_query) { @@ -104,7 +104,7 @@ void StorageView::read( query_plan.addStep(std::move(materializing)); /// And also convert to expected structure. - auto header = getSampleBlockForColumns(metadata_snapshot, column_names); + auto header = storage_snapshot->getSampleBlockForColumns(column_names); auto convert_actions_dag = ActionsDAG::makeConvertingActions( query_plan.getCurrentDataStream().header.getColumnsWithTypeAndName(), header.getColumnsWithTypeAndName(), diff --git a/src/Storages/StorageView.h b/src/Storages/StorageView.h index a59328cd471..f55472c4bf7 100644 --- a/src/Storages/StorageView.h +++ b/src/Storages/StorageView.h @@ -23,7 +23,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -33,7 +33,7 @@ public: void read( QueryPlan & query_plan, const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/StorageXDBC.cpp b/src/Storages/StorageXDBC.cpp index 9a8b419a16c..313ab36182a 100644 --- a/src/Storages/StorageXDBC.cpp +++ b/src/Storages/StorageXDBC.cpp @@ -58,7 +58,7 @@ std::string StorageXDBC::getReadMethod() const std::vector> StorageXDBC::getReadURIParams( const Names & /* column_names */, - const StorageMetadataPtr & /* metadata_snapshot */, + const StorageSnapshotPtr & /*storage_snapshot*/, const SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum & /*processed_stage*/, @@ -69,14 +69,14 @@ std::vector> StorageXDBC::getReadURIParams( std::function StorageXDBC::getReadPOSTDataCallback( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum & /*processed_stage*/, size_t /*max_block_size*/) const { String query = transformQueryForExternalDatabase(query_info, - metadata_snapshot->getColumns().getOrdinary(), + storage_snapshot->metadata->getColumns().getOrdinary(), bridge_helper->getIdentifierQuotingStyle(), remote_database_name, remote_table_name, @@ -85,7 +85,7 @@ std::function StorageXDBC::getReadPOSTDataCallback( NamesAndTypesList cols; for (const String & name : column_names) { - auto column_data = metadata_snapshot->getColumns().getPhysical(name); + auto column_data = storage_snapshot->metadata->getColumns().getPhysical(name); cols.emplace_back(column_data.name, column_data.type); } @@ -101,17 +101,17 @@ std::function StorageXDBC::getReadPOSTDataCallback( Pipe StorageXDBC::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr local_context, QueryProcessingStage::Enum processed_stage, size_t max_block_size, unsigned num_streams) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); bridge_helper->startBridgeSync(); - return IStorageURLBase::read(column_names, metadata_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); + return IStorageURLBase::read(column_names, storage_snapshot, query_info, local_context, processed_stage, max_block_size, num_streams); } BlockOutputStreamPtr StorageXDBC::write(const ASTPtr & /*query*/, const StorageMetadataPtr & metadata_snapshot, ContextPtr local_context) @@ -140,9 +140,9 @@ BlockOutputStreamPtr StorageXDBC::write(const ASTPtr & /*query*/, const StorageM chooseCompressionMethod(uri.toString(), compression_method)); } -Block StorageXDBC::getHeaderBlock(const Names & column_names, const StorageMetadataPtr & metadata_snapshot) const +Block StorageXDBC::getHeaderBlock(const Names & column_names, const StorageSnapshotPtr & storage_snapshot) const { - return getSampleBlockForColumns(metadata_snapshot, column_names); + return storage_snapshot->getSampleBlockForColumns(column_names); } std::string StorageXDBC::getName() const diff --git a/src/Storages/StorageXDBC.h b/src/Storages/StorageXDBC.h index db0b506546d..cd13c09a169 100644 --- a/src/Storages/StorageXDBC.h +++ b/src/Storages/StorageXDBC.h @@ -17,7 +17,7 @@ class StorageXDBC : public IStorageURLBase public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -48,7 +48,7 @@ private: std::vector> getReadURIParams( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum & processed_stage, @@ -56,13 +56,13 @@ private: std::function getReadPOSTDataCallback( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, const SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum & processed_stage, size_t max_block_size) const override; - Block getHeaderBlock(const Names & column_names, const StorageMetadataPtr & metadata_snapshot) const override; + Block getHeaderBlock(const Names & column_names, const StorageSnapshotPtr & storage_snapshot) const override; }; } diff --git a/src/Storages/System/IStorageSystemOneBlock.h b/src/Storages/System/IStorageSystemOneBlock.h index 752265e7c40..f9c74d8ec29 100644 --- a/src/Storages/System/IStorageSystemOneBlock.h +++ b/src/Storages/System/IStorageSystemOneBlock.h @@ -46,16 +46,16 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, size_t /*max_block_size*/, unsigned /*num_streams*/) override { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); - Block sample_block = metadata_snapshot->getSampleBlockWithVirtuals(getVirtuals()); + Block sample_block = storage_snapshot->metadata->getSampleBlockWithVirtuals(getVirtuals()); MutableColumns res_columns = sample_block.cloneEmptyColumns(); fillData(res_columns, context, query_info); diff --git a/src/Storages/System/StorageSystemColumns.cpp b/src/Storages/System/StorageSystemColumns.cpp index 424ba6347c2..05db859e0aa 100644 --- a/src/Storages/System/StorageSystemColumns.cpp +++ b/src/Storages/System/StorageSystemColumns.cpp @@ -241,20 +241,20 @@ private: Pipe StorageSystemColumns::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, const size_t max_block_size, const unsigned /*num_streams*/) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); /// Create a mask of what columns are needed in the result. NameSet names_set(column_names.begin(), column_names.end()); - Block sample_block = metadata_snapshot->getSampleBlock(); + Block sample_block = storage_snapshot->metadata->getSampleBlock(); Block header; std::vector columns_mask(sample_block.columns()); diff --git a/src/Storages/System/StorageSystemColumns.h b/src/Storages/System/StorageSystemColumns.h index 015b8627c4b..d8565193a4f 100644 --- a/src/Storages/System/StorageSystemColumns.h +++ b/src/Storages/System/StorageSystemColumns.h @@ -19,7 +19,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/System/StorageSystemDataSkippingIndices.cpp b/src/Storages/System/StorageSystemDataSkippingIndices.cpp index 27fceb97a31..8e995e1f0e6 100644 --- a/src/Storages/System/StorageSystemDataSkippingIndices.cpp +++ b/src/Storages/System/StorageSystemDataSkippingIndices.cpp @@ -146,18 +146,18 @@ private: Pipe StorageSystemDataSkippingIndices::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /* processed_stage */, size_t max_block_size, unsigned int /* num_streams */) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); NameSet names_set(column_names.begin(), column_names.end()); - Block sample_block = metadata_snapshot->getSampleBlock(); + Block sample_block = storage_snapshot->metadata->getSampleBlock(); Block header; std::vector columns_mask(sample_block.columns()); diff --git a/src/Storages/System/StorageSystemDataSkippingIndices.h b/src/Storages/System/StorageSystemDataSkippingIndices.h index 8497f16606f..3a67cc9bd11 100644 --- a/src/Storages/System/StorageSystemDataSkippingIndices.h +++ b/src/Storages/System/StorageSystemDataSkippingIndices.h @@ -16,7 +16,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/System/StorageSystemDetachedParts.cpp b/src/Storages/System/StorageSystemDetachedParts.cpp index 1a9418485e5..1021f31d04a 100644 --- a/src/Storages/System/StorageSystemDetachedParts.cpp +++ b/src/Storages/System/StorageSystemDetachedParts.cpp @@ -31,7 +31,7 @@ StorageSystemDetachedParts::StorageSystemDetachedParts(const StorageID & table_i Pipe StorageSystemDetachedParts::read( const Names & /* column_names */, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, @@ -41,7 +41,7 @@ Pipe StorageSystemDetachedParts::read( StoragesInfoStream stream(query_info, context); /// Create the result. - Block block = metadata_snapshot->getSampleBlock(); + Block block = storage_snapshot->metadata->getSampleBlock(); MutableColumns new_columns = block.cloneEmptyColumns(); while (StoragesInfo info = stream.next()) diff --git a/src/Storages/System/StorageSystemDetachedParts.h b/src/Storages/System/StorageSystemDetachedParts.h index 9350e758d93..9d4fb76f597 100644 --- a/src/Storages/System/StorageSystemDetachedParts.h +++ b/src/Storages/System/StorageSystemDetachedParts.h @@ -24,7 +24,7 @@ protected: Pipe read( const Names & /* column_names */, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, diff --git a/src/Storages/System/StorageSystemDisks.cpp b/src/Storages/System/StorageSystemDisks.cpp index 51c62a1614d..bcd99237d67 100644 --- a/src/Storages/System/StorageSystemDisks.cpp +++ b/src/Storages/System/StorageSystemDisks.cpp @@ -28,14 +28,14 @@ StorageSystemDisks::StorageSystemDisks(const StorageID & table_id_) Pipe StorageSystemDisks::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); MutableColumnPtr col_name = ColumnString::create(); MutableColumnPtr col_path = ColumnString::create(); @@ -65,7 +65,7 @@ Pipe StorageSystemDisks::read( UInt64 num_rows = res_columns.at(0)->size(); Chunk chunk(std::move(res_columns), num_rows); - return Pipe(std::make_shared(metadata_snapshot->getSampleBlock(), std::move(chunk))); + return Pipe(std::make_shared(storage_snapshot->metadata->getSampleBlock(), std::move(chunk))); } } diff --git a/src/Storages/System/StorageSystemDisks.h b/src/Storages/System/StorageSystemDisks.h index 1274903ff00..3eb757f7547 100644 --- a/src/Storages/System/StorageSystemDisks.h +++ b/src/Storages/System/StorageSystemDisks.h @@ -22,7 +22,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/System/StorageSystemNumbers.cpp b/src/Storages/System/StorageSystemNumbers.cpp index 4edda96d2ff..bfdfafd57ab 100644 --- a/src/Storages/System/StorageSystemNumbers.cpp +++ b/src/Storages/System/StorageSystemNumbers.cpp @@ -124,14 +124,14 @@ StorageSystemNumbers::StorageSystemNumbers(const StorageID & table_id, bool mult Pipe StorageSystemNumbers::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo &, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, size_t max_block_size, unsigned num_streams) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); if (limit && *limit < max_block_size) { diff --git a/src/Storages/System/StorageSystemNumbers.h b/src/Storages/System/StorageSystemNumbers.h index 960c354fda2..95a7d2b7a4f 100644 --- a/src/Storages/System/StorageSystemNumbers.h +++ b/src/Storages/System/StorageSystemNumbers.h @@ -31,7 +31,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/System/StorageSystemOne.cpp b/src/Storages/System/StorageSystemOne.cpp index 09b4c438422..d0c7072b070 100644 --- a/src/Storages/System/StorageSystemOne.cpp +++ b/src/Storages/System/StorageSystemOne.cpp @@ -22,14 +22,14 @@ StorageSystemOne::StorageSystemOne(const StorageID & table_id_) Pipe StorageSystemOne::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo &, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); Block header{ColumnWithTypeAndName( DataTypeUInt8().createColumn(), diff --git a/src/Storages/System/StorageSystemOne.h b/src/Storages/System/StorageSystemOne.h index 71a29847919..7ae00bed263 100644 --- a/src/Storages/System/StorageSystemOne.h +++ b/src/Storages/System/StorageSystemOne.h @@ -23,7 +23,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/System/StorageSystemPartsBase.cpp b/src/Storages/System/StorageSystemPartsBase.cpp index 5676adb49be..dc5ebe6be22 100644 --- a/src/Storages/System/StorageSystemPartsBase.cpp +++ b/src/Storages/System/StorageSystemPartsBase.cpp @@ -26,7 +26,7 @@ namespace ErrorCodes extern const int TABLE_IS_DROPPED; } -bool StorageSystemPartsBase::hasStateColumn(const Names & column_names, const StorageMetadataPtr & metadata_snapshot) const +bool StorageSystemPartsBase::hasStateColumn(const Names & column_names, const StorageSnapshotPtr & storage_snapshot) const { bool has_state_column = false; Names real_column_names; @@ -41,7 +41,7 @@ bool StorageSystemPartsBase::hasStateColumn(const Names & column_names, const St /// Do not check if only _state column is requested if (!(has_state_column && real_column_names.empty())) - check(metadata_snapshot, real_column_names); + storage_snapshot->check(real_column_names); return has_state_column; } @@ -235,14 +235,14 @@ StoragesInfo StoragesInfoStream::next() Pipe StorageSystemPartsBase::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - bool has_state_column = hasStateColumn(column_names, metadata_snapshot); + bool has_state_column = hasStateColumn(column_names, storage_snapshot); StoragesInfoStream stream(query_info, context); @@ -250,7 +250,7 @@ Pipe StorageSystemPartsBase::read( NameSet names_set(column_names.begin(), column_names.end()); - Block sample = metadata_snapshot->getSampleBlock(); + Block sample = storage_snapshot->metadata->getSampleBlock(); Block header; std::vector columns_mask(sample.columns()); diff --git a/src/Storages/System/StorageSystemPartsBase.h b/src/Storages/System/StorageSystemPartsBase.h index 86f62e37fad..b16d5c46c72 100644 --- a/src/Storages/System/StorageSystemPartsBase.h +++ b/src/Storages/System/StorageSystemPartsBase.h @@ -58,7 +58,7 @@ class StorageSystemPartsBase : public IStorage public: Pipe read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, @@ -68,7 +68,7 @@ public: NamesAndTypesList getVirtuals() const override; private: - bool hasStateColumn(const Names & column_names, const StorageMetadataPtr & metadata_snapshot) const; + bool hasStateColumn(const Names & column_names, const StorageSnapshotPtr & storage_snapshot) const; protected: const FormatSettings format_settings; diff --git a/src/Storages/System/StorageSystemReplicas.cpp b/src/Storages/System/StorageSystemReplicas.cpp index 2d4c90b5954..28e70743b68 100644 --- a/src/Storages/System/StorageSystemReplicas.cpp +++ b/src/Storages/System/StorageSystemReplicas.cpp @@ -58,14 +58,14 @@ StorageSystemReplicas::StorageSystemReplicas(const StorageID & table_id_) Pipe StorageSystemReplicas::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); const auto access = context->getAccess(); const bool check_access_for_databases = !access->isGranted(AccessType::SHOW_TABLES); @@ -145,7 +145,7 @@ Pipe StorageSystemReplicas::read( col_engine = filtered_block.getByName("engine").column; } - MutableColumns res_columns = metadata_snapshot->getSampleBlock().cloneEmptyColumns(); + MutableColumns res_columns = storage_snapshot->metadata->getSampleBlock().cloneEmptyColumns(); for (size_t i = 0, size = col_database->size(); i < size; ++i) { @@ -186,8 +186,6 @@ Pipe StorageSystemReplicas::read( res_columns[col_num++]->insert(status.zookeeper_exception); } - Block header = metadata_snapshot->getSampleBlock(); - Columns fin_columns; fin_columns.reserve(res_columns.size()); @@ -201,7 +199,7 @@ Pipe StorageSystemReplicas::read( UInt64 num_rows = fin_columns.at(0)->size(); Chunk chunk(std::move(fin_columns), num_rows); - return Pipe(std::make_shared(metadata_snapshot->getSampleBlock(), std::move(chunk))); + return Pipe(std::make_shared(storage_snapshot->metadata->getSampleBlock(), std::move(chunk))); } diff --git a/src/Storages/System/StorageSystemReplicas.h b/src/Storages/System/StorageSystemReplicas.h index 1454628f363..74b93274f1c 100644 --- a/src/Storages/System/StorageSystemReplicas.h +++ b/src/Storages/System/StorageSystemReplicas.h @@ -20,7 +20,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/System/StorageSystemStoragePolicies.cpp b/src/Storages/System/StorageSystemStoragePolicies.cpp index 8f20ab63f25..2333f8cf0f9 100644 --- a/src/Storages/System/StorageSystemStoragePolicies.cpp +++ b/src/Storages/System/StorageSystemStoragePolicies.cpp @@ -37,14 +37,14 @@ StorageSystemStoragePolicies::StorageSystemStoragePolicies(const StorageID & tab Pipe StorageSystemStoragePolicies::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, const size_t /*max_block_size*/, const unsigned /*num_streams*/) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); MutableColumnPtr col_policy_name = ColumnString::create(); MutableColumnPtr col_volume_name = ColumnString::create(); @@ -88,7 +88,7 @@ Pipe StorageSystemStoragePolicies::read( UInt64 num_rows = res_columns.at(0)->size(); Chunk chunk(std::move(res_columns), num_rows); - return Pipe(std::make_shared(metadata_snapshot->getSampleBlock(), std::move(chunk))); + return Pipe(std::make_shared(storage_snapshot->metadata->getSampleBlock(), std::move(chunk))); } } diff --git a/src/Storages/System/StorageSystemStoragePolicies.h b/src/Storages/System/StorageSystemStoragePolicies.h index 25afd4166c8..1bd671a40e7 100644 --- a/src/Storages/System/StorageSystemStoragePolicies.h +++ b/src/Storages/System/StorageSystemStoragePolicies.h @@ -22,7 +22,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/System/StorageSystemTables.cpp b/src/Storages/System/StorageSystemTables.cpp index 6c924e04fce..a0d078096d0 100644 --- a/src/Storages/System/StorageSystemTables.cpp +++ b/src/Storages/System/StorageSystemTables.cpp @@ -504,20 +504,20 @@ private: Pipe StorageSystemTables::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, const size_t max_block_size, const unsigned /*num_streams*/) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); /// Create a mask of what columns are needed in the result. NameSet names_set(column_names.begin(), column_names.end()); - Block sample_block = metadata_snapshot->getSampleBlock(); + Block sample_block = storage_snapshot->metadata->getSampleBlock(); Block res_block; std::vector columns_mask(sample_block.columns()); diff --git a/src/Storages/System/StorageSystemTables.h b/src/Storages/System/StorageSystemTables.h index b196a87b093..36443651451 100644 --- a/src/Storages/System/StorageSystemTables.h +++ b/src/Storages/System/StorageSystemTables.h @@ -20,7 +20,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/System/StorageSystemZeros.cpp b/src/Storages/System/StorageSystemZeros.cpp index 6292ff4d414..93f00d62e70 100644 --- a/src/Storages/System/StorageSystemZeros.cpp +++ b/src/Storages/System/StorageSystemZeros.cpp @@ -92,14 +92,14 @@ StorageSystemZeros::StorageSystemZeros(const StorageID & table_id_, bool multith Pipe StorageSystemZeros::read( const Names & column_names, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo &, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, size_t max_block_size, unsigned num_streams) { - check(metadata_snapshot, column_names); + storage_snapshot->check(column_names); bool use_multiple_streams = multithreaded; diff --git a/src/Storages/System/StorageSystemZeros.h b/src/Storages/System/StorageSystemZeros.h index d33d9b4b099..283f4c49ded 100644 --- a/src/Storages/System/StorageSystemZeros.h +++ b/src/Storages/System/StorageSystemZeros.h @@ -22,7 +22,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, diff --git a/src/Storages/tests/gtest_storage_log.cpp b/src/Storages/tests/gtest_storage_log.cpp index e8057c54a0f..974988a691c 100644 --- a/src/Storages/tests/gtest_storage_log.cpp +++ b/src/Storages/tests/gtest_storage_log.cpp @@ -112,16 +112,17 @@ std::string readData(DB::StoragePtr & table, const DB::ContextPtr context) { using namespace DB; auto metadata_snapshot = table->getInMemoryMetadataPtr(); + auto storage_snapshot = table->getStorageSnapshot(metadata_snapshot); Names column_names; column_names.push_back("a"); SelectQueryInfo query_info; QueryProcessingStage::Enum stage = table->getQueryProcessingStage( - context, QueryProcessingStage::Complete, metadata_snapshot, query_info); + context, QueryProcessingStage::Complete, storage_snapshot, query_info); QueryPipeline pipeline; - pipeline.init(table->read(column_names, metadata_snapshot, query_info, context, stage, 8192, 1)); + pipeline.init(table->read(column_names, storage_snapshot, query_info, context, stage, 8192, 1)); BlockInputStreamPtr in = std::make_shared(std::move(pipeline)); Block sample; diff --git a/tests/queries/0_stateless/01825_type_json_schema_race_long.reference b/tests/queries/0_stateless/01825_type_json_schema_race_long.reference new file mode 100644 index 00000000000..d86bac9de59 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_schema_race_long.reference @@ -0,0 +1 @@ +OK diff --git a/tests/queries/0_stateless/01825_type_json_schema_race_long.sh b/tests/queries/0_stateless/01825_type_json_schema_race_long.sh new file mode 100755 index 00000000000..70da8b52d61 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_schema_race_long.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -e + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json_race" +$CLICKHOUSE_CLIENT -q "CREATE TABLE t_json_race (data JSON) ENGINE = MergeTree ORDER BY tuple()" + +function test_case() +{ + $CLICKHOUSE_CLIENT -q "TRUNCATE TABLE t_json_race" + + echo '{"data": {"k1": 1, "k2": 2}}' | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_race FORMAT JSONEachRow" + + pids=() + for _ in {1..5}; do + $CLICKHOUSE_CLIENT -q "SELECT * FROM t_json_race WHERE 0 IN (SELECT sleep(0.05)) FORMAT Null" & + pids+=($!) + done + + echo '{"data": {"k1": "str", "k2": "str1"}}' | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_race FORMAT JSONEachRow" & + + for pid in "${pids[@]}"; do + wait "$pid" || exit 1 + done +} + +for i in {1..30}; do test_case; done + +$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json_race" +echo OK From cd05df90f149d0b4d0b2807c295367638620694b Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 9 Jul 2021 17:11:44 +0300 Subject: [PATCH 0042/1647] fix snapshot for storage Merge --- src/Storages/StorageMerge.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Storages/StorageMerge.cpp b/src/Storages/StorageMerge.cpp index 12b2e69e7a1..c779b1f11d8 100644 --- a/src/Storages/StorageMerge.cpp +++ b/src/Storages/StorageMerge.cpp @@ -282,10 +282,11 @@ Pipe 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); if (processed_stage == QueryProcessingStage::FetchColumns && !storage_columns.getAliases().empty()) { - auto syntax_result = TreeRewriter(local_context).analyzeSelect(query_info.query, TreeRewriterResult({}, storage, storage->getStorageSnapshot(storage_metadata_snapshot))); + auto syntax_result = TreeRewriter(local_context).analyzeSelect(query_info.query, TreeRewriterResult({}, storage, nested_storage_snaphsot)); ASTPtr required_columns_expr_list = std::make_shared(); ASTPtr column_expr; @@ -321,7 +322,7 @@ Pipe StorageMerge::read( } auto source_pipe = createSources( - storage_snapshot, + nested_storage_snaphsot, query_info, processed_stage, max_block_size, From e1afdc1f1e9c2aa75bb8608f038f9c7201c2b444 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 9 Jul 2021 17:54:53 +0300 Subject: [PATCH 0043/1647] dynamic subcolumns: add mode for DESCRIBE query to show deduced object types --- src/Core/Settings.h | 1 + src/Interpreters/InterpreterDescribeQuery.cpp | 26 +++++++++++++------ src/Storages/StorageSnapshot.cpp | 9 +++++++ src/Storages/StorageSnapshot.h | 2 ++ .../01825_type_json_describe.reference | 3 +++ .../0_stateless/01825_type_json_describe.sql | 16 ++++++++++++ 6 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_describe.reference create mode 100644 tests/queries/0_stateless/01825_type_json_describe.sql diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 28e46160a98..5013366101e 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -451,6 +451,7 @@ class IColumn; M(Bool, force_optimize_projection, false, "If projection optimization is enabled, SELECT queries need to use projection", 0) \ M(Bool, async_socket_for_remote, true, "Asynchronously read from socket executing remote query", 0) \ M(Bool, insert_null_as_default, true, "Insert DEFAULT values instead of NULL in INSERT SELECT (UNION ALL)", 0) \ + M(Bool, describe_extend_object_type, false, "Deduce concrete type of columns of type Object in DESCRIBE query", 0) \ \ M(Bool, optimize_rewrite_sum_if_to_count_if, true, "Rewrite sumIf() and sum(if()) function countIf() function when logically equivalent", 0) \ M(UInt64, insert_shard_id, 0, "If non zero, when insert into a distributed table, the data will be inserted into the shard `insert_shard_id` synchronously. Possible values range from 1 to `shards_number` of corresponding distributed table", 0) \ diff --git a/src/Interpreters/InterpreterDescribeQuery.cpp b/src/Interpreters/InterpreterDescribeQuery.cpp index 705e52da72c..72cf2dc18ab 100644 --- a/src/Interpreters/InterpreterDescribeQuery.cpp +++ b/src/Interpreters/InterpreterDescribeQuery.cpp @@ -63,27 +63,33 @@ Block InterpreterDescribeQuery::getSampleBlock() BlockInputStreamPtr InterpreterDescribeQuery::executeImpl() { ColumnsDescription columns; + StorageSnapshotPtr storage_snapshot; const auto & ast = query_ptr->as(); const auto & table_expression = ast.table_expression->as(); + auto context = getContext(); + const auto & settings = context->getSettingsRef(); + if (table_expression.subquery) { auto names_and_types = InterpreterSelectWithUnionQuery::getSampleBlock( - table_expression.subquery->children.at(0), getContext()).getNamesAndTypesList(); + table_expression.subquery->children.at(0), context).getNamesAndTypesList(); columns = ColumnsDescription(std::move(names_and_types)); } else if (table_expression.table_function) { - TableFunctionPtr table_function_ptr = TableFunctionFactory::instance().get(table_expression.table_function, getContext()); - columns = table_function_ptr->getActualTableStructure(getContext()); + TableFunctionPtr table_function_ptr = TableFunctionFactory::instance().get(table_expression.table_function, context); + columns = table_function_ptr->getActualTableStructure(context); } else { - auto table_id = getContext()->resolveStorageID(table_expression.database_and_table_name); - getContext()->checkAccess(AccessType::SHOW_COLUMNS, table_id); - auto table = DatabaseCatalog::instance().getTable(table_id, getContext()); - auto table_lock = table->lockForShare(getContext()->getInitialQueryId(), getContext()->getSettingsRef().lock_acquire_timeout); + auto table_id = context->resolveStorageID(table_expression.database_and_table_name); + context->checkAccess(AccessType::SHOW_COLUMNS, table_id); + auto table = DatabaseCatalog::instance().getTable(table_id, context); + auto table_lock = table->lockForShare(context->getInitialQueryId(), settings.lock_acquire_timeout); + auto metadata_snapshot = table->getInMemoryMetadataPtr(); + storage_snapshot = table->getStorageSnapshot(metadata_snapshot); columns = metadata_snapshot->getColumns(); } @@ -93,7 +99,11 @@ BlockInputStreamPtr InterpreterDescribeQuery::executeImpl() for (const auto & column : columns) { res_columns[0]->insert(column.name); - res_columns[1]->insert(column.type->getName()); + + if (settings.describe_extend_object_type && storage_snapshot) + res_columns[1]->insert(storage_snapshot->getConcreteType(column.name)->getName()); + else + res_columns[1]->insert(column.type->getName()); if (column.default_desc.expression) { diff --git a/src/Storages/StorageSnapshot.cpp b/src/Storages/StorageSnapshot.cpp index cd1e516c7fe..ffff2ce47c4 100644 --- a/src/Storages/StorageSnapshot.cpp +++ b/src/Storages/StorageSnapshot.cpp @@ -92,4 +92,13 @@ void StorageSnapshot::check(const Names & column_names) const } } +DataTypePtr StorageSnapshot::getConcreteType(const String & column_name) const +{ + auto it = object_types.find(column_name); + if (it != object_types.end()) + return it->second; + + return metadata->getColumns().get(column_name).type; +} + } diff --git a/src/Storages/StorageSnapshot.h b/src/Storages/StorageSnapshot.h index a34eef8116a..a1bed1fc253 100644 --- a/src/Storages/StorageSnapshot.h +++ b/src/Storages/StorageSnapshot.h @@ -51,6 +51,8 @@ struct StorageSnapshot /// Verify that all the requested names are in the table and are set correctly: /// list of names is not empty and the names do not repeat. void check(const Names & column_names) const; + + DataTypePtr getConcreteType(const String & column_name) const; }; using StorageSnapshotPtr = std::shared_ptr; diff --git a/tests/queries/0_stateless/01825_type_json_describe.reference b/tests/queries/0_stateless/01825_type_json_describe.reference new file mode 100644 index 00000000000..629b60cb629 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_describe.reference @@ -0,0 +1,3 @@ +data Object(\'json\') +data Tuple(k1 Int8) +data Tuple(k1 String, k2 Array(Int8)) diff --git a/tests/queries/0_stateless/01825_type_json_describe.sql b/tests/queries/0_stateless/01825_type_json_describe.sql new file mode 100644 index 00000000000..4a99ebd3336 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_describe.sql @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS t_json_desc; + +CREATE TABLE t_json_desc (data JSON) ENGINE = MergeTree ORDER BY tuple(); + +INSERT INTO t_json_desc FORMAT JSONAsObject {"k1": 10} +; + +DESC TABLE t_json_desc; +DESC TABLE t_json_desc SETTINGS describe_extend_object_type = 1; + +INSERT INTO t_json_desc FORMAT JSONAsObject {"k1": "q", "k2": [1, 2, 3]} +; + +DESC TABLE t_json_desc SETTINGS describe_extend_object_type = 1; + +DROP TABLE IF EXISTS t_json_desc; From 6ba41dc265e183a87e4cd1535c9952f144e57203 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 12 Jul 2021 16:45:35 +0300 Subject: [PATCH 0044/1647] fix projections with storage snapshot --- src/Interpreters/InterpreterSelectQuery.cpp | 5 +-- .../QueryPlan/ReadFromMergeTree.cpp | 33 +++++++++---------- src/Processors/QueryPlan/ReadFromMergeTree.h | 5 +-- src/Storages/IStorage.cpp | 2 -- .../MergeTree/MergeTreeBlockReadUtils.cpp | 4 +-- src/Storages/MergeTree/MergeTreeData.cpp | 6 ---- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 26 +++++---------- .../MergeTree/MergeTreeDataSelectExecutor.h | 4 --- .../MergeTree/StorageFromMergeTreeDataPart.h | 2 -- src/Storages/StorageSnapshot.cpp | 2 +- src/Storages/StorageSnapshot.h | 7 ++++ 11 files changed, 37 insertions(+), 59 deletions(-) diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 6d641b539af..7d6b55d758a 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -620,7 +620,10 @@ Block InterpreterSelectQuery::getSampleBlockImpl() /// TODO how can we make IN index work if we cache parts before selecting a projection? /// XXX Used for IN set index analysis. Is this a proper way? if (query_info.projection) + { metadata_snapshot->selected_projection = query_info.projection->desc; + storage_snapshot->addProjection(query_info.projection->desc); + } } /// Do I need to perform the first part of the pipeline? @@ -1965,8 +1968,6 @@ void InterpreterSelectQuery::executeFetchColumns(QueryProcessingStage::Enum proc /// Create step which reads from empty source if storage has no data. if (!query_plan.isInitialized()) { - /// TODO: fix. - // const auto & metadata = query_info.projection ? query_info.projection->desc->metadata : metadata_snapshot; auto header = storage_snapshot->getSampleBlockForColumns(required_columns); addEmptySourceToQueryPlan(query_plan, header, query_info, context); } diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.cpp b/src/Processors/QueryPlan/ReadFromMergeTree.cpp index a3c4067000b..db5af2f3d26 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.cpp +++ b/src/Processors/QueryPlan/ReadFromMergeTree.cpp @@ -75,8 +75,6 @@ ReadFromMergeTree::ReadFromMergeTree( const MergeTreeData & data_, const SelectQueryInfo & query_info_, StorageSnapshotPtr storage_snapshot_, - StorageMetadataPtr metadata_snapshot_, - StorageMetadataPtr metadata_snapshot_base_, ContextPtr context_, size_t max_block_size_, size_t num_streams_, @@ -97,8 +95,7 @@ ReadFromMergeTree::ReadFromMergeTree( , prewhere_info(getPrewhereInfo(query_info)) , actions_settings(ExpressionActionsSettings::fromContext(context_)) , storage_snapshot(std::move(storage_snapshot_)) - , metadata_snapshot(std::move(metadata_snapshot_)) - , metadata_snapshot_base(std::move(metadata_snapshot_base_)) + , metadata_for_reading(storage_snapshot->getMetadataForQuery()) , context(std::move(context_)) , max_block_size(max_block_size_) , requested_num_streams(num_streams_) @@ -464,7 +461,7 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsWithOrder( { SortDescription sort_description; for (size_t j = 0; j < input_order_info->order_key_prefix_descr.size(); ++j) - sort_description.emplace_back(metadata_snapshot->getSortingKey().column_names[j], + sort_description.emplace_back(metadata_for_reading->getSortingKey().column_names[j], input_order_info->direction, 1); auto sorting_key_expr = std::make_shared(sorting_key_prefix_expr); @@ -691,7 +688,7 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( } auto sorting_expr = std::make_shared( - metadata_snapshot->getSortingKey().expression->getActionsDAG().clone()); + metadata_for_reading->getSortingKey().expression->getActionsDAG().clone()); pipe.addSimpleTransform([sorting_expr](const Block & header) { @@ -708,12 +705,12 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( continue; } - Names sort_columns = metadata_snapshot->getSortingKeyColumns(); + Names sort_columns = metadata_for_reading->getSortingKeyColumns(); SortDescription sort_description; size_t sort_columns_size = sort_columns.size(); sort_description.reserve(sort_columns_size); - Names partition_key_columns = metadata_snapshot->getPartitionKey().column_names; + Names partition_key_columns = metadata_for_reading->getPartitionKey().column_names; const auto & header = pipe.getHeader(); for (size_t i = 0; i < sort_columns_size; ++i) @@ -753,7 +750,7 @@ Pipe ReadFromMergeTree::spreadMarkRangesAmongStreamsFinal( out_projection = createProjection(pipe.getHeader()); auto sorting_expr = std::make_shared( - metadata_snapshot->getSortingKey().expression->getActionsDAG().clone()); + metadata_for_reading->getSortingKey().expression->getActionsDAG().clone()); pipe.addSimpleTransform([sorting_expr](const Block & header) { @@ -782,14 +779,14 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre /// If there are only virtual columns in the query, you must request at least one non-virtual one. if (result.column_names_to_read.empty()) { - NamesAndTypesList available_real_columns = metadata_snapshot->getColumns().getAllPhysical(); + NamesAndTypesList available_real_columns = metadata_for_reading->getColumns().getAllPhysical(); result.column_names_to_read.push_back(ExpressionActions::getSmallestColumn(available_real_columns)); } storage_snapshot->check(result.column_names_to_read); // Build and check if primary key is used when necessary - const auto & primary_key = metadata_snapshot->getPrimaryKey(); + const auto & primary_key = metadata_for_reading->getPrimaryKey(); Names primary_key_columns = primary_key.column_names; KeyCondition key_condition(query_info, context, primary_key_columns, primary_key.expression); @@ -805,12 +802,12 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre const auto & select = query_info.query->as(); MergeTreeDataSelectExecutor::filterPartsByPartition( - parts, part_values, metadata_snapshot_base, data, query_info, context, + parts, part_values, storage_snapshot->metadata, data, query_info, context, max_block_numbers_to_read.get(), log, result.index_stats); result.sampling = MergeTreeDataSelectExecutor::getSampling( - select, metadata_snapshot->getColumns().getAllPhysical(), parts, key_condition, - data, metadata_snapshot, context, sample_factor_column_queried, log); + select, metadata_for_reading->getColumns().getAllPhysical(), parts, key_condition, + data, metadata_for_reading, context, sample_factor_column_queried, log); if (result.sampling.read_nothing) return result; @@ -823,7 +820,7 @@ ReadFromMergeTree::AnalysisResult ReadFromMergeTree::selectRangesToRead(MergeTre result.parts_with_ranges = MergeTreeDataSelectExecutor::filterPartsByPrimaryKeyAndSkipIndexes( std::move(parts), - metadata_snapshot, + metadata_for_reading, query_info, context, key_condition, @@ -913,7 +910,7 @@ void ReadFromMergeTree::initializePipeline(QueryPipeline & pipeline, const Build if (select.final()) { /// Add columns needed to calculate the sorting expression and the sign. - std::vector add_columns = metadata_snapshot->getColumnsRequiredForSortingKey(); + std::vector add_columns = metadata_for_reading->getColumnsRequiredForSortingKey(); column_names_to_read.insert(column_names_to_read.end(), add_columns.begin(), add_columns.end()); if (!data.merging_params.sign_column.empty()) @@ -932,10 +929,10 @@ void ReadFromMergeTree::initializePipeline(QueryPipeline & pipeline, const Build else if ((settings.optimize_read_in_order || settings.optimize_aggregation_in_order) && input_order_info) { size_t prefix_size = input_order_info->order_key_prefix_descr.size(); - auto order_key_prefix_ast = metadata_snapshot->getSortingKey().expression_list_ast->clone(); + auto order_key_prefix_ast = metadata_for_reading->getSortingKey().expression_list_ast->clone(); order_key_prefix_ast->children.resize(prefix_size); - auto syntax_result = TreeRewriter(context).analyze(order_key_prefix_ast, metadata_snapshot->getColumns().getAllPhysical()); + auto syntax_result = TreeRewriter(context).analyze(order_key_prefix_ast, metadata_for_reading->getColumns().getAllPhysical()); auto sorting_key_prefix_expr = ExpressionAnalyzer(order_key_prefix_ast, syntax_result, context).getActionsDAG(false); pipe = spreadMarkRangesAmongStreamsWithOrder( diff --git a/src/Processors/QueryPlan/ReadFromMergeTree.h b/src/Processors/QueryPlan/ReadFromMergeTree.h index 764227ef304..be844ef0cc0 100644 --- a/src/Processors/QueryPlan/ReadFromMergeTree.h +++ b/src/Processors/QueryPlan/ReadFromMergeTree.h @@ -61,8 +61,6 @@ public: const MergeTreeData & data_, const SelectQueryInfo & query_info_, StorageSnapshotPtr storage_snapshot, - StorageMetadataPtr metadata_snapshot_, - StorageMetadataPtr metadata_snapshot_base_, ContextPtr context_, size_t max_block_size_, size_t num_streams_, @@ -94,8 +92,7 @@ private: ExpressionActionsSettings actions_settings; StorageSnapshotPtr storage_snapshot; - StorageMetadataPtr metadata_snapshot; - StorageMetadataPtr metadata_snapshot_base; + StorageMetadataPtr metadata_for_reading; ContextPtr context; diff --git a/src/Storages/IStorage.cpp b/src/Storages/IStorage.cpp index 2ccaf9b2c1c..80e5b52ec28 100644 --- a/src/Storages/IStorage.cpp +++ b/src/Storages/IStorage.cpp @@ -113,8 +113,6 @@ void IStorage::read( auto pipe = read(column_names, storage_snapshot, query_info, context, processed_stage, max_block_size, num_streams); if (pipe.empty()) { - /// TODO: fix - // const auto & metadata_for_query = query_info.projection ? query_info.projection->desc->metadata : storage_snapshot->metadata; auto header = storage_snapshot->getSampleBlockForColumns(column_names); InterpreterSelectQuery::addEmptySourceToQueryPlan(query_plan, header, query_info, context); } diff --git a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp index 88a596dd1ae..7eeb9189599 100644 --- a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp +++ b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp @@ -275,7 +275,7 @@ MergeTreeReadTaskColumns getReadTaskColumns( Names pre_column_names; /// inject columns required for defaults evaluation - bool should_reorder = !injectRequiredColumns(storage, storage_snapshot->metadata, data_part, column_names).empty(); + bool should_reorder = !injectRequiredColumns(storage, storage_snapshot->getMetadataForQuery(), data_part, column_names).empty(); if (prewhere_info) { @@ -300,7 +300,7 @@ MergeTreeReadTaskColumns getReadTaskColumns( if (pre_column_names.empty()) pre_column_names.push_back(column_names[0]); - const auto injected_pre_columns = injectRequiredColumns(storage, storage_snapshot->metadata, data_part, pre_column_names); + const auto injected_pre_columns = injectRequiredColumns(storage, storage_snapshot->getMetadataForQuery(), data_part, pre_column_names); if (!injected_pre_columns.empty()) should_reorder = true; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 74976c057ef..49f9eba5b0f 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -3890,8 +3890,6 @@ static void selectBestProjection( auto sum_marks = reader.estimateNumMarksToRead( projection_parts, candidate.required_columns, - storage_snapshot, - storage_snapshot->metadata, candidate.desc->metadata, query_info, // TODO syntax_analysis_result set in index query_context, @@ -3909,8 +3907,6 @@ static void selectBestProjection( sum_marks += reader.estimateNumMarksToRead( normal_parts, required_columns, - storage_snapshot, - storage_snapshot->metadata, storage_snapshot->metadata, query_info, // TODO syntax_analysis_result set in index query_context, @@ -4184,8 +4180,6 @@ bool MergeTreeData::getQueryProcessingStageWithAggregateProjection( min_sum_marks = reader.estimateNumMarksToRead( parts, analysis_result.required_columns, - storage_snapshot, - metadata_snapshot, metadata_snapshot, query_info, // TODO syntax_analysis_result set in index query_context, diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 6c2bb157c4f..b762ecc167b 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -134,7 +134,7 @@ QueryPlanPtr MergeTreeDataSelectExecutor::read( { const auto & settings = context->getSettingsRef(); const auto & parts = storage_snapshot->parts; - const auto & metadata_snapshot = storage_snapshot->metadata; + const auto & metadata_for_reading = storage_snapshot->getMetadataForQuery(); if (!query_info.projection) { @@ -142,8 +142,6 @@ QueryPlanPtr MergeTreeDataSelectExecutor::read( parts, column_names_to_return, storage_snapshot, - metadata_snapshot, - metadata_snapshot, query_info, context, max_block_size, @@ -151,7 +149,7 @@ QueryPlanPtr MergeTreeDataSelectExecutor::read( max_block_numbers_to_read); if (plan->isInitialized() && settings.allow_experimental_projection_optimization && settings.force_optimize_projection - && !metadata_snapshot->projections.empty()) + && !metadata_for_reading->projections.empty()) throw Exception( "No projection is used when allow_experimental_projection_optimization = 1 and force_optimize_projection = 1", ErrorCodes::PROJECTION_NOT_USED); @@ -189,8 +187,6 @@ QueryPlanPtr MergeTreeDataSelectExecutor::read( projection_parts, query_info.projection->required_columns, storage_snapshot, - metadata_snapshot, - query_info.projection->desc->metadata, query_info, context, max_block_size, @@ -1090,8 +1086,6 @@ static void selectColumnNames( size_t MergeTreeDataSelectExecutor::estimateNumMarksToRead( MergeTreeData::DataPartsVector parts, const Names & column_names_to_return, - const StorageSnapshotPtr & storage_snapshot, - const StorageMetadataPtr & metadata_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, ContextPtr context, @@ -1114,14 +1108,14 @@ size_t MergeTreeDataSelectExecutor::estimateNumMarksToRead( if (part_values && part_values->empty()) return 0; + auto physical_columns = metadata_snapshot->getColumns().getAllPhysical(); + /// If there are only virtual columns in the query, you must request at least one non-virtual one. if (real_column_names.empty()) - { - NamesAndTypesList available_real_columns = metadata_snapshot->getColumns().getAllPhysical(); - real_column_names.push_back(ExpressionActions::getSmallestColumn(available_real_columns)); - } + real_column_names.push_back(ExpressionActions::getSmallestColumn(physical_columns)); - storage_snapshot->check(real_column_names); + /// TODO: do we support dynamic columns here? + metadata_snapshot->check(physical_columns.addTypes(real_column_names)); const auto & primary_key = metadata_snapshot->getPrimaryKey(); Names primary_key_columns = primary_key.column_names; @@ -1140,7 +1134,7 @@ size_t MergeTreeDataSelectExecutor::estimateNumMarksToRead( ReadFromMergeTree::IndexStats index_stats; filterPartsByPartition( - parts, part_values, metadata_snapshot_base, data, query_info, + parts, part_values, metadata_snapshot, data, query_info, context, max_block_numbers_to_read.get(), log, index_stats); auto sampling = MergeTreeDataSelectExecutor::getSampling( @@ -1172,8 +1166,6 @@ QueryPlanPtr MergeTreeDataSelectExecutor::readFromParts( MergeTreeData::DataPartsVector parts, const Names & column_names_to_return, const StorageSnapshotPtr & storage_snapshot, - const StorageMetadataPtr & metadata_snapshot_base, - const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, ContextPtr context, const UInt64 max_block_size, @@ -1199,8 +1191,6 @@ QueryPlanPtr MergeTreeDataSelectExecutor::readFromParts( data, query_info, storage_snapshot, - metadata_snapshot, - metadata_snapshot_base, context, max_block_size, num_streams, diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h index 88443c0febc..d2e26d870ae 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.h @@ -50,8 +50,6 @@ public: MergeTreeData::DataPartsVector parts, const Names & column_names, const StorageSnapshotPtr & storage_snapshot, - const StorageMetadataPtr & metadata_snapshot_base, - const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, ContextPtr context, UInt64 max_block_size, @@ -64,8 +62,6 @@ public: size_t estimateNumMarksToRead( MergeTreeData::DataPartsVector parts, const Names & column_names, - const StorageSnapshotPtr & storage_snapshot, - const StorageMetadataPtr & storage_snapshot_base, const StorageMetadataPtr & metadata_snapshot, const SelectQueryInfo & query_info, ContextPtr context, diff --git a/src/Storages/MergeTree/StorageFromMergeTreeDataPart.h b/src/Storages/MergeTree/StorageFromMergeTreeDataPart.h index c14964c9e23..a938fb3f573 100644 --- a/src/Storages/MergeTree/StorageFromMergeTreeDataPart.h +++ b/src/Storages/MergeTree/StorageFromMergeTreeDataPart.h @@ -37,8 +37,6 @@ public: parts, column_names, storage_snapshot, - storage_snapshot->metadata, - storage_snapshot->metadata, query_info, context, max_block_size, diff --git a/src/Storages/StorageSnapshot.cpp b/src/Storages/StorageSnapshot.cpp index ffff2ce47c4..982057bf72c 100644 --- a/src/Storages/StorageSnapshot.cpp +++ b/src/Storages/StorageSnapshot.cpp @@ -15,7 +15,7 @@ namespace ErrorCodes NamesAndTypesList StorageSnapshot::getColumns(const GetColumnsOptions & options) const { - auto all_columns = metadata->getColumns().get(options); + auto all_columns = getMetadataForQuery()->getColumns().get(options); if (options.with_virtuals) { diff --git a/src/Storages/StorageSnapshot.h b/src/Storages/StorageSnapshot.h index a1bed1fc253..1ee61c059a0 100644 --- a/src/Storages/StorageSnapshot.h +++ b/src/Storages/StorageSnapshot.h @@ -19,6 +19,9 @@ struct StorageSnapshot const NameToTypeMap object_types; const DataPartsVector parts; + /// TODO: fix + mutable const ProjectionDescription * projection = nullptr; + StorageSnapshot( const IStorage & storage_, const StorageMetadataPtr & metadata_) @@ -53,6 +56,10 @@ struct StorageSnapshot void check(const Names & column_names) const; DataTypePtr getConcreteType(const String & column_name) const; + + void addProjection(const ProjectionDescription * projection_) const { projection = projection_; } + + StorageMetadataPtr getMetadataForQuery() const { return (projection ? projection->metadata : metadata); } }; using StorageSnapshotPtr = std::shared_ptr; From 5d175bf557bc46c540d2da76258a24fc0f0bb94b Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 12 Jul 2021 17:54:02 +0300 Subject: [PATCH 0045/1647] dynamic columns: support distributed tables --- src/Core/Settings.h | 2 +- src/Interpreters/InterpreterDescribeQuery.cpp | 2 +- src/Storages/StorageDistributed.cpp | 28 ++++++++ src/Storages/StorageDistributed.h | 2 + src/Storages/getStructureOfRemoteTable.cpp | 64 +++++++++++++++++++ src/Storages/getStructureOfRemoteTable.h | 6 ++ .../01825_type_json_distributed.reference | 4 ++ .../01825_type_json_distributed.sql | 14 ++++ 8 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_distributed.reference create mode 100644 tests/queries/0_stateless/01825_type_json_distributed.sql diff --git a/src/Core/Settings.h b/src/Core/Settings.h index 5013366101e..ff4ef241bd3 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -451,7 +451,7 @@ class IColumn; M(Bool, force_optimize_projection, false, "If projection optimization is enabled, SELECT queries need to use projection", 0) \ M(Bool, async_socket_for_remote, true, "Asynchronously read from socket executing remote query", 0) \ M(Bool, insert_null_as_default, true, "Insert DEFAULT values instead of NULL in INSERT SELECT (UNION ALL)", 0) \ - M(Bool, describe_extend_object_type, false, "Deduce concrete type of columns of type Object in DESCRIBE query", 0) \ + M(Bool, describe_extend_object_types, false, "Deduce concrete type of columns of type Object in DESCRIBE query", 0) \ \ M(Bool, optimize_rewrite_sum_if_to_count_if, true, "Rewrite sumIf() and sum(if()) function countIf() function when logically equivalent", 0) \ M(UInt64, insert_shard_id, 0, "If non zero, when insert into a distributed table, the data will be inserted into the shard `insert_shard_id` synchronously. Possible values range from 1 to `shards_number` of corresponding distributed table", 0) \ diff --git a/src/Interpreters/InterpreterDescribeQuery.cpp b/src/Interpreters/InterpreterDescribeQuery.cpp index 72cf2dc18ab..5c976c1cd23 100644 --- a/src/Interpreters/InterpreterDescribeQuery.cpp +++ b/src/Interpreters/InterpreterDescribeQuery.cpp @@ -100,7 +100,7 @@ BlockInputStreamPtr InterpreterDescribeQuery::executeImpl() { res_columns[0]->insert(column.name); - if (settings.describe_extend_object_type && storage_snapshot) + if (settings.describe_extend_object_types && storage_snapshot) res_columns[1]->insert(storage_snapshot->getConcreteType(column.name)->getName()); else res_columns[1]->insert(column.type->getName()); diff --git a/src/Storages/StorageDistributed.cpp b/src/Storages/StorageDistributed.cpp index c1983993d96..946513988df 100644 --- a/src/Storages/StorageDistributed.cpp +++ b/src/Storages/StorageDistributed.cpp @@ -9,10 +9,12 @@ #include #include #include +#include #include #include #include +#include #include @@ -559,6 +561,32 @@ QueryProcessingStage::Enum StorageDistributed::getQueryProcessingStage( return QueryProcessingStage::WithMergeableState; } +StorageSnapshotPtr StorageDistributed::getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const +{ + auto names_of_objects = getNamesOfObjectColumns(metadata_snapshot->getColumns().getAllPhysical()); + if (names_of_objects.empty()) + return std::make_shared(*this, metadata_snapshot); + + auto columns_in_tables = getExtendedColumnsOfRemoteTables(*getCluster(), StorageID{remote_database, remote_table}, getContext()); + assert(!columns_in_tables.empty()); + + std::unordered_map types_in_tables; + for (const auto & columns : columns_in_tables) + { + for (const auto & [name, type] : columns) + { + if (names_of_objects.count(name)) + types_in_tables[name].push_back(type); + } + } + + StorageSnapshot::NameToTypeMap object_types; + for (const auto & [name, types] : types_in_tables) + object_types.emplace(name, getLeastCommonTypeForObject(types)); + + return std::make_shared(*this, metadata_snapshot, object_types); +} + Pipe StorageDistributed::read( const Names & column_names, const StorageSnapshotPtr & storage_snapshot, diff --git a/src/Storages/StorageDistributed.h b/src/Storages/StorageDistributed.h index 6cd1115e9a9..e1932543a5e 100644 --- a/src/Storages/StorageDistributed.h +++ b/src/Storages/StorageDistributed.h @@ -56,6 +56,8 @@ public: bool isRemote() const override { return true; } + StorageSnapshotPtr getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const override; + QueryProcessingStage::Enum getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageSnapshotPtr &, SelectQueryInfo &) const override; diff --git a/src/Storages/getStructureOfRemoteTable.cpp b/src/Storages/getStructureOfRemoteTable.cpp index fb828b8f744..247f9a23c56 100644 --- a/src/Storages/getStructureOfRemoteTable.cpp +++ b/src/Storages/getStructureOfRemoteTable.cpp @@ -60,6 +60,7 @@ ColumnsDescription getStructureOfRemoteTableInShard( ColumnsDescription res; auto new_context = ClusterProxy::updateSettingsForCluster(cluster, context, context->getSettingsRef()); + new_context->setSetting("describe_extend_object_types", true); /// Expect only needed columns from the result of DESC TABLE. NOTE 'comment' column is ignored for compatibility reasons. Block sample_block @@ -150,4 +151,67 @@ ColumnsDescription getStructureOfRemoteTable( ErrorCodes::NO_REMOTE_SHARD_AVAILABLE); } +std::vector getExtendedColumnsOfRemoteTables( + const Cluster & cluster, + const StorageID & remote_table_id, + ContextPtr context) +{ + const auto & shards_info = cluster.getShardsInfo(); + auto query = "DESC TABLE " + remote_table_id.getFullTableName(); + + auto new_context = ClusterProxy::updateSettingsForCluster(cluster, context, context->getSettingsRef()); + new_context->setSetting("describe_extend_object_types", true); + + /// Expect only needed columns from the result of DESC TABLE. + Block sample_block + { + { ColumnString::create(), std::make_shared(), "name" }, + { ColumnString::create(), std::make_shared(), "type" }, + }; + + auto execute_query_on_shard = [&](const auto & shard_info) + { + /// Execute remote query without restrictions (because it's not real user query, but part of implementation) + auto input = std::make_shared(shard_info.pool, query, sample_block, new_context); + + input->setPoolMode(PoolMode::GET_ONE); + input->setMainTable(remote_table_id); + input->readPrefix(); + + NamesAndTypesList res; + while (auto block = input->read()) + { + const auto & name_col = *block.getByName("name").column; + const auto & type_col = *block.getByName("type").column; + + size_t size = name_col.size(); + for (size_t i = 0; i < size; ++i) + { + auto name = name_col[i].template get(); + auto type_name = type_col[i].template get(); + + res.emplace_back(std::move(name), DataTypeFactory::instance().get(type_name)); + } + } + + return res; + }; + + std::vector columns; + for (const auto & shard_info : shards_info) + { + auto res = execute_query_on_shard(shard_info); + + /// Expect at least some columns. + /// This is a hack to handle the empty block case returned by Connection when skip_unavailable_shards is set. + if (!res.empty()) + columns.emplace_back(std::move(res)); + } + + if (columns.empty()) + throw NetException("All attempts to get table structure failed", ErrorCodes::NO_REMOTE_SHARD_AVAILABLE); + + return columns; +} + } diff --git a/src/Storages/getStructureOfRemoteTable.h b/src/Storages/getStructureOfRemoteTable.h index 3f77236c756..5ca15055b8f 100644 --- a/src/Storages/getStructureOfRemoteTable.h +++ b/src/Storages/getStructureOfRemoteTable.h @@ -8,6 +8,7 @@ namespace DB { + class Context; struct StorageID; @@ -19,4 +20,9 @@ ColumnsDescription getStructureOfRemoteTable( ContextPtr context, const ASTPtr & table_func_ptr = nullptr); +std::vector getExtendedColumnsOfRemoteTables( + const Cluster & cluster, + const StorageID & remote_table_id, + ContextPtr context); + } diff --git a/tests/queries/0_stateless/01825_type_json_distributed.reference b/tests/queries/0_stateless/01825_type_json_distributed.reference new file mode 100644 index 00000000000..21308748ef3 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_distributed.reference @@ -0,0 +1,4 @@ +(2,'qqq',[44,55]) Tuple(k1 Int8, `k2.k3` String, `k2.k4` Array(Int8)) +(2,'qqq',[44,55]) Tuple(k1 Int8, `k2.k3` String, `k2.k4` Array(Int8)) +2 qqq [44,55] +2 qqq [44,55] diff --git a/tests/queries/0_stateless/01825_type_json_distributed.sql b/tests/queries/0_stateless/01825_type_json_distributed.sql new file mode 100644 index 00000000000..faa0717e21e --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_distributed.sql @@ -0,0 +1,14 @@ +DROP TABLE IF EXISTS t_json_local; +DROP TABLE IF EXISTS t_json_dist; + +CREATE TABLE t_json_local(data JSON) ENGINE = MergeTree ORDER BY tuple(); +CREATE TABLE t_json_dist AS t_json_local ENGINE = Distributed(test_cluster_two_shards, currentDatabase(), t_json_local); + +INSERT INTO t_json_local FORMAT JSONAsObject {"k1": 2, "k2": {"k3": "qqq", "k4": [44, 55]}} +; + +SELECT data, toTypeName(data) FROM t_json_dist; +SELECT data.k1, data.k2.k3, data.k2.k4 FROM t_json_dist; + +DROP TABLE IF EXISTS t_json_local; +DROP TABLE IF EXISTS t_json_dist; From c7214cd42c5ec1fffe48612435188721d54ffbbb Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 13 Jul 2021 15:39:12 +0300 Subject: [PATCH 0046/1647] dynamic columns: add integration test for distributed --- .../test_distributed_type_object/__init__.py | 0 .../configs/remote_servers.xml | 18 ++++++++++ .../test_distributed_type_object/test.py | 36 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 tests/integration/test_distributed_type_object/__init__.py create mode 100644 tests/integration/test_distributed_type_object/configs/remote_servers.xml create mode 100644 tests/integration/test_distributed_type_object/test.py diff --git a/tests/integration/test_distributed_type_object/__init__.py b/tests/integration/test_distributed_type_object/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_distributed_type_object/configs/remote_servers.xml b/tests/integration/test_distributed_type_object/configs/remote_servers.xml new file mode 100644 index 00000000000..ebce4697529 --- /dev/null +++ b/tests/integration/test_distributed_type_object/configs/remote_servers.xml @@ -0,0 +1,18 @@ + + + + + + node1 + 9000 + + + + + node2 + 9000 + + + + + diff --git a/tests/integration/test_distributed_type_object/test.py b/tests/integration/test_distributed_type_object/test.py new file mode 100644 index 00000000000..7ceb538cf45 --- /dev/null +++ b/tests/integration/test_distributed_type_object/test.py @@ -0,0 +1,36 @@ +import pytest + +from helpers.cluster import ClickHouseCluster +from helpers.test_tools import TSV + +cluster = ClickHouseCluster(__file__) + +node1 = cluster.add_instance('node1', main_configs=['configs/remote_servers.xml']) +node2 = cluster.add_instance('node2', main_configs=['configs/remote_servers.xml']) + +@pytest.fixture(scope="module") +def started_cluster(): + try: + cluster.start() + + for node in (node1, node2): + node.query("CREATE TABLE local_table(id UInt32, data JSON) ENGINE = MergeTree ORDER BY id") + node.query("CREATE TABLE dist_table AS local_table ENGINE = Distributed(test_cluster, default, local_table)") + + yield cluster + + finally: + cluster.shutdown() + + +def test_distributed_type_object(started_cluster): + node1.query('INSERT INTO local_table FORMAT JSONEachRow {"id": 1, "data": {"k1": 10}}') + node2.query('INSERT INTO local_table FORMAT JSONEachRow {"id": 2, "data": {"k1": 20}}') + + expected = TSV("10\n20\n") + assert TSV(node1.query("SELECT data.k1 FROM dist_table ORDER BY id")) == expected + + node1.query('INSERT INTO local_table FORMAT JSONEachRow {"id": 3, "data": {"k1": "str1"}}') + + expected = TSV("10\n20\nstr1\n") + assert TSV(node1.query("SELECT data.k1 FROM dist_table ORDER BY id")) == expected From 1685ca482639073fde83e67fb823b1ee6e8a47f2 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 20 Jul 2021 15:19:58 +0300 Subject: [PATCH 0047/1647] dynamic columns: fix with compact parts --- src/Storages/MergeTree/IMergeTreeReader.cpp | 2 +- src/Storages/getStructureOfRemoteTable.cpp | 2 -- tests/queries/0_stateless/01825_type_json.sql | 3 +-- tests/queries/0_stateless/01825_type_json_2.sql | 3 +-- tests/queries/0_stateless/01825_type_json_3.sql | 3 +-- tests/queries/0_stateless/01825_type_json_4.sh | 3 +-- tests/queries/0_stateless/01825_type_json_describe.sql | 4 ++-- 7 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Storages/MergeTree/IMergeTreeReader.cpp b/src/Storages/MergeTree/IMergeTreeReader.cpp index 14187564536..bfc015b80c6 100644 --- a/src/Storages/MergeTree/IMergeTreeReader.cpp +++ b/src/Storages/MergeTree/IMergeTreeReader.cpp @@ -232,7 +232,7 @@ NameAndTypePair IMergeTreeReader::getColumnFromPart(const NameAndTypePair & requ auto subcolumn_type = it->second->tryGetSubcolumnType(subcolumn_name); if (!subcolumn_type) - return required_column; + return {required_column.name, required_column.type}; return {it->first, subcolumn_name, it->second, subcolumn_type}; } diff --git a/src/Storages/getStructureOfRemoteTable.cpp b/src/Storages/getStructureOfRemoteTable.cpp index 247f9a23c56..e2cd2b492e7 100644 --- a/src/Storages/getStructureOfRemoteTable.cpp +++ b/src/Storages/getStructureOfRemoteTable.cpp @@ -58,9 +58,7 @@ ColumnsDescription getStructureOfRemoteTableInShard( } ColumnsDescription res; - auto new_context = ClusterProxy::updateSettingsForCluster(cluster, context, context->getSettingsRef()); - new_context->setSetting("describe_extend_object_types", true); /// Expect only needed columns from the result of DESC TABLE. NOTE 'comment' column is ignored for compatibility reasons. Block sample_block diff --git a/tests/queries/0_stateless/01825_type_json.sql b/tests/queries/0_stateless/01825_type_json.sql index 668b39db70f..73110cd866d 100644 --- a/tests/queries/0_stateless/01825_type_json.sql +++ b/tests/queries/0_stateless/01825_type_json.sql @@ -1,8 +1,7 @@ DROP TABLE IF EXISTS t_json; CREATE TABLE t_json(id UInt64, data Object('JSON')) -ENGINE = MergeTree ORDER BY tuple() -SETTINGS min_bytes_for_wide_part = 0; +ENGINE = MergeTree ORDER BY tuple(); SYSTEM STOP MERGES t_json; diff --git a/tests/queries/0_stateless/01825_type_json_2.sql b/tests/queries/0_stateless/01825_type_json_2.sql index fbea36978c6..fa87aacae29 100644 --- a/tests/queries/0_stateless/01825_type_json_2.sql +++ b/tests/queries/0_stateless/01825_type_json_2.sql @@ -1,8 +1,7 @@ DROP TABLE IF EXISTS t_json_2; CREATE TABLE t_json_2(id UInt64, data Object('JSON')) -ENGINE = MergeTree ORDER BY tuple() -SETTINGS min_bytes_for_wide_part = 0; +ENGINE = MergeTree ORDER BY tuple(); INSERT INTO t_json_2 FORMAT JSONEachRow {"id": 1, "data": {"k1": 1, "k2" : 2}} {"id": 2, "data": {"k2": 3, "k3" : 4}}; diff --git a/tests/queries/0_stateless/01825_type_json_3.sql b/tests/queries/0_stateless/01825_type_json_3.sql index cb30f1002fd..a90ec162609 100644 --- a/tests/queries/0_stateless/01825_type_json_3.sql +++ b/tests/queries/0_stateless/01825_type_json_3.sql @@ -1,8 +1,7 @@ DROP TABLE IF EXISTS t_json_3; CREATE TABLE t_json_3(id UInt64, data JSON) -ENGINE = MergeTree ORDER BY tuple() -SETTINGS min_bytes_for_wide_part = 0; +ENGINE = MergeTree ORDER BY tuple(); SYSTEM STOP MERGES t_json_3; diff --git a/tests/queries/0_stateless/01825_type_json_4.sh b/tests/queries/0_stateless/01825_type_json_4.sh index 1b54ea6c1ef..96609e82e60 100755 --- a/tests/queries/0_stateless/01825_type_json_4.sh +++ b/tests/queries/0_stateless/01825_type_json_4.sh @@ -7,8 +7,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) $CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json_4" $CLICKHOUSE_CLIENT -q "CREATE TABLE t_json_4(id UInt64, data JSON) \ -ENGINE = MergeTree ORDER BY tuple() \ -SETTINGS min_bytes_for_wide_part = 0" +ENGINE = MergeTree ORDER BY tuple()" echo '{"id": 1, "data": {"k1": "v1"}}, {"id": 2, "data": {"k1": [1, 2]}}' \ | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 591" diff --git a/tests/queries/0_stateless/01825_type_json_describe.sql b/tests/queries/0_stateless/01825_type_json_describe.sql index 4a99ebd3336..fa002e9cbff 100644 --- a/tests/queries/0_stateless/01825_type_json_describe.sql +++ b/tests/queries/0_stateless/01825_type_json_describe.sql @@ -6,11 +6,11 @@ INSERT INTO t_json_desc FORMAT JSONAsObject {"k1": 10} ; DESC TABLE t_json_desc; -DESC TABLE t_json_desc SETTINGS describe_extend_object_type = 1; +DESC TABLE t_json_desc SETTINGS describe_extend_object_types = 1; INSERT INTO t_json_desc FORMAT JSONAsObject {"k1": "q", "k2": [1, 2, 3]} ; -DESC TABLE t_json_desc SETTINGS describe_extend_object_type = 1; +DESC TABLE t_json_desc SETTINGS describe_extend_object_types = 1; DROP TABLE IF EXISTS t_json_desc; From af65637ca256ea5cacb7487965bc5ea614774cb1 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 21 Jul 2021 17:45:19 +0300 Subject: [PATCH 0048/1647] fix subcolumns of object --- src/DataTypes/ObjectUtils.cpp | 16 +-- src/DataTypes/ObjectUtils.h | 2 +- src/Storages/ColumnsDescription.cpp | 16 +++ src/Storages/ColumnsDescription.h | 2 + .../MergeTree/MergeTreeDataMergerMutator.cpp | 2 +- src/Storages/StorageSnapshot.cpp | 103 ++++++++++++++---- src/Storages/StorageSnapshot.h | 15 ++- 7 files changed, 121 insertions(+), 35 deletions(-) diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index c2b13fd8db3..f0fe9ee8f3b 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -200,33 +200,29 @@ NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list) return res; } -NamesAndTypesList extendObjectColumns(const NamesAndTypesList & columns_list, const NameToTypeMap & object_types, bool with_subcolumns) +void extendObjectColumns(NamesAndTypesList & columns_list, const NameToTypeMap & object_types, bool with_subcolumns) { - NamesAndTypesList result_columns; - for (const auto & column : columns_list) + NamesAndTypesList subcolumns_list; + for (auto & column : columns_list) { auto it = object_types.find(column.name); if (it != object_types.end()) { const auto & object_type = it->second; - result_columns.emplace_back(column.name, object_type); + column.type = object_type; if (with_subcolumns) { for (const auto & subcolumn : object_type->getSubcolumnNames()) { - result_columns.emplace_back(column.name, subcolumn, + subcolumns_list.emplace_back(column.name, subcolumn, object_type, object_type->getSubcolumnType(subcolumn)); } } } - else - { - result_columns.push_back(column); - } } - return result_columns; + columns_list.splice(columns_list.end(), std::move(subcolumns_list)); } void finalizeObjectColumns(MutableColumns & columns) diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index cd0922c6817..bb79b818c8e 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -18,7 +18,7 @@ DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns); DataTypePtr getLeastCommonTypeForObject(const DataTypes & types); NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list); -NamesAndTypesList extendObjectColumns(const NamesAndTypesList & columns_list, const NameToTypeMap & object_types, bool with_subcolumns); +void extendObjectColumns(NamesAndTypesList & columns_list, const NameToTypeMap & object_types, bool with_subcolumns); void finalizeObjectColumns(MutableColumns & columns); diff --git a/src/Storages/ColumnsDescription.cpp b/src/Storages/ColumnsDescription.cpp index 4efaf99bbdc..6bf7d23d4f6 100644 --- a/src/Storages/ColumnsDescription.cpp +++ b/src/Storages/ColumnsDescription.cpp @@ -489,6 +489,22 @@ Names ColumnsDescription::getNamesOfPhysical() const return ret; } +std::optional ColumnsDescription::tryGetColumn(const GetColumnsOptions & options, const String & column_name) const +{ + auto it = columns.get<1>().find(column_name); + if (it != columns.get<1>().end() && (defaultKindToGetKind(it->default_desc.kind) & options.kind)) + return NameAndTypePair(it->name, it->type); + + if (options.with_subcolumns) + { + auto jt = subcolumns.get<0>().find(column_name); + if (jt != subcolumns.get<0>().end()) + return *jt; + } + + return {}; +} + std::optional ColumnsDescription::tryGetColumnOrSubcolumn(GetColumnsOptions::Kind kind, const String & column_name) const { auto it = columns.get<1>().find(column_name); diff --git a/src/Storages/ColumnsDescription.h b/src/Storages/ColumnsDescription.h index 9b765b80763..ff3e4bdfd24 100644 --- a/src/Storages/ColumnsDescription.h +++ b/src/Storages/ColumnsDescription.h @@ -164,6 +164,8 @@ public: std::optional tryGetPhysical(const String & column_name) const; std::optional tryGetColumnOrSubcolumn(GetColumnsOptions::Kind kind, const String & column_name) const; + std::optional tryGetColumn(const GetColumnsOptions & options, const String & column_name) const; + ColumnDefaults getDefaults() const; /// TODO: remove bool hasDefault(const String & column_name) const; bool hasDefaults() const; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index cd7311b231e..3ecf8dc4341 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -722,7 +722,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor auto object_types = MergeTreeData::getObjectTypes(parts, getNamesOfObjectColumns(storage_columns)); auto storage_snapshot = std::make_shared(data, metadata_snapshot, object_types, parts); - storage_columns = extendObjectColumns(storage_columns, object_types, false); + extendObjectColumns(storage_columns, object_types, false); const auto data_settings = data.getSettings(); diff --git a/src/Storages/StorageSnapshot.cpp b/src/Storages/StorageSnapshot.cpp index 19982e779d2..68b76e8508e 100644 --- a/src/Storages/StorageSnapshot.cpp +++ b/src/Storages/StorageSnapshot.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -45,20 +46,28 @@ namespace } } +void StorageSnapshot::init() +{ + for (const auto & [name, type] : storage.getVirtuals()) + virtual_columns[name] = type; + + for (const auto & [name, type] : object_types) + { + for (const auto & subcolumn : type->getSubcolumnNames()) + { + auto full_name = Nested::concatenateName(name, subcolumn); + object_subcolumns[full_name] = {name, subcolumn, type, type->getSubcolumnType(subcolumn)}; + } + } +} + NamesAndTypesList StorageSnapshot::getColumns(const GetColumnsOptions & options) const { auto all_columns = getMetadataForQuery()->getColumns().get(options); - return addVirtualsAndObjects(options, std::move(all_columns)); -} -NamesAndTypesList StorageSnapshot::getColumnsByNames(const GetColumnsOptions & options, const Names & names) const -{ - auto all_columns = getMetadataForQuery()->getColumns().getByNames(options, names); - return addVirtualsAndObjects(options, std::move(all_columns)); -} + if (options.with_extended_objects) + extendObjectColumns(all_columns, object_types, options.with_subcolumns); -NamesAndTypesList StorageSnapshot::addVirtualsAndObjects(const GetColumnsOptions & options, NamesAndTypesList columns_list) const -{ if (options.with_virtuals) { /// Virtual columns must be appended after ordinary, @@ -67,33 +76,75 @@ NamesAndTypesList StorageSnapshot::addVirtualsAndObjects(const GetColumnsOptions if (!virtuals.empty()) { NameSet column_names; - for (const auto & column : columns_list) + for (const auto & column : all_columns) column_names.insert(column.name); for (auto && column : virtuals) if (!column_names.count(column.name)) - columns_list.push_back(std::move(column)); + all_columns.push_back(std::move(column)); } } - if (options.with_extended_objects) - columns_list = extendObjectColumns(columns_list, object_types, options.with_subcolumns); + return all_columns; +} - return columns_list; +NamesAndTypesList StorageSnapshot::getColumnsByNames(const GetColumnsOptions & options, const Names & names) const +{ + NamesAndTypesList res; + const auto & columns = getMetadataForQuery()->getColumns(); + for (const auto & name : names) + { + auto column = columns.tryGetColumn(options, name); + if (column && !isObject(column->type)) + { + res.emplace_back(std::move(*column)); + continue; + } + + if (options.with_extended_objects) + { + auto it = object_types.find(name); + if (it != object_types.end()) + { + res.emplace_back(name, it->second); + continue; + } + + if (options.with_subcolumns) + { + auto jt = object_subcolumns.find(name); + if (jt != object_subcolumns.end()) + { + res.emplace_back(jt->second); + continue; + } + } + } + + if (options.with_virtuals) + { + auto it = virtual_columns.find(name); + if (it != virtual_columns.end()) + { + res.emplace_back(name, it->second); + continue; + } + } + + throw Exception(ErrorCodes::NO_SUCH_COLUMN_IN_TABLE, "There is no column {} in table", name); + } + + return res; } Block StorageSnapshot::getSampleBlockForColumns(const Names & column_names) const { Block res; - /// Virtual columns must be appended after ordinary, because user can - /// override them. - const auto virtuals_map = getColumnsMap(storage.getVirtuals()); const auto & columns = getMetadataForQuery()->getColumns(); - for (const auto & name : column_names) { auto column = columns.tryGetColumnOrSubcolumn(GetColumnsOptions::All, name); - if (column) + if (column && !isObject(column->type)) { res.insert({column->type->createColumn(), column->type, column->name}); } @@ -102,9 +153,16 @@ Block StorageSnapshot::getSampleBlockForColumns(const Names & column_names) cons const auto & type = it->second; res.insert({type->createColumn(), type, name}); } - else if (auto jt = virtuals_map.find(name); jt != virtuals_map.end()) + else if (auto jt = object_subcolumns.find(name); jt != object_subcolumns.end()) { - const auto & type = *jt->second; + const auto & type = jt->second.type; + res.insert({type->createColumn(), type, name}); + } + else if (auto kt = virtual_columns.find(name); kt != virtual_columns.end()) + { + /// Virtual columns must be appended after ordinary, because user can + /// override them. + const auto & type = kt->second; res.insert({type->createColumn(), type, name}); } else @@ -131,7 +189,8 @@ void StorageSnapshot::check(const Names & column_names) const for (const auto & name : column_names) { - bool has_column = columns.hasColumnOrSubcolumn(GetColumnsOptions::AllPhysical, name) || virtuals_map.count(name); + bool has_column = columns.hasColumnOrSubcolumn(GetColumnsOptions::AllPhysical, name) + || object_subcolumns.count(name) || virtuals_map.count(name); if (!has_column) { diff --git a/src/Storages/StorageSnapshot.h b/src/Storages/StorageSnapshot.h index 8793963fa0d..293357c3f83 100644 --- a/src/Storages/StorageSnapshot.h +++ b/src/Storages/StorageSnapshot.h @@ -1,5 +1,6 @@ #pragma once #include +// #include namespace DB { @@ -10,6 +11,12 @@ class IMergeTreeDataPart; using DataPartPtr = std::shared_ptr; using DataPartsVector = std::vector; +// #if !defined(ARCADIA_BUILD) +// using NamesAndTypesMap = google::dense_hash_map; +// #else +// using NamesAndTypesMap = google::sparsehash::dense_hash_map; +// #endif + struct StorageSnapshot { using NameToTypeMap = std::unordered_map; @@ -27,6 +34,7 @@ struct StorageSnapshot const StorageMetadataPtr & metadata_) : storage(storage_), metadata(metadata_) { + init(); } StorageSnapshot( @@ -35,6 +43,7 @@ struct StorageSnapshot const NameToTypeMap & object_types_) : storage(storage_), metadata(metadata_), object_types(object_types_) { + init(); } StorageSnapshot( @@ -44,6 +53,7 @@ struct StorageSnapshot const DataPartsVector & parts_) : storage(storage_), metadata(metadata_), object_types(object_types_), parts(parts_) { + init(); } NamesAndTypesList getColumns(const GetColumnsOptions & options) const; @@ -63,7 +73,10 @@ struct StorageSnapshot StorageMetadataPtr getMetadataForQuery() const { return (projection ? projection->metadata : metadata); } private: - NamesAndTypesList addVirtualsAndObjects(const GetColumnsOptions & options, NamesAndTypesList columns_list) const; + void init(); + + std::unordered_map object_subcolumns; + NameToTypeMap virtual_columns; }; using StorageSnapshotPtr = std::shared_ptr; From 2b58f39c10e7b2e935b01de9807e3e79fd4667c7 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 23 Jul 2021 19:30:18 +0300 Subject: [PATCH 0049/1647] dynamic columns: support missed columns in distributed --- src/DataTypes/ObjectUtils.cpp | 76 ++++++++++-- src/DataTypes/ObjectUtils.h | 10 +- .../ClusterProxy/SelectStreamFactory.cpp | 11 +- .../ClusterProxy/SelectStreamFactory.h | 7 ++ .../ClusterProxy/executeQuery.cpp | 2 +- src/Storages/ColumnsDescription.cpp | 70 ++++------- src/Storages/ColumnsDescription.h | 5 +- .../MergeTree/MergeTreeBlockReadUtils.cpp | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 18 +-- src/Storages/MergeTree/MergeTreeData.h | 7 +- .../MergeTree/MergeTreeDataMergerMutator.cpp | 6 +- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 4 +- src/Storages/StorageDistributed.cpp | 28 +++-- src/Storages/StorageDistributed.h | 6 + src/Storages/StorageSnapshot.cpp | 112 +++++++----------- src/Storages/StorageSnapshot.h | 29 +++-- src/Storages/getStructureOfRemoteTable.cpp | 14 ++- src/Storages/getStructureOfRemoteTable.h | 6 +- .../test_distributed_type_object/test.py | 8 ++ 19 files changed, 242 insertions(+), 179 deletions(-) diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index f0fe9ee8f3b..bee738f8e60 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -8,12 +8,17 @@ #include #include #include +#include #include #include #include #include #include #include +#include +#include +#include +#include namespace DB { @@ -79,7 +84,7 @@ DataTypePtr getDataTypeByColumn(const IColumn & column) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot get data type of column {}", column.getFamilyName()); } -template +template static auto extractVector(const std::vector & vec) { static_assert(I < std::tuple_size_v); @@ -200,29 +205,80 @@ NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list) return res; } -void extendObjectColumns(NamesAndTypesList & columns_list, const NameToTypeMap & object_types, bool with_subcolumns) +void extendObjectColumns(NamesAndTypesList & columns_list, const ColumnsDescription & object_columns, bool with_subcolumns) { NamesAndTypesList subcolumns_list; for (auto & column : columns_list) { - auto it = object_types.find(column.name); - if (it != object_types.end()) + auto object_column = object_columns.tryGetColumn(GetColumnsOptions::All, column.name); + if (object_column) { - const auto & object_type = it->second; - column.type = object_type; + column.type = object_column->type; if (with_subcolumns) + subcolumns_list.splice(subcolumns_list.end(), object_columns.getSubcolumns(column.name)); + } + } + + columns_list.splice(columns_list.end(), std::move(subcolumns_list)); +} + +static void addConstantToWithClause(const ASTPtr & query, const String & column_name, const DataTypePtr & data_type) +{ + auto & select = query->as(); + if (!select.with()) + select.setExpression(ASTSelectQuery::Expression::WITH, std::make_shared()); + + auto literal = std::make_shared(data_type->getDefault()); + auto node = makeASTFunction("CAST", std::move(literal), std::make_shared(data_type->getName())); + + node->alias = column_name; + node->prefer_alias_to_column_name = true; + select.with()->children.push_back(std::move(node)); +} + +void replaceMissedSubcolumnsByConstants( + const ColumnsDescription & expected_columns, + const ColumnsDescription & available_columns, + ASTPtr query) +{ + NamesAndTypesList missed; + for (const auto & column : available_columns) + { + const auto * type_tuple = typeid_cast(column.type.get()); + assert(type_tuple); + + auto expected_column = expected_columns.getColumn(GetColumnsOptions::All, column.name); + const auto * expected_type_tuple = typeid_cast(expected_column.type.get()); + assert(expected_type_tuple); + + if (!type_tuple->equals(*expected_type_tuple)) + { + const auto & names = type_tuple->getElementNames(); + const auto & expected_names = expected_type_tuple->getElementNames(); + const auto & expected_types = expected_type_tuple->getElements(); + + NameSet names_set(names.begin(), names.end()); + + for (size_t i = 0; i < expected_names.size(); ++i) { - for (const auto & subcolumn : object_type->getSubcolumnNames()) + if (!names_set.count(expected_names[i])) { - subcolumns_list.emplace_back(column.name, subcolumn, - object_type, object_type->getSubcolumnType(subcolumn)); + auto full_name = Nested::concatenateName(column.name, expected_names[i]); + missed.emplace_back(std::move(full_name), expected_types[i]); } } } } - columns_list.splice(columns_list.end(), std::move(subcolumns_list)); + if (missed.empty()) + return; + + IdentifierNameSet identifiers; + query->collectIdentifierNames(identifiers); + for (const auto & [name, type] : missed) + if (identifiers.count(name)) + addConstantToWithClause(query, name, type); } void finalizeObjectColumns(MutableColumns & columns) diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index bb79b818c8e..345db6fdf36 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -3,12 +3,11 @@ #include #include #include +#include namespace DB { -using NameToTypeMap = std::unordered_map; - size_t getNumberOfDimensions(const IDataType & type); size_t getNumberOfDimensions(const IColumn & column); DataTypePtr getBaseTypeOfArray(const DataTypePtr & type); @@ -18,7 +17,12 @@ DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns); DataTypePtr getLeastCommonTypeForObject(const DataTypes & types); NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list); -void extendObjectColumns(NamesAndTypesList & columns_list, const NameToTypeMap & object_types, bool with_subcolumns); +void extendObjectColumns(NamesAndTypesList & columns_list, const ColumnsDescription & object_columns, bool with_subcolumns); + +void replaceMissedSubcolumnsByConstants( + const ColumnsDescription & expected_columns, + const ColumnsDescription & available_columns, + ASTPtr query); void finalizeObjectColumns(MutableColumns & columns); diff --git a/src/Interpreters/ClusterProxy/SelectStreamFactory.cpp b/src/Interpreters/ClusterProxy/SelectStreamFactory.cpp index efad9f899d4..39b5c912e76 100644 --- a/src/Interpreters/ClusterProxy/SelectStreamFactory.cpp +++ b/src/Interpreters/ClusterProxy/SelectStreamFactory.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -35,10 +36,14 @@ namespace ClusterProxy SelectStreamFactory::SelectStreamFactory( const Block & header_, + const ColumnsDescriptionByShardNum & objects_by_shard_, + const StorageSnapshotPtr & storage_snapshot_, QueryProcessingStage::Enum processed_stage_, bool has_virtual_shard_num_column_) : header(header_), - processed_stage{processed_stage_}, + objects_by_shard(objects_by_shard_), + storage_snapshot(storage_snapshot_), + processed_stage(processed_stage_), has_virtual_shard_num_column(has_virtual_shard_num_column_) { } @@ -169,6 +174,10 @@ void SelectStreamFactory::createForShard( } } + auto it = objects_by_shard.find(shard_info.shard_num); + if (it != objects_by_shard.end()) + replaceMissedSubcolumnsByConstants(storage_snapshot->object_columns, it->second, modified_query_ast); + auto emplace_local_stream = [&]() { local_plans.emplace_back(createLocalPlan(modified_query_ast, modified_header, context, processed_stage)); diff --git a/src/Interpreters/ClusterProxy/SelectStreamFactory.h b/src/Interpreters/ClusterProxy/SelectStreamFactory.h index d041ac8ea5f..30faf5f509a 100644 --- a/src/Interpreters/ClusterProxy/SelectStreamFactory.h +++ b/src/Interpreters/ClusterProxy/SelectStreamFactory.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace DB { @@ -11,11 +12,15 @@ namespace DB namespace ClusterProxy { +using ColumnsDescriptionByShardNum = std::unordered_map; + class SelectStreamFactory final : public IStreamFactory { public: SelectStreamFactory( const Block & header_, + const ColumnsDescriptionByShardNum & objects_by_shard_, + const StorageSnapshotPtr & storage_snapshot_, QueryProcessingStage::Enum processed_stage_, bool has_virtual_shard_num_column_); @@ -30,6 +35,8 @@ public: private: const Block header; + const ColumnsDescriptionByShardNum objects_by_shard; + const StorageSnapshotPtr storage_snapshot; QueryProcessingStage::Enum processed_stage; bool has_virtual_shard_num_column = false; diff --git a/src/Interpreters/ClusterProxy/executeQuery.cpp b/src/Interpreters/ClusterProxy/executeQuery.cpp index d3a1b40a8e3..b8d32405be7 100644 --- a/src/Interpreters/ClusterProxy/executeQuery.cpp +++ b/src/Interpreters/ClusterProxy/executeQuery.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/src/Storages/ColumnsDescription.cpp b/src/Storages/ColumnsDescription.cpp index 6bf7d23d4f6..af2c6b86921 100644 --- a/src/Storages/ColumnsDescription.cpp +++ b/src/Storages/ColumnsDescription.cpp @@ -359,17 +359,23 @@ NamesAndTypesList ColumnsDescription::getAll() const return ret; } -static NamesAndTypesList getWithSubcolumns(NamesAndTypesList && source_list) +NamesAndTypesList ColumnsDescription::getSubcolumns(const String & name_in_storage) const { - NamesAndTypesList ret; + auto range = subcolumns.get<1>().equal_range(name_in_storage); + return NamesAndTypesList(range.first, range.second); +} + +void ColumnsDescription::addSubcolumnsToList(NamesAndTypesList & source_list) const +{ + NamesAndTypesList subcolumns_list; for (const auto & col : source_list) { - ret.emplace_back(col.name, col.type); - for (const auto & subcolumn : col.type->getSubcolumnNames()) - ret.emplace_back(col.name, subcolumn, col.type, col.type->getSubcolumnType(subcolumn)); + auto range = subcolumns.get<1>().equal_range(col.name); + if (range.first != range.second) + subcolumns_list.insert(subcolumns_list.end(), range.first, range.second); } - return ret; + source_list.splice(source_list.end(), std::move(subcolumns_list)); } NamesAndTypesList ColumnsDescription::get(const GetColumnsOptions & options) const @@ -395,7 +401,7 @@ NamesAndTypesList ColumnsDescription::get(const GetColumnsOptions & options) con } if (options.with_subcolumns) - res = getWithSubcolumns(std::move(res)); + addSubcolumnsToList(res); return res; } @@ -505,17 +511,19 @@ std::optional ColumnsDescription::tryGetColumn(const GetColumns return {}; } +NameAndTypePair ColumnsDescription::getColumn(const GetColumnsOptions & options, const String & column_name) const +{ + auto column = tryGetColumn(options, column_name); + if (!column) + throw Exception(ErrorCodes::NO_SUCH_COLUMN_IN_TABLE, + "There is no column {} in table.", column_name); + + return *column; +} + std::optional ColumnsDescription::tryGetColumnOrSubcolumn(GetColumnsOptions::Kind kind, const String & column_name) const { - auto it = columns.get<1>().find(column_name); - if (it != columns.get<1>().end() && (defaultKindToGetKind(it->default_desc.kind) & kind)) - return NameAndTypePair(it->name, it->type); - - auto jt = subcolumns.get<0>().find(column_name); - if (jt != subcolumns.get<0>().end()) - return *jt; - - return {}; + return tryGetColumn(GetColumnsOptions(kind).withSubcolumns(), column_name); } NameAndTypePair ColumnsDescription::getColumnOrSubcolumn(GetColumnsOptions::Kind kind, const String & column_name) const @@ -530,11 +538,7 @@ NameAndTypePair ColumnsDescription::getColumnOrSubcolumn(GetColumnsOptions::Kind std::optional ColumnsDescription::tryGetPhysical(const String & column_name) const { - auto it = columns.get<1>().find(column_name); - if (it == columns.get<1>().end() || it->default_desc.kind == ColumnDefaultKind::Alias) - return {}; - - return NameAndTypePair(it->name, it->type); + return tryGetColumn(GetColumnsOptions::AllPhysical, column_name); } NameAndTypePair ColumnsDescription::getPhysical(const String & column_name) const @@ -561,30 +565,6 @@ bool ColumnsDescription::hasColumnOrSubcolumn(GetColumnsOptions::Kind kind, cons || hasSubcolumn(column_name); } -void ColumnsDescription::addSubcolumnsToList(NamesAndTypesList & source_list) const -{ - for (const auto & col : source_list) - { - auto range = subcolumns.get<1>().equal_range(col.name); - if (range.first != range.second) - source_list.insert(source_list.end(), range.first, range.second); - } -} - -NamesAndTypesList ColumnsDescription::getAllWithSubcolumns() const -{ - auto columns_list = getAll(); - addSubcolumnsToList(columns_list); - return columns_list; -} - -NamesAndTypesList ColumnsDescription::getAllPhysicalWithSubcolumns() const -{ - auto columns_list = getAllPhysical(); - addSubcolumnsToList(columns_list); - return columns_list; -} - bool ColumnsDescription::hasDefaults() const { for (const auto & column : columns) diff --git a/src/Storages/ColumnsDescription.h b/src/Storages/ColumnsDescription.h index ff3e4bdfd24..cc405f7afa1 100644 --- a/src/Storages/ColumnsDescription.h +++ b/src/Storages/ColumnsDescription.h @@ -124,8 +124,7 @@ public: NamesAndTypesList getAliases() const; NamesAndTypesList getAllPhysical() const; /// ordinary + materialized. NamesAndTypesList getAll() const; /// ordinary + materialized + aliases - NamesAndTypesList getAllWithSubcolumns() const; - NamesAndTypesList getAllPhysicalWithSubcolumns() const; + NamesAndTypesList getSubcolumns(const String & name_in_storage) const; using ColumnTTLs = std::unordered_map; ColumnTTLs getColumnTTLs() const; @@ -160,10 +159,10 @@ public: NameAndTypePair getPhysical(const String & column_name) const; NameAndTypePair getColumnOrSubcolumn(GetColumnsOptions::Kind kind, const String & column_name) const; + NameAndTypePair getColumn(const GetColumnsOptions & options, const String & column_name) const; std::optional tryGetPhysical(const String & column_name) const; std::optional tryGetColumnOrSubcolumn(GetColumnsOptions::Kind kind, const String & column_name) const; - std::optional tryGetColumn(const GetColumnsOptions & options, const String & column_name) const; ColumnDefaults getDefaults() const; /// TODO: remove diff --git a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp index 5fa65f54f74..89b6eb2635c 100644 --- a/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp +++ b/src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp @@ -315,6 +315,7 @@ MergeTreeReadTaskColumns getReadTaskColumns( } MergeTreeReadTaskColumns result; + NamesAndTypesList all_columns; if (check_columns) { @@ -329,7 +330,6 @@ MergeTreeReadTaskColumns getReadTaskColumns( } result.should_reorder = should_reorder; - return result; } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index d1ed3e63457..b33f20a7f4d 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -5115,7 +5115,7 @@ ReservationPtr MergeTreeData::balancedReservation( return reserved_space; } -StorageSnapshot::NameToTypeMap MergeTreeData::getObjectTypes(const DataPartsVector & parts, const NameSet & object_names) +ColumnsDescription MergeTreeData::getObjectsDescription(const DataPartsVector & parts, const NameSet & object_names) { std::unordered_map types_in_parts; @@ -5139,21 +5139,23 @@ StorageSnapshot::NameToTypeMap MergeTreeData::getObjectTypes(const DataPartsVect } } - StorageSnapshot::NameToTypeMap object_types; + ColumnsDescription object_columns; for (const auto & [name, types] : types_in_parts) - object_types.emplace(name, getLeastCommonTypeForObject(types)); + object_columns.add(ColumnDescription{name, getLeastCommonTypeForObject(types)}); - return object_types; + return object_columns; } StorageSnapshotPtr MergeTreeData::getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const { - auto parts = getDataPartsVector(); - auto object_types = getObjectTypes( - parts, + auto snapshot_data = std::make_unique(); + snapshot_data->parts = getDataPartsVector(); + + auto object_columns = getObjectsDescription( + snapshot_data->parts, getNamesOfObjectColumns(metadata_snapshot->getColumns().getAll())); - return std::make_shared(*this, metadata_snapshot, object_types, parts); + return std::make_shared(*this, metadata_snapshot, object_columns, std::move(snapshot_data)); } CurrentlySubmergingEmergingTagger::~CurrentlySubmergingEmergingTagger() diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 10317b26526..1e39610ab46 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -394,6 +394,11 @@ public: bool mayBenefitFromIndexForIn(const ASTPtr & left_in_operand, ContextPtr, const StorageMetadataPtr & metadata_snapshot) const override; + struct SnapshotData : public StorageSnapshot::Data + { + DataPartsVector parts; + }; + StorageSnapshotPtr getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const override; /// Load the set of data parts from disk. Call once - immediately after the object is created. @@ -636,7 +641,7 @@ public: return column_sizes; } - static StorageSnapshot::NameToTypeMap getObjectTypes(const DataPartsVector & parts, const NameSet & object_names); + static ColumnsDescription getObjectsDescription(const DataPartsVector & parts, const NameSet & object_names); /// For ATTACH/DETACH/DROP PARTITION. String getPartitionIDFromQuery(const ASTPtr & ast, ContextPtr context) const; diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 3ecf8dc4341..dfcc22da655 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -720,9 +720,9 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataMergerMutator::mergePartsToTempor Names all_column_names = metadata_snapshot->getColumns().getNamesOfPhysical(); NamesAndTypesList storage_columns = metadata_snapshot->getColumns().getAllPhysical(); - auto object_types = MergeTreeData::getObjectTypes(parts, getNamesOfObjectColumns(storage_columns)); - auto storage_snapshot = std::make_shared(data, metadata_snapshot, object_types, parts); - extendObjectColumns(storage_columns, object_types, false); + auto object_columns = MergeTreeData::getObjectsDescription(parts, getNamesOfObjectColumns(storage_columns)); + auto storage_snapshot = std::make_shared(data, metadata_snapshot, object_columns); + extendObjectColumns(storage_columns, object_columns, false); const auto data_settings = data.getSettings(); diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 1338284bbb6..03d554d35e5 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -133,9 +133,11 @@ QueryPlanPtr MergeTreeDataSelectExecutor::read( std::shared_ptr max_block_numbers_to_read) const { const auto & settings = context->getSettingsRef(); - const auto & parts = storage_snapshot->parts; const auto & metadata_for_reading = storage_snapshot->getMetadataForQuery(); + const auto & snapshot_data = assert_cast(*storage_snapshot->data); + const auto & parts = snapshot_data.parts; + if (!query_info.projection) { auto plan = readFromParts( diff --git a/src/Storages/StorageDistributed.cpp b/src/Storages/StorageDistributed.cpp index 7abc5286479..2fc91331510 100644 --- a/src/Storages/StorageDistributed.cpp +++ b/src/Storages/StorageDistributed.cpp @@ -567,24 +567,30 @@ StorageSnapshotPtr StorageDistributed::getStorageSnapshot(const StorageMetadataP if (names_of_objects.empty()) return std::make_shared(*this, metadata_snapshot); - auto columns_in_tables = getExtendedColumnsOfRemoteTables(*getCluster(), StorageID{remote_database, remote_table}, getContext()); - assert(!columns_in_tables.empty()); + auto snapshot_data = std::make_unique(); + snapshot_data->objects_by_shard = getExtendedObjectsOfRemoteTables( + *getCluster(), + StorageID{remote_database, remote_table}, + names_of_objects, + getContext()); + + assert(!snapshot_data->objects_by_shard.empty()); std::unordered_map types_in_tables; - for (const auto & columns : columns_in_tables) + for (const auto & [_, columns] : snapshot_data->objects_by_shard) { - for (const auto & [name, type] : columns) + for (const auto & column : columns) { - if (names_of_objects.count(name)) - types_in_tables[name].push_back(type); + assert(names_of_objects.count(column.name)); + types_in_tables[column.name].push_back(column.type); } } - StorageSnapshot::NameToTypeMap object_types; + ColumnsDescription object_columns; for (const auto & [name, types] : types_in_tables) - object_types.emplace(name, getLeastCommonTypeForObject(types)); + object_columns.add(ColumnDescription(name, getLeastCommonTypeForObject(types))); - return std::make_shared(*this, metadata_snapshot, object_types); + return std::make_shared(*this, metadata_snapshot, object_columns, std::move(snapshot_data)); } Pipe StorageDistributed::read( @@ -638,9 +644,12 @@ void StorageDistributed::read( if (!remote_table_function_ptr) main_table = StorageID{remote_database, remote_table}; + const auto & snapshot_data = assert_cast(*storage_snapshot->data); ClusterProxy::SelectStreamFactory select_stream_factory = ClusterProxy::SelectStreamFactory( header, + snapshot_data.objects_by_shard, + storage_snapshot, processed_stage, has_virtual_shard_num_column); @@ -1399,3 +1408,4 @@ void registerStorageDistributed(StorageFactory & factory) } } + diff --git a/src/Storages/StorageDistributed.h b/src/Storages/StorageDistributed.h index e1932543a5e..9ecdb3e0fa8 100644 --- a/src/Storages/StorageDistributed.h +++ b/src/Storages/StorageDistributed.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,11 @@ public: bool isRemote() const override { return true; } + struct SnapshotData : public StorageSnapshot::Data + { + ColumnsDescriptionByShardNum objects_by_shard; + }; + StorageSnapshotPtr getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const override; QueryProcessingStage::Enum diff --git a/src/Storages/StorageSnapshot.cpp b/src/Storages/StorageSnapshot.cpp index 68b76e8508e..e1fad3432a2 100644 --- a/src/Storages/StorageSnapshot.cpp +++ b/src/Storages/StorageSnapshot.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace DB { @@ -16,49 +17,10 @@ namespace ErrorCodes extern const int COLUMN_QUERIED_MORE_THAN_ONCE; } -namespace -{ - -#if !defined(ARCADIA_BUILD) - using NamesAndTypesMap = google::dense_hash_map; - using UniqueStrings = google::dense_hash_set; -#else - using NamesAndTypesMap = google::sparsehash::dense_hash_map; - using UniqueStrings = google::sparsehash::dense_hash_set; -#endif - - NamesAndTypesMap getColumnsMap(const NamesAndTypesList & columns) - { - NamesAndTypesMap res; - res.set_empty_key(StringRef()); - - for (const auto & column : columns) - res.insert({column.name, &column.type}); - - return res; - } - - UniqueStrings initUniqueStrings() - { - UniqueStrings strings; - strings.set_empty_key(StringRef()); - return strings; - } -} - void StorageSnapshot::init() { for (const auto & [name, type] : storage.getVirtuals()) virtual_columns[name] = type; - - for (const auto & [name, type] : object_types) - { - for (const auto & subcolumn : type->getSubcolumnNames()) - { - auto full_name = Nested::concatenateName(name, subcolumn); - object_subcolumns[full_name] = {name, subcolumn, type, type->getSubcolumnType(subcolumn)}; - } - } } NamesAndTypesList StorageSnapshot::getColumns(const GetColumnsOptions & options) const @@ -66,7 +28,7 @@ NamesAndTypesList StorageSnapshot::getColumns(const GetColumnsOptions & options) auto all_columns = getMetadataForQuery()->getColumns().get(options); if (options.with_extended_objects) - extendObjectColumns(all_columns, object_types, options.with_subcolumns); + extendObjectColumns(all_columns, object_columns, options.with_subcolumns); if (options.with_virtuals) { @@ -102,22 +64,12 @@ NamesAndTypesList StorageSnapshot::getColumnsByNames(const GetColumnsOptions & o if (options.with_extended_objects) { - auto it = object_types.find(name); - if (it != object_types.end()) + auto object_column = object_columns.tryGetColumn(options, name); + if (object_column) { - res.emplace_back(name, it->second); + res.emplace_back(std::move(*object_column)); continue; } - - if (options.with_subcolumns) - { - auto jt = object_subcolumns.find(name); - if (jt != object_subcolumns.end()) - { - res.emplace_back(jt->second); - continue; - } - } } if (options.with_virtuals) @@ -148,59 +100,68 @@ Block StorageSnapshot::getSampleBlockForColumns(const Names & column_names) cons { res.insert({column->type->createColumn(), column->type, column->name}); } - else if (auto it = object_types.find(name); it != object_types.end()) + else if (auto object_column = object_columns.tryGetColumnOrSubcolumn(GetColumnsOptions::All, name)) { - const auto & type = it->second; - res.insert({type->createColumn(), type, name}); + res.insert({object_column->type->createColumn(), object_column->type, object_column->name}); } - else if (auto jt = object_subcolumns.find(name); jt != object_subcolumns.end()) - { - const auto & type = jt->second.type; - res.insert({type->createColumn(), type, name}); - } - else if (auto kt = virtual_columns.find(name); kt != virtual_columns.end()) + else if (auto it = virtual_columns.find(name); it != virtual_columns.end()) { /// Virtual columns must be appended after ordinary, because user can /// override them. - const auto & type = kt->second; + const auto & type = it->second; res.insert({type->createColumn(), type, name}); } else + { throw Exception(ErrorCodes::NOT_FOUND_COLUMN_IN_BLOCK, "Column {} not found in table {}", backQuote(name), storage.getStorageID().getNameForLogs()); + } } return res; } +namespace +{ + +#if !defined(ARCADIA_BUILD) + using DenseHashSet = google::dense_hash_set; +#else + using DenseHashSet = google::sparsehash::dense_hash_set; +#endif + +} + void StorageSnapshot::check(const Names & column_names) const { const auto & columns = getMetadataForQuery()->getColumns(); + auto options = GetColumnsOptions(GetColumnsOptions::AllPhysical).withSubcolumns(); if (column_names.empty()) { - auto list_of_columns = listOfColumns(columns.getAllPhysicalWithSubcolumns()); + auto list_of_columns = listOfColumns(columns.get(options)); throw Exception(ErrorCodes::EMPTY_LIST_OF_COLUMNS_QUERIED, "Empty list of columns queried. There are columns: {}", list_of_columns); } - const auto virtuals_map = getColumnsMap(storage.getVirtuals()); - auto unique_names = initUniqueStrings(); + DenseHashSet unique_names; + unique_names.set_empty_key(StringRef()); for (const auto & name : column_names) { bool has_column = columns.hasColumnOrSubcolumn(GetColumnsOptions::AllPhysical, name) - || object_subcolumns.count(name) || virtuals_map.count(name); + || object_columns.hasColumnOrSubcolumn(GetColumnsOptions::AllPhysical, name) + || virtual_columns.count(name); if (!has_column) { - auto list_of_columns = listOfColumns(columns.getAllPhysicalWithSubcolumns()); + auto list_of_columns = listOfColumns(columns.get(options)); throw Exception(ErrorCodes::NO_SUCH_COLUMN_IN_TABLE, "There is no column with name {} in table {}. There are columns: {}", backQuote(name), storage.getStorageID().getNameForLogs(), list_of_columns); } - if (unique_names.end() != unique_names.find(name)) + if (unique_names.count(name)) throw Exception(ErrorCodes::COLUMN_QUERIED_MORE_THAN_ONCE, "Column {} queried more than once", name); unique_names.insert(name); @@ -209,11 +170,18 @@ void StorageSnapshot::check(const Names & column_names) const DataTypePtr StorageSnapshot::getConcreteType(const String & column_name) const { - auto it = object_types.find(column_name); - if (it != object_types.end()) - return it->second; + auto object_column = object_columns.tryGetColumnOrSubcolumn(GetColumnsOptions::All, column_name); + if (object_column) + return object_column->type; return metadata->getColumns().get(column_name).type; } +bool StorageSnapshot::isSubcolumnOfObject(const String & name) const +{ + auto split = Nested::splitName(name); + return !split.second.empty() + && object_columns.tryGetColumn(GetColumnsOptions::All, split.first); +} + } diff --git a/src/Storages/StorageSnapshot.h b/src/Storages/StorageSnapshot.h index 293357c3f83..1d5af7e8f88 100644 --- a/src/Storages/StorageSnapshot.h +++ b/src/Storages/StorageSnapshot.h @@ -7,10 +7,6 @@ namespace DB class IStorage; -class IMergeTreeDataPart; -using DataPartPtr = std::shared_ptr; -using DataPartsVector = std::vector; - // #if !defined(ARCADIA_BUILD) // using NamesAndTypesMap = google::dense_hash_map; // #else @@ -19,12 +15,14 @@ using DataPartsVector = std::vector; struct StorageSnapshot { - using NameToTypeMap = std::unordered_map; - const IStorage & storage; const StorageMetadataPtr metadata; - const NameToTypeMap object_types; - const DataPartsVector parts; + const ColumnsDescription object_columns; + + struct Data{}; + using DataPtr = std::unique_ptr; + + const DataPtr data; /// TODO: fix mutable const ProjectionDescription * projection = nullptr; @@ -40,8 +38,8 @@ struct StorageSnapshot StorageSnapshot( const IStorage & storage_, const StorageMetadataPtr & metadata_, - const NameToTypeMap & object_types_) - : storage(storage_), metadata(metadata_), object_types(object_types_) + const ColumnsDescription & object_columns_) + : storage(storage_), metadata(metadata_), object_columns(object_columns_) { init(); } @@ -49,9 +47,9 @@ struct StorageSnapshot StorageSnapshot( const IStorage & storage_, const StorageMetadataPtr & metadata_, - const NameToTypeMap & object_types_, - const DataPartsVector & parts_) - : storage(storage_), metadata(metadata_), object_types(object_types_), parts(parts_) + const ColumnsDescription & object_columns_, + DataPtr data_) + : storage(storage_), metadata(metadata_), object_columns(object_columns_), data(std::move(data_)) { init(); } @@ -72,11 +70,12 @@ struct StorageSnapshot StorageMetadataPtr getMetadataForQuery() const { return (projection ? projection->metadata : metadata); } + bool isSubcolumnOfObject(const String & name) const; + private: void init(); - std::unordered_map object_subcolumns; - NameToTypeMap virtual_columns; + std::unordered_map virtual_columns; }; using StorageSnapshotPtr = std::shared_ptr; diff --git a/src/Storages/getStructureOfRemoteTable.cpp b/src/Storages/getStructureOfRemoteTable.cpp index e2cd2b492e7..db7461317d6 100644 --- a/src/Storages/getStructureOfRemoteTable.cpp +++ b/src/Storages/getStructureOfRemoteTable.cpp @@ -149,9 +149,10 @@ ColumnsDescription getStructureOfRemoteTable( ErrorCodes::NO_REMOTE_SHARD_AVAILABLE); } -std::vector getExtendedColumnsOfRemoteTables( +ColumnsDescriptionByShardNum getExtendedObjectsOfRemoteTables( const Cluster & cluster, const StorageID & remote_table_id, + const NameSet & names_of_objects, ContextPtr context) { const auto & shards_info = cluster.getShardsInfo(); @@ -176,7 +177,7 @@ std::vector getExtendedColumnsOfRemoteTables( input->setMainTable(remote_table_id); input->readPrefix(); - NamesAndTypesList res; + ColumnsDescription res; while (auto block = input->read()) { const auto & name_col = *block.getByName("name").column; @@ -188,14 +189,17 @@ std::vector getExtendedColumnsOfRemoteTables( auto name = name_col[i].template get(); auto type_name = type_col[i].template get(); - res.emplace_back(std::move(name), DataTypeFactory::instance().get(type_name)); + if (!names_of_objects.count(name)) + continue; + + res.add(ColumnDescription(std::move(name), DataTypeFactory::instance().get(type_name))); } } return res; }; - std::vector columns; + ColumnsDescriptionByShardNum columns; for (const auto & shard_info : shards_info) { auto res = execute_query_on_shard(shard_info); @@ -203,7 +207,7 @@ std::vector getExtendedColumnsOfRemoteTables( /// Expect at least some columns. /// This is a hack to handle the empty block case returned by Connection when skip_unavailable_shards is set. if (!res.empty()) - columns.emplace_back(std::move(res)); + columns.emplace(shard_info.shard_num, std::move(res)); } if (columns.empty()) diff --git a/src/Storages/getStructureOfRemoteTable.h b/src/Storages/getStructureOfRemoteTable.h index 5ca15055b8f..2c346b25c2b 100644 --- a/src/Storages/getStructureOfRemoteTable.h +++ b/src/Storages/getStructureOfRemoteTable.h @@ -20,9 +20,13 @@ ColumnsDescription getStructureOfRemoteTable( ContextPtr context, const ASTPtr & table_func_ptr = nullptr); -std::vector getExtendedColumnsOfRemoteTables( + +using ColumnsDescriptionByShardNum = std::unordered_map; + +ColumnsDescriptionByShardNum getExtendedObjectsOfRemoteTables( const Cluster & cluster, const StorageID & remote_table_id, + const NameSet & names_of_objects, ContextPtr context); } diff --git a/tests/integration/test_distributed_type_object/test.py b/tests/integration/test_distributed_type_object/test.py index 7ceb538cf45..476e26a9d1d 100644 --- a/tests/integration/test_distributed_type_object/test.py +++ b/tests/integration/test_distributed_type_object/test.py @@ -34,3 +34,11 @@ def test_distributed_type_object(started_cluster): expected = TSV("10\n20\nstr1\n") assert TSV(node1.query("SELECT data.k1 FROM dist_table ORDER BY id")) == expected + + node1.query('INSERT INTO local_table FORMAT JSONEachRow {"id": 4, "data": {"k2": 30}}') + + expected = TSV("10\t0\n20\t0\nstr1\t0\n\t30") + assert TSV(node1.query("SELECT data.k1, data.k2 FROM dist_table ORDER BY id")) == expected + + expected = TSV("120\n") + assert TSV(node1.query("SELECT sum(data.k2 * id) FROM dist_table SETTINGS optimize_arithmetic_operations_in_aggregate_functions = 0")) == expected From f867995b9447e0efff4b9db45f0ac54503dba143 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 23 Jul 2021 19:47:43 +0300 Subject: [PATCH 0050/1647] remove excessive creation of storage snapshot --- src/Storages/StorageDistributed.cpp | 14 +++++++------- src/Storages/StorageDistributed.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Storages/StorageDistributed.cpp b/src/Storages/StorageDistributed.cpp index 2fc91331510..cf9354325dd 100644 --- a/src/Storages/StorageDistributed.cpp +++ b/src/Storages/StorageDistributed.cpp @@ -276,9 +276,9 @@ void replaceConstantExpressions( ContextPtr context, const NamesAndTypesList & columns, ConstStoragePtr storage, - const StorageMetadataPtr & metadata_snapshot) + const StorageSnapshotPtr & storage_snapshot) { - auto syntax_result = TreeRewriter(context).analyze(node, columns, storage, storage->getStorageSnapshot(metadata_snapshot)); + auto syntax_result = TreeRewriter(context).analyze(node, columns, storage, storage_snapshot); Block block_with_constants = KeyCondition::getBlockWithConstants(node, syntax_result, context); InDepthNodeVisitor visitor(block_with_constants); @@ -500,7 +500,7 @@ QueryProcessingStage::Enum StorageDistributed::getQueryProcessingStage( /// (Anyway it will be calculated in the read()) if (getClusterQueriedNodes(settings, cluster) > 1 && settings.optimize_skip_unused_shards) { - ClusterPtr optimized_cluster = getOptimizedCluster(local_context, storage_snapshot->metadata, query_info.query); + ClusterPtr optimized_cluster = getOptimizedCluster(local_context, storage_snapshot, query_info.query); if (optimized_cluster) { LOG_DEBUG(log, "Skipping irrelevant shards - the query will be sent to the following shards of the cluster (shard numbers): {}", @@ -1040,7 +1040,7 @@ ClusterPtr StorageDistributed::getCluster() const } ClusterPtr StorageDistributed::getOptimizedCluster( - ContextPtr local_context, const StorageMetadataPtr & metadata_snapshot, const ASTPtr & query_ptr) const + ContextPtr local_context, const StorageSnapshotPtr & storage_snapshot, const ASTPtr & query_ptr) const { ClusterPtr cluster = getCluster(); const Settings & settings = local_context->getSettingsRef(); @@ -1049,7 +1049,7 @@ ClusterPtr StorageDistributed::getOptimizedCluster( if (has_sharding_key && sharding_key_is_usable) { - ClusterPtr optimized = skipUnusedShards(cluster, query_ptr, metadata_snapshot, local_context); + ClusterPtr optimized = skipUnusedShards(cluster, query_ptr, storage_snapshot, local_context); if (optimized) return optimized; } @@ -1105,7 +1105,7 @@ IColumn::Selector StorageDistributed::createSelector(const ClusterPtr cluster, c ClusterPtr StorageDistributed::skipUnusedShards( ClusterPtr cluster, const ASTPtr & query_ptr, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, ContextPtr local_context) const { const auto & select = query_ptr->as(); @@ -1125,7 +1125,7 @@ ClusterPtr StorageDistributed::skipUnusedShards( condition_ast = select.prewhere() ? select.prewhere()->clone() : select.where()->clone(); } - replaceConstantExpressions(condition_ast, local_context, metadata_snapshot->getColumns().getAll(), shared_from_this(), metadata_snapshot); + replaceConstantExpressions(condition_ast, local_context, storage_snapshot->metadata->getColumns().getAll(), shared_from_this(), storage_snapshot); size_t limit = local_context->getSettingsRef().optimize_skip_unused_shards_limit; if (!limit || limit > SSIZE_MAX) diff --git a/src/Storages/StorageDistributed.h b/src/Storages/StorageDistributed.h index 9ecdb3e0fa8..bc4dff62a90 100644 --- a/src/Storages/StorageDistributed.h +++ b/src/Storages/StorageDistributed.h @@ -180,9 +180,9 @@ private: /// Apply the following settings: /// - optimize_skip_unused_shards /// - force_optimize_skip_unused_shards - ClusterPtr getOptimizedCluster(ContextPtr, const StorageMetadataPtr & metadata_snapshot, const ASTPtr & query_ptr) const; + ClusterPtr getOptimizedCluster(ContextPtr, const StorageSnapshotPtr & storage_snapshot, const ASTPtr & query_ptr) const; ClusterPtr - skipUnusedShards(ClusterPtr cluster, const ASTPtr & query_ptr, const StorageMetadataPtr & metadata_snapshot, ContextPtr context) const; + skipUnusedShards(ClusterPtr cluster, const ASTPtr & query_ptr, const StorageSnapshotPtr & storage_snapshot, ContextPtr context) const; size_t getRandomShardIndex(const Cluster::ShardsInfo & shards); From 5b3c9dc5decd2b006434d020af7724ed128092f6 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 24 Jul 2021 02:15:44 +0300 Subject: [PATCH 0051/1647] dynamic columns: allow nullable subcolumns --- src/Columns/ColumnNullable.cpp | 19 ++++++-- src/Columns/ColumnNullable.h | 4 +- src/Columns/ColumnObject.cpp | 30 +++++++----- src/Columns/ColumnObject.h | 8 ++-- src/DataTypes/DataTypeObject.cpp | 47 ++++++++++++++----- src/DataTypes/DataTypeObject.h | 5 +- .../01825_type_json_nullable.reference | 17 +++++++ .../0_stateless/01825_type_json_nullable.sql | 24 ++++++++++ 8 files changed, 122 insertions(+), 32 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_nullable.reference create mode 100644 tests/queries/0_stateless/01825_type_json_nullable.sql diff --git a/src/Columns/ColumnNullable.cpp b/src/Columns/ColumnNullable.cpp index c1745e9bed7..1fdb560efc0 100644 --- a/src/Columns/ColumnNullable.cpp +++ b/src/Columns/ColumnNullable.cpp @@ -644,9 +644,22 @@ void ColumnNullable::checkConsistency() const ColumnPtr ColumnNullable::createWithOffsets(const IColumn::Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const { - /// TODO: check if default_field is null. - auto new_values = nested_column->createWithOffsets(offsets, default_field, total_rows, shift); - auto new_null_map = null_map->createWithOffsets(offsets, Field(1), total_rows, shift); + ColumnPtr new_values; + ColumnPtr new_null_map; + + if (default_field.getType() == Field::Types::Null) + { + auto default_column = nested_column->cloneEmpty(); + default_column->insertDefault(); + + new_values = nested_column->createWithOffsets(offsets, (*default_column)[0], total_rows, shift); + new_null_map = null_map->createWithOffsets(offsets, Field(1u), total_rows, shift); + } + else + { + new_values = nested_column->createWithOffsets(offsets, default_field, total_rows, shift); + new_null_map = null_map->createWithOffsets(offsets, Field(0u), total_rows, shift); + } return ColumnNullable::create(new_values, new_null_map); } diff --git a/src/Columns/ColumnNullable.h b/src/Columns/ColumnNullable.h index 39246ad654a..570e6d4137f 100644 --- a/src/Columns/ColumnNullable.h +++ b/src/Columns/ColumnNullable.h @@ -139,12 +139,12 @@ public: double getRatioOfDefaultRows(double sample_ratio) const override { - return null_map->getRatioOfDefaultRows(sample_ratio); + return getRatioOfDefaultRowsImpl(sample_ratio); } void getIndicesOfNonDefaultRows(Offsets & indices, size_t from, size_t limit) const override { - null_map->getIndicesOfNonDefaultRows(indices, from, limit); + getIndicesOfNonDefaultRowsImpl(indices, from, limit); } ColumnPtr createWithOffsets(const Offsets & offsets, const Field & default_field, size_t total_rows, size_t shift) const override; diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 5e31d296d97..a328cbbedd8 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -157,6 +157,7 @@ private: ColumnObject::Subcolumn::Subcolumn(const Subcolumn & other) : least_common_type(other.least_common_type) + , is_nullable(other.is_nullable) , data(other.data) , num_of_defaults_in_prefix(other.num_of_defaults_in_prefix) { @@ -164,12 +165,14 @@ ColumnObject::Subcolumn::Subcolumn(const Subcolumn & other) ColumnObject::Subcolumn::Subcolumn(MutableColumnPtr && data_) : least_common_type(getDataTypeByColumn(*data_)) + , is_nullable(least_common_type->isNullable()) { data.push_back(std::move(data_)); } -ColumnObject::Subcolumn::Subcolumn(size_t size_) +ColumnObject::Subcolumn::Subcolumn(size_t size_, bool is_nullable_) : least_common_type(std::make_shared()) + , is_nullable(is_nullable_) , num_of_defaults_in_prefix(size_) { } @@ -235,8 +238,10 @@ void ColumnObject::Subcolumn::insert(Field && field) return; } - DataTypePtr value_type; - if (base_type->isNullable()) + if (is_nullable && !base_type->isNullable()) + base_type = makeNullable(base_type); + + if (!is_nullable && base_type->isNullable()) { base_type = removeNullable(base_type); if (isNothing(base_type)) @@ -246,14 +251,11 @@ void ColumnObject::Subcolumn::insert(Field && field) } field = applyVisitor(FieldVisitorReplaceNull(base_type->getDefault()), std::move(field)); - value_type = createArrayOfType(base_type, value_dim); - } - else - { - value_type = createArrayOfType(base_type, value_dim); } + auto value_type = createArrayOfType(base_type, value_dim); bool type_changed = false; + if (data.empty()) { data.push_back(value_type->createColumn()); @@ -347,8 +349,14 @@ const ColumnPtr & ColumnObject::Subcolumn::getFinalizedColumnPtr() const return data[0]; } -ColumnObject::ColumnObject(SubcolumnsMap && subcolumns_) +ColumnObject::ColumnObject(bool is_nullable_) + : is_nullable(is_nullable_) +{ +} + +ColumnObject::ColumnObject(SubcolumnsMap && subcolumns_, bool is_nullable_) : subcolumns(std::move(subcolumns_)) + , is_nullable(is_nullable_) { checkConsistency(); } @@ -384,7 +392,7 @@ MutableColumnPtr ColumnObject::cloneResized(size_t new_size) const throw Exception(ErrorCodes::NOT_IMPLEMENTED, "ColumnObject doesn't support resize to non-zero length"); - return ColumnObject::create(); + return ColumnObject::create(is_nullable); } size_t ColumnObject::byteSize() const @@ -442,7 +450,7 @@ void ColumnObject::addSubcolumn(const String & key, size_t new_size, bool check_ "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", key, new_size, size()); - subcolumns[key] = Subcolumn(new_size); + subcolumns[key] = Subcolumn(new_size, is_nullable); } void ColumnObject::addSubcolumn(const String & key, Subcolumn && subcolumn, bool check_size) diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index a54b9fcbdb5..822e25cad32 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -23,7 +23,7 @@ public: { public: Subcolumn() = default; - Subcolumn(size_t size_); + Subcolumn(size_t size_, bool is_nullable); Subcolumn(MutableColumnPtr && data_); Subcolumn(const Subcolumn & other); Subcolumn & operator=(Subcolumn && other) = default; @@ -46,6 +46,7 @@ public: private: DataTypePtr least_common_type; + bool is_nullable = false; std::vector data; size_t num_of_defaults_in_prefix = 0; }; @@ -54,12 +55,13 @@ public: private: SubcolumnsMap subcolumns; + bool is_nullable; public: static constexpr auto COLUMN_NAME_DUMMY = "_dummy"; - ColumnObject() = default; - ColumnObject(SubcolumnsMap && subcolumns_); + ColumnObject(bool is_nullable); + ColumnObject(SubcolumnsMap && subcolumns_, bool is_nullable_); void checkConsistency() const; diff --git a/src/DataTypes/DataTypeObject.cpp b/src/DataTypes/DataTypeObject.cpp index 5f07d9905f4..3ed4c79af07 100644 --- a/src/DataTypes/DataTypeObject.cpp +++ b/src/DataTypes/DataTypeObject.cpp @@ -13,10 +13,12 @@ namespace ErrorCodes { extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; extern const int UNEXPECTED_AST_STRUCTURE; + extern const int BAD_ARGUMENTS; } -DataTypeObject::DataTypeObject(const String & schema_format_) +DataTypeObject::DataTypeObject(const String & schema_format_, bool is_nullable_) : schema_format(Poco::toLower(schema_format_)) + , is_nullable(is_nullable_) , default_serialization(getObjectSerialization(schema_format)) { } @@ -33,33 +35,56 @@ SerializationPtr DataTypeObject::doGetDefaultSerialization() const return default_serialization; } +static constexpr auto NAME_DEFAULT = "Default"; +static constexpr auto NAME_NULL = "Null"; + String DataTypeObject::doGetName() const { WriteBufferFromOwnString out; - out << "Object(" << quote << schema_format << ")"; + out << "Object(" << quote << schema_format; + if (is_nullable) + out << ", " << quote << NAME_NULL; + out << ")"; return out.str(); } - static DataTypePtr create(const ASTPtr & arguments) { - if (!arguments || arguments->children.size() != 1) - throw Exception("Object data type family must have exactly one argument - name of schema format", + if (!arguments || arguments->children.size() < 1 || arguments->children.size() > 2) + throw Exception("Object data type family must have one or two arguments -" + " name of schema format and type of default value", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); - const auto * argument = arguments->children[0]->as(); - if (!argument || argument->value.getType() != Field::Types::String) - throw Exception("Object data type family must have a string as its argument", - ErrorCodes::UNEXPECTED_AST_STRUCTURE); + const auto * literal = arguments->children[0]->as(); + if (!literal || literal->value.getType() != Field::Types::String) + throw Exception(ErrorCodes::UNEXPECTED_AST_STRUCTURE, + "Object data type family must have a const string as its first argument"); - return std::make_shared(argument->value.get()); + bool is_nullable = false; + if (arguments->children.size() == 2) + { + const auto * default_literal = arguments->children[1]->as(); + if (!default_literal || default_literal->value.getType() != Field::Types::String) + throw Exception(ErrorCodes::UNEXPECTED_AST_STRUCTURE, + "Object data type family must have a const string as its second argument"); + + const auto & default_kind = default_literal->value.get(); + if (default_kind == NAME_NULL) + is_nullable = true; + else if (default_kind != NAME_DEFAULT) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Unexpected type of default value '{}'. Should be {} or {}", + default_kind, NAME_DEFAULT, NAME_NULL); + } + + return std::make_shared(literal->value.get(), is_nullable); } void registerDataTypeObject(DataTypeFactory & factory) { factory.registerDataType("Object", create); factory.registerSimpleDataType("JSON", - [] { return std::make_shared("JSON"); }, + [] { return std::make_shared("JSON", false); }, DataTypeFactory::CaseInsensitive); } diff --git a/src/DataTypes/DataTypeObject.h b/src/DataTypes/DataTypeObject.h index 6ba666adf3f..991991124a7 100644 --- a/src/DataTypes/DataTypeObject.h +++ b/src/DataTypes/DataTypeObject.h @@ -17,16 +17,17 @@ class DataTypeObject : public IDataType { private: String schema_format; + bool is_nullable; SerializationPtr default_serialization; public: - DataTypeObject(const String & schema_format_); + DataTypeObject(const String & schema_format_, bool is_nullable_); const char * getFamilyName() const override { return "Object"; } String doGetName() const override; TypeIndex getTypeId() const override { return TypeIndex::Object; } - MutableColumnPtr createColumn() const override { return ColumnObject::create(); } + MutableColumnPtr createColumn() const override { return ColumnObject::create(is_nullable); } Field getDefault() const override { diff --git a/tests/queries/0_stateless/01825_type_json_nullable.reference b/tests/queries/0_stateless/01825_type_json_nullable.reference new file mode 100644 index 00000000000..7f1691a6469 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_nullable.reference @@ -0,0 +1,17 @@ +1 (1,2,NULL) Tuple(k1 Nullable(Int8), k2 Nullable(Int8), k3 Nullable(Int8)) +2 (NULL,3,4) Tuple(k1 Nullable(Int8), k2 Nullable(Int8), k3 Nullable(Int8)) +1 1 2 \N +2 \N 3 4 +1 (1,2,NULL) Tuple(k1 Nullable(Int8), k2 Nullable(Int8), k3 Nullable(String)) +2 (NULL,3,'4') Tuple(k1 Nullable(Int8), k2 Nullable(Int8), k3 Nullable(String)) +3 (NULL,NULL,'10') Tuple(k1 Nullable(Int8), k2 Nullable(Int8), k3 Nullable(String)) +4 (NULL,5,'str') Tuple(k1 Nullable(Int8), k2 Nullable(Int8), k3 Nullable(String)) +1 1 2 \N +2 \N 3 4 +3 \N \N 10 +4 \N 5 str +============ +1 ([11,NULL],[NULL,22],[]) Tuple(`k1.k2` Array(Nullable(Int8)), `k1.k3` Array(Nullable(Int8)), `k1.k4` Array(Nullable(Int8))) +2 ([],[33,NULL,55],[NULL,44,66]) Tuple(`k1.k2` Array(Nullable(Int8)), `k1.k3` Array(Nullable(Int8)), `k1.k4` Array(Nullable(Int8))) +1 [11,NULL] [NULL,22] [] +2 [] [33,NULL,55] [NULL,44,66] diff --git a/tests/queries/0_stateless/01825_type_json_nullable.sql b/tests/queries/0_stateless/01825_type_json_nullable.sql new file mode 100644 index 00000000000..a89bebe82c2 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_nullable.sql @@ -0,0 +1,24 @@ +DROP TABLE IF EXISTS t_json_null; + +CREATE TABLE t_json_null(id UInt64, data Object('JSON', 'Null')) +ENGINE = MergeTree ORDER BY tuple(); + +INSERT INTO t_json_null FORMAT JSONEachRow {"id": 1, "data": {"k1": 1, "k2" : 2}} {"id": 2, "data": {"k2": 3, "k3" : 4}}; + +SELECT id, data, toTypeName(data) FROM t_json_null ORDER BY id; +SELECT id, data.k1, data.k2, data.k3 FROM t_json_null ORDER BY id; + +INSERT INTO t_json_null FORMAT JSONEachRow {"id": 3, "data": {"k3" : 10}} {"id": 4, "data": {"k2": 5, "k3" : "str"}}; + +SELECT id, data, toTypeName(data) FROM t_json_null ORDER BY id; +SELECT id, data.k1, data.k2, data.k3 FROM t_json_null ORDER BY id; + +SELECT '============'; +TRUNCATE TABLE t_json_null; + +INSERT INTO TABLE t_json_null FORMAT JSONEachRow {"id": 1, "data": {"k1" : [{"k2" : 11}, {"k3" : 22}]}} {"id": 2, "data": {"k1" : [{"k3" : 33}, {"k4" : 44}, {"k3" : 55, "k4" : 66}]}}; + +SELECT id, data, toTypeName(data) FROM t_json_null ORDER BY id; +SELECT id, data.k1.k2, data.k1.k3, data.k1.k4 FROM t_json_null ORDER BY id; + +DROP TABLE t_json_null; From 90b6a591e536fb4c0cdb991482a7b961df18e61c Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 24 Jul 2021 03:55:50 +0300 Subject: [PATCH 0052/1647] fix reading from distributed --- src/Storages/StorageDistributed.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storages/StorageDistributed.cpp b/src/Storages/StorageDistributed.cpp index cf9354325dd..018f40f6389 100644 --- a/src/Storages/StorageDistributed.cpp +++ b/src/Storages/StorageDistributed.cpp @@ -563,11 +563,11 @@ QueryProcessingStage::Enum StorageDistributed::getQueryProcessingStage( StorageSnapshotPtr StorageDistributed::getStorageSnapshot(const StorageMetadataPtr & metadata_snapshot) const { + auto snapshot_data = std::make_unique(); auto names_of_objects = getNamesOfObjectColumns(metadata_snapshot->getColumns().getAllPhysical()); if (names_of_objects.empty()) - return std::make_shared(*this, metadata_snapshot); + return std::make_shared(*this, metadata_snapshot, ColumnsDescription{}, std::move(snapshot_data)); - auto snapshot_data = std::make_unique(); snapshot_data->objects_by_shard = getExtendedObjectsOfRemoteTables( *getCluster(), StorageID{remote_database, remote_table}, From 75c81f189aa3155ac36be339400b4825361ab197 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 2 Aug 2021 22:49:52 +0300 Subject: [PATCH 0053/1647] fix destruction of snapshot data --- src/DataTypes/ObjectUtils.cpp | 7 +++++-- src/Storages/StorageSnapshot.h | 7 +++++-- tests/integration/test_distributed_type_object/test.py | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index bee738f8e60..81a658ee689 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -229,8 +229,11 @@ static void addConstantToWithClause(const ASTPtr & query, const String & column_ if (!select.with()) select.setExpression(ASTSelectQuery::Expression::WITH, std::make_shared()); - auto literal = std::make_shared(data_type->getDefault()); - auto node = makeASTFunction("CAST", std::move(literal), std::make_shared(data_type->getName())); + /// TODO: avoid materialize + auto node = makeASTFunction("materialize", + makeASTFunction("CAST", + std::make_shared(data_type->getDefault()), + std::make_shared(data_type->getName()))); node->alias = column_name; node->prefer_alias_to_column_name = true; diff --git a/src/Storages/StorageSnapshot.h b/src/Storages/StorageSnapshot.h index 1d5af7e8f88..fece121cd6f 100644 --- a/src/Storages/StorageSnapshot.h +++ b/src/Storages/StorageSnapshot.h @@ -19,9 +19,12 @@ struct StorageSnapshot const StorageMetadataPtr metadata; const ColumnsDescription object_columns; - struct Data{}; - using DataPtr = std::unique_ptr; + struct Data + { + virtual ~Data() = default; + }; + using DataPtr = std::unique_ptr; const DataPtr data; /// TODO: fix diff --git a/tests/integration/test_distributed_type_object/test.py b/tests/integration/test_distributed_type_object/test.py index 476e26a9d1d..02cb17ded86 100644 --- a/tests/integration/test_distributed_type_object/test.py +++ b/tests/integration/test_distributed_type_object/test.py @@ -41,4 +41,4 @@ def test_distributed_type_object(started_cluster): assert TSV(node1.query("SELECT data.k1, data.k2 FROM dist_table ORDER BY id")) == expected expected = TSV("120\n") - assert TSV(node1.query("SELECT sum(data.k2 * id) FROM dist_table SETTINGS optimize_arithmetic_operations_in_aggregate_functions = 0")) == expected + assert TSV(node1.query("SELECT sum(data.k2 * id) FROM dist_table")) == expected From 5a2daae63a5997e5df8d4acc1990070adfdb3893 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 10 Aug 2021 04:33:57 +0300 Subject: [PATCH 0054/1647] add conversion from tuple to object --- src/Columns/ColumnObject.cpp | 90 +++++++++++++------ src/Columns/ColumnObject.h | 6 +- src/DataTypes/DataTypeObject.h | 2 + src/DataTypes/ObjectUtils.cpp | 38 ++++++++ src/DataTypes/ObjectUtils.h | 1 + src/Functions/FunctionsConversion.h | 55 ++++++++++++ .../Formats/Impl/ValuesBlockInputFormat.cpp | 2 + .../01825_type_json_insert_select.reference | 12 +++ .../01825_type_json_insert_select.sql | 32 +++++++ 9 files changed, 210 insertions(+), 28 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_insert_select.reference create mode 100644 tests/queries/0_stateless/01825_type_json_insert_select.sql diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index a328cbbedd8..431db916afc 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -12,9 +12,6 @@ #include #include -#include -#include - namespace DB { @@ -281,9 +278,41 @@ void ColumnObject::Subcolumn::insert(Field && field) data.back()->insert(std::move(field)); } +void ColumnObject::Subcolumn::insertRangeFrom(const Subcolumn & src, size_t start, size_t length) +{ + assert(src.isFinalized()); + + const auto & src_column = src.data.back(); + const auto & src_type = src.least_common_type; + + if (data.empty()) + { + least_common_type = src_type; + data.push_back(src_type->createColumn()); + data.back()->insertRangeFrom(*src_column, start, length); + } + else if (least_common_type->equals(*src_type)) + { + data.back()->insertRangeFrom(*src_column, start, length); + } + else + { + auto new_least_common_type = getLeastSupertype(DataTypes{least_common_type, src_type}); + auto casted_column = castColumn({src_column, src_type, ""}, new_least_common_type); + + if (!least_common_type->equals(*new_least_common_type)) + { + least_common_type = new_least_common_type; + data.push_back(least_common_type->createColumn()); + } + + data.back()->insertRangeFrom(*casted_column, start, length); + } +} + void ColumnObject::Subcolumn::finalize() { - if (isFinalized()) + if (isFinalized() || data.empty()) return; const auto & to_type = least_common_type; @@ -331,6 +360,14 @@ void ColumnObject::Subcolumn::insertDefault() data.back()->insertDefault(); } +void ColumnObject::Subcolumn::insertManyDefaults(size_t length) +{ + if (data.empty()) + num_of_defaults_in_prefix += length; + else + data.back()->insertManyDefaults(length); +} + IColumn & ColumnObject::Subcolumn::getFinalizedColumn() { assert(isFinalized()); @@ -417,6 +454,21 @@ void ColumnObject::forEachSubcolumn(ColumnCallback) // callback(column.data); } +void ColumnObject::insertRangeFrom(const IColumn & src, size_t start, size_t length) +{ + const auto & src_object = assert_cast(src); + + for (auto & [name, subcolumn] : subcolumns) + { + if (src_object.hasSubcolumn(name)) + subcolumn.insertRangeFrom(src_object.getSubcolumn(name), start, length); + else + subcolumn.insertManyDefaults(length); + } + + finalize(); +} + const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const String & key) const { auto it = subcolumns.find(key); @@ -466,15 +518,13 @@ void ColumnObject::addSubcolumn(const String & key, Subcolumn && subcolumn, bool subcolumns[key] = std::move(subcolumn); } -static bool isPrefix(const Strings & prefix, const Strings & strings) +Strings ColumnObject::getKeys() const { - if (prefix.size() > strings.size()) - return false; - - for (size_t i = 0; i < prefix.size(); ++i) - if (prefix[i] != strings[i]) - return false; - return true; + Strings keys; + keys.reserve(subcolumns.size()); + for (const auto & [key, _] : subcolumns) + keys.emplace_back(key); + return keys; } bool ColumnObject::isFinalized() const @@ -493,21 +543,6 @@ void ColumnObject::finalize() if (isNothing(getBaseTypeOfArray(least_common_type))) continue; - Strings name_parts; - boost::split(name_parts, name, boost::is_any_of(".")); - - for (const auto & [other_name, _] : subcolumns) - { - if (other_name.size() > name.size()) - { - Strings other_name_parts; - boost::split(other_name_parts, other_name, boost::is_any_of(".")); - - if (isPrefix(name_parts, other_name_parts)) - throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Data in Object has ambiguous paths: '{}' and '{}", name, other_name); - } - } - subcolumn.finalize(); new_subcolumns[name] = std::move(subcolumn); } @@ -516,6 +551,7 @@ void ColumnObject::finalize() new_subcolumns[COLUMN_NAME_DUMMY] = Subcolumn{ColumnUInt8::create(old_size)}; std::swap(subcolumns, new_subcolumns); + checkObjectHasNoAmbiguosPaths(getKeys()); } } diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 822e25cad32..c12e88a0d4a 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -38,6 +38,9 @@ public: void insert(Field && field); void insertDefault(); + void insertManyDefaults(size_t length); + void insertRangeFrom(const Subcolumn & src, size_t start, size_t length); + void finalize(); IColumn & getFinalizedColumn(); @@ -75,6 +78,7 @@ public: const SubcolumnsMap & getSubcolumns() const { return subcolumns; } SubcolumnsMap & getSubcolumns() { return subcolumns; } + Strings getKeys() const; bool isFinalized() const; void finalize(); @@ -88,6 +92,7 @@ public: size_t byteSize() const override; size_t allocatedBytes() const override; void forEachSubcolumn(ColumnCallback callback) override; + void insertRangeFrom(const IColumn & src, size_t start, size_t length) override; /// All other methods throw exception. @@ -98,7 +103,6 @@ public: StringRef getDataAt(size_t) const override { throwMustBeConcrete(); } bool isDefaultAt(size_t) const override { throwMustBeConcrete(); } void insert(const Field &) override { throwMustBeConcrete(); } - void insertRangeFrom(const IColumn &, size_t, size_t) override { throwMustBeConcrete(); } void insertData(const char *, size_t) override { throwMustBeConcrete(); } void insertDefault() override { throwMustBeConcrete(); } void popBack(size_t) override { throwMustBeConcrete(); } diff --git a/src/DataTypes/DataTypeObject.h b/src/DataTypes/DataTypeObject.h index 991991124a7..b4b31f0b8ea 100644 --- a/src/DataTypes/DataTypeObject.h +++ b/src/DataTypes/DataTypeObject.h @@ -39,6 +39,8 @@ public: bool isParametric() const override { return true; } SerializationPtr doGetDefaultSerialization() const override; + + bool hasNullableSubcolumns() const { return is_nullable; } }; } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 81a658ee689..21b60f25707 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -20,6 +20,9 @@ #include #include +#include +#include + namespace DB { @@ -27,6 +30,7 @@ namespace ErrorCodes { extern const int TYPE_MISMATCH; extern const int LOGICAL_ERROR; + extern const int DUPLICATE_COLUMN; } static const IDataType * getTypeObject(const DataTypePtr & type) @@ -148,6 +152,38 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con } } +static bool isPrefix(const Strings & prefix, const Strings & strings) +{ + if (prefix.size() > strings.size()) + return false; + + for (size_t i = 0; i < prefix.size(); ++i) + if (prefix[i] != strings[i]) + return false; + return true; +} + +void checkObjectHasNoAmbiguosPaths(const Strings & key_names) +{ + for (const auto & name : key_names) + { + Strings name_parts; + boost::split(name_parts, name, boost::is_any_of(".")); + + for (const auto & other_name : key_names) + { + if (other_name.size() > name.size()) + { + Strings other_name_parts; + boost::split(other_name_parts, other_name, boost::is_any_of(".")); + + if (isPrefix(name_parts, other_name_parts)) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Data in Object has ambiguous paths: '{}' and '{}", name, other_name); + } + } + } +} + DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) { std::unordered_map subcolumns_types; @@ -192,6 +228,8 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) auto tuple_names = extractVector<0>(tuple_elements); auto tuple_types = extractVector<1>(tuple_elements); + checkObjectHasNoAmbiguosPaths(tuple_names); + return std::make_shared(tuple_types, tuple_names); } diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 345db6fdf36..1da7031670d 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -15,6 +15,7 @@ DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns); +void checkObjectHasNoAmbiguosPaths(const Strings & key_names); DataTypePtr getLeastCommonTypeForObject(const DataTypes & types); NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list); void extendObjectColumns(NamesAndTypesList & columns_list, const ColumnsDescription & object_columns, bool with_subcolumns); diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 6aa7ea6c39c..dd9f5824880 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -2978,6 +2980,57 @@ private: } } + WrapperType createObjectWrapper(const DataTypePtr & from_type, const DataTypeObject * to_type) const + { + if (const auto * from_tuple = checkAndGetDataType(from_type.get())) + { + if (!from_tuple->haveExplicitNames()) + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Cast to Object can be performed only from flatten Named Tuple. Got: {}", from_type->getName()); + + const auto & names = from_tuple->getElementNames(); + const auto & from_types = from_tuple->getElements(); + auto to_types = from_types; + + for (auto & type : to_types) + { + if (checkAndGetDataType(type.get())) + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Cast to Object can be performed only from flatten Named Tuple. Got: {}", from_type->getName()); + + type = recursiveRemoveLowCardinality(type); + } + + return [element_wrappers = getElementWrappers(from_types, to_types), + has_nullable_subcolumns = to_type->hasNullableSubcolumns(), from_types, to_types, names] + (ColumnsWithTypeAndName & arguments, const DataTypePtr &, const ColumnNullable * nullable_source, size_t input_rows_count) -> ColumnPtr + { + size_t tuple_size = to_types.size(); + const ColumnTuple & column_tuple = assert_cast(*arguments.front().column); + + ColumnObject::SubcolumnsMap subcolumns; + for (size_t i = 0; i < tuple_size; ++i) + { + ColumnsWithTypeAndName element = {{column_tuple.getColumns()[i], from_types[i], "" }}; + auto converted_column = element_wrappers[i](element, to_types[i], nullable_source, input_rows_count); + subcolumns[names[i]] = converted_column->assumeMutable(); + } + + return ColumnObject::create(std::move(subcolumns), has_nullable_subcolumns); + }; + } + else if (checkAndGetDataType(from_type.get())) + { + return [] (ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, const ColumnNullable *, size_t /*input_rows_count*/) + { + return ConvertImplGenericFromString::execute(arguments, result_type); + }; + } + + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Cast to Object can be performed only from flatten named tuple. Got: {}", from_type->getName()); + } + template WrapperType createEnumWrapper(const DataTypePtr & from_type, const DataTypeEnum * to_type) const { @@ -3353,6 +3406,8 @@ private: return createTupleWrapper(from_type, checkAndGetDataType(to_type.get())); case TypeIndex::Map: return createMapWrapper(from_type, checkAndGetDataType(to_type.get())); + case TypeIndex::Object: + return createObjectWrapper(from_type, checkAndGetDataType(to_type.get())); case TypeIndex::AggregateFunction: return createAggregateFunctionWrapper(from_type, checkAndGetDataType(to_type.get())); default: diff --git a/src/Processors/Formats/Impl/ValuesBlockInputFormat.cpp b/src/Processors/Formats/Impl/ValuesBlockInputFormat.cpp index 1eb64682fae..9d64df9f36f 100644 --- a/src/Processors/Formats/Impl/ValuesBlockInputFormat.cpp +++ b/src/Processors/Formats/Impl/ValuesBlockInputFormat.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace DB @@ -93,6 +94,7 @@ Chunk ValuesBlockInputFormat::generate() return {}; } + finalizeObjectColumns(columns); size_t rows_in_block = columns[0]->size(); return Chunk{std::move(columns), rows_in_block}; } diff --git a/tests/queries/0_stateless/01825_type_json_insert_select.reference b/tests/queries/0_stateless/01825_type_json_insert_select.reference new file mode 100644 index 00000000000..3a7fe84af9e --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_insert_select.reference @@ -0,0 +1,12 @@ +Tuple(k1 Int8, k2 String) +1 (1,'foo') +Tuple(k1 Int8, k2 String, k3 String) +1 (1,'foo','') +2 (2,'bar','') +3 (3,'','aaa') +Tuple(`arr.k11` Array(Int8), `arr.k22` Array(String), `arr.k33` Array(Int8), k1 Int8, k2 String, k3 String) +1 ([],[],[],1,'foo','') +2 ([],[],[],2,'bar','') +3 ([],[],[],3,'','aaa') +4 ([5,7],['6','0'],[0,8],0,'','') +5 ([],['str1'],[],0,'','') diff --git a/tests/queries/0_stateless/01825_type_json_insert_select.sql b/tests/queries/0_stateless/01825_type_json_insert_select.sql new file mode 100644 index 00000000000..da0545993b2 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_insert_select.sql @@ -0,0 +1,32 @@ +DROP TABLE IF EXISTS type_json_src; +DROP TABLE IF EXISTS type_json_dst; + +CREATE TABLE type_json_src (id UInt32, data JSON) ENGINE = MergeTree ORDER BY id; +CREATE TABLE type_json_dst AS type_json_src; + +INSERT INTO type_json_src VALUES (1, '{"k1": 1, "k2": "foo"}'); +INSERT INTO type_json_dst SELECT * FROM type_json_src; + +SELECT DISTINCT toTypeName(data) FROM type_json_dst; +SELECT id, data FROM type_json_dst ORDER BY id; + +INSERT INTO type_json_src VALUES (2, '{"k1": 2, "k2": "bar"}') (3, '{"k1": 3, "k3": "aaa"}'); +INSERT INTO type_json_dst SELECT * FROM type_json_src WHERE id > 1; + +SELECT DISTINCT toTypeName(data) FROM type_json_dst; +SELECT id, data FROM type_json_dst ORDER BY id; + +INSERT INTO type_json_dst VALUES (4, '{"arr": [{"k11": 5, "k22": 6}, {"k11": 7, "k33": 8}]}'); + +INSERT INTO type_json_src VALUES (5, '{"arr": "not array"}'); +INSERT INTO type_json_dst SELECT * FROM type_json_src WHERE id = 5; -- { serverError 15 } + +TRUNCATE TABLE type_json_src; +INSERT INTO type_json_src VALUES (5, '{"arr": [{"k22": "str1"}]}') +INSERT INTO type_json_dst SELECT * FROM type_json_src WHERE id = 5; + +SELECT DISTINCT toTypeName(data) FROM type_json_dst; +SELECT id, data FROM type_json_dst ORDER BY id; + +DROP TABLE type_json_src; +DROP TABLE type_json_dst; From e9f90b2859d14dc315f0c3a314c704e1a16afdf6 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 21 Aug 2021 00:11:22 +0300 Subject: [PATCH 0055/1647] support fields of type object --- src/Columns/ColumnObject.cpp | 88 +++++++++++++++++-- src/Columns/ColumnObject.h | 19 ++-- src/Common/FieldVisitorConvertToNumber.h | 5 ++ src/Common/FieldVisitorDump.cpp | 17 ++++ src/Common/FieldVisitorDump.h | 1 + src/Common/FieldVisitorHash.cpp | 13 +++ src/Common/FieldVisitorHash.h | 1 + src/Common/FieldVisitorSum.cpp | 1 + src/Common/FieldVisitorSum.h | 1 + src/Common/FieldVisitorToString.cpp | 19 ++++ src/Common/FieldVisitorToString.h | 1 + src/Common/FieldVisitorWriteBinary.cpp | 14 +++ src/Common/FieldVisitorWriteBinary.h | 1 + src/Core/Field.cpp | 40 +++++++++ src/Core/Field.h | 45 ++++++++-- src/DataTypes/FieldToDataType.cpp | 8 +- src/DataTypes/FieldToDataType.h | 1 + src/DataTypes/Serializations/JSONDataParser.h | 3 + src/Interpreters/convertFieldToType.cpp | 41 +++++++++ src/Storages/MergeTree/MergeTreePartition.cpp | 12 +++ .../0_stateless/01825_type_json_4.reference | 2 +- .../queries/0_stateless/01825_type_json_4.sh | 2 +- .../01825_type_json_field.reference | 12 +++ .../0_stateless/01825_type_json_field.sql | 24 +++++ 24 files changed, 347 insertions(+), 24 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_field.reference create mode 100644 tests/queries/0_stateless/01825_type_json_field.sql diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 431db916afc..6b5610ce7d1 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace DB @@ -214,7 +215,7 @@ void ColumnObject::Subcolumn::checkTypes() const } } -void ColumnObject::Subcolumn::insert(Field && field) +void ColumnObject::Subcolumn::insert(Field field) { auto value_dim = applyVisitor(FieldVisitorToNumberOfDimensions(), field); auto column_dim = getNumberOfDimensions(*least_common_type); @@ -297,7 +298,7 @@ void ColumnObject::Subcolumn::insertRangeFrom(const Subcolumn & src, size_t star } else { - auto new_least_common_type = getLeastSupertype(DataTypes{least_common_type, src_type}); + auto new_least_common_type = getLeastSupertype(DataTypes{least_common_type, src_type}, true); auto casted_column = castColumn({src_column, src_type, ""}, new_least_common_type); if (!least_common_type->equals(*new_least_common_type)) @@ -448,10 +449,65 @@ size_t ColumnObject::allocatedBytes() const return res; } -void ColumnObject::forEachSubcolumn(ColumnCallback) +void ColumnObject::forEachSubcolumn(ColumnCallback callback) { - // for (auto & [_, column] : subcolumns) - // callback(column.data); + if (!isFinalized()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot iterate over non-finalized ColumnObject"); + + for (auto & [_, column] : subcolumns) + callback(column.data.back()); +} + +void ColumnObject::insert(const Field & field) +{ + const auto & object = field.get(); + + HashSet inserted; + size_t old_size = size(); + for (const auto & [key, value] : object) + { + inserted.insert(key); + if (!hasSubcolumn(key)) + addSubcolumn(key, old_size); + + auto & subcolumn = getSubcolumn(key); + subcolumn.insert(value); + } + + for (auto & [key, subcolumn] : subcolumns) + if (!inserted.has(key)) + subcolumn.insertDefault(); +} + +void ColumnObject::insertDefault() +{ + for (auto & [_, subcolumn] : subcolumns) + subcolumn.insertDefault(); +} + +Field ColumnObject::operator[](size_t n) const +{ + if (!isFinalized()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot get Field from non-finalized ColumnObject"); + + Object object; + for (const auto & [key, subcolumn] : subcolumns) + object[key] = (*subcolumn.data.back())[n]; + + return object; +} + +void ColumnObject::get(size_t n, Field & res) const +{ + if (!isFinalized()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot get Field from non-finalized ColumnObject"); + + auto & object = res.get(); + for (const auto & [key, subcolumn] : subcolumns) + { + auto it = object.try_emplace(key).first; + subcolumn.data.back()->get(n, it->second); + } } void ColumnObject::insertRangeFrom(const IColumn & src, size_t start, size_t length) @@ -469,6 +525,27 @@ void ColumnObject::insertRangeFrom(const IColumn & src, size_t start, size_t len finalize(); } +ColumnPtr ColumnObject::replicate(const Offsets & offsets) const +{ + if (!isFinalized()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot replicate non-finalized ColumnObject"); + + auto res_column = ColumnObject::create(is_nullable); + for (auto & [key, subcolumn] : subcolumns) + res_column->addSubcolumn(key, Subcolumn(subcolumn.data.back()->replicate(offsets)->assumeMutable())); + + return res_column; +} + +void ColumnObject::popBack(size_t length) +{ + if (!isFinalized()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot popBack from non-finalized ColumnObject"); + + for (auto & [_, subcolumn] : subcolumns) + subcolumn.data.back()->popBack(length); +} + const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const String & key) const { auto it = subcolumns.find(key); @@ -515,6 +592,7 @@ void ColumnObject::addSubcolumn(const String & key, Subcolumn && subcolumn, bool "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", key, subcolumn.size(), size()); + subcolumn.setNullable(is_nullable); subcolumns[key] = std::move(subcolumn); } diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index f7cc8ef2ea6..26976d6afe6 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -36,17 +36,20 @@ public: const DataTypePtr & getLeastCommonType() const { return least_common_type; } void checkTypes() const; - void insert(Field && field); + void insert(Field field); void insertDefault(); void insertManyDefaults(size_t length); void insertRangeFrom(const Subcolumn & src, size_t start, size_t length); void finalize(); + void setNullable(bool value) { is_nullable = value; } IColumn & getFinalizedColumn(); const IColumn & getFinalizedColumn() const; const ColumnPtr & getFinalizedColumnPtr() const; + friend class ColumnObject; + private: DataTypePtr least_common_type; bool is_nullable = false; @@ -86,26 +89,27 @@ public: /// Part of interface const char * getFamilyName() const override { return "Object"; } + TypeIndex getDataType() const override { return TypeIndex::Object; } size_t size() const override; MutableColumnPtr cloneResized(size_t new_size) const override; size_t byteSize() const override; size_t allocatedBytes() const override; void forEachSubcolumn(ColumnCallback callback) override; + void insert(const Field & field) override; + void insertDefault() override; void insertRangeFrom(const IColumn & src, size_t start, size_t length) override; + ColumnPtr replicate(const Offsets & offsets) const override; + void popBack(size_t length) override; + Field operator[](size_t n) const override; + void get(size_t n, Field & res) const override; /// All other methods throw exception. ColumnPtr decompress() const override { throwMustBeConcrete(); } - TypeIndex getDataType() const override { throwMustBeConcrete(); } - Field operator[](size_t) const override { throwMustBeConcrete(); } - void get(size_t, Field &) const override { throwMustBeConcrete(); } StringRef getDataAt(size_t) const override { throwMustBeConcrete(); } bool isDefaultAt(size_t) const override { throwMustBeConcrete(); } - void insert(const Field &) override { throwMustBeConcrete(); } void insertData(const char *, size_t) override { throwMustBeConcrete(); } - void insertDefault() override { throwMustBeConcrete(); } - void popBack(size_t) override { throwMustBeConcrete(); } StringRef serializeValueIntoArena(size_t, Arena &, char const *&) const override { throwMustBeConcrete(); } const char * deserializeAndInsertFromArena(const char *) override { throwMustBeConcrete(); } const char * skipSerializedInArena(const char *) const override { throwMustBeConcrete(); } @@ -121,7 +125,6 @@ public: bool hasEqualValues() const override { throwMustBeConcrete(); } void getPermutation(bool, size_t, int, Permutation &) const override { throwMustBeConcrete(); } void updatePermutation(bool, size_t, int, Permutation &, EqualRanges &) const override { throwMustBeConcrete(); } - ColumnPtr replicate(const Offsets &) const override { throwMustBeConcrete(); } MutableColumns scatter(ColumnIndex, const Selector &) const override { throwMustBeConcrete(); } void gather(ColumnGathererStream &) override { throwMustBeConcrete(); } void getExtremes(Field &, Field &) const override { throwMustBeConcrete(); } diff --git a/src/Common/FieldVisitorConvertToNumber.h b/src/Common/FieldVisitorConvertToNumber.h index 82a804691d7..66e32893426 100644 --- a/src/Common/FieldVisitorConvertToNumber.h +++ b/src/Common/FieldVisitorConvertToNumber.h @@ -56,6 +56,11 @@ public: throw Exception("Cannot convert Map to " + demangle(typeid(T).name()), ErrorCodes::CANNOT_CONVERT_TYPE); } + T operator() (const Object &) const + { + throw Exception("Cannot convert Object to " + demangle(typeid(T).name()), ErrorCodes::CANNOT_CONVERT_TYPE); + } + T operator() (const UInt64 & x) const { return T(x); } T operator() (const Int64 & x) const { return T(x); } T operator() (const Int128 & x) const { return T(x); } diff --git a/src/Common/FieldVisitorDump.cpp b/src/Common/FieldVisitorDump.cpp index 660677404ad..b1bcfd8fd10 100644 --- a/src/Common/FieldVisitorDump.cpp +++ b/src/Common/FieldVisitorDump.cpp @@ -96,6 +96,23 @@ String FieldVisitorDump::operator() (const Map & x) const return wb.str(); } +String FieldVisitorDump::operator() (const Object & x) const +{ + WriteBufferFromOwnString wb; + + wb << "Object_("; + for (auto it = x.begin(); it != x.end(); ++it) + { + if (it != x.begin()) + wb << ", "; + wb << "(" << it->first << ", " << applyVisitor(*this, it->second) << ")"; + } + wb << ')'; + + return wb.str(); + +} + String FieldVisitorDump::operator() (const AggregateFunctionStateData & x) const { WriteBufferFromOwnString wb; diff --git a/src/Common/FieldVisitorDump.h b/src/Common/FieldVisitorDump.h index bc82d35f0f1..280176355aa 100644 --- a/src/Common/FieldVisitorDump.h +++ b/src/Common/FieldVisitorDump.h @@ -24,6 +24,7 @@ public: String operator() (const Array & x) const; String operator() (const Tuple & x) const; String operator() (const Map & x) const; + String operator() (const Object & x) const; String operator() (const DecimalField & x) const; String operator() (const DecimalField & x) const; String operator() (const DecimalField & x) const; diff --git a/src/Common/FieldVisitorHash.cpp b/src/Common/FieldVisitorHash.cpp index 259dd871d20..dfe96946a69 100644 --- a/src/Common/FieldVisitorHash.cpp +++ b/src/Common/FieldVisitorHash.cpp @@ -106,6 +106,19 @@ void FieldVisitorHash::operator() (const Array & x) const applyVisitor(*this, elem); } +void FieldVisitorHash::operator() (const Object & x) const +{ + UInt8 type = Field::Types::Object; + hash.update(type); + hash.update(x.size()); + + for (const auto & [key, value]: x) + { + hash.update(key); + applyVisitor(*this, value); + } +} + void FieldVisitorHash::operator() (const DecimalField & x) const { UInt8 type = Field::Types::Decimal32; diff --git a/src/Common/FieldVisitorHash.h b/src/Common/FieldVisitorHash.h index bf7c3d5004f..378b1cfa233 100644 --- a/src/Common/FieldVisitorHash.h +++ b/src/Common/FieldVisitorHash.h @@ -30,6 +30,7 @@ public: void operator() (const Array & x) const; void operator() (const Tuple & x) const; void operator() (const Map & x) const; + void operator() (const Object & x) const; void operator() (const DecimalField & x) const; void operator() (const DecimalField & x) const; void operator() (const DecimalField & x) const; diff --git a/src/Common/FieldVisitorSum.cpp b/src/Common/FieldVisitorSum.cpp index e0ffca28341..6fb437d06c8 100644 --- a/src/Common/FieldVisitorSum.cpp +++ b/src/Common/FieldVisitorSum.cpp @@ -28,6 +28,7 @@ bool FieldVisitorSum::operator() (String &) const { throw Exception("Cannot sum bool FieldVisitorSum::operator() (Array &) const { throw Exception("Cannot sum Arrays", ErrorCodes::LOGICAL_ERROR); } bool FieldVisitorSum::operator() (Tuple &) const { throw Exception("Cannot sum Tuples", ErrorCodes::LOGICAL_ERROR); } bool FieldVisitorSum::operator() (Map &) const { throw Exception("Cannot sum Maps", ErrorCodes::LOGICAL_ERROR); } +bool FieldVisitorSum::operator() (Object &) const { throw Exception("Cannot sum Objects", ErrorCodes::LOGICAL_ERROR); } bool FieldVisitorSum::operator() (UUID &) const { throw Exception("Cannot sum UUIDs", ErrorCodes::LOGICAL_ERROR); } bool FieldVisitorSum::operator() (AggregateFunctionStateData &) const diff --git a/src/Common/FieldVisitorSum.h b/src/Common/FieldVisitorSum.h index 4c34fa86455..5f96854dd02 100644 --- a/src/Common/FieldVisitorSum.h +++ b/src/Common/FieldVisitorSum.h @@ -27,6 +27,7 @@ public: bool operator() (Array &) const; bool operator() (Tuple &) const; bool operator() (Map &) const; + bool operator() (Object &) const; bool operator() (UUID &) const; bool operator() (AggregateFunctionStateData &) const; diff --git a/src/Common/FieldVisitorToString.cpp b/src/Common/FieldVisitorToString.cpp index b8750d95e5e..cad92b0cf56 100644 --- a/src/Common/FieldVisitorToString.cpp +++ b/src/Common/FieldVisitorToString.cpp @@ -128,5 +128,24 @@ String FieldVisitorToString::operator() (const Map & x) const return wb.str(); } +String FieldVisitorToString::operator() (const Object & x) const +{ + WriteBufferFromOwnString wb; + + wb << '{'; + for (auto it = x.begin(); it != x.end(); ++it) + { + if (it != x.begin()) + wb << ", "; + + writeDoubleQuoted(it->first, wb); + wb << ": " << applyVisitor(*this, it->second); + } + wb << '}'; + + return wb.str(); + +} + } diff --git a/src/Common/FieldVisitorToString.h b/src/Common/FieldVisitorToString.h index 139f011927f..77059caa6b1 100644 --- a/src/Common/FieldVisitorToString.h +++ b/src/Common/FieldVisitorToString.h @@ -24,6 +24,7 @@ public: String operator() (const Array & x) const; String operator() (const Tuple & x) const; String operator() (const Map & x) const; + String operator() (const Object & x) const; String operator() (const DecimalField & x) const; String operator() (const DecimalField & x) const; String operator() (const DecimalField & x) const; diff --git a/src/Common/FieldVisitorWriteBinary.cpp b/src/Common/FieldVisitorWriteBinary.cpp index 56df9f1e43a..adb2bb8decd 100644 --- a/src/Common/FieldVisitorWriteBinary.cpp +++ b/src/Common/FieldVisitorWriteBinary.cpp @@ -68,5 +68,19 @@ void FieldVisitorWriteBinary::operator() (const Map & x, WriteBuffer & buf) cons } } +void FieldVisitorWriteBinary::operator() (const Object & x, WriteBuffer & buf) const +{ + const size_t size = x.size(); + writeBinary(size, buf); + + for (const auto & [key, value] : x) + { + const UInt8 type = value.getType(); + writeBinary(type, buf); + writeBinary(key, buf); + Field::dispatch([&buf] (const auto & val) { FieldVisitorWriteBinary()(val, buf); }, value); + } +} + } diff --git a/src/Common/FieldVisitorWriteBinary.h b/src/Common/FieldVisitorWriteBinary.h index 5f7bf578e32..2eedb54ff12 100644 --- a/src/Common/FieldVisitorWriteBinary.h +++ b/src/Common/FieldVisitorWriteBinary.h @@ -23,6 +23,7 @@ public: void operator() (const Array & x, WriteBuffer & buf) const; void operator() (const Tuple & x, WriteBuffer & buf) const; void operator() (const Map & x, WriteBuffer & buf) const; + void operator() (const Object & x, WriteBuffer & buf) const; void operator() (const DecimalField & x, WriteBuffer & buf) const; void operator() (const DecimalField & x, WriteBuffer & buf) const; void operator() (const DecimalField & x, WriteBuffer & buf) const; diff --git a/src/Core/Field.cpp b/src/Core/Field.cpp index 8739f56d991..511b77a8f9e 100644 --- a/src/Core/Field.cpp +++ b/src/Core/Field.cpp @@ -99,6 +99,12 @@ inline Field getBinaryValue(UInt8 type, ReadBuffer & buf) readBinary(value, buf); return value; } + case Field::Types::Object: + { + Object value; + readBinary(value, buf); + return value; + } case Field::Types::AggregateFunctionState: { AggregateFunctionStateData value; @@ -202,6 +208,40 @@ void writeText(const Map & x, WriteBuffer & buf) writeFieldText(Field(x), buf); } +void readBinary(Object & x, ReadBuffer & buf) +{ + size_t size; + readBinary(size, buf); + + for (size_t index = 0; index < size; ++index) + { + UInt8 type; + String key; + readBinary(type, buf); + readBinary(key, buf); + x[key] = getBinaryValue(type, buf); + } +} + +void writeBinary(const Object & x, WriteBuffer & buf) +{ + const size_t size = x.size(); + writeBinary(size, buf); + + for (const auto & [key, value] : x) + { + const UInt8 type = value.getType(); + writeBinary(type, buf); + writeBinary(key, buf); + Field::dispatch([&buf] (const auto & val) { FieldVisitorWriteBinary()(val, buf); }, value); + } +} + +void writeText(const Object & x, WriteBuffer & buf) +{ + writeFieldText(Field(x), buf); +} + template void readQuoted(DecimalField & x, ReadBuffer & buf) { diff --git a/src/Core/Field.h b/src/Core/Field.h index 0023497e970..a548617bfc2 100644 --- a/src/Core/Field.h +++ b/src/Core/Field.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -52,10 +53,22 @@ DEFINE_FIELD_VECTOR(Array); DEFINE_FIELD_VECTOR(Tuple); /// An array with the following structure: [(key1, value1), (key2, value2), ...] -DEFINE_FIELD_VECTOR(Map); +DEFINE_FIELD_VECTOR(Map); /// TODO: use map instead of vector. #undef DEFINE_FIELD_VECTOR +using FieldMap = std::map, AllocatorWithMemoryTracking>>; + +#define DEFINE_FIELD_MAP(X) \ +struct X : public FieldMap \ +{ \ + using FieldMap::FieldMap; \ +} + +DEFINE_FIELD_MAP(Object); + +#undef DEFINE_FIELD_MAP + struct AggregateFunctionStateData { String name; /// Name with arguments. @@ -216,6 +229,7 @@ template <> struct NearestFieldTypeImpl { using Type = String; }; template <> struct NearestFieldTypeImpl { using Type = Array; }; template <> struct NearestFieldTypeImpl { using Type = Tuple; }; template <> struct NearestFieldTypeImpl { using Type = Map; }; +template <> struct NearestFieldTypeImpl { using Type = Object; }; template <> struct NearestFieldTypeImpl { using Type = UInt64; }; template <> struct NearestFieldTypeImpl { using Type = Null; }; template <> struct NearestFieldTypeImpl { using Type = NegativeInfinity; }; @@ -271,6 +285,7 @@ public: Int256 = 25, Map = 26, UUID = 27, + Object = 28, // Special types for index analysis NegativeInfinity = 254, @@ -296,6 +311,7 @@ public: case Array: return "Array"; case Tuple: return "Tuple"; case Map: return "Map"; + case Object: return "Object"; case Decimal32: return "Decimal32"; case Decimal64: return "Decimal64"; case Decimal128: return "Decimal128"; @@ -486,6 +502,7 @@ public: case Types::Array: return get() < rhs.get(); case Types::Tuple: return get() < rhs.get(); case Types::Map: return get() < rhs.get(); + case Types::Object: return get() < rhs.get(); case Types::Decimal32: return get>() < rhs.get>(); case Types::Decimal64: return get>() < rhs.get>(); case Types::Decimal128: return get>() < rhs.get>(); @@ -526,6 +543,7 @@ public: case Types::Array: return get() <= rhs.get(); case Types::Tuple: return get() <= rhs.get(); case Types::Map: return get() <= rhs.get(); + case Types::Object: return get() <= rhs.get(); case Types::Decimal32: return get>() <= rhs.get>(); case Types::Decimal64: return get>() <= rhs.get>(); case Types::Decimal128: return get>() <= rhs.get>(); @@ -566,6 +584,7 @@ public: case Types::Array: return get() == rhs.get(); case Types::Tuple: return get() == rhs.get(); case Types::Map: return get() == rhs.get(); + case Types::Object: return get() == rhs.get(); case Types::UInt128: return get() == rhs.get(); case Types::UInt256: return get() == rhs.get(); case Types::Int128: return get() == rhs.get(); @@ -612,6 +631,7 @@ public: case Types::Array: return f(field.template get()); case Types::Tuple: return f(field.template get()); case Types::Map: return f(field.template get()); + case Types::Object: return f(field.template get()); case Types::Decimal32: return f(field.template get>()); case Types::Decimal64: return f(field.template get>()); case Types::Decimal128: return f(field.template get>()); @@ -728,6 +748,9 @@ private: case Types::Map: destroy(); break; + case Types::Object: + destroy(); + break; case Types::AggregateFunctionState: destroy(); break; @@ -767,6 +790,7 @@ template <> struct Field::TypeToEnum { static const Types::Which value template <> struct Field::TypeToEnum { static const Types::Which value = Types::Array; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::Tuple; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::Map; }; +template <> struct Field::TypeToEnum { static const Types::Which value = Types::Object; }; template <> struct Field::TypeToEnum>{ static const Types::Which value = Types::Decimal32; }; template <> struct Field::TypeToEnum>{ static const Types::Which value = Types::Decimal64; }; template <> struct Field::TypeToEnum>{ static const Types::Which value = Types::Decimal128; }; @@ -789,6 +813,7 @@ template <> struct Field::EnumToType { using Type = Strin template <> struct Field::EnumToType { using Type = Array; }; template <> struct Field::EnumToType { using Type = Tuple; }; template <> struct Field::EnumToType { using Type = Map; }; +template <> struct Field::EnumToType { using Type = Object; }; template <> struct Field::EnumToType { using Type = DecimalField; }; template <> struct Field::EnumToType { using Type = DecimalField; }; template <> struct Field::EnumToType { using Type = DecimalField; }; @@ -872,6 +897,7 @@ T safeGet(Field & field) template <> inline constexpr const char * TypeName = "Array"; template <> inline constexpr const char * TypeName = "Tuple"; template <> inline constexpr const char * TypeName = "Map"; +template <> inline constexpr const char * TypeName = "Object"; template <> inline constexpr const char * TypeName = "AggregateFunctionState"; @@ -938,34 +964,39 @@ class WriteBuffer; /// It is assumed that all elements of the array have the same type. void readBinary(Array & x, ReadBuffer & buf); - [[noreturn]] inline void readText(Array &, ReadBuffer &) { throw Exception("Cannot read Array.", ErrorCodes::NOT_IMPLEMENTED); } [[noreturn]] inline void readQuoted(Array &, ReadBuffer &) { throw Exception("Cannot read Array.", ErrorCodes::NOT_IMPLEMENTED); } /// It is assumed that all elements of the array have the same type. /// Also write size and type into buf. UInt64 and Int64 is written in variadic size form void writeBinary(const Array & x, WriteBuffer & buf); - void writeText(const Array & x, WriteBuffer & buf); - [[noreturn]] inline void writeQuoted(const Array &, WriteBuffer &) { throw Exception("Cannot write Array quoted.", ErrorCodes::NOT_IMPLEMENTED); } void readBinary(Tuple & x, ReadBuffer & buf); - [[noreturn]] inline void readText(Tuple &, ReadBuffer &) { throw Exception("Cannot read Tuple.", ErrorCodes::NOT_IMPLEMENTED); } [[noreturn]] inline void readQuoted(Tuple &, ReadBuffer &) { throw Exception("Cannot read Tuple.", ErrorCodes::NOT_IMPLEMENTED); } void writeBinary(const Tuple & x, WriteBuffer & buf); - void writeText(const Tuple & x, WriteBuffer & buf); +[[noreturn]] inline void writeQuoted(const Tuple &, WriteBuffer &) { throw Exception("Cannot write Tuple quoted.", ErrorCodes::NOT_IMPLEMENTED); } void readBinary(Map & x, ReadBuffer & buf); [[noreturn]] inline void readText(Map &, ReadBuffer &) { throw Exception("Cannot read Map.", ErrorCodes::NOT_IMPLEMENTED); } [[noreturn]] inline void readQuoted(Map &, ReadBuffer &) { throw Exception("Cannot read Map.", ErrorCodes::NOT_IMPLEMENTED); } + void writeBinary(const Map & x, WriteBuffer & buf); void writeText(const Map & x, WriteBuffer & buf); [[noreturn]] inline void writeQuoted(const Map &, WriteBuffer &) { throw Exception("Cannot write Map quoted.", ErrorCodes::NOT_IMPLEMENTED); } +void readBinary(Object & x, ReadBuffer & buf); +[[noreturn]] inline void readText(Object &, ReadBuffer &) { throw Exception("Cannot read Object.", ErrorCodes::NOT_IMPLEMENTED); } +[[noreturn]] inline void readQuoted(Object &, ReadBuffer &) { throw Exception("Cannot read Object.", ErrorCodes::NOT_IMPLEMENTED); } + +void writeBinary(const Object & x, WriteBuffer & buf); +void writeText(const Object & x, WriteBuffer & buf); +[[noreturn]] inline void writeQuoted(const Object &, WriteBuffer &) { throw Exception("Cannot write Object quoted.", ErrorCodes::NOT_IMPLEMENTED); } + __attribute__ ((noreturn)) inline void writeText(const AggregateFunctionStateData &, WriteBuffer &) { // This probably doesn't make any sense, but we have to have it for @@ -984,8 +1015,6 @@ void readQuoted(DecimalField & x, ReadBuffer & buf); void writeFieldText(const Field & x, WriteBuffer & buf); -[[noreturn]] inline void writeQuoted(const Tuple &, WriteBuffer &) { throw Exception("Cannot write Tuple quoted.", ErrorCodes::NOT_IMPLEMENTED); } - String toString(const Field & x); } diff --git a/src/DataTypes/FieldToDataType.cpp b/src/DataTypes/FieldToDataType.cpp index 38a0968cc81..d315ed2f902 100644 --- a/src/DataTypes/FieldToDataType.cpp +++ b/src/DataTypes/FieldToDataType.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -124,7 +125,6 @@ DataTypePtr FieldToDataType::operator() (const Array & x) const return std::make_shared(getLeastSupertype(element_types, allow_convertion_to_string)); } - DataTypePtr FieldToDataType::operator() (const Tuple & tuple) const { if (tuple.empty()) @@ -159,6 +159,12 @@ DataTypePtr FieldToDataType::operator() (const Map & map) const getLeastSupertype(value_types, allow_convertion_to_string)); } +DataTypePtr FieldToDataType::operator() (const Object &) const +{ + /// TODO: Do we need different parameters for type Object? + return std::make_shared("json", false); +} + DataTypePtr FieldToDataType::operator() (const AggregateFunctionStateData & x) const { const auto & name = static_cast(x).name; diff --git a/src/DataTypes/FieldToDataType.h b/src/DataTypes/FieldToDataType.h index f7a611b90bc..47f552fdcea 100644 --- a/src/DataTypes/FieldToDataType.h +++ b/src/DataTypes/FieldToDataType.h @@ -38,6 +38,7 @@ public: DataTypePtr operator() (const Array & x) const; DataTypePtr operator() (const Tuple & tuple) const; DataTypePtr operator() (const Map & map) const; + DataTypePtr operator() (const Object & map) const; DataTypePtr operator() (const DecimalField & x) const; DataTypePtr operator() (const DecimalField & x) const; DataTypePtr operator() (const DecimalField & x) const; diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index daefb6a9b9a..ecf0107b255 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -2,6 +2,7 @@ #include #include +#include namespace DB { @@ -74,6 +75,8 @@ public: private: void traverse(const Element & element, String current_path, ParseResult & result) { + checkStackSize(); + if (element.isObject()) { auto object = element.getObject(); diff --git a/src/Interpreters/convertFieldToType.cpp b/src/Interpreters/convertFieldToType.cpp index 51ab49c5096..575efaa8d92 100644 --- a/src/Interpreters/convertFieldToType.cpp +++ b/src/Interpreters/convertFieldToType.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -358,6 +359,46 @@ Field convertFieldToTypeImpl(const Field & src, const IDataType & type, const ID return src; } + else if (const auto * object_type = typeid_cast(&type)) + { + const auto * from_type_tuple = typeid_cast(from_type_hint); + if (src.getType() == Field::Types::Tuple && from_type_tuple && from_type_tuple->haveExplicitNames()) + { + const auto & names = from_type_tuple->getElementNames(); + const auto & tuple = src.get(); + + if (names.size() != tuple.size()) + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Bad size of tuple in IN or VALUES section (while converting to Object). Expected size: {}, actual size: {}", + names.size(), tuple.size()); + + Object object; + for (size_t i = 0; i < names.size(); ++i) + object[names[i]] = tuple[i]; + + return object; + } + + if (src.getType() == Field::Types::Map) + { + Object object; + const auto & map = src.get(); + for (size_t i = 0; i < map.size(); ++i) + { + const auto & map_entry = map[i].get(); + const auto & key = map_entry[0]; + const auto & value = map_entry[1]; + + if (key.getType() != Field::Types::String) + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Cannot convert from Map with key of type {} to Object", key.getTypeName()); + + object[key.get()] = value; + } + + return object; + } + } /// Conversion from string by parsing. if (src.getType() == Field::Types::String) diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index 0d457971dc6..1902c4fc180 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -133,6 +133,18 @@ namespace for (const auto & elem : x) applyVisitor(*this, elem); } + void operator() (const Object & x) const + { + UInt8 type = Field::Types::Object; + hash.update(type); + hash.update(x.size()); + + for (const auto & [key, value]: x) + { + hash.update(key); + applyVisitor(*this, value); + } + } void operator() (const DecimalField & x) const { UInt8 type = Field::Types::Decimal32; diff --git a/tests/queries/0_stateless/01825_type_json_4.reference b/tests/queries/0_stateless/01825_type_json_4.reference index 8c5510d8d5b..8bef8c02ee5 100644 --- a/tests/queries/0_stateless/01825_type_json_4.reference +++ b/tests/queries/0_stateless/01825_type_json_4.reference @@ -1,4 +1,4 @@ -Code: 594 +Code: 598 Code: 15 Code: 53 1 ('v1') Tuple(k1 String) diff --git a/tests/queries/0_stateless/01825_type_json_4.sh b/tests/queries/0_stateless/01825_type_json_4.sh index 711e86f474f..5d85e59cd5f 100755 --- a/tests/queries/0_stateless/01825_type_json_4.sh +++ b/tests/queries/0_stateless/01825_type_json_4.sh @@ -10,7 +10,7 @@ $CLICKHOUSE_CLIENT -q "CREATE TABLE t_json_4(id UInt64, data JSON) \ ENGINE = MergeTree ORDER BY tuple()" echo '{"id": 1, "data": {"k1": "v1"}}, {"id": 2, "data": {"k1": [1, 2]}}' \ - | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 594" + | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 598" echo '{"id": 1, "data": {"k1": "v1"}}, {"id": 2, "data": {"k1": [{"k2" : 1}, {"k2" : 2}]}}' \ | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 15" diff --git a/tests/queries/0_stateless/01825_type_json_field.reference b/tests/queries/0_stateless/01825_type_json_field.reference new file mode 100644 index 00000000000..cabaf04d7ad --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_field.reference @@ -0,0 +1,12 @@ +1 10 a +Tuple(a UInt32, s String) +1 10 a 0 +2 sss b 300 +3 20 c 0 +Tuple(a String, b UInt64, s String) +1 10 a 0 +2 sss b 300 +3 20 c 0 +4 30 400 +5 0 qqq 0 foo +Tuple(a String, b UInt64, s String, t String) diff --git a/tests/queries/0_stateless/01825_type_json_field.sql b/tests/queries/0_stateless/01825_type_json_field.sql new file mode 100644 index 00000000000..b1e00b3ee96 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_field.sql @@ -0,0 +1,24 @@ +DROP TABLE IF EXISTS t_json_field; + +CREATE TABLE t_json_field (id UInt32, data JSON) +ENGINE = MergeTree ORDER BY tuple(); + +INSERT INTO t_json_field VALUES (1, (10, 'a')::Tuple(a UInt32, s String)); + +SELECT id, data.a, data.s FROM t_json_field ORDER BY id; +SELECT DISTINCT toTypeName(data) FROM t_json_field; + +INSERT INTO t_json_field VALUES (2, ('sss', 300, 'b')::Tuple(a String, b UInt64, s String)), (3, (20, 'c')::Tuple(a UInt32, s String)); + +SELECT id, data.a, data.s, data.b FROM t_json_field ORDER BY id; +SELECT DISTINCT toTypeName(data) FROM t_json_field; + +INSERT INTO t_json_field VALUES (4, map('a', 30, 'b', 400)), (5, map('s', 'qqq', 't', 'foo')); + +SELECT id, data.a, data.s, data.b, data.t FROM t_json_field ORDER BY id; +SELECT DISTINCT toTypeName(data) FROM t_json_field; + +INSERT INTO t_json_field VALUES (6, map(1, 2, 3, 4)); -- { clientError 53 } +INSERT INTO t_json_field VALUES (6, (1, 2, 3)); -- { clientError 53 } + +DROP TABLE t_json_field; From b05943f4a460f394a86871c7f1f39ee32b75064b Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 21 Aug 2021 03:31:44 +0300 Subject: [PATCH 0056/1647] add text serialization for type Object --- src/DataTypes/ObjectUtils.cpp | 2 +- .../Serializations/SerializationObject.cpp | 51 +++++++++++++++---- .../Serializations/SerializationObject.h | 4 +- src/Functions/FunctionsConversion.h | 5 +- .../0_stateless/01825_type_json_5.reference | 5 ++ .../queries/0_stateless/01825_type_json_5.sql | 19 +++++++ 6 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_5.reference create mode 100644 tests/queries/0_stateless/01825_type_json_5.sql diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 21b60f25707..7352dda256a 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -121,7 +121,7 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con if (!column_object.isFinalized()) throw Exception(ErrorCodes::LOGICAL_ERROR, - "Cannot convert to tuple column '{}' from type {}. Column should be finalized firts", + "Cannot convert to tuple column '{}' from type {}. Column should be finalized first", name_type.name, name_type.type->getName()); std::vector> tuple_elements; diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 6277f58bef9..1326a7b56e8 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -156,7 +156,7 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( settings.path.back() = Substream::ObjectStructure; settings.path.back().object_key_name = key; - auto type = getDataTypeByColumn(subcolumn.getFinalizedColumn()); + const auto & type = subcolumn.getLeastCommonType(); if (auto * stream = settings.getter(settings.path)) { writeStringBinary(key, *stream); @@ -264,34 +264,63 @@ void SerializationObject::deserializeBinary(IColumn &, ReadBuffer &) con throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); } +/// TODO: use format different of JSON in serializations. + template -void SerializationObject::serializeText(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const +void SerializationObject::serializeTextImpl(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); + const auto & column_object = assert_cast(column); + const auto & subcolumns = column_object.getSubcolumns(); + + writeChar('{', ostr); + for (auto it = subcolumns.begin(); it != subcolumns.end(); ++it) + { + if (it != subcolumns.begin()) + writeCString(",", ostr); + + writeDoubleQuoted(it->first, ostr); + writeChar(':', ostr); + + auto serialization = it->second.getLeastCommonType()->getDefaultSerialization(); + serialization->serializeTextJSON(it->second.getFinalizedColumn(), row_num, ostr, settings); + } + writeChar('}', ostr); } template -void SerializationObject::serializeTextEscaped(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const +void SerializationObject::serializeText(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); + serializeTextImpl(column, row_num, ostr, settings); } template -void SerializationObject::serializeTextQuoted(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const +void SerializationObject::serializeTextEscaped(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); + WriteBufferFromOwnString ostr_str; + serializeTextImpl(column, row_num, ostr_str, settings); + writeEscapedString(ostr_str.str(), ostr); } template -void SerializationObject::serializeTextJSON(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const +void SerializationObject::serializeTextQuoted(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); + WriteBufferFromOwnString ostr_str; + serializeTextImpl(column, row_num, ostr_str, settings); + writeQuotedString(ostr_str.str(), ostr); } template -void SerializationObject::serializeTextCSV(const IColumn &, size_t, WriteBuffer &, const FormatSettings &) const +void SerializationObject::serializeTextJSON(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not implemented for SerializationObject"); + serializeTextImpl(column, row_num, ostr, settings); +} + +template +void SerializationObject::serializeTextCSV(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const +{ + WriteBufferFromOwnString ostr_str; + serializeTextImpl(column, row_num, ostr_str, settings); + writeCSVString(ostr_str.str(), ostr); } SerializationPtr getObjectSerialization(const String & schema_format) diff --git a/src/DataTypes/Serializations/SerializationObject.h b/src/DataTypes/Serializations/SerializationObject.h index eba013b1664..da490e6f461 100644 --- a/src/DataTypes/Serializations/SerializationObject.h +++ b/src/DataTypes/Serializations/SerializationObject.h @@ -40,7 +40,7 @@ public: void serializeBinary(const IColumn & column, size_t row_num, WriteBuffer & ostr) const override; void deserializeBinary(IColumn & column, ReadBuffer & istr) const override; - void serializeText(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings &) const override; + void serializeText(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const override; void serializeTextEscaped(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const override; void serializeTextQuoted(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const override; void serializeTextJSON(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const override; @@ -59,6 +59,8 @@ private: template void deserializeTextImpl(IColumn & column, Reader && reader) const; + void serializeTextImpl(const IColumn & column, size_t row_num, WriteBuffer & ostr, const FormatSettings & settings) const; + mutable Parser parser; }; diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 79e0426d1db..5134e7a61ea 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -3044,7 +3044,10 @@ private: { return [] (ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, const ColumnNullable *, size_t /*input_rows_count*/) { - return ConvertImplGenericFromString::execute(arguments, result_type); + auto res = ConvertImplGenericFromString::execute(arguments, result_type); + auto & res_object = assert_cast(res->assumeMutableRef()); + res_object.finalize(); + return res; }; } diff --git a/tests/queries/0_stateless/01825_type_json_5.reference b/tests/queries/0_stateless/01825_type_json_5.reference new file mode 100644 index 00000000000..8abbbe74968 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_5.reference @@ -0,0 +1,5 @@ +{"a.c":2,"a.b":1} +{"s":{"a.c":2,"a.b":1}} +1 [22,33] +2 qqq [44] +Tuple(k1 Int8, `k2.k3` String, `k2.k4` Array(Int8)) diff --git a/tests/queries/0_stateless/01825_type_json_5.sql b/tests/queries/0_stateless/01825_type_json_5.sql new file mode 100644 index 00000000000..7d9498e454b --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_5.sql @@ -0,0 +1,19 @@ +SELECT '{"a": {"b": 1, "c": 2}}'::JSON AS s; +SELECT '{"a": {"b": 1, "c": 2}}'::JSON AS s format JSONEachRow; + +DROP TABLE IF EXISTS t_json_5; +DROP TABLE IF EXISTS t_json_str_5; + +CREATE TABLE t_json_str_5 (data String) ENGINE = MergeTree ORDER BY tuple(); +CREATE TABLE t_json_5 (data JSON) ENGINE = MergeTree ORDER BY tuple(); + +INSERT INTO t_json_str_5 FORMAT JSONAsString {"k1": 1, "k2": {"k4": [22, 33]}}, {"k1": 2, "k2": {"k3": "qqq", "k4": [44]}} +; + +INSERT INTO t_json_5 SELECT data FROM t_json_str_5; + +SELECT data.k1, data.k2.k3, data.k2.k4 FROM t_json_5 ORDER BY data.k1; +SELECT DISTINCT toTypeName(data) FROM t_json_5; + +DROP TABLE t_json_5; +DROP TABLE t_json_str_5; From ee9f85686b66f6a7984ca8556d137bd571610b16 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 9 Sep 2021 14:27:10 +0300 Subject: [PATCH 0057/1647] fix build --- src/Core/Field.h | 4 --- src/Storages/MergeTree/MergeTreeData.cpp | 32 +++++++++++++++++++++++- src/Storages/StorageExecutable.cpp | 4 +-- src/Storages/StorageExecutable.h | 2 +- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/Core/Field.h b/src/Core/Field.h index a139316e3fd..45abb10f3b9 100644 --- a/src/Core/Field.h +++ b/src/Core/Field.h @@ -297,10 +297,6 @@ public: Map = 26, UUID = 27, Object = 28, - - // Special types for index analysis - NegativeInfinity = 254, - PositiveInfinity = 255, }; static const char * toString(Which which) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 11b52a8bd27..28185a44512 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -4515,7 +4515,37 @@ bool MergeTreeData::getQueryProcessingStageWithAggregateProjection( } // Let's select the best projection to execute the query. - if (!candidates.empty() && !selected_candidate)src/Storages/StorageS3.cpp + if (!candidates.empty() && !selected_candidate) + { + std::shared_ptr max_added_blocks; + if (settings.select_sequential_consistency) + { + if (const StorageReplicatedMergeTree * replicated = dynamic_cast(this)) + max_added_blocks = std::make_shared(replicated->getMaxAddedBlocks()); + } + + auto parts = getDataPartsVector(); + MergeTreeDataSelectExecutor reader(*this); + query_info.merge_tree_select_result_ptr = reader.estimateNumMarksToRead( + parts, + analysis_result.required_columns, + metadata_snapshot, + metadata_snapshot, + query_info, + query_context, + settings.max_threads, + max_added_blocks); + + if (!query_info.merge_tree_select_result_ptr->error()) + { + // Add 1 to base sum_marks so that we prefer projections even when they have equal number of marks to read. + // NOTE: It is not clear if we need it. E.g. projections do not support skip index for now. + min_sum_marks = query_info.merge_tree_select_result_ptr->marks() + 1; + } + + /// Favor aggregate projections + for (auto & candidate : candidates) + { if (candidate.desc->type == ProjectionDescription::Type::Aggregate) { selectBestProjection( diff --git a/src/Storages/StorageExecutable.cpp b/src/Storages/StorageExecutable.cpp index 4b0aaf6caea..86479f8242a 100644 --- a/src/Storages/StorageExecutable.cpp +++ b/src/Storages/StorageExecutable.cpp @@ -77,7 +77,7 @@ StorageExecutable::StorageExecutable( Pipe StorageExecutable::read( const Names & /*column_names*/, - const StorageMetadataPtr & metadata_snapshot, + const StorageSnapshotPtr & storage_snapshot, SelectQueryInfo & /*query_info*/, ContextPtr context, QueryProcessingStage::Enum /*processed_stage*/, @@ -172,7 +172,7 @@ Pipe StorageExecutable::read( tasks.emplace_back(std::move(task)); } - auto sample_block = metadata_snapshot->getSampleBlock(); + auto sample_block = storage_snapshot->metadata->getSampleBlock(); ShellCommandSourceConfiguration configuration; configuration.max_block_size = max_block_size; diff --git a/src/Storages/StorageExecutable.h b/src/Storages/StorageExecutable.h index dd986ee3956..4974f845c14 100644 --- a/src/Storages/StorageExecutable.h +++ b/src/Storages/StorageExecutable.h @@ -31,7 +31,7 @@ public: Pipe read( const Names & column_names, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & query_info, ContextPtr context, QueryProcessingStage::Enum processed_stage, From 3ff89a4e292419fb06028ccf3f73678c44168f66 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 9 Sep 2021 14:31:21 +0300 Subject: [PATCH 0058/1647] fix tests --- tests/queries/0_stateless/01825_type_json_4.reference | 2 +- tests/queries/0_stateless/01825_type_json_4.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/01825_type_json_4.reference b/tests/queries/0_stateless/01825_type_json_4.reference index 8bef8c02ee5..37a2413598f 100644 --- a/tests/queries/0_stateless/01825_type_json_4.reference +++ b/tests/queries/0_stateless/01825_type_json_4.reference @@ -1,4 +1,4 @@ -Code: 598 +Code: 619 Code: 15 Code: 53 1 ('v1') Tuple(k1 String) diff --git a/tests/queries/0_stateless/01825_type_json_4.sh b/tests/queries/0_stateless/01825_type_json_4.sh index 5d85e59cd5f..bb563820e6d 100755 --- a/tests/queries/0_stateless/01825_type_json_4.sh +++ b/tests/queries/0_stateless/01825_type_json_4.sh @@ -10,7 +10,7 @@ $CLICKHOUSE_CLIENT -q "CREATE TABLE t_json_4(id UInt64, data JSON) \ ENGINE = MergeTree ORDER BY tuple()" echo '{"id": 1, "data": {"k1": "v1"}}, {"id": 2, "data": {"k1": [1, 2]}}' \ - | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 598" + | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 619" echo '{"id": 1, "data": {"k1": "v1"}}, {"id": 2, "data": {"k1": [{"k2" : 1}, {"k2" : 2}]}}' \ | $CLICKHOUSE_CLIENT -q "INSERT INTO t_json_4 FORMAT JSONEachRow" 2>&1 | grep -o -m1 "Code: 15" From 71bf39c69c8c2fa5f19489cfe224b4c980ea89e3 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 9 Sep 2021 21:17:54 +0300 Subject: [PATCH 0059/1647] fix tests --- src/Storages/StorageSnapshot.cpp | 6 ++++-- tests/queries/0_stateless/01825_type_json_field.reference | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Storages/StorageSnapshot.cpp b/src/Storages/StorageSnapshot.cpp index e1fad3432a2..d452f3d9137 100644 --- a/src/Storages/StorageSnapshot.cpp +++ b/src/Storages/StorageSnapshot.cpp @@ -96,11 +96,13 @@ Block StorageSnapshot::getSampleBlockForColumns(const Names & column_names) cons for (const auto & name : column_names) { auto column = columns.tryGetColumnOrSubcolumn(GetColumnsOptions::All, name); - if (column && !isObject(column->type)) + auto object_column = object_columns.tryGetColumnOrSubcolumn(GetColumnsOptions::All, name); + + if (column && !object_column) { res.insert({column->type->createColumn(), column->type, column->name}); } - else if (auto object_column = object_columns.tryGetColumnOrSubcolumn(GetColumnsOptions::All, name)) + else if (object_column) { res.insert({object_column->type->createColumn(), object_column->type, object_column->name}); } diff --git a/tests/queries/0_stateless/01825_type_json_field.reference b/tests/queries/0_stateless/01825_type_json_field.reference index cabaf04d7ad..b5637b1fbb7 100644 --- a/tests/queries/0_stateless/01825_type_json_field.reference +++ b/tests/queries/0_stateless/01825_type_json_field.reference @@ -1,12 +1,12 @@ 1 10 a -Tuple(a UInt32, s String) +Tuple(a UInt8, s String) 1 10 a 0 2 sss b 300 3 20 c 0 -Tuple(a String, b UInt64, s String) +Tuple(a String, b UInt16, s String) 1 10 a 0 2 sss b 300 3 20 c 0 4 30 400 5 0 qqq 0 foo -Tuple(a String, b UInt64, s String, t String) +Tuple(a String, b UInt16, s String, t String) From ee7c0d4cc1eec5fadf9a081db4110053b6d71c27 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 10 Sep 2021 00:16:57 +0300 Subject: [PATCH 0060/1647] dynamic columns: fix several cases of parsing json --- src/DataTypes/Serializations/JSONDataParser.h | 9 +- src/Formats/registerFormats.cpp | 2 + src/IO/ReadHelpers.cpp | 24 +++- .../Impl/JSONAsObjectRowInputFormat.cpp | 84 ------------ .../Impl/JSONAsStringRowInputFormat.cpp | 125 ++++++++++++------ .../Formats/Impl/JSONAsStringRowInputFormat.h | 32 ++++- 6 files changed, 134 insertions(+), 142 deletions(-) delete mode 100644 src/Processors/Formats/Impl/JSONAsObjectRowInputFormat.cpp diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index ecf0107b255..c5d6bdd2826 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -12,11 +12,8 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -namespace -{ - template -Field getValueAsField(const Element & element) +static Field getValueAsField(const Element & element) { if (element.isBool()) return element.getBool(); if (element.isInt64()) return element.getInt64(); @@ -28,7 +25,7 @@ Field getValueAsField(const Element & element) throw Exception(ErrorCodes::LOGICAL_ERROR, "Unsupported type of JSON field"); } -String getNextPath(const String & current_path, const std::string_view & key) +static String getNextPath(const String & current_path, const std::string_view & key) { String next_path = current_path; if (!key.empty()) @@ -41,8 +38,6 @@ String getNextPath(const String & current_path, const std::string_view & key) return next_path; } -} - struct ParseResult { Strings paths; diff --git a/src/Formats/registerFormats.cpp b/src/Formats/registerFormats.cpp index 1ed5d558ef6..5a955aeccd5 100644 --- a/src/Formats/registerFormats.cpp +++ b/src/Formats/registerFormats.cpp @@ -84,6 +84,7 @@ void registerInputFormatProcessorCapnProto(FormatFactory & factory); /// Non trivial prefix and suffix checkers for disabling parallel parsing. void registerNonTrivialPrefixAndSuffixCheckerJSONEachRow(FormatFactory & factory); void registerNonTrivialPrefixAndSuffixCheckerJSONAsString(FormatFactory & factory); +void registerNonTrivialPrefixAndSuffixCheckerJSONAsObject(FormatFactory & factory); void registerFormats() { @@ -163,6 +164,7 @@ void registerFormats() registerNonTrivialPrefixAndSuffixCheckerJSONEachRow(factory); registerNonTrivialPrefixAndSuffixCheckerJSONAsString(factory); + registerNonTrivialPrefixAndSuffixCheckerJSONAsObject(factory); } } diff --git a/src/IO/ReadHelpers.cpp b/src/IO/ReadHelpers.cpp index 9bbd87629b6..cd3435f1801 100644 --- a/src/IO/ReadHelpers.cpp +++ b/src/IO/ReadHelpers.cpp @@ -771,28 +771,38 @@ ReturnType readJSONObjectPossiblyInvalid(Vector & s, ReadBuffer & buf) s.push_back(*buf.position()); ++buf.position(); + Int64 balance = 1; + bool quotes = false; + while (!buf.eof()) { - char * next_pos = find_first_symbols<'\\', '{', '}'>(buf.position(), buf.buffer().end()); + char * next_pos = find_first_symbols<'\\', '{', '}', '"'>(buf.position(), buf.buffer().end()); appendToStringOrVector(s, buf, next_pos); buf.position() = next_pos; if (!buf.hasPendingData()) continue; + s.push_back(*buf.position()); + if (*buf.position() == '\\') { - parseJSONEscapeSequence(s, buf); + ++buf.position(); + if (!buf.eof()) + { + s.push_back(*buf.position()); + ++buf.position(); + } + continue; } - if (*buf.position() == '}') - --balance; - else if (*buf.position() == '{') - ++balance; + if (*buf.position() == '"') + quotes = !quotes; + else if (!quotes) // can be only '{' or '}' + balance += *buf.position() == '{' ? 1 : -1; - s.push_back(*buf.position()); ++buf.position(); if (balance == 0) diff --git a/src/Processors/Formats/Impl/JSONAsObjectRowInputFormat.cpp b/src/Processors/Formats/Impl/JSONAsObjectRowInputFormat.cpp deleted file mode 100644 index 48550c50329..00000000000 --- a/src/Processors/Formats/Impl/JSONAsObjectRowInputFormat.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int BAD_ARGUMENTS; -} - -namespace -{ - -class JSONAsObjectRowInputFormat : public IRowInputFormat -{ -public: - JSONAsObjectRowInputFormat(ReadBuffer & in_, const Block & header_, Params params_, const FormatSettings & format_settings_); - - bool readRow(MutableColumns & columns, RowReadExtension & ext) override; - String getName() const override { return "JSONAsObjectRowInputFormat"; } - -private: - const FormatSettings format_settings; -}; - -JSONAsObjectRowInputFormat::JSONAsObjectRowInputFormat( - ReadBuffer & in_, const Block & header_, Params params_, const FormatSettings & format_settings_) - : IRowInputFormat(header_, in_, std::move(params_)) - , format_settings(format_settings_) -{ - if (header_.columns() != 1) - throw Exception(ErrorCodes::BAD_ARGUMENTS, - "Input format JSONAsObject is only suitable for tables with a single column of type Object but the number of columns is {}", - header_.columns()); - - if (!isObject(header_.getByPosition(0).type)) - throw Exception(ErrorCodes::BAD_ARGUMENTS, - "Input format JSONAsObject is only suitable for tables with a single column of type Object but the column type is {}", - header_.getByPosition(0).type->getName()); -} - - -bool JSONAsObjectRowInputFormat::readRow(MutableColumns & columns, RowReadExtension &) -{ - assert(serializations.size() == 1); - assert(columns.size() == 1); - - skipWhitespaceIfAny(in); - if (!in.eof()) - serializations[0]->deserializeTextJSON(*columns[0], in, format_settings); - - skipWhitespaceIfAny(in); - if (!in.eof() && *in.position() == ',') - ++in.position(); - skipWhitespaceIfAny(in); - - return !in.eof(); -} - -} - -void registerInputFormatProcessorJSONAsObject(FormatFactory & factory) -{ - factory.registerInputFormatProcessor("JSONAsObject", []( - ReadBuffer & buf, - const Block & sample, - IRowInputFormat::Params params, - const FormatSettings & settings) - { - return std::make_shared(buf, sample, std::move(params), settings); - }); -} - -void registerFileSegmentationEngineJSONAsObject(FormatFactory & factory) -{ - factory.registerFileSegmentationEngine("JSONAsObject", &fileSegmentationEngineJSONEachRowImpl); -} - -} diff --git a/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.cpp b/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.cpp index e968c187bef..6e3396dd48a 100644 --- a/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.cpp +++ b/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.cpp @@ -14,27 +14,23 @@ namespace ErrorCodes extern const int INCORRECT_DATA; } -JSONAsStringRowInputFormat::JSONAsStringRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_) : - IRowInputFormat(header_, in_, std::move(params_)), buf(in) +JSONAsRowInputFormat::JSONAsRowInputFormat( + const Block & header_, ReadBuffer & in_, Params params_) + : IRowInputFormat(header_, in_, std::move(params_)), buf(in) { if (header_.columns() > 1) throw Exception(ErrorCodes::BAD_ARGUMENTS, - "This input format is only suitable for tables with a single column of type String but the number of columns is {}", + "This input format is only suitable for tables with a single column of type String or Object, but the number of columns is {}", header_.columns()); - - if (!isString(removeNullable(removeLowCardinality(header_.getByPosition(0).type)))) - throw Exception(ErrorCodes::BAD_ARGUMENTS, - "This input format is only suitable for tables with a single column of type String but the column type is {}", - header_.getByPosition(0).type->getName()); } -void JSONAsStringRowInputFormat::resetParser() +void JSONAsRowInputFormat::resetParser() { IRowInputFormat::resetParser(); buf.reset(); } -void JSONAsStringRowInputFormat::readPrefix() +void JSONAsRowInputFormat::readPrefix() { /// In this format, BOM at beginning of stream cannot be confused with value, so it is safe to skip it. skipBOMIfExists(buf); @@ -47,7 +43,7 @@ void JSONAsStringRowInputFormat::readPrefix() } } -void JSONAsStringRowInputFormat::readSuffix() +void JSONAsRowInputFormat::readSuffix() { skipWhitespaceIfAny(buf); if (data_in_square_brackets) @@ -63,6 +59,51 @@ void JSONAsStringRowInputFormat::readSuffix() assertEOF(buf); } +bool JSONAsRowInputFormat::readRow(MutableColumns & columns, RowReadExtension &) +{ + assert(columns.size() == 1); + assert(serializations.size() == 1); + + if (!allow_new_rows) + return false; + + skipWhitespaceIfAny(buf); + if (!buf.eof()) + { + if (!data_in_square_brackets && *buf.position() == ';') + { + /// ';' means the end of query, but it cannot be before ']'. + return allow_new_rows = false; + } + else if (data_in_square_brackets && *buf.position() == ']') + { + /// ']' means the end of query. + return allow_new_rows = false; + } + } + + if (!buf.eof()) + readJSONObject(*columns[0]); + + skipWhitespaceIfAny(buf); + if (!buf.eof() && *buf.position() == ',') + ++buf.position(); + skipWhitespaceIfAny(buf); + + return !buf.eof(); +} + + +JSONAsStringRowInputFormat::JSONAsStringRowInputFormat( + const Block & header_, ReadBuffer & in_, Params params_) + : JSONAsRowInputFormat(header_, in_, params_) +{ + if (!isString(removeNullable(removeLowCardinality(header_.getByPosition(0).type)))) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "This input format is only suitable for tables with a single column of type String but the column type is {}", + header_.getByPosition(0).type->getName()); +} + void JSONAsStringRowInputFormat::readJSONObject(IColumn & column) { PeekableReadBufferCheckpoint checkpoint{buf}; @@ -140,35 +181,21 @@ void JSONAsStringRowInputFormat::readJSONObject(IColumn & column) buf.position() = end; } -bool JSONAsStringRowInputFormat::readRow(MutableColumns & columns, RowReadExtension &) + +JSONAsObjectRowInputFormat::JSONAsObjectRowInputFormat( + const Block & header_, ReadBuffer & in_, Params params_, const FormatSettings & format_settings_) + : JSONAsRowInputFormat(header_, in_, params_) + , format_settings(format_settings_) { - if (!allow_new_rows) - return false; + if (!isObject(header_.getByPosition(0).type)) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "Input format JSONAsObject is only suitable for tables with a single column of type Object but the column type is {}", + header_.getByPosition(0).type->getName()); +} - skipWhitespaceIfAny(buf); - if (!buf.eof()) - { - if (!data_in_square_brackets && *buf.position() == ';') - { - /// ';' means the end of query, but it cannot be before ']'. - return allow_new_rows = false; - } - else if (data_in_square_brackets && *buf.position() == ']') - { - /// ']' means the end of query. - return allow_new_rows = false; - } - } - - if (!buf.eof()) - readJSONObject(*columns[0]); - - skipWhitespaceIfAny(buf); - if (!buf.eof() && *buf.position() == ',') - ++buf.position(); - skipWhitespaceIfAny(buf); - - return !buf.eof(); +void JSONAsObjectRowInputFormat::readJSONObject(IColumn & column) +{ + serializations[0]->deserializeTextJSON(column, buf, format_settings); } void registerInputFormatProcessorJSONAsString(FormatFactory & factory) @@ -193,4 +220,26 @@ void registerNonTrivialPrefixAndSuffixCheckerJSONAsString(FormatFactory & factor factory.registerNonTrivialPrefixAndSuffixChecker("JSONAsString", nonTrivialPrefixAndSuffixCheckerJSONEachRowImpl); } +void registerInputFormatProcessorJSONAsObject(FormatFactory & factory) +{ + factory.registerInputFormatProcessor("JSONAsObject", []( + ReadBuffer & buf, + const Block & sample, + IRowInputFormat::Params params, + const FormatSettings & settings) + { + return std::make_shared(sample, buf, std::move(params), settings); + }); +} + +void registerFileSegmentationEngineJSONAsObject(FormatFactory & factory) +{ + factory.registerFileSegmentationEngine("JSONAsObject", &fileSegmentationEngineJSONEachRowImpl); +} + +void registerNonTrivialPrefixAndSuffixCheckerJSONAsObject(FormatFactory & factory) +{ + factory.registerNonTrivialPrefixAndSuffixChecker("JSONAsObject", nonTrivialPrefixAndSuffixCheckerJSONEachRowImpl); +} + } diff --git a/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.h b/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.h index c70d9efb178..2efa16cde69 100644 --- a/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.h +++ b/src/Processors/Formats/Impl/JSONAsStringRowInputFormat.h @@ -13,26 +13,46 @@ class ReadBuffer; /// Each JSON object is parsed as a whole to string. /// This format can only parse a table with single field of type String. -class JSONAsStringRowInputFormat : public IRowInputFormat +class JSONAsRowInputFormat : public IRowInputFormat { public: - JSONAsStringRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_); + JSONAsRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_); bool readRow(MutableColumns & columns, RowReadExtension & ext) override; - String getName() const override { return "JSONAsStringRowInputFormat"; } void resetParser() override; void readPrefix() override; void readSuffix() override; -private: - void readJSONObject(IColumn & column); - +protected: + virtual void readJSONObject(IColumn & column) = 0; PeekableReadBuffer buf; +private: /// This flag is needed to know if data is in square brackets. bool data_in_square_brackets = false; bool allow_new_rows = true; }; +class JSONAsStringRowInputFormat final : public JSONAsRowInputFormat +{ +public: + JSONAsStringRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_); + String getName() const override { return "JSONAsStringRowInputFormat"; } + +private: + void readJSONObject(IColumn & column) override; +}; + +class JSONAsObjectRowInputFormat final : public JSONAsRowInputFormat +{ +public: + JSONAsObjectRowInputFormat(const Block & header_, ReadBuffer & in_, Params params_, const FormatSettings & format_settings_); + String getName() const override { return "JSONAsObjectRowInputFormat"; } + +private: + void readJSONObject(IColumn & column) override; + const FormatSettings format_settings; +}; + } From 3bd96d709d944378dbe9a82cb0334c450059e76f Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 11 Sep 2021 03:20:54 +0300 Subject: [PATCH 0061/1647] dynamic columns: fix several cases of parsing json --- src/Columns/ColumnObject.cpp | 58 ++++++++++++++----- ....reference => 01825_type_json_1.reference} | 0 ...25_type_json.sql => 01825_type_json_1.sql} | 0 .../0_stateless/01825_type_json_6.reference | 3 + .../queries/0_stateless/01825_type_json_6.sh | 56 ++++++++++++++++++ 5 files changed, 103 insertions(+), 14 deletions(-) rename tests/queries/0_stateless/{01825_type_json.reference => 01825_type_json_1.reference} (100%) rename tests/queries/0_stateless/{01825_type_json.sql => 01825_type_json_1.sql} (100%) create mode 100644 tests/queries/0_stateless/01825_type_json_6.reference create mode 100755 tests/queries/0_stateless/01825_type_json_6.sh diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 6b5610ce7d1..0953e1ec538 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -13,7 +13,6 @@ #include #include - namespace DB { @@ -28,25 +27,45 @@ namespace ErrorCodes namespace { +Array createEmptyArrayField(size_t num_dimensions) +{ + Array array; + Array * current_array = &array; + for (size_t i = 1; i < num_dimensions; ++i) + { + current_array->push_back(Array()); + current_array = ¤t_array->back().get(); + } + + return array; +} + class FieldVisitorReplaceNull : public StaticVisitor { public: - [[maybe_unused]] explicit FieldVisitorReplaceNull(const Field & replacement_) + [[maybe_unused]] explicit FieldVisitorReplaceNull( + const Field & replacement_, size_t num_dimensions_) : replacement(replacement_) + , num_dimensions(num_dimensions_) { } - Field operator()(const Null &) const { return replacement; } - template Field operator()(const T & x) const { - if constexpr (std::is_base_of_v) + if constexpr (std::is_same_v) { + return num_dimensions + ? createEmptyArrayField(num_dimensions) + : replacement; + } + else if constexpr (std::is_same_v) + { + assert(num_dimensions > 0); const size_t size = x.size(); - T res(size); + Array res(size); for (size_t i = 0; i < size; ++i) - res[i] = applyVisitor(*this, x[i]); + res[i] = applyVisitor(FieldVisitorReplaceNull(replacement, num_dimensions - 1), x[i]); return res; } else @@ -54,7 +73,8 @@ public: } private: - Field replacement; + const Field & replacement; + size_t num_dimensions; }; class FieldVisitorToNumberOfDimensions : public StaticVisitor @@ -66,16 +86,24 @@ public: return 1; const size_t size = x.size(); - size_t dimensions = applyVisitor(*this, x[0]); - for (size_t i = 1; i < size; ++i) + std::optional dimensions; + + for (size_t i = 0; i < size; ++i) { + /// Do not count Nulls, because they will be replaced by default + /// values with proper number of dimensions. + if (x[i].isNull()) + continue; + size_t current_dimensions = applyVisitor(*this, x[i]); - if (current_dimensions != dimensions) + if (!dimensions) + dimensions = current_dimensions; + else if (current_dimensions != *dimensions) throw Exception(ErrorCodes::NUMBER_OF_DIMENSIONS_MISMATHED, "Number of dimensions mismatched among array elements"); } - return 1 + dimensions; + return 1 + dimensions.value_or(0); } template @@ -239,6 +267,7 @@ void ColumnObject::Subcolumn::insert(Field field) if (is_nullable && !base_type->isNullable()) base_type = makeNullable(base_type); + auto value_type = createArrayOfType(base_type, value_dim); if (!is_nullable && base_type->isNullable()) { base_type = removeNullable(base_type); @@ -248,10 +277,11 @@ void ColumnObject::Subcolumn::insert(Field field) return; } - field = applyVisitor(FieldVisitorReplaceNull(base_type->getDefault()), std::move(field)); + value_type = createArrayOfType(base_type, value_dim); + auto default_value = value_type->getDefault(); + field = applyVisitor(FieldVisitorReplaceNull(default_value, value_dim), std::move(field)); } - auto value_type = createArrayOfType(base_type, value_dim); bool type_changed = false; if (data.empty()) diff --git a/tests/queries/0_stateless/01825_type_json.reference b/tests/queries/0_stateless/01825_type_json_1.reference similarity index 100% rename from tests/queries/0_stateless/01825_type_json.reference rename to tests/queries/0_stateless/01825_type_json_1.reference diff --git a/tests/queries/0_stateless/01825_type_json.sql b/tests/queries/0_stateless/01825_type_json_1.sql similarity index 100% rename from tests/queries/0_stateless/01825_type_json.sql rename to tests/queries/0_stateless/01825_type_json_1.sql diff --git a/tests/queries/0_stateless/01825_type_json_6.reference b/tests/queries/0_stateless/01825_type_json_6.reference new file mode 100644 index 00000000000..2efc2031545 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_6.reference @@ -0,0 +1,3 @@ +Tuple(key String, `out.outputs.index` Array(Array(Int32)), `out.outputs.n` Array(Array(Int8)), `out.type` Array(Int8), `out.value` Array(Int8)) +v1 [0,0] [1,2] [[],[1960131]] [[],[1960131]] +v2 [1,1] [4,3] [[1881212],[]] [[1881212],[]] diff --git a/tests/queries/0_stateless/01825_type_json_6.sh b/tests/queries/0_stateless/01825_type_json_6.sh new file mode 100755 index 00000000000..5ab0b77ab6b --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_6.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json_6;" + +$CLICKHOUSE_CLIENT -q"CREATE TABLE t_json_6 (data JSON) ENGINE = MergeTree ORDER BY tuple();" + +cat < Date: Sat, 11 Sep 2021 03:25:28 +0300 Subject: [PATCH 0062/1647] fix test --- tests/queries/0_stateless/01825_type_json_6.reference | 4 ++-- tests/queries/0_stateless/01825_type_json_6.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/queries/0_stateless/01825_type_json_6.reference b/tests/queries/0_stateless/01825_type_json_6.reference index 2efc2031545..d3229d48632 100644 --- a/tests/queries/0_stateless/01825_type_json_6.reference +++ b/tests/queries/0_stateless/01825_type_json_6.reference @@ -1,3 +1,3 @@ Tuple(key String, `out.outputs.index` Array(Array(Int32)), `out.outputs.n` Array(Array(Int8)), `out.type` Array(Int8), `out.value` Array(Int8)) -v1 [0,0] [1,2] [[],[1960131]] [[],[1960131]] -v2 [1,1] [4,3] [[1881212],[]] [[1881212],[]] +v1 [0,0] [1,2] [[],[1960131]] [[],[0]] +v2 [1,1] [4,3] [[1881212],[]] [[1],[]] diff --git a/tests/queries/0_stateless/01825_type_json_6.sh b/tests/queries/0_stateless/01825_type_json_6.sh index 5ab0b77ab6b..837b0d2290c 100755 --- a/tests/queries/0_stateless/01825_type_json_6.sh +++ b/tests/queries/0_stateless/01825_type_json_6.sh @@ -6,7 +6,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) $CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json_6;" -$CLICKHOUSE_CLIENT -q"CREATE TABLE t_json_6 (data JSON) ENGINE = MergeTree ORDER BY tuple();" +$CLICKHOUSE_CLIENT -q "CREATE TABLE t_json_6 (data JSON) ENGINE = MergeTree ORDER BY tuple();" cat < Date: Mon, 13 Sep 2021 15:40:39 +0300 Subject: [PATCH 0063/1647] dynamic columns: optimize check for ambiguos paths --- src/DataTypes/ObjectUtils.cpp | 32 ++++++++++++++++---------------- src/DataTypes/ObjectUtils.h | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 7352dda256a..48f0a7415c3 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -143,7 +143,7 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con if (it == storage_columns_map.end()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Column '{}' not found in storage", name_type.name); - getLeastCommonTypeForObject({type_tuple, it->second}); + getLeastCommonTypeForObject({type_tuple, it->second}, true); name_type.type = type_tuple; column.type = type_tuple; @@ -165,26 +165,25 @@ static bool isPrefix(const Strings & prefix, const Strings & strings) void checkObjectHasNoAmbiguosPaths(const Strings & key_names) { - for (const auto & name : key_names) + size_t size = key_names.size(); + std::vector names_parts(size); + + for (size_t i = 0; i < size; ++i) + boost::split(names_parts[i], key_names[i], boost::is_any_of(".")); + + for (size_t i = 0; i < size; ++i) { - Strings name_parts; - boost::split(name_parts, name, boost::is_any_of(".")); - - for (const auto & other_name : key_names) + for (size_t j = 0; j < i; ++j) { - if (other_name.size() > name.size()) - { - Strings other_name_parts; - boost::split(other_name_parts, other_name, boost::is_any_of(".")); - - if (isPrefix(name_parts, other_name_parts)) - throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Data in Object has ambiguous paths: '{}' and '{}", name, other_name); - } + if (isPrefix(names_parts[i], names_parts[j]) || isPrefix(names_parts[j], names_parts[i])) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, + "Data in Object has ambiguous paths: '{}' and '{}", + key_names[i], key_names[j]); } } } -DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) +DataTypePtr getLeastCommonTypeForObject(const DataTypes & types, bool check_ambiguos_paths) { std::unordered_map subcolumns_types; for (const auto & type : types) @@ -228,7 +227,8 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types) auto tuple_names = extractVector<0>(tuple_elements); auto tuple_types = extractVector<1>(tuple_elements); - checkObjectHasNoAmbiguosPaths(tuple_names); + if (check_ambiguos_paths) + checkObjectHasNoAmbiguosPaths(tuple_names); return std::make_shared(tuple_types, tuple_names); } diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 1da7031670d..0c0074d2c98 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -16,7 +16,7 @@ DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns); void checkObjectHasNoAmbiguosPaths(const Strings & key_names); -DataTypePtr getLeastCommonTypeForObject(const DataTypes & types); +DataTypePtr getLeastCommonTypeForObject(const DataTypes & types, bool check_ambiguos_paths = false); NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list); void extendObjectColumns(NamesAndTypesList & columns_list, const ColumnsDescription & object_columns, bool with_subcolumns); From d09f973b83a0dd35d4cb9e3f17be8dbc041179be Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 13 Sep 2021 17:02:38 +0300 Subject: [PATCH 0064/1647] dynamic columns: fix several cases of parsing json --- src/Columns/ColumnObject.cpp | 10 +++++-- .../0_stateless/01825_type_json_7.reference | 4 +++ .../queries/0_stateless/01825_type_json_7.sh | 29 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 tests/queries/0_stateless/01825_type_json_7.reference create mode 100755 tests/queries/0_stateless/01825_type_json_7.sh diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 0953e1ec538..9476a5b2a2e 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -245,10 +245,16 @@ void ColumnObject::Subcolumn::checkTypes() const void ColumnObject::Subcolumn::insert(Field field) { - auto value_dim = applyVisitor(FieldVisitorToNumberOfDimensions(), field); auto column_dim = getNumberOfDimensions(*least_common_type); + auto value_dim = applyVisitor(FieldVisitorToNumberOfDimensions(), field); - if (!isNothing(least_common_type) && value_dim != column_dim) + if (isNothing(least_common_type)) + column_dim = value_dim; + + if (field.isNull()) + value_dim = column_dim; + + if (value_dim != column_dim) throw Exception(ErrorCodes::NUMBER_OF_DIMENSIONS_MISMATHED, "Dimension of types mismatched beetwen inserted value and column." "Dimension of value: {}. Dimension of column: {}", diff --git a/tests/queries/0_stateless/01825_type_json_7.reference b/tests/queries/0_stateless/01825_type_json_7.reference new file mode 100644 index 00000000000..263f1688a91 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_7.reference @@ -0,0 +1,4 @@ +Tuple(categories Array(String), key String) +v1 [] +v2 ['foo','bar'] +v3 [] diff --git a/tests/queries/0_stateless/01825_type_json_7.sh b/tests/queries/0_stateless/01825_type_json_7.sh new file mode 100755 index 00000000000..24de546b206 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_7.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json_7;" + +$CLICKHOUSE_CLIENT -q "CREATE TABLE t_json_7 (data JSON) ENGINE = MergeTree ORDER BY tuple();" + +cat < Date: Mon, 13 Sep 2021 17:45:33 +0300 Subject: [PATCH 0065/1647] dynamic columns: fix write to replicated --- src/DataTypes/IDataType.h | 4 +++- src/Storages/StorageInMemoryMetadata.cpp | 13 ++++++++++--- tests/queries/0_stateless/01825_type_json_2.sql | 2 +- tests/queries/0_stateless/01825_type_json_3.sql | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/DataTypes/IDataType.h b/src/DataTypes/IDataType.h index 5954a7493f2..3ddee3ffa8c 100644 --- a/src/DataTypes/IDataType.h +++ b/src/DataTypes/IDataType.h @@ -384,10 +384,12 @@ inline bool isDecimal(const DataTypePtr & data_type) { return WhichDataType(data inline bool isTuple(const DataTypePtr & data_type) { return WhichDataType(data_type).isTuple(); } inline bool isArray(const DataTypePtr & data_type) { return WhichDataType(data_type).isArray(); } inline bool isMap(const DataTypePtr & data_type) {return WhichDataType(data_type).isMap(); } -inline bool isObject(const DataTypePtr & data_type) {return WhichDataType(data_type).isObject(); } inline bool isNothing(const DataTypePtr & data_type) { return WhichDataType(data_type).isNothing(); } inline bool isUUID(const DataTypePtr & data_type) { return WhichDataType(data_type).isUUID(); } +template +inline bool isObject(const T & data_type) {return WhichDataType(data_type).isObject(); } + template inline bool isUInt8(const T & data_type) { diff --git a/src/Storages/StorageInMemoryMetadata.cpp b/src/Storages/StorageInMemoryMetadata.cpp index b1531ad430e..074b0be4e3c 100644 --- a/src/Storages/StorageInMemoryMetadata.cpp +++ b/src/Storages/StorageInMemoryMetadata.cpp @@ -507,7 +507,10 @@ void StorageInMemoryMetadata::check(const NamesAndTypesList & provided_columns) listOfColumns(available_columns)); const auto * available_type = it->getMapped(); - if (!column.type->equals(*available_type) && !isCompatibleEnumTypes(available_type, column.type.get())) + + if (!isObject(*available_type) + && !column.type->equals(*available_type) + && !isCompatibleEnumTypes(available_type, column.type.get())) throw Exception( ErrorCodes::TYPE_MISMATCH, "Type mismatch for column {}. Column has type {}, got type {}", @@ -554,7 +557,9 @@ void StorageInMemoryMetadata::check(const NamesAndTypesList & provided_columns, const auto * provided_column_type = it->getMapped(); const auto * available_column_type = jt->getMapped(); - if (!provided_column_type->equals(*available_column_type) && !isCompatibleEnumTypes(available_column_type, provided_column_type)) + if (!isObject(*provided_column_type) + && !provided_column_type->equals(*available_column_type) + && !isCompatibleEnumTypes(available_column_type, provided_column_type)) throw Exception( ErrorCodes::TYPE_MISMATCH, "Type mismatch for column {}. Column has type {}, got type {}", @@ -596,7 +601,9 @@ void StorageInMemoryMetadata::check(const Block & block, bool need_all) const listOfColumns(available_columns)); const auto * available_type = it->getMapped(); - if (!column.type->equals(*available_type) && !isCompatibleEnumTypes(available_type, column.type.get())) + if (!isObject(*available_type) + && !column.type->equals(*available_type) + && !isCompatibleEnumTypes(available_type, column.type.get())) throw Exception( ErrorCodes::TYPE_MISMATCH, "Type mismatch for column {}. Column has type {}, got type {}", diff --git a/tests/queries/0_stateless/01825_type_json_2.sql b/tests/queries/0_stateless/01825_type_json_2.sql index fa87aacae29..126ae297e1b 100644 --- a/tests/queries/0_stateless/01825_type_json_2.sql +++ b/tests/queries/0_stateless/01825_type_json_2.sql @@ -1,7 +1,7 @@ DROP TABLE IF EXISTS t_json_2; CREATE TABLE t_json_2(id UInt64, data Object('JSON')) -ENGINE = MergeTree ORDER BY tuple(); +ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/test_01825_2/t_json_2', 'r1') ORDER BY tuple(); INSERT INTO t_json_2 FORMAT JSONEachRow {"id": 1, "data": {"k1": 1, "k2" : 2}} {"id": 2, "data": {"k2": 3, "k3" : 4}}; diff --git a/tests/queries/0_stateless/01825_type_json_3.sql b/tests/queries/0_stateless/01825_type_json_3.sql index a90ec162609..93dab846c17 100644 --- a/tests/queries/0_stateless/01825_type_json_3.sql +++ b/tests/queries/0_stateless/01825_type_json_3.sql @@ -1,7 +1,7 @@ DROP TABLE IF EXISTS t_json_3; CREATE TABLE t_json_3(id UInt64, data JSON) -ENGINE = MergeTree ORDER BY tuple(); +ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/test_01825_3/t_json_3', 'r1') ORDER BY tuple(); SYSTEM STOP MERGES t_json_3; From b0c0b2bc1096ad49d773fcf7520f827f895bd928 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Tue, 14 Sep 2021 17:02:32 +0300 Subject: [PATCH 0066/1647] fix some builds --- src/Columns/ColumnObject.cpp | 19 +++++-------- src/Columns/ColumnObject.h | 2 -- src/Columns/ya.make | 1 + src/DataTypes/FieldToDataType.h | 28 ------------------- src/DataTypes/ObjectUtils.cpp | 13 +++------ .../Serializations/SerializationObject.cpp | 6 ++-- .../Serializations/SerializationObject.h | 4 +-- src/DataTypes/ya.make | 3 ++ src/Functions/IFunction.h | 6 ++-- src/Interpreters/InterpreterDescribeQuery.cpp | 17 ++++++----- src/Interpreters/convertFieldToType.cpp | 2 +- src/Storages/IStorage.cpp | 4 --- src/Storages/StorageSnapshot.cpp | 1 - src/Storages/ya.make | 1 + .../01825_type_json_schema_race_long.sh | 2 +- 15 files changed, 34 insertions(+), 75 deletions(-) diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 9476a5b2a2e..807eb4fb62f 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -22,6 +22,7 @@ namespace ErrorCodes extern const int ILLEGAL_COLUMN; extern const int DUPLICATE_COLUMN; extern const int NUMBER_OF_DIMENSIONS_MISMATHED; + extern const int NOT_IMPLEMENTED; } namespace @@ -181,14 +182,6 @@ private: } -ColumnObject::Subcolumn::Subcolumn(const Subcolumn & other) - : least_common_type(other.least_common_type) - , is_nullable(other.is_nullable) - , data(other.data) - , num_of_defaults_in_prefix(other.num_of_defaults_in_prefix) -{ -} - ColumnObject::Subcolumn::Subcolumn(MutableColumnPtr && data_) : least_common_type(getDataTypeByColumn(*data_)) , is_nullable(least_common_type->isNullable()) @@ -238,7 +231,7 @@ void ColumnObject::Subcolumn::checkTypes() const auto prefix_common_type = getLeastSupertype(prefix_types); if (!prefix_common_type->equals(*current_type)) throw Exception(ErrorCodes::LOGICAL_ERROR, - "Data type {} of column at postion {} cannot represent all columns from i-th prefix", + "Data type {} of column at position {} cannot represent all columns from i-th prefix", current_type->getName(), i); } } @@ -256,7 +249,7 @@ void ColumnObject::Subcolumn::insert(Field field) if (value_dim != column_dim) throw Exception(ErrorCodes::NUMBER_OF_DIMENSIONS_MISMATHED, - "Dimension of types mismatched beetwen inserted value and column." + "Dimension of types mismatched between inserted value and column." "Dimension of value: {}. Dimension of column: {}", value_dim, column_dim); @@ -273,7 +266,7 @@ void ColumnObject::Subcolumn::insert(Field field) if (is_nullable && !base_type->isNullable()) base_type = makeNullable(base_type); - auto value_type = createArrayOfType(base_type, value_dim); + DataTypePtr value_type; if (!is_nullable && base_type->isNullable()) { base_type = removeNullable(base_type); @@ -287,6 +280,8 @@ void ColumnObject::Subcolumn::insert(Field field) auto default_value = value_type->getDefault(); field = applyVisitor(FieldVisitorReplaceNull(default_value, value_dim), std::move(field)); } + else + value_type = createArrayOfType(base_type, value_dim); bool type_changed = false; @@ -496,7 +491,7 @@ void ColumnObject::forEachSubcolumn(ColumnCallback callback) void ColumnObject::insert(const Field & field) { - const auto & object = field.get(); + const auto & object = field.get(); HashSet inserted; size_t old_size = size(); diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 26976d6afe6..80749e42183 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -25,8 +25,6 @@ public: Subcolumn() = default; Subcolumn(size_t size_, bool is_nullable); Subcolumn(MutableColumnPtr && data_); - Subcolumn(const Subcolumn & other); - Subcolumn & operator=(Subcolumn && other) = default; size_t size() const; size_t byteSize() const; diff --git a/src/Columns/ya.make b/src/Columns/ya.make index 9cf23e27809..d287bd6d4f9 100644 --- a/src/Columns/ya.make +++ b/src/Columns/ya.make @@ -29,6 +29,7 @@ SRCS( ColumnLowCardinality.cpp ColumnMap.cpp ColumnNullable.cpp + ColumnObject.cpp ColumnSparse.cpp ColumnString.cpp ColumnTuple.cpp diff --git a/src/DataTypes/FieldToDataType.h b/src/DataTypes/FieldToDataType.h index 84024f75fdb..9dc06e4b386 100644 --- a/src/DataTypes/FieldToDataType.h +++ b/src/DataTypes/FieldToDataType.h @@ -49,32 +49,4 @@ private: bool allow_convertion_to_string; }; - -// template struct FieldTypeToTypeIndex; - -// #define DEFINE_FOR_TYPE(T) \ -// template <> struct FieldTypeToTypeIndex { static constexpr auto value = TypeIndex::T; }; - -// DEFINE_FOR_TYPE(UInt64) -// DEFINE_FOR_TYPE(UInt128) -// DEFINE_FOR_TYPE(UInt256) -// DEFINE_FOR_TYPE(Int64) -// DEFINE_FOR_TYPE(Int128) -// DEFINE_FOR_TYPE(Int256) -// DEFINE_FOR_TYPE(UUID) -// DEFINE_FOR_TYPE(Float64) -// DEFINE_FOR_TYPE(String) -// DEFINE_FOR_TYPE(Array) -// DEFINE_FOR_TYPE(Tuple) -// DEFINE_FOR_TYPE(Map) -// DEFINE_FOR_TYPE(Decimal32) -// DEFINE_FOR_TYPE(Decimal64) -// DEFINE_FOR_TYPE(Decimal128) -// DEFINE_FOR_TYPE(Decimal256) - -// template <> struct FieldTypeToTypeIndex { static constexpr auto value = TypeIndex::Nothing; }; -// template <> struct FieldTypeToTypeIndex { static constexpr auto value = TypeIndex::AggregateFunction; }; - -// #undef DEFINE_FOR_TYPE - } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 48f0a7415c3..92b6273ec73 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -33,11 +33,6 @@ namespace ErrorCodes extern const int DUPLICATE_COLUMN; } -static const IDataType * getTypeObject(const DataTypePtr & type) -{ - return typeid_cast(type.get()); -} - size_t getNumberOfDimensions(const IDataType & type) { if (const auto * type_array = typeid_cast(&type)) @@ -107,11 +102,11 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con for (auto & name_type : columns_list) { - if (const auto * type_object = getTypeObject(name_type.type)) + if (isObject(name_type.type)) { auto & column = block.getByName(name_type.name); - if (!getTypeObject(column.type)) + if (!isObject(column.type)) throw Exception(ErrorCodes::TYPE_MISMATCH, "Type for column '{}' mismatch in columns list and in block. In list: {}, in block: {}", name_type.name, name_type.type->getName(), column.type->getName()); @@ -131,7 +126,7 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con subcolumn.getFinalizedColumnPtr()); std::sort(tuple_elements.begin(), tuple_elements.end(), - [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); } ); + [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); }); auto tuple_names = extractVector<0>(tuple_elements); auto tuple_types = extractVector<1>(tuple_elements); @@ -222,7 +217,7 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types, bool check_ambi tuple_elements.emplace_back(ColumnObject::COLUMN_NAME_DUMMY, std::make_shared()); std::sort(tuple_elements.begin(), tuple_elements.end(), - [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); } ); + [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); }); auto tuple_names = extractVector<0>(tuple_elements); auto tuple_types = extractVector<1>(tuple_elements); diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 1326a7b56e8..4d3a526b276 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -21,7 +21,7 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; extern const int INCORRECT_DATA; extern const int CANNOT_READ_ALL_DATA; - extern const int TYPE_MISMATCH; + extern const int LOGICAL_ERROR; } template @@ -97,8 +97,8 @@ void SerializationObject::deserializeTextCSV(IColumn & column, ReadBuffe } template -template -void SerializationObject::checkSerializationIsSupported(Settings & settings, StatePtr & state) const +template +void SerializationObject::checkSerializationIsSupported(TSettings & settings, TStatePtr & state) const { if (settings.position_independent_encoding) throw Exception(ErrorCodes::NOT_IMPLEMENTED, diff --git a/src/DataTypes/Serializations/SerializationObject.h b/src/DataTypes/Serializations/SerializationObject.h index da490e6f461..532de22a47a 100644 --- a/src/DataTypes/Serializations/SerializationObject.h +++ b/src/DataTypes/Serializations/SerializationObject.h @@ -53,8 +53,8 @@ public: void deserializeTextCSV(IColumn & column, ReadBuffer & istr, const FormatSettings & settings) const override; private: - template - void checkSerializationIsSupported(Settings & settings, StatePtr & state) const; + template + void checkSerializationIsSupported(TSettings & settings, TStatePtr & state) const; template void deserializeTextImpl(IColumn & column, Reader && reader) const; diff --git a/src/DataTypes/ya.make b/src/DataTypes/ya.make index a912c215b18..35f920bbc91 100644 --- a/src/DataTypes/ya.make +++ b/src/DataTypes/ya.make @@ -32,6 +32,7 @@ SRCS( DataTypeNothing.cpp DataTypeNullable.cpp DataTypeNumberBase.cpp + DataTypeObject.cpp DataTypeString.cpp DataTypeTuple.cpp DataTypeUUID.cpp @@ -41,6 +42,7 @@ SRCS( FieldToDataType.cpp IDataType.cpp NestedUtils.cpp + ObjectUtils.cpp Serializations/ISerialization.cpp Serializations/SerializationAggregateFunction.cpp Serializations/SerializationArray.cpp @@ -60,6 +62,7 @@ SRCS( Serializations/SerializationNothing.cpp Serializations/SerializationNullable.cpp Serializations/SerializationNumber.cpp + Serializations/SerializationObject.cpp Serializations/SerializationSparse.cpp Serializations/SerializationString.cpp Serializations/SerializationTuple.cpp diff --git a/src/Functions/IFunction.h b/src/Functions/IFunction.h index ba3b5dfaf12..9bfdae7df93 100644 --- a/src/Functions/IFunction.h +++ b/src/Functions/IFunction.h @@ -81,7 +81,7 @@ protected: /** If function arguments has single sparse column and all other arguments are constants, call function on nested column. * Otherwise, convert all sparse columns to ordinary columns. * If default value doesn't change after function execution, returns sparse column as a result. - * Otherwide, result column is converted to full. + * Otherwise, result column is converted to full. */ virtual bool useDefaultImplementationForSparseColumns() const { return true; } @@ -367,7 +367,7 @@ protected: /** If function arguments has single sparse column and all other arguments are constants, call function on nested column. * Otherwise, convert all sparse columns to ordinary columns. * If default value doesn't change after function execution, returns sparse column as a result. - * Otherwide, result column is converted to full. + * Otherwise, result column is converted to full. */ virtual bool useDefaultImplementationForSparseColumns() const { return true; } @@ -425,7 +425,7 @@ public: /** If function arguments has single sparse column and all other arguments are constants, call function on nested column. * Otherwise, convert all sparse columns to ordinary columns. * If default value doesn't change after function execution, returns sparse column as a result. - * Otherwide, result column is converted to full. + * Otherwise, result column is converted to full. */ virtual bool useDefaultImplementationForSparseColumns() const { return true; } diff --git a/src/Interpreters/InterpreterDescribeQuery.cpp b/src/Interpreters/InterpreterDescribeQuery.cpp index 5c976c1cd23..385508cee51 100644 --- a/src/Interpreters/InterpreterDescribeQuery.cpp +++ b/src/Interpreters/InterpreterDescribeQuery.cpp @@ -67,26 +67,25 @@ BlockInputStreamPtr InterpreterDescribeQuery::executeImpl() const auto & ast = query_ptr->as(); const auto & table_expression = ast.table_expression->as(); - auto context = getContext(); - const auto & settings = context->getSettingsRef(); + const auto & settings = getContext()->getSettingsRef(); if (table_expression.subquery) { auto names_and_types = InterpreterSelectWithUnionQuery::getSampleBlock( - table_expression.subquery->children.at(0), context).getNamesAndTypesList(); + table_expression.subquery->children.at(0), getContext()).getNamesAndTypesList(); columns = ColumnsDescription(std::move(names_and_types)); } else if (table_expression.table_function) { - TableFunctionPtr table_function_ptr = TableFunctionFactory::instance().get(table_expression.table_function, context); - columns = table_function_ptr->getActualTableStructure(context); + TableFunctionPtr table_function_ptr = TableFunctionFactory::instance().get(table_expression.table_function, getContext()); + columns = table_function_ptr->getActualTableStructure(getContext()); } else { - auto table_id = context->resolveStorageID(table_expression.database_and_table_name); - context->checkAccess(AccessType::SHOW_COLUMNS, table_id); - auto table = DatabaseCatalog::instance().getTable(table_id, context); - auto table_lock = table->lockForShare(context->getInitialQueryId(), settings.lock_acquire_timeout); + auto table_id = getContext()->resolveStorageID(table_expression.database_and_table_name); + getContext()->checkAccess(AccessType::SHOW_COLUMNS, table_id); + auto table = DatabaseCatalog::instance().getTable(table_id, getContext()); + auto table_lock = table->lockForShare(getContext()->getInitialQueryId(), settings.lock_acquire_timeout); auto metadata_snapshot = table->getInMemoryMetadataPtr(); storage_snapshot = table->getStorageSnapshot(metadata_snapshot); diff --git a/src/Interpreters/convertFieldToType.cpp b/src/Interpreters/convertFieldToType.cpp index 575efaa8d92..7256a6b4b0f 100644 --- a/src/Interpreters/convertFieldToType.cpp +++ b/src/Interpreters/convertFieldToType.cpp @@ -359,7 +359,7 @@ Field convertFieldToTypeImpl(const Field & src, const IDataType & type, const ID return src; } - else if (const auto * object_type = typeid_cast(&type)) + else if (isObject(type)) { const auto * from_type_tuple = typeid_cast(from_type_hint); if (src.getType() == Field::Types::Tuple && from_type_tuple && from_type_tuple->haveExplicitNames()) diff --git a/src/Storages/IStorage.cpp b/src/Storages/IStorage.cpp index dbc960838e1..94f3b648f18 100644 --- a/src/Storages/IStorage.cpp +++ b/src/Storages/IStorage.cpp @@ -22,10 +22,6 @@ namespace ErrorCodes extern const int TABLE_IS_DROPPED; extern const int NOT_IMPLEMENTED; extern const int DEADLOCK_AVOIDED; - extern const int NOT_FOUND_COLUMN_IN_BLOCK; - extern const int EMPTY_LIST_OF_COLUMNS_QUERIED; - extern const int NO_SUCH_COLUMN_IN_TABLE; - extern const int COLUMN_QUERIED_MORE_THAN_ONCE; } bool IStorage::isVirtualColumn(const String & column_name, const StorageMetadataPtr & metadata_snapshot) const diff --git a/src/Storages/StorageSnapshot.cpp b/src/Storages/StorageSnapshot.cpp index d452f3d9137..d3922b1c769 100644 --- a/src/Storages/StorageSnapshot.cpp +++ b/src/Storages/StorageSnapshot.cpp @@ -4,7 +4,6 @@ #include #include #include -#include namespace DB { diff --git a/src/Storages/ya.make b/src/Storages/ya.make index c85a0dbd902..71b9fc724a4 100644 --- a/src/Storages/ya.make +++ b/src/Storages/ya.make @@ -149,6 +149,7 @@ SRCS( StorageReplicatedMergeTree.cpp StorageSQLite.cpp StorageSet.cpp + StorageSnapshot.cpp StorageStripeLog.cpp StorageTinyLog.cpp StorageURL.cpp diff --git a/tests/queries/0_stateless/01825_type_json_schema_race_long.sh b/tests/queries/0_stateless/01825_type_json_schema_race_long.sh index 70da8b52d61..3bd3e0388dc 100755 --- a/tests/queries/0_stateless/01825_type_json_schema_race_long.sh +++ b/tests/queries/0_stateless/01825_type_json_schema_race_long.sh @@ -28,7 +28,7 @@ function test_case() done } -for i in {1..30}; do test_case; done +for _ in {1..30}; do test_case; done $CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_json_race" echo OK From 4114b8293709fd69565ff691391b0bbfa25acfb3 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 23 Sep 2021 23:15:49 +0300 Subject: [PATCH 0067/1647] hotfixes for merge task --- src/Storages/MergeTree/MergePlainMergeTreeTask.cpp | 2 +- src/Storages/MergeTree/MergePlainMergeTreeTask.h | 9 +++++++++ src/Storages/StorageMergeTree.cpp | 9 ++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Storages/MergeTree/MergePlainMergeTreeTask.cpp b/src/Storages/MergeTree/MergePlainMergeTreeTask.cpp index 0830463a2fe..887725d1aba 100644 --- a/src/Storages/MergeTree/MergePlainMergeTreeTask.cpp +++ b/src/Storages/MergeTree/MergePlainMergeTreeTask.cpp @@ -108,7 +108,7 @@ void MergePlainMergeTreeTask::prepare() void MergePlainMergeTreeTask::finish() { new_part = merge_task->getFuture().get(); - storage.merger_mutator.renameMergedTemporaryPart(new_part, future_part->parts, nullptr, nullptr); //FIXME + storage.merger_mutator.renameMergedTemporaryPart(new_part, future_part->parts, txn, nullptr); //FIXME write_part_log({}); } diff --git a/src/Storages/MergeTree/MergePlainMergeTreeTask.h b/src/Storages/MergeTree/MergePlainMergeTreeTask.h index f199557684c..0e6b4a0405a 100644 --- a/src/Storages/MergeTree/MergePlainMergeTreeTask.h +++ b/src/Storages/MergeTree/MergePlainMergeTreeTask.h @@ -36,6 +36,12 @@ public: StorageID getStorageID() override; + void setCurrentTransaction(MergeTreeTransactionHolder && txn_holder_, MergeTreeTransactionPtr && txn_) + { + txn_holder = std::move(txn_holder_); + txn = std::move(txn_); + } + private: void prepare(); @@ -73,6 +79,9 @@ private: IExecutableTask::TaskResultCallback task_result_callback; MergeTaskPtr merge_task{nullptr}; + + MergeTreeTransactionHolder txn_holder; + MergeTreeTransactionPtr txn; }; diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index c604e4ecb94..abba8d37695 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -857,6 +857,8 @@ bool StorageMergeTree::merge( auto task = std::make_shared( *this, metadata_snapshot, deduplicate, deduplicate_by_columns, merge_mutate_entry, table_lock_holder, [](bool){}); + task->setCurrentTransaction(MergeTreeTransactionHolder{}, MergeTreeTransactionPtr{txn}); + executeHere(task); return true; @@ -1033,13 +1035,17 @@ bool StorageMergeTree::scheduleDataProcessingJob(BackgroundJobsAssignee & assign auto share_lock = lockForShare(RWLockImpl::NO_QUERY, getSettings()->lock_acquire_timeout_for_background_operations); + /// FIXME Transactions: do not begin transaction if we don't need it + auto txn = TransactionLog::instance().beginTransaction(); + MergeTreeTransactionHolder autocommit{txn, true}; + bool has_mutations = false; { std::unique_lock lock(currently_processing_in_background_mutex); if (merger_mutator.merges_blocker.isCancelled()) return false; - merge_entry = selectPartsToMerge(metadata_snapshot, false, {}, false, nullptr, share_lock, lock, nullptr); + merge_entry = selectPartsToMerge(metadata_snapshot, false, {}, false, nullptr, share_lock, lock, txn); if (!merge_entry) { mutate_entry = selectPartsToMutate(metadata_snapshot, nullptr, share_lock); @@ -1050,6 +1056,7 @@ bool StorageMergeTree::scheduleDataProcessingJob(BackgroundJobsAssignee & assign if (merge_entry) { auto task = std::make_shared(*this, metadata_snapshot, false, Names{}, merge_entry, share_lock, common_assignee_trigger); + task->setCurrentTransaction(std::move(autocommit), std::move(txn)); assignee.scheduleMergeMutateTask(task); return true; } From 2db11bc6c56e156823dd7cf34e6519306696086b Mon Sep 17 00:00:00 2001 From: Pavel Kruglov Date: Fri, 20 Aug 2021 15:17:51 +0300 Subject: [PATCH 0068/1647] Add backward compatibility check in stress test --- docker/test/stress/Dockerfile | 1 + docker/test/stress/download_previous_release | 103 ++++++++++++++++ docker/test/stress/run.sh | 112 +++++++++++++++++- docker/test/stress/stress | 8 +- programs/client/Client.cpp | 3 + src/Client/ClientBase.cpp | 6 + src/Client/ClientBase.h | 2 + tests/clickhouse-test | 8 ++ ...788_update_nested_type_subcolumn_check.sql | 4 + .../0_stateless/01889_sqlite_read_write.sh | 1 - .../01943_non_deterministic_order_key.sql | 2 + 11 files changed, 241 insertions(+), 9 deletions(-) create mode 100755 docker/test/stress/download_previous_release diff --git a/docker/test/stress/Dockerfile b/docker/test/stress/Dockerfile index 3fe1b790d5a..f3497ba9f4a 100644 --- a/docker/test/stress/Dockerfile +++ b/docker/test/stress/Dockerfile @@ -23,6 +23,7 @@ RUN apt-get update -y \ brotli COPY ./stress /stress +COPY ./download_previous_release /download_previous_release COPY run.sh / ENV DATASETS="hits visits" diff --git a/docker/test/stress/download_previous_release b/docker/test/stress/download_previous_release new file mode 100755 index 00000000000..ad3b5ed2123 --- /dev/null +++ b/docker/test/stress/download_previous_release @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 + +import requests +import re +import os + +CLICKHOUSE_TAGS_URL = "https://api.github.com/repos/ClickHouse/ClickHouse/tags" + +CLICKHOUSE_COMMON_STATIC_DOWNLOAD_URL = "https://github.com/ClickHouse/ClickHouse/releases/download/v{version}-{type}/clickhouse-common-static_{version}_amd64.deb" +CLICKHOUSE_COMMON_STATIC_DBG_DOWNLOAD_URL = "https://github.com/ClickHouse/ClickHouse/releases/download/v{version}-{type}/clickhouse-common-static-dbg_{version}_amd64.deb" +CLICKHOUSE_SERVER_DOWNLOAD_URL = "https://github.com/ClickHouse/ClickHouse/releases/download/v{version}-{type}/clickhouse-server_{version}_all.deb" +CLICKHOUSE_CLIENT_DOWNLOAD_URL = "https://github.com/ClickHouse/ClickHouse/releases/download/v{version}-{type}/clickhouse-client_{version}_amd64.deb" +CLICKHOUSE_TEST_DOWNLOAD_URL = "https://github.com/ClickHouse/ClickHouse/releases/download/v{version}-{type}/clickhouse-test_{version}_amd64.deb" + + +CLICKHOUSE_COMMON_STATIC_PACKET_NAME = "clickhouse-common-static_{version}_amd64.deb" +CLICKHOUSE_COMMON_STATIC_DBG_PACKET_NAME = "clickhouse-common-static-dbg_{version}_amd64.deb" +CLICKHOUSE_SERVER_PACKET_NAME = "clickhouse-server_{version}_all.deb" +CLICKHOUSE_CLIENT_PACKET_NAME = "clickhouse-client_{version}_all.deb" +CLICKHOUSE_TEST_PACKET_NAME = "clickhouse-test_{version}_all.deb" + +PACKETS_DIR = "previous_release_package_folder/" +VERSION_PATTERN = r"((?:\d+\.)?(?:\d+\.)?(?:\d+\.)?\d+-lts*)" + + +class Version: + def __init__(self, version): + self.version = version + + def __lt__(self, other): + return list(map(int, self.version.split('.'))) < list(map(int, other.version.split('.'))) + + def __str__(self): + return self.version + + +class ReleaseInfo: + def __init__(self, version, release_type): + self.version = version + self.type = release_type + + +def find_previous_release(server_version, releases): + releases.sort(key=lambda x: x.version, reverse=True) + for release in releases: + if release.version < server_version: + return True, release + + return False, None + + +def get_previous_release(server_version): + page = 1 + found = False + while not found: + response = requests.get(CLICKHOUSE_TAGS_URL, {'page': page, 'per_page': 100}) + if not response.ok: + raise Exception('Cannot load the list of tags from github: ' + response.reason) + + releases_str = set(re.findall(VERSION_PATTERN, response.text)) + if len(releases_str) == 0: + raise Exception('Cannot find previous release for ' + str(server_version) + ' server version') + + releases = list(map(lambda x: ReleaseInfo(Version(x.split('-')[0]), x.split('-')[1]), releases_str)) + found, previous_release = find_previous_release(server_version, releases) + page += 1 + + return previous_release + + +def download_packet(url, local_file_name): + response = requests.get(url) + print(url) + if response.ok: + open(PACKETS_DIR + local_file_name, 'wb').write(response.content) + + +def download_packets(release): + if not os.path.exists(PACKETS_DIR): + os.makedirs(PACKETS_DIR) + + download_packet(CLICKHOUSE_COMMON_STATIC_DOWNLOAD_URL.format(version=release.version, type=release.type), + CLICKHOUSE_COMMON_STATIC_PACKET_NAME.format(version=release.version)) + + download_packet(CLICKHOUSE_COMMON_STATIC_DBG_DOWNLOAD_URL.format(version=release.version, type=release.type), + CLICKHOUSE_COMMON_STATIC_DBG_PACKET_NAME.format(version=release.version)) + + download_packet(CLICKHOUSE_SERVER_DOWNLOAD_URL.format(version=release.version, type=release.type), + CLICKHOUSE_SERVER_PACKET_NAME.format(version=release.version)) + + download_packet(CLICKHOUSE_CLIENT_DOWNLOAD_URL.format(version=release.version, type=release.type), + CLICKHOUSE_CLIENT_PACKET_NAME.format(version=release.version)) + + download_packet(CLICKHOUSE_TEST_DOWNLOAD_URL.format(version=release.version, type=release.type), + CLICKHOUSE_TEST_PACKET_NAME.format(version=release.version)) + + + +if __name__ == '__main__': + server_version = Version(input()) + previous_release = get_previous_release(server_version) + download_packets(previous_release) + diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index afdc026732f..60831a78c2d 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -21,16 +21,20 @@ export THREAD_FUZZER_pthread_mutex_lock_AFTER_SLEEP_PROBABILITY=0.001 export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_SLEEP_PROBABILITY=0.001 export THREAD_FUZZER_pthread_mutex_unlock_AFTER_SLEEP_PROBABILITY=0.001 export THREAD_FUZZER_pthread_mutex_lock_BEFORE_SLEEP_TIME_US=10000 +LONG export THREAD_FUZZER_pthread_mutex_lock_AFTER_SLEEP_TIME_US=10000 export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_SLEEP_TIME_US=10000 export THREAD_FUZZER_pthread_mutex_unlock_AFTER_SLEEP_TIME_US=10000 -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 -dpkg -i package_folder/clickhouse-test_*.deb +function install_packages() +{ + dpkg -i $1/clickhouse-common-static_*.deb + dpkg -i $1/clickhouse-common-static-dbg_*.deb + dpkg -i $1/clickhouse-server_*.deb + dpkg -i $1/clickhouse-client_*.deb + dpkg -i $1/clickhouse-test_*.deb +} function configure() { @@ -107,6 +111,8 @@ quit gdb -batch -command script.gdb -p "$(cat /var/run/clickhouse-server/clickhouse-server.pid)" >> /test_output/gdb.log & } +install_packages package_folder + configure start @@ -175,6 +181,102 @@ zgrep -Fa " " /var/log/clickhouse-server/clickhouse-server.log* > /dev/n zgrep -Fa "########################################" /test_output/* > /dev/null \ && echo -e 'Killed by signal (output files)\tFAIL' >> /test_output/test_results.tsv +echo -e "Backward compatibility check\n" + +echo "Download previous release server" +clickhouse-client --query="SELECT version()" | ./download_previous_release && echo -e 'Download script exit code\tOK' >> /test_output/backward_compatibility_check_results.tsv \ + || echo -e 'Download script failed\tFAIL' >> /test_output/backward_compatibility_check_results.tsv + +if [ "$(ls -A previous_release_package_folder/clickhouse-common-static_*.deb && ls -A previous_release_package_folder/clickhouse-server_*.deb)" ] +then + echo -e "Successfully downloaded previous release packets\tOK" >> /test_output/backward_compatibility_check_results.tsv + stop + + # Uninstall current packages + dpkg --remove clickhouse-test + dpkg --remove clickhouse-client + dpkg --remove clickhouse-server + dpkg --remove clickhouse-common-static-dbg + dpkg --remove clickhouse-common-static + + # Install previous release packages + install_packages previous_release_package_folder + + # Start server from previous release + configure + start + + clickhouse-client --query="SELECT 'Server version: ', version()" + + # Install new package before running stress test because we should use new clickhouse-client and new clickhouse-test + install_packages package_folder + + mkdir tmp_stress_output + + ./stress --backward-compatibility-check --output-folder tmp_stress_output --global-time-limit=1800 \ + && echo -e 'Test script exit code\tOK' >> /test_output/backward_compatibility_check_results.tsv \ + || echo -e 'Test script failed\tFAIL' >> /test_output/backward_compatibility_check_results.tsv + rm -rf tmp_stress_output + + clickhouse-client --query="SELECT 'Tables count:', count() FROM system.tables" + stop + + # Start new server + configure + start + clickhouse-client --query "SELECT 'Server successfully started', 'OK'" >> /test_output/backward_compatibility_check_results.tsv \ + || echo -e 'Server failed to start\tFAIL' >> /test_output/backward_compatibility_check_results.tsv + + clickhouse-client --query="SELECT 'Server version: ', version()" + + # Let the server run for a while before checking log. + sleep 60 + + stop + + # Error messages (we ignore Cancelled merging parts, REPLICA_IS_ALREADY_ACTIVE and errors) + zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "RaftInstance: failed to accept a rpc connection due to error 125" \ + /var/log/clickhouse-server/clickhouse-server.log | zgrep -Fa "" > /dev/null \ + && echo -e 'Error message in clickhouse-server.log\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ + || echo -e 'No Error messages in clickhouse-server.log\tOK' >> /test_output/backward_compatibility_check_results.tsv + + # Sanitizer asserts + zgrep -Fa "==================" /var/log/clickhouse-server/stderr.log >> /test_output/tmp + zgrep -Fa "WARNING" /var/log/clickhouse-server/stderr.log >> /test_output/tmp + zgrep -Fav "ASan doesn't fully support makecontext/swapcontext functions" /test_output/tmp > /dev/null \ + && echo -e 'Sanitizer assert (in stderr.log)\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ + || echo -e 'No sanitizer asserts\tOK' >> /test_output/backward_compatibility_check_results.tsv + rm -f /test_output/tmp + + # OOM + zgrep -Fa " Application: Child process was terminated by signal 9" /var/log/clickhouse-server/clickhouse-server.log > /dev/null \ + && echo -e 'OOM killer (or signal 9) in clickhouse-server.log\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ + || echo -e 'No OOM messages in clickhouse-server.log\tOK' >> /test_output/backward_compatibility_check_results.tsv + + # Logical errors + zgrep -Fa "Code: 49, e.displayText() = DB::Exception:" /var/log/clickhouse-server/clickhouse-server.log > /dev/null \ + && echo -e 'Logical error thrown (see clickhouse-server.log)\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ + || echo -e 'No logical errors\tOK' >> /test_output/backward_compatibility_check_results.tsv + + # Crash + zgrep -Fa "########################################" /var/log/clickhouse-server/clickhouse-server.log > /dev/null \ + && echo -e 'Killed by signal (in clickhouse-server.log)\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ + || echo -e 'Not crashed\tOK' >> /test_output/backward_compatibility_check_results.tsv + + # It also checks for crash without stacktrace (printed by watchdog) + zgrep -Fa " " /var/log/clickhouse-server/clickhouse-server.log > /dev/null \ + && echo -e 'Fatal message in clickhouse-server.log\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ + || echo -e 'No fatal messages in clickhouse-server.log\tOK' >> /test_output/backward_compatibility_check_results.tsv + +else + echo -e "Failed to download previous release packets\tFAIL" >> /test_output/backward_compatibility_check_results.tsv +fi + +zgrep -Fa "FAIL" /test_output/backward_compatibility_check_results.tsv > /dev/null \ + && echo -e 'Backward compatibility check\tFAIL' >> /test_output/test_results.tsv \ + || echo -e 'Backward compatibility check\tOK' >> /test_output/test_results.tsv + + # Put logs into /test_output/ for log_file in /var/log/clickhouse-server/clickhouse-server.log* do diff --git a/docker/test/stress/stress b/docker/test/stress/stress index 8fc4ade2da6..cfe8ae9ec6d 100755 --- a/docker/test/stress/stress +++ b/docker/test/stress/stress @@ -47,7 +47,8 @@ def get_options(i): return ' '.join(options) -def run_func_test(cmd, output_prefix, num_processes, skip_tests_option, global_time_limit): +def run_func_test(cmd, output_prefix, num_processes, skip_tests_option, global_time_limit, backward_compatibility_check): + backward_compatibility_check_option = '--backward-compatibility-check' if backward_compatibility_check else '' global_time_limit_option = '' if global_time_limit: global_time_limit_option = "--global_time_limit={}".format(global_time_limit) @@ -56,7 +57,7 @@ def run_func_test(cmd, output_prefix, num_processes, skip_tests_option, global_t pipes = [] for i in range(0, len(output_paths)): f = open(output_paths[i], 'w') - full_command = "{} {} {} {}".format(cmd, get_options(i), global_time_limit_option, skip_tests_option) + full_command = "{} {} {} {} {}".format(cmd, get_options(i), global_time_limit_option, skip_tests_option, backward_compatibility_check_option) logging.info("Run func tests '%s'", full_command) p = Popen(full_command, shell=True, stdout=f, stderr=f) pipes.append(p) @@ -140,6 +141,7 @@ if __name__ == "__main__": parser.add_argument("--output-folder") parser.add_argument("--global-time-limit", type=int, default=3600) parser.add_argument("--num-parallel", type=int, default=cpu_count()) + parser.add_argument('--backward-compatibility-check', action='store_true', default=False) parser.add_argument('--hung-check', action='store_true', default=False) # make sense only for hung check parser.add_argument('--drop-databases', action='store_true', default=False) @@ -148,7 +150,7 @@ if __name__ == "__main__": if args.drop_databases and not args.hung_check: raise Exception("--drop-databases only used in hung check (--hung-check)") func_pipes = [] - func_pipes = run_func_test(args.test_cmd, args.output_folder, args.num_parallel, args.skip_func_tests, args.global_time_limit) + func_pipes = run_func_test(args.test_cmd, args.output_folder, args.num_parallel, args.skip_func_tests, args.global_time_limit, args.backward_compatibility_check) logging.info("Will wait functests to finish") while True: diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index 34081168429..88eb9f3d157 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -1028,6 +1028,7 @@ void Client::addAndCheckOptions(OptionsDescription & options_description, po::va ("no-warnings", "disable warnings when client connects to server") ("max_memory_usage_in_client", po::value(), "sets memory limit in client") + ("fake-drop", "Ignore all DROP queries") ; /// Commandline options related to external tables. @@ -1148,6 +1149,8 @@ void Client::processOptions(const OptionsDescription & options_description, server_logs_file = options["server_logs_file"].as(); if (options.count("no-warnings")) config().setBool("no-warnings", true); + if (options.count("fake-drop")) + fake_drop = true; if ((query_fuzzer_runs = options["query-fuzzer-runs"].as())) { diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 7e402028aa5..56dcc38b37b 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -439,6 +439,12 @@ void ClientBase::processTextAsSingleQuery(const String & full_query) void ClientBase::processOrdinaryQuery(const String & query_to_execute, ASTPtr parsed_query) { + if (fake_drop) + { + if (parsed_query->as()) + return; + } + /// 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. diff --git a/src/Client/ClientBase.h b/src/Client/ClientBase.h index bf9e8fdfe47..9d6bf408b79 100644 --- a/src/Client/ClientBase.h +++ b/src/Client/ClientBase.h @@ -212,6 +212,8 @@ protected: int query_fuzzer_runs = 0; QueryProcessingStage::Enum query_processing_stage; + + bool fake_drop = false; }; } diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 19080f3934f..ebba7704af4 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -265,6 +265,7 @@ class FailureReason(enum.Enum): NO_LONG = "not running long tests" REPLICATED_DB = "replicated-database" BUILD = "not running for current build" + BACKWARD_INCOMPATIBLE = "test is backward incompatible" # UNKNOWN reasons NO_REFERENCE = "no reference file" @@ -402,6 +403,9 @@ class TestCase: elif tags and ('no-replicated-database' in tags) and args.replicated_database: return FailureReason.REPLICATED_DB + elif tags and ('backward-incompatible' in tags) and args.backward_compatibility_check: + return FailureReason.BACKWARD_INCOMPATIBLE + elif tags: for build_flag in args.build_flags: if 'no-' + build_flag in tags: @@ -1384,6 +1388,7 @@ if __name__ == '__main__': group.add_argument('--shard', action='store_true', default=None, dest='shard', help='Run sharding related tests (required to clickhouse-server listen 127.0.0.2 127.0.0.3)') group.add_argument('--no-shard', action='store_false', default=None, dest='shard', help='Do not run shard related tests') + group.add_argument('--backward-compatibility-check', action='store_true', default=False, help='Ignore all drop queries in tests') args = parser.parse_args() if args.queries and not os.path.isdir(args.queries): @@ -1442,6 +1447,9 @@ if __name__ == '__main__': if os.getenv("CLICKHOUSE_DATABASE"): args.client += ' --database=' + os.getenv("CLICKHOUSE_DATABASE") + if args.backward_compatibility_check: + args.client += ' --fake-drop' + if args.client_option: # Set options for client if 'CLICKHOUSE_CLIENT_OPT' in os.environ: diff --git a/tests/queries/0_stateless/01788_update_nested_type_subcolumn_check.sql b/tests/queries/0_stateless/01788_update_nested_type_subcolumn_check.sql index accb785ba03..efd8ea2a565 100644 --- a/tests/queries/0_stateless/01788_update_nested_type_subcolumn_check.sql +++ b/tests/queries/0_stateless/01788_update_nested_type_subcolumn_check.sql @@ -31,6 +31,8 @@ select * from test_wide_nested; alter table test_wide_nested update `info.id` = [100,200], `info.age` = [10,20,30], `info.name` = ['a','b','c'] where id = 0; -- { serverError 341 } +kill mutation where table = 'test_wide_nested' and database = currentDatabase() format Null; + -- Recreate table, because KILL MUTATION is not suitable for parallel tests execution. SELECT '********* test 2 **********'; DROP TABLE test_wide_nested; @@ -54,6 +56,8 @@ select * from test_wide_nested; alter table test_wide_nested update `info.id` = [100,200,300], `info.age` = [10,20,30] where id = 1; -- { serverError 341 } +kill mutation where table = 'test_wide_nested' and database = currentDatabase() format Null; + DROP TABLE test_wide_nested; SELECT '********* test 3 **********'; diff --git a/tests/queries/0_stateless/01889_sqlite_read_write.sh b/tests/queries/0_stateless/01889_sqlite_read_write.sh index 247f44b61e7..fc87aa08fa7 100755 --- a/tests/queries/0_stateless/01889_sqlite_read_write.sh +++ b/tests/queries/0_stateless/01889_sqlite_read_write.sh @@ -19,7 +19,6 @@ DB_PATH2=$CUR_DIR/${CURR_DATABASE}_db2 function cleanup() { ${CLICKHOUSE_CLIENT} --query="DROP DATABASE IF EXISTS ${CURR_DATABASE}" - rm -r "${DB_PATH}" "${DB_PATH2}" } trap cleanup EXIT diff --git a/tests/queries/0_stateless/01943_non_deterministic_order_key.sql b/tests/queries/0_stateless/01943_non_deterministic_order_key.sql index 200a88ec677..0929dcda601 100644 --- a/tests/queries/0_stateless/01943_non_deterministic_order_key.sql +++ b/tests/queries/0_stateless/01943_non_deterministic_order_key.sql @@ -1,3 +1,5 @@ +-- Tags: backward-incompatible + CREATE TABLE a (number UInt64) ENGINE = MergeTree ORDER BY if(now() > toDateTime('2020-06-01 13:31:40'), toInt64(number), -number); -- { serverError 36 } CREATE TABLE b (number UInt64) ENGINE = MergeTree ORDER BY now() > toDateTime(number); -- { serverError 36 } CREATE TABLE c (number UInt64) ENGINE = MergeTree ORDER BY now(); -- { serverError 36 } From 6fc043026eae1747ed43ebce8ad9fa2f44a79dc0 Mon Sep 17 00:00:00 2001 From: Pavel Kruglov Date: Tue, 28 Sep 2021 14:09:14 +0300 Subject: [PATCH 0069/1647] Update run.sh --- docker/test/stress/run.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 60831a78c2d..a7c08dd04ee 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -199,6 +199,8 @@ then dpkg --remove clickhouse-common-static-dbg dpkg --remove clickhouse-common-static + rm -rf /var/lib/clickhouse/* + # Install previous release packages install_packages previous_release_package_folder @@ -234,8 +236,8 @@ then stop - # Error messages (we ignore Cancelled merging parts, REPLICA_IS_ALREADY_ACTIVE and errors) - zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "RaftInstance: failed to accept a rpc connection due to error 125" \ + # Error messages (we should ignore some errors) + zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "DDLWorker: Cannot parse DDL task query" -e "RaftInstance: failed to accept a rpc connection due to error 125" \ /var/log/clickhouse-server/clickhouse-server.log | zgrep -Fa "" > /dev/null \ && echo -e 'Error message in clickhouse-server.log\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ || echo -e 'No Error messages in clickhouse-server.log\tOK' >> /test_output/backward_compatibility_check_results.tsv From 821daa21da9b5101e4ea99c2701aee95e35ca009 Mon Sep 17 00:00:00 2001 From: avogar Date: Tue, 28 Sep 2021 14:17:50 +0300 Subject: [PATCH 0070/1647] Update run.sh --- docker/test/stress/run.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index a7c08dd04ee..67febb00ae5 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -221,6 +221,7 @@ then rm -rf tmp_stress_output clickhouse-client --query="SELECT 'Tables count:', count() FROM system.tables" + stop # Start new server From 31302afe81cf9b51f3b30fa7351aacb9c0adf71d Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 29 Sep 2021 14:07:02 +0300 Subject: [PATCH 0071/1647] Update run.sh --- docker/test/stress/run.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 67febb00ae5..c09080050b7 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -21,7 +21,7 @@ export THREAD_FUZZER_pthread_mutex_lock_AFTER_SLEEP_PROBABILITY=0.001 export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_SLEEP_PROBABILITY=0.001 export THREAD_FUZZER_pthread_mutex_unlock_AFTER_SLEEP_PROBABILITY=0.001 export THREAD_FUZZER_pthread_mutex_lock_BEFORE_SLEEP_TIME_US=10000 -LONG + export THREAD_FUZZER_pthread_mutex_lock_AFTER_SLEEP_TIME_US=10000 export THREAD_FUZZER_pthread_mutex_unlock_BEFORE_SLEEP_TIME_US=10000 export THREAD_FUZZER_pthread_mutex_unlock_AFTER_SLEEP_TIME_US=10000 @@ -238,7 +238,7 @@ then stop # Error messages (we should ignore some errors) - zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "DDLWorker: Cannot parse DDL task query" -e "RaftInstance: failed to accept a rpc connection due to error 125" \ + zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "DDLWorker: Cannot parse DDL task query" -e "RaftInstance: failed to accept a rpc connection due to error 125" -e "UNKNOWN_DATABASE" \ /var/log/clickhouse-server/clickhouse-server.log | zgrep -Fa "" > /dev/null \ && echo -e 'Error message in clickhouse-server.log\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ || echo -e 'No Error messages in clickhouse-server.log\tOK' >> /test_output/backward_compatibility_check_results.tsv From fd7d1131ec5cd39d06bec160d8ba7ed9ce57c304 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 6 Oct 2021 13:17:48 +0300 Subject: [PATCH 0072/1647] 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 c09080050b7..561ecce5303 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -238,7 +238,7 @@ then stop # Error messages (we should ignore some errors) - zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "DDLWorker: Cannot parse DDL task query" -e "RaftInstance: failed to accept a rpc connection due to error 125" -e "UNKNOWN_DATABASE" \ + zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "DDLWorker: Cannot parse DDL task query" -e "RaftInstance: failed to accept a rpc connection due to error 125" -e "UNKNOWN_DATABASE" -e "NETWORK_ERROR" \ /var/log/clickhouse-server/clickhouse-server.log | zgrep -Fa "" > /dev/null \ && echo -e 'Error message in clickhouse-server.log\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ || echo -e 'No Error messages in clickhouse-server.log\tOK' >> /test_output/backward_compatibility_check_results.tsv From 77c22dce3c63436a2b241cf5e144b4f6cafebcd8 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Mon, 18 Oct 2021 18:53:42 +0800 Subject: [PATCH 0073/1647] Zookeeper load balancing settings --- src/Common/ZooKeeper/ZooKeeper.cpp | 95 +++++++++++++++++++++++++---- src/Common/ZooKeeper/ZooKeeper.h | 37 ++++++++++- src/Core/SettingsEnums.cpp | 8 +++ src/Core/SettingsEnums.h | 19 ++++++ tests/config/config.d/zookeeper.xml | 2 + 5 files changed, 145 insertions(+), 16 deletions(-) diff --git a/src/Common/ZooKeeper/ZooKeeper.cpp b/src/Common/ZooKeeper/ZooKeeper.cpp index 3d505c088db..65295a96d3e 100644 --- a/src/Common/ZooKeeper/ZooKeeper.cpp +++ b/src/Common/ZooKeeper/ZooKeeper.cpp @@ -5,15 +5,16 @@ #include #include -#include #include #include -#include +#include #include #include +#include #include +#include #define ZOOKEEPER_CONNECTION_TIMEOUT_MS 1000 @@ -47,7 +48,7 @@ static void check(Coordination::Error code, const std::string & path) void ZooKeeper::init(const std::string & implementation_, const Strings & hosts_, const std::string & identity_, - int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_) + int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, ZooKeeperLoadBalancing zookeeper_load_balancing_) { log = &Poco::Logger::get("ZooKeeper"); hosts = hosts_; @@ -56,6 +57,7 @@ void ZooKeeper::init(const std::string & implementation_, const Strings & hosts_ operation_timeout_ms = operation_timeout_ms_; chroot = chroot_; implementation = implementation_; + zookeeper_load_balancing = zookeeper_load_balancing_; if (implementation == "zookeeper") { @@ -65,14 +67,13 @@ void ZooKeeper::init(const std::string & implementation_, const Strings & hosts_ Coordination::ZooKeeper::Nodes nodes; nodes.reserve(hosts.size()); - Strings shuffled_hosts = hosts; /// Shuffle the hosts to distribute the load among ZooKeeper nodes. - pcg64 generator(randomSeed()); - std::shuffle(shuffled_hosts.begin(), shuffled_hosts.end(), generator); + std::vector shuffled_hosts = shuffleHosts(); bool dns_error = false; - for (auto & host_string : shuffled_hosts) + for (auto & host : shuffled_hosts) { + auto & host_string = host.host; try { bool secure = bool(startsWith(host_string, "secure://")); @@ -153,23 +154,85 @@ void ZooKeeper::init(const std::string & implementation_, const Strings & hosts_ } } +std::vector ZooKeeper::shuffleHosts() const +{ + std::vector hostname_differences; + hostname_differences.resize(hosts.size()); + String local_hostname = getFQDNOrHostName(); + for (size_t i = 0; i < hosts.size(); ++i) + { + String ip_or_hostname = hosts[i].substr(0, hosts[i].find_last_of(":")); + hostname_differences[i] = DB::getHostNameDifference(local_hostname, Poco::Net::DNS::resolve(ip_or_hostname).name()); + } + + size_t offset = 0; + std::function get_priority; + switch (ZooKeeperLoadBalancing(zookeeper_load_balancing)) + { + case ZooKeeperLoadBalancing::NEAREST_HOSTNAME: + get_priority = [&](size_t i) { return hostname_differences[i]; }; + break; + case ZooKeeperLoadBalancing::IN_ORDER: + get_priority = [](size_t i) { return i; }; + break; + case ZooKeeperLoadBalancing::RANDOM: + break; + case ZooKeeperLoadBalancing::FIRST_OR_RANDOM: + get_priority = [offset](size_t i) -> size_t { return i != offset; }; + break; + case ZooKeeperLoadBalancing::ROUND_ROBIN: + static size_t last_used = 0; + if (last_used >= hosts.size()) + last_used = 0; + ++last_used; + /* Consider hosts.size() equals to 5 + * last_used = 1 -> get_priority: 0 1 2 3 4 + * last_used = 2 -> get_priority: 4 0 1 2 3 + * last_used = 3 -> get_priority: 4 3 0 1 2 + * ... + * */ + get_priority = [&](size_t i) { ++i; return i < last_used ? hosts.size() - i : i - last_used; }; + break; + } + + std::vector shuffle_hosts; + for (size_t i = 0; i < hosts.size(); ++i) + { + ShuffleHost shuffle_host; + shuffle_host.host = hosts[i]; + if (get_priority) + shuffle_host.priority = get_priority(i); + shuffle_host.randomize(); + shuffle_hosts.emplace_back(shuffle_host); + } + + std::sort( + shuffle_hosts.begin(), shuffle_hosts.end(), + [](const ShuffleHost & lhs, const ShuffleHost & rhs) + { + return ShuffleHost::compare(lhs, rhs); + }); + + return shuffle_hosts; +} + ZooKeeper::ZooKeeper(const std::string & hosts_string, const std::string & identity_, int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, const std::string & implementation_, - std::shared_ptr zk_log_) + std::shared_ptr zk_log_, ZooKeeperLoadBalancing zookeeper_load_balancing_) { zk_log = std::move(zk_log_); Strings hosts_strings; splitInto<','>(hosts_strings, hosts_string); - init(implementation_, hosts_strings, identity_, session_timeout_ms_, operation_timeout_ms_, chroot_); + init(implementation_, hosts_strings, identity_, session_timeout_ms_, operation_timeout_ms_, chroot_, zookeeper_load_balancing_); } ZooKeeper::ZooKeeper(const Strings & hosts_, const std::string & identity_, int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, const std::string & implementation_, - std::shared_ptr zk_log_) + std::shared_ptr zk_log_, ZooKeeperLoadBalancing zookeeper_load_balancing_) { zk_log = std::move(zk_log_); - init(implementation_, hosts_, identity_, session_timeout_ms_, operation_timeout_ms_, chroot_); + init(implementation_, hosts_, identity_, session_timeout_ms_, operation_timeout_ms_, chroot_, zookeeper_load_balancing_); } struct ZooKeeperArgs @@ -182,6 +245,7 @@ struct ZooKeeperArgs session_timeout_ms = Coordination::DEFAULT_SESSION_TIMEOUT_MS; operation_timeout_ms = Coordination::DEFAULT_OPERATION_TIMEOUT_MS; implementation = "zookeeper"; + zookeeper_load_balancing = ZooKeeperLoadBalancing::RANDOM; for (const auto & key : keys) { if (startsWith(key, "node")) @@ -212,6 +276,10 @@ struct ZooKeeperArgs { implementation = config.getString(config_name + "." + key); } + else if (key == "zookeeper_load_balancing") + { + zookeeper_load_balancing = DB::SettingFieldZooKeeperLoadBalancingTraits::fromString(config.getString(config_name + "." + key)); + } else throw KeeperException(std::string("Unknown key ") + key + " in config file", Coordination::Error::ZBADARGUMENTS); } @@ -231,13 +299,14 @@ struct ZooKeeperArgs int operation_timeout_ms; std::string chroot; std::string implementation; + ZooKeeperLoadBalancing zookeeper_load_balancing; }; ZooKeeper::ZooKeeper(const Poco::Util::AbstractConfiguration & config, const std::string & config_name, std::shared_ptr zk_log_) : zk_log(std::move(zk_log_)) { ZooKeeperArgs args(config, config_name); - init(args.implementation, args.hosts, args.identity, args.session_timeout_ms, args.operation_timeout_ms, args.chroot); + init(args.implementation, args.hosts, args.identity, args.session_timeout_ms, args.operation_timeout_ms, args.chroot, args.zookeeper_load_balancing); } bool ZooKeeper::configChanged(const Poco::Util::AbstractConfiguration & config, const std::string & config_name) const @@ -752,7 +821,7 @@ bool ZooKeeper::waitForDisappear(const std::string & path, const WaitCondition & ZooKeeperPtr ZooKeeper::startNewSession() const { - return std::make_shared(hosts, identity, session_timeout_ms, operation_timeout_ms, chroot, implementation, zk_log); + return std::make_shared(hosts, identity, session_timeout_ms, operation_timeout_ms, chroot, implementation, zk_log, zookeeper_load_balancing); } diff --git a/src/Common/ZooKeeper/ZooKeeper.h b/src/Common/ZooKeeper/ZooKeeper.h index 8e015b1f331..4ad19eb3a4c 100644 --- a/src/Common/ZooKeeper/ZooKeeper.h +++ b/src/Common/ZooKeeper/ZooKeeper.h @@ -13,7 +13,10 @@ #include #include #include +#include +#include #include +#include namespace ProfileEvents @@ -37,6 +40,29 @@ namespace zkutil /// Preferred size of multi() command (in number of ops) constexpr size_t MULTI_BATCH_SIZE = 100; +struct ShuffleHost +{ + String host; + /// Priority from the GetPriorityFunc. + Int64 priority = 0; + UInt32 random = 0; + + void randomize() + { + random = rng(); + } + + static bool compare(const ShuffleHost & lhs, const ShuffleHost & rhs) + { + return std::forward_as_tuple(lhs.priority, lhs.random) + < std::forward_as_tuple(rhs.priority, rhs.random); + } + +private: + std::minstd_rand rng = std::minstd_rand(randomSeed()); +}; + +using ZooKeeperLoadBalancing = DB::ZooKeeperLoadBalancing; /// ZooKeeper session. The interface is substantially different from the usual libzookeeper API. /// @@ -58,14 +84,16 @@ public: int32_t operation_timeout_ms_ = Coordination::DEFAULT_OPERATION_TIMEOUT_MS, const std::string & chroot_ = "", const std::string & implementation_ = "zookeeper", - std::shared_ptr zk_log_ = nullptr); + std::shared_ptr zk_log_ = nullptr, + ZooKeeperLoadBalancing zookeeper_load_balancing_ = ZooKeeperLoadBalancing::RANDOM); ZooKeeper(const Strings & hosts_, const std::string & identity_ = "", int32_t session_timeout_ms_ = Coordination::DEFAULT_SESSION_TIMEOUT_MS, int32_t operation_timeout_ms_ = Coordination::DEFAULT_OPERATION_TIMEOUT_MS, const std::string & chroot_ = "", const std::string & implementation_ = "zookeeper", - std::shared_ptr zk_log_ = nullptr); + std::shared_ptr zk_log_ = nullptr, + ZooKeeperLoadBalancing zookeeper_load_balancing_ = ZooKeeperLoadBalancing::RANDOM); /** Config of the form: @@ -91,6 +119,8 @@ public: */ ZooKeeper(const Poco::Util::AbstractConfiguration & config, const std::string & config_name, std::shared_ptr zk_log_); + std::vector shuffleHosts() const; + /// Creates a new session with the same parameters. This method can be used for reconnecting /// after the session has expired. /// This object remains unchanged, and the new session is returned. @@ -284,7 +314,7 @@ private: friend class EphemeralNodeHolder; void init(const std::string & implementation_, const Strings & hosts_, const std::string & identity_, - int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_); + int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, ZooKeeperLoadBalancing zookeeper_load_balancing_); /// The following methods don't any throw exceptions but return error codes. Coordination::Error createImpl(const std::string & path, const std::string & data, int32_t mode, std::string & path_created); @@ -310,6 +340,7 @@ private: Poco::Logger * log = nullptr; std::shared_ptr zk_log; + ZooKeeperLoadBalancing zookeeper_load_balancing; AtomicStopwatch session_uptime; }; diff --git a/src/Core/SettingsEnums.cpp b/src/Core/SettingsEnums.cpp index 8e588b62326..2aa296533fe 100644 --- a/src/Core/SettingsEnums.cpp +++ b/src/Core/SettingsEnums.cpp @@ -116,4 +116,12 @@ IMPLEMENT_SETTING_ENUM(ShortCircuitFunctionEvaluation, ErrorCodes::BAD_ARGUMENTS {{"enable", ShortCircuitFunctionEvaluation::ENABLE}, {"force_enable", ShortCircuitFunctionEvaluation::FORCE_ENABLE}, {"disable", ShortCircuitFunctionEvaluation::DISABLE}}) + +IMPLEMENT_SETTING_ENUM(ZooKeeperLoadBalancing, ErrorCodes::UNKNOWN_LOAD_BALANCING, + {{"random", ZooKeeperLoadBalancing::RANDOM}, + {"nearest_hostname", ZooKeeperLoadBalancing::NEAREST_HOSTNAME}, + {"in_order", ZooKeeperLoadBalancing::IN_ORDER}, + {"first_or_random", ZooKeeperLoadBalancing::FIRST_OR_RANDOM}, + {"round_robin", ZooKeeperLoadBalancing::ROUND_ROBIN}}) + } diff --git a/src/Core/SettingsEnums.h b/src/Core/SettingsEnums.h index 33c5a6d8645..a308fa1745b 100644 --- a/src/Core/SettingsEnums.h +++ b/src/Core/SettingsEnums.h @@ -168,4 +168,23 @@ enum class ShortCircuitFunctionEvaluation DECLARE_SETTING_ENUM(ShortCircuitFunctionEvaluation) +enum class ZooKeeperLoadBalancing +{ + /// among replicas with a minimum number of errors selected randomly + RANDOM = 0, + /// a replica is selected among the replicas with the minimum number of errors + /// with the minimum number of distinguished characters in the replica name and local hostname + NEAREST_HOSTNAME, + // replicas with the same number of errors are accessed in the same order + // as they are specified in the configuration. + IN_ORDER, + /// if first replica one has higher number of errors, + /// pick a random one from replicas with minimum number of errors + FIRST_OR_RANDOM, + // round robin across replicas with the same number of errors. + ROUND_ROBIN, +}; + +DECLARE_SETTING_ENUM(ZooKeeperLoadBalancing) + } diff --git a/tests/config/config.d/zookeeper.xml b/tests/config/config.d/zookeeper.xml index 4fa529a6180..63057224ef9 100644 --- a/tests/config/config.d/zookeeper.xml +++ b/tests/config/config.d/zookeeper.xml @@ -1,5 +1,7 @@ + + random localhost 9181 From fed7bb594179257373aaf1b2109bcd1da6dd3bb8 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Mon, 18 Oct 2021 19:10:53 +0800 Subject: [PATCH 0074/1647] Update comments. --- src/Core/SettingsEnums.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Core/SettingsEnums.h b/src/Core/SettingsEnums.h index a308fa1745b..b7506e52176 100644 --- a/src/Core/SettingsEnums.h +++ b/src/Core/SettingsEnums.h @@ -170,18 +170,17 @@ DECLARE_SETTING_ENUM(ShortCircuitFunctionEvaluation) enum class ZooKeeperLoadBalancing { - /// among replicas with a minimum number of errors selected randomly + /// Randomly select one from the zookeeper nodes. RANDOM = 0, - /// a replica is selected among the replicas with the minimum number of errors - /// with the minimum number of distinguished characters in the replica name and local hostname + /// Choose one from the zookeeper node that has the least + /// number of characters different from the hostname of the local host NEAREST_HOSTNAME, - // replicas with the same number of errors are accessed in the same order - // as they are specified in the configuration. + /// Select one from the zookeeper node configuration in order. IN_ORDER, - /// if first replica one has higher number of errors, - /// pick a random one from replicas with minimum number of errors + /// If the first node cannot be connected, + /// one will be randomly selected from other nodes. FIRST_OR_RANDOM, - // round robin across replicas with the same number of errors. + /// Round robin from the node configured by zookeeper. ROUND_ROBIN, }; From 62a15c1c1a7077539d9faafd4615b9f3a755af75 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Tue, 19 Oct 2021 12:43:54 +0800 Subject: [PATCH 0075/1647] Fix some build error and try fix undefined symbol: DB::SettingFieldZooKeeperLoadBalancingTraits::fromString build error. --- src/Common/ZooKeeper/ZooKeeper.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Common/ZooKeeper/ZooKeeper.cpp b/src/Common/ZooKeeper/ZooKeeper.cpp index 65295a96d3e..3bff19fc2d9 100644 --- a/src/Common/ZooKeeper/ZooKeeper.cpp +++ b/src/Common/ZooKeeper/ZooKeeper.cpp @@ -158,10 +158,10 @@ std::vector ZooKeeper::shuffleHosts() const { std::vector hostname_differences; hostname_differences.resize(hosts.size()); - String local_hostname = getFQDNOrHostName(); + const String & local_hostname = getFQDNOrHostName(); for (size_t i = 0; i < hosts.size(); ++i) { - String ip_or_hostname = hosts[i].substr(0, hosts[i].find_last_of(":")); + const String & ip_or_hostname = hosts[i].substr(0, hosts[i].find_last_of(':')); hostname_differences[i] = DB::getHostNameDifference(local_hostname, Poco::Net::DNS::resolve(ip_or_hostname).name()); } @@ -278,7 +278,9 @@ struct ZooKeeperArgs } else if (key == "zookeeper_load_balancing") { - zookeeper_load_balancing = DB::SettingFieldZooKeeperLoadBalancingTraits::fromString(config.getString(config_name + "." + key)); + DB::SettingFieldZooKeeperLoadBalancing setting_field; + setting_field.parseFromString(config.getString(config_name + "." + key)); + zookeeper_load_balancing = setting_field.value; } else throw KeeperException(std::string("Unknown key ") + key + " in config file", Coordination::Error::ZBADARGUMENTS); @@ -317,8 +319,8 @@ bool ZooKeeper::configChanged(const Poco::Util::AbstractConfiguration & config, if (args.implementation == implementation && implementation == "testkeeper") return false; - return std::tie(args.implementation, args.hosts, args.identity, args.session_timeout_ms, args.operation_timeout_ms, args.chroot) - != std::tie(implementation, hosts, identity, session_timeout_ms, operation_timeout_ms, chroot); + return std::tie(args.implementation, args.hosts, args.identity, args.session_timeout_ms, args.operation_timeout_ms, args.chroot, args.zookeeper_load_balancing) + != std::tie(implementation, hosts, identity, session_timeout_ms, operation_timeout_ms, chroot, zookeeper_load_balancing); } From a6ae846f02c32a2c91728ade3b4ff58c86ca85c2 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Tue, 19 Oct 2021 17:39:03 +0800 Subject: [PATCH 0076/1647] fix biuld and PVS check --- src/Common/ZooKeeper/ZooKeeper.cpp | 40 ++++++++++--- src/Common/ZooKeeper/ZooKeeper.h | 18 +++++- src/Core/SettingsEnums.cpp | 7 --- src/Core/SettingsEnums.h | 18 ------ .../__init__.py | 0 .../configs/remote_servers.xml | 23 ++++++++ .../configs/zookeeper_config_in_order.xml | 20 +++++++ .../test.py | 57 +++++++++++++++++++ 8 files changed, 149 insertions(+), 34 deletions(-) create mode 100644 tests/integration/test_zookeeper_config_load_balancing/__init__.py create mode 100644 tests/integration/test_zookeeper_config_load_balancing/configs/remote_servers.xml create mode 100644 tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_in_order.xml create mode 100644 tests/integration/test_zookeeper_config_load_balancing/test.py diff --git a/src/Common/ZooKeeper/ZooKeeper.cpp b/src/Common/ZooKeeper/ZooKeeper.cpp index 3bff19fc2d9..2f33888846b 100644 --- a/src/Common/ZooKeeper/ZooKeeper.cpp +++ b/src/Common/ZooKeeper/ZooKeeper.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -27,6 +26,7 @@ namespace ErrorCodes { extern const int LOGICAL_ERROR; extern const int NOT_IMPLEMENTED; + extern const int UNKNOWN_LOAD_BALANCING; } } @@ -46,6 +46,35 @@ static void check(Coordination::Error code, const std::string & path) throw KeeperException(code, path); } +static ZooKeeperLoadBalancing fromString(const std::string_view & str) +{ + static const std::unordered_map map = [] { + std::unordered_map res; + constexpr std::pair pairs[] + = {{"random", ZooKeeperLoadBalancing::RANDOM}, + {"nearest_hostname", ZooKeeperLoadBalancing::NEAREST_HOSTNAME}, + {"in_order", ZooKeeperLoadBalancing::IN_ORDER}, + {"first_or_random", ZooKeeperLoadBalancing::FIRST_OR_RANDOM}, + {"round_robin", ZooKeeperLoadBalancing::ROUND_ROBIN}}; + for (const auto & [name, val] : pairs) + res.emplace(name, val); + return res; + }(); + auto it = map.find(str); + if (it != map.end()) + return it->second; + String msg = "Unexpected value of ZooKeeperLoadBalancing: '" + String{str} + "'. Must be one of ["; + bool need_comma = false; + for (auto & name : map | boost::adaptors::map_keys) + { + if (std::exchange(need_comma, true)) + msg += ", "; + msg += "'" + String{name} + "'"; + } + msg += "]"; + throw DB::Exception(msg, DB::ErrorCodes::UNKNOWN_LOAD_BALANCING); +} + void ZooKeeper::init(const std::string & implementation_, const Strings & hosts_, const std::string & identity_, int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, ZooKeeperLoadBalancing zookeeper_load_balancing_) @@ -165,7 +194,6 @@ std::vector ZooKeeper::shuffleHosts() const hostname_differences[i] = DB::getHostNameDifference(local_hostname, Poco::Net::DNS::resolve(ip_or_hostname).name()); } - size_t offset = 0; std::function get_priority; switch (ZooKeeperLoadBalancing(zookeeper_load_balancing)) { @@ -178,7 +206,7 @@ std::vector ZooKeeper::shuffleHosts() const case ZooKeeperLoadBalancing::RANDOM: break; case ZooKeeperLoadBalancing::FIRST_OR_RANDOM: - get_priority = [offset](size_t i) -> size_t { return i != offset; }; + get_priority = [](size_t i) -> size_t { return i != 0; }; break; case ZooKeeperLoadBalancing::ROUND_ROBIN: static size_t last_used = 0; @@ -191,7 +219,7 @@ std::vector ZooKeeper::shuffleHosts() const * last_used = 3 -> get_priority: 4 3 0 1 2 * ... * */ - get_priority = [&](size_t i) { ++i; return i < last_used ? hosts.size() - i : i - last_used; }; + get_priority = [this, last_used_value = last_used](size_t i) { ++i; return i < last_used_value ? hosts.size() - i : i - last_used_value; }; break; } @@ -278,9 +306,7 @@ struct ZooKeeperArgs } else if (key == "zookeeper_load_balancing") { - DB::SettingFieldZooKeeperLoadBalancing setting_field; - setting_field.parseFromString(config.getString(config_name + "." + key)); - zookeeper_load_balancing = setting_field.value; + zookeeper_load_balancing = fromString(config.getString(config_name + "." + key)); } else throw KeeperException(std::string("Unknown key ") + key + " in config file", Coordination::Error::ZBADARGUMENTS); diff --git a/src/Common/ZooKeeper/ZooKeeper.h b/src/Common/ZooKeeper/ZooKeeper.h index 4ad19eb3a4c..c992ffe3a43 100644 --- a/src/Common/ZooKeeper/ZooKeeper.h +++ b/src/Common/ZooKeeper/ZooKeeper.h @@ -14,7 +14,6 @@ #include #include #include -#include #include #include @@ -62,7 +61,22 @@ private: std::minstd_rand rng = std::minstd_rand(randomSeed()); }; -using ZooKeeperLoadBalancing = DB::ZooKeeperLoadBalancing; +enum class ZooKeeperLoadBalancing +{ + /// Randomly select one from the zookeeper nodes. + RANDOM = 0, + /// Choose one from the zookeeper node that has the least + /// number of characters different from the hostname of the local host + NEAREST_HOSTNAME, + /// Select one from the zookeeper node configuration in order. + IN_ORDER, + /// If the first node cannot be connected, + /// one will be randomly selected from other nodes. + FIRST_OR_RANDOM, + /// Round robin from the node configured by zookeeper. + ROUND_ROBIN, +}; + /// ZooKeeper session. The interface is substantially different from the usual libzookeeper API. /// diff --git a/src/Core/SettingsEnums.cpp b/src/Core/SettingsEnums.cpp index 2aa296533fe..6a5d8136227 100644 --- a/src/Core/SettingsEnums.cpp +++ b/src/Core/SettingsEnums.cpp @@ -117,11 +117,4 @@ IMPLEMENT_SETTING_ENUM(ShortCircuitFunctionEvaluation, ErrorCodes::BAD_ARGUMENTS {"force_enable", ShortCircuitFunctionEvaluation::FORCE_ENABLE}, {"disable", ShortCircuitFunctionEvaluation::DISABLE}}) -IMPLEMENT_SETTING_ENUM(ZooKeeperLoadBalancing, ErrorCodes::UNKNOWN_LOAD_BALANCING, - {{"random", ZooKeeperLoadBalancing::RANDOM}, - {"nearest_hostname", ZooKeeperLoadBalancing::NEAREST_HOSTNAME}, - {"in_order", ZooKeeperLoadBalancing::IN_ORDER}, - {"first_or_random", ZooKeeperLoadBalancing::FIRST_OR_RANDOM}, - {"round_robin", ZooKeeperLoadBalancing::ROUND_ROBIN}}) - } diff --git a/src/Core/SettingsEnums.h b/src/Core/SettingsEnums.h index b7506e52176..33c5a6d8645 100644 --- a/src/Core/SettingsEnums.h +++ b/src/Core/SettingsEnums.h @@ -168,22 +168,4 @@ enum class ShortCircuitFunctionEvaluation DECLARE_SETTING_ENUM(ShortCircuitFunctionEvaluation) -enum class ZooKeeperLoadBalancing -{ - /// Randomly select one from the zookeeper nodes. - RANDOM = 0, - /// Choose one from the zookeeper node that has the least - /// number of characters different from the hostname of the local host - NEAREST_HOSTNAME, - /// Select one from the zookeeper node configuration in order. - IN_ORDER, - /// If the first node cannot be connected, - /// one will be randomly selected from other nodes. - FIRST_OR_RANDOM, - /// Round robin from the node configured by zookeeper. - ROUND_ROBIN, -}; - -DECLARE_SETTING_ENUM(ZooKeeperLoadBalancing) - } diff --git a/tests/integration/test_zookeeper_config_load_balancing/__init__.py b/tests/integration/test_zookeeper_config_load_balancing/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_zookeeper_config_load_balancing/configs/remote_servers.xml b/tests/integration/test_zookeeper_config_load_balancing/configs/remote_servers.xml new file mode 100644 index 00000000000..63fdcea5dab --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/configs/remote_servers.xml @@ -0,0 +1,23 @@ + + + + + + node1 + 9000 + + + + node2 + 9000 + + + + node3 + 9000 + + + + + + diff --git a/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_in_order.xml b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_in_order.xml new file mode 100644 index 00000000000..bbed71532aa --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_in_order.xml @@ -0,0 +1,20 @@ + + + + random + + zoo1 + 2181 + + + zoo2 + 2181 + + + zoo3 + 2181 + + 3000 + /root_a + + diff --git a/tests/integration/test_zookeeper_config_load_balancing/test.py b/tests/integration/test_zookeeper_config_load_balancing/test.py new file mode 100644 index 00000000000..95d9db27a7d --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/test.py @@ -0,0 +1,57 @@ +import time +import pytest +import logging +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_root_a.xml') + +node1 = cluster.add_instance('node1', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_root_a.xml"]) +node2 = cluster.add_instance('node2', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_root_a.xml"]) +node3 = cluster.add_instance('node3', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_root_b.xml"]) + +def create_zk_roots(zk): + zk.ensure_path('/root_a') + zk.ensure_path('/root_b') + logging.debug(f"Create ZK roots:{zk.get_children('/')}") + +@pytest.fixture(scope="module", autouse=True) +def started_cluster(): + try: + cluster.add_zookeeper_startup_command(create_zk_roots) + cluster.start() + + yield cluster + + finally: + cluster.shutdown() + +def test_chroot_with_same_root(started_cluster): + for i, node in enumerate([node1, node2]): + node.query('DROP TABLE IF EXISTS simple SYNC') + node.query(''' + CREATE TABLE simple (date Date, id UInt32) + ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); + '''.format(replica=node.name)) + for j in range(2): # Second insert to test deduplication + node.query("INSERT INTO simple VALUES ({0}, {0})".format(i)) + + time.sleep(1) + + assert node1.query('select count() from simple').strip() == '2' + assert node2.query('select count() from simple').strip() == '2' + +def test_chroot_with_different_root(started_cluster): + for i, node in [(1, node1), (3, node3)]: + node.query('DROP TABLE IF EXISTS simple_different SYNC') + node.query(''' + CREATE TABLE simple_different (date Date, id UInt32) + ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple_different', '{replica}', date, id, 8192); + '''.format(replica=node.name)) + for j in range(2): # Second insert to test deduplication + node.query("INSERT INTO simple_different VALUES ({0}, {0})".format(i)) + + assert node1.query('select count() from simple_different').strip() == '1' + assert node3.query('select count() from simple_different').strip() == '1' From 8480ae631acda0caa381141c4204583a6e1a9150 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Wed, 20 Oct 2021 16:35:37 +0800 Subject: [PATCH 0077/1647] Refactor and add test. --- src/Client/ConnectionPoolWithFailover.cpp | 69 ++-------- src/Client/ConnectionPoolWithFailover.h | 8 +- src/Common/GetPriorityForLoadBalancing.cpp | 41 ++++++ src/Common/GetPriorityForLoadBalancing.h | 32 +++++ src/Common/ZooKeeper/ZooKeeper.cpp | 114 +++++---------- src/Common/ZooKeeper/ZooKeeper.h | 27 +--- .../configs/zookeeper_config_in_order.xml | 3 +- .../configs/zookeeper_config_round_robin.xml | 19 +++ .../configs/zookeeper_log.xml | 7 + .../test.py | 130 ++++++++++++++---- .../test_round_robin.py | 100 ++++++++++++++ 11 files changed, 357 insertions(+), 193 deletions(-) create mode 100644 src/Common/GetPriorityForLoadBalancing.cpp create mode 100644 src/Common/GetPriorityForLoadBalancing.h create mode 100644 tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_round_robin.xml create mode 100644 tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_log.xml create mode 100644 tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py diff --git a/src/Client/ConnectionPoolWithFailover.cpp b/src/Client/ConnectionPoolWithFailover.cpp index aaffe85ae2e..ecfc6bd5c08 100644 --- a/src/Client/ConnectionPoolWithFailover.cpp +++ b/src/Client/ConnectionPoolWithFailover.cpp @@ -29,15 +29,15 @@ ConnectionPoolWithFailover::ConnectionPoolWithFailover( time_t decrease_error_period_, size_t max_error_cap_) : Base(std::move(nested_pools_), decrease_error_period_, max_error_cap_, &Poco::Logger::get("ConnectionPoolWithFailover")) - , default_load_balancing(load_balancing) + , get_priority_load_balancing(load_balancing) { const std::string & local_hostname = getFQDNOrHostName(); - hostname_differences.resize(nested_pools.size()); + get_priority_load_balancing.hostname_differences.resize(nested_pools.size()); for (size_t i = 0; i < nested_pools.size(); ++i) { ConnectionPool & connection_pool = dynamic_cast(*nested_pools[i]); - hostname_differences[i] = getHostNameDifference(local_hostname, connection_pool.getHost()); + get_priority_load_balancing.hostname_differences[i] = getHostNameDifference(local_hostname, connection_pool.getHost()); } } @@ -50,36 +50,12 @@ IConnectionPool::Entry ConnectionPoolWithFailover::get(const ConnectionTimeouts return tryGetEntry(pool, timeouts, fail_message, settings); }; - size_t offset = 0; if (settings) - offset = settings->load_balancing_first_offset % nested_pools.size(); - GetPriorityFunc get_priority; - switch (settings ? LoadBalancing(settings->load_balancing) : default_load_balancing) { - case LoadBalancing::NEAREST_HOSTNAME: - get_priority = [&](size_t i) { return hostname_differences[i]; }; - break; - case LoadBalancing::IN_ORDER: - get_priority = [](size_t i) { return i; }; - break; - case LoadBalancing::RANDOM: - break; - case LoadBalancing::FIRST_OR_RANDOM: - get_priority = [offset](size_t i) -> size_t { return i != offset; }; - break; - case LoadBalancing::ROUND_ROBIN: - if (last_used >= nested_pools.size()) - last_used = 0; - ++last_used; - /* Consider nested_pools.size() equals to 5 - * last_used = 1 -> get_priority: 0 1 2 3 4 - * last_used = 2 -> get_priority: 4 0 1 2 3 - * last_used = 3 -> get_priority: 4 3 0 1 2 - * ... - * */ - get_priority = [&](size_t i) { ++i; return i < last_used ? nested_pools.size() - i : i - last_used; }; - break; + get_priority_load_balancing.offset = settings->load_balancing_first_offset % nested_pools.size(); + get_priority_load_balancing.load_balancing = settings->load_balancing; } + GetPriorityFunc get_priority = get_priority_load_balancing.getPriorityFunc(); UInt64 max_ignored_errors = settings ? settings->distributed_replica_max_ignored_errors.value : 0; bool fallback_to_stale_replicas = settings ? settings->fallback_to_stale_replicas_for_distributed_queries.value : true; @@ -172,39 +148,12 @@ std::vector ConnectionPoolWithFailover::g ConnectionPoolWithFailover::Base::GetPriorityFunc ConnectionPoolWithFailover::makeGetPriorityFunc(const Settings * settings) { - size_t offset = 0; if (settings) - offset = settings->load_balancing_first_offset % nested_pools.size(); - - GetPriorityFunc get_priority; - switch (settings ? LoadBalancing(settings->load_balancing) : default_load_balancing) { - case LoadBalancing::NEAREST_HOSTNAME: - get_priority = [&](size_t i) { return hostname_differences[i]; }; - break; - case LoadBalancing::IN_ORDER: - get_priority = [](size_t i) { return i; }; - break; - case LoadBalancing::RANDOM: - break; - case LoadBalancing::FIRST_OR_RANDOM: - get_priority = [offset](size_t i) -> size_t { return i != offset; }; - break; - case LoadBalancing::ROUND_ROBIN: - if (last_used >= nested_pools.size()) - last_used = 0; - ++last_used; - /* Consider nested_pools.size() equals to 5 - * last_used = 1 -> get_priority: 0 1 2 3 4 - * last_used = 2 -> get_priority: 5 0 1 2 3 - * last_used = 3 -> get_priority: 5 4 0 1 2 - * ... - * */ - get_priority = [&](size_t i) { ++i; return i < last_used ? nested_pools.size() - i : i - last_used; }; - break; + get_priority_load_balancing.offset = settings->load_balancing_first_offset % nested_pools.size(); + get_priority_load_balancing.load_balancing = settings->load_balancing; } - - return get_priority; + return get_priority_load_balancing.getPriorityFunc(); } std::vector ConnectionPoolWithFailover::getManyImpl( diff --git a/src/Client/ConnectionPoolWithFailover.h b/src/Client/ConnectionPoolWithFailover.h index ce70c27838b..3c838459733 100644 --- a/src/Client/ConnectionPoolWithFailover.h +++ b/src/Client/ConnectionPoolWithFailover.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -110,9 +111,10 @@ private: GetPriorityFunc makeGetPriorityFunc(const Settings * settings); private: - std::vector hostname_differences; /// Distances from name of this host to the names of hosts of pools. - size_t last_used = 0; /// Last used for round_robin policy. - LoadBalancing default_load_balancing; + GetPriorityForLoadBalancing get_priority_load_balancing; +// std::vector hostname_differences; /// Distances from name of this host to the names of hosts of pools. +// size_t last_used = 0; /// Last used for round_robin policy. +// LoadBalancing default_load_balancing; }; using ConnectionPoolWithFailoverPtr = std::shared_ptr; diff --git a/src/Common/GetPriorityForLoadBalancing.cpp b/src/Common/GetPriorityForLoadBalancing.cpp new file mode 100644 index 00000000000..ae621d9e75c --- /dev/null +++ b/src/Common/GetPriorityForLoadBalancing.cpp @@ -0,0 +1,41 @@ +#include + +namespace DB +{ + +std::function GetPriorityForLoadBalancing::getPriorityFunc() const +{ + std::function get_priority; + switch (load_balancing) + { + case LoadBalancing::NEAREST_HOSTNAME: + get_priority = [&](size_t i) { return hostname_differences[i]; }; + break; + case LoadBalancing::IN_ORDER: + get_priority = [](size_t i) { return i; }; + break; + case LoadBalancing::RANDOM: + break; + case LoadBalancing::FIRST_OR_RANDOM: + get_priority = [&](size_t i) -> size_t { return i != offset; }; + break; + case LoadBalancing::ROUND_ROBIN: + if (last_used >= pool_size) + last_used = 0; + ++last_used; + /* Consider pool_size equals to 5 + * last_used = 1 -> get_priority: 0 1 2 3 4 + * last_used = 2 -> get_priority: 4 0 1 2 3 + * last_used = 3 -> get_priority: 4 3 0 1 2 + * ... + * */ + get_priority = [&](size_t i) { + ++i; + return i < last_used ? pool_size - i : i - last_used; + }; + break; + } + return get_priority; +} + +} diff --git a/src/Common/GetPriorityForLoadBalancing.h b/src/Common/GetPriorityForLoadBalancing.h new file mode 100644 index 00000000000..b845c2e7616 --- /dev/null +++ b/src/Common/GetPriorityForLoadBalancing.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace DB +{ + +class GetPriorityForLoadBalancing +{ +public: + GetPriorityForLoadBalancing(LoadBalancing load_balancing_) : load_balancing(load_balancing_) {} + GetPriorityForLoadBalancing(){} + + bool operator!=(const GetPriorityForLoadBalancing & other) + { + return offset != other.offset || pool_size != other.pool_size || load_balancing != other.load_balancing + || hostname_differences != other.hostname_differences; + } + + std::function getPriorityFunc() const; + + std::vector hostname_differences; /// Distances from name of this host to the names of hosts of pools. + size_t offset = 0; /// for first_or_random policy. + size_t pool_size; /// for round_robin policy. + + LoadBalancing load_balancing = LoadBalancing::RANDOM; + +private: + mutable size_t last_used = 0; /// Last used for round_robin policy. +}; + +} diff --git a/src/Common/ZooKeeper/ZooKeeper.cpp b/src/Common/ZooKeeper/ZooKeeper.cpp index 2f33888846b..b1f6269d128 100644 --- a/src/Common/ZooKeeper/ZooKeeper.cpp +++ b/src/Common/ZooKeeper/ZooKeeper.cpp @@ -46,38 +46,9 @@ static void check(Coordination::Error code, const std::string & path) throw KeeperException(code, path); } -static ZooKeeperLoadBalancing fromString(const std::string_view & str) -{ - static const std::unordered_map map = [] { - std::unordered_map res; - constexpr std::pair pairs[] - = {{"random", ZooKeeperLoadBalancing::RANDOM}, - {"nearest_hostname", ZooKeeperLoadBalancing::NEAREST_HOSTNAME}, - {"in_order", ZooKeeperLoadBalancing::IN_ORDER}, - {"first_or_random", ZooKeeperLoadBalancing::FIRST_OR_RANDOM}, - {"round_robin", ZooKeeperLoadBalancing::ROUND_ROBIN}}; - for (const auto & [name, val] : pairs) - res.emplace(name, val); - return res; - }(); - auto it = map.find(str); - if (it != map.end()) - return it->second; - String msg = "Unexpected value of ZooKeeperLoadBalancing: '" + String{str} + "'. Must be one of ["; - bool need_comma = false; - for (auto & name : map | boost::adaptors::map_keys) - { - if (std::exchange(need_comma, true)) - msg += ", "; - msg += "'" + String{name} + "'"; - } - msg += "]"; - throw DB::Exception(msg, DB::ErrorCodes::UNKNOWN_LOAD_BALANCING); -} - void ZooKeeper::init(const std::string & implementation_, const Strings & hosts_, const std::string & identity_, - int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, ZooKeeperLoadBalancing zookeeper_load_balancing_) + int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, const GetPriorityForLoadBalancing & get_priority_load_balancing_) { log = &Poco::Logger::get("ZooKeeper"); hosts = hosts_; @@ -86,7 +57,7 @@ void ZooKeeper::init(const std::string & implementation_, const Strings & hosts_ operation_timeout_ms = operation_timeout_ms_; chroot = chroot_; implementation = implementation_; - zookeeper_load_balancing = zookeeper_load_balancing_; + get_priority_load_balancing = get_priority_load_balancing_; if (implementation == "zookeeper") { @@ -185,44 +156,7 @@ void ZooKeeper::init(const std::string & implementation_, const Strings & hosts_ std::vector ZooKeeper::shuffleHosts() const { - std::vector hostname_differences; - hostname_differences.resize(hosts.size()); - const String & local_hostname = getFQDNOrHostName(); - for (size_t i = 0; i < hosts.size(); ++i) - { - const String & ip_or_hostname = hosts[i].substr(0, hosts[i].find_last_of(':')); - hostname_differences[i] = DB::getHostNameDifference(local_hostname, Poco::Net::DNS::resolve(ip_or_hostname).name()); - } - - std::function get_priority; - switch (ZooKeeperLoadBalancing(zookeeper_load_balancing)) - { - case ZooKeeperLoadBalancing::NEAREST_HOSTNAME: - get_priority = [&](size_t i) { return hostname_differences[i]; }; - break; - case ZooKeeperLoadBalancing::IN_ORDER: - get_priority = [](size_t i) { return i; }; - break; - case ZooKeeperLoadBalancing::RANDOM: - break; - case ZooKeeperLoadBalancing::FIRST_OR_RANDOM: - get_priority = [](size_t i) -> size_t { return i != 0; }; - break; - case ZooKeeperLoadBalancing::ROUND_ROBIN: - static size_t last_used = 0; - if (last_used >= hosts.size()) - last_used = 0; - ++last_used; - /* Consider hosts.size() equals to 5 - * last_used = 1 -> get_priority: 0 1 2 3 4 - * last_used = 2 -> get_priority: 4 0 1 2 3 - * last_used = 3 -> get_priority: 4 3 0 1 2 - * ... - * */ - get_priority = [this, last_used_value = last_used](size_t i) { ++i; return i < last_used_value ? hosts.size() - i : i - last_used_value; }; - break; - } - + std::function get_priority = get_priority_load_balancing.getPriorityFunc(); std::vector shuffle_hosts; for (size_t i = 0; i < hosts.size(); ++i) { @@ -246,21 +180,21 @@ std::vector ZooKeeper::shuffleHosts() const ZooKeeper::ZooKeeper(const std::string & hosts_string, const std::string & identity_, int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, const std::string & implementation_, - std::shared_ptr zk_log_, ZooKeeperLoadBalancing zookeeper_load_balancing_) + std::shared_ptr zk_log_, const GetPriorityForLoadBalancing & get_priority_load_balancing_) { zk_log = std::move(zk_log_); Strings hosts_strings; splitInto<','>(hosts_strings, hosts_string); - init(implementation_, hosts_strings, identity_, session_timeout_ms_, operation_timeout_ms_, chroot_, zookeeper_load_balancing_); + init(implementation_, hosts_strings, identity_, session_timeout_ms_, operation_timeout_ms_, chroot_, get_priority_load_balancing_); } ZooKeeper::ZooKeeper(const Strings & hosts_, const std::string & identity_, int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, const std::string & implementation_, - std::shared_ptr zk_log_, ZooKeeperLoadBalancing zookeeper_load_balancing_) + std::shared_ptr zk_log_, const GetPriorityForLoadBalancing & get_priority_load_balancing_) { zk_log = std::move(zk_log_); - init(implementation_, hosts_, identity_, session_timeout_ms_, operation_timeout_ms_, chroot_, zookeeper_load_balancing_); + init(implementation_, hosts_, identity_, session_timeout_ms_, operation_timeout_ms_, chroot_, get_priority_load_balancing_); } struct ZooKeeperArgs @@ -273,7 +207,6 @@ struct ZooKeeperArgs session_timeout_ms = Coordination::DEFAULT_SESSION_TIMEOUT_MS; operation_timeout_ms = Coordination::DEFAULT_OPERATION_TIMEOUT_MS; implementation = "zookeeper"; - zookeeper_load_balancing = ZooKeeperLoadBalancing::RANDOM; for (const auto & key : keys) { if (startsWith(key, "node")) @@ -306,7 +239,7 @@ struct ZooKeeperArgs } else if (key == "zookeeper_load_balancing") { - zookeeper_load_balancing = fromString(config.getString(config_name + "." + key)); + get_priority_load_balancing.load_balancing = DB::SettingFieldLoadBalancingTraits::fromString(config.getString(config_name + "." + key)); } else throw KeeperException(std::string("Unknown key ") + key + " in config file", Coordination::Error::ZBADARGUMENTS); @@ -319,6 +252,24 @@ struct ZooKeeperArgs if (chroot.back() == '/') chroot.pop_back(); } + + /// init get_priority_load_balancing + get_priority_load_balancing.hostname_differences.resize(hosts.size()); + const String & local_hostname = getFQDNOrHostName(); + for (size_t i = 0; i < hosts.size(); ++i) + { + const String & ip_or_hostname = hosts[i].substr(0, hosts[i].find_last_of(':')); + try + { + get_priority_load_balancing.hostname_differences[i] = DB::getHostNameDifference(local_hostname, Poco::Net::DNS::resolve(ip_or_hostname).name()); + } + catch (...) + { + /// There may be HostNotFoundException or DNSException, these exceptions will be processed later. + LOG_ERROR(&Poco::Logger::get("ZooKeeperArgs"), "Cannot use ZooKeeper host {}, hostname differences will be set to the maximum value", hosts[i]); + } + } + get_priority_load_balancing.pool_size = hosts.size(); } Strings hosts; @@ -327,14 +278,14 @@ struct ZooKeeperArgs int operation_timeout_ms; std::string chroot; std::string implementation; - ZooKeeperLoadBalancing zookeeper_load_balancing; + GetPriorityForLoadBalancing get_priority_load_balancing; }; ZooKeeper::ZooKeeper(const Poco::Util::AbstractConfiguration & config, const std::string & config_name, std::shared_ptr zk_log_) : zk_log(std::move(zk_log_)) { ZooKeeperArgs args(config, config_name); - init(args.implementation, args.hosts, args.identity, args.session_timeout_ms, args.operation_timeout_ms, args.chroot, args.zookeeper_load_balancing); + init(args.implementation, args.hosts, args.identity, args.session_timeout_ms, args.operation_timeout_ms, args.chroot, args.get_priority_load_balancing); } bool ZooKeeper::configChanged(const Poco::Util::AbstractConfiguration & config, const std::string & config_name) const @@ -345,8 +296,11 @@ bool ZooKeeper::configChanged(const Poco::Util::AbstractConfiguration & config, if (args.implementation == implementation && implementation == "testkeeper") return false; - return std::tie(args.implementation, args.hosts, args.identity, args.session_timeout_ms, args.operation_timeout_ms, args.chroot, args.zookeeper_load_balancing) - != std::tie(implementation, hosts, identity, session_timeout_ms, operation_timeout_ms, chroot, zookeeper_load_balancing); + if (args.get_priority_load_balancing != get_priority_load_balancing) + return true; + + return std::tie(args.implementation, args.hosts, args.identity, args.session_timeout_ms, args.operation_timeout_ms, args.chroot) + != std::tie(implementation, hosts, identity, session_timeout_ms, operation_timeout_ms, chroot); } @@ -849,7 +803,7 @@ bool ZooKeeper::waitForDisappear(const std::string & path, const WaitCondition & ZooKeeperPtr ZooKeeper::startNewSession() const { - return std::make_shared(hosts, identity, session_timeout_ms, operation_timeout_ms, chroot, implementation, zk_log, zookeeper_load_balancing); + return std::make_shared(hosts, identity, session_timeout_ms, operation_timeout_ms, chroot, implementation, zk_log, get_priority_load_balancing); } diff --git a/src/Common/ZooKeeper/ZooKeeper.h b/src/Common/ZooKeeper/ZooKeeper.h index c992ffe3a43..94232aeac86 100644 --- a/src/Common/ZooKeeper/ZooKeeper.h +++ b/src/Common/ZooKeeper/ZooKeeper.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -61,22 +62,7 @@ private: std::minstd_rand rng = std::minstd_rand(randomSeed()); }; -enum class ZooKeeperLoadBalancing -{ - /// Randomly select one from the zookeeper nodes. - RANDOM = 0, - /// Choose one from the zookeeper node that has the least - /// number of characters different from the hostname of the local host - NEAREST_HOSTNAME, - /// Select one from the zookeeper node configuration in order. - IN_ORDER, - /// If the first node cannot be connected, - /// one will be randomly selected from other nodes. - FIRST_OR_RANDOM, - /// Round robin from the node configured by zookeeper. - ROUND_ROBIN, -}; - +using GetPriorityForLoadBalancing = DB::GetPriorityForLoadBalancing; /// ZooKeeper session. The interface is substantially different from the usual libzookeeper API. /// @@ -99,7 +85,7 @@ public: const std::string & chroot_ = "", const std::string & implementation_ = "zookeeper", std::shared_ptr zk_log_ = nullptr, - ZooKeeperLoadBalancing zookeeper_load_balancing_ = ZooKeeperLoadBalancing::RANDOM); + const GetPriorityForLoadBalancing & get_priority_load_balancing_ = {}); ZooKeeper(const Strings & hosts_, const std::string & identity_ = "", int32_t session_timeout_ms_ = Coordination::DEFAULT_SESSION_TIMEOUT_MS, @@ -107,7 +93,7 @@ public: const std::string & chroot_ = "", const std::string & implementation_ = "zookeeper", std::shared_ptr zk_log_ = nullptr, - ZooKeeperLoadBalancing zookeeper_load_balancing_ = ZooKeeperLoadBalancing::RANDOM); + const GetPriorityForLoadBalancing & get_priority_load_balancing_ = {}); /** Config of the form: @@ -328,7 +314,7 @@ private: friend class EphemeralNodeHolder; void init(const std::string & implementation_, const Strings & hosts_, const std::string & identity_, - int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, ZooKeeperLoadBalancing zookeeper_load_balancing_); + int32_t session_timeout_ms_, int32_t operation_timeout_ms_, const std::string & chroot_, const GetPriorityForLoadBalancing & get_priority_load_balancing_); /// The following methods don't any throw exceptions but return error codes. Coordination::Error createImpl(const std::string & path, const std::string & data, int32_t mode, std::string & path_created); @@ -354,7 +340,8 @@ private: Poco::Logger * log = nullptr; std::shared_ptr zk_log; - ZooKeeperLoadBalancing zookeeper_load_balancing; + + GetPriorityForLoadBalancing get_priority_load_balancing; AtomicStopwatch session_uptime; }; diff --git a/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_in_order.xml b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_in_order.xml index bbed71532aa..ebd266d80b0 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_in_order.xml +++ b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_in_order.xml @@ -1,7 +1,7 @@ - random + in_order zoo1 2181 @@ -15,6 +15,5 @@ 2181 3000 - /root_a diff --git a/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_round_robin.xml b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_round_robin.xml new file mode 100644 index 00000000000..3b64d629e6e --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_round_robin.xml @@ -0,0 +1,19 @@ + + + + round_robin + + zoo1 + 2181 + + + zoo2 + 2181 + + + zoo3 + 2181 + + 3000 + + diff --git a/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_log.xml b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_log.xml new file mode 100644 index 00000000000..a70cbc3ecc2 --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_log.xml @@ -0,0 +1,7 @@ + + + system + zookeeper_log
+ 7500 +
+
diff --git a/tests/integration/test_zookeeper_config_load_balancing/test.py b/tests/integration/test_zookeeper_config_load_balancing/test.py index 95d9db27a7d..951dd7f12b4 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test.py @@ -3,24 +3,19 @@ import pytest import logging from helpers.cluster import ClickHouseCluster -cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_root_a.xml') +cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_in_order.xml') node1 = cluster.add_instance('node1', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_root_a.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) node2 = cluster.add_instance('node2', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_root_a.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) node3 = cluster.add_instance('node3', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_root_b.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) -def create_zk_roots(zk): - zk.ensure_path('/root_a') - zk.ensure_path('/root_b') - logging.debug(f"Create ZK roots:{zk.get_children('/')}") @pytest.fixture(scope="module", autouse=True) def started_cluster(): try: - cluster.add_zookeeper_startup_command(create_zk_roots) cluster.start() yield cluster @@ -28,30 +23,109 @@ def started_cluster(): finally: cluster.shutdown() -def test_chroot_with_same_root(started_cluster): - for i, node in enumerate([node1, node2]): +def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): + start = time.time() + while time.time() - start < timeout: + try: + for instance in zk_nodes: + conn = started_cluster.get_kazoo_client(instance) + conn.get_children('/') + print("All instances of ZooKeeper started") + return + except Exception as ex: + print(("Can't connect to ZooKeeper " + str(ex))) + time.sleep(0.5) + +def test_in_order(started_cluster): + zoo1_ip = started_cluster.get_instance_ip("zoo1") + for i, node in enumerate([node1, node3]): node.query('DROP TABLE IF EXISTS simple SYNC') node.query(''' CREATE TABLE simple (date Date, id UInt32) ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); '''.format(replica=node.name)) - for j in range(2): # Second insert to test deduplication - node.query("INSERT INTO simple VALUES ({0}, {0})".format(i)) - time.sleep(1) + time.sleep(5) + assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo1_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo1_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert node1.query('select count() from simple').strip() == '2' - assert node2.query('select count() from simple').strip() == '2' -def test_chroot_with_different_root(started_cluster): - for i, node in [(1, node1), (3, node3)]: - node.query('DROP TABLE IF EXISTS simple_different SYNC') - node.query(''' - CREATE TABLE simple_different (date Date, id UInt32) - ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple_different', '{replica}', date, id, 8192); - '''.format(replica=node.name)) - for j in range(2): # Second insert to test deduplication - node.query("INSERT INTO simple_different VALUES ({0}, {0})".format(i)) - - assert node1.query('select count() from simple_different').strip() == '1' - assert node3.query('select count() from simple_different').strip() == '1' +# def test_round_robin(started_cluster): +# new_config = """ +# +# +# round_robin +# +# zoo1 +# 2181 +# +# +# zoo2 +# 2181 +# +# +# zoo3 +# 2181 +# +# 3000 +# +# +# """ +# for i, node in enumerate([node1, node3]): +# node.replace_config("/etc/clickhouse-server/conf.d/zookeeper.xml", new_config) +# node.query("SYSTEM RELOAD CONFIG") +# +# started_cluster.stop_zookeeper_nodes(["zoo1"]) +# zoo2_ip = started_cluster.get_instance_ip("zoo2") +# for i, node in enumerate([node1, node3]): +# node.query('DROP TABLE IF EXISTS simple SYNC') +# node.query(''' +# CREATE TABLE simple (date Date, id UInt32) +# ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); +# '''.format(replica=node.name)) +# assert '::ffff:' + str(zoo2_ip) + '\n' == node.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') +# ## start zoo2, zoo3, table will be readonly too, because it only connect to zoo1 +# started_cluster.start_zookeeper_nodes(["zoo1"]) +# wait_zookeeper_node_to_start(started_cluster, ["zoo1"]) +# +# +# def test_nearest_hostname(started_cluster): +# new_config = """ +# +# +# nearest_hostname +# +# zoo1 +# 2181 +# +# +# zoo2 +# 2181 +# +# +# zoo3 +# 2181 +# +# 3000 +# +# +# """ +# for i, node in enumerate([node1, node3]): +# node.replace_config("/etc/clickhouse-server/conf.d/zookeeper.xml", new_config) +# node.query("SYSTEM RELOAD CONFIG") +# +# zoo1_ip = started_cluster.get_instance_ip("zoo1") +# zoo2_ip = started_cluster.get_instance_ip("zoo2") +# zoo3_ip = started_cluster.get_instance_ip("zoo3") +# +# for i, node in enumerate([node1, node3]): +# node.query('DROP TABLE IF EXISTS simple SYNC') +# node.query(''' +# CREATE TABLE simple (date Date, id UInt32) +# ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); +# '''.format(replica=node.name)) +# +# assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') +# assert '::ffff:' + str(zoo2_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') +# assert '::ffff:' + str(zoo3_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py b/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py new file mode 100644 index 00000000000..bee32205499 --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py @@ -0,0 +1,100 @@ +import time +import pytest +import logging +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_in_order.xml') + +node1 = cluster.add_instance('node1', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml", "configs/zookeeper_log.xml"]) +node2 = cluster.add_instance('node2', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml", "configs/zookeeper_log.xml"]) +node3 = cluster.add_instance('node3', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml", "configs/zookeeper_log.xml"]) + + +@pytest.fixture(scope="module", autouse=True) +def started_cluster(): + try: + cluster.start() + + yield cluster + + finally: + cluster.shutdown() + +def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): + start = time.time() + while time.time() - start < timeout: + try: + for instance in zk_nodes: + conn = started_cluster.get_kazoo_client(instance) + conn.get_children('/') + print("All instances of ZooKeeper started") + return + except Exception as ex: + print(("Can't connect to ZooKeeper " + str(ex))) + time.sleep(0.5) + + + +def test_round_robin(started_cluster): + + started_cluster.stop_zookeeper_nodes(["zoo1"]) + zoo2_ip = started_cluster.get_instance_ip("zoo2") + for i, node in enumerate([node1, node3]): + node.query('DROP TABLE IF EXISTS simple SYNC') + node.query(''' + CREATE TABLE simple (date Date, id UInt32) + ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); + '''.format(replica=node.name)) + + time.sleep(5) + assert '::ffff:' + str(zoo2_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo2_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo2_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + + ## start zoo2, zoo3, table will be readonly too, because it only connect to zoo1 + started_cluster.start_zookeeper_nodes(["zoo1"]) + wait_zookeeper_node_to_start(started_cluster, ["zoo1"]) + + +# def test_nearest_hostname(started_cluster): +# new_config = """ +# +# +# nearest_hostname +# +# zoo1 +# 2181 +# +# +# zoo2 +# 2181 +# +# +# zoo3 +# 2181 +# +# 3000 +# +# +# """ +# for i, node in enumerate([node1, node3]): +# node.replace_config("/etc/clickhouse-server/conf.d/zookeeper.xml", new_config) +# node.query("SYSTEM RELOAD CONFIG") +# +# zoo1_ip = started_cluster.get_instance_ip("zoo1") +# zoo2_ip = started_cluster.get_instance_ip("zoo2") +# zoo3_ip = started_cluster.get_instance_ip("zoo3") +# +# for i, node in enumerate([node1, node3]): +# node.query('DROP TABLE IF EXISTS simple SYNC') +# node.query(''' +# CREATE TABLE simple (date Date, id UInt32) +# ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); +# '''.format(replica=node.name)) +# +# assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') +# assert '::ffff:' + str(zoo2_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') +# assert '::ffff:' + str(zoo3_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') From 2110a0fbd1a16cf4fa5f96cb9d7fff16b3aec034 Mon Sep 17 00:00:00 2001 From: avogar Date: Thu, 21 Oct 2021 08:43:06 +0300 Subject: [PATCH 0078/1647] Add version mark in backward-incompatible tag --- tests/clickhouse-test | 26 ++++++++++++++++++- .../01942_create_table_with_sample.sql | 2 ++ .../01943_non_deterministic_order_key.sql | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index 51b4bce0616..fed5e1fa5cb 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -57,6 +57,8 @@ MAX_RETRIES = 3 TEST_FILE_EXTENSIONS = ['.sql', '.sql.j2', '.sh', '.py', '.expect'] +VERSION_PATTERN = r"^((?:\d+\.)?(?:\d+\.)?(?:\d+\.)?\d+)$" + class HTTPError(Exception): def __init__(self, message=None, code=None): self.message = message @@ -406,6 +408,28 @@ class TestCase: self.testcase_args = None self.runs_count = 0 + # Check if test contains tag "backward-incompatible" and we should skip it + def check_backward_incompatible_tag(self, tags) -> bool: + for tag in tags: + if tag.startswith("backward-incompatible"): + split = tag.split(':') + + # If version is not specified in tag, always skip this test. + if len(split) == 1: + return True + version_from_tag = split[1] + + # Check if extracted string from tag is a real ClickHouse version, if not - always skip test. + if re.match(VERSION_PATTERN, version_from_tag) is None: + return True + + server_version = str(clickhouse_execute(args, "SELECT version()").decode()) + # If server version is less or equal from the version specified in tag, we should skip this test. + if list(map(int, server_version.split('.'))) <= list(map(int, version_from_tag.split('.'))): + return True + + return False + # should skip test, should increment skipped_total, skip reason def should_skip_test(self, suite) -> Optional[FailureReason]: tags = self.tags @@ -438,7 +462,7 @@ class TestCase: elif tags and ('no-replicated-database' in tags) and args.replicated_database: return FailureReason.REPLICATED_DB - elif tags and ('backward-incompatible' in tags) and args.backward_compatibility_check: + elif args.backward_compatibility_check and self.check_backward_incompatible_tag(tags): return FailureReason.BACKWARD_INCOMPATIBLE elif tags: diff --git a/tests/queries/0_stateless/01942_create_table_with_sample.sql b/tests/queries/0_stateless/01942_create_table_with_sample.sql index 6320edd7a31..bd14baf2c8f 100644 --- a/tests/queries/0_stateless/01942_create_table_with_sample.sql +++ b/tests/queries/0_stateless/01942_create_table_with_sample.sql @@ -1,3 +1,5 @@ +-- Tags: backward-incompatible:21.9.1.1 + CREATE TABLE IF NOT EXISTS sample_incorrect (`x` UUID) ENGINE = MergeTree diff --git a/tests/queries/0_stateless/01943_non_deterministic_order_key.sql b/tests/queries/0_stateless/01943_non_deterministic_order_key.sql index 0929dcda601..8a949cd36de 100644 --- a/tests/queries/0_stateless/01943_non_deterministic_order_key.sql +++ b/tests/queries/0_stateless/01943_non_deterministic_order_key.sql @@ -1,4 +1,4 @@ --- Tags: backward-incompatible +-- Tags: backward-incompatible:21.9.1.1 CREATE TABLE a (number UInt64) ENGINE = MergeTree ORDER BY if(now() > toDateTime('2020-06-01 13:31:40'), toInt64(number), -number); -- { serverError 36 } CREATE TABLE b (number UInt64) ENGINE = MergeTree ORDER BY now() > toDateTime(number); -- { serverError 36 } From e6cf9605a5b88345b8387647dc893bfdbb8d5a3a Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Thu, 21 Oct 2021 15:46:34 +0800 Subject: [PATCH 0079/1647] Refactor and add test. --- src/Client/ConnectionPoolWithFailover.h | 3 - src/Common/ZooKeeper/ZooKeeper.cpp | 12 +- .../remote_servers_nearest_hostname.xml | 23 +++ .../zookeeper_config_first_or_random.xml | 19 +++ .../zookeeper_config_nearest_hostname.xml | 19 +++ .../test.py | 131 ------------------ .../test_first_or_random.py | 53 +++++++ .../test_in_order.py | 53 +++++++ .../test_nearest_hostname.py | 56 ++++++++ .../test_round_robin.py | 47 +------ 10 files changed, 229 insertions(+), 187 deletions(-) create mode 100644 tests/integration/test_zookeeper_config_load_balancing/configs/remote_servers_nearest_hostname.xml create mode 100644 tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_first_or_random.xml create mode 100644 tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_nearest_hostname.xml delete mode 100644 tests/integration/test_zookeeper_config_load_balancing/test.py create mode 100644 tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py create mode 100644 tests/integration/test_zookeeper_config_load_balancing/test_in_order.py create mode 100644 tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py diff --git a/src/Client/ConnectionPoolWithFailover.h b/src/Client/ConnectionPoolWithFailover.h index 3c838459733..0ec02cc48de 100644 --- a/src/Client/ConnectionPoolWithFailover.h +++ b/src/Client/ConnectionPoolWithFailover.h @@ -112,9 +112,6 @@ private: private: GetPriorityForLoadBalancing get_priority_load_balancing; -// std::vector hostname_differences; /// Distances from name of this host to the names of hosts of pools. -// size_t last_used = 0; /// Last used for round_robin policy. -// LoadBalancing default_load_balancing; }; using ConnectionPoolWithFailoverPtr = std::shared_ptr; diff --git a/src/Common/ZooKeeper/ZooKeeper.cpp b/src/Common/ZooKeeper/ZooKeeper.cpp index b1f6269d128..30e13c982ec 100644 --- a/src/Common/ZooKeeper/ZooKeeper.cpp +++ b/src/Common/ZooKeeper/ZooKeeper.cpp @@ -258,16 +258,8 @@ struct ZooKeeperArgs const String & local_hostname = getFQDNOrHostName(); for (size_t i = 0; i < hosts.size(); ++i) { - const String & ip_or_hostname = hosts[i].substr(0, hosts[i].find_last_of(':')); - try - { - get_priority_load_balancing.hostname_differences[i] = DB::getHostNameDifference(local_hostname, Poco::Net::DNS::resolve(ip_or_hostname).name()); - } - catch (...) - { - /// There may be HostNotFoundException or DNSException, these exceptions will be processed later. - LOG_ERROR(&Poco::Logger::get("ZooKeeperArgs"), "Cannot use ZooKeeper host {}, hostname differences will be set to the maximum value", hosts[i]); - } + const String & node_host = hosts[i].substr(0, hosts[i].find_last_of(':')); + get_priority_load_balancing.hostname_differences[i] = DB::getHostNameDifference(local_hostname, node_host); } get_priority_load_balancing.pool_size = hosts.size(); } diff --git a/tests/integration/test_zookeeper_config_load_balancing/configs/remote_servers_nearest_hostname.xml b/tests/integration/test_zookeeper_config_load_balancing/configs/remote_servers_nearest_hostname.xml new file mode 100644 index 00000000000..62f361049c9 --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/configs/remote_servers_nearest_hostname.xml @@ -0,0 +1,23 @@ + + + + + + nod1 + 9000 + + + + nod2 + 9000 + + + + nod3 + 9000 + + + + + + diff --git a/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_first_or_random.xml b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_first_or_random.xml new file mode 100644 index 00000000000..9688480fa90 --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_first_or_random.xml @@ -0,0 +1,19 @@ + + + + first_or_random + + zoo1 + 2181 + + + zoo2 + 2181 + + + zoo3 + 2181 + + 3000 + + diff --git a/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_nearest_hostname.xml b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_nearest_hostname.xml new file mode 100644 index 00000000000..265ebe05fab --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_config_nearest_hostname.xml @@ -0,0 +1,19 @@ + + + + nearest_hostname + + zoo1 + 2181 + + + zoo2 + 2181 + + + zoo3 + 2181 + + 3000 + + diff --git a/tests/integration/test_zookeeper_config_load_balancing/test.py b/tests/integration/test_zookeeper_config_load_balancing/test.py deleted file mode 100644 index 951dd7f12b4..00000000000 --- a/tests/integration/test_zookeeper_config_load_balancing/test.py +++ /dev/null @@ -1,131 +0,0 @@ -import time -import pytest -import logging -from helpers.cluster import ClickHouseCluster - -cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_in_order.xml') - -node1 = cluster.add_instance('node1', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) -node2 = cluster.add_instance('node2', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) -node3 = cluster.add_instance('node3', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) - - -@pytest.fixture(scope="module", autouse=True) -def started_cluster(): - try: - cluster.start() - - yield cluster - - finally: - cluster.shutdown() - -def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): - start = time.time() - while time.time() - start < timeout: - try: - for instance in zk_nodes: - conn = started_cluster.get_kazoo_client(instance) - conn.get_children('/') - print("All instances of ZooKeeper started") - return - except Exception as ex: - print(("Can't connect to ZooKeeper " + str(ex))) - time.sleep(0.5) - -def test_in_order(started_cluster): - zoo1_ip = started_cluster.get_instance_ip("zoo1") - for i, node in enumerate([node1, node3]): - node.query('DROP TABLE IF EXISTS simple SYNC') - node.query(''' - CREATE TABLE simple (date Date, id UInt32) - ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); - '''.format(replica=node.name)) - - time.sleep(5) - assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo1_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo1_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - - -# def test_round_robin(started_cluster): -# new_config = """ -# -# -# round_robin -# -# zoo1 -# 2181 -# -# -# zoo2 -# 2181 -# -# -# zoo3 -# 2181 -# -# 3000 -# -# -# """ -# for i, node in enumerate([node1, node3]): -# node.replace_config("/etc/clickhouse-server/conf.d/zookeeper.xml", new_config) -# node.query("SYSTEM RELOAD CONFIG") -# -# started_cluster.stop_zookeeper_nodes(["zoo1"]) -# zoo2_ip = started_cluster.get_instance_ip("zoo2") -# for i, node in enumerate([node1, node3]): -# node.query('DROP TABLE IF EXISTS simple SYNC') -# node.query(''' -# CREATE TABLE simple (date Date, id UInt32) -# ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); -# '''.format(replica=node.name)) -# assert '::ffff:' + str(zoo2_ip) + '\n' == node.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') -# ## start zoo2, zoo3, table will be readonly too, because it only connect to zoo1 -# started_cluster.start_zookeeper_nodes(["zoo1"]) -# wait_zookeeper_node_to_start(started_cluster, ["zoo1"]) -# -# -# def test_nearest_hostname(started_cluster): -# new_config = """ -# -# -# nearest_hostname -# -# zoo1 -# 2181 -# -# -# zoo2 -# 2181 -# -# -# zoo3 -# 2181 -# -# 3000 -# -# -# """ -# for i, node in enumerate([node1, node3]): -# node.replace_config("/etc/clickhouse-server/conf.d/zookeeper.xml", new_config) -# node.query("SYSTEM RELOAD CONFIG") -# -# zoo1_ip = started_cluster.get_instance_ip("zoo1") -# zoo2_ip = started_cluster.get_instance_ip("zoo2") -# zoo3_ip = started_cluster.get_instance_ip("zoo3") -# -# for i, node in enumerate([node1, node3]): -# node.query('DROP TABLE IF EXISTS simple SYNC') -# node.query(''' -# CREATE TABLE simple (date Date, id UInt32) -# ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); -# '''.format(replica=node.name)) -# -# assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') -# assert '::ffff:' + str(zoo2_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') -# assert '::ffff:' + str(zoo3_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py b/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py new file mode 100644 index 00000000000..5d510ae3da4 --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py @@ -0,0 +1,53 @@ +import time +import pytest +import logging +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_first_or_random.xml') + +node1 = cluster.add_instance('node1', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml", "configs/zookeeper_log.xml"]) +node2 = cluster.add_instance('node2', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml", "configs/zookeeper_log.xml"]) +node3 = cluster.add_instance('node3', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml", "configs/zookeeper_log.xml"]) + + +@pytest.fixture(scope="module", autouse=True) +def started_cluster(): + try: + cluster.start() + + yield cluster + + finally: + cluster.shutdown() + +def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): + start = time.time() + while time.time() - start < timeout: + try: + for instance in zk_nodes: + conn = started_cluster.get_kazoo_client(instance) + conn.get_children('/') + print("All instances of ZooKeeper started") + return + except Exception as ex: + print(("Can't connect to ZooKeeper " + str(ex))) + time.sleep(0.5) + +def test_first_or_random(started_cluster): + wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) + time.sleep(2) + zoo1_ip = started_cluster.get_instance_ip("zoo1") + for i, node in enumerate([node1, node3]): + node.query('DROP TABLE IF EXISTS simple SYNC') + node.query(''' + CREATE TABLE simple (date Date, id UInt32) + ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); + '''.format(replica=node.name)) + + time.sleep(5) + assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo1_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo1_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py b/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py new file mode 100644 index 00000000000..88143116170 --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py @@ -0,0 +1,53 @@ +import time +import pytest +import logging +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_in_order.xml') + +node1 = cluster.add_instance('node1', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) +node2 = cluster.add_instance('node2', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) +node3 = cluster.add_instance('node3', with_zookeeper=True, + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) + + +@pytest.fixture(scope="module", autouse=True) +def started_cluster(): + try: + cluster.start() + + yield cluster + + finally: + cluster.shutdown() + +def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): + start = time.time() + while time.time() - start < timeout: + try: + for instance in zk_nodes: + conn = started_cluster.get_kazoo_client(instance) + conn.get_children('/') + print("All instances of ZooKeeper started") + return + except Exception as ex: + print(("Can't connect to ZooKeeper " + str(ex))) + time.sleep(0.5) + +def test_in_order(started_cluster): + wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) + time.sleep(2) + zoo1_ip = started_cluster.get_instance_ip("zoo1") + for i, node in enumerate([node1, node3]): + node.query('DROP TABLE IF EXISTS simple SYNC') + node.query(''' + CREATE TABLE simple (date Date, id UInt32) + ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); + '''.format(replica=node.name)) + + time.sleep(5) + assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo1_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo1_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py b/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py new file mode 100644 index 00000000000..79fa61c0104 --- /dev/null +++ b/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py @@ -0,0 +1,56 @@ +import time +import pytest +import logging +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_nearest_hostname.xml') + +node1 = cluster.add_instance('nod1', with_zookeeper=True, + main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml", "configs/zookeeper_log.xml"]) +node2 = cluster.add_instance('nod2', with_zookeeper=True, + main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml", "configs/zookeeper_log.xml"]) +node3 = cluster.add_instance('nod3', with_zookeeper=True, + main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml", "configs/zookeeper_log.xml"]) + + +@pytest.fixture(scope="module", autouse=True) +def started_cluster(): + try: + cluster.start() + + yield cluster + + finally: + cluster.shutdown() + +def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): + start = time.time() + while time.time() - start < timeout: + try: + for instance in zk_nodes: + conn = started_cluster.get_kazoo_client(instance) + conn.get_children('/') + print("All instances of ZooKeeper started") + return + except Exception as ex: + print(("Can't connect to ZooKeeper " + str(ex))) + time.sleep(0.5) + +def test_nearest_hostname(started_cluster): + wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) + time.sleep(2) + zoo1_ip = started_cluster.get_instance_ip("zoo1") + zoo2_ip = started_cluster.get_instance_ip("zoo2") + zoo3_ip = started_cluster.get_instance_ip("zoo3") + + for i, node in enumerate([node1, node3]): + node.query('DROP TABLE IF EXISTS simple SYNC') + node.query(''' + CREATE TABLE simple (date Date, id UInt32) + ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); + '''.format(replica=node.name)) + + time.sleep(5) + assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo2_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '::ffff:' + str(zoo3_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py b/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py index bee32205499..f447f929d38 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py @@ -3,7 +3,7 @@ import pytest import logging from helpers.cluster import ClickHouseCluster -cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_in_order.xml') +cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_round_robin.xml') node1 = cluster.add_instance('node1', with_zookeeper=True, main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml", "configs/zookeeper_log.xml"]) @@ -39,8 +39,9 @@ def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): def test_round_robin(started_cluster): - + wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) started_cluster.stop_zookeeper_nodes(["zoo1"]) + time.sleep(10) zoo2_ip = started_cluster.get_instance_ip("zoo2") for i, node in enumerate([node1, node3]): node.query('DROP TABLE IF EXISTS simple SYNC') @@ -50,6 +51,7 @@ def test_round_robin(started_cluster): '''.format(replica=node.name)) time.sleep(5) + print("zoo2", zoo2_ip) assert '::ffff:' + str(zoo2_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') assert '::ffff:' + str(zoo2_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') assert '::ffff:' + str(zoo2_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') @@ -57,44 +59,3 @@ def test_round_robin(started_cluster): ## start zoo2, zoo3, table will be readonly too, because it only connect to zoo1 started_cluster.start_zookeeper_nodes(["zoo1"]) wait_zookeeper_node_to_start(started_cluster, ["zoo1"]) - - -# def test_nearest_hostname(started_cluster): -# new_config = """ -# -# -# nearest_hostname -# -# zoo1 -# 2181 -# -# -# zoo2 -# 2181 -# -# -# zoo3 -# 2181 -# -# 3000 -# -# -# """ -# for i, node in enumerate([node1, node3]): -# node.replace_config("/etc/clickhouse-server/conf.d/zookeeper.xml", new_config) -# node.query("SYSTEM RELOAD CONFIG") -# -# zoo1_ip = started_cluster.get_instance_ip("zoo1") -# zoo2_ip = started_cluster.get_instance_ip("zoo2") -# zoo3_ip = started_cluster.get_instance_ip("zoo3") -# -# for i, node in enumerate([node1, node3]): -# node.query('DROP TABLE IF EXISTS simple SYNC') -# node.query(''' -# CREATE TABLE simple (date Date, id UInt32) -# ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); -# '''.format(replica=node.name)) -# -# assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') -# assert '::ffff:' + str(zoo2_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') -# assert '::ffff:' + str(zoo3_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') From 5c34e24f4948cab5a49ab9fb7c7a87c29435a9ef Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Thu, 21 Oct 2021 15:57:21 +0800 Subject: [PATCH 0080/1647] Fix code style --- src/Common/GetPriorityForLoadBalancing.cpp | 3 ++- src/Common/ZooKeeper/ZooKeeper.cpp | 1 - src/Common/ZooKeeper/ZooKeeper.h | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Common/GetPriorityForLoadBalancing.cpp b/src/Common/GetPriorityForLoadBalancing.cpp index ae621d9e75c..15ba4e2534c 100644 --- a/src/Common/GetPriorityForLoadBalancing.cpp +++ b/src/Common/GetPriorityForLoadBalancing.cpp @@ -29,7 +29,8 @@ std::function GetPriorityForLoadBalancing::getPriorityFunc * last_used = 3 -> get_priority: 4 3 0 1 2 * ... * */ - get_priority = [&](size_t i) { + get_priority = [&](size_t i) + { ++i; return i < last_used ? pool_size - i : i - last_used; }; diff --git a/src/Common/ZooKeeper/ZooKeeper.cpp b/src/Common/ZooKeeper/ZooKeeper.cpp index 30e13c982ec..2a964ceba89 100644 --- a/src/Common/ZooKeeper/ZooKeeper.cpp +++ b/src/Common/ZooKeeper/ZooKeeper.cpp @@ -26,7 +26,6 @@ namespace ErrorCodes { extern const int LOGICAL_ERROR; extern const int NOT_IMPLEMENTED; - extern const int UNKNOWN_LOAD_BALANCING; } } diff --git a/src/Common/ZooKeeper/ZooKeeper.h b/src/Common/ZooKeeper/ZooKeeper.h index 94232aeac86..392c0427545 100644 --- a/src/Common/ZooKeeper/ZooKeeper.h +++ b/src/Common/ZooKeeper/ZooKeeper.h @@ -43,7 +43,6 @@ constexpr size_t MULTI_BATCH_SIZE = 100; struct ShuffleHost { String host; - /// Priority from the GetPriorityFunc. Int64 priority = 0; UInt32 random = 0; From 6cd0f18bfda86578232d0ace9cd521bb02b65481 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Thu, 21 Oct 2021 17:59:24 +0800 Subject: [PATCH 0081/1647] Fix PVS check --- src/Client/ConnectionPoolWithFailover.cpp | 2 +- src/Common/GetPriorityForLoadBalancing.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Client/ConnectionPoolWithFailover.cpp b/src/Client/ConnectionPoolWithFailover.cpp index ecfc6bd5c08..0e213dc6700 100644 --- a/src/Client/ConnectionPoolWithFailover.cpp +++ b/src/Client/ConnectionPoolWithFailover.cpp @@ -29,7 +29,7 @@ ConnectionPoolWithFailover::ConnectionPoolWithFailover( time_t decrease_error_period_, size_t max_error_cap_) : Base(std::move(nested_pools_), decrease_error_period_, max_error_cap_, &Poco::Logger::get("ConnectionPoolWithFailover")) - , get_priority_load_balancing(load_balancing) + , get_priority_load_balancing(load_balancing, nested_pools.size()) { const std::string & local_hostname = getFQDNOrHostName(); diff --git a/src/Common/GetPriorityForLoadBalancing.h b/src/Common/GetPriorityForLoadBalancing.h index b845c2e7616..940ece2b0bc 100644 --- a/src/Common/GetPriorityForLoadBalancing.h +++ b/src/Common/GetPriorityForLoadBalancing.h @@ -8,7 +8,7 @@ namespace DB class GetPriorityForLoadBalancing { public: - GetPriorityForLoadBalancing(LoadBalancing load_balancing_) : load_balancing(load_balancing_) {} + GetPriorityForLoadBalancing(LoadBalancing load_balancing_, size_t pool_size_) : load_balancing(load_balancing_), pool_size(pool_size_) {} GetPriorityForLoadBalancing(){} bool operator!=(const GetPriorityForLoadBalancing & other) @@ -19,11 +19,11 @@ public: std::function getPriorityFunc() const; + LoadBalancing load_balancing = LoadBalancing::RANDOM; + std::vector hostname_differences; /// Distances from name of this host to the names of hosts of pools. size_t offset = 0; /// for first_or_random policy. - size_t pool_size; /// for round_robin policy. - - LoadBalancing load_balancing = LoadBalancing::RANDOM; + size_t pool_size = 0; /// for round_robin policy. private: mutable size_t last_used = 0; /// Last used for round_robin policy. From ca02f9757c74d22e1bea333def29b29de6e37af1 Mon Sep 17 00:00:00 2001 From: avogar Date: Thu, 21 Oct 2021 13:19:21 +0300 Subject: [PATCH 0082/1647] Chenge test 02097_default_dict_get_add_database a bit --- .../0_stateless/02097_default_dict_get_add_database.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/02097_default_dict_get_add_database.sql b/tests/queries/0_stateless/02097_default_dict_get_add_database.sql index af177566476..892389b3062 100644 --- a/tests/queries/0_stateless/02097_default_dict_get_add_database.sql +++ b/tests/queries/0_stateless/02097_default_dict_get_add_database.sql @@ -29,8 +29,8 @@ SOURCE(CLICKHOUSE(TABLE 'test_table')); DROP TABLE IF EXISTS test_table_default; CREATE TABLE test_table_default ( - data_1 DEFAULT dictGetUInt64('test_dictionary', 'data_column_1', toUInt64(0)), - data_2 DEFAULT dictGet(test_dictionary, 'data_column_2', toUInt64(0)) + data_1 DEFAULT dictGetUInt64('02097_db.test_dictionary', 'data_column_1', toUInt64(0)), + data_2 DEFAULT dictGet(02097_db.test_dictionary, 'data_column_2', toUInt64(0)) ) ENGINE=TinyLog; From d1891c2527258d6bbfd3b699ae5e836ed687b03c Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Fri, 22 Oct 2021 14:27:50 +0800 Subject: [PATCH 0083/1647] Fix build --- src/Common/ZooKeeper/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/ZooKeeper/CMakeLists.txt b/src/Common/ZooKeeper/CMakeLists.txt index d29fba53277..7510cd0755c 100644 --- a/src/Common/ZooKeeper/CMakeLists.txt +++ b/src/Common/ZooKeeper/CMakeLists.txt @@ -4,7 +4,7 @@ add_headers_and_sources(clickhouse_common_zookeeper .) add_library(clickhouse_common_zookeeper ${clickhouse_common_zookeeper_headers} ${clickhouse_common_zookeeper_sources}) -target_link_libraries (clickhouse_common_zookeeper PUBLIC clickhouse_common_io common PRIVATE string_utils) +target_link_libraries (clickhouse_common_zookeeper PUBLIC clickhouse_common_io common dbms PRIVATE string_utils) if (ENABLE_EXAMPLES) add_subdirectory(examples) From 4e49eba087c8b5fc0db7c7f7dd819e0b5a80701e Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Fri, 22 Oct 2021 20:23:25 +0800 Subject: [PATCH 0084/1647] Fix data race --- src/Client/ConnectionPoolWithFailover.cpp | 21 ++++++++++++++------- src/Common/GetPriorityForLoadBalancing.cpp | 6 +++--- src/Common/GetPriorityForLoadBalancing.h | 13 +++++-------- src/Common/ZooKeeper/ZooKeeper.cpp | 3 +-- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Client/ConnectionPoolWithFailover.cpp b/src/Client/ConnectionPoolWithFailover.cpp index 0e213dc6700..accde6b5475 100644 --- a/src/Client/ConnectionPoolWithFailover.cpp +++ b/src/Client/ConnectionPoolWithFailover.cpp @@ -29,7 +29,7 @@ ConnectionPoolWithFailover::ConnectionPoolWithFailover( time_t decrease_error_period_, size_t max_error_cap_) : Base(std::move(nested_pools_), decrease_error_period_, max_error_cap_, &Poco::Logger::get("ConnectionPoolWithFailover")) - , get_priority_load_balancing(load_balancing, nested_pools.size()) + , get_priority_load_balancing(load_balancing) { const std::string & local_hostname = getFQDNOrHostName(); @@ -50,12 +50,16 @@ IConnectionPool::Entry ConnectionPoolWithFailover::get(const ConnectionTimeouts return tryGetEntry(pool, timeouts, fail_message, settings); }; + GetPriorityForLoadBalancing get_priority_local(get_priority_load_balancing); + size_t offset = 0; + LoadBalancing load_balancing = get_priority_load_balancing.load_balancing; if (settings) { - get_priority_load_balancing.offset = settings->load_balancing_first_offset % nested_pools.size(); - get_priority_load_balancing.load_balancing = settings->load_balancing; + offset = settings->load_balancing_first_offset % nested_pools.size(); + load_balancing = LoadBalancing(settings->load_balancing); } - GetPriorityFunc get_priority = get_priority_load_balancing.getPriorityFunc(); + + GetPriorityFunc get_priority = get_priority_local.getPriorityFunc(load_balancing, offset, nested_pools.size()); UInt64 max_ignored_errors = settings ? settings->distributed_replica_max_ignored_errors.value : 0; bool fallback_to_stale_replicas = settings ? settings->fallback_to_stale_replicas_for_distributed_queries.value : true; @@ -148,12 +152,15 @@ std::vector ConnectionPoolWithFailover::g ConnectionPoolWithFailover::Base::GetPriorityFunc ConnectionPoolWithFailover::makeGetPriorityFunc(const Settings * settings) { + size_t offset = 0; + LoadBalancing load_balancing = get_priority_load_balancing.load_balancing; if (settings) { - get_priority_load_balancing.offset = settings->load_balancing_first_offset % nested_pools.size(); - get_priority_load_balancing.load_balancing = settings->load_balancing; + offset = settings->load_balancing_first_offset % nested_pools.size(); + load_balancing = LoadBalancing(settings->load_balancing); } - return get_priority_load_balancing.getPriorityFunc(); + + return get_priority_load_balancing.getPriorityFunc(load_balancing, offset, nested_pools.size()); } std::vector ConnectionPoolWithFailover::getManyImpl( diff --git a/src/Common/GetPriorityForLoadBalancing.cpp b/src/Common/GetPriorityForLoadBalancing.cpp index 15ba4e2534c..fa0eeb14bed 100644 --- a/src/Common/GetPriorityForLoadBalancing.cpp +++ b/src/Common/GetPriorityForLoadBalancing.cpp @@ -3,10 +3,10 @@ namespace DB { -std::function GetPriorityForLoadBalancing::getPriorityFunc() const +std::function GetPriorityForLoadBalancing::getPriorityFunc(LoadBalancing load_balance, size_t offset, size_t pool_size) const { std::function get_priority; - switch (load_balancing) + switch (load_balance) { case LoadBalancing::NEAREST_HOSTNAME: get_priority = [&](size_t i) { return hostname_differences[i]; }; @@ -17,7 +17,7 @@ std::function GetPriorityForLoadBalancing::getPriorityFunc case LoadBalancing::RANDOM: break; case LoadBalancing::FIRST_OR_RANDOM: - get_priority = [&](size_t i) -> size_t { return i != offset; }; + get_priority = [offset](size_t i) -> size_t { return i != offset; }; break; case LoadBalancing::ROUND_ROBIN: if (last_used >= pool_size) diff --git a/src/Common/GetPriorityForLoadBalancing.h b/src/Common/GetPriorityForLoadBalancing.h index 940ece2b0bc..a6b8c88bb73 100644 --- a/src/Common/GetPriorityForLoadBalancing.h +++ b/src/Common/GetPriorityForLoadBalancing.h @@ -8,22 +8,19 @@ namespace DB class GetPriorityForLoadBalancing { public: - GetPriorityForLoadBalancing(LoadBalancing load_balancing_, size_t pool_size_) : load_balancing(load_balancing_), pool_size(pool_size_) {} + GetPriorityForLoadBalancing(LoadBalancing load_balancing_) : load_balancing(load_balancing_) {} GetPriorityForLoadBalancing(){} bool operator!=(const GetPriorityForLoadBalancing & other) { - return offset != other.offset || pool_size != other.pool_size || load_balancing != other.load_balancing - || hostname_differences != other.hostname_differences; + return load_balancing != other.load_balancing || hostname_differences != other.hostname_differences; } - std::function getPriorityFunc() const; - - LoadBalancing load_balancing = LoadBalancing::RANDOM; + std::function getPriorityFunc(LoadBalancing load_balancing, size_t offset, size_t pool_size) const; std::vector hostname_differences; /// Distances from name of this host to the names of hosts of pools. - size_t offset = 0; /// for first_or_random policy. - size_t pool_size = 0; /// for round_robin policy. + + LoadBalancing load_balancing = LoadBalancing::RANDOM; private: mutable size_t last_used = 0; /// Last used for round_robin policy. diff --git a/src/Common/ZooKeeper/ZooKeeper.cpp b/src/Common/ZooKeeper/ZooKeeper.cpp index 2a964ceba89..5e43eda636c 100644 --- a/src/Common/ZooKeeper/ZooKeeper.cpp +++ b/src/Common/ZooKeeper/ZooKeeper.cpp @@ -155,7 +155,7 @@ void ZooKeeper::init(const std::string & implementation_, const Strings & hosts_ std::vector ZooKeeper::shuffleHosts() const { - std::function get_priority = get_priority_load_balancing.getPriorityFunc(); + std::function get_priority = get_priority_load_balancing.getPriorityFunc(get_priority_load_balancing.load_balancing, 0, hosts.size()); std::vector shuffle_hosts; for (size_t i = 0; i < hosts.size(); ++i) { @@ -260,7 +260,6 @@ struct ZooKeeperArgs const String & node_host = hosts[i].substr(0, hosts[i].find_last_of(':')); get_priority_load_balancing.hostname_differences[i] = DB::getHostNameDifference(local_hostname, node_host); } - get_priority_load_balancing.pool_size = hosts.size(); } Strings hosts; From 1710e5ea5989cbe9103df7551a98e8bbf20e1a7b Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Mon, 25 Oct 2021 19:10:54 +0800 Subject: [PATCH 0085/1647] Fix build --- src/CMakeLists.txt | 2 ++ src/Common/ZooKeeper/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 09aaa85c394..95f2051399d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -183,6 +183,8 @@ endmacro() add_object_library(clickhouse_access Access) add_object_library(clickhouse_backups Backups) add_object_library(clickhouse_core Core) +add_library (clickhouse_core_settings_enums Core/SettingsEnums.cpp) +target_link_libraries(clickhouse_core_settings_enums PRIVATE common clickhouse_common_io) add_object_library(clickhouse_core_mysql Core/MySQL) add_object_library(clickhouse_compression Compression) add_object_library(clickhouse_querypipeline QueryPipeline) diff --git a/src/Common/ZooKeeper/CMakeLists.txt b/src/Common/ZooKeeper/CMakeLists.txt index 7510cd0755c..5797a0a5e21 100644 --- a/src/Common/ZooKeeper/CMakeLists.txt +++ b/src/Common/ZooKeeper/CMakeLists.txt @@ -4,7 +4,7 @@ add_headers_and_sources(clickhouse_common_zookeeper .) add_library(clickhouse_common_zookeeper ${clickhouse_common_zookeeper_headers} ${clickhouse_common_zookeeper_sources}) -target_link_libraries (clickhouse_common_zookeeper PUBLIC clickhouse_common_io common dbms PRIVATE string_utils) +target_link_libraries (clickhouse_common_zookeeper PUBLIC clickhouse_core_settings_enums clickhouse_common_io common PRIVATE string_utils) if (ENABLE_EXAMPLES) add_subdirectory(examples) From bf9aebac90be71566b8cf8650dd16c56b478bb27 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Tue, 26 Oct 2021 12:45:09 +0800 Subject: [PATCH 0086/1647] Fix test and build --- src/Client/ConnectionPoolWithFailover.cpp | 3 +-- src/Common/GetPriorityForLoadBalancing.h | 2 +- .../test_first_or_random.py | 2 +- .../test_zookeeper_config_load_balancing/test_in_order.py | 2 +- .../test_nearest_hostname.py | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Client/ConnectionPoolWithFailover.cpp b/src/Client/ConnectionPoolWithFailover.cpp index accde6b5475..13d39980e1c 100644 --- a/src/Client/ConnectionPoolWithFailover.cpp +++ b/src/Client/ConnectionPoolWithFailover.cpp @@ -50,7 +50,6 @@ IConnectionPool::Entry ConnectionPoolWithFailover::get(const ConnectionTimeouts return tryGetEntry(pool, timeouts, fail_message, settings); }; - GetPriorityForLoadBalancing get_priority_local(get_priority_load_balancing); size_t offset = 0; LoadBalancing load_balancing = get_priority_load_balancing.load_balancing; if (settings) @@ -59,7 +58,7 @@ IConnectionPool::Entry ConnectionPoolWithFailover::get(const ConnectionTimeouts load_balancing = LoadBalancing(settings->load_balancing); } - GetPriorityFunc get_priority = get_priority_local.getPriorityFunc(load_balancing, offset, nested_pools.size()); + GetPriorityFunc get_priority = get_priority_load_balancing.getPriorityFunc(load_balancing, offset, nested_pools.size()); UInt64 max_ignored_errors = settings ? settings->distributed_replica_max_ignored_errors.value : 0; bool fallback_to_stale_replicas = settings ? settings->fallback_to_stale_replicas_for_distributed_queries.value : true; diff --git a/src/Common/GetPriorityForLoadBalancing.h b/src/Common/GetPriorityForLoadBalancing.h index a6b8c88bb73..4ec686188e4 100644 --- a/src/Common/GetPriorityForLoadBalancing.h +++ b/src/Common/GetPriorityForLoadBalancing.h @@ -16,7 +16,7 @@ public: return load_balancing != other.load_balancing || hostname_differences != other.hostname_differences; } - std::function getPriorityFunc(LoadBalancing load_balancing, size_t offset, size_t pool_size) const; + std::function getPriorityFunc(LoadBalancing load_balance, size_t offset, size_t pool_size) const; std::vector hostname_differences; /// Distances from name of this host to the names of hosts of pools. diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py b/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py index 5d510ae3da4..71084492b44 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py @@ -38,7 +38,7 @@ def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): def test_first_or_random(started_cluster): wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) - time.sleep(2) + time.sleep(10) zoo1_ip = started_cluster.get_instance_ip("zoo1") for i, node in enumerate([node1, node3]): node.query('DROP TABLE IF EXISTS simple SYNC') diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py b/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py index 88143116170..92ba927860c 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py @@ -38,7 +38,7 @@ def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): def test_in_order(started_cluster): wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) - time.sleep(2) + time.sleep(10) zoo1_ip = started_cluster.get_instance_ip("zoo1") for i, node in enumerate([node1, node3]): node.query('DROP TABLE IF EXISTS simple SYNC') diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py b/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py index 79fa61c0104..832af32bbae 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py @@ -38,7 +38,7 @@ def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): def test_nearest_hostname(started_cluster): wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) - time.sleep(2) + time.sleep(10) zoo1_ip = started_cluster.get_instance_ip("zoo1") zoo2_ip = started_cluster.get_instance_ip("zoo2") zoo3_ip = started_cluster.get_instance_ip("zoo3") From 4e53eb0e4e65f8d1da865db971f78955a39f5920 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Wed, 27 Oct 2021 11:29:31 +0800 Subject: [PATCH 0087/1647] empty commit From 5e5b6ade00a52d80db45c7734408ea6c413cbd38 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 27 Oct 2021 14:46:53 +0300 Subject: [PATCH 0088/1647] Update clickhouse-test --- tests/clickhouse-test | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index fed5e1fa5cb..bc260954246 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -409,8 +409,8 @@ class TestCase: self.runs_count = 0 # Check if test contains tag "backward-incompatible" and we should skip it - def check_backward_incompatible_tag(self, tags) -> bool: - for tag in tags: + def check_backward_incompatible_tag(self) -> bool: + for tag in self.tags: if tag.startswith("backward-incompatible"): split = tag.split(':') @@ -462,7 +462,7 @@ class TestCase: elif tags and ('no-replicated-database' in tags) and args.replicated_database: return FailureReason.REPLICATED_DB - elif args.backward_compatibility_check and self.check_backward_incompatible_tag(tags): + elif args.backward_compatibility_check and self.check_backward_incompatible_tag(): return FailureReason.BACKWARD_INCOMPATIBLE elif tags: From 49e03025dde6513921787d0ab9b56df27fb557b7 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Mon, 1 Nov 2021 18:26:50 +0800 Subject: [PATCH 0089/1647] better test --- .../configs/zookeeper_log.xml | 7 ---- .../test_first_or_random.py | 34 +++------------ .../test_in_order.py | 35 +++------------- .../test_nearest_hostname.py | 38 +++-------------- .../test_round_robin.py | 41 ++++--------------- 5 files changed, 26 insertions(+), 129 deletions(-) delete mode 100644 tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_log.xml diff --git a/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_log.xml b/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_log.xml deleted file mode 100644 index a70cbc3ecc2..00000000000 --- a/tests/integration/test_zookeeper_config_load_balancing/configs/zookeeper_log.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - system - zookeeper_log
- 7500 -
-
diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py b/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py index 71084492b44..38361016512 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py @@ -6,11 +6,11 @@ from helpers.cluster import ClickHouseCluster cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_first_or_random.xml') node1 = cluster.add_instance('node1', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml"]) node2 = cluster.add_instance('node2', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml"]) node3 = cluster.add_instance('node3', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml"]) @pytest.fixture(scope="module", autouse=True) @@ -23,31 +23,9 @@ def started_cluster(): finally: cluster.shutdown() -def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): - start = time.time() - while time.time() - start < timeout: - try: - for instance in zk_nodes: - conn = started_cluster.get_kazoo_client(instance) - conn.get_children('/') - print("All instances of ZooKeeper started") - return - except Exception as ex: - print(("Can't connect to ZooKeeper " + str(ex))) - time.sleep(0.5) def test_first_or_random(started_cluster): - wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) - time.sleep(10) - zoo1_ip = started_cluster.get_instance_ip("zoo1") - for i, node in enumerate([node1, node3]): - node.query('DROP TABLE IF EXISTS simple SYNC') - node.query(''' - CREATE TABLE simple (date Date, id UInt32) - ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); - '''.format(replica=node.name)) - time.sleep(5) - assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo1_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo1_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '1' == str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + assert '1' == str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + assert '1' == str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py b/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py index 92ba927860c..c11d05f0a75 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py @@ -6,11 +6,11 @@ from helpers.cluster import ClickHouseCluster cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_in_order.xml') node1 = cluster.add_instance('node1', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml"]) node2 = cluster.add_instance('node2', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml"]) node3 = cluster.add_instance('node3', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml"]) @pytest.fixture(scope="module", autouse=True) @@ -23,31 +23,8 @@ def started_cluster(): finally: cluster.shutdown() -def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): - start = time.time() - while time.time() - start < timeout: - try: - for instance in zk_nodes: - conn = started_cluster.get_kazoo_client(instance) - conn.get_children('/') - print("All instances of ZooKeeper started") - return - except Exception as ex: - print(("Can't connect to ZooKeeper " + str(ex))) - time.sleep(0.5) - def test_in_order(started_cluster): - wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) - time.sleep(10) - zoo1_ip = started_cluster.get_instance_ip("zoo1") - for i, node in enumerate([node1, node3]): - node.query('DROP TABLE IF EXISTS simple SYNC') - node.query(''' - CREATE TABLE simple (date Date, id UInt32) - ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); - '''.format(replica=node.name)) - time.sleep(5) - assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo1_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo1_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '1' == str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + assert '1' == str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + assert '1' == str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py b/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py index 832af32bbae..30fca5c5395 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py @@ -6,11 +6,11 @@ from helpers.cluster import ClickHouseCluster cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_nearest_hostname.xml') node1 = cluster.add_instance('nod1', with_zookeeper=True, - main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml"]) node2 = cluster.add_instance('nod2', with_zookeeper=True, - main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml"]) node3 = cluster.add_instance('nod3', with_zookeeper=True, - main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml"]) @pytest.fixture(scope="module", autouse=True) @@ -23,34 +23,8 @@ def started_cluster(): finally: cluster.shutdown() -def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): - start = time.time() - while time.time() - start < timeout: - try: - for instance in zk_nodes: - conn = started_cluster.get_kazoo_client(instance) - conn.get_children('/') - print("All instances of ZooKeeper started") - return - except Exception as ex: - print(("Can't connect to ZooKeeper " + str(ex))) - time.sleep(0.5) - def test_nearest_hostname(started_cluster): - wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) - time.sleep(10) - zoo1_ip = started_cluster.get_instance_ip("zoo1") - zoo2_ip = started_cluster.get_instance_ip("zoo2") - zoo3_ip = started_cluster.get_instance_ip("zoo3") - for i, node in enumerate([node1, node3]): - node.query('DROP TABLE IF EXISTS simple SYNC') - node.query(''' - CREATE TABLE simple (date Date, id UInt32) - ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); - '''.format(replica=node.name)) - - time.sleep(5) - assert '::ffff:' + str(zoo1_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo2_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo3_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '1' == str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + assert '1' == str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo2_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + assert '1' == str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo3_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py b/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py index f447f929d38..98d751f075b 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py @@ -6,11 +6,11 @@ from helpers.cluster import ClickHouseCluster cluster = ClickHouseCluster(__file__, zookeeper_config_path='configs/zookeeper_config_round_robin.xml') node1 = cluster.add_instance('node1', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml"]) node2 = cluster.add_instance('node2', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml"]) node3 = cluster.add_instance('node3', with_zookeeper=True, - main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml", "configs/zookeeper_log.xml"]) + main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml"]) @pytest.fixture(scope="module", autouse=True) @@ -23,39 +23,14 @@ def started_cluster(): finally: cluster.shutdown() -def wait_zookeeper_node_to_start(started_cluster, zk_nodes, timeout=60): - start = time.time() - while time.time() - start < timeout: - try: - for instance in zk_nodes: - conn = started_cluster.get_kazoo_client(instance) - conn.get_children('/') - print("All instances of ZooKeeper started") - return - except Exception as ex: - print(("Can't connect to ZooKeeper " + str(ex))) - time.sleep(0.5) - - def test_round_robin(started_cluster): - wait_zookeeper_node_to_start(started_cluster, ["zoo1", "zoo2", "zoo3"]) + started_cluster.stop_zookeeper_nodes(["zoo1"]) - time.sleep(10) - zoo2_ip = started_cluster.get_instance_ip("zoo2") - for i, node in enumerate([node1, node3]): - node.query('DROP TABLE IF EXISTS simple SYNC') - node.query(''' - CREATE TABLE simple (date Date, id UInt32) - ENGINE = ReplicatedMergeTree('/clickhouse/tables/0/simple', '{replica}', date, id, 8192); - '''.format(replica=node.name)) + time.sleep(1) - time.sleep(5) - print("zoo2", zoo2_ip) - assert '::ffff:' + str(zoo2_ip) + '\n' == node1.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo2_ip) + '\n' == node2.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') - assert '::ffff:' + str(zoo2_ip) + '\n' == node3.query('SELECT IPv6NumToString(address) FROM system.zookeeper_log ORDER BY event_time DESC LIMIT 1') + assert '1' == str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo2_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + assert '1' == str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo2_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + assert '1' == str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo2_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() - ## start zoo2, zoo3, table will be readonly too, because it only connect to zoo1 started_cluster.start_zookeeper_nodes(["zoo1"]) - wait_zookeeper_node_to_start(started_cluster, ["zoo1"]) From 821ad7cb2ae2c0c66afe3279cf0ebbf6c1c09bc2 Mon Sep 17 00:00:00 2001 From: zhangxiao871 <821008736@qq.com> Date: Tue, 2 Nov 2021 15:40:05 +0800 Subject: [PATCH 0090/1647] try fix test --- .../{test_first_or_random.py => test.py} | 8 ++++++-- .../test_zookeeper_config_load_balancing/test_in_order.py | 7 ++++++- .../test_nearest_hostname.py | 7 ++++++- .../test_round_robin.py | 7 ++++++- 4 files changed, 24 insertions(+), 5 deletions(-) rename tests/integration/test_zookeeper_config_load_balancing/{test_first_or_random.py => test.py} (78%) diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py b/tests/integration/test_zookeeper_config_load_balancing/test.py similarity index 78% rename from tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py rename to tests/integration/test_zookeeper_config_load_balancing/test.py index 38361016512..144ba14ce40 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_first_or_random.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test.py @@ -13,7 +13,7 @@ node3 = cluster.add_instance('node3', with_zookeeper=True, main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_first_or_random.xml"]) -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def started_cluster(): try: cluster.start() @@ -25,7 +25,11 @@ def started_cluster(): def test_first_or_random(started_cluster): - + print(str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + + print(str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + + print(str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py b/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py index c11d05f0a75..095aba72217 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_in_order.py @@ -13,7 +13,7 @@ node3 = cluster.add_instance('node3', with_zookeeper=True, main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_in_order.xml"]) -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def started_cluster(): try: cluster.start() @@ -25,6 +25,11 @@ def started_cluster(): def test_in_order(started_cluster): + print(str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + + print(str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + + print(str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py b/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py index 30fca5c5395..23c0386b1d2 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_nearest_hostname.py @@ -13,7 +13,7 @@ node3 = cluster.add_instance('nod3', with_zookeeper=True, main_configs=["configs/remote_servers_nearest_hostname.xml", "configs/zookeeper_config_nearest_hostname.xml"]) -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def started_cluster(): try: cluster.start() @@ -25,6 +25,11 @@ def started_cluster(): def test_nearest_hostname(started_cluster): + print(str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo1_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + + print(str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo2_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + + print(str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo3_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() diff --git a/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py b/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py index 98d751f075b..3623371c244 100644 --- a/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py +++ b/tests/integration/test_zookeeper_config_load_balancing/test_round_robin.py @@ -13,7 +13,7 @@ node3 = cluster.add_instance('node3', with_zookeeper=True, main_configs=["configs/remote_servers.xml", "configs/zookeeper_config_round_robin.xml"]) -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="module") def started_cluster(): try: cluster.start() @@ -29,8 +29,13 @@ def test_round_robin(started_cluster): started_cluster.stop_zookeeper_nodes(["zoo1"]) time.sleep(1) + print(str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node1.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo2_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + + print(str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node2.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo2_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() + + print(str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep ':2181' | grep ESTABLISHED"], privileged=True, user='root'))) assert '1' == str(node3.exec_in_container(['bash', '-c', "lsof -a -i4 -i6 -itcp -w | grep 'roottestzookeeperconfigloadbalancing_zoo2_1.roottestzookeeperconfigloadbalancing_default:2181' | grep ESTABLISHED | wc -l"], privileged=True, user='root')).strip() started_cluster.start_zookeeper_nodes(["zoo1"]) From 482154940f3294d18a8d6fa31bc9b2a39b129041 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 2 Nov 2021 17:48:40 +0300 Subject: [PATCH 0091/1647] Update 01079_parallel_alter_detach_table_zookeeper.sh --- .../0_stateless/01079_parallel_alter_detach_table_zookeeper.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01079_parallel_alter_detach_table_zookeeper.sh b/tests/queries/0_stateless/01079_parallel_alter_detach_table_zookeeper.sh index c95554d26bb..22c4de28cd1 100755 --- a/tests/queries/0_stateless/01079_parallel_alter_detach_table_zookeeper.sh +++ b/tests/queries/0_stateless/01079_parallel_alter_detach_table_zookeeper.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: zookeeper, no-parallel, no-fasttest +# Tags: zookeeper, no-parallel, no-fasttest, backward-incompatible CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From 03f1e3c67d2fb1e2171c773c001cf600c18d036b Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 8 Nov 2021 14:02:36 +0300 Subject: [PATCH 0092/1647] Update run.sh --- docker/test/stress/run.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/test/stress/run.sh b/docker/test/stress/run.sh index 5e4ef036c9d..ca1c5c3e830 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -90,6 +90,9 @@ function start() # use root to match with current uid clickhouse start --user root >/var/log/clickhouse-server/stdout.log 2>>/var/log/clickhouse-server/stderr.log sleep 0.5 + cat /var/log/clickhouse-server/stdout.log + tail -n200 /var/log/clickhouse-server/stderr.log + tail -n200 /var/log/clickhouse-server/clickhouse-server.log counter=$((counter + 1)) done From 08af98b4a1a5d642e22977eaf79d8c5f7d9cf1c7 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 9 Nov 2021 16:34:13 +0300 Subject: [PATCH 0093/1647] Update download_previous_release --- docker/test/stress/download_previous_release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/stress/download_previous_release b/docker/test/stress/download_previous_release index ad3b5ed2123..fc95b196b14 100755 --- a/docker/test/stress/download_previous_release +++ b/docker/test/stress/download_previous_release @@ -20,7 +20,7 @@ CLICKHOUSE_CLIENT_PACKET_NAME = "clickhouse-client_{version}_all.deb" CLICKHOUSE_TEST_PACKET_NAME = "clickhouse-test_{version}_all.deb" PACKETS_DIR = "previous_release_package_folder/" -VERSION_PATTERN = r"((?:\d+\.)?(?:\d+\.)?(?:\d+\.)?\d+-lts*)" +VERSION_PATTERN = r"((?:\d+\.)?(?:\d+\.)?(?:\d+\.)?\d+*)" class Version: From 5656203bc65a5728ac74f784a0b8888124567e20 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 8 Nov 2021 21:56:09 +0300 Subject: [PATCH 0094/1647] minor fixes --- src/Common/TransactionID.cpp | 16 ++++++ ...{TransactionMetadata.h => TransactionID.h} | 33 +---------- src/Core/Settings.h | 2 +- .../FunctionsTransactionCounters.cpp | 2 +- src/Interpreters/Context.cpp | 1 + src/Interpreters/InterpreterFactory.cpp | 1 - .../InterpreterTransactionControlQuery.h | 4 +- src/Interpreters/MergeTreeTransaction.h | 2 +- .../MergeTreeTransactionHolder.cpp | 2 +- src/Interpreters/QueryLog.cpp | 2 +- src/Interpreters/QueryLog.h | 2 +- src/Interpreters/TransactionLog.cpp | 11 ++-- .../TransactionVersionMetadata.cpp} | 56 +++++++++---------- src/Interpreters/TransactionVersionMetadata.h | 36 ++++++++++++ src/Parsers/ASTTransactionControl.cpp | 5 -- src/Storages/MergeTree/IMergeTreeDataPart.h | 2 +- src/Storages/MergeTree/MergeTreeData.cpp | 2 +- src/Storages/System/StorageSystemParts.cpp | 6 +- .../01171_mv_select_insert_isolation_long.sh | 2 + .../01172_transaction_counters.reference | 1 + .../01172_transaction_counters.sql | 1 + .../01174_select_insert_isolation.sh | 2 + 22 files changed, 108 insertions(+), 83 deletions(-) create mode 100644 src/Common/TransactionID.cpp rename src/Common/{TransactionMetadata.h => TransactionID.h} (64%) rename src/{Common/TransactionMetadata.cpp => Interpreters/TransactionVersionMetadata.cpp} (84%) create mode 100644 src/Interpreters/TransactionVersionMetadata.h diff --git a/src/Common/TransactionID.cpp b/src/Common/TransactionID.cpp new file mode 100644 index 00000000000..4cf93636c11 --- /dev/null +++ b/src/Common/TransactionID.cpp @@ -0,0 +1,16 @@ +#include +#include + +namespace DB +{ + +TIDHash TransactionID::getHash() const +{ + SipHash hash; + hash.update(start_csn); + hash.update(local_tid); + hash.update(host_id); + return hash.get64(); +} + +} diff --git a/src/Common/TransactionMetadata.h b/src/Common/TransactionID.h similarity index 64% rename from src/Common/TransactionMetadata.h rename to src/Common/TransactionID.h index 8aa1488f1a6..fa745efcf43 100644 --- a/src/Common/TransactionMetadata.h +++ b/src/Common/TransactionID.h @@ -21,9 +21,7 @@ struct TransactionID { CSN start_csn = 0; LocalTID local_tid = 0; - UUID host_id = UUIDHelpers::Nil; /// Depends on #17278, leave it Nil for now. - - static DataTypePtr getDataType(); + UUID host_id = UUIDHelpers::Nil; bool operator == (const TransactionID & rhs) const { @@ -49,8 +47,10 @@ namespace Tx const CSN UnknownCSN = 0; const CSN PrehistoricCSN = 1; +const CSN MaxReservedCSN = 16; const LocalTID PrehistoricLocalTID = 1; +const LocalTID MaxReservedLocalTID = 16; const TransactionID EmptyTID = {0, 0, UUIDHelpers::Nil}; const TransactionID PrehistoricTID = {PrehistoricCSN, PrehistoricLocalTID, UUIDHelpers::Nil}; @@ -60,33 +60,6 @@ const CSN RolledBackCSN = std::numeric_limits::max(); } -struct VersionMetadata -{ - const TransactionID mintid = Tx::EmptyTID; - TransactionID maxtid = Tx::EmptyTID; - - std::atomic maxtid_lock = 0; - - std::atomic mincsn = Tx::UnknownCSN; - std::atomic maxcsn = Tx::UnknownCSN; - - bool isVisible(const MergeTreeTransaction & txn); - bool isVisible(Snapshot snapshot_version, TransactionID current_tid = Tx::EmptyTID); - - TransactionID getMinTID() const { return mintid; } - TransactionID getMaxTID() const; - - void lockMaxTID(const TransactionID & tid, const String & error_context = {}); - void unlockMaxTID(const TransactionID & tid); - - bool isMaxTIDLocked() const; - - /// It can be called only from MergeTreeTransaction or on server startup - void setMinTID(const TransactionID & tid); - - bool canBeRemoved(Snapshot oldest_snapshot_version); -}; - } template<> diff --git a/src/Core/Settings.h b/src/Core/Settings.h index f8b574a21d8..e3d38127cc9 100644 --- a/src/Core/Settings.h +++ b/src/Core/Settings.h @@ -150,7 +150,7 @@ class IColumn; M(UInt64, merge_tree_max_rows_to_use_cache, (128 * 8192), "The maximum number of rows per request, to use the cache of uncompressed data. If the request is large, the cache is not used. (For large queries not to flush out the cache.)", 0) \ M(UInt64, merge_tree_max_bytes_to_use_cache, (192 * 10 * 1024 * 1024), "The maximum number of bytes per request, to use the cache of uncompressed data. If the request is large, the cache is not used. (For large queries not to flush out the cache.)", 0) \ M(UInt64, merge_tree_clear_old_temporary_directories_interval_seconds, 60, "The period of executing the clear old temporary directories operation in background.", 0) \ - M(UInt64, merge_tree_clear_old_parts_interval_seconds, 1, "The period of executing the clear old parts operation in background.", 0) \ + M(UInt64, merge_tree_clear_old_parts_interval_seconds, 30, "The period of executing the clear old parts operation in background.", 0) \ M(Bool, do_not_merge_across_partitions_select_final, false, "Merge parts only in one partition in select final", 0) \ \ M(UInt64, mysql_max_rows_to_insert, 65536, "The maximum number of rows in MySQL batch insertion of the MySQL storage engine", 0) \ diff --git a/src/Functions/FunctionsTransactionCounters.cpp b/src/Functions/FunctionsTransactionCounters.cpp index 7e821645b76..f2547734e52 100644 --- a/src/Functions/FunctionsTransactionCounters.cpp +++ b/src/Functions/FunctionsTransactionCounters.cpp @@ -29,7 +29,7 @@ public: DataTypePtr getReturnTypeImpl(const DataTypes &) const override { - return TransactionID::getDataType(); + return getTransactionIDDataType(); } bool isDeterministic() const override { return false; } diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index a2afd9a7e94..c6cb959f529 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -132,6 +132,7 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; extern const int INVALID_SETTING_VALUE; extern const int UNKNOWN_READ_METHOD; + extern const int NOT_IMPLEMENTED; } diff --git a/src/Interpreters/InterpreterFactory.cpp b/src/Interpreters/InterpreterFactory.cpp index 4fc73f1a806..5dcee1eae05 100644 --- a/src/Interpreters/InterpreterFactory.cpp +++ b/src/Interpreters/InterpreterFactory.cpp @@ -63,7 +63,6 @@ #include #include #include -#include #include #include diff --git a/src/Interpreters/InterpreterTransactionControlQuery.h b/src/Interpreters/InterpreterTransactionControlQuery.h index b3d2cc06b9e..fc71939502e 100644 --- a/src/Interpreters/InterpreterTransactionControlQuery.h +++ b/src/Interpreters/InterpreterTransactionControlQuery.h @@ -20,8 +20,8 @@ public: bool ignoreLimits() const override { return true; } private: BlockIO executeBegin(ContextMutablePtr session_context); - BlockIO executeCommit(ContextMutablePtr session_context); - BlockIO executeRollback(ContextMutablePtr session_context); + static BlockIO executeCommit(ContextMutablePtr session_context); + static BlockIO executeRollback(ContextMutablePtr session_context); private: ContextMutablePtr query_context; diff --git a/src/Interpreters/MergeTreeTransaction.h b/src/Interpreters/MergeTreeTransaction.h index 1ead2fdce87..921a1c10951 100644 --- a/src/Interpreters/MergeTreeTransaction.h +++ b/src/Interpreters/MergeTreeTransaction.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include diff --git a/src/Interpreters/MergeTreeTransactionHolder.cpp b/src/Interpreters/MergeTreeTransactionHolder.cpp index b6492286c3f..b1c50ff6d55 100644 --- a/src/Interpreters/MergeTreeTransactionHolder.cpp +++ b/src/Interpreters/MergeTreeTransactionHolder.cpp @@ -61,7 +61,7 @@ MergeTreeTransactionHolder::MergeTreeTransactionHolder(const MergeTreeTransactio txn = nullptr; } -MergeTreeTransactionHolder & MergeTreeTransactionHolder::operator=(const MergeTreeTransactionHolder &) +MergeTreeTransactionHolder & MergeTreeTransactionHolder::operator=(const MergeTreeTransactionHolder &) // NOLINT { assert(txn == nullptr); return *this; diff --git a/src/Interpreters/QueryLog.cpp b/src/Interpreters/QueryLog.cpp index 2a236d39a4a..8fc04d0d769 100644 --- a/src/Interpreters/QueryLog.cpp +++ b/src/Interpreters/QueryLog.cpp @@ -117,7 +117,7 @@ NamesAndTypesList QueryLogElement::getNamesAndTypes() {"used_storages", std::make_shared(std::make_shared())}, {"used_table_functions", std::make_shared(std::make_shared())}, - {"transaction_id", TransactionID::getDataType()} + {"transaction_id", getTransactionIDDataType()}, }; } diff --git a/src/Interpreters/QueryLog.h b/src/Interpreters/QueryLog.h index 2b3a603e681..9844eede4d0 100644 --- a/src/Interpreters/QueryLog.h +++ b/src/Interpreters/QueryLog.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace ProfileEvents { diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index b8799522b5a..0624ddd116c 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -1,6 +1,7 @@ #include -#include +#include #include +#include #include namespace DB @@ -20,9 +21,9 @@ TransactionLog & TransactionLog::instance() TransactionLog::TransactionLog() : log(&Poco::Logger::get("TransactionLog")) { - latest_snapshot = 1; - csn_counter = 1; - local_tid_counter = 1; + latest_snapshot = Tx::MaxReservedCSN; + csn_counter = Tx::MaxReservedCSN; + local_tid_counter = Tx::MaxReservedLocalTID; } Snapshot TransactionLog::getLatestSnapshot() const @@ -37,7 +38,7 @@ MergeTreeTransactionPtr TransactionLog::beginTransaction() std::lock_guard lock{running_list_mutex}; Snapshot snapshot = latest_snapshot.load(); LocalTID ltid = 1 + local_tid_counter.fetch_add(1); - txn = std::make_shared(snapshot, ltid, UUIDHelpers::Nil); + txn = std::make_shared(snapshot, ltid, ServerUUID::get()); bool inserted = running_list.try_emplace(txn->tid.getHash(), txn).second; if (!inserted) throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} exists", txn->tid.getHash(), txn->tid); diff --git a/src/Common/TransactionMetadata.cpp b/src/Interpreters/TransactionVersionMetadata.cpp similarity index 84% rename from src/Common/TransactionMetadata.cpp rename to src/Interpreters/TransactionVersionMetadata.cpp index c3513380a49..f7d2c885f55 100644 --- a/src/Common/TransactionMetadata.cpp +++ b/src/Interpreters/TransactionVersionMetadata.cpp @@ -1,35 +1,18 @@ -#include -#include +#include #include #include #include #include +//#include + namespace DB { namespace ErrorCodes { -extern const int SERIALIZATION_ERROR; -extern const int LOGICAL_ERROR; -} - -DataTypePtr TransactionID::getDataType() -{ - DataTypes types; - types.push_back(std::make_shared()); - types.push_back(std::make_shared()); - types.push_back(std::make_shared()); - return std::make_shared(std::move(types)); -} - -TIDHash TransactionID::getHash() const -{ - SipHash hash; - hash.update(start_csn); - hash.update(local_tid); - hash.update(host_id); - return hash.get64(); + extern const int SERIALIZATION_ERROR; + extern const int LOGICAL_ERROR; } /// It can be used for introspection purposes only @@ -62,8 +45,8 @@ void VersionMetadata::lockMaxTID(const TransactionID & tid, const String & error if (!locked) { throw Exception(ErrorCodes::SERIALIZATION_ERROR, "Serialization error: " - "Transaction {} tried to remove data part, " - "but it's locked ({}) by another transaction {} which is currently removing this part. {}", + "Transaction {} tried to remove data part, " + "but it's locked ({}) by another transaction {} which is currently removing this part. {}", tid, expected_max_lock_value, getMaxTID(), error_context); } @@ -112,11 +95,14 @@ bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current_tid) { + //Poco::Logger * log = &Poco::Logger::get("WTF"); assert(mintid); CSN min = mincsn.load(std::memory_order_relaxed); TIDHash max_lock = maxtid_lock.load(std::memory_order_relaxed); CSN max = maxcsn.load(std::memory_order_relaxed); + //LOG_TRACE(log, "Checking if mintid {} mincsn {} maxtidhash {} maxcsn {} visible for {} {}", mintid, min, max_lock, max, snapshot_version, current_tid); + [[maybe_unused]] bool had_mincsn = min; [[maybe_unused]] bool had_maxtid = max_lock; [[maybe_unused]] bool had_maxcsn = max; @@ -151,13 +137,14 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current /// Data part has mintid/maxtid, but does not have mincsn/maxcsn. /// It means that some transaction is creating/removing the part right now or has done it recently - /// and we don't know if it was already committed ot not. + /// and we don't know if it was already committed or not. assert(!had_mincsn || (had_maxtid && !had_maxcsn)); assert(!current_tid || (mintid != current_tid && max_lock != current_tid.getHash())); /// Before doing CSN lookup, let's check some extra conditions. - /// If snapshot_version <= some_tid.start_csn, then changes of transaction with some_tid - /// are definitely not visible for us, so we don't need to check if it was committed. + /// If snapshot_version <= some_tid.start_csn, then changes of the transaction with some_tid + /// are definitely not visible for us (because the transaction can be committed with greater CSN only), + /// so we don't need to check if it was committed. if (snapshot_version <= mintid.start_csn) return false; @@ -169,16 +156,18 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current /// But for long-running writing transactions we will always do /// CNS lookup and get 0 (UnknownCSN) until the transaction is committer/rolled back. min = TransactionLog::instance().getCSN(mintid); + //LOG_TRACE(log, "Got min {}", min); if (!min) return false; /// Part creation is not committed yet - /// We don't need to check if CSNs are already writen or not, - /// because once writen CSN cannot be changed, so it's safe to overwrite it (with tha same value). + /// We don't need to check if CSNs are already written or not, + /// because once written CSN cannot be changed, so it's safe to overwrite it (with the same value). mincsn.store(min, std::memory_order_relaxed); if (max_lock) { max = TransactionLog::instance().getCSN(max_lock); + //LOG_TRACE(log, "Got ax {}", max); if (max) maxcsn.store(max, std::memory_order_relaxed); } @@ -221,4 +210,13 @@ bool VersionMetadata::canBeRemoved(Snapshot oldest_snapshot_version) return max <= oldest_snapshot_version; } +DataTypePtr getTransactionIDDataType() +{ + DataTypes types; + types.push_back(std::make_shared()); + types.push_back(std::make_shared()); + types.push_back(std::make_shared()); + return std::make_shared(std::move(types)); +} + } diff --git a/src/Interpreters/TransactionVersionMetadata.h b/src/Interpreters/TransactionVersionMetadata.h new file mode 100644 index 00000000000..94d6a1905e9 --- /dev/null +++ b/src/Interpreters/TransactionVersionMetadata.h @@ -0,0 +1,36 @@ +#pragma once +#include + +namespace DB +{ + +struct VersionMetadata +{ + const TransactionID mintid = Tx::EmptyTID; + TransactionID maxtid = Tx::EmptyTID; + + std::atomic maxtid_lock = 0; + + std::atomic mincsn = Tx::UnknownCSN; + std::atomic maxcsn = Tx::UnknownCSN; + + bool isVisible(const MergeTreeTransaction & txn); + bool isVisible(Snapshot snapshot_version, TransactionID current_tid = Tx::EmptyTID); + + TransactionID getMinTID() const { return mintid; } + TransactionID getMaxTID() const; + + void lockMaxTID(const TransactionID & tid, const String & error_context = {}); + void unlockMaxTID(const TransactionID & tid); + + bool isMaxTIDLocked() const; + + /// It can be called only from MergeTreeTransaction or on server startup + void setMinTID(const TransactionID & tid); + + bool canBeRemoved(Snapshot oldest_snapshot_version); +}; + +DataTypePtr getTransactionIDDataType(); + +} diff --git a/src/Parsers/ASTTransactionControl.cpp b/src/Parsers/ASTTransactionControl.cpp index d12c9d6d6e4..2eb74903522 100644 --- a/src/Parsers/ASTTransactionControl.cpp +++ b/src/Parsers/ASTTransactionControl.cpp @@ -5,11 +5,6 @@ namespace DB { -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - void ASTTransactionControl::formatImpl(const FormatSettings & format /*state*/, FormatState &, FormatStateStacked /*frame*/) const { switch (action) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 12e071abe86..0a318276059 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 707c80510ce..0e646829176 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -2725,7 +2725,7 @@ MergeTreeData::DataPartsVector MergeTreeData::removePartsInRangeFromWorkingSet( void MergeTreeData::restoreAndActivatePart(const DataPartPtr & part, DataPartsLock * acquired_lock) { - auto lock = (acquired_lock) ? DataPartsLock() : lockParts(); + auto lock = (acquired_lock) ? DataPartsLock() : lockParts(); //-V1018 assert(part->getState() != DataPartState::Committed); addPartContributionToColumnAndSecondaryIndexSizes(part); addPartContributionToDataVolume(part); diff --git a/src/Storages/System/StorageSystemParts.cpp b/src/Storages/System/StorageSystemParts.cpp index 2f2b639b380..950d680f157 100644 --- a/src/Storages/System/StorageSystemParts.cpp +++ b/src/Storages/System/StorageSystemParts.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include namespace DB { @@ -84,8 +84,8 @@ StorageSystemParts::StorageSystemParts(const StorageID & table_id_) {"projections", std::make_shared(std::make_shared())}, {"visible", std::make_shared()}, - {"mintid", TransactionID::getDataType()}, - {"maxtid", TransactionID::getDataType()}, + {"mintid", getTransactionIDDataType()}, + {"maxtid", getTransactionIDDataType()}, {"mincsn", std::make_shared()}, {"maxcsn", std::make_shared()}, } diff --git a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh index f506962a36a..c48f86f4aee 100755 --- a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh +++ b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Tags: long, no-parallel +# Test is too heavy, avoid parallel run in Flaky Check CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh diff --git a/tests/queries/0_stateless/01172_transaction_counters.reference b/tests/queries/0_stateless/01172_transaction_counters.reference index fe055805d93..1f463a25c20 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.reference +++ b/tests/queries/0_stateless/01172_transaction_counters.reference @@ -8,3 +8,4 @@ 4 all_1_1_0 1 (0,0,'00000000-0000-0000-0000-000000000000') 0 4 all_2_2_0 18446744073709551615 (0,0,'00000000-0000-0000-0000-000000000000') 0 4 all_3_3_0 0 (0,0,'00000000-0000-0000-0000-000000000000') 0 +5 1 diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql index 1d9311e4e86..ca114643130 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.sql +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -18,6 +18,7 @@ begin transaction; insert into txn_counters(n) values (3); select 3, system.parts.name, txn_counters.mintid = system.parts.mintid from txn_counters join system.parts on txn_counters._part = system.parts.name where database=currentDatabase() and table='txn_counters' order by system.parts.name; select 4, name, mincsn, maxtid, maxcsn from system.parts where database=currentDatabase() and table='txn_counters' order by system.parts.name; +select 5, transactionID().3 == serverUUID(); commit; drop table txn_counters; diff --git a/tests/queries/0_stateless/01174_select_insert_isolation.sh b/tests/queries/0_stateless/01174_select_insert_isolation.sh index 2bc7185e3fa..663939eb269 100755 --- a/tests/queries/0_stateless/01174_select_insert_isolation.sh +++ b/tests/queries/0_stateless/01174_select_insert_isolation.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# shellcheck disable=SC2015 + CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh From 87df3c8272e94f36e76bc898166560b9ee053d6f Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 15 Nov 2021 15:03:48 +0300 Subject: [PATCH 0095/1647] Fix --- docker/test/stress/download_previous_release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/stress/download_previous_release b/docker/test/stress/download_previous_release index fc95b196b14..73ffb2afd9a 100755 --- a/docker/test/stress/download_previous_release +++ b/docker/test/stress/download_previous_release @@ -20,7 +20,7 @@ CLICKHOUSE_CLIENT_PACKET_NAME = "clickhouse-client_{version}_all.deb" CLICKHOUSE_TEST_PACKET_NAME = "clickhouse-test_{version}_all.deb" PACKETS_DIR = "previous_release_package_folder/" -VERSION_PATTERN = r"((?:\d+\.)?(?:\d+\.)?(?:\d+\.)?\d+*)" +VERSION_PATTERN = r"((?:\d+\.)?(?:\d+\.)?(?:\d+\.)?\d+.*)" class Version: From 6180aedab6b13ffea4cea7d52f6bc84fccff25d2 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Mon, 15 Nov 2021 23:16:35 +0300 Subject: [PATCH 0096/1647] Update download_previous_release --- docker/test/stress/download_previous_release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/stress/download_previous_release b/docker/test/stress/download_previous_release index 73ffb2afd9a..3d4c649d091 100755 --- a/docker/test/stress/download_previous_release +++ b/docker/test/stress/download_previous_release @@ -20,7 +20,7 @@ CLICKHOUSE_CLIENT_PACKET_NAME = "clickhouse-client_{version}_all.deb" CLICKHOUSE_TEST_PACKET_NAME = "clickhouse-test_{version}_all.deb" PACKETS_DIR = "previous_release_package_folder/" -VERSION_PATTERN = r"((?:\d+\.)?(?:\d+\.)?(?:\d+\.)?\d+.*)" +VERSION_PATTERN = r"((?:\d+\.)?(?:\d+\.)?(?:\d+\.)?\d+-[a-zA-Z]*)" class Version: From 6bf9a170ab53071c084cf67262f1e7948c6d1921 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 17 Nov 2021 13:48:39 +0300 Subject: [PATCH 0097/1647] Update 01160_table_dependencies.sh --- tests/queries/0_stateless/01160_table_dependencies.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/01160_table_dependencies.sh b/tests/queries/0_stateless/01160_table_dependencies.sh index a0a3f05c6a9..00121e9d989 100755 --- a/tests/queries/0_stateless/01160_table_dependencies.sh +++ b/tests/queries/0_stateless/01160_table_dependencies.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: backward-incompatible:21.12.1.8761 CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From a1631e92687c7779d852b22017ed9089b2552416 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 17 Nov 2021 13:53:38 +0300 Subject: [PATCH 0098/1647] Update 02022_storage_filelog_one_file.sh --- tests/queries/0_stateless/02022_storage_filelog_one_file.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/02022_storage_filelog_one_file.sh b/tests/queries/0_stateless/02022_storage_filelog_one_file.sh index 8ae0ce0ec1c..76fce0162c6 100755 --- a/tests/queries/0_stateless/02022_storage_filelog_one_file.sh +++ b/tests/queries/0_stateless/02022_storage_filelog_one_file.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: backward-incompatible set -eu From 0a4647f927db65f1b65006d1c5df5c63c26dc296 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 17 Nov 2021 21:14:14 +0300 Subject: [PATCH 0099/1647] support alter partition --- .../TransactionVersionMetadata.cpp | 7 + src/Interpreters/executeQuery.cpp | 7 +- src/Storages/IStorage.h | 5 - src/Storages/MergeTree/IMergeTreeDataPart.h | 1 + src/Storages/MergeTree/MergeTreeData.cpp | 121 +++++++++++++----- src/Storages/MergeTree/MergeTreeData.h | 46 ++++--- .../MergeTree/MergeTreeDataMergerMutator.cpp | 2 +- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 2 +- src/Storages/StorageMergeTree.cpp | 16 +-- src/Storages/StorageProxy.h | 1 - src/Storages/StorageReplicatedMergeTree.cpp | 11 +- ...alter_partition_isolation_stress.reference | 6 + .../01169_alter_partition_isolation_stress.sh | 113 ++++++++++++++++ .../01170_alter_partition_isolation.reference | 30 +++++ .../01170_alter_partition_isolation.sh | 70 ++++++++++ .../01171_mv_select_insert_isolation_long.sh | 10 ++ tests/queries/0_stateless/transactions.lib | 14 ++ 17 files changed, 391 insertions(+), 71 deletions(-) create mode 100644 tests/queries/0_stateless/01169_alter_partition_isolation_stress.reference create mode 100755 tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh create mode 100644 tests/queries/0_stateless/01170_alter_partition_isolation.reference create mode 100755 tests/queries/0_stateless/01170_alter_partition_isolation.sh create mode 100755 tests/queries/0_stateless/transactions.lib diff --git a/src/Interpreters/TransactionVersionMetadata.cpp b/src/Interpreters/TransactionVersionMetadata.cpp index f7d2c885f55..cbcff6057a5 100644 --- a/src/Interpreters/TransactionVersionMetadata.cpp +++ b/src/Interpreters/TransactionVersionMetadata.cpp @@ -44,6 +44,13 @@ void VersionMetadata::lockMaxTID(const TransactionID & tid, const String & error bool locked = maxtid_lock.compare_exchange_strong(expected_max_lock_value, max_lock_value); if (!locked) { + if (tid == Tx::PrehistoricTID && expected_max_lock_value == Tx::PrehistoricTID.getHash()) + { + /// Don't need to lock part for queries without transaction + //FIXME Transactions: why is it possible? + return; + } + throw Exception(ErrorCodes::SERIALIZATION_ERROR, "Serialization error: " "Transaction {} tried to remove data part, " "but it's locked ({}) by another transaction {} which is currently removing this part. {}", diff --git a/src/Interpreters/executeQuery.cpp b/src/Interpreters/executeQuery.cpp index dde6ff7dd72..b558e58997d 100644 --- a/src/Interpreters/executeQuery.cpp +++ b/src/Interpreters/executeQuery.cpp @@ -172,10 +172,15 @@ static void logQuery(const String & query, ContextPtr context, bool internal) if (!comment.empty()) comment = fmt::format(" (comment: {})", comment); - LOG_DEBUG(&Poco::Logger::get("executeQuery"), "(from {}{}{}){} {}", + String transaction_info; + if (auto txn = context->getCurrentTransaction()) + transaction_info = fmt::format(" (TID: {}, TIDH: {})", txn->tid, txn->tid.getHash()); + + LOG_DEBUG(&Poco::Logger::get("executeQuery"), "(from {}{}{}){}{} {}", client_info.current_address.toString(), (current_user != "default" ? ", user: " + current_user : ""), (!initial_query_id.empty() && current_query_id != initial_query_id ? ", initial_query_id: " + initial_query_id : std::string()), + transaction_info, comment, joinLines(query)); diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index fa5f2c28b06..3bdd535b585 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -538,11 +538,6 @@ public: /// Similar to above but checks for DETACH. It's only used for DICTIONARIES. virtual void checkTableCanBeDetached() const {} - /// Checks that Partition could be dropped right now - /// Otherwise - throws an exception with detailed information. - /// We do not use mutex because it is not very important that the size could change during the operation. - virtual void checkPartitionCanBeDropped(const ASTPtr & /*partition*/) {} - /// Returns true if Storage may store some data on disk. /// NOTE: may not be equivalent to !getDataPaths().empty() virtual bool storesDataOnDisk() const { return false; } diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 0a318276059..d6b25a5a6ef 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -226,6 +226,7 @@ public: */ enum class State { + ///TODO Transactions: rename Committed to Active, because it becomes confusing Temporary, /// the part is generating now, it is not in data_parts list PreCommitted, /// the part is in data_parts, but not used for SELECTs Committed, /// active data part, used by current and upcoming SELECTs diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index cfb5a32ae9e..d14a4d9260f 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -3142,9 +3142,23 @@ MergeTreeData::DataPartPtr MergeTreeData::getActiveContainingPart(const String & return getActiveContainingPart(part_info); } -MergeTreeData::DataPartsVector MergeTreeData::getDataPartsVectorInPartition(MergeTreeData::DataPartState state, const String & partition_id) const +MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVectorInPartition(ContextPtr local_context, const String & partition_id) const { - DataPartStateAndPartitionID state_with_partition{state, partition_id}; + if (const auto * txn = local_context->getCurrentTransaction().get()) + { + DataPartStateAndPartitionID active_parts{MergeTreeDataPartState::Committed, partition_id}; + DataPartStateAndPartitionID outdated_parts{MergeTreeDataPartState::Outdated, partition_id}; + DataPartsVector res; + { + auto lock = lockParts(); + res.insert(res.end(), data_parts_by_state_and_info.lower_bound(active_parts), data_parts_by_state_and_info.upper_bound(active_parts)); + res.insert(res.end(), data_parts_by_state_and_info.lower_bound(outdated_parts), data_parts_by_state_and_info.upper_bound(outdated_parts)); + } + filterVisibleDataParts(res, txn->getSnapshot(), txn->tid); + return res; + } + + DataPartStateAndPartitionID state_with_partition{MergeTreeDataPartState::Committed, partition_id}; auto lock = lockParts(); return DataPartsVector( @@ -3152,19 +3166,37 @@ MergeTreeData::DataPartsVector MergeTreeData::getDataPartsVectorInPartition(Merg data_parts_by_state_and_info.upper_bound(state_with_partition)); } -MergeTreeData::DataPartsVector MergeTreeData::getDataPartsVectorInPartitions(MergeTreeData::DataPartState state, const std::unordered_set & partition_ids) const +MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVectorInPartitions(ContextPtr local_context, const std::unordered_set & partition_ids) const { - auto lock = lockParts(); + auto txn = local_context->getCurrentTransaction(); DataPartsVector res; - for (const auto & partition_id : partition_ids) { - DataPartStateAndPartitionID state_with_partition{state, partition_id}; - insertAtEnd( - res, - DataPartsVector( - data_parts_by_state_and_info.lower_bound(state_with_partition), - data_parts_by_state_and_info.upper_bound(state_with_partition))); + auto lock = lockParts(); + for (const auto & partition_id : partition_ids) + { + DataPartStateAndPartitionID active_parts{MergeTreeDataPartState::Committed, partition_id}; + insertAtEnd( + res, + DataPartsVector( + data_parts_by_state_and_info.lower_bound(active_parts), + data_parts_by_state_and_info.upper_bound(active_parts))); + + if (txn) + { + DataPartStateAndPartitionID outdated_parts{MergeTreeDataPartState::Committed, partition_id}; + + insertAtEnd( + res, + DataPartsVector( + data_parts_by_state_and_info.lower_bound(outdated_parts), + data_parts_by_state_and_info.upper_bound(outdated_parts))); + } + } } + + if (txn) + filterVisibleDataParts(res, txn->getSnapshot(), txn->tid); + return res; } @@ -3295,10 +3327,10 @@ void MergeTreeData::checkAlterPartitionIsPossible( } } -void MergeTreeData::checkPartitionCanBeDropped(const ASTPtr & partition) +void MergeTreeData::checkPartitionCanBeDropped(const ASTPtr & partition, ContextPtr local_context) { - const String partition_id = getPartitionIDFromQuery(partition, getContext()); - auto parts_to_remove = getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); + const String partition_id = getPartitionIDFromQuery(partition, local_context); + auto parts_to_remove = getVisibleDataPartsVectorInPartition(local_context, partition_id); UInt64 partition_size = 0; @@ -3337,7 +3369,7 @@ void MergeTreeData::movePartitionToDisk(const ASTPtr & partition, const String & throw Exception("Part " + partition_id + " is not exists or not active", ErrorCodes::NO_SUCH_DATA_PART); } else - parts = getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); + parts = getVisibleDataPartsVectorInPartition(local_context, partition_id); auto disk = getStoragePolicy()->getDiskByName(name); if (!disk) @@ -3382,7 +3414,7 @@ void MergeTreeData::movePartitionToVolume(const ASTPtr & partition, const String throw Exception("Part " + partition_id + " is not exists or not active", ErrorCodes::NO_SUCH_DATA_PART); } else - parts = getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); + parts = getVisibleDataPartsVectorInPartition(local_context, partition_id); auto volume = getStoragePolicy()->getVolumeByName(name); if (!volume) @@ -3454,7 +3486,7 @@ Pipe MergeTreeData::alterPartition( } else { - checkPartitionCanBeDropped(command.partition); + checkPartitionCanBeDropped(command.partition, query_context); dropPartition(command.partition, command.detach, query_context); } } @@ -3503,7 +3535,7 @@ Pipe MergeTreeData::alterPartition( case PartitionCommand::REPLACE_PARTITION: { if (command.replace) - checkPartitionCanBeDropped(command.partition); + checkPartitionCanBeDropped(command.partition, query_context); String from_database = query_context->resolveDatabase(command.from_database); auto from_storage = DatabaseCatalog::instance().getTable({from_database, command.from_table}, query_context); replacePartitionFrom(from_storage, command.partition, command.replace, query_context); @@ -3564,7 +3596,7 @@ BackupEntries MergeTreeData::backup(const ASTs & partitions, ContextPtr local_co if (partitions.empty()) data_parts = getDataPartsVector(); else - data_parts = getDataPartsVectorInPartitions(MergeTreeDataPartState::Committed, getPartitionIDsFromQuery(partitions, local_context)); + data_parts = getVisibleDataPartsVectorInPartitions(local_context, getPartitionIDsFromQuery(partitions, local_context)); return backupDataParts(data_parts); } @@ -3771,26 +3803,54 @@ String MergeTreeData::getPartitionIDFromQuery(const ASTPtr & ast, ContextPtr loc } -DataPartsVector MergeTreeData::getDataPartsVector(ContextPtr local_context) const +DataPartsVector MergeTreeData::getVisibleDataPartsVector(ContextPtr local_context) const { - return getVisibleDataPartsVector(local_context->getCurrentTransaction()); + DataPartsVector res; + if (const auto * txn = local_context->getCurrentTransaction().get()) + { + res = getDataPartsVector({DataPartState::Committed, DataPartState::Outdated}); + filterVisibleDataParts(res, txn->getSnapshot(), txn->tid); + } + else + { + res = getDataPartsVector(); + } + return res; } MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVector(const MergeTreeTransactionPtr & txn) const { - if (!txn) - return getDataPartsVector(); + DataPartsVector res; + if (txn) + { + res = getDataPartsVector({DataPartState::Committed, DataPartState::Outdated}); + filterVisibleDataParts(res, txn->getSnapshot(), txn->tid); + } + else + { + res = getDataPartsVector(); + } + return res; +} - DataPartsVector maybe_visible_parts = getDataPartsVector({DataPartState::PreCommitted, DataPartState::Committed, DataPartState::Outdated}); +MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVector(Snapshot snapshot_version, TransactionID current_tid) const +{ + auto res = getDataPartsVector({DataPartState::Committed, DataPartState::Outdated}); + filterVisibleDataParts(res, snapshot_version, current_tid); + return res; +} + +void MergeTreeData::filterVisibleDataParts(DataPartsVector & maybe_visible_parts, Snapshot snapshot_version, TransactionID current_tid) const +{ if (maybe_visible_parts.empty()) - return maybe_visible_parts; + return; auto it = maybe_visible_parts.begin(); auto it_last = maybe_visible_parts.end() - 1; String visible_parts_str; while (it <= it_last) { - if ((*it)->versions.isVisible(*txn)) + if ((*it)->versions.isVisible(snapshot_version, current_tid)) { visible_parts_str += (*it)->name; visible_parts_str += " "; @@ -3804,9 +3864,8 @@ MergeTreeData::DataPartsVector MergeTreeData::getVisibleDataPartsVector(const Me } size_t new_size = it_last - maybe_visible_parts.begin() + 1; - LOG_TRACE(log, "Got {} parts visible for {}: {}", new_size, txn->tid, visible_parts_str); + LOG_TEST(log, "Got {} parts visible in snapshot {} (TID {}): {}", new_size, snapshot_version, current_tid, visible_parts_str); maybe_visible_parts.resize(new_size); - return maybe_visible_parts; } @@ -4238,7 +4297,7 @@ MergeTreeData::DataParts MergeTreeData::getDataParts(const DataPartStates & affo return res; } -MergeTreeData::DataParts MergeTreeData::getDataParts() const +MergeTreeData::DataParts MergeTreeData::getDataPartsForInternalUsage() const { return getDataParts({DataPartState::Committed}); } @@ -4879,7 +4938,7 @@ bool MergeTreeData::getQueryProcessingStageWithAggregateProjection( max_added_blocks = std::make_shared(replicated->getMaxAddedBlocks()); } - auto parts = getDataPartsVector(query_context); + auto parts = getVisibleDataPartsVector(query_context); // If minmax_count_projection is a valid candidate, check its completeness. if (minmax_conut_projection_candidate) @@ -5237,7 +5296,7 @@ PartitionCommandsResultInfo MergeTreeData::freezePartitionsByMatcher( const String shadow_path = "shadow/"; /// Acquire a snapshot of active data parts to prevent removing while doing backup. - const auto data_parts = getDataParts(); + const auto data_parts = getVisibleDataPartsVector(local_context); String backup_name = (!with_name.empty() ? escapeForFileName(with_name) : toString(increment)); String backup_path = fs::path(shadow_path) / backup_name / ""; diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index bf00db2c5c1..903cf0f979c 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -425,6 +425,7 @@ public: Int64 getMaxBlockNumber() const; + /// Returns a copy of the list so that the caller shouldn't worry about locks. DataParts getDataParts(const DataPartStates & affordable_states) const; @@ -436,24 +437,18 @@ public: /// Returns absolutely all parts (and snapshot of their states) DataPartsVector getAllDataPartsVector(DataPartStateVector * out_states = nullptr, bool require_projection_parts = false) const; - /// Returns all detached parts - DetachedPartsInfo getDetachedParts() const; - - void validateDetachedPartName(const String & name) const; - - void dropDetached(const ASTPtr & partition, bool part, ContextPtr context); - - MutableDataPartsVector tryLoadPartsToAttach(const ASTPtr & partition, bool attach_part, - ContextPtr context, PartsTemporaryRename & renamed_parts); - - /// Returns Committed parts - DataParts getDataParts() const; + /// Returns parts in Committed state (NOT in terms of transactions, should be used carefully) + DataParts getDataPartsForInternalUsage() const; DataPartsVector getDataPartsVector() const; - DataPartsVector getDataPartsVector(ContextPtr local_context) const; - DataPartsVector getVisibleDataPartsVector(const MergeTreeTransactionPtr & txn) const; + void filterVisibleDataParts(DataPartsVector & maybe_visible_parts, Snapshot snapshot_version, TransactionID current_tid) const; - /// Returns a committed part with the given name or a part containing it. If there is no such part, returns nullptr. + /// Returns parts that visible with current snapshot + DataPartsVector getVisibleDataPartsVector(ContextPtr local_context) const; + DataPartsVector getVisibleDataPartsVector(const MergeTreeTransactionPtr & txn) const; + DataPartsVector getVisibleDataPartsVector(Snapshot snapshot_version, TransactionID current_tid) const; + + /// Returns a part in Committed state with the given name or a part containing it. If there is no such part, returns nullptr. DataPartPtr getActiveContainingPart(const String & part_name) const; DataPartPtr getActiveContainingPart(const MergeTreePartInfo & part_info) const; DataPartPtr getActiveContainingPart(const MergeTreePartInfo & part_info, DataPartState state, DataPartsLock & lock) const; @@ -463,8 +458,8 @@ public: void swapActivePart(MergeTreeData::DataPartPtr part_copy); /// Returns all parts in specified partition - DataPartsVector getDataPartsVectorInPartition(DataPartState state, const String & partition_id) const; - DataPartsVector getDataPartsVectorInPartitions(DataPartState state, const std::unordered_set & partition_ids) const; + DataPartsVector getVisibleDataPartsVectorInPartition(ContextPtr local_context, const String & partition_id) const; + DataPartsVector getVisibleDataPartsVectorInPartitions(ContextPtr local_context, const std::unordered_set & partition_ids) const; /// Returns the part with the given name and state or nullptr if no such part. DataPartPtr getPartIfExists(const String & part_name, const DataPartStates & valid_states); @@ -484,6 +479,18 @@ public: /// Makes sense only for ordinary MergeTree engines because for them block numbering doesn't depend on partition. std::optional getMinPartDataVersion() const; + + /// Returns all detached parts + DetachedPartsInfo getDetachedParts() const; + + void validateDetachedPartName(const String & name) const; + + void dropDetached(const ASTPtr & partition, bool part, ContextPtr context); + + MutableDataPartsVector tryLoadPartsToAttach(const ASTPtr & partition, bool attach_part, + ContextPtr context, PartsTemporaryRename & renamed_parts); + + /// If the table contains too many active parts, sleep for a while to give them time to merge. /// If until is non-null, wake up from the sleep earlier if the event happened. void delayInsertOrThrowIfNeeded(Poco::Event * until = nullptr) const; @@ -656,7 +663,10 @@ public: /// Moves partition to specified Volume void movePartitionToVolume(const ASTPtr & partition, const String & name, bool moving_part, ContextPtr context); - void checkPartitionCanBeDropped(const ASTPtr & partition) override; + /// Checks that Partition could be dropped right now + /// Otherwise - throws an exception with detailed information. + /// We do not use mutex because it is not very important that the size could change during the operation. + void checkPartitionCanBeDropped(const ASTPtr & partition, ContextPtr local_context); void checkPartCanBeDropped(const String & part_name); diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 3a6944df633..7fa55005f93 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -395,7 +395,7 @@ MergeTreeData::DataPartsVector MergeTreeDataMergerMutator::selectAllPartsFromPar { MergeTreeData::DataPartsVector parts_from_partition; - MergeTreeData::DataParts data_parts = data.getDataParts(); + MergeTreeData::DataParts data_parts = data.getDataPartsForInternalUsage(); for (const auto & current_part : data_parts) { diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 1fc61012e6f..c766c775ac7 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -130,7 +130,7 @@ QueryPlanPtr MergeTreeDataSelectExecutor::read( return std::make_unique(); const auto & settings = context->getSettingsRef(); - auto parts = data.getDataPartsVector(context); + auto parts = data.getVisibleDataPartsVector(context); if (!query_info.projection) { diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index e1165dbe3c8..ec7504715e8 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -87,7 +87,7 @@ StorageMergeTree::StorageMergeTree( { loadDataParts(has_force_restore_data_flag); - if (!attach && !getDataParts().empty()) + if (!attach && !getDataPartsForInternalUsage().empty()) throw Exception("Data directory for table already containing data parts - probably it was unclean DROP table or manual intervention. You must either clear directory by hand or use ATTACH TABLE instead of CREATE TABLE if you need to use that parts.", ErrorCodes::INCORRECT_DATA); increment.set(getMaxBlockNumber()); @@ -258,7 +258,7 @@ void StorageMergeTree::truncate(const ASTPtr &, const StorageMetadataPtr &, Cont /// This protects against "revival" of data for a removed partition after completion of merge. auto merge_blocker = stopMergesAndWait(); - auto parts_to_remove = getDataPartsVector(); + auto parts_to_remove = getVisibleDataPartsVector(local_context); removePartsFromWorkingSet(local_context->getCurrentTransaction().get(), parts_to_remove, true); LOG_INFO(log, "Removed {} parts.", parts_to_remove.size()); @@ -713,9 +713,9 @@ std::shared_ptr StorageMergeTree::selectPartsToMerge( { /// Cannot merge parts if some of them is not visible in current snapshot /// TODO We can use simplified visibility rules (without CSN lookup) here - if (left && !left->versions.isVisible(*tx)) + if (left && !left->versions.isVisible(tx->getSnapshot(), Tx::EmptyTID)) return false; - if (right && !right->versions.isVisible(*tx)) + if (right && !right->versions.isVisible(tx->getSnapshot(), Tx::EmptyTID)) return false; } @@ -1288,7 +1288,7 @@ void StorageMergeTree::dropPartition(const ASTPtr & partition, bool detach, Cont /// This protects against "revival" of data for a removed partition after completion of merge. auto merge_blocker = stopMergesAndWait(); String partition_id = getPartitionIDFromQuery(partition, local_context); - parts_to_remove = getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); + parts_to_remove = getVisibleDataPartsVectorInPartition(local_context, partition_id); /// TODO should we throw an exception if parts_to_remove is empty? removePartsFromWorkingSet(local_context->getCurrentTransaction().get(), parts_to_remove, true); @@ -1370,7 +1370,7 @@ void StorageMergeTree::replacePartitionFrom(const StoragePtr & source_table, con MergeTreeData & src_data = checkStructureAndGetMergeTreeData(source_table, source_metadata_snapshot, my_metadata_snapshot); String partition_id = getPartitionIDFromQuery(partition, local_context); - DataPartsVector src_parts = src_data.getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); + DataPartsVector src_parts = src_data.getVisibleDataPartsVectorInPartition(local_context, partition_id); MutableDataPartsVector dst_parts; static const String TMP_PREFIX = "tmp_replace_from_"; @@ -1455,7 +1455,7 @@ void StorageMergeTree::movePartitionToTable(const StoragePtr & dest_table, const MergeTreeData & src_data = dest_table_storage->checkStructureAndGetMergeTreeData(*this, metadata_snapshot, dest_metadata_snapshot); String partition_id = getPartitionIDFromQuery(partition, local_context); - DataPartsVector src_parts = src_data.getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); + DataPartsVector src_parts = src_data.getVisibleDataPartsVectorInPartition(local_context, partition_id); MutableDataPartsVector dst_parts; static const String TMP_PREFIX = "tmp_move_from_"; @@ -1535,7 +1535,7 @@ CheckResults StorageMergeTree::checkData(const ASTPtr & query, ContextPtr local_ if (const auto & check_query = query->as(); check_query.partition) { String partition_id = getPartitionIDFromQuery(check_query.partition, local_context); - data_parts = getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); + data_parts = getVisibleDataPartsVectorInPartition(local_context, partition_id); } else data_parts = getDataPartsVector(); diff --git a/src/Storages/StorageProxy.h b/src/Storages/StorageProxy.h index 304f84c02eb..c3f4536da82 100644 --- a/src/Storages/StorageProxy.h +++ b/src/Storages/StorageProxy.h @@ -145,7 +145,6 @@ public: CheckResults checkData(const ASTPtr & query , ContextPtr context) override { return getNested()->checkData(query, context); } void checkTableCanBeDropped() const override { getNested()->checkTableCanBeDropped(); } - void checkPartitionCanBeDropped(const ASTPtr & partition) override { getNested()->checkPartitionCanBeDropped(partition); } bool storesDataOnDisk() const override { return getNested()->storesDataOnDisk(); } Strings getDataPaths() const override { return getNested()->getDataPaths(); } StoragePolicyPtr getStoragePolicy() const override { return getNested()->getStoragePolicy(); } diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 306d4d4f7e9..3a0aa3089a2 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -405,7 +405,7 @@ StorageReplicatedMergeTree::StorageReplicatedMergeTree( if (!attach) { - if (!getDataParts().empty()) + if (!getDataPartsForInternalUsage().empty()) throw Exception("Data directory for table already contains data parts" " - probably it was unclean DROP table or manual intervention." " You must either clear directory by hand or use ATTACH TABLE" @@ -2452,7 +2452,7 @@ void StorageReplicatedMergeTree::cloneReplica(const String & source_replica, Coo tryRemovePartsFromZooKeeperWithRetries(parts_to_remove_from_zk); - auto local_active_parts = getDataParts(); + auto local_active_parts = getDataPartsForInternalUsage(); DataPartsVector parts_to_remove_from_working_set; @@ -4187,7 +4187,7 @@ ReplicatedMergeTreeQuorumAddedParts::PartitionIdToMaxBlock StorageReplicatedMerg { ReplicatedMergeTreeQuorumAddedParts::PartitionIdToMaxBlock max_added_blocks; - for (const auto & data_part : getDataParts()) + for (const auto & data_part : getDataPartsForInternalUsage()) { max_added_blocks[data_part->info.partition_id] = std::max(max_added_blocks[data_part->info.partition_id], data_part->info.max_block); @@ -4293,6 +4293,7 @@ void StorageReplicatedMergeTree::foreachCommittedParts(Func && func, bool select max_added_blocks = getMaxAddedBlocks(); auto lock = lockParts(); + /// TODO Transactions: should we count visible parts only? for (const auto & part : getDataPartsStateRange(DataPartState::Committed)) { if (part->isEmpty()) @@ -6246,7 +6247,7 @@ void StorageReplicatedMergeTree::replacePartitionFrom( String partition_id = getPartitionIDFromQuery(partition, query_context); /// NOTE: Some covered parts may be missing in src_all_parts if corresponding log entries are not executed yet. - DataPartsVector src_all_parts = src_data.getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); + DataPartsVector src_all_parts = src_data.getVisibleDataPartsVectorInPartition(query_context, partition_id); LOG_DEBUG(log, "Cloning {} parts", src_all_parts.size()); @@ -7068,7 +7069,7 @@ CheckResults StorageReplicatedMergeTree::checkData(const ASTPtr & query, Context if (const auto & check_query = query->as(); check_query.partition) { String partition_id = getPartitionIDFromQuery(check_query.partition, local_context); - data_parts = getDataPartsVectorInPartition(MergeTreeDataPartState::Committed, partition_id); + data_parts = getVisibleDataPartsVectorInPartition(local_context, partition_id); } else data_parts = getDataPartsVector(); diff --git a/tests/queries/0_stateless/01169_alter_partition_isolation_stress.reference b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.reference new file mode 100644 index 00000000000..bf9c6e88bb2 --- /dev/null +++ b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.reference @@ -0,0 +1,6 @@ +1 1 +2 1 +3 1 +4 1 +1 +10 100 diff --git a/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh new file mode 100755 index 00000000000..f05a3ec2b24 --- /dev/null +++ b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# Tags: long + +# shellcheck disable=SC2015 + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +set -e + +$CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS src"; +$CLICKHOUSE_CLIENT --query "DROP TABLE IF EXISTS dst"; +$CLICKHOUSE_CLIENT --query "CREATE TABLE src (n UInt64, type UInt8) ENGINE=MergeTree ORDER BY type SETTINGS old_parts_lifetime=0"; +$CLICKHOUSE_CLIENT --query "CREATE TABLE dst (n UInt64, type UInt8) ENGINE=MergeTree ORDER BY type SETTINGS old_parts_lifetime=0"; + +function thread_insert() +{ + set -e + trap "exit 0" INT + while true; do + action="ROLLBACK" + if (( RANDOM % 2 )); then + action="COMMIT" + fi + val=$RANDOM + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + INSERT INTO src VALUES ($val, 1); + INSERT INTO src VALUES ($val, 2); + COMMIT;" + sleep 0.$RANDOM; + done +} + + +# NOTE +# ALTER PARTITION query stops merges, +# but serialization error is still possible if some merge was assigned (and committed) between BEGIN and ALTER. +function thread_partition_src_to_dst() +{ + set -e + count=0 + sum=0 + for i in {1..20}; do + out=$( + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + INSERT INTO src VALUES ($i, 3); + INSERT INTO dst SELECT * FROM src; + ALTER TABLE src DROP PARTITION ID 'all'; + SELECT throwIf((SELECT (count(), sum(n)) FROM merge(currentDatabase(), '') WHERE type=3) != ($count + 1, $sum + $i)) FORMAT Null; + COMMIT;" 2>&1) ||: + + echo "$out" | grep -Fv "SERIALIZATION_ERROR" | grep -F "Received from " && $CLICKHOUSE_CLIENT -q "SELECT _table, type, arraySort(groupArray(n)) FROM merge(currentDatabase(), '') GROUP BY _table, type ORDER BY _table, type" ||: + echo "$out" | grep -Fa "SERIALIZATION_ERROR" >/dev/null || count=$((count+1)) && sum=$((sum+i)) + done +} + +function thread_partition_dst_to_src() +{ + set -e + for i in {1..20}; do + action="ROLLBACK" + if (( i % 2 )); then + action="COMMIT" + fi + $CLICKHOUSE_CLIENT --multiquery --query " + SYSTEM STOP MERGES dst; + BEGIN TRANSACTION; + INSERT INTO dst VALUES ($i, 4); + INSERT INTO src SELECT * FROM dst; + ALTER TABLE dst DROP PARTITION ID 'all'; + SYSTEM START MERGES dst; + SELECT throwIf((SELECT (count(), sum(n)) FROM merge(currentDatabase(), '') WHERE type=4) != (toUInt8($i/2 + 1), (select sum(number) from numbers(1, $i) where number % 2 or number=$i))) FORMAT Null; + $action;" || $CLICKHOUSE_CLIENT -q "SELECT _table, type, arraySort(groupArray(n)) FROM merge(currentDatabase(), '') GROUP BY _table, type ORDER BY _table, type" + done +} + +function thread_select() +{ + set -e + trap "exit 0" INT + while true; do + $CLICKHOUSE_CLIENT --multiquery --query " + BEGIN TRANSACTION; + -- no duplicates + SELECT type, throwIf(count(n) != countDistinct(n)) FROM src GROUP BY type FORMAT Null; + SELECT type, throwIf(count(n) != countDistinct(n)) FROM dst GROUP BY type FORMAT Null; + -- rows inserted by thread_insert moved together + SELECT _table, throwIf(arraySort(groupArrayIf(n, type=1)) != arraySort(groupArrayIf(n, type=2))) FROM merge(currentDatabase(), '') GROUP BY _table FORMAT Null; + COMMIT;" || $CLICKHOUSE_CLIENT -q "SELECT _table, type, arraySort(groupArray(n)) FROM merge(currentDatabase(), '') GROUP BY _table, type ORDER BY _table, type" + done +} + +thread_insert & PID_1=$! +thread_select & PID_2=$! + +thread_partition_src_to_dst & PID_3=$! +thread_partition_dst_to_src & PID_4=$! +wait $PID_3 && wait $PID_4 + +kill -INT $PID_1 +kill -INT $PID_2 +wait + +$CLICKHOUSE_CLIENT -q "SELECT type, count(n) = countDistinct(n) FROM merge(currentDatabase(), '') GROUP BY type ORDER BY type" +$CLICKHOUSE_CLIENT -q "SELECT DISTINCT arraySort(groupArrayIf(n, type=1)) = arraySort(groupArrayIf(n, type=2)) FROM merge(currentDatabase(), '') GROUP BY _table ORDER BY _table" +$CLICKHOUSE_CLIENT -q "SELECT count(n), sum(n) FROM merge(currentDatabase(), '') WHERE type=4" + + +$CLICKHOUSE_CLIENT --query "DROP TABLE src"; +$CLICKHOUSE_CLIENT --query "DROP TABLE dst"; diff --git a/tests/queries/0_stateless/01170_alter_partition_isolation.reference b/tests/queries/0_stateless/01170_alter_partition_isolation.reference new file mode 100644 index 00000000000..fc772355a57 --- /dev/null +++ b/tests/queries/0_stateless/01170_alter_partition_isolation.reference @@ -0,0 +1,30 @@ +1 1 +2 3 +3 2 +3 4 +4 3 + +5 3 +5 5 + +6 3 +6 5 +6 6 +7 8 +8 3 +8 5 +8 7 +8 9 +SERIALIZATION_ERROR +INVALID_TRANSACTION +9 8 + +10 8 + +11 8 +11 11 +11 12 +12 8 +12 8 +12 11 +12 12 diff --git a/tests/queries/0_stateless/01170_alter_partition_isolation.sh b/tests/queries/0_stateless/01170_alter_partition_isolation.sh new file mode 100755 index 00000000000..4174f8215fe --- /dev/null +++ b/tests/queries/0_stateless/01170_alter_partition_isolation.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh +# shellcheck source=./transactions.lib +. "$CURDIR"/transactions.lib + +$CLICKHOUSE_CLIENT -q "drop table if exists mt" +$CLICKHOUSE_CLIENT -q "create table mt (n int) engine=MergeTree order by n" + +tx 1 "begin transaction" +tx 1 "insert into mt values (1)" +tx 2 "begin transaction" +tx 2 "insert into mt values (2)" +tx 1 "select 1, n from mt order by n" +tx 1 "alter table mt drop partition id 'all'" +tx 2 "insert into mt values (4)" +tx 1 "insert into mt values (3)" +tx 1 "select 2, n from mt order by n" +tx 2 "select 3, n from mt order by n" +tx 2 "alter table mt drop partition id 'all'" +tx 2 "insert into mt values (5)" +tx 1 "select 4, n from mt order by n" +tx 2 "commit" +tx 1 "commit" + +echo '' +$CLICKHOUSE_CLIENT -q "select 5, n from mt order by n" +echo '' + +tx 4 "begin transaction" +tx 4 "insert into mt values (6)" +tx 3 "begin transaction" +tx 3 "insert into mt values (7)" +tx 4 "select 6, n from mt order by n" +tx 4 "alter table mt drop partition id 'all'" +tx 3 "insert into mt values (9)" +tx 4 "insert into mt values (8)" +tx 4 "select 7, n from mt order by n" +tx 3 "select 8, n from mt order by n" +tx 3 "alter table mt drop partition id 'all'" | grep -Eo "SERIALIZATION_ERROR" | uniq +tx 3 "insert into mt values (10)" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 4 "select 9, n from mt order by n" +tx 3 "rollback" +tx 4 "commit" + +echo '' +$CLICKHOUSE_CLIENT -q "select 10, n from mt order by n" +echo '' + +$CLICKHOUSE_CLIENT -q "drop table if exists another_mt" +$CLICKHOUSE_CLIENT -q "create table another_mt (n int) engine=MergeTree order by n" + +tx 5 "begin transaction" +tx 5 "insert into another_mt values (11)" +tx 6 "begin transaction" +tx 6 "insert into mt values (12)" +tx 6 "insert into another_mt values (13)" +tx 5 "alter table another_mt move partition id 'all' to table mt" +tx 6 "alter table another_mt replace partition id 'all' from mt" +tx 5 "alter table another_mt attach partition id 'all' from mt" +tx 5 "commit" +tx 6 "commit" + +$CLICKHOUSE_CLIENT -q "select 11, n from mt order by n" +$CLICKHOUSE_CLIENT -q "select 12, n from another_mt order by n" + +$CLICKHOUSE_CLIENT -q "drop table another_mt" +$CLICKHOUSE_CLIENT -q "drop table mt" diff --git a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh index c48f86f4aee..672a49df5fc 100755 --- a/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh +++ b/tests/queries/0_stateless/01171_mv_select_insert_isolation_long.sh @@ -22,6 +22,7 @@ $CLICKHOUSE_CLIENT --query "INSERT INTO src VALUES (0, 0)" # some transactions will fail due to constraint function thread_insert_commit() { + set -e for i in {1..100}; do $CLICKHOUSE_CLIENT --multiquery --query " BEGIN TRANSACTION; @@ -34,6 +35,7 @@ function thread_insert_commit() function thread_insert_rollback() { + set -e for _ in {1..100}; do $CLICKHOUSE_CLIENT --multiquery --query " BEGIN TRANSACTION; @@ -46,11 +48,17 @@ function thread_insert_rollback() # make merges more aggressive function thread_optimize() { + set -e trap "exit 0" INT while true; do optimize_query="OPTIMIZE TABLE src" + partition_id=$(( RANDOM % 2 )) if (( RANDOM % 2 )); then optimize_query="OPTIMIZE TABLE dst" + partition_id="all" + fi + if (( RANDOM % 2 )); then + optimize_query="$optimize_query PARTITION ID '$partition_id'" fi if (( RANDOM % 2 )); then optimize_query="$optimize_query FINAL" @@ -71,6 +79,7 @@ function thread_optimize() function thread_select() { + set -e trap "exit 0" INT while true; do $CLICKHOUSE_CLIENT --multiquery --query " @@ -86,6 +95,7 @@ function thread_select() function thread_select_insert() { + set -e trap "exit 0" INT while true; do $CLICKHOUSE_CLIENT --multiquery --query " diff --git a/tests/queries/0_stateless/transactions.lib b/tests/queries/0_stateless/transactions.lib new file mode 100755 index 00000000000..2d3eafc784a --- /dev/null +++ b/tests/queries/0_stateless/transactions.lib @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +function tx() +{ + tx_num=$1 + query=$2 + + url_without_session="https://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTPS}/?" + session="${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}_$tx_num" + url="${url_without_session}session_id=$session&database=$CLICKHOUSE_DATABASE" + + ${CLICKHOUSE_CURL} -m 30 -sSk "$url" --data "$query" +} + From f2dbeee6c2aa3b6e391defbada3f785d28cf66d6 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 17 Nov 2021 23:03:15 +0300 Subject: [PATCH 0100/1647] fix test --- .../0_stateless/01169_alter_partition_isolation_stress.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh index f05a3ec2b24..693c332dd1c 100755 --- a/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh +++ b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh @@ -53,7 +53,8 @@ function thread_partition_src_to_dst() COMMIT;" 2>&1) ||: echo "$out" | grep -Fv "SERIALIZATION_ERROR" | grep -F "Received from " && $CLICKHOUSE_CLIENT -q "SELECT _table, type, arraySort(groupArray(n)) FROM merge(currentDatabase(), '') GROUP BY _table, type ORDER BY _table, type" ||: - echo "$out" | grep -Fa "SERIALIZATION_ERROR" >/dev/null || count=$((count+1)) && sum=$((sum+i)) + echo "$out" | grep -Fa "SERIALIZATION_ERROR" >/dev/null || count=$((count+1)) + echo "$out" | grep -Fa "SERIALIZATION_ERROR" >/dev/null || sum=$((sum+i)) done } From 600087265806c40b1b9c388993b716ed1f8ed308 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 18 Nov 2021 15:06:22 +0300 Subject: [PATCH 0101/1647] fix tests --- .../01169_alter_partition_isolation_stress.reference | 2 ++ .../0_stateless/01169_alter_partition_isolation_stress.sh | 8 ++++++-- .../0_stateless/01170_alter_partition_isolation.sh | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/01169_alter_partition_isolation_stress.reference b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.reference index bf9c6e88bb2..12b941eab50 100644 --- a/tests/queries/0_stateless/01169_alter_partition_isolation_stress.reference +++ b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.reference @@ -4,3 +4,5 @@ 4 1 1 10 100 +1 1 1 +2 1 1 diff --git a/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh index 693c332dd1c..4506065484d 100755 --- a/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh +++ b/tests/queries/0_stateless/01169_alter_partition_isolation_stress.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: long +# Tags: long, no-replicated-database # shellcheck disable=SC2015 @@ -18,17 +18,18 @@ function thread_insert() { set -e trap "exit 0" INT + val=1 while true; do action="ROLLBACK" if (( RANDOM % 2 )); then action="COMMIT" fi - val=$RANDOM $CLICKHOUSE_CLIENT --multiquery --query " BEGIN TRANSACTION; INSERT INTO src VALUES ($val, 1); INSERT INTO src VALUES ($val, 2); COMMIT;" + val=$((val+1)) sleep 0.$RANDOM; done } @@ -90,6 +91,8 @@ function thread_select() SELECT type, throwIf(count(n) != countDistinct(n)) FROM dst GROUP BY type FORMAT Null; -- rows inserted by thread_insert moved together SELECT _table, throwIf(arraySort(groupArrayIf(n, type=1)) != arraySort(groupArrayIf(n, type=2))) FROM merge(currentDatabase(), '') GROUP BY _table FORMAT Null; + -- all rows are inserted in insert_thread + SELECT type, throwIf(count(n) != max(n)), throwIf(sum(n) != max(n)*(max(n)+1)/2) FROM merge(currentDatabase(), '') WHERE type IN (1, 2) GROUP BY type ORDER BY type FORMAT Null; COMMIT;" || $CLICKHOUSE_CLIENT -q "SELECT _table, type, arraySort(groupArray(n)) FROM merge(currentDatabase(), '') GROUP BY _table, type ORDER BY _table, type" done } @@ -108,6 +111,7 @@ wait $CLICKHOUSE_CLIENT -q "SELECT type, count(n) = countDistinct(n) FROM merge(currentDatabase(), '') GROUP BY type ORDER BY type" $CLICKHOUSE_CLIENT -q "SELECT DISTINCT arraySort(groupArrayIf(n, type=1)) = arraySort(groupArrayIf(n, type=2)) FROM merge(currentDatabase(), '') GROUP BY _table ORDER BY _table" $CLICKHOUSE_CLIENT -q "SELECT count(n), sum(n) FROM merge(currentDatabase(), '') WHERE type=4" +$CLICKHOUSE_CLIENT -q "SELECT type, count(n) == max(n), sum(n) == max(n)*(max(n)+1)/2 FROM merge(currentDatabase(), '') WHERE type IN (1, 2) GROUP BY type ORDER BY type" $CLICKHOUSE_CLIENT --query "DROP TABLE src"; diff --git a/tests/queries/0_stateless/01170_alter_partition_isolation.sh b/tests/queries/0_stateless/01170_alter_partition_isolation.sh index 4174f8215fe..2db178fb6d1 100755 --- a/tests/queries/0_stateless/01170_alter_partition_isolation.sh +++ b/tests/queries/0_stateless/01170_alter_partition_isolation.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Tags: no-fasttest, no-replicated-database +# Looks like server does not listen https port in fasttest +# FIXME Replicated database executes ALTERs in separate context, so transaction info is lost CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From 52885db5d741951a2e08ab38cbe104cf90332250 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 19 Nov 2021 19:51:03 +0300 Subject: [PATCH 0102/1647] remove strange multimap from mutations --- .../MergeTree/MergeTreeMutationEntry.cpp | 45 ++++++++++++++++--- .../MergeTree/MergeTreeMutationEntry.h | 10 +++-- src/Storages/StorageMergeTree.cpp | 23 +++++----- src/Storages/StorageMergeTree.h | 4 +- 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeMutationEntry.cpp b/src/Storages/MergeTree/MergeTreeMutationEntry.cpp index 2aefb3df2be..0f71742fb09 100644 --- a/src/Storages/MergeTree/MergeTreeMutationEntry.cpp +++ b/src/Storages/MergeTree/MergeTreeMutationEntry.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -10,7 +11,39 @@ namespace DB { -MergeTreeMutationEntry::MergeTreeMutationEntry(MutationCommands commands_, DiskPtr disk_, const String & path_prefix_, Int64 tmp_number) +namespace ErrorCodes +{ + extern const int BAD_ARGUMENTS; +} + +String MergeTreeMutationEntry::versionToFileName(UInt64 block_number_) +{ + assert(block_number_); + return fmt::format("mutation_{}.txt", block_number_); +} + +UInt64 MergeTreeMutationEntry::tryParseFileName(const String & file_name_) +{ + UInt64 maybe_block_number = 0; + ReadBufferFromString file_name_buf(file_name_); + if (!checkString("mutation_", file_name_buf)) + return 0; + if (!tryReadIntText(maybe_block_number, file_name_buf)) + return 0; + if (!checkString(".txt", file_name_buf)) + return 0; + assert(maybe_block_number); + return maybe_block_number; +} + +UInt64 MergeTreeMutationEntry::parseFileName(const String & file_name_) +{ + if (UInt64 maybe_block_number = tryParseFileName(file_name_)) + return maybe_block_number; + throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cannot parse mutation version from file name, expected 'mutation_.txt', got '{}'", file_name_); +} + +MergeTreeMutationEntry::MergeTreeMutationEntry(MutationCommands commands_, DiskPtr disk_, const String & path_prefix_, UInt64 tmp_number) : create_time(time(nullptr)) , commands(std::move(commands_)) , disk(std::move(disk_)) @@ -35,10 +68,11 @@ MergeTreeMutationEntry::MergeTreeMutationEntry(MutationCommands commands_, DiskP } } -void MergeTreeMutationEntry::commit(Int64 block_number_) +void MergeTreeMutationEntry::commit(UInt64 block_number_) { + assert(block_number_); block_number = block_number_; - String new_file_name = "mutation_" + toString(block_number) + ".txt"; + String new_file_name = versionToFileName(block_number); disk->moveFile(path_prefix + file_name, path_prefix + new_file_name); is_temp = false; file_name = new_file_name; @@ -62,10 +96,7 @@ MergeTreeMutationEntry::MergeTreeMutationEntry(DiskPtr disk_, const String & pat , file_name(file_name_) , is_temp(false) { - ReadBufferFromString file_name_buf(file_name); - file_name_buf >> "mutation_" >> block_number >> ".txt"; - assertEOF(file_name_buf); - + block_number = parseFileName(file_name); auto buf = disk->readFile(path_prefix + file_name); *buf >> "format version: 1\n"; diff --git a/src/Storages/MergeTree/MergeTreeMutationEntry.h b/src/Storages/MergeTree/MergeTreeMutationEntry.h index e01ce4320b3..7554a03836e 100644 --- a/src/Storages/MergeTree/MergeTreeMutationEntry.h +++ b/src/Storages/MergeTree/MergeTreeMutationEntry.h @@ -21,7 +21,7 @@ struct MergeTreeMutationEntry String file_name; bool is_temp = false; - Int64 block_number = 0; + UInt64 block_number = 0; String latest_failed_part; MergeTreePartInfo latest_failed_part_info; @@ -29,15 +29,19 @@ struct MergeTreeMutationEntry String latest_fail_reason; /// Create a new entry and write it to a temporary file. - MergeTreeMutationEntry(MutationCommands commands_, DiskPtr disk, const String & path_prefix_, Int64 tmp_number); + MergeTreeMutationEntry(MutationCommands commands_, DiskPtr disk, const String & path_prefix_, UInt64 tmp_number); MergeTreeMutationEntry(const MergeTreeMutationEntry &) = delete; MergeTreeMutationEntry(MergeTreeMutationEntry &&) = default; /// Commit entry and rename it to a permanent file. - void commit(Int64 block_number_); + void commit(UInt64 block_number_); void removeFile(); + static String versionToFileName(UInt64 block_number_); + static UInt64 tryParseFileName(const String & file_name_); + static UInt64 parseFileName(const String & file_name_); + /// Load an existing entry. MergeTreeMutationEntry(DiskPtr disk_, const String & path_prefix_, const String & file_name_); diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index ec7504715e8..d9231b25a70 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -412,8 +412,9 @@ Int64 StorageMergeTree::startMutation(const MutationCommands & commands, String version = increment.get(); entry.commit(version); mutation_file_name = entry.file_name; - auto insertion = current_mutations_by_id.emplace(mutation_file_name, std::move(entry)); - current_mutations_by_version.emplace(version, insertion.first->second); + bool inserted = current_mutations_by_version.try_emplace(version, std::move(entry)).second; + if (!inserted) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Mutation {} already exists, it's a bug", version); LOG_INFO(log, "Added mutation: {}", mutation_file_name); } @@ -618,16 +619,18 @@ std::vector StorageMergeTree::getMutationsStatus() cons CancellationCode StorageMergeTree::killMutation(const String & mutation_id) { LOG_TRACE(log, "Killing mutation {}", mutation_id); + UInt64 mutation_version = MergeTreeMutationEntry::tryParseFileName(mutation_id); + if (!mutation_version) + return CancellationCode::NotFound; std::optional to_kill; { std::lock_guard lock(currently_processing_in_background_mutex); - auto it = current_mutations_by_id.find(mutation_id); - if (it != current_mutations_by_id.end()) + auto it = current_mutations_by_version.find(mutation_version); + if (it != current_mutations_by_version.end()) { to_kill.emplace(std::move(it->second)); - current_mutations_by_id.erase(it); - current_mutations_by_version.erase(to_kill->block_number); + current_mutations_by_version.erase(it); } } @@ -668,10 +671,11 @@ void StorageMergeTree::loadMutations() if (startsWith(it->name(), "mutation_")) { MergeTreeMutationEntry entry(disk, path, it->name()); - Int64 block_number = entry.block_number; + UInt64 block_number = entry.block_number; LOG_DEBUG(log, "Loading mutation: {} entry, commands size: {}", it->name(), entry.commands.size()); - auto insertion = current_mutations_by_id.emplace(it->name(), std::move(entry)); - current_mutations_by_version.emplace(block_number, insertion.first->second); + auto inserted = current_mutations_by_version.try_emplace(block_number, std::move(entry)).second; + if (!inserted) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Mutation {} already exists, it's a bug", block_number); } else if (startsWith(it->name(), "tmp_mutation_")) { @@ -1111,7 +1115,6 @@ size_t StorageMergeTree::clearOldMutations(bool truncate) for (size_t i = 0; i < to_delete_count; ++i) { mutations_to_delete.push_back(std::move(it->second)); - current_mutations_by_id.erase(mutations_to_delete.back().file_name); it = current_mutations_by_version.erase(it); } } diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index dd737dbd5fa..9edef38fef4 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -131,9 +131,7 @@ private: /// This set have to be used with `currently_processing_in_background_mutex`. DataParts currently_merging_mutating_parts; - - std::map current_mutations_by_id; - std::multimap current_mutations_by_version; + std::map current_mutations_by_version; std::atomic shutdown_called {false}; From 18e60919c55d89fb674d92d6b66908a1f5433ea6 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Tue, 23 Nov 2021 16:49:59 +0300 Subject: [PATCH 0103/1647] 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 ca1c5c3e830..ed1dacf3d1a 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -241,7 +241,7 @@ then stop # Error messages (we should ignore some errors) - zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "DDLWorker: Cannot parse DDL task query" -e "RaftInstance: failed to accept a rpc connection due to error 125" -e "UNKNOWN_DATABASE" -e "NETWORK_ERROR" \ + zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "DDLWorker: Cannot parse DDL task query" -e "RaftInstance: failed to accept a rpc connection due to error 125" -e "UNKNOWN_DATABASE" -e "NETWORK_ERROR" -e "UNKNOWN_TABLE" \ /var/log/clickhouse-server/clickhouse-server.log | zgrep -Fa "" > /dev/null \ && echo -e 'Error message in clickhouse-server.log\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ || echo -e 'No Error messages in clickhouse-server.log\tOK' >> /test_output/backward_compatibility_check_results.tsv From b0a607d95a0fea5be1c69e286f29776e4f690513 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:22:59 +0300 Subject: [PATCH 0104/1647] Update 01747_system_session_log_long.sh --- tests/queries/0_stateless/01747_system_session_log_long.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/queries/0_stateless/01747_system_session_log_long.sh b/tests/queries/0_stateless/01747_system_session_log_long.sh index b41bf077b57..452f1d3222e 100755 --- a/tests/queries/0_stateless/01747_system_session_log_long.sh +++ b/tests/queries/0_stateless/01747_system_session_log_long.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: long, no-parallel, no-fasttest +# Tags: long, no-parallel, no-fasttest, backward-incompatible # Tag no-fasttest: Accesses CH via mysql table function (which is unavailable) ################################################################################################## @@ -369,4 +369,4 @@ GROUP BY user_name, interface, type ORDER BY user_name, interface, type; -EOF \ No newline at end of file +EOF From aaed515493e88ba201e0afed362331365a2a705b Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:26:06 +0300 Subject: [PATCH 0105/1647] Update 01747_system_session_log_long.sh --- tests/queries/0_stateless/01747_system_session_log_long.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01747_system_session_log_long.sh b/tests/queries/0_stateless/01747_system_session_log_long.sh index 452f1d3222e..9b127e0b48d 100755 --- a/tests/queries/0_stateless/01747_system_session_log_long.sh +++ b/tests/queries/0_stateless/01747_system_session_log_long.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: long, no-parallel, no-fasttest, backward-incompatible +# Tags: long, no-parallel, no-fasttest # Tag no-fasttest: Accesses CH via mysql table function (which is unavailable) ################################################################################################## From 653eeca8a7acf1bc2291c67912ce3a394812c6e6 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:29:31 +0300 Subject: [PATCH 0106/1647] Update 01555_system_distribution_queue_mask.sql --- .../0_stateless/01555_system_distribution_queue_mask.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql b/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql index 0143b8e46ed..ac977d8491b 100644 --- a/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql +++ b/tests/queries/0_stateless/01555_system_distribution_queue_mask.sql @@ -1,3 +1,5 @@ +-- Tags: backward-incompatible + -- force data path with the user/pass in it set use_compact_format_in_distributed_parts_names=0; -- use async send even for localhost From 4124e690f414c40a83d59fec91b7062da354b0f3 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Fri, 26 Nov 2021 15:56:04 +0300 Subject: [PATCH 0107/1647] Update 01191_rename_dictionary.sql --- tests/queries/0_stateless/01191_rename_dictionary.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01191_rename_dictionary.sql b/tests/queries/0_stateless/01191_rename_dictionary.sql index e9fed1dd6b2..fa100b21a80 100644 --- a/tests/queries/0_stateless/01191_rename_dictionary.sql +++ b/tests/queries/0_stateless/01191_rename_dictionary.sql @@ -1,4 +1,4 @@ --- Tags: no-parallel +-- Tags: no-parallel, backward-incompatible DROP DATABASE IF EXISTS test_01191; CREATE DATABASE test_01191 ENGINE=Atomic; From 63fceca6a8c5371bc2d89af26ec1b54063e1132b Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Wed, 1 Dec 2021 05:58:24 +0300 Subject: [PATCH 0108/1647] support nested in json type (wip) --- src/Columns/ColumnObject.cpp | 129 ++++++--- src/Columns/ColumnObject.h | 25 +- src/DataTypes/NestedUtils.cpp | 6 + src/DataTypes/ObjectUtils.cpp | 269 ++++++++++++++++-- src/DataTypes/ObjectUtils.h | 17 +- .../Serializations/JSONDataParser.cpp | 80 ++++++ src/DataTypes/Serializations/JSONDataParser.h | 102 +++++-- .../Serializations/SerializationObject.cpp | 83 +++++- .../tests/gtest_json_parser.cpp | 154 +++++++++- src/Functions/FunctionsConversion.h | 8 +- src/Interpreters/InterpreterDescribeQuery.cpp | 10 +- src/Storages/StorageSnapshot.cpp | 10 +- .../0_stateless/01825_type_json_1.reference | 6 +- .../0_stateless/01825_type_json_3.reference | 22 +- .../0_stateless/01825_type_json_5.reference | 2 +- .../0_stateless/01825_type_json_6.reference | 2 +- .../01825_type_json_distributed.reference | 4 +- .../01825_type_json_insert_select.reference | 12 +- 18 files changed, 770 insertions(+), 171 deletions(-) create mode 100644 src/DataTypes/Serializations/JSONDataParser.cpp diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 807eb4fb62f..67ba7aaf600 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -9,10 +10,13 @@ #include #include #include +#include #include #include #include +#include + namespace DB { @@ -41,6 +45,14 @@ Array createEmptyArrayField(size_t num_dimensions) return array; } +ColumnPtr recreateWithDefaultValues(const ColumnPtr & column) +{ + if (const auto * column_array = checkAndGetColumn(column.get())) + return ColumnArray::create(recreateWithDefaultValues(column_array->getDataPtr()), IColumn::mutate(column_array->getOffsetsPtr())); + else + return column->cloneEmpty()->cloneResized(column->size()); +} + class FieldVisitorReplaceNull : public StaticVisitor { public: @@ -83,9 +95,6 @@ class FieldVisitorToNumberOfDimensions : public StaticVisitor public: size_t operator()(const Array & x) const { - if (x.empty()) - return 1; - const size_t size = x.size(); std::optional dimensions; @@ -111,7 +120,7 @@ public: size_t operator()(const T &) const { return 0; } }; -class FieldVisitorToScalarType: public StaticVisitor<> +class FieldVisitorToScalarType : public StaticVisitor<> { public: using FieldType = Field::Types::Which; @@ -182,14 +191,15 @@ private: } -ColumnObject::Subcolumn::Subcolumn(MutableColumnPtr && data_) +ColumnObject::Subcolumn::Subcolumn(MutableColumnPtr && data_, bool is_nullable_) : least_common_type(getDataTypeByColumn(*data_)) - , is_nullable(least_common_type->isNullable()) + , is_nullable(is_nullable_) { data.push_back(std::move(data_)); } -ColumnObject::Subcolumn::Subcolumn(size_t size_, bool is_nullable_) +ColumnObject::Subcolumn::Subcolumn( + size_t size_, bool is_nullable_) : least_common_type(std::make_shared()) , is_nullable(is_nullable_) , num_of_defaults_in_prefix(size_) @@ -277,7 +287,7 @@ void ColumnObject::Subcolumn::insert(Field field) } value_type = createArrayOfType(base_type, value_dim); - auto default_value = value_type->getDefault(); + auto default_value = base_type->getDefault(); field = applyVisitor(FieldVisitorReplaceNull(default_value, value_dim), std::move(field)); } else @@ -384,6 +394,17 @@ void ColumnObject::Subcolumn::finalize() num_of_defaults_in_prefix = 0; } +Field ColumnObject::Subcolumn::getLastField() const +{ + if (data.empty()) + return Field(); + + const auto & last_part = data.back(); + assert(!last_part.empty()); + return (*last_part)[last_part->size() - 1]; +} + + void ColumnObject::Subcolumn::insertDefault() { if (data.empty()) @@ -436,13 +457,13 @@ void ColumnObject::checkConsistency() const return; size_t first_size = subcolumns.begin()->second.size(); - for (const auto & [name, column] : subcolumns) + for (const auto & [key, column] : subcolumns) { if (first_size != column.size()) { throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of subcolumns are inconsistent in ColumnObject." " Subcolumn '{}' has {} rows, subcolumn '{}' has {} rows", - subcolumns.begin()->first, first_size, name, column.size()); + subcolumns.begin()->first.getPath(), first_size, key.getPath(), column.size()); } } } @@ -495,9 +516,10 @@ void ColumnObject::insert(const Field & field) HashSet inserted; size_t old_size = size(); - for (const auto & [key, value] : object) + for (const auto & [key_str, value] : object) { - inserted.insert(key); + Path key(key_str); + inserted.insert(key_str); if (!hasSubcolumn(key)) addSubcolumn(key, old_size); @@ -506,7 +528,7 @@ void ColumnObject::insert(const Field & field) } for (auto & [key, subcolumn] : subcolumns) - if (!inserted.has(key)) + if (!inserted.has(key.getPath())) subcolumn.insertDefault(); } @@ -523,7 +545,7 @@ Field ColumnObject::operator[](size_t n) const Object object; for (const auto & [key, subcolumn] : subcolumns) - object[key] = (*subcolumn.data.back())[n]; + object[key.getPath()] = (*subcolumn.data.back())[n]; return object; } @@ -536,7 +558,7 @@ void ColumnObject::get(size_t n, Field & res) const auto & object = res.get(); for (const auto & [key, subcolumn] : subcolumns) { - auto it = object.try_emplace(key).first; + auto it = object.try_emplace(key.getPath()).first; subcolumn.data.back()->get(n, it->second); } } @@ -562,8 +584,11 @@ ColumnPtr ColumnObject::replicate(const Offsets & offsets) const throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot replicate non-finalized ColumnObject"); auto res_column = ColumnObject::create(is_nullable); - for (auto & [key, subcolumn] : subcolumns) - res_column->addSubcolumn(key, Subcolumn(subcolumn.data.back()->replicate(offsets)->assumeMutable())); + for (const auto & [key, subcolumn] : subcolumns) + { + auto replicated_data = subcolumn.data.back()->replicate(offsets)->assumeMutable(); + res_column->addSubcolumn(key, std::move(replicated_data), is_nullable); + } return res_column; } @@ -577,59 +602,93 @@ void ColumnObject::popBack(size_t length) subcolumn.data.back()->popBack(length); } -const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const String & key) const +const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const Path & key) const { auto it = subcolumns.find(key); if (it != subcolumns.end()) return it->second; - throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key); + throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key.getPath()); } -ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const String & key) +ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const Path & key) { auto it = subcolumns.find(key); if (it != subcolumns.end()) return it->second; - throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key); + throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key.getPath()); } -bool ColumnObject::hasSubcolumn(const String & key) const +std::optional ColumnObject::findSubcolumnForNested(const Path & key, size_t expected_size) const +{ + for (const auto & [other_key, other_subcolumn] : subcolumns) + { + if (key == other_key || expected_size != other_subcolumn.size()) + continue; + + auto split_lhs = Nested::splitName(key.getPath(), true); + auto split_rhs = Nested::splitName(other_key.getPath(), true); + + if (!split_lhs.first.empty() && split_lhs.first == split_rhs.first) + return other_key; + } + + return {}; +} + +bool ColumnObject::hasSubcolumn(const Path & key) const { return subcolumns.count(key) != 0; } -void ColumnObject::addSubcolumn(const String & key, size_t new_size, bool check_size) +void ColumnObject::addSubcolumn(const Path & key, size_t new_size, bool check_size) { if (subcolumns.count(key)) - throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key); + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); if (check_size && new_size != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", - key, new_size, size()); + key.getPath(), new_size, size()); - subcolumns[key] = Subcolumn(new_size, is_nullable); + if (key.hasNested()) + { + auto nested_key = findSubcolumnForNested(key, new_size); + if (nested_key) + { + auto & nested_subcolumn = subcolumns[*nested_key]; + nested_subcolumn.finalize(); + auto default_column = recreateWithDefaultValues(nested_subcolumn.getFinalizedColumnPtr()); + subcolumns[key] = Subcolumn(default_column->assumeMutable(), is_nullable); + } + else + { + subcolumns[key] = Subcolumn(new_size, is_nullable); + } + } + else + { + subcolumns[key] = Subcolumn(new_size, is_nullable); + } } -void ColumnObject::addSubcolumn(const String & key, Subcolumn && subcolumn, bool check_size) +void ColumnObject::addSubcolumn(const Path & key, MutableColumnPtr && subcolumn, bool check_size) { if (subcolumns.count(key)) - throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key); + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); - if (check_size && subcolumn.size() != size()) + if (check_size && subcolumn->size() != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", - key, subcolumn.size(), size()); + key.getPath(), subcolumn->size(), size()); - subcolumn.setNullable(is_nullable); - subcolumns[key] = std::move(subcolumn); + subcolumns[key] = Subcolumn(std::move(subcolumn), is_nullable); } -Strings ColumnObject::getKeys() const +Paths ColumnObject::getKeys() const { - Strings keys; + Paths keys; keys.reserve(subcolumns.size()); for (const auto & [key, _] : subcolumns) keys.emplace_back(key); @@ -657,7 +716,7 @@ void ColumnObject::finalize() } if (new_subcolumns.empty()) - new_subcolumns[COLUMN_NAME_DUMMY] = Subcolumn{ColumnUInt8::create(old_size)}; + new_subcolumns[Path(COLUMN_NAME_DUMMY)] = Subcolumn{ColumnUInt8::create(old_size), is_nullable}; std::swap(subcolumns, new_subcolumns); checkObjectHasNoAmbiguosPaths(getKeys()); diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 80749e42183..038d1317ff3 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -23,8 +24,8 @@ public: { public: Subcolumn() = default; - Subcolumn(size_t size_, bool is_nullable); - Subcolumn(MutableColumnPtr && data_); + Subcolumn(size_t size_, bool is_nullable_); + Subcolumn(MutableColumnPtr && data_, bool is_nullable_); size_t size() const; size_t byteSize() const; @@ -40,7 +41,8 @@ public: void insertRangeFrom(const Subcolumn & src, size_t start, size_t length); void finalize(); - void setNullable(bool value) { is_nullable = value; } + + Field getLastField() const; IColumn & getFinalizedColumn(); const IColumn & getFinalizedColumn() const; @@ -55,7 +57,7 @@ public: size_t num_of_defaults_in_prefix = 0; }; - using SubcolumnsMap = std::unordered_map; + using SubcolumnsMap = std::unordered_map; private: SubcolumnsMap subcolumns; @@ -64,22 +66,23 @@ private: public: static constexpr auto COLUMN_NAME_DUMMY = "_dummy"; - ColumnObject(bool is_nullable); + explicit ColumnObject(bool is_nullable_); ColumnObject(SubcolumnsMap && subcolumns_, bool is_nullable_); void checkConsistency() const; - bool hasSubcolumn(const String & key) const; + bool hasSubcolumn(const Path & key) const; - const Subcolumn & getSubcolumn(const String & key) const; - Subcolumn & getSubcolumn(const String & key); + const Subcolumn & getSubcolumn(const Path & key) const; + Subcolumn & getSubcolumn(const Path & key); + std::optional findSubcolumnForNested(const Path & key, size_t expected_size) const; - void addSubcolumn(const String & key, size_t new_size, bool check_size = false); - void addSubcolumn(const String & key, Subcolumn && subcolumn, bool check_size = false); + void addSubcolumn(const Path & key, size_t new_size, bool check_size = false); + void addSubcolumn(const Path & key, MutableColumnPtr && subcolumn, bool check_size = false); const SubcolumnsMap & getSubcolumns() const { return subcolumns; } SubcolumnsMap & getSubcolumns() { return subcolumns; } - Strings getKeys() const; + Paths getKeys() const; bool isFinalized() const; void finalize(); diff --git a/src/DataTypes/NestedUtils.cpp b/src/DataTypes/NestedUtils.cpp index d3c42b0d029..7d75f5cb2b8 100644 --- a/src/DataTypes/NestedUtils.cpp +++ b/src/DataTypes/NestedUtils.cpp @@ -30,6 +30,12 @@ namespace Nested std::string concatenateName(const std::string & nested_table_name, const std::string & nested_field_name) { + if (nested_table_name.empty()) + return nested_field_name; + + if (nested_field_name.empty()) + return nested_table_name; + return nested_table_name + "." + nested_field_name; } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 70d90dbf14b..76b29ef8ccf 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -1,10 +1,10 @@ #include #include -#include #include #include #include #include +#include #include #include #include @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -120,30 +121,35 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con "Cannot convert to tuple column '{}' from type {}. Column should be finalized first", name_type.name, name_type.type->getName()); - std::vector> tuple_elements; + std::vector> tuple_elements; + tuple_elements.reserve(subcolumns_map.size()); for (const auto & [key, subcolumn] : subcolumns_map) tuple_elements.emplace_back(key, subcolumn.getLeastCommonType(), subcolumn.getFinalizedColumnPtr()); std::sort(tuple_elements.begin(), tuple_elements.end(), - [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); }); + [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs).getPath() < std::get<0>(rhs).getPath(); }); - auto tuple_names = extractVector<0>(tuple_elements); + auto tuple_paths = extractVector<0>(tuple_elements); auto tuple_types = extractVector<1>(tuple_elements); auto tuple_columns = extractVector<2>(tuple_elements); - auto type_tuple = std::make_shared(tuple_types, tuple_names); - auto it = storage_columns_map.find(name_type.name); if (it == storage_columns_map.end()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Column '{}' not found in storage", name_type.name); + Strings tuple_names; + tuple_names.reserve(tuple_paths.size()); + for (const auto & path : tuple_paths) + tuple_names.push_back(path.getPath()); + + auto type_tuple = std::make_shared(tuple_types, tuple_names); + getLeastCommonTypeForObject({type_tuple, it->second}, true); - name_type.type = type_tuple; - column.type = type_tuple; - column.column = ColumnTuple::create(tuple_columns); + std::tie(column.type, column.column) = unflattenTuple(tuple_paths, tuple_types, tuple_columns); + name_type.type = column.type; } } } @@ -159,13 +165,13 @@ static bool isPrefix(const Strings & prefix, const Strings & strings) return true; } -void checkObjectHasNoAmbiguosPaths(const Strings & key_names) +void checkObjectHasNoAmbiguosPaths(const Paths & paths) { - size_t size = key_names.size(); + size_t size = paths.size(); std::vector names_parts(size); for (size_t i = 0; i < size; ++i) - boost::split(names_parts[i], key_names[i], boost::is_any_of(".")); + names_parts[i] = paths[i].getParts(); for (size_t i = 0; i < size; ++i) { @@ -174,14 +180,15 @@ void checkObjectHasNoAmbiguosPaths(const Strings & key_names) if (isPrefix(names_parts[i], names_parts[j]) || isPrefix(names_parts[j], names_parts[i])) throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Data in Object has ambiguous paths: '{}' and '{}", - key_names[i], key_names[j]); + paths[i].getPath(), paths[i].getPath()); } } } DataTypePtr getLeastCommonTypeForObject(const DataTypes & types, bool check_ambiguos_paths) { - std::unordered_map subcolumns_types; + std::unordered_map subcolumns_types; + for (const auto & type : types) { const auto * type_tuple = typeid_cast(type.get()); @@ -189,19 +196,18 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types, bool check_ambi throw Exception(ErrorCodes::LOGICAL_ERROR, "Least common type for object can be deduced only from tuples, but {} given", type->getName()); - const auto & tuple_names = type_tuple->getElementNames(); - const auto & tuple_types = type_tuple->getElements(); - assert(tuple_names.size() == tuple_types.size()); + auto [tuple_paths, tuple_types] = flattenTuple(type); + assert(tuple_paths.size() == tuple_types.size()); - for (size_t i = 0; i < tuple_names.size(); ++i) - subcolumns_types[tuple_names[i]].push_back(tuple_types[i]); + for (size_t i = 0; i < tuple_paths.size(); ++i) + subcolumns_types[tuple_paths[i]].push_back(tuple_types[i]); } - std::vector> tuple_elements; - for (const auto & [name, subtypes] : subcolumns_types) + std::vector> tuple_elements; + for (const auto & [key, subtypes] : subcolumns_types) { assert(!subtypes.empty()); - if (name == ColumnObject::COLUMN_NAME_DUMMY) + if (key.getPath() == ColumnObject::COLUMN_NAME_DUMMY) continue; size_t first_dim = getNumberOfDimensions(*subtypes[0]); @@ -209,24 +215,24 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types, bool check_ambi if (first_dim != getNumberOfDimensions(*subtypes[i])) throw Exception(ErrorCodes::TYPE_MISMATCH, "Uncompatible types of subcolumn '{}': {} and {}", - name, subtypes[0]->getName(), subtypes[i]->getName()); + key.getPath(), subtypes[0]->getName(), subtypes[i]->getName()); - tuple_elements.emplace_back(name, getLeastSupertype(subtypes, /*allow_conversion_to_string=*/ true)); + tuple_elements.emplace_back(key, getLeastSupertype(subtypes, /*allow_conversion_to_string=*/ true)); } if (tuple_elements.empty()) - tuple_elements.emplace_back(ColumnObject::COLUMN_NAME_DUMMY, std::make_shared()); + tuple_elements.emplace_back(Path(ColumnObject::COLUMN_NAME_DUMMY), std::make_shared()); std::sort(tuple_elements.begin(), tuple_elements.end(), - [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); }); + [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs).getPath() < std::get<0>(rhs).getPath(); }); - auto tuple_names = extractVector<0>(tuple_elements); + auto tuple_paths = extractVector<0>(tuple_elements); auto tuple_types = extractVector<1>(tuple_elements); if (check_ambiguos_paths) - checkObjectHasNoAmbiguosPaths(tuple_names); + checkObjectHasNoAmbiguosPaths(tuple_paths); - return std::make_shared(tuple_types, tuple_names); + return unflattenTuple(tuple_paths, tuple_types); } NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list) @@ -257,6 +263,211 @@ void extendObjectColumns(NamesAndTypesList & columns_list, const ColumnsDescript columns_list.splice(columns_list.end(), std::move(subcolumns_list)); } +namespace +{ + +struct SubcolumnsHolder +{ + SubcolumnsHolder( + const std::vector & paths_, + const DataTypes & types_, + const Columns & columns_) + : paths(paths_) + , types(types_) + , columns(columns_) + { + parts.reserve(paths.size()); + levels.reserve(paths.size()); + + for (const auto & path : paths) + { + parts.emplace_back(path.getParts()); + + size_t num_parts = path.getNumParts(); + + levels.emplace_back(); + levels.back().resize(num_parts); + + for (size_t i = 1; i < num_parts; ++i) + levels.back()[i] = levels.back()[i - 1] + path.isNested(i - 1); + } + } + + std::pair reduceNumberOfDimensions(size_t path_idx, size_t key_idx) const + { + auto type = types[path_idx]; + auto column = columns[path_idx]; + + if (!isArray(type)) + return {type, column}; + + size_t type_dimensions = getNumberOfDimensions(*type); + size_t column_dimensions = getNumberOfDimensions(*column); + + if (levels[path_idx][key_idx] > type_dimensions) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Level of nested ({}) cannot be greater than number of dimensions in array ({})", + levels[path_idx][key_idx], type_dimensions); + + if (type_dimensions != column_dimensions) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Number of dimensionsin type ({}) is incompatible with number of dimension in column ({})", + type_dimensions, column_dimensions); + + size_t dimensions_to_reduce = levels[path_idx][key_idx]; + while (dimensions_to_reduce--) + { + const auto & type_array = assert_cast(*type); + const auto & column_array = assert_cast(*column); + + type = type_array.getNestedType(); + column = column_array.getDataPtr(); + } + + return {type, column}; + } + + std::vector paths; + DataTypes types; + Columns columns; + + std::vector parts; + std::vector> levels; +}; + + +void flattenTupleImpl(Path path, DataTypePtr type, size_t array_level, Paths & new_paths, DataTypes & new_types) +{ + bool is_nested = isNested(type); + if (is_nested) + type = assert_cast(*type).getNestedType(); + + if (const auto * type_tuple = typeid_cast(type.get())) + { + const auto & tuple_names = type_tuple->getElementNames(); + const auto & tuple_types = type_tuple->getElements(); + + for (size_t i = 0; i < tuple_names.size(); ++i) + { + auto next_path = Path::getNext(path, Path(tuple_names[i]), is_nested); + size_t next_level = array_level + is_nested; + flattenTupleImpl(next_path, tuple_types[i], next_level, new_paths, new_types); + } + } + else if (const auto * type_array = typeid_cast(type.get())) + { + flattenTupleImpl(path, type_array->getNestedType(), array_level + 1, new_paths, new_types); + } + else + { + new_paths.push_back(path); + new_types.push_back(createArrayOfType(type, array_level)); + } +} + +void unflattenTupleImpl( + const SubcolumnsHolder & holder, + size_t from, size_t to, size_t depth, + Names & new_names, DataTypes & new_types, Columns & new_columns) +{ + size_t start = from; + for (size_t i = from + 1; i <= to; ++i) + { + if (i < to && holder.parts[i].size() <= depth) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot unflatten Tuple. Not enough name parts in path"); + + if (i == to || holder.parts[i][depth] != holder.parts[start][depth]) + { + if (i - start > 1) + { + Names tuple_names; + DataTypes tuple_types; + Columns tuple_columns; + + unflattenTupleImpl(holder, start, i, depth + 1, tuple_names, tuple_types, tuple_columns); + + assert(!tuple_names.empty()); + assert(tuple_names.size() == tuple_types.size()); + assert(tuple_names.size() == tuple_columns.size()); + + new_names.push_back(holder.parts[start][depth]); + + if (holder.paths[start].isNested(depth)) + { + auto array_column = holder.reduceNumberOfDimensions(start, depth).second; + auto offsets_column = assert_cast(*array_column).getOffsetsPtr(); + + new_types.push_back(createNested(tuple_types, tuple_names)); + new_columns.push_back(ColumnArray::create(ColumnTuple::create(std::move(tuple_columns)), offsets_column)); + } + else + { + new_types.push_back(std::make_shared(tuple_types, tuple_names)); + new_columns.push_back(ColumnTuple::create(std::move(tuple_columns))); + } + } + else + { + WriteBufferFromOwnString wb; + wb << holder.parts[start][depth]; + for (size_t j = depth + 1; j < holder.parts[start].size(); ++j) + wb << "." << holder.parts[start][j]; + + auto [new_type, new_column] = holder.reduceNumberOfDimensions(start, depth); + + new_names.push_back(wb.str()); + new_types.push_back(std::move(new_type)); + new_columns.push_back(std::move(new_column)); + } + + start = i; + } + } +} + +} + +std::pair flattenTuple(const DataTypePtr & type) +{ + Paths new_paths; + DataTypes new_types; + + flattenTupleImpl({}, type, 0, new_paths, new_types); + return {new_paths, new_types}; +} + +DataTypePtr unflattenTuple(const Paths & paths, const DataTypes & tuple_types) +{ + assert(paths.size() == types.size()); + Columns tuple_columns(tuple_types.size()); + for (size_t i = 0; i < tuple_types.size(); ++i) + tuple_columns[i] = tuple_types[i]->createColumn(); + + return unflattenTuple(paths, tuple_types, tuple_columns).first; +} + +std::pair unflattenTuple( + const Paths & paths, + const DataTypes & tuple_types, + const Columns & tuple_columns) +{ + assert(paths.size() == types.size()); + assert(paths.size() == columns.size()); + + Names new_names; + DataTypes new_types; + Columns new_columns; + SubcolumnsHolder holder(paths, tuple_types, tuple_columns); + + unflattenTupleImpl(holder, 0, paths.size(), 0, new_names, new_types, new_columns); + + return + { + std::make_shared(new_types, new_names), + ColumnTuple::create(new_columns) + }; +} + static void addConstantToWithClause(const ASTPtr & query, const String & column_name, const DataTypePtr & data_type) { auto & select = query->as(); diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 0c0074d2c98..869f396fde9 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace DB { @@ -15,11 +17,24 @@ DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns); -void checkObjectHasNoAmbiguosPaths(const Strings & key_names); +void checkObjectHasNoAmbiguosPaths(const Paths & paths); DataTypePtr getLeastCommonTypeForObject(const DataTypes & types, bool check_ambiguos_paths = false); NameSet getNamesOfObjectColumns(const NamesAndTypesList & columns_list); void extendObjectColumns(NamesAndTypesList & columns_list, const ColumnsDescription & object_columns, bool with_subcolumns); +using DataTypeTuplePtr = std::shared_ptr; + +std::pair flattenTuple(const DataTypePtr & type); + +DataTypePtr unflattenTuple( + const Paths & paths, + const DataTypes & tuple_types); + +std::pair unflattenTuple( + const Paths & paths, + const DataTypes & tuple_types, + const Columns & tuple_columns); + void replaceMissedSubcolumnsByConstants( const ColumnsDescription & expected_columns, const ColumnsDescription & available_columns, diff --git a/src/DataTypes/Serializations/JSONDataParser.cpp b/src/DataTypes/Serializations/JSONDataParser.cpp new file mode 100644 index 00000000000..a91b205e96d --- /dev/null +++ b/src/DataTypes/Serializations/JSONDataParser.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +#include +#include + +#include +#include + +namespace DB +{ + +Path::Path(std::string_view path_) + : path(path_) + , num_parts(1 + std::count(path.begin(), path.end(), '.')) +{ +} + +void Path::append(const Path & other) +{ + if (!other.path.empty()) + { + path.reserve(path.size() + other.path.size() + 1); + if (!path.empty()) + path += "."; + path += other.path; + } + + is_nested |= other.is_nested << num_parts; + num_parts += other.num_parts; +} + +Strings Path::getParts() const +{ + Strings parts; + parts.reserve(num_parts); + boost::algorithm::split(parts, path, boost::is_any_of(".")); + return parts; +} + +void Path::writeBinary(WriteBuffer & out) const +{ + writeStringBinary(path, out); + writeVarUInt(num_parts, out); + writeVarUInt(is_nested.to_ullong(), out); +} + +void Path::readBinary(ReadBuffer & in) +{ + readStringBinary(path, in); + readVarUInt(num_parts, in); + + UInt64 bits; + readVarUInt(bits, in); + is_nested = {bits}; +} + +Path Path::getNext(const Path & current_path, const Path & key, bool make_nested) +{ + Path next(current_path); + next.append(key); + + if (!next.empty()) + { + size_t nested_index = 0; + if (!current_path.empty()) + nested_index = current_path.num_parts - 1; + next.is_nested.set(nested_index, make_nested); + } + + return next; +} + +size_t Path::Hash::operator()(const Path & value) const +{ + return std::hash{}(value.path); +} + +} diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index c5d6bdd2826..3be7d461981 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -3,6 +3,7 @@ #include #include #include +#include namespace DB { @@ -10,8 +11,50 @@ namespace DB namespace ErrorCodes { extern const int LOGICAL_ERROR; + extern const int NUMBER_OF_DIMENSIONS_MISMATHED; } +class ReadBuffer; +class WriteBuffer; + +class Path +{ +public: + // using BitSet = boost::dynamic_bitset, 8>>; + + Path() = default; + explicit Path(std::string_view path_); + + void append(const Path & other); + void append(std::string_view key); + + const String & getPath() const { return path; } + bool isNested(size_t i) const { return is_nested.test(i); } + bool hasNested() const { return is_nested.any(); } + + size_t getNumParts() const { return num_parts; } + bool empty() const { return path.empty(); } + + Strings getParts() const; + + static Path getNext(const Path & current_path, const Path & key, bool make_nested = false); + + void writeBinary(WriteBuffer & out) const; + void readBinary(ReadBuffer & in); + + bool operator==(const Path & other) const { return path == other.path; } + bool operator!=(const Path & other) const { return !(*this == other); } + struct Hash { size_t operator()(const Path & value) const; }; + +private: + String path; + size_t num_parts = 0; + /// TODO: use dynamic bitset + std::bitset<64> is_nested; +}; + +using Paths = std::vector; + template static Field getValueAsField(const Element & element) { @@ -25,22 +68,9 @@ static Field getValueAsField(const Element & element) throw Exception(ErrorCodes::LOGICAL_ERROR, "Unsupported type of JSON field"); } -static String getNextPath(const String & current_path, const std::string_view & key) -{ - String next_path = current_path; - if (!key.empty()) - { - if (!next_path.empty()) - next_path += "."; - next_path += key; - } - - return next_path; -} - struct ParseResult { - Strings paths; + std::vector paths; std::vector values; }; @@ -63,12 +93,12 @@ public: return {}; ParseResult result; - traverse(document, "", result); + traverse(document, {}, result); return result; } private: - void traverse(const Element & element, String current_path, ParseResult & result) + void traverse(const Element & element, Path current_path, ParseResult & result) { checkStackSize(); @@ -81,14 +111,16 @@ private: for (auto it = object.begin(); it != object.end(); ++it) { const auto & [key, value] = *it; - traverse(value, getNextPath(current_path, key), result); + traverse(value, Path::getNext(current_path, Path(key)), result); } } else if (element.isArray()) { auto array = element.getArray(); - using PathToArray = HashMap; + using PathWithArray = std::pair; + using PathToArray = HashMap; + PathToArray arrays_by_path; Arena strings_pool; @@ -97,32 +129,37 @@ private: for (auto it = array.begin(); it != array.end(); ++it) { ParseResult element_result; - traverse(*it, "", element_result); + traverse(*it, {}, element_result); - auto && [paths, values] = element_result; + auto & [paths, values] = element_result; for (size_t i = 0; i < paths.size(); ++i) { - const auto & path = paths[i]; - - if (auto found = arrays_by_path.find(path)) + if (auto * found = arrays_by_path.find(paths[i].getPath())) { - auto & path_array = found->getMapped(); + auto & path_array = found->getMapped().second; + assert(path_array.size() == current_size); path_array.push_back(std::move(values[i])); } else { - StringRef ref{strings_pool.insert(path.data(), path.size()), path.size()}; - auto & path_array = arrays_by_path[ref]; - + Array path_array; path_array.reserve(array.size()); path_array.resize(current_size); path_array.push_back(std::move(values[i])); + + const auto & path_str = paths[i].getPath(); + StringRef ref{strings_pool.insert(path_str.data(), path_str.size()), path_str.size()}; + + auto & elem = arrays_by_path[ref]; + elem.first = std::move(paths[i]); + elem.second = std::move(path_array); } } - for (auto & [path, path_array] : arrays_by_path) + for (auto & [_, value] : arrays_by_path) { + auto & path_array = value.second; assert(path_array.size() == current_size || path_array.size() == current_size + 1); if (path_array.size() == current_size) path_array.push_back(Field()); @@ -141,16 +178,19 @@ private: result.paths.reserve(result.paths.size() + arrays_by_path.size()); result.values.reserve(result.paths.size() + arrays_by_path.size()); - for (auto && [path, path_array] : arrays_by_path) + bool is_nested = arrays_by_path.size() > 1 || arrays_by_path.begin()->getKey().size != 0; + + for (auto && [_, value] : arrays_by_path) { - result.paths.push_back(getNextPath(current_path, static_cast(path))); + auto && [path, path_array] = value; + result.paths.push_back(Path::getNext(current_path, path, is_nested)); result.values.push_back(std::move(path_array)); } } } else { - result.paths.push_back(current_path); + result.paths.push_back(std::move(current_path)); result.values.push_back(getValueAsField(element)); } } diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 4d3a526b276..f97571ce463 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,37 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } +namespace +{ + +class FieldVisitorReplaceScalars : public StaticVisitor +{ +public: + explicit FieldVisitorReplaceScalars(const Field & replacement_) : replacement(replacement_) + { + } + + template + Field operator()(const T & x) const + { + if constexpr (std::is_same_v) + { + const size_t size = x.size(); + Array res(size); + for (size_t i = 0; i < size; ++i) + res[i] = applyVisitor(*this, x[i]); + return res; + } + else + return replacement; + } + +private: + const Field & replacement; +}; + +} + template template void SerializationObject::deserializeTextImpl(IColumn & column, Reader && reader) const @@ -37,12 +69,12 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & if (!result) throw Exception(ErrorCodes::INCORRECT_DATA, "Cannot parse object"); - auto && [paths, values] = *result; + auto & [paths, values] = *result; assert(paths.size() == values.size()); HashSet paths_set; for (const auto & path : paths) - paths_set.insert(path); + paths_set.insert(path.getPath()); if (paths.size() != paths_set.size()) throw Exception(ErrorCodes::INCORRECT_DATA, "Object has ambiguous paths"); @@ -61,8 +93,33 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & for (auto & [key, subcolumn] : column_object.getSubcolumns()) { - if (!paths_set.has(key)) - subcolumn.insertDefault(); + if (!paths_set.has(key.getPath())) + { + if (key.hasNested()) + { + auto nested_key = column_object.findSubcolumnForNested(key, subcolumn.size() + 1); + + if (nested_key) + { + const auto & subcolumn_nested = column_object.getSubcolumn(*nested_key); + auto last_field = subcolumn_nested.getLastField(); + if (last_field.isNull()) + continue; + + auto default_scalar = getBaseTypeOfArray(subcolumn_nested.getLeastCommonType())->getDefault(); + auto default_field = applyVisitor(FieldVisitorReplaceScalars(default_scalar), last_field); + subcolumn.insert(std::move(default_field)); + } + else + { + subcolumn.insertDefault(); + } + } + else + { + subcolumn.insertDefault(); + } + } } } @@ -154,17 +211,17 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( for (const auto & [key, subcolumn] : column_object.getSubcolumns()) { settings.path.back() = Substream::ObjectStructure; - settings.path.back().object_key_name = key; + settings.path.back().object_key_name = key.getPath(); const auto & type = subcolumn.getLeastCommonType(); if (auto * stream = settings.getter(settings.path)) { - writeStringBinary(key, *stream); + key.writeBinary(*stream); writeStringBinary(type->getName(), *stream); } settings.path.back() = Substream::ObjectElement; - settings.path.back().object_key_name = key; + settings.path.back().object_key_name = key.getPath(); if (auto * stream = settings.getter(settings.path)) { @@ -201,13 +258,13 @@ void SerializationObject::deserializeBinaryBulkWithMultipleStreams( settings.path.back() = Substream::ObjectElement; for (size_t i = 0; i < num_subcolumns; ++i) { - String key; + Path key; String type_name; settings.path.back() = Substream::ObjectStructure; if (auto * stream = settings.getter(settings.path)) { - readStringBinary(key, *stream); + key.readBinary(*stream); readStringBinary(type_name, *stream); } else @@ -217,7 +274,7 @@ void SerializationObject::deserializeBinaryBulkWithMultipleStreams( } settings.path.back() = Substream::ObjectElement; - settings.path.back().object_key_name = key; + settings.path.back().object_key_name = key.getPath(); if (auto * stream = settings.getter(settings.path)) { @@ -225,12 +282,12 @@ void SerializationObject::deserializeBinaryBulkWithMultipleStreams( auto serialization = type->getDefaultSerialization(); ColumnPtr subcolumn_data = type->createColumn(); serialization->deserializeBinaryBulkWithMultipleStreams(subcolumn_data, limit, settings, state, cache); - column_object.addSubcolumn(key, subcolumn_data->assumeMutable()); + column_object.addSubcolumn(Path(key), subcolumn_data->assumeMutable()); } else { throw Exception(ErrorCodes::CANNOT_READ_ALL_DATA, - "Cannot read subcolumn '{}' of DataTypeObject, because its stream is missing", key); + "Cannot read subcolumn '{}' of DataTypeObject, because its stream is missing", key.getPath()); } } @@ -278,7 +335,7 @@ void SerializationObject::serializeTextImpl(const IColumn & column, size if (it != subcolumns.begin()) writeCString(",", ostr); - writeDoubleQuoted(it->first, ostr); + writeDoubleQuoted(it->first.getPath(), ostr); writeChar(':', ostr); auto serialization = it->second.getLeastCommonType()->getDefaultSerialization(); diff --git a/src/DataTypes/Serializations/tests/gtest_json_parser.cpp b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp index f7d84db5e7f..c14b1fa1f72 100644 --- a/src/DataTypes/Serializations/tests/gtest_json_parser.cpp +++ b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #if USE_SIMDJSON @@ -9,6 +10,8 @@ using namespace DB; const String json1 = R"({"k1" : 1, "k2" : {"k3" : "aa", "k4" : 2}})"; + +/// Nested(k2 String, k3 Nested(k4 String)) const String json2 = R"({"k1" : [ { @@ -24,7 +27,6 @@ R"({"k1" : [ TEST(JSONDataParser, ReadJSON) { - { String json_bad = json1 + "aaaaaaa"; @@ -46,31 +48,153 @@ TEST(JSONDataParser, ReadJSON) } } +static void check( + const String & json_str, + const Strings & expected_paths, + const std::vector & expected_values, + const std::vector> & expected_nested, + const String & tag) +{ + JSONDataParser parser; + auto res = parser.parse(json_str.data(), json_str.size()); + ASSERT_TRUE(res.has_value()) << tag; + + const auto & [paths, values] = *res; + + ASSERT_EQ(paths.size(), expected_paths.size()) << tag; + ASSERT_EQ(values.size(), expected_values.size()) << tag; + + Strings paths_str; + std::vector> paths_is_nested; + + for (const auto & path : paths) + { + paths_str.push_back(path.getPath()); + paths_is_nested.emplace_back(); + for (size_t i = 0; i < path.getNumParts(); ++i) + paths_is_nested.back().push_back(path.isNested(i)); + } + + ASSERT_EQ(paths_str, expected_paths) << tag; + ASSERT_EQ(values, expected_values) << tag; + ASSERT_EQ(paths_is_nested, expected_nested) << tag; +} + TEST(JSONDataParser, Parse) { { - JSONDataParser parser; - auto res = parser.parse(json1.data(), json1.size()); - ASSERT_TRUE(res.has_value()); - - const auto & [paths, values] = *res; - ASSERT_EQ(paths, (Strings{"k1", "k2.k3", "k2.k4"})); - ASSERT_EQ(values, (std::vector{1, "aa", 2})); + check(json1, + {"k1", "k2.k3", "k2.k4"}, + {1, "aa", 2}, + {{false}, {false, false}, {false, false}}, + "json1"); } { - JSONDataParser parser; - auto res = parser.parse(json2.data(), json2.size()); - ASSERT_TRUE(res.has_value()); - - const auto & [paths, values] = *res; - ASSERT_EQ(paths, (Strings{"k1.k3.k4", "k1.k2"})); + Strings paths = {"k1.k3.k4", "k1.k2"}; auto k1k3k4 = Array{Array{"bbb", "ccc"}, Array{"eee", "fff"}}; auto k1k2 = Array{"aaa", "ddd"}; - ASSERT_EQ(values, (std::vector{k1k3k4, k1k2})); + + check(json2, paths, {k1k3k4, k1k2}, {{true, true, false}, {true, false}}, "json2"); } + { + /// Nested(k2 Tuple(k3 Array(Int), k4 Array(Int)), k5 String) + const String json3 = + R"({"k1": [ + { + "k2": { + "k3": [1, 2], + "k4": [3, 4] + }, + "k5": "foo" + }, + { + "k2": { + "k3": [5, 6], + "k4": [7, 8] + }, + "k5": "bar" + } + ]})"; + + Strings paths = {"k1.k2.k4", "k1.k5", "k1.k2.k3"}; + + auto k1k2k3 = Array{Array{1, 2}, Array{5, 6}}; + auto k1k2k4 = Array{Array{3, 4}, Array{7, 8}}; + auto k1k5 = Array{"foo", "bar"}; + + check(json3, paths, + {k1k2k4, k1k5, k1k2k3}, + {{true, false, false}, {true, false}, {true, false, false}}, + "json3"); + } + + { + /// Nested(k2 Nested(k3 Int, k4 Int), k5 String) + const String json4 = + R"({"k1": [ + { + "k2": [{"k3": 1, "k4": 3}, {"k3": 2, "k4": 4}], + "k5": "foo" + }, + { + "k2": [{"k3": 5, "k4": 7}, {"k3": 6, "k4": 8}], + "k5": "bar" + } + ]})"; + + Strings paths = {"k1.k2.k4", "k1.k5", "k1.k2.k3"}; + + auto k1k2k3 = Array{Array{1, 2}, Array{5, 6}}; + auto k1k2k4 = Array{Array{3, 4}, Array{7, 8}}; + auto k1k5 = Array{"foo", "bar"}; + + check(json4, paths, + {k1k2k4, k1k5, k1k2k3}, + {{true, true, false}, {true, false}, {true, true, false}}, + "json4"); + } + + { + const String json5 = R"({"k1": [[1, 2, 3], [4, 5], [6]]})"; + check(json5, {"k1"}, {Array{Array{1, 2, 3}, Array{4, 5}, Array{6}}}, {{false}}, "json5"); + } + + { + /// Array(Nested(k2 Int, k3 Int)) + const String json6 = R"({ + "k1": [ + [{"k2": 1, "k3": 2}, {"k2": 3, "k3": 4}], + [{"k2": 5, "k3": 6}] + ] + })"; + + Strings paths = {"k1.k2", "k1.k3"}; + + auto k1k2 = Array{Array{1, 3}, Array{5}}; + auto k1k3 = Array{Array{2, 4}, Array{6}}; + + check(json6, paths, {k1k2, k1k3}, {{true, true}, {true, true}}, "json6"); + } + + { + /// Nested(k2 Array(Int), k3 Array(Int)) + const String json7 = R"({ + "k1": [ + {"k2": [1, 3], "k3": [2, 4]}, + {"k2": [5], "k3": [6]} + ] + })"; + + Strings paths = {"k1.k2", "k1.k3"}; + + auto k1k2 = Array{Array{1, 3}, Array{5}}; + auto k1k3 = Array{Array{2, 4}, Array{6}}; + + check(json7, paths, {k1k2, k1k3}, {{true, false}, {true, false}}, "json7"); + } } #endif diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index 8cddcbc3ec8..d02f3db25f0 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -3046,15 +3046,15 @@ private: size_t tuple_size = to_types.size(); const ColumnTuple & column_tuple = assert_cast(*arguments.front().column); - ColumnObject::SubcolumnsMap subcolumns; + auto res = ColumnObject::create(has_nullable_subcolumns); for (size_t i = 0; i < tuple_size; ++i) { ColumnsWithTypeAndName element = {{column_tuple.getColumns()[i], from_types[i], "" }}; auto converted_column = element_wrappers[i](element, to_types[i], nullable_source, input_rows_count); - subcolumns[names[i]] = converted_column->assumeMutable(); + res->addSubcolumn(Path(names[i]), converted_column->assumeMutable()); } - return ColumnObject::create(std::move(subcolumns), has_nullable_subcolumns); + return res; }; } else if (checkAndGetDataType(from_type.get())) @@ -3069,7 +3069,7 @@ private: } throw Exception(ErrorCodes::TYPE_MISMATCH, - "Cast to Object can be performed only from flatten named tuple. Got: {}", from_type->getName()); + "Cast to Object can be performed only from flatten named tuple or string. Got: {}", from_type->getName()); } template diff --git a/src/Interpreters/InterpreterDescribeQuery.cpp b/src/Interpreters/InterpreterDescribeQuery.cpp index 9eb098c9f3b..da5fcedd469 100644 --- a/src/Interpreters/InterpreterDescribeQuery.cpp +++ b/src/Interpreters/InterpreterDescribeQuery.cpp @@ -93,7 +93,9 @@ BlockIO InterpreterDescribeQuery::execute() columns = metadata_snapshot->getColumns(); } - bool include_subcolumns = getContext()->getSettingsRef().describe_include_subcolumns; + bool extend_object_types = settings.describe_extend_object_types && storage_snapshot; + bool include_subcolumns = settings.describe_include_subcolumns; + Block sample_block = getSampleBlock(include_subcolumns); MutableColumns res_columns = sample_block.cloneEmptyColumns(); @@ -101,7 +103,7 @@ BlockIO InterpreterDescribeQuery::execute() { res_columns[0]->insert(column.name); - if (settings.describe_extend_object_types && storage_snapshot) + if (extend_object_types) res_columns[1]->insert(storage_snapshot->getConcreteType(column.name)->getName()); else res_columns[1]->insert(column.type->getName()); @@ -137,6 +139,8 @@ BlockIO InterpreterDescribeQuery::execute() { for (const auto & column : columns) { + auto type = extend_object_types ? storage_snapshot->getConcreteType(column.name) : column.type; + IDataType::forEachSubcolumn([&](const auto & path, const auto & name, const auto & data) { res_columns[0]->insert(Nested::concatenateName(column.name, name)); @@ -159,7 +163,7 @@ BlockIO InterpreterDescribeQuery::execute() res_columns[6]->insertDefault(); res_columns[7]->insert(1u); - }, {column.type->getDefaultSerialization(), column.type, nullptr, nullptr}); + }, { type->getDefaultSerialization(), type, nullptr, nullptr }); } } diff --git a/src/Storages/StorageSnapshot.cpp b/src/Storages/StorageSnapshot.cpp index d3922b1c769..44dd77b2ce2 100644 --- a/src/Storages/StorageSnapshot.cpp +++ b/src/Storages/StorageSnapshot.cpp @@ -33,15 +33,15 @@ NamesAndTypesList StorageSnapshot::getColumns(const GetColumnsOptions & options) { /// Virtual columns must be appended after ordinary, /// because user can override them. - auto virtuals = storage.getVirtuals(); - if (!virtuals.empty()) + if (!virtual_columns.empty()) { NameSet column_names; for (const auto & column : all_columns) column_names.insert(column.name); - for (auto && column : virtuals) - if (!column_names.count(column.name)) - all_columns.push_back(std::move(column)); + + for (const auto & [name, type] : virtual_columns) + if (!column_names.count(name)) + all_columns.emplace_back(name, type); } } diff --git a/tests/queries/0_stateless/01825_type_json_1.reference b/tests/queries/0_stateless/01825_type_json_1.reference index 4c21f14d4e9..59fad00bfae 100644 --- a/tests/queries/0_stateless/01825_type_json_1.reference +++ b/tests/queries/0_stateless/01825_type_json_1.reference @@ -1,12 +1,12 @@ 1 aa bb c 2 ee ff 3 foo -all_1_1_0 data Tuple(k1 String, `k2.k3` String, `k2.k4` String, k5 String) +all_1_1_0 data Tuple(k1 String, k2 Tuple(k3 String, k4 String), k5 String) all_2_2_0 data Tuple(k5 String) -all_1_2_1 data Tuple(k1 String, `k2.k3` String, `k2.k4` String, k5 String) +all_1_2_1 data Tuple(k1 String, k2 Tuple(k3 String, k4 String), k5 String) ============ 1 ['aaa','ddd'] [['bbb','ccc'],['eee','fff']] -all_3_3_0 data Tuple(`k1.k2` Array(String), `k1.k3.k4` Array(Array(String))) +all_3_3_0 data Tuple(k1 Nested(k2 String, `k3.k4` Array(String))) ============ 1 a 42 2 b 4200 diff --git a/tests/queries/0_stateless/01825_type_json_3.reference b/tests/queries/0_stateless/01825_type_json_3.reference index 830d44d779c..24d191a6e5d 100644 --- a/tests/queries/0_stateless/01825_type_json_3.reference +++ b/tests/queries/0_stateless/01825_type_json_3.reference @@ -3,29 +3,29 @@ 1 0 2 v1 2 ======== -1 ([],[]) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) -2 (['v1','v4'],['v3','']) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +1 ([]) Tuple(k1 Nested(k2 String, k3 String)) +2 ([('v1','v3'),('v4','')]) Tuple(k1 Nested(k2 String, k3 String)) 1 [] [] 2 ['v1','v4'] ['v3',''] -1 ([],[]) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) -2 (['v1','v4'],['v3','']) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) -3 ([],[]) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) -4 ([],[]) Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) -all_2_2_0 data Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +1 ([]) Tuple(k1 Nested(k2 String, k3 String)) +2 ([('v1','v3'),('v4','')]) Tuple(k1 Nested(k2 String, k3 String)) +3 ([]) Tuple(k1 Nested(k2 String, k3 String)) +4 ([]) Tuple(k1 Nested(k2 String, k3 String)) +all_2_2_0 data Tuple(k1 Nested(k2 String, k3 String)) all_3_3_0 data Tuple(_dummy UInt8) 1 [] [] 2 ['v1','v4'] ['v3',''] 3 [] [] 4 [] [] -data Tuple(`k1.k2` Array(String), `k1.k3` Array(String)) +data Tuple(k1 Nested(k2 String, k3 String)) 1 [] [] 2 ['v1','v4'] ['v3',''] 3 [] [] 4 [] [] ======== -1 (1,'foo',[]) Tuple(`k1.k2` Int8, `k1.k3` String, k4 Array(Int8)) -2 (0,'',[1,2,3]) Tuple(`k1.k2` Int8, `k1.k3` String, k4 Array(Int8)) -3 (10,'',[]) Tuple(`k1.k2` Int8, `k1.k3` String, k4 Array(Int8)) +1 ((1,'foo'),[]) Tuple(k1 Tuple(k2 Int8, k3 String), k4 Array(Int8)) +2 ((0,''),[1,2,3]) Tuple(k1 Tuple(k2 Int8, k3 String), k4 Array(Int8)) +3 ((10,''),[]) Tuple(k1 Tuple(k2 Int8, k3 String), k4 Array(Int8)) 1 1 foo [] 2 0 [1,2,3] 3 10 [] diff --git a/tests/queries/0_stateless/01825_type_json_5.reference b/tests/queries/0_stateless/01825_type_json_5.reference index 8abbbe74968..6b4dfab0643 100644 --- a/tests/queries/0_stateless/01825_type_json_5.reference +++ b/tests/queries/0_stateless/01825_type_json_5.reference @@ -2,4 +2,4 @@ {"s":{"a.c":2,"a.b":1}} 1 [22,33] 2 qqq [44] -Tuple(k1 Int8, `k2.k3` String, `k2.k4` Array(Int8)) +Tuple(k1 Int8, k2 Tuple(k3 String, k4 Array(Int8))) diff --git a/tests/queries/0_stateless/01825_type_json_6.reference b/tests/queries/0_stateless/01825_type_json_6.reference index d3229d48632..7fcd2a40826 100644 --- a/tests/queries/0_stateless/01825_type_json_6.reference +++ b/tests/queries/0_stateless/01825_type_json_6.reference @@ -1,3 +1,3 @@ -Tuple(key String, `out.outputs.index` Array(Array(Int32)), `out.outputs.n` Array(Array(Int8)), `out.type` Array(Int8), `out.value` Array(Int8)) +Tuple(key String, out Nested(outputs Nested(index Int32, n Int8), type Int8, value Int8)) v1 [0,0] [1,2] [[],[1960131]] [[],[0]] v2 [1,1] [4,3] [[1881212],[]] [[1],[]] diff --git a/tests/queries/0_stateless/01825_type_json_distributed.reference b/tests/queries/0_stateless/01825_type_json_distributed.reference index 21308748ef3..9ae85ac888c 100644 --- a/tests/queries/0_stateless/01825_type_json_distributed.reference +++ b/tests/queries/0_stateless/01825_type_json_distributed.reference @@ -1,4 +1,4 @@ -(2,'qqq',[44,55]) Tuple(k1 Int8, `k2.k3` String, `k2.k4` Array(Int8)) -(2,'qqq',[44,55]) Tuple(k1 Int8, `k2.k3` String, `k2.k4` Array(Int8)) +(2,('qqq',[44,55])) Tuple(k1 Int8, k2 Tuple(k3 String, k4 Array(Int8))) +(2,('qqq',[44,55])) Tuple(k1 Int8, k2 Tuple(k3 String, k4 Array(Int8))) 2 qqq [44,55] 2 qqq [44,55] diff --git a/tests/queries/0_stateless/01825_type_json_insert_select.reference b/tests/queries/0_stateless/01825_type_json_insert_select.reference index 3a7fe84af9e..ebf4c1ee736 100644 --- a/tests/queries/0_stateless/01825_type_json_insert_select.reference +++ b/tests/queries/0_stateless/01825_type_json_insert_select.reference @@ -4,9 +4,9 @@ Tuple(k1 Int8, k2 String, k3 String) 1 (1,'foo','') 2 (2,'bar','') 3 (3,'','aaa') -Tuple(`arr.k11` Array(Int8), `arr.k22` Array(String), `arr.k33` Array(Int8), k1 Int8, k2 String, k3 String) -1 ([],[],[],1,'foo','') -2 ([],[],[],2,'bar','') -3 ([],[],[],3,'','aaa') -4 ([5,7],['6','0'],[0,8],0,'','') -5 ([],['str1'],[],0,'','') +Tuple(arr Nested(k11 Int8, k22 String, k33 Int8), k1 Int8, k2 String, k3 String) +1 ([],1,'foo','') +2 ([],2,'bar','') +3 ([],3,'','aaa') +4 ([(5,'6',0),(7,'0',8)],0,'','') +5 ([],0,'','') From 3548e974e1a8d7cbeaf8789b461cfc6f648f3135 Mon Sep 17 00:00:00 2001 From: Youenn Lebras Date: Fri, 13 Aug 2021 17:26:47 +0200 Subject: [PATCH 0109/1647] Implement HasAll specialization for SSE and AVX2 --- src/Functions/GatherUtils/Algorithms.h | 893 +++++++++++++++++++++-- src/Functions/GatherUtils/CMakeLists.txt | 4 + src/Functions/tests/gtest_hasAll.cpp | 104 +++ 3 files changed, 921 insertions(+), 80 deletions(-) create mode 100644 src/Functions/tests/gtest_hasAll.cpp diff --git a/src/Functions/GatherUtils/Algorithms.h b/src/Functions/GatherUtils/Algorithms.h index fc54eaf88ab..245794da976 100644 --- a/src/Functions/GatherUtils/Algorithms.h +++ b/src/Functions/GatherUtils/Algorithms.h @@ -7,7 +7,9 @@ #include #include #include "GatherUtils.h" - +#if defined(__AVX2__) + #include +#endif namespace DB::ErrorCodes { @@ -418,41 +420,55 @@ void NO_INLINE conditional(SourceA && src_a, SourceB && src_b, Sink && sink, con } -/// Methods to check if first array has elements from second array, overloaded for various combinations of types. -template < - ArraySearchType search_type, - typename FirstSliceType, - typename SecondSliceType, - bool (*isEqual)(const FirstSliceType &, const SecondSliceType &, size_t, size_t)> -bool sliceHasImplAnyAll(const FirstSliceType & first, const SecondSliceType & second, const UInt8 * first_null_map, const UInt8 * second_null_map) + +template +bool sliceEqualElements(const NumericArraySlice & first [[maybe_unused]], + const NumericArraySlice & second [[maybe_unused]], + size_t first_ind [[maybe_unused]], + size_t second_ind [[maybe_unused]]) { - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - for (size_t i = 0; i < second.size; ++i) - { - bool has = false; - for (size_t j = 0; j < first.size && !has; ++j) - { - const bool is_first_null = has_first_null_map && first_null_map[j]; - const bool is_second_null = has_second_null_map && second_null_map[i]; - - if (is_first_null && is_second_null) - has = true; - - if (!is_first_null && !is_second_null && isEqual(first, second, j, i)) - has = true; - } - - if (has && search_type == ArraySearchType::Any) - return true; - - if (!has && search_type == ArraySearchType::All) - return false; - } - return search_type == ArraySearchType::All; + /// TODO: Decimal scale + if constexpr (is_decimal && is_decimal) + return accurate::equalsOp(first.data[first_ind].value, second.data[second_ind].value); + else if constexpr (is_decimal || is_decimal) + return false; + else + return accurate::equalsOp(first.data[first_ind], second.data[second_ind]); } +template +bool sliceEqualElements(const NumericArraySlice &, const GenericArraySlice &, size_t, size_t) +{ + return false; +} + +template +bool sliceEqualElements(const GenericArraySlice &, const NumericArraySlice &, size_t, size_t) +{ + return false; +} + +inline ALWAYS_INLINE bool sliceEqualElements(const GenericArraySlice & first, const GenericArraySlice & second, size_t first_ind, size_t second_ind) +{ + return first.elements->compareAt(first_ind + first.begin, second_ind + second.begin, *second.elements, -1) == 0; +} + +template +bool insliceEqualElements(const NumericArraySlice & first [[maybe_unused]], + size_t first_ind [[maybe_unused]], + size_t second_ind [[maybe_unused]]) +{ + if constexpr (is_decimal) + return accurate::equalsOp(first.data[first_ind].value, first.data[second_ind].value); + else + return accurate::equalsOp(first.data[first_ind], first.data[second_ind]); +} +inline ALWAYS_INLINE bool insliceEqualElements(const GenericArraySlice & first, size_t first_ind, size_t second_ind) +{ + return first.elements->compareAt(first_ind + first.begin, second_ind + first.begin, *first.elements, -1) == 0; +} + + /// For details of Knuth-Morris-Pratt string matching algorithm see /// https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm. @@ -481,6 +497,770 @@ std::vector buildKMPPrefixFunction(const SliceType & pattern, const Equa } +/// Methods to check if first array has elements from second array, overloaded for various combinations of types. +template < + ArraySearchType search_type, + typename FirstSliceType, + typename SecondSliceType, + bool (*isEqual)(const FirstSliceType &, const SecondSliceType &, size_t, size_t)> +bool sliceHasImplAnyAll(const FirstSliceType & first, const SecondSliceType & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + for (size_t i = 0; i < second.size; ++i) + { + bool has = false; + for (unsigned j = 0; j < first.size && !has; ++j) + { + const bool is_first_null = has_first_null_map && first_null_map[j]; + const bool is_second_null = has_second_null_map && second_null_map[i]; + + if (is_first_null && is_second_null) + has = true; + + if (!is_first_null && !is_second_null && isEqual(first, second, j, i)) + has = true; + } + + if (has && search_type == ArraySearchType::Any) + return true; + + if (!has && search_type == ArraySearchType::All) + return false; + } + return search_type == ArraySearchType::All; +} + + +#if defined(__AVX2__) +// AVX2 - Int specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (first.size == 0) return true; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (has_second_null_map != has_first_null_map && has_first_null_map) return false; + + unsigned j = 0; + short has_mask = 1; + const int full = -1, none = 0; + const __m256i ones = _mm256_set1_epi32(full); + const __m256i zeros = _mm256_setzero_si256(); + if (first.size > 7 && second.size > 7) + { + for (; j < first.size-7 && has_mask; j += 8) + { + has_mask = 0; + const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(first.data+j)); + // bitmask is fulfilled with ones for ones which are considered as null in the corresponding null map, 0 otherwise; + __m256i bitmask = has_first_null_map ? _mm256_set_epi32((first_null_map[j+7])? full: none, + (first_null_map[j+6])? full: none, + (first_null_map[j+5])? full: none, + (first_null_map[j+4])? full: none, + (first_null_map[j+3])? full: none, + (first_null_map[j+2])? full: none, + (first_null_map[j+1])? full: none, + (first_null_map[j]) ? full: none + ) + :zeros; + + size_t i = 0; + // Browse second array to try to match ell first elements + for (; i < second.size-7 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 8) + { + const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(second.data+i)); + // Create a mask to avoid to compare null elements + // set_m128i takes two arguments: (high segment, low segment) that are two __m128i convert from 8bits to 32bits to fit to our following operations + const __m256i second_nm_mask = _mm256_set_m128i(_mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(second_null_map+i+4))), + _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(second_null_map+i)))); + bitmask = + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + second_nm_mask, + _mm256_cmpeq_epi32(f_data, s_data)), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(6,5,4,3,2,1,0,7)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(6,5,4,3,2,1,0,7))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6)))), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(4,3,2,1,0,7,6,5)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(4,3,2,1,0,7,6,5))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(2,1,0,7,6,5,4,3)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(2,1,0,7,6,5,4,3))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2)))), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(0,7,6,5,4,3,2,1)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(0,7,6,5,4,3,2,1))))))) + ,bitmask); + } + + if (i < second.size) + { + // Loop(i)-jam + for (; i < second.size && !has_mask; i++) + { + if (second_null_map[i]) continue; + __m256i v_i = _mm256_set1_epi32(second.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi32(f_data, v_i)); + has_mask = _mm256_testc_si256 (bitmask, ones); + } + } + } + } + + bool found = false; + // Loop(j)-jam + for (; j < first.size && has_mask; j++) + { + found = (has_first_null_map && first_null_map[j])? true: false; + for (unsigned i = 0; i < second.size && !found; i ++) + { + if (has_second_null_map && second_null_map[i]) continue; + found = (second.data[i] == first.data[j]); + } + if (!found) + return false; + } + return has_mask || found; +} + +// TODO: Discuss about +// raise an error : "error: no viable conversion from 'const NumericArraySlice' to 'const NumericArraySlice'" +// How should we do, copy past each function ?? I haven't found a way to specialize a same function body for two different types. +// AVX2 UInt specialization +// template <> +// inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +// { +// return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements > (static_cast &>(first), static_cast &>(second), first_null_map, second_null_map); +// } + +// AVX2 Int64 specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (first.size == 0) return true; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (has_second_null_map != has_first_null_map && has_first_null_map) return false; + + unsigned j = 0; + short has_mask = 1; + const int full = -1, none = 0; + const __m256i ones = _mm256_set1_epi64x(full); + const __m256i zeros = _mm256_setzero_si256(); + if (first.size > 3 && second.size > 3) + { + for (; j < first.size-3 && has_mask; j += 4) + { + has_mask = 0; + const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(first.data+j)); + __m256i bitmask = has_first_null_map ? _mm256_set_epi64x((first_null_map[j+3])? full: none, + (first_null_map[j+2])? full: none, + (first_null_map[j+1])? full: none, + (first_null_map[j]) ? full: none + ) + :zeros; + + unsigned i = 0; + for (; i < second.size-3 && !has_mask; has_mask = _mm256_testc_si256 (bitmask, ones), i += 4) + { + const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(second.data+i)); + const __m256i second_nm_mask = _mm256_set_m128i(_mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(second_null_map+i+2))), + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(second_null_map+i)))); + bitmask = + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + second_nm_mask, + _mm256_cmpeq_epi64(f_data, s_data)), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6))))), + + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2))))) + ), + bitmask); + } + + if (i < second.size) + { + for (; i < second.size && !has_mask; i++) + { + if (second_null_map[i]) continue; + __m256i v_i = _mm256_set1_epi64x(second.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi64(f_data, v_i)); + has_mask = _mm256_testc_si256 (bitmask, ones); + } + } + } + } + + bool found = false; + for (; j < first.size && (has_mask || first.size <= 2); j++) + { + found = (has_first_null_map && first_null_map[j])? true: false; + for (unsigned i = 0; i < second.size && !found; i ++) + { + if (has_second_null_map && second_null_map[i]) continue; + found = (second.data[i] == first.data[j]); + } + if (!found) + return false; + } + return has_mask || found; +} + +// AVX2 Int16_t specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (first.size == 0) return true; + + const bool has_second_null_map = second_null_map != nullptr; + const bool has_first_null_map = first_null_map != nullptr; + if (has_second_null_map != has_first_null_map && has_first_null_map) return false; + + unsigned j = 0; + short has_mask = 1; + const int full = -1, none = 0; + const __m256i ones = _mm256_set1_epi16(full); + const __m256i zeros = _mm256_setzero_si256(); + if (first.size > 15 && second.size > 15) + { + for (; j < first.size-15 && has_mask; j += 16) + { + has_mask = 0; + const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(first.data+j)); + __m256i bitmask = has_first_null_map ? _mm256_set_epi16((first_null_map[j+15])? full: none, (first_null_map[j+14])? full: none, + (first_null_map[j+13])? full: none, (first_null_map[j+12])? full: none, + (first_null_map[j+11])? full: none, (first_null_map[j+10])? full: none, + (first_null_map[j+9])? full: none, (first_null_map[j+8])? full: none, + (first_null_map[j+7])? full: none, (first_null_map[j+6])? full: none, + (first_null_map[j+5])? full: none, (first_null_map[j+4])? full: none, + (first_null_map[j+3])? full: none, (first_null_map[j+2])? full: none, + (first_null_map[j+1])? full: none, (first_null_map[j]) ? full: none + ) + :zeros; + unsigned i = 0; + for (; i < second.size-15 && !has_mask; has_mask = _mm256_testc_si256 (bitmask, ones), i += 16) + { + const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(second.data+i)); + const __m256i second_nm_mask = _mm256_set_m128i(_mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(second_null_map+i+8))), + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(second_null_map+i)))); + bitmask = + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + second_nm_mask, + _mm256_cmpeq_epi16(f_data, s_data)), + _mm256_andnot_si256( + _mm256_shuffle_epi8(second_nm_mask, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(second_nm_mask, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(second_nm_mask, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(second_nm_mask, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(second_nm_mask, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(second_nm_mask, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(second_nm_mask, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permute2x128_si256(second_nm_mask,second_nm_mask,1), + _mm256_cmpeq_epi16(f_data, _mm256_permute2x128_si256(s_data,s_data,1))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(second_nm_mask,second_nm_mask,1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data,s_data,1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(second_nm_mask,second_nm_mask,1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data,s_data,1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(second_nm_mask,second_nm_mask,1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data,s_data,1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(second_nm_mask,second_nm_mask,1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data,s_data,1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(second_nm_mask,second_nm_mask,1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data,s_data,1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(second_nm_mask,second_nm_mask,1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data,s_data,1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(second_nm_mask,second_nm_mask,1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data,s_data,1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))))) + ) + ), + bitmask); + } + + if (i < second.size) + { + for (; i < second.size && !has_mask; i++) + { + if (second_null_map[i]) continue; + __m256i v_i = _mm256_set1_epi16(second.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi16(f_data, v_i)); + has_mask = _mm256_testc_si256 (bitmask, ones); + } + } + } + } + + bool found = false; + for (; j < first.size && (has_mask || first.size <= 2); j++) + { + found = (has_first_null_map && first_null_map[j])? true: false; + for (unsigned i = 0; i < second.size && !found; i ++) + { + if (has_second_null_map && second_null_map[i]) continue; + found = (second.data[i] == first.data[j]); + } + if (!found) + return false; + } + return has_mask || found; +} + +#else + +// SSE4.2 Int specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (first.size == 0) return true; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (has_second_null_map != has_first_null_map && has_first_null_map) return false; + + unsigned j = 0; + short has_mask = 1; + const __m128i zeros = _mm_setzero_si128(); + if (first.size > 3 && second.size > 2) + { + const int full = -1, none = 0; + for (; j < first.size-3 && has_mask; j += 4) + { + has_mask = 0; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(first.data+j)); + __m128i bitmask = has_first_null_map ? _mm_set_epi32((first_null_map[j+3])? full: none, + (first_null_map[j+2])? full: none, + (first_null_map[j+1])? full: none, + (first_null_map[j]) ? full: none + ) + :zeros; + + unsigned i = 0; + for (; i < second.size-3 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 4) + { + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(second.data+i)); + const __m128i second_nm_mask = (has_second_null_map)? _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(second_null_map+i))) + : zeros; + + bitmask = + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + second_nm_mask, + _mm_cmpeq_epi32(f_data, s_data)), + _mm_andnot_si128( + _mm_shuffle_epi32(second_nm_mask, _MM_SHUFFLE(2,1,0,3)), + _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(2,1,0,3))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi32(second_nm_mask, _MM_SHUFFLE(1,0,3,2)), + _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2)))), + _mm_andnot_si128( + _mm_shuffle_epi32(second_nm_mask, _MM_SHUFFLE(0,3,2,1)), + _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(0,3,2,1))))) + ), + bitmask); + } + + if (i < second.size) + { + for (; i < second.size && !has_mask; i++) + { + if (has_second_null_map && second_null_map[i]) continue; + __m128i r_i = _mm_set1_epi32(second.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi32(f_data, r_i)); + has_mask = _mm_test_all_ones(bitmask); + } + } + } + } + + bool found = false; + for (; j < first.size && has_mask; j++) + { + found = (has_first_null_map && first_null_map[j])? true: false; + for (unsigned i = 0; i < second.size && !found; i ++) + { + if (has_second_null_map && second_null_map[i]) continue; + found = (second.data[i] == first.data[j]); + } + if (!found) + return false; + } + return has_mask || found; +} + +// SSE4.2 Int64 specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (first.size == 0) return true; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (has_first_null_map != has_second_null_map && has_first_null_map) return false; + + unsigned j = 0; + short has_mask = 1; + const Int64 full = -1, none = 0; + const __m128i zeros = _mm_setzero_si128(); + for (; j < first.size-1 && has_mask; j += 2) + { + has_mask = 0; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(first.data+j)); + __m128i bitmask = has_first_null_map ? _mm_set_epi64x((first_null_map[j+1])? full: none, + (first_null_map[j]) ? full: none + ) + : zeros; + unsigned i = 0; + for (; i < second.size-1 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 2) + { + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(second.data+i)); + const __m128i second_nm_mask = (has_second_null_map)? _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(second_null_map+i))) + : zeros; + bitmask = + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + second_nm_mask, + _mm_cmpeq_epi32(f_data, s_data)), + _mm_andnot_si128( + _mm_shuffle_epi32(second_nm_mask, _MM_SHUFFLE(1,0,3,2)), + _mm_cmpeq_epi64(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2))))) + ,bitmask); + } + + if (i < second.size) + { + for (; i < second.size && !has_mask; i++) + { + if (has_second_null_map && second_null_map[i]) continue; + __m128i v_i = _mm_set1_epi64x(second.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi64(f_data, v_i)); + has_mask = _mm_test_all_ones(bitmask); + } + } + } + + bool found = false; + for (; j < first.size && has_mask; j++) + { + // skip null elements since both have at least one + found = (has_first_null_map && first_null_map[j])? true: false; + for (unsigned i = 0; i < second.size && !found; i ++) + { + if (has_second_null_map && second_null_map[i]) continue; + found = (second.data[i] == first.data[j]); + } + if (!found) + return false; + } + return has_mask || found; +} + +// SSE4.2 Int16_t specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (first.size == 0) return true; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (has_first_null_map != has_second_null_map && has_first_null_map) return false; + + unsigned j = 0; + short has_mask = 1; + const int16_t full = -1, none = 0; + const __m128i zeros = _mm_setzero_si128(); + if (first.size > 6 && second.size > 6) + { + for (; j < first.size-7 && has_mask; j += 8) + { + has_mask = 0; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(first.data+j)); + __m128i bitmask = has_first_null_map ? _mm_set_epi16((first_null_map[j+7])? full: none, (first_null_map[j+6])? full: none, + (first_null_map[j+5])? full: none, (first_null_map[j+4])? full: none, + (first_null_map[j+3])? full: none, (first_null_map[j+2])? full: none, + (first_null_map[j+1])? full: none, (first_null_map[j]) ? full: none + ) + :zeros; + unsigned i = 0; + for (; i < second.size-7 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 8) + { + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(second.data+i)); + const __m128i second_nm_mask = (has_second_null_map)? _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(second_null_map+i))) + : zeros; + bitmask = + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + second_nm_mask, + _mm_cmpeq_epi16(f_data, s_data)), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10))))) + ), + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))))) + ), + bitmask); + } + + if (i < second.size) + { + for (; i < second.size && !has_mask; i++) + { + if (has_second_null_map && second_null_map[i]) continue; + __m128i v_i = _mm_set1_epi16(second.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi16(f_data, v_i)); + has_mask = _mm_test_all_ones(bitmask); + } + } + } + } + + bool found = false; + for (; j < first.size && (has_mask || first.size <= 2); j++) + { + found = (has_first_null_map && first_null_map[j])? true: false; + for (unsigned i = 0; i < second.size && !found; i ++) + { + if (has_second_null_map && second_null_map[i]) continue; + found = (second.data[i] == first.data[j]); + } + if (!found) + return false; + } + return has_mask || found; +} +#endif + +// SSE4.2 Int8_t specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (first.size == 0) return true; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (has_first_null_map != has_second_null_map && has_first_null_map) return false; + + unsigned j = 0; + short has_mask = 1; + const int full = -1, none = 0; + const __m128i zeros = _mm_setzero_si128(); + if (first.size > 15) +{ + for (; j < first.size-15 && has_mask; j += 16) + { + has_mask = 0; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(first.data+j)); + __m128i bitmask = has_first_null_map ? _mm_set_epi8((first_null_map[j+15])? full: none, (first_null_map[j+14])? full: none, + (first_null_map[j+13])? full: none, (first_null_map[j+12])? full: none, + (first_null_map[j+11])? full: none, (first_null_map[j+10])? full: none, + (first_null_map[j+9]) ? full: none, (first_null_map[j+8]) ? full: none, + (first_null_map[j+7]) ? full: none, (first_null_map[j+6]) ? full: none, + (first_null_map[j+5]) ? full: none, (first_null_map[j+4]) ? full: none, + (first_null_map[j+3]) ? full: none, (first_null_map[j+2]) ? full: none, + (first_null_map[j+1]) ? full: none, (first_null_map[j]) ? full: none + ) + : zeros; + unsigned i = 0; + for (; i < second.size-15 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 16) + { + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(second.data+i)); + const __m128i second_nm_mask = (has_second_null_map)? _mm_lddqu_si128(reinterpret_cast(second_null_map+i)) + : zeros; + bitmask = + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + second_nm_mask, + _mm_cmpeq_epi8(f_data, s_data)), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,15)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,15))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(12,11,10,9,8,7,6,5,4,3,2,1,0,15,14,13)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(12,11,10,9,8,7,6,5,4,3,2,1,0,15,14,13))))) + ), + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(10,9,8,7,6,5,4,3,2,1,0,15,14,13,12,11)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(10,9,8,7,6,5,4,3,2,1,0,15,14,13,12,11))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(8,7,6,5,4,3,2,1,0,15,14,13,12,11,10,9)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(8,7,6,5,4,3,2,1,0,15,14,13,12,11,10,9))))))), + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,7)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,7))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(4,3,2,1,0,15,14,13,12,11,10,9,8,7,6,5)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(4,3,2,1,0,15,14,13,12,11,10,9,8,7,6,5)))))), + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(2,1,0,15,14,13,12,11,10,9,8,7,6,5,4,3)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(2,1,0,15,14,13,12,11,10,9,8,7,6,5,4,3))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))), + _mm_andnot_si128( + _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(0,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(0,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)))))))), + bitmask); + } + + if (i < second.size) + { + for (; i < second.size && !has_mask; i++) + { + if (has_second_null_map && second_null_map[i]) continue; + __m128i v_i = _mm_set1_epi8(second.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi8(f_data, v_i)); + has_mask = _mm_test_all_ones(bitmask); + } + } + } + } + + bool found = false; + for (; j < first.size && has_mask; j++) + { + found = (has_first_null_map && first_null_map[j])? true: false; + for (unsigned i = 0; i < second.size && !found; i ++) + { + if (has_second_null_map && second_null_map[i]) continue; + found = (second.data[i] == first.data[j]); + } + if (!found) + return false; + } + + return has_mask || found; +} + + template < typename FirstSliceType, typename SecondSliceType, bool (*isEqual)(const FirstSliceType &, const SecondSliceType &, size_t, size_t), @@ -551,53 +1331,6 @@ bool sliceHasImpl(const FirstSliceType & first, const SecondSliceType & second, } -template -bool sliceEqualElements(const NumericArraySlice & first [[maybe_unused]], - const NumericArraySlice & second [[maybe_unused]], - size_t first_ind [[maybe_unused]], - size_t second_ind [[maybe_unused]]) -{ - /// TODO: Decimal scale - if constexpr (is_decimal && is_decimal) - return accurate::equalsOp(first.data[first_ind].value, second.data[second_ind].value); - else if constexpr (is_decimal || is_decimal) - return false; - else - return accurate::equalsOp(first.data[first_ind], second.data[second_ind]); -} - -template -bool sliceEqualElements(const NumericArraySlice &, const GenericArraySlice &, size_t, size_t) -{ - return false; -} - -template -bool sliceEqualElements(const GenericArraySlice &, const NumericArraySlice &, size_t, size_t) -{ - return false; -} - -inline ALWAYS_INLINE bool sliceEqualElements(const GenericArraySlice & first, const GenericArraySlice & second, size_t first_ind, size_t second_ind) -{ - return first.elements->compareAt(first_ind + first.begin, second_ind + second.begin, *second.elements, -1) == 0; -} - -template -bool insliceEqualElements(const NumericArraySlice & first [[maybe_unused]], - size_t first_ind [[maybe_unused]], - size_t second_ind [[maybe_unused]]) -{ - if constexpr (is_decimal) - return accurate::equalsOp(first.data[first_ind].value, first.data[second_ind].value); - else - return accurate::equalsOp(first.data[first_ind], first.data[second_ind]); -} -inline ALWAYS_INLINE bool insliceEqualElements(const GenericArraySlice & first, size_t first_ind, size_t second_ind) -{ - return first.elements->compareAt(first_ind + first.begin, second_ind + first.begin, *first.elements, -1) == 0; -} - template bool sliceHas(const NumericArraySlice & first, const NumericArraySlice & second) { diff --git a/src/Functions/GatherUtils/CMakeLists.txt b/src/Functions/GatherUtils/CMakeLists.txt index f30527c2a46..731407e774c 100644 --- a/src/Functions/GatherUtils/CMakeLists.txt +++ b/src/Functions/GatherUtils/CMakeLists.txt @@ -11,6 +11,10 @@ if (HAS_SUGGEST_DESTRUCTOR_OVERRIDE) target_compile_definitions(clickhouse_functions_gatherutils PUBLIC HAS_SUGGEST_DESTRUCTOR_OVERRIDE) endif() +if (HAVE_AVX2) + target_compile_options(clickhouse_functions_gatherutils PRIVATE -mavx2 -DNAMESPACE=AVX2) +endif() + if (STRIP_DEBUG_SYMBOLS_FUNCTIONS) target_compile_options(clickhouse_functions_gatherutils PRIVATE "-g0") endif() diff --git a/src/Functions/tests/gtest_hasAll.cpp b/src/Functions/tests/gtest_hasAll.cpp new file mode 100644 index 00000000000..bbc841e7605 --- /dev/null +++ b/src/Functions/tests/gtest_hasAll.cpp @@ -0,0 +1,104 @@ +#include + +#include + +using namespace DB::GatherUtils; + + + +template +void array_init(T* first, size_t first_size, T* second, size_t second_size, bool expected_return) { + T i = 0; + for (; i < second_size; i++) { + second[i] = i; + } + for (i=0; i < first_size; i++) { + first[i] = second[std::rand()%second_size]; + } + // Set one element different from + if (!expected_return) { + first[first_size-1] = second_size+1; + } +} + +void null_map_init(UInt8 * null_map, size_t null_map_size, size_t nb_elem) { + for (int i =0; i < null_map_size-1 && i < nb_elem; i++) { + null_map[std::rand()%null_map_size-1] = 1; + } +} + +template +bool test_hasAll(size_t first_size, size_t second_size, bool have_null_map, bool expected_output) { + T* first_data = new T [first_size]; + T* second_data = new T [second_size]; + + UInt8 *first_nm = nullptr, *second_nm = nullptr; + if (have_null_map) { + first_nm = new UInt8 [first_size]; + second_nm = new UInt8 [second_size]; + null_map_init(first_nm, first_size, 5); + null_map_init(second_nm, second_size, 2); + } + + array_init(first_data, first_size, second_data, second_size, expected_output); + + NumericArraySlice first = {first_data, first_size}; + NumericArraySlice second = {second_data, second_size}; + + // Test + /// Check if all first array are elements from second array, overloaded for various combinations of types. + return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(first, second, nullptr, nullptr); +} + +TEST(HasAll, integer) +{ + bool test1 = test_hasAll(4, 100, false, true); + bool test2 = test_hasAll(4, 100, false, false); + bool test3 = test_hasAll(100, 4096, false, true); + bool test4 = test_hasAll(100, 4096, false, false); + + ASSERT_EQ(test1, true); + ASSERT_EQ(test2, false); + ASSERT_EQ(test3, true); + ASSERT_EQ(test4, false); +} + + +TEST(HasAll, int64) +{ + bool test1 = test_hasAll(2, 100, false, true); + bool test2 = test_hasAll(2, 100, false, false); + bool test3 = test_hasAll(100, 4096, false, true); + bool test4 = test_hasAll(100, 4096, false, false); + + ASSERT_EQ(test1, true); + ASSERT_EQ(test2, false); + ASSERT_EQ(test3, true); + ASSERT_EQ(test4, false); +} + +TEST(HasAll, int16) +{ + bool test1 = test_hasAll(2, 100, false, true); + bool test2 = test_hasAll(2, 100, false, false); + bool test3 = test_hasAll(100, 4096, false, true); + bool test4 = test_hasAll(100, 4096, false, false); + + ASSERT_EQ(test1, true); + ASSERT_EQ(test2, false); + ASSERT_EQ(test3, true); + ASSERT_EQ(test4, false); +} + +TEST(HasAll, int8) +{ + bool test1 = test_hasAll(2, 100, false, true); + bool test2 = test_hasAll(2, 100, false, false); + bool test3 = test_hasAll(50, 125, false, true); + bool test4 = test_hasAll(50, 125, false, false); + + ASSERT_EQ(test1, true); + ASSERT_EQ(test2, false); + ASSERT_EQ(test3, true); + ASSERT_EQ(test4, false); +} From abecb8114f470aa2b00122ee6079a32d6b17d62a Mon Sep 17 00:00:00 2001 From: Jakub Kuklis Date: Wed, 18 Aug 2021 15:19:31 +0200 Subject: [PATCH 0110/1647] Refactoring the hasAll gtest so that it works with the original hasAll --- src/Functions/tests/gtest_hasAll.cpp | 97 +++++++++++++++------------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/src/Functions/tests/gtest_hasAll.cpp b/src/Functions/tests/gtest_hasAll.cpp index bbc841e7605..310c059bbbc 100644 --- a/src/Functions/tests/gtest_hasAll.cpp +++ b/src/Functions/tests/gtest_hasAll.cpp @@ -5,57 +5,66 @@ using namespace DB::GatherUtils; - template -void array_init(T* first, size_t first_size, T* second, size_t second_size, bool expected_return) { - T i = 0; - for (; i < second_size; i++) { - second[i] = i; +void array_init(T* elements_to_have, size_t elements_to_have_count, T* set_elements, size_t set_size, bool expected_output) { + for (T i = 0; i < set_size; ++i) + { + set_elements[i] = i; } - for (i=0; i < first_size; i++) { - first[i] = second[std::rand()%second_size]; + for (T i = 0; i < elements_to_have_count; ++i) + { + elements_to_have[i] = set_elements[std::rand() % set_size]; } - // Set one element different from - if (!expected_return) { - first[first_size-1] = second_size+1; + if (!expected_output) + { + // make one element to be searched for missing from the target set + elements_to_have[elements_to_have_count - 1] = set_size + 1; } } -void null_map_init(UInt8 * null_map, size_t null_map_size, size_t nb_elem) { - for (int i =0; i < null_map_size-1 && i < nb_elem; i++) { - null_map[std::rand()%null_map_size-1] = 1; +void null_map_init(UInt8 * null_map, size_t null_map_size, size_t null_elements_count) +{ + for (int i = 0; i < null_map_size; ++i) + { + null_map[i] = 0; + } + for (int i = 0; i < null_map_size - 1 && i < null_elements_count; ++i) + { + null_map[std::rand() % null_map_size - 1] = 1; } } template -bool test_hasAll(size_t first_size, size_t second_size, bool have_null_map, bool expected_output) { - T* first_data = new T [first_size]; - T* second_data = new T [second_size]; +bool testHasAll(size_t elements_to_have_count, size_t set_size, bool have_null_map, bool expected_output) +{ + T * set_elements = new T[set_size]; + T * elements_to_have = new T[elements_to_have_count]; - UInt8 *first_nm = nullptr, *second_nm = nullptr; - if (have_null_map) { - first_nm = new UInt8 [first_size]; - second_nm = new UInt8 [second_size]; - null_map_init(first_nm, first_size, 5); - null_map_init(second_nm, second_size, 2); + UInt8 * first_nm = nullptr, * second_nm = nullptr; + if (have_null_map) + { + first_nm = new UInt8[set_size]; + second_nm = new UInt8[elements_to_have_count]; + null_map_init(first_nm, set_size, 5); + null_map_init(second_nm, elements_to_have_count, 2); } - array_init(first_data, first_size, second_data, second_size, expected_output); + array_init(elements_to_have, elements_to_have_count, set_elements, set_size, expected_output); - NumericArraySlice first = {first_data, first_size}; - NumericArraySlice second = {second_data, second_size}; + NumericArraySlice first = {set_elements, set_size}; + NumericArraySlice second = {elements_to_have, elements_to_have_count}; - // Test - /// Check if all first array are elements from second array, overloaded for various combinations of types. - return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(first, second, nullptr, nullptr); + /// Check whether all elements of the second array are also elements of the first array, overloaded for various combinations of types. + return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + first, second, first_nm, second_nm); } TEST(HasAll, integer) { - bool test1 = test_hasAll(4, 100, false, true); - bool test2 = test_hasAll(4, 100, false, false); - bool test3 = test_hasAll(100, 4096, false, true); - bool test4 = test_hasAll(100, 4096, false, false); + bool test1 = testHasAll(4, 100, false, true); + bool test2 = testHasAll(4, 100, false, false); + bool test3 = testHasAll(100, 4096, false, true); + bool test4 = testHasAll(100, 4096, false, false); ASSERT_EQ(test1, true); ASSERT_EQ(test2, false); @@ -66,10 +75,10 @@ TEST(HasAll, integer) TEST(HasAll, int64) { - bool test1 = test_hasAll(2, 100, false, true); - bool test2 = test_hasAll(2, 100, false, false); - bool test3 = test_hasAll(100, 4096, false, true); - bool test4 = test_hasAll(100, 4096, false, false); + bool test1 = testHasAll(2, 100, false, true); + bool test2 = testHasAll(2, 100, false, false); + bool test3 = testHasAll(100, 4096, false, true); + bool test4 = testHasAll(100, 4096, false, false); ASSERT_EQ(test1, true); ASSERT_EQ(test2, false); @@ -79,10 +88,10 @@ TEST(HasAll, int64) TEST(HasAll, int16) { - bool test1 = test_hasAll(2, 100, false, true); - bool test2 = test_hasAll(2, 100, false, false); - bool test3 = test_hasAll(100, 4096, false, true); - bool test4 = test_hasAll(100, 4096, false, false); + bool test1 = testHasAll(2, 100, false, true); + bool test2 = testHasAll(2, 100, false, false); + bool test3 = testHasAll(100, 4096, false, true); + bool test4 = testHasAll(100, 4096, false, false); ASSERT_EQ(test1, true); ASSERT_EQ(test2, false); @@ -92,10 +101,10 @@ TEST(HasAll, int16) TEST(HasAll, int8) { - bool test1 = test_hasAll(2, 100, false, true); - bool test2 = test_hasAll(2, 100, false, false); - bool test3 = test_hasAll(50, 125, false, true); - bool test4 = test_hasAll(50, 125, false, false); + bool test1 = testHasAll(2, 100, false, true); + bool test2 = testHasAll(2, 100, false, false); + bool test3 = testHasAll(50, 125, false, true); + bool test4 = testHasAll(50, 125, false, false); ASSERT_EQ(test1, true); ASSERT_EQ(test2, false); From 92ec28a87b42653d7d1abef2369e4e6e3407a2cb Mon Sep 17 00:00:00 2001 From: Jakub Kuklis Date: Wed, 18 Aug 2021 16:52:14 +0200 Subject: [PATCH 0111/1647] Refactoring the new SIMD hasAll implementation to comply with current hasAll implementation (swapping 'first' and 'second' arguments meaning) and with ClickHouse C++ guidelines --- src/Functions/GatherUtils/Algorithms.h | 622 +++++++++++++------------ 1 file changed, 336 insertions(+), 286 deletions(-) diff --git a/src/Functions/GatherUtils/Algorithms.h b/src/Functions/GatherUtils/Algorithms.h index 245794da976..2812821e339 100644 --- a/src/Functions/GatherUtils/Algorithms.h +++ b/src/Functions/GatherUtils/Algorithms.h @@ -7,7 +7,7 @@ #include #include #include "GatherUtils.h" -#if defined(__AVX2__) +#if defined(__AVX2__) || defined(__SSE4_2__) #include #endif @@ -420,7 +420,6 @@ void NO_INLINE conditional(SourceA && src_a, SourceB && src_b, Sink && sink, con } - template bool sliceEqualElements(const NumericArraySlice & first [[maybe_unused]], const NumericArraySlice & second [[maybe_unused]], @@ -469,7 +468,6 @@ inline ALWAYS_INLINE bool insliceEqualElements(const GenericArraySlice & first, } - /// For details of Knuth-Morris-Pratt string matching algorithm see /// https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm. /// A "prefix-function" is defined as: i-th element is the length of the longest of all prefixes that end in i-th position @@ -536,91 +534,97 @@ bool sliceHasImplAnyAll(const FirstSliceType & first, const SecondSliceType & se #if defined(__AVX2__) // AVX2 - Int specialization template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) { - if (first.size == 0) return true; + if (second.size == 0) + return true; - const bool has_first_null_map = first_null_map != nullptr; + const bool has_first_null_map = first_null_map != nullptr; const bool has_second_null_map = second_null_map != nullptr; - if (has_second_null_map != has_first_null_map && has_first_null_map) return false; + if (!has_first_null_map && has_second_null_map) + return false; unsigned j = 0; short has_mask = 1; const int full = -1, none = 0; const __m256i ones = _mm256_set1_epi32(full); const __m256i zeros = _mm256_setzero_si256(); - if (first.size > 7 && second.size > 7) + if (second.size > 7 && first.size > 7) { - for (; j < first.size-7 && has_mask; j += 8) + for (; j < second.size - 7 && has_mask; j += 8) { has_mask = 0; - const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(first.data+j)); - // bitmask is fulfilled with ones for ones which are considered as null in the corresponding null map, 0 otherwise; - __m256i bitmask = has_first_null_map ? _mm256_set_epi32((first_null_map[j+7])? full: none, - (first_null_map[j+6])? full: none, - (first_null_map[j+5])? full: none, - (first_null_map[j+4])? full: none, - (first_null_map[j+3])? full: none, - (first_null_map[j+2])? full: none, - (first_null_map[j+1])? full: none, - (first_null_map[j]) ? full: none - ) - :zeros; + const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); + // bitmask is filled with minus ones for ones which are considered as null in the corresponding null map, 0 otherwise; + __m256i bitmask = has_second_null_map ? + _mm256_set_epi32( + (second_null_map[j + 7]) ? full : none, + (second_null_map[j + 6]) ? full : none, + (second_null_map[j + 5]) ? full : none, + (second_null_map[j + 4]) ? full : none, + (second_null_map[j + 3]) ? full : none, + (second_null_map[j + 2]) ? full : none, + (second_null_map[j + 1]) ? full : none, + (second_null_map[j]) ? full : none) + : zeros; size_t i = 0; - // Browse second array to try to match ell first elements - for (; i < second.size-7 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 8) + // Search first array to try to match all second elements + for (; i < first.size - 7 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 8) { - const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(second.data+i)); + const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); // Create a mask to avoid to compare null elements // set_m128i takes two arguments: (high segment, low segment) that are two __m128i convert from 8bits to 32bits to fit to our following operations - const __m256i second_nm_mask = _mm256_set_m128i(_mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(second_null_map+i+4))), - _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(second_null_map+i)))); + const __m256i first_nm_mask = _mm256_set_m128i( + _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 4))), + _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))); bitmask = _mm256_or_si256( _mm256_or_si256( _mm256_or_si256( _mm256_or_si256( _mm256_andnot_si256( - second_nm_mask, + first_nm_mask, _mm256_cmpeq_epi32(f_data, s_data)), _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(6,5,4,3,2,1,0,7)), + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(6,5,4,3,2,1,0,7)), _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(6,5,4,3,2,1,0,7))))), _mm256_or_si256( _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6)))), _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(4,3,2,1,0,7,6,5)), + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(4,3,2,1,0,7,6,5)), _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(4,3,2,1,0,7,6,5))))) ), _mm256_or_si256( _mm256_or_si256( _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(2,1,0,7,6,5,4,3)), + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(2,1,0,7,6,5,4,3)), _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(2,1,0,7,6,5,4,3))))), _mm256_or_si256( _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2)))), _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(0,7,6,5,4,3,2,1)), - _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(0,7,6,5,4,3,2,1))))))) - ,bitmask); + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(0,7,6,5,4,3,2,1)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(0,7,6,5,4,3,2,1))))))), + bitmask); } - if (i < second.size) + if (i < first.size) { // Loop(i)-jam - for (; i < second.size && !has_mask; i++) + for (; i < first.size && !has_mask; ++i) { - if (second_null_map[i]) continue; - __m256i v_i = _mm256_set1_epi32(second.data[i]); - bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi32(f_data, v_i)); + if (has_first_null_map && first_null_map[i]) + continue; + __m256i v_i = _mm256_set1_epi32(first.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi32(f_data, v_i)); has_mask = _mm256_testc_si256 (bitmask, ones); } } @@ -629,13 +633,15 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll -// inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +// inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( +// const NumericArraySlice & second, const NumericArraySlice & first, const UInt8 * second_null_map, const UInt8 * first_null_map) // { -// return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements > (static_cast &>(first), static_cast &>(second), first_null_map, second_null_map); +// return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements > ( +// static_cast &>(second), static_cast &>(first), second_null_map, first_null_map); // } // AVX2 Int64 specialization template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) { - if (first.size == 0) return true; + if (second.size == 0) + return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (has_second_null_map != has_first_null_map && has_first_null_map) return false; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (!has_first_null_map && has_second_null_map) + return false; unsigned j = 0; short has_mask = 1; const int full = -1, none = 0; const __m256i ones = _mm256_set1_epi64x(full); const __m256i zeros = _mm256_setzero_si256(); - if (first.size > 3 && second.size > 3) + if (second.size > 3 && first.size > 3) { - for (; j < first.size-3 && has_mask; j += 4) + for (; j < second.size - 3 && has_mask; j += 4) { has_mask = 0; - const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(first.data+j)); - __m256i bitmask = has_first_null_map ? _mm256_set_epi64x((first_null_map[j+3])? full: none, - (first_null_map[j+2])? full: none, - (first_null_map[j+1])? full: none, - (first_null_map[j]) ? full: none - ) - :zeros; + const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); + __m256i bitmask = has_second_null_map ? + _mm256_set_epi64x( + (second_null_map[j + 3])? full : none, + (second_null_map[j + 2])? full : none, + (second_null_map[j + 1])? full : none, + (second_null_map[j]) ? full : none) + : zeros; unsigned i = 0; - for (; i < second.size-3 && !has_mask; has_mask = _mm256_testc_si256 (bitmask, ones), i += 4) + for (; i < first.size - 3 && !has_mask; has_mask = _mm256_testc_si256 (bitmask, ones), i += 4) { - const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(second.data+i)); - const __m256i second_nm_mask = _mm256_set_m128i(_mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(second_null_map+i+2))), - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(second_null_map+i)))); + const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); + const __m256i first_nm_mask = _mm256_set_m128i( + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 2))), + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))); bitmask = _mm256_or_si256( _mm256_or_si256( _mm256_or_si256( _mm256_andnot_si256( - second_nm_mask, + first_nm_mask, _mm256_cmpeq_epi64(f_data, s_data)), _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6))))), _mm256_or_si256( _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(second_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), - _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2))))) - ), + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2)))))), bitmask); } - if (i < second.size) + if (i < first.size) { - for (; i < second.size && !has_mask; i++) + for (; i < first.size && !has_mask; ++i) { - if (second_null_map[i]) continue; - __m256i v_i = _mm256_set1_epi64x(second.data[i]); - bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi64(f_data, v_i)); + if (has_first_null_map && first_null_map[i]) + continue; + __m256i v_i = _mm256_set1_epi64x(first.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi64(f_data, v_i)); has_mask = _mm256_testc_si256 (bitmask, ones); } } @@ -723,13 +736,14 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) { - if (first.size == 0) return true; + if (second.size == 0) + return true; - const bool has_second_null_map = second_null_map != nullptr; const bool has_first_null_map = first_null_map != nullptr; - if (has_second_null_map != has_first_null_map && has_first_null_map) return false; + const bool has_second_null_map = second_null_map != nullptr; + if (!has_first_null_map && has_second_null_map) + return false; unsigned j = 0; short has_mask = 1; const int full = -1, none = 0; const __m256i ones = _mm256_set1_epi16(full); const __m256i zeros = _mm256_setzero_si256(); - if (first.size > 15 && second.size > 15) + if (second.size > 15 && first.size > 15) { - for (; j < first.size-15 && has_mask; j += 16) + for (; j < second.size - 15 && has_mask; j += 16) { has_mask = 0; - const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(first.data+j)); - __m256i bitmask = has_first_null_map ? _mm256_set_epi16((first_null_map[j+15])? full: none, (first_null_map[j+14])? full: none, - (first_null_map[j+13])? full: none, (first_null_map[j+12])? full: none, - (first_null_map[j+11])? full: none, (first_null_map[j+10])? full: none, - (first_null_map[j+9])? full: none, (first_null_map[j+8])? full: none, - (first_null_map[j+7])? full: none, (first_null_map[j+6])? full: none, - (first_null_map[j+5])? full: none, (first_null_map[j+4])? full: none, - (first_null_map[j+3])? full: none, (first_null_map[j+2])? full: none, - (first_null_map[j+1])? full: none, (first_null_map[j]) ? full: none - ) - :zeros; + const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); + __m256i bitmask = has_second_null_map ? + _mm256_set_epi16( + (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, + (second_null_map[j + 13]) ? full : none, (second_null_map[j + 12]) ? full : none, + (second_null_map[j + 11]) ? full : none, (second_null_map[j + 10]) ? full : none, + (second_null_map[j + 9]) ? full : none, (second_null_map[j + 8])? full : none, + (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6])? full : none, + (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4])? full : none, + (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2])? full : none, + (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full : none) + : zeros; unsigned i = 0; - for (; i < second.size-15 && !has_mask; has_mask = _mm256_testc_si256 (bitmask, ones), i += 16) + for (; i < first.size - 15 && !has_mask; has_mask = _mm256_testc_si256 (bitmask, ones), i += 16) { - const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(second.data+i)); - const __m256i second_nm_mask = _mm256_set_m128i(_mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(second_null_map+i+8))), - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(second_null_map+i)))); + const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); + const __m256i first_nm_mask = _mm256_set_m128i( + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i+8))), + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i)))); bitmask = _mm256_or_si256( _mm256_or_si256( @@ -781,79 +800,80 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) { - if (first.size == 0) return true; + if (second.size == 0) + return true; const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (has_second_null_map != has_first_null_map && has_first_null_map) return false; + const bool has_second_null_map = second_null_map != nullptr; + if (!has_first_null_map && has_second_null_map) + return false; unsigned j = 0; short has_mask = 1; const __m128i zeros = _mm_setzero_si128(); - if (first.size > 3 && second.size > 2) + if (second.size > 3 && first.size > 2) { const int full = -1, none = 0; - for (; j < first.size-3 && has_mask; j += 4) + for (; j < second.size - 3 && has_mask; j += 4) { has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(first.data+j)); - __m128i bitmask = has_first_null_map ? _mm_set_epi32((first_null_map[j+3])? full: none, - (first_null_map[j+2])? full: none, - (first_null_map[j+1])? full: none, - (first_null_map[j]) ? full: none - ) - :zeros; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); + __m128i bitmask = has_second_null_map ? + _mm_set_epi32( + (second_null_map[j + 3]) ? full : none, + (second_null_map[j + 2]) ? full : none, + (second_null_map[j + 1]) ? full : none, + (second_null_map[j]) ? full : none) + : zeros; unsigned i = 0; - for (; i < second.size-3 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 4) + for (; i < first.size - 3 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 4) { - const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(second.data+i)); - const __m128i second_nm_mask = (has_second_null_map)? _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(second_null_map+i))) - : zeros; + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); + const __m128i first_nm_mask = has_first_null_map ? + _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) + : zeros; bitmask = _mm_or_si128( _mm_or_si128( _mm_or_si128( _mm_andnot_si128( - second_nm_mask, + first_nm_mask, _mm_cmpeq_epi32(f_data, s_data)), _mm_andnot_si128( - _mm_shuffle_epi32(second_nm_mask, _MM_SHUFFLE(2,1,0,3)), + _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(2,1,0,3)), _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(2,1,0,3))))), _mm_or_si128( _mm_andnot_si128( - _mm_shuffle_epi32(second_nm_mask, _MM_SHUFFLE(1,0,3,2)), + _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(1,0,3,2)), _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2)))), _mm_andnot_si128( - _mm_shuffle_epi32(second_nm_mask, _MM_SHUFFLE(0,3,2,1)), + _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(0,3,2,1)), _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(0,3,2,1))))) ), bitmask); } - if (i < second.size) + if (i < first.size) { - for (; i < second.size && !has_mask; i++) + for (; i < first.size && !has_mask; ++i) { - if (has_second_null_map && second_null_map[i]) continue; - __m128i r_i = _mm_set1_epi32(second.data[i]); - bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi32(f_data, r_i)); + if (has_first_null_map && first_null_map[i]) + continue; + __m128i r_i = _mm_set1_epi32(first.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi32(f_data, r_i)); has_mask = _mm_test_all_ones(bitmask); } } @@ -946,13 +973,14 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) { - if (first.size == 0) return true; + if (second.size == 0) + return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (has_first_null_map != has_second_null_map && has_first_null_map) return false; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (!has_first_null_map && has_second_null_map) + return false; unsigned j = 0; short has_mask = 1; const Int64 full = -1, none = 0; const __m128i zeros = _mm_setzero_si128(); - for (; j < first.size-1 && has_mask; j += 2) + for (; j < second.size - 1 && has_mask; j += 2) { has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(first.data+j)); - __m128i bitmask = has_first_null_map ? _mm_set_epi64x((first_null_map[j+1])? full: none, - (first_null_map[j]) ? full: none - ) - : zeros; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); + __m128i bitmask = has_second_null_map ? + _mm_set_epi64x( + (second_null_map[j + 1]) ? full : none, + (second_null_map[j]) ? full : none) + : zeros; unsigned i = 0; - for (; i < second.size-1 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 2) + for (; i < first.size - 1 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 2) { - const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(second.data+i)); - const __m128i second_nm_mask = (has_second_null_map)? _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(second_null_map+i))) - : zeros; + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); + const __m128i first_nm_mask = has_first_null_map ? + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) + : zeros; bitmask = _mm_or_si128( _mm_or_si128( _mm_andnot_si128( - second_nm_mask, + first_nm_mask, _mm_cmpeq_epi32(f_data, s_data)), _mm_andnot_si128( - _mm_shuffle_epi32(second_nm_mask, _MM_SHUFFLE(1,0,3,2)), - _mm_cmpeq_epi64(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2))))) - ,bitmask); + _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(1,0,3,2)), + _mm_cmpeq_epi64(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2))))), + bitmask); } - if (i < second.size) + if (i < first.size) { - for (; i < second.size && !has_mask; i++) + for (; i < first.size && !has_mask; ++i) { - if (has_second_null_map && second_null_map[i]) continue; - __m128i v_i = _mm_set1_epi64x(second.data[i]); - bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi64(f_data, v_i)); + if (has_first_null_map && first_null_map[i]) + continue; + __m128i v_i = _mm_set1_epi64x(first.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi64(f_data, v_i)); has_mask = _mm_test_all_ones(bitmask); } } } bool found = false; - for (; j < first.size && has_mask; j++) + for (; j < second.size && has_mask; j++) { - // skip null elements since both have at least one - found = (has_first_null_map && first_null_map[j])? true: false; - for (unsigned i = 0; i < second.size && !found; i ++) + found = (has_second_null_map && second_null_map[j]) ? true : false; + for (unsigned i = 0; i < first.size && !found; ++i) { - if (has_second_null_map && second_null_map[i]) continue; - found = (second.data[i] == first.data[j]); + if (has_first_null_map && first_null_map[i]) + continue; + found = (first.data[i] == second.data[j]); } if (!found) return false; @@ -1030,80 +1064,87 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) { - if (first.size == 0) return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (has_first_null_map != has_second_null_map && has_first_null_map) return false; + if (second.size == 0) + return true; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (!has_first_null_map && has_second_null_map) + return false; unsigned j = 0; short has_mask = 1; const int16_t full = -1, none = 0; const __m128i zeros = _mm_setzero_si128(); - if (first.size > 6 && second.size > 6) + if (second.size > 6 && first.size > 6) { - for (; j < first.size-7 && has_mask; j += 8) + for (; j < second.size - 7 && has_mask; j += 8) { has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(first.data+j)); - __m128i bitmask = has_first_null_map ? _mm_set_epi16((first_null_map[j+7])? full: none, (first_null_map[j+6])? full: none, - (first_null_map[j+5])? full: none, (first_null_map[j+4])? full: none, - (first_null_map[j+3])? full: none, (first_null_map[j+2])? full: none, - (first_null_map[j+1])? full: none, (first_null_map[j]) ? full: none - ) - :zeros; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data+j)); + __m128i bitmask = has_second_null_map ? + _mm_set_epi16( + (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6]) ? full : none, + (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4]) ? full : none, + (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2]) ? full : none, + (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full: none) + : zeros; unsigned i = 0; - for (; i < second.size-7 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 8) + for (; i < first.size-7 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 8) { - const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(second.data+i)); - const __m128i second_nm_mask = (has_second_null_map)? _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(second_null_map+i))) - : zeros; + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); + const __m128i first_nm_mask = has_first_null_map ? + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i))) + : zeros; bitmask = _mm_or_si128( _mm_or_si128( _mm_or_si128( _mm_or_si128( _mm_andnot_si128( - second_nm_mask, + first_nm_mask, _mm_cmpeq_epi16(f_data, s_data)), _mm_andnot_si128( - _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)), + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)), _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14))))), _mm_or_si128( _mm_andnot_si128( - _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)), + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)), _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)))), _mm_andnot_si128( - _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)), + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)), _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10))))) ), _mm_or_si128( _mm_or_si128( _mm_andnot_si128( - _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)), + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)), _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)))), _mm_andnot_si128( - _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)), + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)), _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6))))), _mm_or_si128( _mm_andnot_si128( - _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)), + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)), _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)))), _mm_andnot_si128( - _mm_shuffle_epi8(second_nm_mask, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))))) ), bitmask); } - if (i < second.size) + if (i < first.size) { - for (; i < second.size && !has_mask; i++) + for (; i < first.size && !has_mask; ++i) { - if (has_second_null_map && second_null_map[i]) continue; - __m128i v_i = _mm_set1_epi16(second.data[i]); - bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi16(f_data, v_i)); + if (has_first_null_map && first_null_map[i]) + continue; + __m128i v_i = _mm_set1_epi16(first.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi16(f_data, v_i)); has_mask = _mm_test_all_ones(bitmask); } } @@ -1111,57 +1152,62 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >(const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) { - if (first.size == 0) return true; + if (second.size == 0) + return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (has_first_null_map != has_second_null_map && has_first_null_map) return false; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + if (!has_first_null_map && has_second_null_map) + return false; unsigned j = 0; short has_mask = 1; const int full = -1, none = 0; const __m128i zeros = _mm_setzero_si128(); - if (first.size > 15) -{ - for (; j < first.size-15 && has_mask; j += 16) + if (second.size > 15) + { + for (; j < second.size - 15 && has_mask; j += 16) { has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(first.data+j)); - __m128i bitmask = has_first_null_map ? _mm_set_epi8((first_null_map[j+15])? full: none, (first_null_map[j+14])? full: none, - (first_null_map[j+13])? full: none, (first_null_map[j+12])? full: none, - (first_null_map[j+11])? full: none, (first_null_map[j+10])? full: none, - (first_null_map[j+9]) ? full: none, (first_null_map[j+8]) ? full: none, - (first_null_map[j+7]) ? full: none, (first_null_map[j+6]) ? full: none, - (first_null_map[j+5]) ? full: none, (first_null_map[j+4]) ? full: none, - (first_null_map[j+3]) ? full: none, (first_null_map[j+2]) ? full: none, - (first_null_map[j+1]) ? full: none, (first_null_map[j]) ? full: none - ) - : zeros; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data+j)); + __m128i bitmask = has_second_null_map ? + _mm_set_epi8( + (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, + (second_null_map[j + 13]) ? full : none, (second_null_map[j + 12]) ? full : none, + (second_null_map[j + 11]) ? full : none, (second_null_map[j + 10]) ? full : none, + (second_null_map[j + 9]) ? full : none, (second_null_map[j + 8]) ? full : none, + (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6]) ? full : none, + (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4]) ? full : none, + (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2]) ? full : none, + (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full : none) + : zeros; unsigned i = 0; - for (; i < second.size-15 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 16) + for (; i < first.size - 15 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 16) { - const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(second.data+i)); - const __m128i second_nm_mask = (has_second_null_map)? _mm_lddqu_si128(reinterpret_cast(second_null_map+i)) - : zeros; + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data+i)); + const __m128i first_nm_mask = has_first_null_map ? + _mm_lddqu_si128(reinterpret_cast(first_null_map+i)) + : zeros; bitmask = _mm_or_si128( _mm_or_si128( @@ -1169,89 +1215,91 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll Date: Thu, 19 Aug 2021 11:25:14 +0200 Subject: [PATCH 0112/1647] Adding a null map negative test, the new hasAll implementation needs correction for that case --- src/Functions/tests/gtest_hasAll.cpp | 64 +++++++++++++++++----------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/src/Functions/tests/gtest_hasAll.cpp b/src/Functions/tests/gtest_hasAll.cpp index 310c059bbbc..b7ba59f91c7 100644 --- a/src/Functions/tests/gtest_hasAll.cpp +++ b/src/Functions/tests/gtest_hasAll.cpp @@ -6,57 +6,58 @@ using namespace DB::GatherUtils; template -void array_init(T* elements_to_have, size_t elements_to_have_count, T* set_elements, size_t set_size, bool expected_output) { - for (T i = 0; i < set_size; ++i) +void arrayInit(T* elements_to_have, size_t nb_elements_to_have, T* array_elements, size_t array_size, bool all_elements_present) { + for (T i = 0; i < array_size; ++i) { - set_elements[i] = i; + array_elements[i] = i; } - for (T i = 0; i < elements_to_have_count; ++i) + for (T i = 0; i < nb_elements_to_have; ++i) { - elements_to_have[i] = set_elements[std::rand() % set_size]; + elements_to_have[i] = array_elements[std::rand() % array_size]; } - if (!expected_output) + if (!all_elements_present) { - // make one element to be searched for missing from the target set - elements_to_have[elements_to_have_count - 1] = set_size + 1; + /// make one element to be searched for missing from the target array + elements_to_have[nb_elements_to_have - 1] = array_size + 1; } } -void null_map_init(UInt8 * null_map, size_t null_map_size, size_t null_elements_count) +void nullMapInit(UInt8 * null_map, size_t null_map_size, size_t nb_null_elements) { for (int i = 0; i < null_map_size; ++i) { null_map[i] = 0; } - for (int i = 0; i < null_map_size - 1 && i < null_elements_count; ++i) + for (int i = 0; i < null_map_size - 1 && i < nb_null_elements; ++i) { - null_map[std::rand() % null_map_size - 1] = 1; + null_map[std::rand() % null_map_size] = 1; } } template -bool testHasAll(size_t elements_to_have_count, size_t set_size, bool have_null_map, bool expected_output) +bool testHasAll(size_t nb_elements_to_have, size_t array_size, bool with_null_maps, bool all_elements_present) { - T * set_elements = new T[set_size]; - T * elements_to_have = new T[elements_to_have_count]; + auto array_elements = std::make_unique(array_size); + auto elements_to_have = std::make_unique(nb_elements_to_have); - UInt8 * first_nm = nullptr, * second_nm = nullptr; - if (have_null_map) + std::unique_ptr first_nm = nullptr, second_nm = nullptr; + if (with_null_maps) { - first_nm = new UInt8[set_size]; - second_nm = new UInt8[elements_to_have_count]; - null_map_init(first_nm, set_size, 5); - null_map_init(second_nm, elements_to_have_count, 2); + first_nm = std::make_unique(array_size); + second_nm = std::make_unique(nb_elements_to_have); + /// add a null to elements to have, but not to the target array, making the answer negative + nullMapInit(first_nm.get(), array_size, 0); + nullMapInit(second_nm.get(), nb_elements_to_have, 1); } - array_init(elements_to_have, elements_to_have_count, set_elements, set_size, expected_output); + arrayInit(elements_to_have.get(), nb_elements_to_have, array_elements.get(), array_size, all_elements_present); - NumericArraySlice first = {set_elements, set_size}; - NumericArraySlice second = {elements_to_have, elements_to_have_count}; + NumericArraySlice first = {array_elements.get(), array_size}; + NumericArraySlice second = {elements_to_have.get(), nb_elements_to_have}; - /// Check whether all elements of the second array are also elements of the first array, overloaded for various combinations of types. + /// check whether all elements of the second array are also elements of the first array, overloaded for various combinations of types. return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - first, second, first_nm, second_nm); + first, second, first_nm.get(), second_nm.get()); } TEST(HasAll, integer) @@ -111,3 +112,16 @@ TEST(HasAll, int8) ASSERT_EQ(test3, true); ASSERT_EQ(test4, false); } + +TEST(HasAllSingleNullElement, all) +{ + bool test1 = testHasAll(4, 100, true, true); + bool test2 = testHasAll(4, 100, true, true); + bool test3 = testHasAll(4, 100, true, true); + bool test4 = testHasAll(4, 100, true, true); + + ASSERT_EQ(test1, false); + ASSERT_EQ(test2, false); + ASSERT_EQ(test3, false); + ASSERT_EQ(test4, false); +} From 763bd006a75be454f5c109ef306a0e4e538726b1 Mon Sep 17 00:00:00 2001 From: Jakub Kuklis Date: Thu, 19 Aug 2021 14:13:30 +0200 Subject: [PATCH 0113/1647] Correcting new hasAll implementation for the case with null elements present in 'second' and absent in 'first', refactoring the outer loop remainder into a separate function, improving null checking in the default implementation --- src/Functions/GatherUtils/Algorithms.h | 307 +++++++++++++------------ 1 file changed, 155 insertions(+), 152 deletions(-) diff --git a/src/Functions/GatherUtils/Algorithms.h b/src/Functions/GatherUtils/Algorithms.h index 2812821e339..d37341b0f81 100644 --- a/src/Functions/GatherUtils/Algorithms.h +++ b/src/Functions/GatherUtils/Algorithms.h @@ -7,10 +7,12 @@ #include #include #include "GatherUtils.h" + #if defined(__AVX2__) || defined(__SSE4_2__) - #include +#include #endif + namespace DB::ErrorCodes { extern const int LOGICAL_ERROR; @@ -495,6 +497,20 @@ std::vector buildKMPPrefixFunction(const SliceType & pattern, const Equa } +inline ALWAYS_INLINE bool hasNull(const UInt8 * null_map, size_t null_map_size) +{ + if (null_map != nullptr) + { + for (size_t i = 0; i < null_map_size; ++i) + { + if (null_map[i]) + return true; + } + } + return false; +} + + /// Methods to check if first array has elements from second array, overloaded for various combinations of types. template < ArraySearchType search_type, @@ -506,19 +522,35 @@ bool sliceHasImplAnyAll(const FirstSliceType & first, const SecondSliceType & se const bool has_first_null_map = first_null_map != nullptr; const bool has_second_null_map = second_null_map != nullptr; + const bool has_second_null = hasNull(second_null_map, second.size); + if (has_second_null) + { + const bool has_first_null = hasNull(first_null_map, first.size); + + if (has_first_null && search_type == ArraySearchType::Any) + return true; + + if (!has_first_null && search_type == ArraySearchType::All) + return false; + } + for (size_t i = 0; i < second.size; ++i) { + if (has_second_null_map && second_null_map[i]) + continue; + bool has = false; - for (unsigned j = 0; j < first.size && !has; ++j) + + for (size_t j = 0; j < first.size && !has; ++j) { - const bool is_first_null = has_first_null_map && first_null_map[j]; - const bool is_second_null = has_second_null_map && second_null_map[i]; + if (has_first_null_map && first_null_map[j]) + continue; - if (is_first_null && is_second_null) - has = true; - - if (!is_first_null && !is_second_null && isEqual(first, second, j, i)) + if (isEqual(first, second, j, i)) + { has = true; + break; + } } if (has && search_type == ArraySearchType::Any) @@ -531,21 +563,60 @@ bool sliceHasImplAnyAll(const FirstSliceType & first, const SecondSliceType & se } +#if defined(__AVX2__) || defined(__SSE4_2__) + +template +inline ALWAYS_INLINE bool hasAllIntegralLoopRemainder( + size_t j, const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + for (; j < second.size; ++j) + { + // skip null elements since both have at least one - assuming it was checked earlier that at least one element in 'first' is null + if (has_second_null_map && second_null_map[j]) + continue; + + bool found = false; + + for (size_t i = 0; i < first.size; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + + if (first.data[i] == second.data[j]) + { + found = true; + break; + } + } + + if (!found) + return false; + } + return true; +} + +#endif + + #if defined(__AVX2__) -// AVX2 - Int specialization +// AVX2 Int specialization template <> inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) { if (second.size == 0) return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (!has_first_null_map && has_second_null_map) + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) return false; - unsigned j = 0; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; short has_mask = 1; const int full = -1, none = 0; const __m256i ones = _mm256_set1_epi32(full); @@ -625,28 +696,16 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll // inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( -// const NumericArraySlice & second, const NumericArraySlice & first, const UInt8 * second_null_map, const UInt8 * first_null_map) +// const NumericArraySlice & second, const NumericArraySlice & first, const UInt8 * first_null_map, const UInt8 * second_null_map) // { -// return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements > ( +// return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements > ( // static_cast &>(second), static_cast &>(first), second_null_map, first_null_map); // } // AVX2 Int64 specialization template <> inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) { if (second.size == 0) return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (!has_first_null_map && has_second_null_map) + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) return false; - unsigned j = 0; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; short has_mask = 1; const int full = -1, none = 0; const __m256i ones = _mm256_set1_epi64x(full); @@ -694,7 +754,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); const __m256i first_nm_mask = _mm256_set_m128i( @@ -729,42 +789,33 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 2) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); } // AVX2 Int16_t specialization template <> inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) { if (second.size == 0) return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (!has_first_null_map && has_second_null_map) + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) return false; - unsigned j = 0; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; short has_mask = 1; const int full = -1, none = 0; const __m256i ones = _mm256_set1_epi16(full); @@ -787,7 +838,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); const __m256i first_nm_mask = _mm256_set_m128i( @@ -874,26 +925,16 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 2) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); } #elif defined(__SSE4_2__) @@ -901,17 +942,18 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) { if (second.size == 0) return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (!has_first_null_map && has_second_null_map) + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) return false; - unsigned j = 0; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; short has_mask = 1; const __m128i zeros = _mm_setzero_si128(); if (second.size > 3 && first.size > 2) @@ -972,36 +1014,27 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) { if (second.size == 0) return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (!has_first_null_map && has_second_null_map) + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) return false; - unsigned j = 0; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; short has_mask = 1; const Int64 full = -1, none = 0; const __m128i zeros = _mm_setzero_si128(); @@ -1046,36 +1079,27 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) { if (second.size == 0) return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (!has_first_null_map && has_second_null_map) + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) return false; - unsigned j = 0; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; short has_mask = 1; const int16_t full = -1, none = 0; const __m128i zeros = _mm_setzero_si128(); @@ -1151,36 +1175,27 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 2) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); } // SSE4.2 Int8_t specialization template <> inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * second_null_map, const UInt8 * first_null_map) + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) { if (second.size == 0) return true; - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - if (!has_first_null_map && has_second_null_map) + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) return false; - unsigned j = 0; + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; short has_mask = 1; const int full = -1, none = 0; const __m128i zeros = _mm_setzero_si128(); @@ -1291,21 +1306,10 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll Date: Thu, 19 Aug 2021 15:29:26 +0200 Subject: [PATCH 0114/1647] Correcting { placement --- src/Functions/tests/gtest_hasAll.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Functions/tests/gtest_hasAll.cpp b/src/Functions/tests/gtest_hasAll.cpp index b7ba59f91c7..89f011cd7f1 100644 --- a/src/Functions/tests/gtest_hasAll.cpp +++ b/src/Functions/tests/gtest_hasAll.cpp @@ -6,7 +6,8 @@ using namespace DB::GatherUtils; template -void arrayInit(T* elements_to_have, size_t nb_elements_to_have, T* array_elements, size_t array_size, bool all_elements_present) { +void arrayInit(T* elements_to_have, size_t nb_elements_to_have, T* array_elements, size_t array_size, bool all_elements_present) +{ for (T i = 0; i < array_size; ++i) { array_elements[i] = i; From 169c49c58378a6be1729b1ac2eadaf991d01b1ac Mon Sep 17 00:00:00 2001 From: Jakub Kuklis Date: Fri, 20 Aug 2021 13:00:40 +0200 Subject: [PATCH 0115/1647] Correcting style and resolving warnings --- src/Functions/tests/gtest_hasAll.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Functions/tests/gtest_hasAll.cpp b/src/Functions/tests/gtest_hasAll.cpp index 89f011cd7f1..ca7bc80b4aa 100644 --- a/src/Functions/tests/gtest_hasAll.cpp +++ b/src/Functions/tests/gtest_hasAll.cpp @@ -1,20 +1,29 @@ +#include #include - #include using namespace DB::GatherUtils; +auto uni_int_dist(int min, int max) +{ + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution<> dist(min, max); + return std::make_pair(dist, mt); +} + template void arrayInit(T* elements_to_have, size_t nb_elements_to_have, T* array_elements, size_t array_size, bool all_elements_present) { - for (T i = 0; i < array_size; ++i) + for (size_t i = 0; i < array_size; ++i) { array_elements[i] = i; } - for (T i = 0; i < nb_elements_to_have; ++i) + auto [dist, gen] = uni_int_dist(0, array_size - 1); + for (size_t i = 0; i < nb_elements_to_have; ++i) { - elements_to_have[i] = array_elements[std::rand() % array_size]; + elements_to_have[i] = array_elements[dist(gen)]; } if (!all_elements_present) { @@ -25,13 +34,15 @@ void arrayInit(T* elements_to_have, size_t nb_elements_to_have, T* array_element void nullMapInit(UInt8 * null_map, size_t null_map_size, size_t nb_null_elements) { - for (int i = 0; i < null_map_size; ++i) + /// -2 to keep the last element of the array non-null + auto [dist, gen] = uni_int_dist(0, null_map_size - 2); + for (size_t i = 0; i < null_map_size; ++i) { null_map[i] = 0; } - for (int i = 0; i < null_map_size - 1 && i < nb_null_elements; ++i) + for (size_t i = 0; i < null_map_size - 1 && i < nb_null_elements; ++i) { - null_map[std::rand() % null_map_size] = 1; + null_map[dist(gen)] = 1; } } From a3c08acac3d3ca3239b19dc09cf4bfb3730c37d3 Mon Sep 17 00:00:00 2001 From: Jakub Kuklis Date: Thu, 26 Aug 2021 12:07:56 +0200 Subject: [PATCH 0116/1647] Moving sliceHasImplAnyAll and sliceEqualElements to separate header files to avoid SIMD instructions bloat in Algorithms.h --- src/Functions/GatherUtils/Algorithms.h | 856 +----------------- .../GatherUtils/sliceEqualElements.h | 41 + .../GatherUtils/sliceHasImplAnyAll.h | 839 +++++++++++++++++ 3 files changed, 882 insertions(+), 854 deletions(-) create mode 100644 src/Functions/GatherUtils/sliceEqualElements.h create mode 100644 src/Functions/GatherUtils/sliceHasImplAnyAll.h diff --git a/src/Functions/GatherUtils/Algorithms.h b/src/Functions/GatherUtils/Algorithms.h index d37341b0f81..4bab415f199 100644 --- a/src/Functions/GatherUtils/Algorithms.h +++ b/src/Functions/GatherUtils/Algorithms.h @@ -7,10 +7,8 @@ #include #include #include "GatherUtils.h" - -#if defined(__AVX2__) || defined(__SSE4_2__) -#include -#endif +#include "sliceEqualElements.h" +#include "sliceHasImplAnyAll.h" namespace DB::ErrorCodes @@ -422,38 +420,6 @@ void NO_INLINE conditional(SourceA && src_a, SourceB && src_b, Sink && sink, con } -template -bool sliceEqualElements(const NumericArraySlice & first [[maybe_unused]], - const NumericArraySlice & second [[maybe_unused]], - size_t first_ind [[maybe_unused]], - size_t second_ind [[maybe_unused]]) -{ - /// TODO: Decimal scale - if constexpr (is_decimal && is_decimal) - return accurate::equalsOp(first.data[first_ind].value, second.data[second_ind].value); - else if constexpr (is_decimal || is_decimal) - return false; - else - return accurate::equalsOp(first.data[first_ind], second.data[second_ind]); -} - -template -bool sliceEqualElements(const NumericArraySlice &, const GenericArraySlice &, size_t, size_t) -{ - return false; -} - -template -bool sliceEqualElements(const GenericArraySlice &, const NumericArraySlice &, size_t, size_t) -{ - return false; -} - -inline ALWAYS_INLINE bool sliceEqualElements(const GenericArraySlice & first, const GenericArraySlice & second, size_t first_ind, size_t second_ind) -{ - return first.elements->compareAt(first_ind + first.begin, second_ind + second.begin, *second.elements, -1) == 0; -} - template bool insliceEqualElements(const NumericArraySlice & first [[maybe_unused]], size_t first_ind [[maybe_unused]], @@ -497,824 +463,6 @@ std::vector buildKMPPrefixFunction(const SliceType & pattern, const Equa } -inline ALWAYS_INLINE bool hasNull(const UInt8 * null_map, size_t null_map_size) -{ - if (null_map != nullptr) - { - for (size_t i = 0; i < null_map_size; ++i) - { - if (null_map[i]) - return true; - } - } - return false; -} - - -/// Methods to check if first array has elements from second array, overloaded for various combinations of types. -template < - ArraySearchType search_type, - typename FirstSliceType, - typename SecondSliceType, - bool (*isEqual)(const FirstSliceType &, const SecondSliceType &, size_t, size_t)> -bool sliceHasImplAnyAll(const FirstSliceType & first, const SecondSliceType & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - const bool has_second_null = hasNull(second_null_map, second.size); - if (has_second_null) - { - const bool has_first_null = hasNull(first_null_map, first.size); - - if (has_first_null && search_type == ArraySearchType::Any) - return true; - - if (!has_first_null && search_type == ArraySearchType::All) - return false; - } - - for (size_t i = 0; i < second.size; ++i) - { - if (has_second_null_map && second_null_map[i]) - continue; - - bool has = false; - - for (size_t j = 0; j < first.size && !has; ++j) - { - if (has_first_null_map && first_null_map[j]) - continue; - - if (isEqual(first, second, j, i)) - { - has = true; - break; - } - } - - if (has && search_type == ArraySearchType::Any) - return true; - - if (!has && search_type == ArraySearchType::All) - return false; - } - return search_type == ArraySearchType::All; -} - - -#if defined(__AVX2__) || defined(__SSE4_2__) - -template -inline ALWAYS_INLINE bool hasAllIntegralLoopRemainder( - size_t j, const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - for (; j < second.size; ++j) - { - // skip null elements since both have at least one - assuming it was checked earlier that at least one element in 'first' is null - if (has_second_null_map && second_null_map[j]) - continue; - - bool found = false; - - for (size_t i = 0; i < first.size; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - - if (first.data[i] == second.data[j]) - { - found = true; - break; - } - } - - if (!found) - return false; - } - return true; -} - -#endif - - -#if defined(__AVX2__) -// AVX2 Int specialization -template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - if (second.size == 0) - return true; - - if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) - return false; - - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - size_t j = 0; - short has_mask = 1; - const int full = -1, none = 0; - const __m256i ones = _mm256_set1_epi32(full); - const __m256i zeros = _mm256_setzero_si256(); - if (second.size > 7 && first.size > 7) - { - for (; j < second.size - 7 && has_mask; j += 8) - { - has_mask = 0; - const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); - // bitmask is filled with minus ones for ones which are considered as null in the corresponding null map, 0 otherwise; - __m256i bitmask = has_second_null_map ? - _mm256_set_epi32( - (second_null_map[j + 7]) ? full : none, - (second_null_map[j + 6]) ? full : none, - (second_null_map[j + 5]) ? full : none, - (second_null_map[j + 4]) ? full : none, - (second_null_map[j + 3]) ? full : none, - (second_null_map[j + 2]) ? full : none, - (second_null_map[j + 1]) ? full : none, - (second_null_map[j]) ? full : none) - : zeros; - - size_t i = 0; - // Search first array to try to match all second elements - for (; i < first.size - 7 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 8) - { - const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); - // Create a mask to avoid to compare null elements - // set_m128i takes two arguments: (high segment, low segment) that are two __m128i convert from 8bits to 32bits to fit to our following operations - const __m256i first_nm_mask = _mm256_set_m128i( - _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 4))), - _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))); - bitmask = - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - first_nm_mask, - _mm256_cmpeq_epi32(f_data, s_data)), - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(6,5,4,3,2,1,0,7)), - _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(6,5,4,3,2,1,0,7))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), - _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6)))), - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(4,3,2,1,0,7,6,5)), - _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(4,3,2,1,0,7,6,5))))) - ), - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), - _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(2,1,0,7,6,5,4,3)), - _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(2,1,0,7,6,5,4,3))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), - _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2)))), - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(0,7,6,5,4,3,2,1)), - _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(0,7,6,5,4,3,2,1))))))), - bitmask); - } - - if (i < first.size) - { - // Loop(i)-jam - for (; i < first.size && !has_mask; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - __m256i v_i = _mm256_set1_epi32(first.data[i]); - bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi32(f_data, v_i)); - has_mask = _mm256_testc_si256(bitmask, ones); - } - } - } - } - - if (!has_mask) - return false; - - return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); -} - -// TODO: Discuss about -// raise an error : "error: no viable conversion from 'const NumericArraySlice' to 'const NumericArraySlice'" -// How should we do, copy past each function ?? I haven't found a way to specialize a same function body for two different types. -// AVX2 UInt specialization -// template <> -// inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( -// const NumericArraySlice & second, const NumericArraySlice & first, const UInt8 * first_null_map, const UInt8 * second_null_map) -// { -// return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements > ( -// static_cast &>(second), static_cast &>(first), second_null_map, first_null_map); -// } - -// AVX2 Int64 specialization -template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - if (second.size == 0) - return true; - - if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) - return false; - - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - size_t j = 0; - short has_mask = 1; - const int full = -1, none = 0; - const __m256i ones = _mm256_set1_epi64x(full); - const __m256i zeros = _mm256_setzero_si256(); - if (second.size > 3 && first.size > 3) - { - for (; j < second.size - 3 && has_mask; j += 4) - { - has_mask = 0; - const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); - __m256i bitmask = has_second_null_map ? - _mm256_set_epi64x( - (second_null_map[j + 3])? full : none, - (second_null_map[j + 2])? full : none, - (second_null_map[j + 1])? full : none, - (second_null_map[j]) ? full : none) - : zeros; - - unsigned i = 0; - for (; i < first.size - 3 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 4) - { - const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); - const __m256i first_nm_mask = _mm256_set_m128i( - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 2))), - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))); - bitmask = - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - first_nm_mask, - _mm256_cmpeq_epi64(f_data, s_data)), - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), - _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6))))), - - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), - _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), - _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2)))))), - bitmask); - } - - if (i < first.size) - { - for (; i < first.size && !has_mask; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - __m256i v_i = _mm256_set1_epi64x(first.data[i]); - bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi64(f_data, v_i)); - has_mask = _mm256_testc_si256(bitmask, ones); - } - } - } - } - - if (!has_mask && second.size > 2) - return false; - - return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); -} - -// AVX2 Int16_t specialization -template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - if (second.size == 0) - return true; - - if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) - return false; - - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - size_t j = 0; - short has_mask = 1; - const int full = -1, none = 0; - const __m256i ones = _mm256_set1_epi16(full); - const __m256i zeros = _mm256_setzero_si256(); - if (second.size > 15 && first.size > 15) - { - for (; j < second.size - 15 && has_mask; j += 16) - { - has_mask = 0; - const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); - __m256i bitmask = has_second_null_map ? - _mm256_set_epi16( - (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, - (second_null_map[j + 13]) ? full : none, (second_null_map[j + 12]) ? full : none, - (second_null_map[j + 11]) ? full : none, (second_null_map[j + 10]) ? full : none, - (second_null_map[j + 9]) ? full : none, (second_null_map[j + 8])? full : none, - (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6])? full : none, - (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4])? full : none, - (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2])? full : none, - (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full : none) - : zeros; - unsigned i = 0; - for (; i < first.size - 15 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 16) - { - const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); - const __m256i first_nm_mask = _mm256_set_m128i( - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i+8))), - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i)))); - bitmask = - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - first_nm_mask, - _mm256_cmpeq_epi16(f_data, s_data)), - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26))))) - ), - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)))))) - ), - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_permute2x128_si256(first_nm_mask, first_nm_mask,1), - _mm256_cmpeq_epi16(f_data, _mm256_permute2x128_si256(s_data, s_data, 1))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10))))) - ), - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data ,s_data, 1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data ,s_data ,1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))))) - ) - ), - bitmask); - } - - if (i < first.size) - { - for (; i < first.size && !has_mask; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - __m256i v_i = _mm256_set1_epi16(first.data[i]); - bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi16(f_data, v_i)); - has_mask = _mm256_testc_si256(bitmask, ones); - } - } - } - } - - if (!has_mask && second.size > 2) - return false; - - return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); -} - -#elif defined(__SSE4_2__) - -// SSE4.2 Int specialization -template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - if (second.size == 0) - return true; - - if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) - return false; - - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - size_t j = 0; - short has_mask = 1; - const __m128i zeros = _mm_setzero_si128(); - if (second.size > 3 && first.size > 2) - { - const int full = -1, none = 0; - for (; j < second.size - 3 && has_mask; j += 4) - { - has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); - __m128i bitmask = has_second_null_map ? - _mm_set_epi32( - (second_null_map[j + 3]) ? full : none, - (second_null_map[j + 2]) ? full : none, - (second_null_map[j + 1]) ? full : none, - (second_null_map[j]) ? full : none) - : zeros; - - unsigned i = 0; - for (; i < first.size - 3 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 4) - { - const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); - const __m128i first_nm_mask = has_first_null_map ? - _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) - : zeros; - - bitmask = - _mm_or_si128( - _mm_or_si128( - _mm_or_si128( - _mm_andnot_si128( - first_nm_mask, - _mm_cmpeq_epi32(f_data, s_data)), - _mm_andnot_si128( - _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(2,1,0,3)), - _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(2,1,0,3))))), - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(1,0,3,2)), - _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2)))), - _mm_andnot_si128( - _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(0,3,2,1)), - _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(0,3,2,1))))) - ), - bitmask); - } - - if (i < first.size) - { - for (; i < first.size && !has_mask; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - __m128i r_i = _mm_set1_epi32(first.data[i]); - bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi32(f_data, r_i)); - has_mask = _mm_test_all_ones(bitmask); - } - } - } - } - - if (!has_mask) - return false; - - return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); -} - -// SSE4.2 Int64 specialization -template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - if (second.size == 0) - return true; - - if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) - return false; - - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - size_t j = 0; - short has_mask = 1; - const Int64 full = -1, none = 0; - const __m128i zeros = _mm_setzero_si128(); - for (; j < second.size - 1 && has_mask; j += 2) - { - has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); - __m128i bitmask = has_second_null_map ? - _mm_set_epi64x( - (second_null_map[j + 1]) ? full : none, - (second_null_map[j]) ? full : none) - : zeros; - unsigned i = 0; - for (; i < first.size - 1 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 2) - { - const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); - const __m128i first_nm_mask = has_first_null_map ? - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) - : zeros; - bitmask = - _mm_or_si128( - _mm_or_si128( - _mm_andnot_si128( - first_nm_mask, - _mm_cmpeq_epi32(f_data, s_data)), - _mm_andnot_si128( - _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(1,0,3,2)), - _mm_cmpeq_epi64(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2))))), - bitmask); - } - - if (i < first.size) - { - for (; i < first.size && !has_mask; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - __m128i v_i = _mm_set1_epi64x(first.data[i]); - bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi64(f_data, v_i)); - has_mask = _mm_test_all_ones(bitmask); - } - } - } - - if (!has_mask) - return false; - - return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); -} - -// SSE4.2 Int16_t specialization -template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - if (second.size == 0) - return true; - - if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) - return false; - - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - size_t j = 0; - short has_mask = 1; - const int16_t full = -1, none = 0; - const __m128i zeros = _mm_setzero_si128(); - if (second.size > 6 && first.size > 6) - { - for (; j < second.size - 7 && has_mask; j += 8) - { - has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data+j)); - __m128i bitmask = has_second_null_map ? - _mm_set_epi16( - (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6]) ? full : none, - (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4]) ? full : none, - (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2]) ? full : none, - (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full: none) - : zeros; - unsigned i = 0; - for (; i < first.size-7 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 8) - { - const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); - const __m128i first_nm_mask = has_first_null_map ? - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i))) - : zeros; - bitmask = - _mm_or_si128( - _mm_or_si128( - _mm_or_si128( - _mm_or_si128( - _mm_andnot_si128( - first_nm_mask, - _mm_cmpeq_epi16(f_data, s_data)), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)), - _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14))))), - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)), - _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)), - _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10))))) - ), - _mm_or_si128( - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)), - _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)), - _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6))))), - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)), - _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), - _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))))) - ), - bitmask); - } - - if (i < first.size) - { - for (; i < first.size && !has_mask; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - __m128i v_i = _mm_set1_epi16(first.data[i]); - bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi16(f_data, v_i)); - has_mask = _mm_test_all_ones(bitmask); - } - } - } - } - - if (!has_mask && second.size > 2) - return false; - - return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); -} - -// SSE4.2 Int8_t specialization -template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - if (second.size == 0) - return true; - - if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) - return false; - - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - size_t j = 0; - short has_mask = 1; - const int full = -1, none = 0; - const __m128i zeros = _mm_setzero_si128(); - if (second.size > 15) - { - for (; j < second.size - 15 && has_mask; j += 16) - { - has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data+j)); - __m128i bitmask = has_second_null_map ? - _mm_set_epi8( - (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, - (second_null_map[j + 13]) ? full : none, (second_null_map[j + 12]) ? full : none, - (second_null_map[j + 11]) ? full : none, (second_null_map[j + 10]) ? full : none, - (second_null_map[j + 9]) ? full : none, (second_null_map[j + 8]) ? full : none, - (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6]) ? full : none, - (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4]) ? full : none, - (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2]) ? full : none, - (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full : none) - : zeros; - unsigned i = 0; - for (; i < first.size - 15 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 16) - { - const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data+i)); - const __m128i first_nm_mask = has_first_null_map ? - _mm_lddqu_si128(reinterpret_cast(first_null_map+i)) - : zeros; - bitmask = - _mm_or_si128( - _mm_or_si128( - _mm_or_si128( - _mm_or_si128( - _mm_or_si128( - _mm_andnot_si128( - first_nm_mask, - _mm_cmpeq_epi8(f_data, s_data)), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,15)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,15))))), - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(12,11,10,9,8,7,6,5,4,3,2,1,0,15,14,13)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(12,11,10,9,8,7,6,5,4,3,2,1,0,15,14,13))))) - ), - _mm_or_si128( - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(10,9,8,7,6,5,4,3,2,1,0,15,14,13,12,11)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(10,9,8,7,6,5,4,3,2,1,0,15,14,13,12,11))))), - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(8,7,6,5,4,3,2,1,0,15,14,13,12,11,10,9)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(8,7,6,5,4,3,2,1,0,15,14,13,12,11,10,9))))))), - _mm_or_si128( - _mm_or_si128( - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,7)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,7))))), - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(4,3,2,1,0,15,14,13,12,11,10,9,8,7,6,5)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(4,3,2,1,0,15,14,13,12,11,10,9,8,7,6,5)))))), - _mm_or_si128( - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(2,1,0,15,14,13,12,11,10,9,8,7,6,5,4,3)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(2,1,0,15,14,13,12,11,10,9,8,7,6,5,4,3))))), - _mm_or_si128( - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))), - _mm_andnot_si128( - _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(0,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)), - _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(0,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)))))))), - bitmask); - } - - if (i < first.size) - { - for (; i < first.size && !has_mask; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - __m128i v_i = _mm_set1_epi8(first.data[i]); - bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi8(f_data, v_i)); - has_mask = _mm_test_all_ones(bitmask); - } - } - } - } - - if (!has_mask) - return false; - - return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); -} - -#endif - - template < typename FirstSliceType, typename SecondSliceType, bool (*isEqual)(const FirstSliceType &, const SecondSliceType &, size_t, size_t), diff --git a/src/Functions/GatherUtils/sliceEqualElements.h b/src/Functions/GatherUtils/sliceEqualElements.h new file mode 100644 index 00000000000..f219d51c56a --- /dev/null +++ b/src/Functions/GatherUtils/sliceEqualElements.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include "Slices.h" + +namespace DB::GatherUtils +{ + +template +bool sliceEqualElements(const NumericArraySlice & first [[maybe_unused]], + const NumericArraySlice & second [[maybe_unused]], + size_t first_ind [[maybe_unused]], + size_t second_ind [[maybe_unused]]) +{ + /// TODO: Decimal scale + if constexpr (is_decimal && is_decimal) + return accurate::equalsOp(first.data[first_ind].value, second.data[second_ind].value); + else if constexpr (is_decimal || is_decimal) + return false; + else + return accurate::equalsOp(first.data[first_ind], second.data[second_ind]); +} + +template +bool sliceEqualElements(const NumericArraySlice &, const GenericArraySlice &, size_t, size_t) +{ + return false; +} + +template +bool sliceEqualElements(const GenericArraySlice &, const NumericArraySlice &, size_t, size_t) +{ + return false; +} + +inline ALWAYS_INLINE bool sliceEqualElements(const GenericArraySlice & first, const GenericArraySlice & second, size_t first_ind, size_t second_ind) +{ + return first.elements->compareAt(first_ind + first.begin, second_ind + second.begin, *second.elements, -1) == 0; +} + +} diff --git a/src/Functions/GatherUtils/sliceHasImplAnyAll.h b/src/Functions/GatherUtils/sliceHasImplAnyAll.h new file mode 100644 index 00000000000..59d37473e42 --- /dev/null +++ b/src/Functions/GatherUtils/sliceHasImplAnyAll.h @@ -0,0 +1,839 @@ +#pragma once + +#include "GatherUtils.h" +#include "Slices.h" +#include "sliceEqualElements.h" + +#if defined(__AVX2__) || defined(__SSE4_2__) +#include +#endif + +namespace DB::GatherUtils +{ + +namespace +{ + +inline ALWAYS_INLINE bool hasNull(const UInt8 * null_map, size_t null_map_size) +{ + if (null_map != nullptr) + { + for (size_t i = 0; i < null_map_size; ++i) + { + if (null_map[i]) + return true; + } + } + return false; +} + +} + +/// Methods to check if first array has elements from second array, overloaded for various combinations of types. +template < + ArraySearchType search_type, + typename FirstSliceType, + typename SecondSliceType, + bool (*isEqual)(const FirstSliceType &, const SecondSliceType &, size_t, size_t)> +bool sliceHasImplAnyAll(const FirstSliceType & first, const SecondSliceType & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + const bool has_second_null = hasNull(second_null_map, second.size); + if (has_second_null) + { + const bool has_first_null = hasNull(first_null_map, first.size); + + if (has_first_null && search_type == ArraySearchType::Any) + return true; + + if (!has_first_null && search_type == ArraySearchType::All) + return false; + } + + for (size_t i = 0; i < second.size; ++i) + { + if (has_second_null_map && second_null_map[i]) + continue; + + bool has = false; + + for (size_t j = 0; j < first.size && !has; ++j) + { + if (has_first_null_map && first_null_map[j]) + continue; + + if (isEqual(first, second, j, i)) + { + has = true; + break; + } + } + + if (has && search_type == ArraySearchType::Any) + return true; + + if (!has && search_type == ArraySearchType::All) + return false; + } + return search_type == ArraySearchType::All; +} + + +#if defined(__AVX2__) || defined(__SSE4_2__) + +namespace +{ + +template +inline ALWAYS_INLINE bool hasAllIntegralLoopRemainder( + size_t j, const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + for (; j < second.size; ++j) + { + // skip null elements since both have at least one - assuming it was checked earlier that at least one element in 'first' is null + if (has_second_null_map && second_null_map[j]) + continue; + + bool found = false; + + for (size_t i = 0; i < first.size; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + + if (first.data[i] == second.data[j]) + { + found = true; + break; + } + } + + if (!found) + return false; + } + return true; +} + +} + +#endif + +#if defined(__AVX2__) +// AVX2 Int specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (second.size == 0) + return true; + + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) + return false; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; + short has_mask = 1; + const int full = -1, none = 0; + const __m256i ones = _mm256_set1_epi32(full); + const __m256i zeros = _mm256_setzero_si256(); + if (second.size > 7 && first.size > 7) + { + for (; j < second.size - 7 && has_mask; j += 8) + { + has_mask = 0; + const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); + // bitmask is filled with minus ones for ones which are considered as null in the corresponding null map, 0 otherwise; + __m256i bitmask = has_second_null_map ? + _mm256_set_epi32( + (second_null_map[j + 7]) ? full : none, + (second_null_map[j + 6]) ? full : none, + (second_null_map[j + 5]) ? full : none, + (second_null_map[j + 4]) ? full : none, + (second_null_map[j + 3]) ? full : none, + (second_null_map[j + 2]) ? full : none, + (second_null_map[j + 1]) ? full : none, + (second_null_map[j]) ? full : none) + : zeros; + + size_t i = 0; + // Search first array to try to match all second elements + for (; i < first.size - 7 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 8) + { + const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); + // Create a mask to avoid to compare null elements + // set_m128i takes two arguments: (high segment, low segment) that are two __m128i convert from 8bits to 32bits to fit to our following operations + const __m256i first_nm_mask = _mm256_set_m128i( + _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 4))), + _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))); + bitmask = + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + first_nm_mask, + _mm256_cmpeq_epi32(f_data, s_data)), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(6,5,4,3,2,1,0,7)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(6,5,4,3,2,1,0,7))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6)))), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(4,3,2,1,0,7,6,5)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(4,3,2,1,0,7,6,5))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(2,1,0,7,6,5,4,3)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(2,1,0,7,6,5,4,3))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2)))), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(0,7,6,5,4,3,2,1)), + _mm256_cmpeq_epi32(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(0,7,6,5,4,3,2,1))))))), + bitmask); + } + + if (i < first.size) + { + // Loop(i)-jam + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m256i v_i = _mm256_set1_epi32(first.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi32(f_data, v_i)); + has_mask = _mm256_testc_si256(bitmask, ones); + } + } + } + } + + if (!has_mask) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); +} + +// TODO: Discuss about +// raise an error : "error: no viable conversion from 'const NumericArraySlice' to 'const NumericArraySlice'" +// How should we do, copy past each function ?? I haven't found a way to specialize a same function body for two different types. +// AVX2 UInt specialization +// template <> +// inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( +// const NumericArraySlice & second, const NumericArraySlice & first, const UInt8 * first_null_map, const UInt8 * second_null_map) +// { +// return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements > ( +// static_cast &>(second), static_cast &>(first), second_null_map, first_null_map); +// } + +// AVX2 Int64 specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (second.size == 0) + return true; + + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) + return false; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; + short has_mask = 1; + const int full = -1, none = 0; + const __m256i ones = _mm256_set1_epi64x(full); + const __m256i zeros = _mm256_setzero_si256(); + if (second.size > 3 && first.size > 3) + { + for (; j < second.size - 3 && has_mask; j += 4) + { + has_mask = 0; + const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); + __m256i bitmask = has_second_null_map ? + _mm256_set_epi64x( + (second_null_map[j + 3])? full : none, + (second_null_map[j + 2])? full : none, + (second_null_map[j + 1])? full : none, + (second_null_map[j]) ? full : none) + : zeros; + + unsigned i = 0; + for (; i < first.size - 3 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 4) + { + const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); + const __m256i first_nm_mask = _mm256_set_m128i( + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 2))), + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))); + bitmask = + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + first_nm_mask, + _mm256_cmpeq_epi64(f_data, s_data)), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6))))), + + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2)))))), + bitmask); + } + + if (i < first.size) + { + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m256i v_i = _mm256_set1_epi64x(first.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi64(f_data, v_i)); + has_mask = _mm256_testc_si256(bitmask, ones); + } + } + } + } + + if (!has_mask && second.size > 2) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); +} + +// AVX2 Int16_t specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (second.size == 0) + return true; + + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) + return false; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; + short has_mask = 1; + const int full = -1, none = 0; + const __m256i ones = _mm256_set1_epi16(full); + const __m256i zeros = _mm256_setzero_si256(); + if (second.size > 15 && first.size > 15) + { + for (; j < second.size - 15 && has_mask; j += 16) + { + has_mask = 0; + const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); + __m256i bitmask = has_second_null_map ? + _mm256_set_epi16( + (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, + (second_null_map[j + 13]) ? full : none, (second_null_map[j + 12]) ? full : none, + (second_null_map[j + 11]) ? full : none, (second_null_map[j + 10]) ? full : none, + (second_null_map[j + 9]) ? full : none, (second_null_map[j + 8])? full : none, + (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6])? full : none, + (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4])? full : none, + (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2])? full : none, + (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full : none) + : zeros; + unsigned i = 0; + for (; i < first.size - 15 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 16) + { + const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); + const __m256i first_nm_mask = _mm256_set_m128i( + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i+8))), + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i)))); + bitmask = + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + first_nm_mask, + _mm256_cmpeq_epi16(f_data, s_data)), + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permute2x128_si256(first_nm_mask, first_nm_mask,1), + _mm256_cmpeq_epi16(f_data, _mm256_permute2x128_si256(s_data, s_data, 1))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data ,s_data, 1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data ,s_data ,1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))))) + ) + ), + bitmask); + } + + if (i < first.size) + { + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m256i v_i = _mm256_set1_epi16(first.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi16(f_data, v_i)); + has_mask = _mm256_testc_si256(bitmask, ones); + } + } + } + } + + if (!has_mask && second.size > 2) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); +} + +#elif defined(__SSE4_2__) + +// SSE4.2 Int specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (second.size == 0) + return true; + + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) + return false; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; + short has_mask = 1; + const __m128i zeros = _mm_setzero_si128(); + if (second.size > 3 && first.size > 2) + { + const int full = -1, none = 0; + for (; j < second.size - 3 && has_mask; j += 4) + { + has_mask = 0; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); + __m128i bitmask = has_second_null_map ? + _mm_set_epi32( + (second_null_map[j + 3]) ? full : none, + (second_null_map[j + 2]) ? full : none, + (second_null_map[j + 1]) ? full : none, + (second_null_map[j]) ? full : none) + : zeros; + + unsigned i = 0; + for (; i < first.size - 3 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 4) + { + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); + const __m128i first_nm_mask = has_first_null_map ? + _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) + : zeros; + + bitmask = + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + first_nm_mask, + _mm_cmpeq_epi32(f_data, s_data)), + _mm_andnot_si128( + _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(2,1,0,3)), + _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(2,1,0,3))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(1,0,3,2)), + _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2)))), + _mm_andnot_si128( + _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(0,3,2,1)), + _mm_cmpeq_epi32(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(0,3,2,1))))) + ), + bitmask); + } + + if (i < first.size) + { + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m128i r_i = _mm_set1_epi32(first.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi32(f_data, r_i)); + has_mask = _mm_test_all_ones(bitmask); + } + } + } + } + + if (!has_mask) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); +} + +// SSE4.2 Int64 specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (second.size == 0) + return true; + + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) + return false; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; + short has_mask = 1; + const Int64 full = -1, none = 0; + const __m128i zeros = _mm_setzero_si128(); + for (; j < second.size - 1 && has_mask; j += 2) + { + has_mask = 0; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); + __m128i bitmask = has_second_null_map ? + _mm_set_epi64x( + (second_null_map[j + 1]) ? full : none, + (second_null_map[j]) ? full : none) + : zeros; + unsigned i = 0; + for (; i < first.size - 1 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 2) + { + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); + const __m128i first_nm_mask = has_first_null_map ? + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) + : zeros; + bitmask = + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + first_nm_mask, + _mm_cmpeq_epi32(f_data, s_data)), + _mm_andnot_si128( + _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(1,0,3,2)), + _mm_cmpeq_epi64(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2))))), + bitmask); + } + + if (i < first.size) + { + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m128i v_i = _mm_set1_epi64x(first.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi64(f_data, v_i)); + has_mask = _mm_test_all_ones(bitmask); + } + } + } + + if (!has_mask) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); +} + +// SSE4.2 Int16_t specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (second.size == 0) + return true; + + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) + return false; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; + short has_mask = 1; + const int16_t full = -1, none = 0; + const __m128i zeros = _mm_setzero_si128(); + if (second.size > 6 && first.size > 6) + { + for (; j < second.size - 7 && has_mask; j += 8) + { + has_mask = 0; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data+j)); + __m128i bitmask = has_second_null_map ? + _mm_set_epi16( + (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6]) ? full : none, + (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4]) ? full : none, + (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2]) ? full : none, + (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full: none) + : zeros; + unsigned i = 0; + for (; i < first.size-7 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 8) + { + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); + const __m128i first_nm_mask = has_first_null_map ? + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i))) + : zeros; + bitmask = + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + first_nm_mask, + _mm_cmpeq_epi16(f_data, s_data)), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10))))) + ), + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), + _mm_cmpeq_epi16(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))))) + ), + bitmask); + } + + if (i < first.size) + { + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m128i v_i = _mm_set1_epi16(first.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi16(f_data, v_i)); + has_mask = _mm_test_all_ones(bitmask); + } + } + } + } + + if (!has_mask && second.size > 2) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); +} + +// SSE4.2 Int8_t specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (second.size == 0) + return true; + + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) + return false; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; + short has_mask = 1; + const int full = -1, none = 0; + const __m128i zeros = _mm_setzero_si128(); + if (second.size > 15) + { + for (; j < second.size - 15 && has_mask; j += 16) + { + has_mask = 0; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data+j)); + __m128i bitmask = has_second_null_map ? + _mm_set_epi8( + (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, + (second_null_map[j + 13]) ? full : none, (second_null_map[j + 12]) ? full : none, + (second_null_map[j + 11]) ? full : none, (second_null_map[j + 10]) ? full : none, + (second_null_map[j + 9]) ? full : none, (second_null_map[j + 8]) ? full : none, + (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6]) ? full : none, + (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4]) ? full : none, + (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2]) ? full : none, + (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full : none) + : zeros; + unsigned i = 0; + for (; i < first.size - 15 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 16) + { + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data+i)); + const __m128i first_nm_mask = has_first_null_map ? + _mm_lddqu_si128(reinterpret_cast(first_null_map+i)) + : zeros; + bitmask = + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + first_nm_mask, + _mm_cmpeq_epi8(f_data, s_data)), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,15)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,15))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,15,14)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(12,11,10,9,8,7,6,5,4,3,2,1,0,15,14,13)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(12,11,10,9,8,7,6,5,4,3,2,1,0,15,14,13))))) + ), + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,15,14,13,12)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(10,9,8,7,6,5,4,3,2,1,0,15,14,13,12,11)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(10,9,8,7,6,5,4,3,2,1,0,15,14,13,12,11))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(9,8,7,6,5,4,3,2,1,0,15,14,13,12,11,10)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(8,7,6,5,4,3,2,1,0,15,14,13,12,11,10,9)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(8,7,6,5,4,3,2,1,0,15,14,13,12,11,10,9))))))), + _mm_or_si128( + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,7)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,7))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(5,4,3,2,1,0,15,14,13,12,11,10,9,8,7,6)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(4,3,2,1,0,15,14,13,12,11,10,9,8,7,6,5)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(4,3,2,1,0,15,14,13,12,11,10,9,8,7,6,5)))))), + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(3,2,1,0,15,14,13,12,11,10,9,8,7,6,5,4)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(2,1,0,15,14,13,12,11,10,9,8,7,6,5,4,3)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(2,1,0,15,14,13,12,11,10,9,8,7,6,5,4,3))))), + _mm_or_si128( + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(1,0,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))), + _mm_andnot_si128( + _mm_shuffle_epi8(first_nm_mask, _mm_set_epi8(0,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)), + _mm_cmpeq_epi8(f_data, _mm_shuffle_epi8(s_data, _mm_set_epi8(0,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)))))))), + bitmask); + } + + if (i < first.size) + { + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m128i v_i = _mm_set1_epi8(first.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi8(f_data, v_i)); + has_mask = _mm_test_all_ones(bitmask); + } + } + } + } + + if (!has_mask) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); +} + +#endif + +} From 13878d261850d190dfec91c10d1d8846df7e8035 Mon Sep 17 00:00:00 2001 From: Youenn Lebras Date: Tue, 31 Aug 2021 14:04:15 +0200 Subject: [PATCH 0117/1647] Modify include files according to the processors capabilities --- src/Functions/GatherUtils/sliceHasImplAnyAll.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Functions/GatherUtils/sliceHasImplAnyAll.h b/src/Functions/GatherUtils/sliceHasImplAnyAll.h index 59d37473e42..5603a802e7a 100644 --- a/src/Functions/GatherUtils/sliceHasImplAnyAll.h +++ b/src/Functions/GatherUtils/sliceHasImplAnyAll.h @@ -4,8 +4,12 @@ #include "Slices.h" #include "sliceEqualElements.h" -#if defined(__AVX2__) || defined(__SSE4_2__) -#include +#if defined(__SSE4_2__) + #include + #include +#endif +#if defined(__AVX2__) + #include #endif namespace DB::GatherUtils @@ -124,7 +128,7 @@ inline ALWAYS_INLINE bool hasAllIntegralLoopRemainder( #endif #if defined(__AVX2__) -// AVX2 Int specialization +// AVX2 Int specialization of sliceHasImplAnyAll template <> inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) From ade754d444f668eb0534e36f998bba341ba047e4 Mon Sep 17 00:00:00 2001 From: Youenn Lebras Date: Thu, 2 Sep 2021 18:28:25 +0200 Subject: [PATCH 0118/1647] Fix a bug for avx2 and add performance tests for HasAll --- tests/performance/hasAll.xml | 113 +++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 tests/performance/hasAll.xml diff --git a/tests/performance/hasAll.xml b/tests/performance/hasAll.xml new file mode 100644 index 00000000000..a6ceb915bd5 --- /dev/null +++ b/tests/performance/hasAll.xml @@ -0,0 +1,113 @@ + + CREATE TABLE test_table_small (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_small2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_smallf (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_medium (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_medium2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_mediumf (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_large (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_large2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_largef (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + + + + INSERT INTO test_table_small SELECT + groupArraySample(500)(number) AS set, + groupArraySample(10)(number) AS subset + FROM (SELECT * FROM numbers(500)) + + INSERT INTO test_table_small2 SELECT + groupArraySample(500)(number) AS set, + groupArraySample(400)(number) AS subset + FROM (SELECT * FROM numbers(500)) + + INSERT INTO test_table_smallf SELECT + groupArraySample(500)(number) AS set, + groupArraySample(10)(number) AS subset + FROM (SELECT * FROM numbers(5000000)) + + + + INSERT INTO test_table_medium SELECT + groupArraySample(50000)(number) AS set, + groupArraySample(10)(number) AS subset + FROM + ( + SELECT * + FROM numbers(50000) + ) + + INSERT INTO test_table_medium2 SELECT + groupArraySample(50000)(number) AS set, + groupArraySample(4000)(number) AS subset + FROM + ( + SELECT * + FROM numbers(50000) + ) + + INSERT INTO test_table_mediumf SELECT + groupArraySample(50000)(number) AS set, + groupArraySample(10)(number) AS subset + FROM + ( + SELECT * + FROM numbers(5000000) + ) + + + + INSERT INTO test_table_large SELECT + groupArraySample(5000000)(number) AS set, + groupArraySample(10)(number) AS subset + FROM + ( + SELECT * + FROM numbers(5000000) + ) + + INSERT INTO test_table_large2 SELECT + groupArraySample(5000000)(number) AS set, + groupArraySample(4000)(number) AS subset + FROM + ( + SELECT * + FROM numbers(5000000) + ) + + INSERT INTO test_table_largef SELECT + groupArraySample(5000000)(number) AS set, + groupArraySample(10)(number) AS subset + FROM + ( + SELECT * + FROM numbers(100000000) + ) + + + select hasAll(set, subset) from test_table_small + select hasAll(set, subset) from test_table_small2 + select hasAll(set, subset) from test_table_smallf + + select hasAll(set, subset) from test_table_medium + select hasAll(set, subset) from test_table_medium2 + select hasAll(set, subset) from test_table_mediumf + + select hasAll(set, subset) from test_table_large + select hasAll(set, subset) from test_table_large2 + select hasAll(set, subset) from test_table_largef + + DROP TABLE IF EXISTS test_table_small + DROP TABLE IF EXISTS test_table_small2 + DROP TABLE IF EXISTS test_table_smallf + + DROP TABLE IF EXISTS test_table_medium + DROP TABLE IF EXISTS test_table_medium2 + DROP TABLE IF EXISTS test_table_mediumf + + DROP TABLE IF EXISTS test_table_large + DROP TABLE IF EXISTS test_table_large2 + DROP TABLE IF EXISTS test_table_largef + From a71944d11ddcbc2d277692d8175fea5d91220aed Mon Sep 17 00:00:00 2001 From: Youenn Lebras Date: Fri, 3 Sep 2021 12:19:42 +0200 Subject: [PATCH 0119/1647] Add performance tests for HasAll for int{64,16,8} --- tests/performance/hasAll.xml | 113 ------------------------ tests/performance/hasAll_simd_int16.xml | 52 +++++++++++ tests/performance/hasAll_simd_int32.xml | 52 +++++++++++ tests/performance/hasAll_simd_int64.xml | 52 +++++++++++ tests/performance/hasAll_simd_int8.xml | 52 +++++++++++ 5 files changed, 208 insertions(+), 113 deletions(-) delete mode 100644 tests/performance/hasAll.xml create mode 100644 tests/performance/hasAll_simd_int16.xml create mode 100644 tests/performance/hasAll_simd_int32.xml create mode 100644 tests/performance/hasAll_simd_int64.xml create mode 100644 tests/performance/hasAll_simd_int8.xml diff --git a/tests/performance/hasAll.xml b/tests/performance/hasAll.xml deleted file mode 100644 index a6ceb915bd5..00000000000 --- a/tests/performance/hasAll.xml +++ /dev/null @@ -1,113 +0,0 @@ - - CREATE TABLE test_table_small (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_small2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_smallf (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - - CREATE TABLE test_table_medium (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_medium2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_mediumf (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - - CREATE TABLE test_table_large (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_large2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_largef (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - - - - INSERT INTO test_table_small SELECT - groupArraySample(500)(number) AS set, - groupArraySample(10)(number) AS subset - FROM (SELECT * FROM numbers(500)) - - INSERT INTO test_table_small2 SELECT - groupArraySample(500)(number) AS set, - groupArraySample(400)(number) AS subset - FROM (SELECT * FROM numbers(500)) - - INSERT INTO test_table_smallf SELECT - groupArraySample(500)(number) AS set, - groupArraySample(10)(number) AS subset - FROM (SELECT * FROM numbers(5000000)) - - - - INSERT INTO test_table_medium SELECT - groupArraySample(50000)(number) AS set, - groupArraySample(10)(number) AS subset - FROM - ( - SELECT * - FROM numbers(50000) - ) - - INSERT INTO test_table_medium2 SELECT - groupArraySample(50000)(number) AS set, - groupArraySample(4000)(number) AS subset - FROM - ( - SELECT * - FROM numbers(50000) - ) - - INSERT INTO test_table_mediumf SELECT - groupArraySample(50000)(number) AS set, - groupArraySample(10)(number) AS subset - FROM - ( - SELECT * - FROM numbers(5000000) - ) - - - - INSERT INTO test_table_large SELECT - groupArraySample(5000000)(number) AS set, - groupArraySample(10)(number) AS subset - FROM - ( - SELECT * - FROM numbers(5000000) - ) - - INSERT INTO test_table_large2 SELECT - groupArraySample(5000000)(number) AS set, - groupArraySample(4000)(number) AS subset - FROM - ( - SELECT * - FROM numbers(5000000) - ) - - INSERT INTO test_table_largef SELECT - groupArraySample(5000000)(number) AS set, - groupArraySample(10)(number) AS subset - FROM - ( - SELECT * - FROM numbers(100000000) - ) - - - select hasAll(set, subset) from test_table_small - select hasAll(set, subset) from test_table_small2 - select hasAll(set, subset) from test_table_smallf - - select hasAll(set, subset) from test_table_medium - select hasAll(set, subset) from test_table_medium2 - select hasAll(set, subset) from test_table_mediumf - - select hasAll(set, subset) from test_table_large - select hasAll(set, subset) from test_table_large2 - select hasAll(set, subset) from test_table_largef - - DROP TABLE IF EXISTS test_table_small - DROP TABLE IF EXISTS test_table_small2 - DROP TABLE IF EXISTS test_table_smallf - - DROP TABLE IF EXISTS test_table_medium - DROP TABLE IF EXISTS test_table_medium2 - DROP TABLE IF EXISTS test_table_mediumf - - DROP TABLE IF EXISTS test_table_large - DROP TABLE IF EXISTS test_table_large2 - DROP TABLE IF EXISTS test_table_largef - diff --git a/tests/performance/hasAll_simd_int16.xml b/tests/performance/hasAll_simd_int16.xml new file mode 100644 index 00000000000..c2ce4eec77f --- /dev/null +++ b/tests/performance/hasAll_simd_int16.xml @@ -0,0 +1,52 @@ + + CREATE TABLE test_table_small (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_small2 (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_smallf (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_medium (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_medium2 (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_mediumf (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_large (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_large2 (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_largef (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + + + INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) + INSERT INTO test_table_small2 SELECT groupArraySample(500)(number) AS set, groupArraySample(400)(number) AS subset FROM (SELECT * FROM numbers(500)) + INSERT INTO test_table_smallf SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(5000000)) + + + INSERT INTO test_table_medium SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000)) + INSERT INTO test_table_medium2 SELECT groupArraySample(1000000)(number) AS set, groupArraySample(4000)(number) AS subset FROM (SELECT * FROM numbers(1000000)) + INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) + + + INSERT INTO test_table_large SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + INSERT INTO test_table_large2 SELECT groupArraySample(50000000)(number) AS set, groupArraySample(8000)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + INSERT INTO test_table_largef SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000000)) + + select hasAll(set, subset) from test_table_small + select hasAll(set, subset) from test_table_small2 + select hasAll(set, subset) from test_table_smallf + + select hasAll(set, subset) from test_table_medium + select hasAll(set, subset) from test_table_medium2 + select hasAll(set, subset) from test_table_mediumf + + select hasAll(set, subset) from test_table_large + select hasAll(set, subset) from test_table_large2 Settings max_execution_time=300 + select hasAll(set, subset) from test_table_largef + + DROP TABLE IF EXISTS test_table_small + DROP TABLE IF EXISTS test_table_small2 + DROP TABLE IF EXISTS test_table_smallf + + DROP TABLE IF EXISTS test_table_medium + DROP TABLE IF EXISTS test_table_medium2 + DROP TABLE IF EXISTS test_table_mediumf + + DROP TABLE IF EXISTS test_table_large + DROP TABLE IF EXISTS test_table_large2 + DROP TABLE IF EXISTS test_table_largef + diff --git a/tests/performance/hasAll_simd_int32.xml b/tests/performance/hasAll_simd_int32.xml new file mode 100644 index 00000000000..4543dea161b --- /dev/null +++ b/tests/performance/hasAll_simd_int32.xml @@ -0,0 +1,52 @@ + + CREATE TABLE test_table_small (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_small2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_smallf (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_medium (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_medium2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_mediumf (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_large (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_large2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_largef (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + + + INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) + INSERT INTO test_table_small2 SELECT groupArraySample(500)(number) AS set, groupArraySample(400)(number) AS subset FROM (SELECT * FROM numbers(500)) + INSERT INTO test_table_smallf SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(5000000)) + + + INSERT INTO test_table_medium SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000)) + INSERT INTO test_table_medium2 SELECT groupArraySample(1000000)(number) AS set, groupArraySample(4000)(number) AS subset FROM (SELECT * FROM numbers(1000000)) + INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) + + + INSERT INTO test_table_large SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + INSERT INTO test_table_large2 SELECT groupArraySample(50000000)(number) AS set, groupArraySample(4000)(number) AS subset FROM (SELECT * FROM numbers(50000000)) Settings max_execution_time=30 + INSERT INTO test_table_largef SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000000)) + + select hasAll(set, subset) from test_table_small + select hasAll(set, subset) from test_table_small2 + select hasAll(set, subset) from test_table_smallf + + select hasAll(set, subset) from test_table_medium + select hasAll(set, subset) from test_table_medium2 + select hasAll(set, subset) from test_table_mediumf + + select hasAll(set, subset) from test_table_large + select hasAll(set, subset) from test_table_large2 Settings max_execution_time=300 + select hasAll(set, subset) from test_table_largef + + DROP TABLE IF EXISTS test_table_small + DROP TABLE IF EXISTS test_table_small2 + DROP TABLE IF EXISTS test_table_smallf + + DROP TABLE IF EXISTS test_table_medium + DROP TABLE IF EXISTS test_table_medium2 + DROP TABLE IF EXISTS test_table_mediumf + + DROP TABLE IF EXISTS test_table_large + DROP TABLE IF EXISTS test_table_large2 + DROP TABLE IF EXISTS test_table_largef + diff --git a/tests/performance/hasAll_simd_int64.xml b/tests/performance/hasAll_simd_int64.xml new file mode 100644 index 00000000000..07e52483bb1 --- /dev/null +++ b/tests/performance/hasAll_simd_int64.xml @@ -0,0 +1,52 @@ + + CREATE TABLE test_table_small (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_small2 (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_smallf (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_medium (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_medium2 (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_mediumf (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_large (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_large2 (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_largef (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + + + INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) + INSERT INTO test_table_small2 SELECT groupArraySample(500)(number) AS set, groupArraySample(400)(number) AS subset FROM (SELECT * FROM numbers(500)) + INSERT INTO test_table_smallf SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(5000000)) + + + INSERT INTO test_table_medium SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000)) + INSERT INTO test_table_medium2 SELECT groupArraySample(1000000)(number) AS set, groupArraySample(4000)(number) AS subset FROM (SELECT * FROM numbers(1000000)) + INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) + + + INSERT INTO test_table_large SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + INSERT INTO test_table_large2 SELECT groupArraySample(50000000)(number) AS set, groupArraySample(2000)(number) AS subset FROM (SELECT * FROM numbers(50000000)) Settings max_execution_time=30 + INSERT INTO test_table_largef SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000000)) + + select hasAll(set, subset) from test_table_small + select hasAll(set, subset) from test_table_small2 + select hasAll(set, subset) from test_table_smallf + + select hasAll(set, subset) from test_table_medium + select hasAll(set, subset) from test_table_medium2 + select hasAll(set, subset) from test_table_mediumf + + select hasAll(set, subset) from test_table_large + select hasAll(set, subset) from test_table_large2 Settings max_execution_time=300 + select hasAll(set, subset) from test_table_largef + + DROP TABLE IF EXISTS test_table_small + DROP TABLE IF EXISTS test_table_small2 + DROP TABLE IF EXISTS test_table_smallf + + DROP TABLE IF EXISTS test_table_medium + DROP TABLE IF EXISTS test_table_medium2 + DROP TABLE IF EXISTS test_table_mediumf + + DROP TABLE IF EXISTS test_table_large + DROP TABLE IF EXISTS test_table_large2 + DROP TABLE IF EXISTS test_table_largef + diff --git a/tests/performance/hasAll_simd_int8.xml b/tests/performance/hasAll_simd_int8.xml new file mode 100644 index 00000000000..5ddc84aa5bd --- /dev/null +++ b/tests/performance/hasAll_simd_int8.xml @@ -0,0 +1,52 @@ + + CREATE TABLE test_table_small (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_small2 (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_smallf (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_medium (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_medium2 (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_mediumf (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + + CREATE TABLE test_table_large (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_large2 (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + CREATE TABLE test_table_largef (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + + + INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) + INSERT INTO test_table_small2 SELECT groupArraySample(500)(number) AS set, groupArraySample(400)(number) AS subset FROM (SELECT * FROM numbers(500)) + INSERT INTO test_table_smallf SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(5000000)) + + + INSERT INTO test_table_medium SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000)) + INSERT INTO test_table_medium2 SELECT groupArraySample(1000000)(number) AS set, groupArraySample(4000)(number) AS subset FROM (SELECT * FROM numbers(1000000)) + INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) + + + INSERT INTO test_table_large SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + INSERT INTO test_table_large2 SELECT groupArraySample(50000000)(number) AS set, groupArraySample(4000)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + INSERT INTO test_table_largef SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000000)) + + select hasAll(set, subset) from test_table_small + select hasAll(set, subset) from test_table_small2 + select hasAll(set, subset) from test_table_smallf + + select hasAll(set, subset) from test_table_medium + select hasAll(set, subset) from test_table_medium2 + select hasAll(set, subset) from test_table_mediumf + + select hasAll(set, subset) from test_table_large + select hasAll(set, subset) from test_table_large2 Settings max_execution_time=300 + select hasAll(set, subset) from test_table_largef + + DROP TABLE IF EXISTS test_table_small + DROP TABLE IF EXISTS test_table_small2 + DROP TABLE IF EXISTS test_table_smallf + + DROP TABLE IF EXISTS test_table_medium + DROP TABLE IF EXISTS test_table_medium2 + DROP TABLE IF EXISTS test_table_mediumf + + DROP TABLE IF EXISTS test_table_large + DROP TABLE IF EXISTS test_table_large2 + DROP TABLE IF EXISTS test_table_largef + From 62487fe2fcf0eb90e53bd9291afec42761f4a797 Mon Sep 17 00:00:00 2001 From: Youenn Lebras Date: Mon, 6 Sep 2021 09:20:03 +0200 Subject: [PATCH 0120/1647] Pass SSE version to 4.2 and exploiting it's specific loadu --- src/Functions/GatherUtils/CMakeLists.txt | 3 + .../GatherUtils/sliceHasImplAnyAll.h | 140 ++++++++++-------- 2 files changed, 79 insertions(+), 64 deletions(-) diff --git a/src/Functions/GatherUtils/CMakeLists.txt b/src/Functions/GatherUtils/CMakeLists.txt index 731407e774c..a379ccbadde 100644 --- a/src/Functions/GatherUtils/CMakeLists.txt +++ b/src/Functions/GatherUtils/CMakeLists.txt @@ -11,6 +11,9 @@ if (HAS_SUGGEST_DESTRUCTOR_OVERRIDE) target_compile_definitions(clickhouse_functions_gatherutils PUBLIC HAS_SUGGEST_DESTRUCTOR_OVERRIDE) endif() +if (HAVE_SSE42) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -msse4.2") +endif() if (HAVE_AVX2) target_compile_options(clickhouse_functions_gatherutils PRIVATE -mavx2 -DNAMESPACE=AVX2) endif() diff --git a/src/Functions/GatherUtils/sliceHasImplAnyAll.h b/src/Functions/GatherUtils/sliceHasImplAnyAll.h index 5603a802e7a..111b9d767dd 100644 --- a/src/Functions/GatherUtils/sliceHasImplAnyAll.h +++ b/src/Functions/GatherUtils/sliceHasImplAnyAll.h @@ -6,6 +6,7 @@ #if defined(__SSE4_2__) #include + #include #include #endif #if defined(__AVX2__) @@ -153,7 +154,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(second.data + j)); - // bitmask is filled with minus ones for ones which are considered as null in the corresponding null map, 0 otherwise; + // bits of the bitmask are set to one if considered as null in the corresponding null map, 0 otherwise; __m256i bitmask = has_second_null_map ? _mm256_set_epi32( (second_null_map[j + 7]) ? full : none, @@ -167,15 +168,16 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); // Create a mask to avoid to compare null elements - // set_m128i takes two arguments: (high segment, low segment) that are two __m128i convert from 8bits to 32bits to fit to our following operations - const __m256i first_nm_mask = _mm256_set_m128i( - _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 4))), - _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))); + // set_m128i takes two arguments: (high segment, low segment) that are two __m128i convert from 8bits to 32bits to match with next operations + const __m256i first_nm_mask = has_first_null_map? + _mm256_set_m128i( + _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 4))), + _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))) + : zeros; bitmask = _mm256_or_si256( _mm256_or_si256( @@ -228,7 +230,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 7) return false; return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); @@ -262,7 +264,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 3 && first.size > 3) @@ -271,6 +273,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(second.data + j)); + // bits of the bitmask are set to one if considered as null in the corresponding null map, 0 otherwise; __m256i bitmask = has_second_null_map ? _mm256_set_epi64x( (second_null_map[j + 3])? full : none, @@ -283,9 +286,11 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); - const __m256i first_nm_mask = _mm256_set_m128i( - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 2))), - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))); + const __m256i first_nm_mask = has_first_null_map? + _mm256_set_m128i( + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 2))), + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))) + : zeros; bitmask = _mm256_or_si256( _mm256_or_si256( @@ -321,7 +326,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 2) + if (!has_mask && second.size > 3) return false; return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); @@ -343,7 +348,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 15 && first.size > 15) @@ -367,9 +372,11 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); - const __m256i first_nm_mask = _mm256_set_m128i( - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i+8))), - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i)))); + const __m256i first_nm_mask = has_first_null_map? + _mm256_set_m128i( + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 8))), + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))) + : zeros; bitmask = _mm256_or_si256( _mm256_or_si256( @@ -457,7 +464,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 2) + if (!has_mask && second.size > 15) return false; return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); @@ -481,10 +488,10 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 3 && first.size > 2) + if (second.size > 3 && first.size > 3) { - const int full = -1, none = 0; for (; j < second.size - 3 && has_mask; j += 4) { has_mask = 0; @@ -540,7 +547,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 3) return false; return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); @@ -564,48 +571,51 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 1 && first.size > 1) { - has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); - __m128i bitmask = has_second_null_map ? - _mm_set_epi64x( - (second_null_map[j + 1]) ? full : none, - (second_null_map[j]) ? full : none) - : zeros; - unsigned i = 0; - for (; i < first.size - 1 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 2) + for (; j < second.size - 1 && has_mask; j += 2) { - const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); - const __m128i first_nm_mask = has_first_null_map ? - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) + has_mask = 0; + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); + __m128i bitmask = has_second_null_map ? + _mm_set_epi64x( + (second_null_map[j + 1]) ? full : none, + (second_null_map[j]) ? full : none) : zeros; - bitmask = - _mm_or_si128( - _mm_or_si128( - _mm_andnot_si128( - first_nm_mask, - _mm_cmpeq_epi32(f_data, s_data)), - _mm_andnot_si128( - _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(1,0,3,2)), - _mm_cmpeq_epi64(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2))))), - bitmask); - } - - if (i < first.size) - { - for (; i < first.size && !has_mask; ++i) + unsigned i = 0; + for (; i < first.size - 1 && !has_mask; has_mask = _mm_test_all_ones(bitmask), i += 2) { - if (has_first_null_map && first_null_map[i]) - continue; - __m128i v_i = _mm_set1_epi64x(first.data[i]); - bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi64(f_data, v_i)); - has_mask = _mm_test_all_ones(bitmask); + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); + const __m128i first_nm_mask = has_first_null_map ? + _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) + : zeros; + bitmask = + _mm_or_si128( + _mm_or_si128( + _mm_andnot_si128( + first_nm_mask, + _mm_cmpeq_epi64(f_data, s_data)), + _mm_andnot_si128( + _mm_shuffle_epi32(first_nm_mask, _MM_SHUFFLE(1,0,3,2)), + _mm_cmpeq_epi64(f_data, _mm_shuffle_epi32(s_data, _MM_SHUFFLE(1,0,3,2))))), + bitmask); + } + + if (i < first.size) + { + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m128i v_i = _mm_set1_epi64x(first.data[i]); + bitmask = _mm_or_si128(bitmask, _mm_cmpeq_epi64(f_data, v_i)); + has_mask = _mm_test_all_ones(bitmask); + } } } } - if (!has_mask) + if (!has_mask && second.size > 1) return false; return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); @@ -634,7 +644,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(second.data+j)); + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); __m128i bitmask = has_second_null_map ? _mm_set_epi16( (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6]) ? full : none, @@ -647,7 +657,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); const __m128i first_nm_mask = has_first_null_map ? - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map+i))) + _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) : zeros; bitmask = _mm_or_si128( @@ -701,13 +711,15 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 2) + if (!has_mask && second.size > 6) return false; return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); } -// SSE4.2 Int8_t specialization +// Int8 version is faster with SSE than with AVX2 +#if defined(__SSE4_2__) +// SSE2 Int8_t specialization template <> inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) @@ -723,14 +735,14 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 15) + if (second.size > 15 && first.size > 15) { for (; j < second.size - 15 && has_mask; j += 16) { has_mask = 0; - const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data+j)); + const __m128i f_data = _mm_lddqu_si128(reinterpret_cast(second.data + j)); __m128i bitmask = has_second_null_map ? _mm_set_epi8( (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, @@ -745,9 +757,9 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data+i)); + const __m128i s_data = _mm_lddqu_si128(reinterpret_cast(first.data + i)); const __m128i first_nm_mask = has_first_null_map ? - _mm_lddqu_si128(reinterpret_cast(first_null_map+i)) + _mm_lddqu_si128(reinterpret_cast(first_null_map + i)) : zeros; bitmask = _mm_or_si128( @@ -832,7 +844,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll 15) return false; return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); From a810ce5dcb46a6de40fdc1afa9cb5ee7eab6a5b5 Mon Sep 17 00:00:00 2001 From: Youenn Lebras Date: Thu, 9 Sep 2021 11:21:32 +0200 Subject: [PATCH 0121/1647] Remove AVX2 to figure out where is the illegal intruction Enable AVX2 - int32 --- .../GatherUtils/sliceHasImplAnyAll.h | 235 ------------------ 1 file changed, 235 deletions(-) diff --git a/src/Functions/GatherUtils/sliceHasImplAnyAll.h b/src/Functions/GatherUtils/sliceHasImplAnyAll.h index 111b9d767dd..7c253cbc407 100644 --- a/src/Functions/GatherUtils/sliceHasImplAnyAll.h +++ b/src/Functions/GatherUtils/sliceHasImplAnyAll.h @@ -12,7 +12,6 @@ #if defined(__AVX2__) #include #endif - namespace DB::GatherUtils { @@ -236,240 +235,6 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll' to 'const NumericArraySlice'" -// How should we do, copy past each function ?? I haven't found a way to specialize a same function body for two different types. -// AVX2 UInt specialization -// template <> -// inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( -// const NumericArraySlice & second, const NumericArraySlice & first, const UInt8 * first_null_map, const UInt8 * second_null_map) -// { -// return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements > ( -// static_cast &>(second), static_cast &>(first), second_null_map, first_null_map); -// } - -// AVX2 Int64 specialization -template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - if (second.size == 0) - return true; - - if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) - return false; - - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - size_t j = 0; - short has_mask = 1; - const Int64 full = -1, none = 0; - const __m256i ones = _mm256_set1_epi64x(full); - const __m256i zeros = _mm256_setzero_si256(); - if (second.size > 3 && first.size > 3) - { - for (; j < second.size - 3 && has_mask; j += 4) - { - has_mask = 0; - const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); - // bits of the bitmask are set to one if considered as null in the corresponding null map, 0 otherwise; - __m256i bitmask = has_second_null_map ? - _mm256_set_epi64x( - (second_null_map[j + 3])? full : none, - (second_null_map[j + 2])? full : none, - (second_null_map[j + 1])? full : none, - (second_null_map[j]) ? full : none) - : zeros; - - unsigned i = 0; - for (; i < first.size - 3 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 4) - { - const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); - const __m256i first_nm_mask = has_first_null_map? - _mm256_set_m128i( - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 2))), - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))) - : zeros; - bitmask = - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - first_nm_mask, - _mm256_cmpeq_epi64(f_data, s_data)), - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), - _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6))))), - - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), - _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), - _mm256_andnot_si256( - _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), - _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2)))))), - bitmask); - } - - if (i < first.size) - { - for (; i < first.size && !has_mask; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - __m256i v_i = _mm256_set1_epi64x(first.data[i]); - bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi64(f_data, v_i)); - has_mask = _mm256_testc_si256(bitmask, ones); - } - } - } - } - - if (!has_mask && second.size > 3) - return false; - - return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); -} - -// AVX2 Int16_t specialization -template <> -inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( - const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) -{ - if (second.size == 0) - return true; - - if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) - return false; - - const bool has_first_null_map = first_null_map != nullptr; - const bool has_second_null_map = second_null_map != nullptr; - - size_t j = 0; - short has_mask = 1; - const int16_t full = -1, none = 0; - const __m256i ones = _mm256_set1_epi16(full); - const __m256i zeros = _mm256_setzero_si256(); - if (second.size > 15 && first.size > 15) - { - for (; j < second.size - 15 && has_mask; j += 16) - { - has_mask = 0; - const __m256i f_data = _mm256_lddqu_si256(reinterpret_cast(second.data + j)); - __m256i bitmask = has_second_null_map ? - _mm256_set_epi16( - (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, - (second_null_map[j + 13]) ? full : none, (second_null_map[j + 12]) ? full : none, - (second_null_map[j + 11]) ? full : none, (second_null_map[j + 10]) ? full : none, - (second_null_map[j + 9]) ? full : none, (second_null_map[j + 8])? full : none, - (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6])? full : none, - (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4])? full : none, - (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2])? full : none, - (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full : none) - : zeros; - unsigned i = 0; - for (; i < first.size - 15 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 16) - { - const __m256i s_data = _mm256_lddqu_si256(reinterpret_cast(first.data + i)); - const __m256i first_nm_mask = has_first_null_map? - _mm256_set_m128i( - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 8))), - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))) - : zeros; - bitmask = - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - first_nm_mask, - _mm256_cmpeq_epi16(f_data, s_data)), - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26))))) - ), - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)))))) - ), - _mm256_or_si256( - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_permute2x128_si256(first_nm_mask, first_nm_mask,1), - _mm256_cmpeq_epi16(f_data, _mm256_permute2x128_si256(s_data, s_data, 1))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10))))) - ), - _mm256_or_si256( - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data ,s_data, 1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6))))), - _mm256_or_si256( - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data ,s_data ,1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)))), - _mm256_andnot_si256( - _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), - _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))))) - ) - ), - bitmask); - } - - if (i < first.size) - { - for (; i < first.size && !has_mask; ++i) - { - if (has_first_null_map && first_null_map[i]) - continue; - __m256i v_i = _mm256_set1_epi16(first.data[i]); - bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi16(f_data, v_i)); - has_mask = _mm256_testc_si256(bitmask, ones); - } - } - } - } - - if (!has_mask && second.size > 15) - return false; - - return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); -} - #elif defined(__SSE4_2__) // SSE4.2 Int specialization From 2a2eb3a27bf623e23c08d462d17ade6791bbab23 Mon Sep 17 00:00:00 2001 From: Youenn Lebras Date: Fri, 10 Sep 2021 15:07:36 +0200 Subject: [PATCH 0122/1647] re-enable full AVX2 - change lddqu to loadu - Update CmakeList.txt --- src/Functions/GatherUtils/CMakeLists.txt | 16 +- .../GatherUtils/sliceHasImplAnyAll.h | 269 ++++++++++++++++-- 2 files changed, 262 insertions(+), 23 deletions(-) diff --git a/src/Functions/GatherUtils/CMakeLists.txt b/src/Functions/GatherUtils/CMakeLists.txt index a379ccbadde..b1c72656f24 100644 --- a/src/Functions/GatherUtils/CMakeLists.txt +++ b/src/Functions/GatherUtils/CMakeLists.txt @@ -11,13 +11,15 @@ if (HAS_SUGGEST_DESTRUCTOR_OVERRIDE) target_compile_definitions(clickhouse_functions_gatherutils PUBLIC HAS_SUGGEST_DESTRUCTOR_OVERRIDE) endif() -if (HAVE_SSE42) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -msse4.2") -endif() -if (HAVE_AVX2) - target_compile_options(clickhouse_functions_gatherutils PRIVATE -mavx2 -DNAMESPACE=AVX2) -endif() - if (STRIP_DEBUG_SYMBOLS_FUNCTIONS) target_compile_options(clickhouse_functions_gatherutils PRIVATE "-g0") endif() + +if (HAVE_SSE42) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2") + target_compile_options(clickhouse_functions_gatherutils PRIVATE -msse4.2) +endif() +if (HAVE_AVX2) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2") + target_compile_options(clickhouse_functions_gatherutils PRIVATE -mavx2) +endif() diff --git a/src/Functions/GatherUtils/sliceHasImplAnyAll.h b/src/Functions/GatherUtils/sliceHasImplAnyAll.h index 7c253cbc407..a14acd08e93 100644 --- a/src/Functions/GatherUtils/sliceHasImplAnyAll.h +++ b/src/Functions/GatherUtils/sliceHasImplAnyAll.h @@ -128,6 +128,19 @@ inline ALWAYS_INLINE bool hasAllIntegralLoopRemainder( #endif #if defined(__AVX2__) + +// TODO: Discuss about +// raise an error : "error: no viable conversion from 'const NumericArraySlice' to 'const NumericArraySlice'" +// How should we do, copy past each function ?? I haven't found a way to specialize a same function body for two different types. +// AVX2 UInt specialization +// template <> +// inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( +// const NumericArraySlice & second, const NumericArraySlice & first, const UInt8 * first_null_map, const UInt8 * second_null_map) +// { +// return sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements > ( +// static_cast &>(second), static_cast &>(first), second_null_map, first_null_map); +// } + // AVX2 Int specialization of sliceHasImplAnyAll template <> inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( @@ -152,7 +165,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(second.data + j)); + const __m256i f_data = _mm256_loadu_si256(reinterpret_cast(second.data + j)); // bits of the bitmask are set to one if considered as null in the corresponding null map, 0 otherwise; __m256i bitmask = has_second_null_map ? _mm256_set_epi32( @@ -169,13 +182,13 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); + const __m256i s_data = _mm256_loadu_si256(reinterpret_cast(first.data + i)); // Create a mask to avoid to compare null elements // set_m128i takes two arguments: (high segment, low segment) that are two __m128i convert from 8bits to 32bits to match with next operations const __m256i first_nm_mask = has_first_null_map? _mm256_set_m128i( - _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i + 4))), - _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i)))) + _mm_cvtepi8_epi32(_mm_loadu_si128(reinterpret_cast(first_null_map + i + 4))), + _mm_cvtepi8_epi32(_mm_loadu_si128(reinterpret_cast(first_null_map + i)))) : zeros; bitmask = _mm256_or_si256( @@ -235,6 +248,228 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (second.size == 0) + return true; + + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) + return false; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; + short has_mask = 1; + const Int64 full = -1, none = 0; + const __m256i ones = _mm256_set1_epi64x(full); + const __m256i zeros = _mm256_setzero_si256(); + if (second.size > 3 && first.size > 3) + { + for (; j < second.size - 3 && has_mask; j += 4) + { + has_mask = 0; + const __m256i f_data = _mm256_loadu_si256(reinterpret_cast(second.data + j)); + // bits of the bitmask are set to one if considered as null in the corresponding null map, 0 otherwise; + __m256i bitmask = has_second_null_map ? + _mm256_set_epi64x( + (second_null_map[j + 3])? full : none, + (second_null_map[j + 2])? full : none, + (second_null_map[j + 1])? full : none, + (second_null_map[j]) ? full : none) + : zeros; + + unsigned i = 0; + for (; i < first.size - 3 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 4) + { + const __m256i s_data = _mm256_loadu_si256(reinterpret_cast(first.data + i)); + const __m256i first_nm_mask = has_first_null_map? + _mm256_set_m128i( + _mm_cvtepi8_epi64(_mm_loadu_si128(reinterpret_cast(first_null_map + i + 2))), + _mm_cvtepi8_epi64(_mm_loadu_si128(reinterpret_cast(first_null_map + i)))) + : zeros; + bitmask = + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + first_nm_mask, + _mm256_cmpeq_epi64(f_data, s_data)), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(5,4,3,2,1,0,7,6)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(5,4,3,2,1,0,7,6))))), + + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(3,2,1,0,7,6,5,4)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(3,2,1,0,7,6,5,4)))), + _mm256_andnot_si256( + _mm256_permutevar8x32_epi32(first_nm_mask, _mm256_set_epi32(1,0,7,6,5,4,3,2)), + _mm256_cmpeq_epi64(f_data, _mm256_permutevar8x32_epi32(s_data, _mm256_set_epi32(1,0,7,6,5,4,3,2)))))), + bitmask); + } + + if (i < first.size) + { + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m256i v_i = _mm256_set1_epi64x(first.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi64(f_data, v_i)); + has_mask = _mm256_testc_si256(bitmask, ones); + } + } + } + } + + if (!has_mask && second.size > 3) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); +} + +// AVX2 Int16_t specialization +template <> +inline ALWAYS_INLINE bool sliceHasImplAnyAll, NumericArraySlice, sliceEqualElements >( + const NumericArraySlice & first, const NumericArraySlice & second, const UInt8 * first_null_map, const UInt8 * second_null_map) +{ + if (second.size == 0) + return true; + + if (!hasNull(first_null_map, first.size) && hasNull(second_null_map, second.size)) + return false; + + const bool has_first_null_map = first_null_map != nullptr; + const bool has_second_null_map = second_null_map != nullptr; + + size_t j = 0; + short has_mask = 1; + const int16_t full = -1, none = 0; + const __m256i ones = _mm256_set1_epi16(full); + const __m256i zeros = _mm256_setzero_si256(); + if (second.size > 15 && first.size > 15) + { + for (; j < second.size - 15 && has_mask; j += 16) + { + has_mask = 0; + const __m256i f_data = _mm256_loadu_si256(reinterpret_cast(second.data + j)); + __m256i bitmask = has_second_null_map ? + _mm256_set_epi16( + (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, + (second_null_map[j + 13]) ? full : none, (second_null_map[j + 12]) ? full : none, + (second_null_map[j + 11]) ? full : none, (second_null_map[j + 10]) ? full : none, + (second_null_map[j + 9]) ? full : none, (second_null_map[j + 8])? full : none, + (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6])? full : none, + (second_null_map[j + 5]) ? full : none, (second_null_map[j + 4])? full : none, + (second_null_map[j + 3]) ? full : none, (second_null_map[j + 2])? full : none, + (second_null_map[j + 1]) ? full : none, (second_null_map[j]) ? full : none) + : zeros; + unsigned i = 0; + for (; i < first.size - 15 && !has_mask; has_mask = _mm256_testc_si256(bitmask, ones), i += 16) + { + const __m256i s_data = _mm256_loadu_si256(reinterpret_cast(first.data + i)); + const __m256i first_nm_mask = has_first_null_map? + _mm256_set_m128i( + _mm_cvtepi8_epi16(_mm_loadu_si128(reinterpret_cast(first_null_map + i + 8))), + _mm_cvtepi8_epi16(_mm_loadu_si128(reinterpret_cast(first_null_map + i)))) + : zeros; + bitmask = + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + first_nm_mask, + _mm256_cmpeq_epi16(f_data, s_data)), + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(first_nm_mask, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(s_data, _mm256_set_epi8(17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18)))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_permute2x128_si256(first_nm_mask, first_nm_mask,1), + _mm256_cmpeq_epi16(f_data, _mm256_permute2x128_si256(s_data, s_data, 1))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(13,12,11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(11,10,9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(9,8,7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10))))) + ), + _mm256_or_si256( + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data ,s_data, 1), _mm256_set_epi8(7,6,5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(5,4,3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6))))), + _mm256_or_si256( + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data ,s_data ,1), _mm256_set_epi8(3,2,1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4)))), + _mm256_andnot_si256( + _mm256_shuffle_epi8(_mm256_permute2x128_si256(first_nm_mask, first_nm_mask, 1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)), + _mm256_cmpeq_epi16(f_data, _mm256_shuffle_epi8(_mm256_permute2x128_si256(s_data, s_data, 1), _mm256_set_epi8(1,0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2)))))) + ) + ), + bitmask); + } + + if (i < first.size) + { + for (; i < first.size && !has_mask; ++i) + { + if (has_first_null_map && first_null_map[i]) + continue; + __m256i v_i = _mm256_set1_epi16(first.data[i]); + bitmask = _mm256_or_si256(bitmask, _mm256_cmpeq_epi16(f_data, v_i)); + has_mask = _mm256_testc_si256(bitmask, ones); + } + } + } + } + + if (!has_mask && second.size > 15) + return false; + + return hasAllIntegralLoopRemainder(j, first, second, first_null_map, second_null_map); +} + #elif defined(__SSE4_2__) // SSE4.2 Int specialization @@ -260,7 +495,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(second.data + j)); + const __m128i f_data = _mm_loadu_si128(reinterpret_cast(second.data + j)); __m128i bitmask = has_second_null_map ? _mm_set_epi32( (second_null_map[j + 3]) ? full : none, @@ -272,9 +507,9 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); + const __m128i s_data = _mm_loadu_si128(reinterpret_cast(first.data + i)); const __m128i first_nm_mask = has_first_null_map ? - _mm_cvtepi8_epi32(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) + _mm_cvtepi8_epi32(_mm_loadu_si128(reinterpret_cast(first_null_map + i))) : zeros; bitmask = @@ -341,7 +576,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(second.data + j)); + const __m128i f_data = _mm_loadu_si128(reinterpret_cast(second.data + j)); __m128i bitmask = has_second_null_map ? _mm_set_epi64x( (second_null_map[j + 1]) ? full : none, @@ -350,9 +585,9 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); + const __m128i s_data = _mm_loadu_si128(reinterpret_cast(first.data + i)); const __m128i first_nm_mask = has_first_null_map ? - _mm_cvtepi8_epi64(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) + _mm_cvtepi8_epi64(_mm_loadu_si128(reinterpret_cast(first_null_map + i))) : zeros; bitmask = _mm_or_si128( @@ -409,7 +644,7 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(second.data + j)); + const __m128i f_data = _mm_loadu_si128(reinterpret_cast(second.data + j)); __m128i bitmask = has_second_null_map ? _mm_set_epi16( (second_null_map[j + 7]) ? full : none, (second_null_map[j + 6]) ? full : none, @@ -420,9 +655,9 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); + const __m128i s_data = _mm_loadu_si128(reinterpret_cast(first.data + i)); const __m128i first_nm_mask = has_first_null_map ? - _mm_cvtepi8_epi16(_mm_lddqu_si128(reinterpret_cast(first_null_map + i))) + _mm_cvtepi8_epi16(_mm_loadu_si128(reinterpret_cast(first_null_map + i))) : zeros; bitmask = _mm_or_si128( @@ -482,6 +717,8 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(second.data + j)); + const __m128i f_data = _mm_loadu_si128(reinterpret_cast(second.data + j)); __m128i bitmask = has_second_null_map ? _mm_set_epi8( (second_null_map[j + 15]) ? full : none, (second_null_map[j + 14]) ? full : none, @@ -522,9 +759,9 @@ inline ALWAYS_INLINE bool sliceHasImplAnyAll(first.data + i)); + const __m128i s_data = _mm_loadu_si128(reinterpret_cast(first.data + i)); const __m128i first_nm_mask = has_first_null_map ? - _mm_lddqu_si128(reinterpret_cast(first_null_map + i)) + _mm_loadu_si128(reinterpret_cast(first_null_map + i)) : zeros; bitmask = _mm_or_si128( From 72fb56904d8de814bddeb08f93d8c0882b6cd4d2 Mon Sep 17 00:00:00 2001 From: youenn lebras Date: Tue, 26 Oct 2021 10:43:23 +0200 Subject: [PATCH 0123/1647] Add cmake option to enable or not AVX2 instructions --- src/Functions/GatherUtils/CMakeLists.txt | 6 ++++-- src/Functions/GatherUtils/sliceHasImplAnyAll.h | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Functions/GatherUtils/CMakeLists.txt b/src/Functions/GatherUtils/CMakeLists.txt index b1c72656f24..f291663550d 100644 --- a/src/Functions/GatherUtils/CMakeLists.txt +++ b/src/Functions/GatherUtils/CMakeLists.txt @@ -1,4 +1,6 @@ include("${ClickHouse_SOURCE_DIR}/cmake/dbms_glob_sources.cmake") +option(ENABLE_AVX2 "Enable AVX2 instructions (when available) when build for modern Intel CPUs" OFF) + add_headers_and_sources(clickhouse_functions_gatherutils .) add_library(clickhouse_functions_gatherutils ${clickhouse_functions_gatherutils_sources} ${clickhouse_functions_gatherutils_headers}) target_link_libraries(clickhouse_functions_gatherutils PRIVATE dbms) @@ -19,7 +21,7 @@ if (HAVE_SSE42) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2") target_compile_options(clickhouse_functions_gatherutils PRIVATE -msse4.2) endif() -if (HAVE_AVX2) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2") +if (HAVE_AVX2 AND ENABLE_AVX2) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2 -DENABLE_AVX2") target_compile_options(clickhouse_functions_gatherutils PRIVATE -mavx2) endif() diff --git a/src/Functions/GatherUtils/sliceHasImplAnyAll.h b/src/Functions/GatherUtils/sliceHasImplAnyAll.h index a14acd08e93..9028a94b2aa 100644 --- a/src/Functions/GatherUtils/sliceHasImplAnyAll.h +++ b/src/Functions/GatherUtils/sliceHasImplAnyAll.h @@ -85,7 +85,7 @@ bool sliceHasImplAnyAll(const FirstSliceType & first, const SecondSliceType & se } -#if defined(__AVX2__) || defined(__SSE4_2__) +#if (defined(__AVX2__) && defined(ENABLE_AVX2)) || defined(__SSE4_2__) namespace { @@ -127,7 +127,7 @@ inline ALWAYS_INLINE bool hasAllIntegralLoopRemainder( #endif -#if defined(__AVX2__) +#if defined(__AVX2__) && defined(ENABLE_AVX2) // TODO: Discuss about // raise an error : "error: no viable conversion from 'const NumericArraySlice' to 'const NumericArraySlice'" From 0154eab9cb1000c831bc44c49cfc1d3ccf2ff5c1 Mon Sep 17 00:00:00 2001 From: youenn lebras Date: Wed, 8 Dec 2021 10:27:42 +0100 Subject: [PATCH 0124/1647] Modify performance tests for HasAll, removing Large tests to see if it helps passing CICD --- tests/performance/hasAll_simd_int16.xml | 16 ++++++++-------- tests/performance/hasAll_simd_int32.xml | 16 ++++++++-------- tests/performance/hasAll_simd_int64.xml | 16 ++++++++-------- tests/performance/hasAll_simd_int8.xml | 16 ++++++++-------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/performance/hasAll_simd_int16.xml b/tests/performance/hasAll_simd_int16.xml index c2ce4eec77f..63d869e7794 100644 --- a/tests/performance/hasAll_simd_int16.xml +++ b/tests/performance/hasAll_simd_int16.xml @@ -7,9 +7,9 @@ CREATE TABLE test_table_medium2 (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set CREATE TABLE test_table_mediumf (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_large (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set + INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) @@ -22,9 +22,9 @@ INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) - INSERT INTO test_table_large SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + select hasAll(set, subset) from test_table_small select hasAll(set, subset) from test_table_small2 @@ -34,9 +34,9 @@ select hasAll(set, subset) from test_table_medium2 select hasAll(set, subset) from test_table_mediumf - select hasAll(set, subset) from test_table_large + DROP TABLE IF EXISTS test_table_small DROP TABLE IF EXISTS test_table_small2 @@ -46,7 +46,7 @@ DROP TABLE IF EXISTS test_table_medium2 DROP TABLE IF EXISTS test_table_mediumf - DROP TABLE IF EXISTS test_table_large + diff --git a/tests/performance/hasAll_simd_int32.xml b/tests/performance/hasAll_simd_int32.xml index 4543dea161b..074901737b0 100644 --- a/tests/performance/hasAll_simd_int32.xml +++ b/tests/performance/hasAll_simd_int32.xml @@ -7,9 +7,9 @@ CREATE TABLE test_table_medium2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set CREATE TABLE test_table_mediumf (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_large (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set + INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) @@ -22,9 +22,9 @@ INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) - INSERT INTO test_table_large SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + select hasAll(set, subset) from test_table_small select hasAll(set, subset) from test_table_small2 @@ -34,9 +34,9 @@ select hasAll(set, subset) from test_table_medium2 select hasAll(set, subset) from test_table_mediumf - select hasAll(set, subset) from test_table_large + DROP TABLE IF EXISTS test_table_small DROP TABLE IF EXISTS test_table_small2 @@ -46,7 +46,7 @@ DROP TABLE IF EXISTS test_table_medium2 DROP TABLE IF EXISTS test_table_mediumf - DROP TABLE IF EXISTS test_table_large + diff --git a/tests/performance/hasAll_simd_int64.xml b/tests/performance/hasAll_simd_int64.xml index 07e52483bb1..9e68d3d219c 100644 --- a/tests/performance/hasAll_simd_int64.xml +++ b/tests/performance/hasAll_simd_int64.xml @@ -7,9 +7,9 @@ CREATE TABLE test_table_medium2 (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set CREATE TABLE test_table_mediumf (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_large (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set + INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) @@ -22,9 +22,9 @@ INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) - INSERT INTO test_table_large SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + select hasAll(set, subset) from test_table_small select hasAll(set, subset) from test_table_small2 @@ -34,9 +34,9 @@ select hasAll(set, subset) from test_table_medium2 select hasAll(set, subset) from test_table_mediumf - select hasAll(set, subset) from test_table_large + DROP TABLE IF EXISTS test_table_small DROP TABLE IF EXISTS test_table_small2 @@ -46,7 +46,7 @@ DROP TABLE IF EXISTS test_table_medium2 DROP TABLE IF EXISTS test_table_mediumf - DROP TABLE IF EXISTS test_table_large + diff --git a/tests/performance/hasAll_simd_int8.xml b/tests/performance/hasAll_simd_int8.xml index 5ddc84aa5bd..4a0b30524ad 100644 --- a/tests/performance/hasAll_simd_int8.xml +++ b/tests/performance/hasAll_simd_int8.xml @@ -7,9 +7,9 @@ CREATE TABLE test_table_medium2 (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set CREATE TABLE test_table_mediumf (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set - CREATE TABLE test_table_large (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set + INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) @@ -22,9 +22,9 @@ INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) - INSERT INTO test_table_large SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(50000000)) + select hasAll(set, subset) from test_table_small select hasAll(set, subset) from test_table_small2 @@ -34,9 +34,9 @@ select hasAll(set, subset) from test_table_medium2 select hasAll(set, subset) from test_table_mediumf - select hasAll(set, subset) from test_table_large + DROP TABLE IF EXISTS test_table_small DROP TABLE IF EXISTS test_table_small2 @@ -46,7 +46,7 @@ DROP TABLE IF EXISTS test_table_medium2 DROP TABLE IF EXISTS test_table_mediumf - DROP TABLE IF EXISTS test_table_large + From c2b761acf282c00290dae1cbeb1ae43d8a7858bd Mon Sep 17 00:00:00 2001 From: youenn lebras Date: Wed, 8 Dec 2021 11:01:24 +0100 Subject: [PATCH 0125/1647] Add cmake option to enable or not AVX2 instructions This reverts commit bca8eca44fe382b6efe80a381d42e6ede8a91fa3. --- src/Functions/GatherUtils/CMakeLists.txt | 2 +- tests/performance/hasAll_simd_int16.xml | 16 ++++++++-------- tests/performance/hasAll_simd_int32.xml | 16 ++++++++-------- tests/performance/hasAll_simd_int64.xml | 16 ++++++++-------- tests/performance/hasAll_simd_int8.xml | 16 ++++++++-------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/Functions/GatherUtils/CMakeLists.txt b/src/Functions/GatherUtils/CMakeLists.txt index f291663550d..10909b99b82 100644 --- a/src/Functions/GatherUtils/CMakeLists.txt +++ b/src/Functions/GatherUtils/CMakeLists.txt @@ -22,6 +22,6 @@ if (HAVE_SSE42) target_compile_options(clickhouse_functions_gatherutils PRIVATE -msse4.2) endif() if (HAVE_AVX2 AND ENABLE_AVX2) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2 -DENABLE_AVX2") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2 -DENABLE_AVX2") target_compile_options(clickhouse_functions_gatherutils PRIVATE -mavx2) endif() diff --git a/tests/performance/hasAll_simd_int16.xml b/tests/performance/hasAll_simd_int16.xml index 63d869e7794..c2ce4eec77f 100644 --- a/tests/performance/hasAll_simd_int16.xml +++ b/tests/performance/hasAll_simd_int16.xml @@ -7,9 +7,9 @@ CREATE TABLE test_table_medium2 (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set CREATE TABLE test_table_mediumf (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set - + CREATE TABLE test_table_largef (`set` Array(Int16), `subset` Array (Int16)) ENGINE = MergeTree ORDER BY set INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) @@ -22,9 +22,9 @@ INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) - + INSERT INTO test_table_largef SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000000)) select hasAll(set, subset) from test_table_small select hasAll(set, subset) from test_table_small2 @@ -34,9 +34,9 @@ select hasAll(set, subset) from test_table_medium2 select hasAll(set, subset) from test_table_mediumf - + select hasAll(set, subset) from test_table_largef DROP TABLE IF EXISTS test_table_small DROP TABLE IF EXISTS test_table_small2 @@ -46,7 +46,7 @@ DROP TABLE IF EXISTS test_table_medium2 DROP TABLE IF EXISTS test_table_mediumf - + DROP TABLE IF EXISTS test_table_largef diff --git a/tests/performance/hasAll_simd_int32.xml b/tests/performance/hasAll_simd_int32.xml index 074901737b0..4543dea161b 100644 --- a/tests/performance/hasAll_simd_int32.xml +++ b/tests/performance/hasAll_simd_int32.xml @@ -7,9 +7,9 @@ CREATE TABLE test_table_medium2 (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set CREATE TABLE test_table_mediumf (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set - + CREATE TABLE test_table_largef (`set` Array(Int32), `subset` Array (Int32)) ENGINE = MergeTree ORDER BY set INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) @@ -22,9 +22,9 @@ INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) - + INSERT INTO test_table_largef SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000000)) select hasAll(set, subset) from test_table_small select hasAll(set, subset) from test_table_small2 @@ -34,9 +34,9 @@ select hasAll(set, subset) from test_table_medium2 select hasAll(set, subset) from test_table_mediumf - + select hasAll(set, subset) from test_table_largef DROP TABLE IF EXISTS test_table_small DROP TABLE IF EXISTS test_table_small2 @@ -46,7 +46,7 @@ DROP TABLE IF EXISTS test_table_medium2 DROP TABLE IF EXISTS test_table_mediumf - + DROP TABLE IF EXISTS test_table_largef diff --git a/tests/performance/hasAll_simd_int64.xml b/tests/performance/hasAll_simd_int64.xml index 9e68d3d219c..07e52483bb1 100644 --- a/tests/performance/hasAll_simd_int64.xml +++ b/tests/performance/hasAll_simd_int64.xml @@ -7,9 +7,9 @@ CREATE TABLE test_table_medium2 (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set CREATE TABLE test_table_mediumf (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set - + CREATE TABLE test_table_largef (`set` Array(Int64), `subset` Array (Int64)) ENGINE = MergeTree ORDER BY set INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) @@ -22,9 +22,9 @@ INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) - + INSERT INTO test_table_largef SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000000)) select hasAll(set, subset) from test_table_small select hasAll(set, subset) from test_table_small2 @@ -34,9 +34,9 @@ select hasAll(set, subset) from test_table_medium2 select hasAll(set, subset) from test_table_mediumf - + select hasAll(set, subset) from test_table_largef DROP TABLE IF EXISTS test_table_small DROP TABLE IF EXISTS test_table_small2 @@ -46,7 +46,7 @@ DROP TABLE IF EXISTS test_table_medium2 DROP TABLE IF EXISTS test_table_mediumf - + DROP TABLE IF EXISTS test_table_largef diff --git a/tests/performance/hasAll_simd_int8.xml b/tests/performance/hasAll_simd_int8.xml index 4a0b30524ad..5ddc84aa5bd 100644 --- a/tests/performance/hasAll_simd_int8.xml +++ b/tests/performance/hasAll_simd_int8.xml @@ -7,9 +7,9 @@ CREATE TABLE test_table_medium2 (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set CREATE TABLE test_table_mediumf (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set - + CREATE TABLE test_table_largef (`set` Array(Int8), `subset` Array (Int8)) ENGINE = MergeTree ORDER BY set INSERT INTO test_table_small SELECT groupArraySample(500)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(500)) @@ -22,9 +22,9 @@ INSERT INTO test_table_mediumf SELECT groupArraySample(1000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(100000000)) - + INSERT INTO test_table_largef SELECT groupArraySample(50000000)(number) AS set, groupArraySample(10)(number) AS subset FROM (SELECT * FROM numbers(1000000000)) select hasAll(set, subset) from test_table_small select hasAll(set, subset) from test_table_small2 @@ -34,9 +34,9 @@ select hasAll(set, subset) from test_table_medium2 select hasAll(set, subset) from test_table_mediumf - + select hasAll(set, subset) from test_table_largef DROP TABLE IF EXISTS test_table_small DROP TABLE IF EXISTS test_table_small2 @@ -46,7 +46,7 @@ DROP TABLE IF EXISTS test_table_medium2 DROP TABLE IF EXISTS test_table_mediumf - + DROP TABLE IF EXISTS test_table_largef From 6e1c16c2e7df8d091b03dcebfcb53ddc04012fc8 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 14 Dec 2021 23:06:34 +0300 Subject: [PATCH 0126/1647] add support for mutations --- src/Common/TransactionID.h | 42 +++-- src/Interpreters/MergeTreeTransaction.cpp | 69 ++++++-- src/Interpreters/MergeTreeTransaction.h | 10 +- src/Interpreters/TransactionLog.cpp | 2 +- .../TransactionVersionMetadata.cpp | 39 ++-- src/Interpreters/TransactionVersionMetadata.h | 1 + src/Storages/IStorage.h | 5 + .../MergeTree/MergeMutateSelectedEntry.h | 6 +- src/Storages/MergeTree/MergeTreeData.cpp | 2 +- src/Storages/MergeTree/MergeTreeData.h | 3 +- .../MergeTree/MergeTreeMutationEntry.cpp | 8 +- .../MergeTree/MergeTreeMutationEntry.h | 9 +- .../MergeTree/MutatePlainMergeTreeTask.cpp | 7 +- src/Storages/StorageMergeTree.cpp | 166 ++++++++++++------ src/Storages/StorageMergeTree.h | 5 +- .../01168_mutations_isolation.reference | 20 +++ .../0_stateless/01168_mutations_isolation.sh | 57 ++++++ 17 files changed, 340 insertions(+), 111 deletions(-) create mode 100644 tests/queries/0_stateless/01168_mutations_isolation.reference create mode 100755 tests/queries/0_stateless/01168_mutations_isolation.sh diff --git a/src/Common/TransactionID.h b/src/Common/TransactionID.h index fa745efcf43..9037601ffad 100644 --- a/src/Common/TransactionID.h +++ b/src/Common/TransactionID.h @@ -17,6 +17,17 @@ using Snapshot = CSN; using LocalTID = UInt64; using TIDHash = UInt64; +namespace Tx +{ + const CSN UnknownCSN = 0; + const CSN PrehistoricCSN = 1; + const CSN CommittingCSN = 2; /// TODO do we really need it? + const CSN MaxReservedCSN = 16; + + const LocalTID PrehistoricLocalTID = 1; + const LocalTID MaxReservedLocalTID = 16; +} + struct TransactionID { CSN start_csn = 0; @@ -33,31 +44,28 @@ struct TransactionID return !(*this == rhs); } - operator bool() const + TIDHash getHash() const; + + bool isEmpty() const { - assert(local_tid || (start_csn == 0 && host_id == UUIDHelpers::Nil)); - return local_tid; + assert((local_tid == 0) == (start_csn == 0 && host_id == UUIDHelpers::Nil)); + return local_tid == 0; } - TIDHash getHash() const; + bool isPrehistoric() const + { + assert((local_tid == Tx::PrehistoricLocalTID) == (start_csn == Tx::PrehistoricCSN)); + return local_tid == Tx::PrehistoricLocalTID; + } }; namespace Tx { + const TransactionID EmptyTID = {0, 0, UUIDHelpers::Nil}; + const TransactionID PrehistoricTID = {PrehistoricCSN, PrehistoricLocalTID, UUIDHelpers::Nil}; -const CSN UnknownCSN = 0; -const CSN PrehistoricCSN = 1; -const CSN MaxReservedCSN = 16; - -const LocalTID PrehistoricLocalTID = 1; -const LocalTID MaxReservedLocalTID = 16; - -const TransactionID EmptyTID = {0, 0, UUIDHelpers::Nil}; -const TransactionID PrehistoricTID = {PrehistoricCSN, PrehistoricLocalTID, UUIDHelpers::Nil}; - -/// So far, that changes will never become visible -const CSN RolledBackCSN = std::numeric_limits::max(); - + /// So far, that changes will never become visible + const CSN RolledBackCSN = std::numeric_limits::max(); } } diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index b9fb94de8e3..b2f1d8ae150 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -6,6 +6,12 @@ namespace DB { +namespace ErrorCodes +{ + extern const int INVALID_TRANSACTION; + extern const int LOGICAL_ERROR; +} + MergeTreeTransaction::MergeTreeTransaction(Snapshot snapshot_, LocalTID local_tid_, UUID host_id) : tid({snapshot_, local_tid_, host_id}) , snapshot(snapshot_) @@ -15,9 +21,10 @@ MergeTreeTransaction::MergeTreeTransaction(Snapshot snapshot_, LocalTID local_ti MergeTreeTransaction::State MergeTreeTransaction::getState() const { - if (csn == Tx::UnknownCSN) + CSN c = csn.load(); + if (c == Tx::UnknownCSN || c == Tx::CommittingCSN) return RUNNING; - if (csn == Tx::RolledBackCSN) + if (c == Tx::RolledBackCSN) return ROLLED_BACK; return COMMITTED; } @@ -64,42 +71,77 @@ void MergeTreeTransaction::addNewPartAndRemoveCovered(const StoragePtr & storage void MergeTreeTransaction::addNewPart(const StoragePtr & storage, const DataPartPtr & new_part) { - assert(csn == Tx::UnknownCSN); + CSN c = csn.load(); + if (c == Tx::RolledBackCSN) + throw Exception(ErrorCodes::INVALID_TRANSACTION, "Transaction was cancelled"); + else if (c != Tx::UnknownCSN) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected CSN state: {}", c); + storages.insert(storage); creating_parts.push_back(new_part); } void MergeTreeTransaction::removeOldPart(const StoragePtr & storage, const DataPartPtr & part_to_remove) { - assert(csn == Tx::UnknownCSN); + CSN c = csn.load(); + if (c == Tx::RolledBackCSN) + throw Exception(ErrorCodes::INVALID_TRANSACTION, "Transaction was cancelled"); + else if (c != Tx::UnknownCSN) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected CSN state: {}", c); + storages.insert(storage); removing_parts.push_back(part_to_remove); } +void MergeTreeTransaction::addMutation(const StoragePtr & table, const String & mutation_id) +{ + mutations.emplace_back(table, mutation_id); +} + bool MergeTreeTransaction::isReadOnly() const { return creating_parts.empty() && removing_parts.empty(); } -void MergeTreeTransaction::beforeCommit() const +void MergeTreeTransaction::beforeCommit() { - assert(csn == Tx::UnknownCSN); + for (const auto & table_and_mutation : mutations) + table_and_mutation.first->waitForMutation(table_and_mutation.second); + + CSN expected = Tx::UnknownCSN; + bool can_commit = csn.compare_exchange_strong(expected, Tx::CommittingCSN); + if (can_commit) + return; + + if (expected == Tx::RolledBackCSN) + throw Exception(ErrorCodes::INVALID_TRANSACTION, "Transaction was cancelled"); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected CSN state: {}", expected); } void MergeTreeTransaction::afterCommit(CSN assigned_csn) noexcept { - assert(csn == Tx::UnknownCSN); - csn = assigned_csn; + [[maybe_unused]] CSN prev_value = csn.exchange(assigned_csn); + assert(prev_value == Tx::CommittingCSN); for (const auto & part : creating_parts) part->versions.mincsn.store(csn); for (const auto & part : removing_parts) part->versions.maxcsn.store(csn); } -void MergeTreeTransaction::rollback() noexcept +bool MergeTreeTransaction::rollback() noexcept { - assert(csn == Tx::UnknownCSN); - csn = Tx::RolledBackCSN; + CSN expected = Tx::UnknownCSN; + bool need_rollback = csn.compare_exchange_strong(expected, Tx::RolledBackCSN); + + if (!need_rollback) + { + /// TODO add assertions for the case when this method called from background operation + return false; + } + + for (const auto & table_and_mutation : mutations) + table_and_mutation.first->killMutation(table_and_mutation.second); + for (const auto & part : creating_parts) part->versions.mincsn.store(Tx::RolledBackCSN); @@ -114,14 +156,11 @@ void MergeTreeTransaction::rollback() noexcept if (part->versions.getMinTID() != tid) const_cast(part->storage).restoreAndActivatePart(part); - /// FIXME seems like session holds shared_ptr to Transaction and transaction holds shared_ptr to parts preventing cleanup + return true; } void MergeTreeTransaction::onException() { - if (csn) - return; - TransactionLog::instance().rollbackTransaction(shared_from_this()); } diff --git a/src/Interpreters/MergeTreeTransaction.h b/src/Interpreters/MergeTreeTransaction.h index 921a1c10951..6733dba4d8f 100644 --- a/src/Interpreters/MergeTreeTransaction.h +++ b/src/Interpreters/MergeTreeTransaction.h @@ -35,6 +35,8 @@ public: void addNewPart(const StoragePtr & storage, const DataPartPtr & new_part); void removeOldPart(const StoragePtr & storage, const DataPartPtr & part_to_remove); + void addMutation(const StoragePtr & table, const String & mutation_id); + static void addNewPart(const StoragePtr & storage, const DataPartPtr & new_part, MergeTreeTransaction * txn); static void removeOldPart(const StoragePtr & storage, const DataPartPtr & part_to_remove, MergeTreeTransaction * txn); static void addNewPartAndRemoveCovered(const StoragePtr & storage, const DataPartPtr & new_part, const DataPartsVector & covered_parts, MergeTreeTransaction * txn); @@ -46,9 +48,9 @@ public: String dumpDescription() const; private: - void beforeCommit() const; + void beforeCommit(); void afterCommit(CSN assigned_csn) noexcept; - void rollback() noexcept; + bool rollback() noexcept; Snapshot snapshot; @@ -56,10 +58,12 @@ private: DataPartsVector creating_parts; DataPartsVector removing_parts; - CSN csn; + std::atomic csn; /// FIXME it's ugly std::list::iterator snapshot_in_use_it; + + std::vector> mutations; }; using MergeTreeTransactionPtr = std::shared_ptr; diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 0624ddd116c..f084c9dbc3c 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -87,7 +87,7 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) void TransactionLog::rollbackTransaction(const MergeTreeTransactionPtr & txn) noexcept { LOG_TRACE(log, "Rolling back transaction {}", txn->tid); - txn->rollback(); + if (txn->rollback()) { std::lock_guard lock{running_list_mutex}; bool removed = running_list.erase(txn->tid.getHash()); diff --git a/src/Interpreters/TransactionVersionMetadata.cpp b/src/Interpreters/TransactionVersionMetadata.cpp index cbcff6057a5..c8e6b9dcfba 100644 --- a/src/Interpreters/TransactionVersionMetadata.cpp +++ b/src/Interpreters/TransactionVersionMetadata.cpp @@ -38,7 +38,20 @@ TransactionID VersionMetadata::getMaxTID() const void VersionMetadata::lockMaxTID(const TransactionID & tid, const String & error_context) { - assert(tid); + TIDHash locked_by = 0; + if (tryLockMaxTID(tid, &locked_by)) + return; + + throw Exception(ErrorCodes::SERIALIZATION_ERROR, + "Serialization error: " + "Transaction {} tried to remove data part, " + "but it's locked ({}) by another transaction {} which is currently removing this part. {}", + tid, locked_by, getMaxTID(), error_context); +} + +bool VersionMetadata::tryLockMaxTID(const TransactionID & tid, TIDHash * locked_by_id) +{ + assert(!tid.isEmpty()); TIDHash max_lock_value = tid.getHash(); TIDHash expected_max_lock_value = 0; bool locked = maxtid_lock.compare_exchange_strong(expected_max_lock_value, max_lock_value); @@ -48,21 +61,21 @@ void VersionMetadata::lockMaxTID(const TransactionID & tid, const String & error { /// Don't need to lock part for queries without transaction //FIXME Transactions: why is it possible? - return; + return true; } - throw Exception(ErrorCodes::SERIALIZATION_ERROR, "Serialization error: " - "Transaction {} tried to remove data part, " - "but it's locked ({}) by another transaction {} which is currently removing this part. {}", - tid, expected_max_lock_value, getMaxTID(), error_context); + if (locked_by_id) + *locked_by_id = expected_max_lock_value; + return false; } maxtid = tid; + return true; } void VersionMetadata::unlockMaxTID(const TransactionID & tid) { - assert(tid); + assert(!tid.isEmpty()); TIDHash max_lock_value = tid.getHash(); TIDHash locked_by = maxtid_lock.load(); @@ -91,7 +104,7 @@ void VersionMetadata::setMinTID(const TransactionID & tid) { /// TODO Transactions: initialize it in constructor on part creation and remove this method /// FIXME ReplicatedMergeTreeBlockOutputStream may add one part multiple times - assert(!mintid || mintid == tid); + assert(mintid.isEmpty() || mintid == tid); const_cast(mintid) = tid; } @@ -103,7 +116,7 @@ bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current_tid) { //Poco::Logger * log = &Poco::Logger::get("WTF"); - assert(mintid); + assert(!mintid.isEmpty()); CSN min = mincsn.load(std::memory_order_relaxed); TIDHash max_lock = maxtid_lock.load(std::memory_order_relaxed); CSN max = maxcsn.load(std::memory_order_relaxed); @@ -115,6 +128,8 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current [[maybe_unused]] bool had_maxcsn = max; assert(!had_maxcsn || had_maxtid); assert(!had_maxcsn || had_mincsn); + assert(min == Tx::UnknownCSN || min == Tx::PrehistoricCSN || Tx::MaxReservedCSN < min); + assert(max == Tx::UnknownCSN || max == Tx::PrehistoricCSN || Tx::MaxReservedCSN < max); /// Fast path: @@ -126,7 +141,7 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current return false; if (max && max <= snapshot_version) return false; - if (current_tid && max_lock && max_lock == current_tid.getHash()) + if (!current_tid.isEmpty() && max_lock && max_lock == current_tid.getHash()) return false; /// Otherwise, part is definitely visible if: @@ -137,7 +152,7 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current return true; if (min && min <= snapshot_version && max && snapshot_version < max) return true; - if (current_tid && mintid == current_tid) + if (!current_tid.isEmpty() && mintid == current_tid) return true; /// End of fast path. @@ -146,7 +161,7 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current /// It means that some transaction is creating/removing the part right now or has done it recently /// and we don't know if it was already committed or not. assert(!had_mincsn || (had_maxtid && !had_maxcsn)); - assert(!current_tid || (mintid != current_tid && max_lock != current_tid.getHash())); + assert(current_tid.isEmpty() || (mintid != current_tid && max_lock != current_tid.getHash())); /// Before doing CSN lookup, let's check some extra conditions. /// If snapshot_version <= some_tid.start_csn, then changes of the transaction with some_tid diff --git a/src/Interpreters/TransactionVersionMetadata.h b/src/Interpreters/TransactionVersionMetadata.h index 94d6a1905e9..48049da2a23 100644 --- a/src/Interpreters/TransactionVersionMetadata.h +++ b/src/Interpreters/TransactionVersionMetadata.h @@ -20,6 +20,7 @@ struct VersionMetadata TransactionID getMinTID() const { return mintid; } TransactionID getMaxTID() const; + bool tryLockMaxTID(const TransactionID & tid, TIDHash * locked_by_id = nullptr); void lockMaxTID(const TransactionID & tid, const String & error_context = {}); void unlockMaxTID(const TransactionID & tid); diff --git a/src/Storages/IStorage.h b/src/Storages/IStorage.h index 6481922067b..c5d9d1c0657 100644 --- a/src/Storages/IStorage.h +++ b/src/Storages/IStorage.h @@ -471,6 +471,11 @@ public: throw Exception("Mutations are not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); } + virtual void waitForMutation(const String & /*mutation_id*/) + { + throw Exception("Mutations are not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); + } + /// Cancel a part move to shard. virtual CancellationCode killPartMoveToShard(const UUID & /*task_uuid*/) { diff --git a/src/Storages/MergeTree/MergeMutateSelectedEntry.h b/src/Storages/MergeTree/MergeMutateSelectedEntry.h index 64136205157..488dfba7618 100644 --- a/src/Storages/MergeTree/MergeMutateSelectedEntry.h +++ b/src/Storages/MergeTree/MergeMutateSelectedEntry.h @@ -39,10 +39,14 @@ struct MergeMutateSelectedEntry FutureMergedMutatedPartPtr future_part; CurrentlyMergingPartsTaggerPtr tagger; MutationCommandsConstPtr commands; - MergeMutateSelectedEntry(FutureMergedMutatedPartPtr future_part_, CurrentlyMergingPartsTaggerPtr tagger_, MutationCommandsConstPtr commands_) + //TransactionID mutation_tid; + MergeTreeTransactionPtr txn; + MergeMutateSelectedEntry(FutureMergedMutatedPartPtr future_part_, CurrentlyMergingPartsTaggerPtr tagger_, + MutationCommandsConstPtr commands_, const MergeTreeTransactionPtr & txn_ = nullptr) : future_part(future_part_) , tagger(std::move(tagger_)) , commands(commands_) + , txn(txn_) {} }; diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index ee3cc893f03..48b5dece723 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -2446,7 +2446,7 @@ bool MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr & part, MergeTreeTra bool MergeTreeData::renameTempPartAndReplace( MutableDataPartPtr & part, MergeTreeTransaction * txn, SimpleIncrement * increment, Transaction * out_transaction, - std::unique_lock & lock, DataPartsVector * out_covered_parts, MergeTreeDeduplicationLog * deduplication_log) + std::unique_lock & lock, DataPartsVector * out_covered_parts, MergeTreeDeduplicationLog * deduplication_log, bool) { if (out_transaction && &out_transaction->data != this) throw Exception("MergeTreeData::Transaction for one table cannot be used with another. It is a bug.", diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 615d2a45826..d5b8901527d 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -521,9 +521,10 @@ public: Transaction * out_transaction = nullptr, MergeTreeDeduplicationLog * deduplication_log = nullptr); /// Low-level version of previous one, doesn't lock mutex + /// FIXME Transactions: remove add_to_txn flag, maybe merge MergeTreeTransaction and Transaction bool renameTempPartAndReplace( MutableDataPartPtr & part, MergeTreeTransaction * txn, SimpleIncrement * increment, Transaction * out_transaction, DataPartsLock & lock, - DataPartsVector * out_covered_parts = nullptr, MergeTreeDeduplicationLog * deduplication_log = nullptr); + DataPartsVector * out_covered_parts = nullptr, MergeTreeDeduplicationLog * deduplication_log = nullptr, bool add_to_txn = true); /// Remove parts from working set immediately (without wait for background diff --git a/src/Storages/MergeTree/MergeTreeMutationEntry.cpp b/src/Storages/MergeTree/MergeTreeMutationEntry.cpp index 0f71742fb09..744a38f5e52 100644 --- a/src/Storages/MergeTree/MergeTreeMutationEntry.cpp +++ b/src/Storages/MergeTree/MergeTreeMutationEntry.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -43,16 +44,20 @@ UInt64 MergeTreeMutationEntry::parseFileName(const String & file_name_) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Cannot parse mutation version from file name, expected 'mutation_.txt', got '{}'", file_name_); } -MergeTreeMutationEntry::MergeTreeMutationEntry(MutationCommands commands_, DiskPtr disk_, const String & path_prefix_, UInt64 tmp_number) +MergeTreeMutationEntry::MergeTreeMutationEntry(MutationCommands commands_, DiskPtr disk_, const String & path_prefix_, UInt64 tmp_number, + const TransactionID & tid_) : create_time(time(nullptr)) , commands(std::move(commands_)) , disk(std::move(disk_)) , path_prefix(path_prefix_) , file_name("tmp_mutation_" + toString(tmp_number) + ".txt") , is_temp(true) + , tid(tid_) + , csn(Tx::UnknownCSN) { try { + /// TODO Transactions: write (and read) tid auto out = disk->writeFile(path_prefix + file_name); *out << "format version: 1\n" << "create time: " << LocalDateTime(create_time) << "\n"; @@ -112,6 +117,7 @@ MergeTreeMutationEntry::MergeTreeMutationEntry(DiskPtr disk_, const String & pat *buf >> "\n"; assertEOF(*buf); + csn = Tx::PrehistoricCSN; } MergeTreeMutationEntry::~MergeTreeMutationEntry() diff --git a/src/Storages/MergeTree/MergeTreeMutationEntry.h b/src/Storages/MergeTree/MergeTreeMutationEntry.h index 7554a03836e..5fb92b9954d 100644 --- a/src/Storages/MergeTree/MergeTreeMutationEntry.h +++ b/src/Storages/MergeTree/MergeTreeMutationEntry.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace DB @@ -28,8 +29,14 @@ struct MergeTreeMutationEntry time_t latest_fail_time = 0; String latest_fail_reason; + /// ID of transaction which has created mutation. + TransactionID tid = Tx::PrehistoricTID; + CSN csn; + /// Create a new entry and write it to a temporary file. - MergeTreeMutationEntry(MutationCommands commands_, DiskPtr disk, const String & path_prefix_, UInt64 tmp_number); + MergeTreeMutationEntry(MutationCommands commands_, DiskPtr disk, const String & path_prefix_, UInt64 tmp_number, + const TransactionID & tid_); + MergeTreeMutationEntry(const MergeTreeMutationEntry &) = delete; MergeTreeMutationEntry(MergeTreeMutationEntry &&) = default; diff --git a/src/Storages/MergeTree/MutatePlainMergeTreeTask.cpp b/src/Storages/MergeTree/MutatePlainMergeTreeTask.cpp index ceca96370c4..4e2ca8aee54 100644 --- a/src/Storages/MergeTree/MutatePlainMergeTreeTask.cpp +++ b/src/Storages/MergeTree/MutatePlainMergeTreeTask.cpp @@ -1,7 +1,7 @@ #include #include - +#include namespace DB { @@ -85,7 +85,8 @@ bool MutatePlainMergeTreeTask::executeStep() new_part = mutate_task->getFuture().get(); - storage.renameTempPartAndReplace(new_part, nullptr); //FIXME + /// FIXME Transaction: it's too optimistic, better to lock parts before starting transaction + storage.renameTempPartAndReplace(new_part, merge_mutate_entry->txn.get()); storage.updateMutationEntriesErrors(future_part, true, ""); write_part_log({}); @@ -94,6 +95,8 @@ bool MutatePlainMergeTreeTask::executeStep() } catch (...) { + if (merge_mutate_entry->txn) + merge_mutate_entry->txn->onException(); storage.updateMutationEntriesErrors(future_part, false, getCurrentExceptionMessage(false)); write_part_log(ExecutionStatus::fromCurrentException()); return false; diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 069c7c6efa0..1d676f2e4a9 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -58,6 +58,22 @@ namespace ActionLocks extern const StorageActionBlockType PartsMove; } +static MergeTreeTransactionPtr tryGetTransactionForMutation(const MergeTreeMutationEntry & mutation, Poco::Logger * log = nullptr) +{ + assert(!mutation.tid.isEmpty()); + if (mutation.tid.isPrehistoric()) + return {}; + + auto txn = TransactionLog::instance().tryGetRunningTransaction(mutation.tid.getHash()); + if (txn) + return txn; + + if (log) + LOG_WARNING(log, "Cannot find transaction {} which had started mutation {}, probably it finished", mutation.tid, mutation.file_name); + + return {}; +} + StorageMergeTree::StorageMergeTree( const StorageID & table_id_, @@ -280,7 +296,6 @@ void StorageMergeTree::alter( StorageInMemoryMetadata new_metadata = getInMemoryMetadata(); StorageInMemoryMetadata old_metadata = getInMemoryMetadata(); auto maybe_mutation_commands = commands.getMutationCommands(new_metadata, local_context->getSettingsRef().materialize_ttl_after_modify, local_context); - String mutation_file_name; Int64 mutation_version = -1; commands.apply(new_metadata, local_context); @@ -302,13 +317,13 @@ void StorageMergeTree::alter( DatabaseCatalog::instance().getDatabase(table_id.database_name)->alterTable(local_context, table_id, new_metadata); if (!maybe_mutation_commands.empty()) - mutation_version = startMutation(maybe_mutation_commands, mutation_file_name); + mutation_version = startMutation(maybe_mutation_commands, local_context); } /// Always execute required mutations synchronously, because alters /// should be executed in sequential order. if (!maybe_mutation_commands.empty()) - waitForMutation(mutation_version, mutation_file_name); + waitForMutation(mutation_version); } { @@ -399,24 +414,35 @@ CurrentlyMergingPartsTagger::~CurrentlyMergingPartsTagger() storage.currently_processing_in_background_condition.notify_all(); } -Int64 StorageMergeTree::startMutation(const MutationCommands & commands, String & mutation_file_name) +Int64 StorageMergeTree::startMutation(const MutationCommands & commands, ContextPtr query_context) { /// Choose any disk, because when we load mutations we search them at each disk /// where storage can be placed. See loadMutations(). auto disk = getStoragePolicy()->getAnyDisk(); + TransactionID current_tid = Tx::PrehistoricTID; + String additional_info; + auto txn = query_context->getCurrentTransaction(); + if (txn) + { + current_tid = txn->tid; + additional_info = fmt::format(" (TID: {}; TIDH: {})", current_tid, current_tid.getHash()); + } + Int64 version; { std::lock_guard lock(currently_processing_in_background_mutex); - MergeTreeMutationEntry entry(commands, disk, relative_data_path, insert_increment.get()); + MergeTreeMutationEntry entry(commands, disk, relative_data_path, insert_increment.get(), current_tid); version = increment.get(); entry.commit(version); - mutation_file_name = entry.file_name; + String mutation_id = entry.file_name; + if (txn) + txn->addMutation(shared_from_this(), mutation_id); bool inserted = current_mutations_by_version.try_emplace(version, std::move(entry)).second; if (!inserted) throw Exception(ErrorCodes::LOGICAL_ERROR, "Mutation {} already exists, it's a bug", version); - LOG_INFO(log, "Added mutation: {}", mutation_file_name); + LOG_INFO(log, "Added mutation: {}{}", mutation_id, additional_info); } background_operations_assignee.trigger(); return version; @@ -462,9 +488,15 @@ void StorageMergeTree::updateMutationEntriesErrors(FutureMergedMutatedPartPtr re mutation_wait_event.notify_all(); } -void StorageMergeTree::waitForMutation(Int64 version, const String & file_name) +void StorageMergeTree::waitForMutation(Int64 version) { - LOG_INFO(log, "Waiting mutation: {}", file_name); + waitForMutation(MergeTreeMutationEntry::versionToFileName(version)); +} + +void StorageMergeTree::waitForMutation(const String & mutation_id) +{ + UInt64 version = MergeTreeMutationEntry::parseFileName(mutation_id); + LOG_INFO(log, "Waiting mutation: {}", mutation_id); { auto check = [version, this]() { @@ -480,29 +512,20 @@ void StorageMergeTree::waitForMutation(Int64 version, const String & file_name) /// At least we have our current mutation std::set mutation_ids; - mutation_ids.insert(file_name); + mutation_ids.insert(mutation_id); auto mutation_status = getIncompleteMutationsStatus(version, &mutation_ids); - try - { - checkMutationStatus(mutation_status, mutation_ids); - } - catch (...) - { - tryLogCurrentException(__PRETTY_FUNCTION__); - throw; - } + checkMutationStatus(mutation_status, mutation_ids); - LOG_INFO(log, "Mutation {} done", file_name); + LOG_INFO(log, "Mutation {} done", mutation_id); } void StorageMergeTree::mutate(const MutationCommands & commands, ContextPtr query_context) { - String mutation_file_name; - Int64 version = startMutation(commands, mutation_file_name); + Int64 version = startMutation(commands, query_context); if (query_context->getSettingsRef().mutations_sync > 0) - waitForMutation(version, mutation_file_name); + waitForMutation(version); } std::optional StorageMergeTree::getIncompleteMutationsStatus(Int64 mutation_version, std::set * mutation_ids) const @@ -518,7 +541,9 @@ std::optional StorageMergeTree::getIncompleteMutationsS const auto & mutation_entry = current_mutation_it->second; - auto data_parts = getDataPartsVector(); + auto txn = tryGetTransactionForMutation(mutation_entry, log); + assert(txn || mutation_entry.tid.isPrehistoric()); + auto data_parts = getVisibleDataPartsVector(txn); for (const auto & data_part : data_parts) { Int64 data_version = getUpdatedDataVersion(data_part, lock); @@ -542,6 +567,17 @@ std::optional StorageMergeTree::getIncompleteMutationsS mutation_ids->insert(it->second.file_name); } } + else if (txn) + { + /// Part is locked by concurrent transaction, most likely it will never be mutated + TIDHash part_locked = data_part->versions.maxtid_lock.load(); + if (part_locked && part_locked != mutation_entry.tid.getHash()) + { + result.latest_failed_part = data_part->name; + result.latest_fail_reason = fmt::format("Serialization error: part {} is locked by transaction {}", data_part->name, part_locked); + result.latest_fail_time = time(nullptr); + } + } return result; } @@ -617,6 +653,12 @@ CancellationCode StorageMergeTree::killMutation(const String & mutation_id) if (!to_kill) return CancellationCode::NotFound; + if (auto txn = tryGetTransactionForMutation(*to_kill, log)) + { + LOG_TRACE(log, "Cancelling transaction {} which had started mutation {}", to_kill->tid, mutation_id); + TransactionLog::instance().rollbackTransaction(txn); + } + getContext()->getMergeList().cancelPartMutations(getStorageID(), {}, to_kill->block_number); to_kill->removeFile(); LOG_TRACE(log, "Cancelled part mutations and removed mutation file {}", mutation_id); @@ -900,11 +942,29 @@ std::shared_ptr StorageMergeTree::selectPartsToMutate( continue; } - auto commands = MutationCommands::create(); + TransactionID first_mutation_tid = mutations_begin_it->second.tid; + MergeTreeTransactionPtr txn = tryGetTransactionForMutation(mutations_begin_it->second, log); + /// FIXME Transactions: we should kill mutations, but cannot do it here while holding currently_processing_in_background_mutex + /// TIDs are not persistent, so it cannot happen for now + assert(txn || first_mutation_tid.isPrehistoric()); + if (txn) + { + /// Mutate visible parts only + /// NOTE Do not mutate visible parts in Outdated state, because it does not make sense: + /// mutation will fail anyway due to serialization error. + if (!part->versions.isVisible(*txn)) + continue; + } + + auto commands = MutationCommands::create(); size_t current_ast_elements = 0; for (auto it = mutations_begin_it; it != mutations_end_it; ++it) { + /// Do not squash mutation from different transactions to be able to commit/rollback them independently. + if (first_mutation_tid != it->second.tid) + break; + size_t commands_size = 0; MutationCommands commands_for_size_validation; for (const auto & command : it->second.commands) @@ -988,7 +1048,7 @@ std::shared_ptr StorageMergeTree::selectPartsToMutate( future_part->type = part->getType(); tagger = std::make_unique(future_part, MergeTreeDataMergerMutator::estimateNeededDiskSpace({part}), *this, metadata_snapshot, true); - return std::make_shared(future_part, std::move(tagger), commands); + return std::make_shared(future_part, std::move(tagger), commands, txn); } } @@ -1112,54 +1172,52 @@ UInt64 StorageMergeTree::getCurrentMutationVersion( size_t StorageMergeTree::clearOldMutations(bool truncate) { - const auto settings = getSettings(); - if (!truncate && !settings->finished_mutations_to_keep) - return 0; + size_t finished_mutations_to_keep = truncate ? 0 : getSettings()->finished_mutations_to_keep; std::vector mutations_to_delete; { std::unique_lock lock(currently_processing_in_background_mutex); - if (!truncate && current_mutations_by_version.size() <= settings->finished_mutations_to_keep) + if (current_mutations_by_version.size() <= finished_mutations_to_keep) return 0; auto end_it = current_mutations_by_version.end(); auto begin_it = current_mutations_by_version.begin(); size_t to_delete_count = std::distance(begin_it, end_it); - if (!truncate) + if (std::optional min_version = getMinPartDataVersion()) + end_it = current_mutations_by_version.upper_bound(*min_version); + + size_t done_count = std::distance(begin_it, end_it); + if (done_count <= finished_mutations_to_keep) + return 0; + + auto part_versions_with_names = getSortedPartVersionsWithNames(lock); + + for (auto it = begin_it; it != end_it; ++it) { - if (std::optional min_version = getMinPartDataVersion()) - end_it = current_mutations_by_version.upper_bound(*min_version); + const PartVersionWithName needle{static_cast(it->first), ""}; + auto versions_it = std::lower_bound( + part_versions_with_names.begin(), part_versions_with_names.end(), needle); - size_t done_count = std::distance(begin_it, end_it); - if (done_count <= settings->finished_mutations_to_keep) - return 0; - - auto part_versions_with_names = getSortedPartVersionsWithNames(lock); - - for (auto it = begin_it; it != end_it; ++it) + if (versions_it != part_versions_with_names.begin()) { - const PartVersionWithName needle{static_cast(it->first), ""}; - auto versions_it = std::lower_bound( - part_versions_with_names.begin(), part_versions_with_names.end(), needle); - - if (versions_it != part_versions_with_names.begin()) - { - done_count = std::distance(begin_it, it); - break; - } + done_count = std::distance(begin_it, it); + break; } - - if (done_count <= settings->finished_mutations_to_keep) - return 0; - - to_delete_count = done_count - settings->finished_mutations_to_keep; } + if (done_count <= finished_mutations_to_keep) + return 0; + + to_delete_count = done_count - finished_mutations_to_keep; + auto it = begin_it; for (size_t i = 0; i < to_delete_count; ++i) { + const auto & tid = it->second.tid; + if (!tid.isPrehistoric() && !TransactionLog::instance().getCSN(tid)) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot remove mutation {}, because transaction {} is not committed. It's a bug"); mutations_to_delete.push_back(std::move(it->second)); it = current_mutations_by_version.erase(it); } diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index f4fe4e57959..7b805d189f2 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -168,9 +168,10 @@ private: /// Allocate block number for new mutation, write mutation to disk /// and into in-memory structures. Wake up merge-mutation task. - Int64 startMutation(const MutationCommands & commands, String & mutation_file_name); + Int64 startMutation(const MutationCommands & commands, ContextPtr query_context); /// Wait until mutation with version will finish mutation for all parts - void waitForMutation(Int64 version, const String & file_name); + void waitForMutation(Int64 version); + void waitForMutation(const String & mutation_id) override; friend struct CurrentlyMergingPartsTagger; diff --git a/tests/queries/0_stateless/01168_mutations_isolation.reference b/tests/queries/0_stateless/01168_mutations_isolation.reference new file mode 100644 index 00000000000..582a42418d6 --- /dev/null +++ b/tests/queries/0_stateless/01168_mutations_isolation.reference @@ -0,0 +1,20 @@ +1 10 all_1_1_0_4 +1 30 all_3_3_0_4 +2 1 all_1_1_0 +2 2 all_2_2_0 +Serialization error +INVALID_TRANSACTION +3 1 all_1_1_0 +Serialization error +4 2 all_1_1_0_8 +4 5 all_11_11_0 +4 6 all_7_7_0_8 +5 2 all_1_1_0_8 +5 5 all_11_11_0 +5 6 all_7_7_0_8 +SERIALIZATION_ERROR +6 2 all_1_1_0_12 +6 6 all_7_7_0_12 +7 20 all_1_1_0_14 +7 60 all_7_7_0_14 +7 80 all_13_13_0_14 diff --git a/tests/queries/0_stateless/01168_mutations_isolation.sh b/tests/queries/0_stateless/01168_mutations_isolation.sh new file mode 100755 index 00000000000..9791e2aa079 --- /dev/null +++ b/tests/queries/0_stateless/01168_mutations_isolation.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-replicated-database +# Looks like server does not listen https port in fasttest +# FIXME Replicated database executes ALTERs in separate context, so transaction info is lost + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh +# shellcheck source=./transactions.lib +. "$CURDIR"/transactions.lib + +$CLICKHOUSE_CLIENT -q "drop table if exists mt" +$CLICKHOUSE_CLIENT -q "create table mt (n int) engine=MergeTree order by tuple()" + +$CLICKHOUSE_CLIENT -q "insert into mt values (1)" + +tx 1 "begin transaction" +tx 2 "begin transaction" +tx 1 "insert into mt values (2)" +tx 2 "insert into mt values (3)" +tx 2 "alter table mt update n=n*10 where 1 settings mutations_sync=1" +tx 2 "select 1, n, _part from mt order by n" +tx 1 "select 2, n, _part from mt order by n" +tx 1 "alter table mt update n=n+1 where 1 settings mutations_sync=1" | grep -Eo "Serialization error" | uniq +tx 1 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 2 "rollback" + + +tx 3 "begin transaction" +tx 3 "select 3, n, _part from mt order by n" +tx 4 "begin transaction" +tx 3 "insert into mt values (2)" +tx 4 "insert into mt values (3)" +tx 4 "alter table mt update n=n*2 where 1" +tx 3 "alter table mt update n=n+42 where 1" +tx 3 "insert into mt values (4)" +tx 4 "insert into mt values (5)" +tx 3 "commit" | grep -Eo "Serialization error" | uniq +tx 4 "commit" + + +tx 5 "begin transaction" +tx 5 "select 4, n, _part from mt order by n" +tx 6 "begin transaction" +tx 6 "alter table mt delete where n%2=1 settings mutations_sync=1" +tx 5 "select 5, n, _part from mt order by n" +tx 5 "alter table mt drop partition id 'all'" | grep -Eo "SERIALIZATION_ERROR" | uniq +tx 6 "select 6, n, _part from mt order by n" +tx 5 "rollback" +tx 6 "insert into mt values (8)" +tx 6 "alter table mt update n=n*10 where 1" +tx 6 "commit" + +tx 8 "begin transaction" +tx 8 "select 7, n, _part from mt order by n" + +$CLICKHOUSE_CLIENT --database_atomic_wait_for_drop_and_detach_synchronously=0 -q "drop table mt" From 89b77d30569c6a86d466d95d242ab9d83488774a Mon Sep 17 00:00:00 2001 From: tavplubix Date: Wed, 15 Dec 2021 01:36:45 +0300 Subject: [PATCH 0127/1647] Update 02117_show_create_table_system.reference --- .../0_stateless/02117_show_create_table_system.reference | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02117_show_create_table_system.reference b/tests/queries/0_stateless/02117_show_create_table_system.reference index 2b391cd292e..563ccc1147c 100644 --- a/tests/queries/0_stateless/02117_show_create_table_system.reference +++ b/tests/queries/0_stateless/02117_show_create_table_system.reference @@ -33,7 +33,7 @@ CREATE TABLE system.numbers\n(\n `number` UInt64\n)\nENGINE = SystemNumbers() CREATE TABLE system.numbers_mt\n(\n `number` UInt64\n)\nENGINE = SystemNumbers()\nCOMMENT \'SYSTEM TABLE is built on the fly.\' CREATE TABLE system.one\n(\n `dummy` UInt8\n)\nENGINE = SystemOne()\nCOMMENT \'SYSTEM TABLE is built on the fly.\' CREATE TABLE system.part_moves_between_shards\n(\n `database` String,\n `table` String,\n `task_name` String,\n `task_uuid` UUID,\n `create_time` DateTime,\n `part_name` String,\n `part_uuid` UUID,\n `to_shard` String,\n `dst_part_name` String,\n `update_time` DateTime,\n `state` String,\n `rollback` UInt8,\n `num_tries` UInt32,\n `last_exception` String\n)\nENGINE = SystemShardMoves()\nCOMMENT \'SYSTEM TABLE is built on the fly.\' -CREATE TABLE system.parts\n(\n `partition` String,\n `name` String,\n `uuid` UUID,\n `part_type` String,\n `active` UInt8,\n `marks` UInt64,\n `rows` UInt64,\n `bytes_on_disk` UInt64,\n `data_compressed_bytes` UInt64,\n `data_uncompressed_bytes` UInt64,\n `marks_bytes` UInt64,\n `secondary_indices_compressed_bytes` UInt64,\n `secondary_indices_uncompressed_bytes` UInt64,\n `secondary_indices_marks_bytes` UInt64,\n `modification_time` DateTime,\n `remove_time` DateTime,\n `refcount` UInt32,\n `min_date` Date,\n `max_date` Date,\n `min_time` DateTime,\n `max_time` DateTime,\n `partition_id` String,\n `min_block_number` Int64,\n `max_block_number` Int64,\n `level` UInt32,\n `data_version` UInt64,\n `primary_key_bytes_in_memory` UInt64,\n `primary_key_bytes_in_memory_allocated` UInt64,\n `is_frozen` UInt8,\n `database` String,\n `table` String,\n `engine` String,\n `disk_name` String,\n `path` String,\n `hash_of_all_files` String,\n `hash_of_uncompressed_files` String,\n `uncompressed_hash_of_compressed_files` String,\n `delete_ttl_info_min` DateTime,\n `delete_ttl_info_max` DateTime,\n `move_ttl_info.expression` Array(String),\n `move_ttl_info.min` Array(DateTime),\n `move_ttl_info.max` Array(DateTime),\n `default_compression_codec` String,\n `recompression_ttl_info.expression` Array(String),\n `recompression_ttl_info.min` Array(DateTime),\n `recompression_ttl_info.max` Array(DateTime),\n `group_by_ttl_info.expression` Array(String),\n `group_by_ttl_info.min` Array(DateTime),\n `group_by_ttl_info.max` Array(DateTime),\n `rows_where_ttl_info.expression` Array(String),\n `rows_where_ttl_info.min` Array(DateTime),\n `rows_where_ttl_info.max` Array(DateTime),\n `projections` Array(String),\n `bytes` UInt64,\n `marks_size` UInt64\n)\nENGINE = SystemParts()\nCOMMENT \'SYSTEM TABLE is built on the fly.\' +CREATE TABLE system.parts\n(\n `partition` String,\n `name` String,\n `uuid` UUID,\n `part_type` String,\n `active` UInt8,\n `marks` UInt64,\n `rows` UInt64,\n `bytes_on_disk` UInt64,\n `data_compressed_bytes` UInt64,\n `data_uncompressed_bytes` UInt64,\n `marks_bytes` UInt64,\n `secondary_indices_compressed_bytes` UInt64,\n `secondary_indices_uncompressed_bytes` UInt64,\n `secondary_indices_marks_bytes` UInt64,\n `modification_time` DateTime,\n `remove_time` DateTime,\n `refcount` UInt32,\n `min_date` Date,\n `max_date` Date,\n `min_time` DateTime,\n `max_time` DateTime,\n `partition_id` String,\n `min_block_number` Int64,\n `max_block_number` Int64,\n `level` UInt32,\n `data_version` UInt64,\n `primary_key_bytes_in_memory` UInt64,\n `primary_key_bytes_in_memory_allocated` UInt64,\n `is_frozen` UInt8,\n `database` String,\n `table` String,\n `engine` String,\n `disk_name` String,\n `path` String,\n `hash_of_all_files` String,\n `hash_of_uncompressed_files` String,\n `uncompressed_hash_of_compressed_files` String,\n `delete_ttl_info_min` DateTime,\n `delete_ttl_info_max` DateTime,\n `move_ttl_info.expression` Array(String),\n `move_ttl_info.min` Array(DateTime),\n `move_ttl_info.max` Array(DateTime),\n `default_compression_codec` String,\n `recompression_ttl_info.expression` Array(String),\n `recompression_ttl_info.min` Array(DateTime),\n `recompression_ttl_info.max` Array(DateTime),\n `group_by_ttl_info.expression` Array(String),\n `group_by_ttl_info.min` Array(DateTime),\n `group_by_ttl_info.max` Array(DateTime),\n `rows_where_ttl_info.expression` Array(String),\n `rows_where_ttl_info.min` Array(DateTime),\n `rows_where_ttl_info.max` Array(DateTime),\n `projections` Array(String),\n `visible` UInt8,\n `mintid` Tuple(UInt64, UInt64, UUID),\n `maxtid` Tuple(UInt64, UInt64, UUID),\n `mincsn` UInt64,\n `maxcsn` UInt64,\n `bytes` UInt64,\n `marks_size` UInt64\n)\nENGINE = SystemParts()\nCOMMENT \'SYSTEM TABLE is built on the fly.\' CREATE TABLE system.parts_columns\n(\n `partition` String,\n `name` String,\n `uuid` UUID,\n `part_type` String,\n `active` UInt8,\n `marks` UInt64,\n `rows` UInt64,\n `bytes_on_disk` UInt64,\n `data_compressed_bytes` UInt64,\n `data_uncompressed_bytes` UInt64,\n `marks_bytes` UInt64,\n `modification_time` DateTime,\n `remove_time` DateTime,\n `refcount` UInt32,\n `min_date` Date,\n `max_date` Date,\n `min_time` DateTime,\n `max_time` DateTime,\n `partition_id` String,\n `min_block_number` Int64,\n `max_block_number` Int64,\n `level` UInt32,\n `data_version` UInt64,\n `primary_key_bytes_in_memory` UInt64,\n `primary_key_bytes_in_memory_allocated` UInt64,\n `database` String,\n `table` String,\n `engine` String,\n `disk_name` String,\n `path` String,\n `column` String,\n `type` String,\n `column_position` UInt64,\n `default_kind` String,\n `default_expression` String,\n `column_bytes_on_disk` UInt64,\n `column_data_compressed_bytes` UInt64,\n `column_data_uncompressed_bytes` UInt64,\n `column_marks_bytes` UInt64,\n `bytes` UInt64,\n `marks_size` UInt64\n)\nENGINE = SystemPartsColumns()\nCOMMENT \'SYSTEM TABLE is built on the fly.\' CREATE TABLE system.privileges\n(\n `privilege` Enum8(\'SQLITE\' = -128, \'ODBC\' = -127, \'JDBC\' = -126, \'HDFS\' = -125, \'S3\' = -124, \'SOURCES\' = -123, \'ALL\' = -122, \'NONE\' = -121, \'SHOW DATABASES\' = 0, \'SHOW TABLES\' = 1, \'SHOW COLUMNS\' = 2, \'SHOW DICTIONARIES\' = 3, \'SHOW\' = 4, \'SELECT\' = 5, \'INSERT\' = 6, \'ALTER UPDATE\' = 7, \'ALTER DELETE\' = 8, \'ALTER ADD COLUMN\' = 9, \'ALTER MODIFY COLUMN\' = 10, \'ALTER DROP COLUMN\' = 11, \'ALTER COMMENT COLUMN\' = 12, \'ALTER CLEAR COLUMN\' = 13, \'ALTER RENAME COLUMN\' = 14, \'ALTER MATERIALIZE COLUMN\' = 15, \'ALTER COLUMN\' = 16, \'ALTER MODIFY COMMENT\' = 17, \'ALTER ORDER BY\' = 18, \'ALTER SAMPLE BY\' = 19, \'ALTER ADD INDEX\' = 20, \'ALTER DROP INDEX\' = 21, \'ALTER MATERIALIZE INDEX\' = 22, \'ALTER CLEAR INDEX\' = 23, \'ALTER INDEX\' = 24, \'ALTER ADD PROJECTION\' = 25, \'ALTER DROP PROJECTION\' = 26, \'ALTER MATERIALIZE PROJECTION\' = 27, \'ALTER CLEAR PROJECTION\' = 28, \'ALTER PROJECTION\' = 29, \'ALTER ADD CONSTRAINT\' = 30, \'ALTER DROP CONSTRAINT\' = 31, \'ALTER CONSTRAINT\' = 32, \'ALTER TTL\' = 33, \'ALTER MATERIALIZE TTL\' = 34, \'ALTER SETTINGS\' = 35, \'ALTER MOVE PARTITION\' = 36, \'ALTER FETCH PARTITION\' = 37, \'ALTER FREEZE PARTITION\' = 38, \'ALTER DATABASE SETTINGS\' = 39, \'ALTER TABLE\' = 40, \'ALTER DATABASE\' = 41, \'ALTER VIEW REFRESH\' = 42, \'ALTER VIEW MODIFY QUERY\' = 43, \'ALTER VIEW\' = 44, \'ALTER\' = 45, \'CREATE DATABASE\' = 46, \'CREATE TABLE\' = 47, \'CREATE VIEW\' = 48, \'CREATE DICTIONARY\' = 49, \'CREATE TEMPORARY TABLE\' = 50, \'CREATE FUNCTION\' = 51, \'CREATE\' = 52, \'DROP DATABASE\' = 53, \'DROP TABLE\' = 54, \'DROP VIEW\' = 55, \'DROP DICTIONARY\' = 56, \'DROP FUNCTION\' = 57, \'DROP\' = 58, \'TRUNCATE\' = 59, \'OPTIMIZE\' = 60, \'KILL QUERY\' = 61, \'MOVE PARTITION BETWEEN SHARDS\' = 62, \'CREATE USER\' = 63, \'ALTER USER\' = 64, \'DROP USER\' = 65, \'CREATE ROLE\' = 66, \'ALTER ROLE\' = 67, \'DROP ROLE\' = 68, \'ROLE ADMIN\' = 69, \'CREATE ROW POLICY\' = 70, \'ALTER ROW POLICY\' = 71, \'DROP ROW POLICY\' = 72, \'CREATE QUOTA\' = 73, \'ALTER QUOTA\' = 74, \'DROP QUOTA\' = 75, \'CREATE SETTINGS PROFILE\' = 76, \'ALTER SETTINGS PROFILE\' = 77, \'DROP SETTINGS PROFILE\' = 78, \'SHOW USERS\' = 79, \'SHOW ROLES\' = 80, \'SHOW ROW POLICIES\' = 81, \'SHOW QUOTAS\' = 82, \'SHOW SETTINGS PROFILES\' = 83, \'SHOW ACCESS\' = 84, \'ACCESS MANAGEMENT\' = 85, \'SYSTEM SHUTDOWN\' = 86, \'SYSTEM DROP DNS CACHE\' = 87, \'SYSTEM DROP MARK CACHE\' = 88, \'SYSTEM DROP UNCOMPRESSED CACHE\' = 89, \'SYSTEM DROP MMAP CACHE\' = 90, \'SYSTEM DROP COMPILED EXPRESSION CACHE\' = 91, \'SYSTEM DROP CACHE\' = 92, \'SYSTEM RELOAD CONFIG\' = 93, \'SYSTEM RELOAD SYMBOLS\' = 94, \'SYSTEM RELOAD DICTIONARY\' = 95, \'SYSTEM RELOAD MODEL\' = 96, \'SYSTEM RELOAD FUNCTION\' = 97, \'SYSTEM RELOAD EMBEDDED DICTIONARIES\' = 98, \'SYSTEM RELOAD\' = 99, \'SYSTEM RESTART DISK\' = 100, \'SYSTEM MERGES\' = 101, \'SYSTEM TTL MERGES\' = 102, \'SYSTEM FETCHES\' = 103, \'SYSTEM MOVES\' = 104, \'SYSTEM DISTRIBUTED SENDS\' = 105, \'SYSTEM REPLICATED SENDS\' = 106, \'SYSTEM SENDS\' = 107, \'SYSTEM REPLICATION QUEUES\' = 108, \'SYSTEM DROP REPLICA\' = 109, \'SYSTEM SYNC REPLICA\' = 110, \'SYSTEM RESTART REPLICA\' = 111, \'SYSTEM RESTORE REPLICA\' = 112, \'SYSTEM FLUSH DISTRIBUTED\' = 113, \'SYSTEM FLUSH LOGS\' = 114, \'SYSTEM FLUSH\' = 115, \'SYSTEM\' = 116, \'dictGet\' = 117, \'addressToLine\' = 118, \'addressToSymbol\' = 119, \'demangle\' = 120, \'INTROSPECTION\' = 121, \'FILE\' = 122, \'URL\' = 123, \'REMOTE\' = 124, \'MONGO\' = 125, \'MYSQL\' = 126, \'POSTGRES\' = 127),\n `aliases` Array(String),\n `level` Nullable(Enum8(\'GLOBAL\' = 0, \'DATABASE\' = 1, \'TABLE\' = 2, \'DICTIONARY\' = 3, \'VIEW\' = 4, \'COLUMN\' = 5)),\n `parent_group` Nullable(Enum8(\'SQLITE\' = -128, \'ODBC\' = -127, \'JDBC\' = -126, \'HDFS\' = -125, \'S3\' = -124, \'SOURCES\' = -123, \'ALL\' = -122, \'NONE\' = -121, \'SHOW DATABASES\' = 0, \'SHOW TABLES\' = 1, \'SHOW COLUMNS\' = 2, \'SHOW DICTIONARIES\' = 3, \'SHOW\' = 4, \'SELECT\' = 5, \'INSERT\' = 6, \'ALTER UPDATE\' = 7, \'ALTER DELETE\' = 8, \'ALTER ADD COLUMN\' = 9, \'ALTER MODIFY COLUMN\' = 10, \'ALTER DROP COLUMN\' = 11, \'ALTER COMMENT COLUMN\' = 12, \'ALTER CLEAR COLUMN\' = 13, \'ALTER RENAME COLUMN\' = 14, \'ALTER MATERIALIZE COLUMN\' = 15, \'ALTER COLUMN\' = 16, \'ALTER MODIFY COMMENT\' = 17, \'ALTER ORDER BY\' = 18, \'ALTER SAMPLE BY\' = 19, \'ALTER ADD INDEX\' = 20, \'ALTER DROP INDEX\' = 21, \'ALTER MATERIALIZE INDEX\' = 22, \'ALTER CLEAR INDEX\' = 23, \'ALTER INDEX\' = 24, \'ALTER ADD PROJECTION\' = 25, \'ALTER DROP PROJECTION\' = 26, \'ALTER MATERIALIZE PROJECTION\' = 27, \'ALTER CLEAR PROJECTION\' = 28, \'ALTER PROJECTION\' = 29, \'ALTER ADD CONSTRAINT\' = 30, \'ALTER DROP CONSTRAINT\' = 31, \'ALTER CONSTRAINT\' = 32, \'ALTER TTL\' = 33, \'ALTER MATERIALIZE TTL\' = 34, \'ALTER SETTINGS\' = 35, \'ALTER MOVE PARTITION\' = 36, \'ALTER FETCH PARTITION\' = 37, \'ALTER FREEZE PARTITION\' = 38, \'ALTER DATABASE SETTINGS\' = 39, \'ALTER TABLE\' = 40, \'ALTER DATABASE\' = 41, \'ALTER VIEW REFRESH\' = 42, \'ALTER VIEW MODIFY QUERY\' = 43, \'ALTER VIEW\' = 44, \'ALTER\' = 45, \'CREATE DATABASE\' = 46, \'CREATE TABLE\' = 47, \'CREATE VIEW\' = 48, \'CREATE DICTIONARY\' = 49, \'CREATE TEMPORARY TABLE\' = 50, \'CREATE FUNCTION\' = 51, \'CREATE\' = 52, \'DROP DATABASE\' = 53, \'DROP TABLE\' = 54, \'DROP VIEW\' = 55, \'DROP DICTIONARY\' = 56, \'DROP FUNCTION\' = 57, \'DROP\' = 58, \'TRUNCATE\' = 59, \'OPTIMIZE\' = 60, \'KILL QUERY\' = 61, \'MOVE PARTITION BETWEEN SHARDS\' = 62, \'CREATE USER\' = 63, \'ALTER USER\' = 64, \'DROP USER\' = 65, \'CREATE ROLE\' = 66, \'ALTER ROLE\' = 67, \'DROP ROLE\' = 68, \'ROLE ADMIN\' = 69, \'CREATE ROW POLICY\' = 70, \'ALTER ROW POLICY\' = 71, \'DROP ROW POLICY\' = 72, \'CREATE QUOTA\' = 73, \'ALTER QUOTA\' = 74, \'DROP QUOTA\' = 75, \'CREATE SETTINGS PROFILE\' = 76, \'ALTER SETTINGS PROFILE\' = 77, \'DROP SETTINGS PROFILE\' = 78, \'SHOW USERS\' = 79, \'SHOW ROLES\' = 80, \'SHOW ROW POLICIES\' = 81, \'SHOW QUOTAS\' = 82, \'SHOW SETTINGS PROFILES\' = 83, \'SHOW ACCESS\' = 84, \'ACCESS MANAGEMENT\' = 85, \'SYSTEM SHUTDOWN\' = 86, \'SYSTEM DROP DNS CACHE\' = 87, \'SYSTEM DROP MARK CACHE\' = 88, \'SYSTEM DROP UNCOMPRESSED CACHE\' = 89, \'SYSTEM DROP MMAP CACHE\' = 90, \'SYSTEM DROP COMPILED EXPRESSION CACHE\' = 91, \'SYSTEM DROP CACHE\' = 92, \'SYSTEM RELOAD CONFIG\' = 93, \'SYSTEM RELOAD SYMBOLS\' = 94, \'SYSTEM RELOAD DICTIONARY\' = 95, \'SYSTEM RELOAD MODEL\' = 96, \'SYSTEM RELOAD FUNCTION\' = 97, \'SYSTEM RELOAD EMBEDDED DICTIONARIES\' = 98, \'SYSTEM RELOAD\' = 99, \'SYSTEM RESTART DISK\' = 100, \'SYSTEM MERGES\' = 101, \'SYSTEM TTL MERGES\' = 102, \'SYSTEM FETCHES\' = 103, \'SYSTEM MOVES\' = 104, \'SYSTEM DISTRIBUTED SENDS\' = 105, \'SYSTEM REPLICATED SENDS\' = 106, \'SYSTEM SENDS\' = 107, \'SYSTEM REPLICATION QUEUES\' = 108, \'SYSTEM DROP REPLICA\' = 109, \'SYSTEM SYNC REPLICA\' = 110, \'SYSTEM RESTART REPLICA\' = 111, \'SYSTEM RESTORE REPLICA\' = 112, \'SYSTEM FLUSH DISTRIBUTED\' = 113, \'SYSTEM FLUSH LOGS\' = 114, \'SYSTEM FLUSH\' = 115, \'SYSTEM\' = 116, \'dictGet\' = 117, \'addressToLine\' = 118, \'addressToSymbol\' = 119, \'demangle\' = 120, \'INTROSPECTION\' = 121, \'FILE\' = 122, \'URL\' = 123, \'REMOTE\' = 124, \'MONGO\' = 125, \'MYSQL\' = 126, \'POSTGRES\' = 127))\n)\nENGINE = SystemPrivileges()\nCOMMENT \'SYSTEM TABLE is built on the fly.\' CREATE TABLE system.processes\n(\n `is_initial_query` UInt8,\n `user` String,\n `query_id` String,\n `address` IPv6,\n `port` UInt16,\n `initial_user` String,\n `initial_query_id` String,\n `initial_address` IPv6,\n `initial_port` UInt16,\n `interface` UInt8,\n `os_user` String,\n `client_hostname` String,\n `client_name` String,\n `client_revision` UInt64,\n `client_version_major` UInt64,\n `client_version_minor` UInt64,\n `client_version_patch` UInt64,\n `http_method` UInt8,\n `http_user_agent` String,\n `http_referer` String,\n `forwarded_for` String,\n `quota_key` String,\n `elapsed` Float64,\n `is_cancelled` UInt8,\n `read_rows` UInt64,\n `read_bytes` UInt64,\n `total_rows_approx` UInt64,\n `written_rows` UInt64,\n `written_bytes` UInt64,\n `memory_usage` Int64,\n `peak_memory_usage` Int64,\n `query` String,\n `thread_ids` Array(UInt64),\n `ProfileEvents` Map(String, UInt64),\n `Settings` Map(String, String),\n `current_database` String,\n `ProfileEvents.Names` Array(String),\n `ProfileEvents.Values` Array(UInt64),\n `Settings.Names` Array(String),\n `Settings.Values` Array(String)\n)\nENGINE = SystemProcesses()\nCOMMENT \'SYSTEM TABLE is built on the fly.\' From e185ad260b5208509afc5f21e95da8ad65585e0a Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 15 Dec 2021 21:19:29 +0300 Subject: [PATCH 0128/1647] fix skipping of some mutations --- src/Storages/MergeTree/MergeTreeData.cpp | 21 ++++++++++++++++++ src/Storages/MergeTree/MergeTreeData.h | 1 + src/Storages/StorageMergeTree.cpp | 13 ++++++++--- src/Storages/StorageReplicatedMergeTree.cpp | 22 ------------------- src/Storages/StorageReplicatedMergeTree.h | 1 - ...02004_invalid_partition_mutation_stuck.sql | 2 +- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index bd3e79b72cc..c31f399522f 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -3867,6 +3867,27 @@ std::unordered_set MergeTreeData::getPartitionIDsFromQuery(const ASTs & return partition_ids; } +std::set MergeTreeData::getPartitionIdsAffectedByCommands( + const MutationCommands & commands, ContextPtr query_context) const +{ + std::set affected_partition_ids; + + for (const auto & command : commands) + { + if (!command.partition) + { + affected_partition_ids.clear(); + break; + } + + affected_partition_ids.insert( + getPartitionIDFromQuery(command.partition, query_context) + ); + } + + return affected_partition_ids; +} + MergeTreeData::DataPartsVector MergeTreeData::getDataPartsVector( const DataPartStates & affordable_states, DataPartStateVector * out_states, bool require_projection_parts) const diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index d5b8901527d..f43bf2cc643 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -706,6 +706,7 @@ public: /// For ATTACH/DETACH/DROP PARTITION. String getPartitionIDFromQuery(const ASTPtr & ast, ContextPtr context) const; std::unordered_set getPartitionIDsFromQuery(const ASTs & asts, ContextPtr context) const; + std::set getPartitionIdsAffectedByCommands(const MutationCommands & commands, ContextPtr query_context) const; /// Extracts MergeTreeData of other *MergeTree* storage /// and checks that their structure suitable for ALTER TABLE ATTACH PARTITION FROM diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 791fe7e239b..35df7ce1c58 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -529,6 +529,9 @@ void StorageMergeTree::waitForMutation(const String & mutation_id) void StorageMergeTree::mutate(const MutationCommands & commands, ContextPtr query_context) { + /// Validate partition IDs (if any) before starting mutation + getPartitionIdsAffectedByCommands(commands, query_context); + Int64 version = startMutation(commands, query_context); if (query_context->getSettingsRef().mutations_sync > 0) @@ -966,6 +969,7 @@ std::shared_ptr StorageMergeTree::selectPartsToMutate( auto commands = MutationCommands::create(); size_t current_ast_elements = 0; + auto last_mutation_to_apply = mutations_end_it; for (auto it = mutations_begin_it; it != mutations_end_it; ++it) { /// Do not squash mutation from different transactions to be able to commit/rollback them independently. @@ -1006,7 +1010,8 @@ std::shared_ptr StorageMergeTree::selectPartsToMutate( MergeTreeMutationEntry & entry = it->second; entry.latest_fail_time = time(nullptr); entry.latest_fail_reason = getCurrentExceptionMessage(false); - continue; + /// NOTE we should not skip mutations, because exception may be retryable (e.g. MEMORY_LIMIT_EXCEEDED) + break; } } @@ -1015,8 +1020,10 @@ std::shared_ptr StorageMergeTree::selectPartsToMutate( current_ast_elements += commands_size; commands->insert(commands->end(), it->second.commands.begin(), it->second.commands.end()); + last_mutation_to_apply = it; } + assert(commands->empty() == (last_mutation_to_apply == mutations_end_it)); if (!commands->empty()) { bool is_partition_affected = false; @@ -1041,13 +1048,13 @@ std::shared_ptr StorageMergeTree::selectPartsToMutate( /// Shall not create a new part, but will do that later if mutation with higher version appear. /// This is needed in order to not produce excessive mutations of non-related parts. auto block_range = std::make_pair(part->info.min_block, part->info.max_block); - updated_version_by_block_range[block_range] = current_mutations_by_version.rbegin()->first; + updated_version_by_block_range[block_range] = last_mutation_to_apply->first; were_some_mutations_for_some_parts_skipped = true; continue; } auto new_part_info = part->info; - new_part_info.mutation = current_mutations_by_version.rbegin()->first; + new_part_info.mutation = last_mutation_to_apply->first; future_part->parts.push_back(part); future_part->part_info = new_part_info; diff --git a/src/Storages/StorageReplicatedMergeTree.cpp b/src/Storages/StorageReplicatedMergeTree.cpp index 2c891436234..b8653d72646 100644 --- a/src/Storages/StorageReplicatedMergeTree.cpp +++ b/src/Storages/StorageReplicatedMergeTree.cpp @@ -4533,28 +4533,6 @@ bool StorageReplicatedMergeTree::executeMetadataAlter(const StorageReplicatedMer } -std::set StorageReplicatedMergeTree::getPartitionIdsAffectedByCommands( - const MutationCommands & commands, ContextPtr query_context) const -{ - std::set affected_partition_ids; - - for (const auto & command : commands) - { - if (!command.partition) - { - affected_partition_ids.clear(); - break; - } - - affected_partition_ids.insert( - getPartitionIDFromQuery(command.partition, query_context) - ); - } - - return affected_partition_ids; -} - - PartitionBlockNumbersHolder StorageReplicatedMergeTree::allocateBlockNumbersInAffectedPartitions( const MutationCommands & commands, ContextPtr query_context, const zkutil::ZooKeeperPtr & zookeeper) const { diff --git a/src/Storages/StorageReplicatedMergeTree.h b/src/Storages/StorageReplicatedMergeTree.h index bcd364df30e..6861d89f070 100644 --- a/src/Storages/StorageReplicatedMergeTree.h +++ b/src/Storages/StorageReplicatedMergeTree.h @@ -717,7 +717,6 @@ private: std::unique_ptr getDefaultSettings() const override; - std::set getPartitionIdsAffectedByCommands(const MutationCommands & commands, ContextPtr query_context) const; PartitionBlockNumbersHolder allocateBlockNumbersInAffectedPartitions( const MutationCommands & commands, ContextPtr query_context, const zkutil::ZooKeeperPtr & zookeeper) const; diff --git a/tests/queries/0_stateless/02004_invalid_partition_mutation_stuck.sql b/tests/queries/0_stateless/02004_invalid_partition_mutation_stuck.sql index 481a5565095..71c8b9af652 100644 --- a/tests/queries/0_stateless/02004_invalid_partition_mutation_stuck.sql +++ b/tests/queries/0_stateless/02004_invalid_partition_mutation_stuck.sql @@ -28,6 +28,6 @@ PARTITION BY p ORDER BY t SETTINGS number_of_free_entries_in_pool_to_execute_mutation=0; INSERT INTO data VALUES (1, now()); -ALTER TABLE data MATERIALIZE INDEX idx IN PARTITION ID 'NO_SUCH_PART'; -- { serverError 341 } +ALTER TABLE data MATERIALIZE INDEX idx IN PARTITION ID 'NO_SUCH_PART'; -- { serverError 248 } ALTER TABLE data MATERIALIZE INDEX idx IN PARTITION ID '1'; ALTER TABLE data MATERIALIZE INDEX idx IN PARTITION ID '2'; From f330ac31c3ce68a405c37485b80cb5ca0d30a500 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 16 Dec 2021 00:49:06 +0300 Subject: [PATCH 0129/1647] fix test --- src/Interpreters/MergeTreeTransaction.cpp | 2 +- .../TransactionVersionMetadata.cpp | 2 ++ src/Storages/MergeTree/MergeTreeData.cpp | 17 ++++++---- .../01168_mutations_isolation.reference | 14 +++++++++ .../0_stateless/01168_mutations_isolation.sh | 31 +++++++++++++++++-- 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index b2f1d8ae150..91e4b51ea07 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -85,7 +85,7 @@ void MergeTreeTransaction::removeOldPart(const StoragePtr & storage, const DataP { CSN c = csn.load(); if (c == Tx::RolledBackCSN) - throw Exception(ErrorCodes::INVALID_TRANSACTION, "Transaction was cancelled"); + throw Exception(ErrorCodes::INVALID_TRANSACTION, "Transaction was cancelled");//FIXME else if (c != Tx::UnknownCSN) throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected CSN state: {}", c); diff --git a/src/Interpreters/TransactionVersionMetadata.cpp b/src/Interpreters/TransactionVersionMetadata.cpp index c8e6b9dcfba..59d3b1ef4b9 100644 --- a/src/Interpreters/TransactionVersionMetadata.cpp +++ b/src/Interpreters/TransactionVersionMetadata.cpp @@ -38,6 +38,7 @@ TransactionID VersionMetadata::getMaxTID() const void VersionMetadata::lockMaxTID(const TransactionID & tid, const String & error_context) { + //LOG_TRACE(&Poco::Logger::get("WTF"), "Trying to lock maxtid by {}: {}\n{}", tid, error_context, StackTrace().toString()); TIDHash locked_by = 0; if (tryLockMaxTID(tid, &locked_by)) return; @@ -75,6 +76,7 @@ bool VersionMetadata::tryLockMaxTID(const TransactionID & tid, TIDHash * locked_ void VersionMetadata::unlockMaxTID(const TransactionID & tid) { + //LOG_TRACE(&Poco::Logger::get("WTF"), "Unlocking maxtid by {}", tid); assert(!tid.isEmpty()); TIDHash max_lock_value = tid.getHash(); TIDHash locked_by = maxtid_lock.load(); diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index c31f399522f..31d4d5a8098 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1663,11 +1663,15 @@ size_t MergeTreeData::clearEmptyParts() auto parts = getDataPartsVector(); for (const auto & part : parts) { - if (part->rows_count == 0) - { - dropPartNoWaitNoThrow(part->name); - ++cleared_count; - } + if (part->rows_count != 0) + continue; + + /// Do not drop empty part if it may be visible for some transaction (otherwise it may cause conflicts) + if (!part->versions.canBeRemoved(TransactionLog::instance().getOldestSnapshot())) + continue; + + dropPartNoWaitNoThrow(part->name); + ++cleared_count; } return cleared_count; } @@ -2734,7 +2738,8 @@ MergeTreeData::DataPartsVector MergeTreeData::removePartsInRangeFromWorkingSet( void MergeTreeData::restoreAndActivatePart(const DataPartPtr & part, DataPartsLock * acquired_lock) { auto lock = (acquired_lock) ? DataPartsLock() : lockParts(); //-V1018 - assert(part->getState() != DataPartState::Committed); + if (part->getState() == DataPartState::Committed) + return; addPartContributionToColumnAndSecondaryIndexSizes(part); addPartContributionToDataVolume(part); modifyPartState(part, DataPartState::Committed); diff --git a/tests/queries/0_stateless/01168_mutations_isolation.reference b/tests/queries/0_stateless/01168_mutations_isolation.reference index 582a42418d6..56e7264b174 100644 --- a/tests/queries/0_stateless/01168_mutations_isolation.reference +++ b/tests/queries/0_stateless/01168_mutations_isolation.reference @@ -16,5 +16,19 @@ SERIALIZATION_ERROR 6 2 all_1_1_0_12 6 6 all_7_7_0_12 7 20 all_1_1_0_14 +7 40 all_15_15_0 7 60 all_7_7_0_14 7 80 all_13_13_0_14 +8 20 all_1_15_1_14 +8 40 all_1_15_1_14 +8 60 all_1_15_1_14 +8 80 all_1_15_1_14 +INVALID_TRANSACTION +9 21 all_1_15_1_18 +9 41 all_1_15_1_18 +9 61 all_1_15_1_18 +9 81 all_1_15_1_18 +10 22 all_1_15_1_19 +10 42 all_1_15_1_19 +10 62 all_1_15_1_19 +10 82 all_1_15_1_19 diff --git a/tests/queries/0_stateless/01168_mutations_isolation.sh b/tests/queries/0_stateless/01168_mutations_isolation.sh index 9791e2aa079..d4d76e91ee0 100755 --- a/tests/queries/0_stateless/01168_mutations_isolation.sh +++ b/tests/queries/0_stateless/01168_mutations_isolation.sh @@ -49,9 +49,36 @@ tx 6 "select 6, n, _part from mt orde tx 5 "rollback" tx 6 "insert into mt values (8)" tx 6 "alter table mt update n=n*10 where 1" +tx 6 "insert into mt values (40)" tx 6 "commit" -tx 8 "begin transaction" -tx 8 "select 7, n, _part from mt order by n" + +tx 7 "begin transaction" +tx 7 "select 7, n, _part from mt order by n" +tx 8 "begin transaction" +tx 8 "alter table mt update n = 0 where 1" +$CLICKHOUSE_CLIENT -q "kill mutation where database=currentDatabase() and mutation_id='mutation_16.txt' format Null" +tx 7 "optimize table mt final" +tx 7 "select 8, n, _part from mt order by n" +tx 8 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 8 "rollback" +tx 10 "begin transaction" +tx 10 "alter table mt update n = 0 where 1" +tx 7 "alter table mt update n=n+1 where 1" +tx 10 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 10 "rollback" +tx 7 "commit" + + +tx 11 "begin transaction" +tx 11 "select 9, n, _part from mt order by n" +tx 12 "begin transaction" +tx 11 "alter table mt update n=n+1 where 1" +tx 12 "alter table mt update n=n+1 where 1" +tx 11 "commit" >/dev/null +tx 12 "commit" >/dev/null + +tx 11 "begin transaction" +tx 11 "select 10, n, _part from mt order by n" $CLICKHOUSE_CLIENT --database_atomic_wait_for_drop_and_detach_synchronously=0 -q "drop table mt" From 079b08a28a1b9752c4c6062d21e9fa170d832a13 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 13 Dec 2021 14:37:23 +0300 Subject: [PATCH 0130/1647] support nested in json type (wip) --- src/Columns/ColumnObject.cpp | 272 +++++++++--------- src/Columns/ColumnObject.h | 18 +- src/DataTypes/ObjectUtils.cpp | 11 +- .../{JSONDataParser.cpp => DataPath.cpp} | 6 +- src/DataTypes/Serializations/DataPath.h | 217 ++++++++++++++ src/DataTypes/Serializations/JSONDataParser.h | 39 +-- .../Serializations/SerializationObject.cpp | 91 +++--- src/Functions/transform.cpp | 2 +- .../WindowView/WindowViewProxyStorage.h | 5 +- 9 files changed, 438 insertions(+), 223 deletions(-) rename src/DataTypes/Serializations/{JSONDataParser.cpp => DataPath.cpp} (89%) create mode 100644 src/DataTypes/Serializations/DataPath.h diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 67ba7aaf600..43a8e118a89 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -45,10 +45,12 @@ Array createEmptyArrayField(size_t num_dimensions) return array; } -ColumnPtr recreateWithDefaultValues(const ColumnPtr & column) +ColumnPtr recreateColumnWithDefaultValues(const ColumnPtr & column) { if (const auto * column_array = checkAndGetColumn(column.get())) - return ColumnArray::create(recreateWithDefaultValues(column_array->getDataPtr()), IColumn::mutate(column_array->getOffsetsPtr())); + return ColumnArray::create( + recreateColumnWithDefaultValues(column_array->getDataPtr()), + IColumn::mutate(column_array->getOffsetsPtr())); else return column->cloneEmpty()->cloneResized(column->size()); } @@ -171,16 +173,8 @@ public: type_indexes.insert(TypeId>); } - DataTypePtr getScalarType() const - { - - auto res = getLeastSupertype(type_indexes, true); - if (have_nulls) - return makeNullable(res); - - return res; - } - + DataTypePtr getScalarType() const { return getLeastSupertype(type_indexes, true); } + bool haveNulls() const { return have_nulls; } bool needConvertField() const { return field_types.size() > 1; } private: @@ -248,6 +242,17 @@ void ColumnObject::Subcolumn::checkTypes() const void ColumnObject::Subcolumn::insert(Field field) { + FieldVisitorToScalarType to_scalar_type_visitor; + applyVisitor(to_scalar_type_visitor, field); + + auto base_type = to_scalar_type_visitor.getScalarType(); + + if (isNothing(base_type) && (!is_nullable || !to_scalar_type_visitor.haveNulls())) + { + insertDefault(); + return; + } + auto column_dim = getNumberOfDimensions(*least_common_type); auto value_dim = applyVisitor(FieldVisitorToNumberOfDimensions(), field); @@ -259,39 +264,24 @@ void ColumnObject::Subcolumn::insert(Field field) if (value_dim != column_dim) throw Exception(ErrorCodes::NUMBER_OF_DIMENSIONS_MISMATHED, - "Dimension of types mismatched between inserted value and column." + "Dimension of types mismatched between inserted value and column. " "Dimension of value: {}. Dimension of column: {}", value_dim, column_dim); - FieldVisitorToScalarType to_scalar_type_visitor; - applyVisitor(to_scalar_type_visitor, field); - - auto base_type = to_scalar_type_visitor.getScalarType(); - if (isNothing(base_type)) - { - insertDefault(); - return; - } - - if (is_nullable && !base_type->isNullable()) + if (is_nullable) base_type = makeNullable(base_type); DataTypePtr value_type; - if (!is_nullable && base_type->isNullable()) + if (!is_nullable && to_scalar_type_visitor.haveNulls()) { - base_type = removeNullable(base_type); - if (isNothing(base_type)) - { - insertDefault(); - return; - } - - value_type = createArrayOfType(base_type, value_dim); auto default_value = base_type->getDefault(); + value_type = createArrayOfType(base_type, value_dim); field = applyVisitor(FieldVisitorReplaceNull(default_value, value_dim), std::move(field)); } else + { value_type = createArrayOfType(base_type, value_dim); + } bool type_changed = false; @@ -394,17 +384,6 @@ void ColumnObject::Subcolumn::finalize() num_of_defaults_in_prefix = 0; } -Field ColumnObject::Subcolumn::getLastField() const -{ - if (data.empty()) - return Field(); - - const auto & last_part = data.back(); - assert(!last_part.empty()); - return (*last_part)[last_part->size() - 1]; -} - - void ColumnObject::Subcolumn::insertDefault() { if (data.empty()) @@ -421,6 +400,56 @@ void ColumnObject::Subcolumn::insertManyDefaults(size_t length) data.back()->insertManyDefaults(length); } +void ColumnObject::Subcolumn::popBack(size_t n) +{ + assert(n <= size); + + size_t num_removed = 0; + for (auto it = data.rbegin(); it != data.rend(); ++it) + { + if (n == 0) + break; + + auto & column = *it; + if (n < column->size()) + { + column->popBack(n); + n = 0; + } + else + { + ++num_removed; + n -= column->size(); + } + } + + data.resize(data.size() - num_removed); + num_of_defaults_in_prefix -= n; +} + +Field ColumnObject::Subcolumn::getLastField() const +{ + if (data.empty()) + return Field(); + + const auto & last_part = data.back(); + assert(!last_part.empty()); + return (*last_part)[last_part->size() - 1]; +} + +ColumnObject::Subcolumn ColumnObject::Subcolumn::recreateWithDefaultValues() const +{ + Subcolumn new_subcolumn; + new_subcolumn.least_common_type = least_common_type; + new_subcolumn.num_of_defaults_in_prefix = num_of_defaults_in_prefix; + new_subcolumn.data.reserve(data.size()); + + for (const auto & part : data) + new_subcolumn.data.push_back(recreateColumnWithDefaultValues(part)); + + return new_subcolumn; +} + IColumn & ColumnObject::Subcolumn::getFinalizedColumn() { assert(isFinalized()); @@ -444,7 +473,7 @@ ColumnObject::ColumnObject(bool is_nullable_) { } -ColumnObject::ColumnObject(SubcolumnsMap && subcolumns_, bool is_nullable_) +ColumnObject::ColumnObject(SubcolumnsTree && subcolumns_, bool is_nullable_) : subcolumns(std::move(subcolumns_)) , is_nullable(is_nullable_) { @@ -456,14 +485,16 @@ void ColumnObject::checkConsistency() const if (subcolumns.empty()) return; - size_t first_size = subcolumns.begin()->second.size(); - for (const auto & [key, column] : subcolumns) + const auto & first_leaf = *subcolumns.begin(); + size_t first_size = first_leaf->column.size(); + + for (const auto & leaf : subcolumns) { - if (first_size != column.size()) + if (first_size != leaf->column.size()) { throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of subcolumns are inconsistent in ColumnObject." " Subcolumn '{}' has {} rows, subcolumn '{}' has {} rows", - subcolumns.begin()->first.getPath(), first_size, key.getPath(), column.size()); + first_leaf->path.getPath(), first_size, leaf->path.getPath(), leaf->column.size()); } } } @@ -473,7 +504,7 @@ size_t ColumnObject::size() const #ifndef NDEBUG checkConsistency(); #endif - return subcolumns.empty() ? 0 : subcolumns.begin()->second.size(); + return subcolumns.empty() ? 0 : (*subcolumns.begin())->column.size(); } MutableColumnPtr ColumnObject::cloneResized(size_t new_size) const @@ -488,16 +519,16 @@ MutableColumnPtr ColumnObject::cloneResized(size_t new_size) const size_t ColumnObject::byteSize() const { size_t res = 0; - for (const auto & [_, column] : subcolumns) - res += column.byteSize(); + for (const auto & entry : subcolumns) + res += entry->column.byteSize(); return res; } size_t ColumnObject::allocatedBytes() const { size_t res = 0; - for (const auto & [_, column] : subcolumns) - res += column.allocatedBytes(); + for (const auto & entry : subcolumns) + res += entry->column.allocatedBytes(); return res; } @@ -506,8 +537,8 @@ void ColumnObject::forEachSubcolumn(ColumnCallback callback) if (!isFinalized()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot iterate over non-finalized ColumnObject"); - for (auto & [_, column] : subcolumns) - callback(column.data.back()); + for (auto & entry : subcolumns) + callback(entry->column.data.back()); } void ColumnObject::insert(const Field & field) @@ -527,15 +558,15 @@ void ColumnObject::insert(const Field & field) subcolumn.insert(value); } - for (auto & [key, subcolumn] : subcolumns) - if (!inserted.has(key.getPath())) - subcolumn.insertDefault(); + for (auto & entry : subcolumns) + if (!inserted.has(entry->path.getPath())) + entry->column.insertDefault(); } void ColumnObject::insertDefault() { - for (auto & [_, subcolumn] : subcolumns) - subcolumn.insertDefault(); + for (auto & entry : subcolumns) + entry->column.insertDefault(); } Field ColumnObject::operator[](size_t n) const @@ -544,8 +575,8 @@ Field ColumnObject::operator[](size_t n) const throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot get Field from non-finalized ColumnObject"); Object object; - for (const auto & [key, subcolumn] : subcolumns) - object[key.getPath()] = (*subcolumn.data.back())[n]; + for (const auto & entry : subcolumns) + object[entry->path.getPath()] = (*entry->column.data.back())[n]; return object; } @@ -556,10 +587,10 @@ void ColumnObject::get(size_t n, Field & res) const throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot get Field from non-finalized ColumnObject"); auto & object = res.get(); - for (const auto & [key, subcolumn] : subcolumns) + for (const auto & entry : subcolumns) { - auto it = object.try_emplace(key.getPath()).first; - subcolumn.data.back()->get(n, it->second); + auto it = object.try_emplace(entry->path.getPath()).first; + entry->column.data.back()->get(n, it->second); } } @@ -567,12 +598,12 @@ void ColumnObject::insertRangeFrom(const IColumn & src, size_t start, size_t len { const auto & src_object = assert_cast(src); - for (auto & [name, subcolumn] : subcolumns) + for (auto & entry : subcolumns) { - if (src_object.hasSubcolumn(name)) - subcolumn.insertRangeFrom(src_object.getSubcolumn(name), start, length); + if (src_object.hasSubcolumn(entry->path)) + entry->column.insertRangeFrom(src_object.getSubcolumn(entry->path), start, length); else - subcolumn.insertManyDefaults(length); + entry->column.insertManyDefaults(length); } finalize(); @@ -584,10 +615,10 @@ ColumnPtr ColumnObject::replicate(const Offsets & offsets) const throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot replicate non-finalized ColumnObject"); auto res_column = ColumnObject::create(is_nullable); - for (const auto & [key, subcolumn] : subcolumns) + for (const auto & entry : subcolumns) { - auto replicated_data = subcolumn.data.back()->replicate(offsets)->assumeMutable(); - res_column->addSubcolumn(key, std::move(replicated_data), is_nullable); + auto replicated_data = entry->column.data.back()->replicate(offsets)->assumeMutable(); + res_column->addSubcolumn(entry->path, std::move(replicated_data), is_nullable); } return res_column; @@ -595,128 +626,105 @@ ColumnPtr ColumnObject::replicate(const Offsets & offsets) const void ColumnObject::popBack(size_t length) { - if (!isFinalized()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot popBack from non-finalized ColumnObject"); - - for (auto & [_, subcolumn] : subcolumns) - subcolumn.data.back()->popBack(length); + for (auto & entry : subcolumns) + entry->column.popBack(length); } const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const Path & key) const { - auto it = subcolumns.find(key); - if (it != subcolumns.end()) - return it->second; + if (const auto * node = subcolumns.findLeaf(key)) + return node->column; throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key.getPath()); } ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const Path & key) { - auto it = subcolumns.find(key); - if (it != subcolumns.end()) - return it->second; + if (const auto * node = subcolumns.findLeaf(key)) + return const_cast(node)->column; throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key.getPath()); } -std::optional ColumnObject::findSubcolumnForNested(const Path & key, size_t expected_size) const -{ - for (const auto & [other_key, other_subcolumn] : subcolumns) - { - if (key == other_key || expected_size != other_subcolumn.size()) - continue; - - auto split_lhs = Nested::splitName(key.getPath(), true); - auto split_rhs = Nested::splitName(other_key.getPath(), true); - - if (!split_lhs.first.empty() && split_lhs.first == split_rhs.first) - return other_key; - } - - return {}; -} - bool ColumnObject::hasSubcolumn(const Path & key) const { - return subcolumns.count(key) != 0; + return subcolumns.findLeaf(key) != nullptr; } void ColumnObject::addSubcolumn(const Path & key, size_t new_size, bool check_size) { - if (subcolumns.count(key)) - throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); - if (check_size && new_size != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", key.getPath(), new_size, size()); + bool inserted = false; if (key.hasNested()) { - auto nested_key = findSubcolumnForNested(key, new_size); - if (nested_key) + const auto * nested_node = subcolumns.findBestMatch(key); + if (nested_node && nested_node->isNested()) { - auto & nested_subcolumn = subcolumns[*nested_key]; - nested_subcolumn.finalize(); - auto default_column = recreateWithDefaultValues(nested_subcolumn.getFinalizedColumnPtr()); - subcolumns[key] = Subcolumn(default_column->assumeMutable(), is_nullable); - } - else - { - subcolumns[key] = Subcolumn(new_size, is_nullable); + const auto * leaf = subcolumns.findLeaf(nested_node, [&](const auto & ) { return true; }); + assert(leaf); + + auto new_subcolumn = leaf->column.recreateWithDefaultValues(); + if (new_subcolumn.size() == new_size + 1) + new_subcolumn.popBack(1); + + inserted = subcolumns.add(key, new_subcolumn); } } - else - { - subcolumns[key] = Subcolumn(new_size, is_nullable); - } + + if (!inserted) + inserted = subcolumns.add(key, Subcolumn(new_size, is_nullable)); + + if (!inserted) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); } void ColumnObject::addSubcolumn(const Path & key, MutableColumnPtr && subcolumn, bool check_size) { - if (subcolumns.count(key)) - throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); - if (check_size && subcolumn->size() != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", key.getPath(), subcolumn->size(), size()); - subcolumns[key] = Subcolumn(std::move(subcolumn), is_nullable); + bool inserted = subcolumns.add(key, Subcolumn(std::move(subcolumn), is_nullable)); + if (!inserted) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); } Paths ColumnObject::getKeys() const { Paths keys; keys.reserve(subcolumns.size()); - for (const auto & [key, _] : subcolumns) - keys.emplace_back(key); + for (const auto & entry : subcolumns) + keys.emplace_back(entry->path); return keys; } bool ColumnObject::isFinalized() const { return std::all_of(subcolumns.begin(), subcolumns.end(), - [](const auto & elem) { return elem.second.isFinalized(); }); + [](const auto & entry) { return entry->column.isFinalized(); }); } void ColumnObject::finalize() { size_t old_size = size(); - SubcolumnsMap new_subcolumns; - for (auto && [name, subcolumn] : subcolumns) + SubcolumnsTree new_subcolumns; + for (auto && entry : subcolumns) { - const auto & least_common_type = subcolumn.getLeastCommonType(); + const auto & least_common_type = entry->column.getLeastCommonType(); if (isNothing(getBaseTypeOfArray(least_common_type))) continue; - subcolumn.finalize(); - new_subcolumns[name] = std::move(subcolumn); + entry->column.finalize(); + new_subcolumns.add(entry->path, std::move(entry->column)); } if (new_subcolumns.empty()) - new_subcolumns[Path(COLUMN_NAME_DUMMY)] = Subcolumn{ColumnUInt8::create(old_size), is_nullable}; + new_subcolumns.add(Path{COLUMN_NAME_DUMMY}, Subcolumn{ColumnUInt8::create(old_size), is_nullable}); std::swap(subcolumns, new_subcolumns); checkObjectHasNoAmbiguosPaths(getKeys()); diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 038d1317ff3..41cdea125a0 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -39,10 +40,12 @@ public: void insertDefault(); void insertManyDefaults(size_t length); void insertRangeFrom(const Subcolumn & src, size_t start, size_t length); + void popBack(size_t n); void finalize(); Field getLastField() const; + Subcolumn recreateWithDefaultValues() const; IColumn & getFinalizedColumn(); const IColumn & getFinalizedColumn() const; @@ -57,17 +60,19 @@ public: size_t num_of_defaults_in_prefix = 0; }; - using SubcolumnsMap = std::unordered_map; + // using SubcolumnsMap = std::unordered_map; + using SubcolumnsTree = SubcolumnsTree; private: - SubcolumnsMap subcolumns; + // SubcolumnsMap subcolumns; + SubcolumnsTree subcolumns; bool is_nullable; public: static constexpr auto COLUMN_NAME_DUMMY = "_dummy"; explicit ColumnObject(bool is_nullable_); - ColumnObject(SubcolumnsMap && subcolumns_, bool is_nullable_); + ColumnObject(SubcolumnsTree && subcolumns_, bool is_nullable_); void checkConsistency() const; @@ -75,13 +80,12 @@ public: const Subcolumn & getSubcolumn(const Path & key) const; Subcolumn & getSubcolumn(const Path & key); - std::optional findSubcolumnForNested(const Path & key, size_t expected_size) const; void addSubcolumn(const Path & key, size_t new_size, bool check_size = false); void addSubcolumn(const Path & key, MutableColumnPtr && subcolumn, bool check_size = false); - const SubcolumnsMap & getSubcolumns() const { return subcolumns; } - SubcolumnsMap & getSubcolumns() { return subcolumns; } + const SubcolumnsTree & getSubcolumns() const { return subcolumns; } + SubcolumnsTree & getSubcolumns() { return subcolumns; } Paths getKeys() const; bool isFinalized() const; @@ -134,7 +138,7 @@ public: void getIndicesOfNonDefaultRows(Offsets &, size_t, size_t) const override { throwMustBeConcrete(); } private: - [[noreturn]] void throwMustBeConcrete() const + [[noreturn]] static void throwMustBeConcrete() { throw Exception("ColumnObject must be converted to ColumnTuple before use", ErrorCodes::LOGICAL_ERROR); } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 76b29ef8ccf..3db5b679757 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -123,10 +124,10 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con std::vector> tuple_elements; tuple_elements.reserve(subcolumns_map.size()); - for (const auto & [key, subcolumn] : subcolumns_map) - tuple_elements.emplace_back(key, - subcolumn.getLeastCommonType(), - subcolumn.getFinalizedColumnPtr()); + for (const auto & entry : subcolumns_map) + tuple_elements.emplace_back(entry->path, + entry->column.getLeastCommonType(), + entry->column.getFinalizedColumnPtr()); std::sort(tuple_elements.begin(), tuple_elements.end(), [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs).getPath() < std::get<0>(rhs).getPath(); }); @@ -179,7 +180,7 @@ void checkObjectHasNoAmbiguosPaths(const Paths & paths) { if (isPrefix(names_parts[i], names_parts[j]) || isPrefix(names_parts[j], names_parts[i])) throw Exception(ErrorCodes::DUPLICATE_COLUMN, - "Data in Object has ambiguous paths: '{}' and '{}", + "Data in Object has ambiguous paths: '{}' and '{}'", paths[i].getPath(), paths[i].getPath()); } } diff --git a/src/DataTypes/Serializations/JSONDataParser.cpp b/src/DataTypes/Serializations/DataPath.cpp similarity index 89% rename from src/DataTypes/Serializations/JSONDataParser.cpp rename to src/DataTypes/Serializations/DataPath.cpp index a91b205e96d..51a007eb1a9 100644 --- a/src/DataTypes/Serializations/JSONDataParser.cpp +++ b/src/DataTypes/Serializations/DataPath.cpp @@ -1,5 +1,9 @@ -#include +#include #include +#include +#include +#include +#include #include #include diff --git a/src/DataTypes/Serializations/DataPath.h b/src/DataTypes/Serializations/DataPath.h new file mode 100644 index 00000000000..35b97d5f938 --- /dev/null +++ b/src/DataTypes/Serializations/DataPath.h @@ -0,0 +1,217 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace DB +{ + +class ReadBuffer; +class WriteBuffer; + +class Path +{ +public: + /// TODO: use dynamic bitset + using BitSet = std::bitset<64>; + + Path() = default; + explicit Path(std::string_view path_); + + void append(const Path & other); + void append(std::string_view key); + + const String & getPath() const { return path; } + bool isNested(size_t i) const { return is_nested.test(i); } + bool hasNested() const { return is_nested.any(); } + BitSet getIsNestedBitSet() const { return is_nested; } + + size_t getNumParts() const { return num_parts; } + bool empty() const { return path.empty(); } + + Strings getParts() const; + + static Path getNext(const Path & current_path, const Path & key, bool make_nested = false); + + void writeBinary(WriteBuffer & out) const; + void readBinary(ReadBuffer & in); + + bool operator==(const Path & other) const { return path == other.path; } + bool operator!=(const Path & other) const { return !(*this == other); } + struct Hash { size_t operator()(const Path & value) const; }; + +private: + String path; + size_t num_parts = 0; + BitSet is_nested; +}; + +using Paths = std::vector; + +template +class SubcolumnsTree +{ +public: + struct Node + { + enum Kind + { + TUPLE, + NESTED, + SCALAR, + }; + + Kind kind = TUPLE; + const Node * parent = nullptr; + std::unordered_map> children; + + bool isNested() const { return kind == NESTED; } + + std::shared_ptr addChild(const String & key_, Kind kind_) + { + auto next_node = kind_ == SCALAR ? std::make_shared() : std::make_shared(); + next_node->kind = kind_; + next_node->parent = this; + children[key_] = next_node; + return next_node; + } + + virtual ~Node() = default; + }; + + struct Leaf : public Node + { + Path path; + ColumnHolder column; + }; + + using NodePtr = std::shared_ptr; + using LeafPtr = std::shared_ptr; + + bool add(const Path & path, const ColumnHolder & column) + { + auto parts = path.getParts(); + auto is_nested = path.getIsNestedBitSet(); + + if (parts.empty()) + return false; + + if (!root) + { + root = std::make_shared(); + root->kind = Node::TUPLE; + } + + Node * current_node = root.get(); + for (size_t i = 0; i < parts.size() - 1; ++i) + { + assert(current_node->kind != Node::SCALAR); + + auto it = current_node->children.find(parts[i]); + if (it != current_node->children.end()) + { + current_node = it->second.get(); + bool current_node_is_nested = current_node->kind == Node::NESTED; + + if (current_node_is_nested != is_nested.test(i)) + return false; + } + else + { + auto next_kind = is_nested.test(i) ? Node::NESTED : Node::TUPLE; + current_node = current_node->addChild(parts[i], next_kind).get(); + } + } + + auto it = current_node->children.find(parts.back()); + if (it != current_node->children.end()) + return false; + + auto node = current_node->addChild(parts.back(), Node::SCALAR); + auto leaf = std::dynamic_pointer_cast(node); + assert(leaf); + + leaf->path = path; + leaf->column = column; + leaves.push_back(std::move(leaf)); + + return true; + } + + const Node * findBestMatch(const Path & path) const + { + if (!root) + return nullptr; + + auto parts = path.getParts(); + const Node * current_node = root.get(); + + for (const auto & part : parts) + { + auto it = current_node->children.find(part); + if (it == current_node->children.end()) + return current_node; + + current_node = it->second.get(); + } + + return current_node; + } + + const Leaf * findLeaf(const Path & path) const + { + return typeid_cast(findBestMatch(path)); + } + + using LeafPredicate = std::function; + + const Leaf * findLeaf(const LeafPredicate & predicate) + { + return findLeaf(root.get(), predicate); + } + + static const Leaf * findLeaf(const Node * node, const LeafPredicate & predicate) + { + if (const auto * leaf = typeid_cast(node)) + return predicate(*leaf) ? leaf : nullptr; + + for (const auto & [_, child] : node->children) + if (const auto * leaf = findLeaf(child.get(), predicate)) + return leaf; + + return nullptr; + } + + using NodePredicate = std::function; + + static const Node * findParent(const Node * node, const NodePredicate & predicate) + { + while (node && !predicate(*node)) + node = node->parent; + return node; + } + + bool empty() const { return root == nullptr; } + size_t size() const { return leaves.size(); } + + using Leaves = std::vector; + const Leaves & getLeaves() const { return leaves; } + + using iterator = typename Leaves::iterator; + using const_iterator = typename Leaves::const_iterator; + + iterator begin() { return leaves.begin(); } + iterator end() { return leaves.end(); } + + const_iterator begin() const { return leaves.begin(); } + const_iterator end() const { return leaves.end(); } + +private: + NodePtr root; + Leaves leaves; +}; + +} diff --git a/src/DataTypes/Serializations/JSONDataParser.h b/src/DataTypes/Serializations/JSONDataParser.h index 3be7d461981..5e2acb5e824 100644 --- a/src/DataTypes/Serializations/JSONDataParser.h +++ b/src/DataTypes/Serializations/JSONDataParser.h @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace DB @@ -17,44 +18,6 @@ namespace ErrorCodes class ReadBuffer; class WriteBuffer; -class Path -{ -public: - // using BitSet = boost::dynamic_bitset, 8>>; - - Path() = default; - explicit Path(std::string_view path_); - - void append(const Path & other); - void append(std::string_view key); - - const String & getPath() const { return path; } - bool isNested(size_t i) const { return is_nested.test(i); } - bool hasNested() const { return is_nested.any(); } - - size_t getNumParts() const { return num_parts; } - bool empty() const { return path.empty(); } - - Strings getParts() const; - - static Path getNext(const Path & current_path, const Path & key, bool make_nested = false); - - void writeBinary(WriteBuffer & out) const; - void readBinary(ReadBuffer & in); - - bool operator==(const Path & other) const { return path == other.path; } - bool operator!=(const Path & other) const { return !(*this == other); } - struct Hash { size_t operator()(const Path & value) const; }; - -private: - String path; - size_t num_parts = 0; - /// TODO: use dynamic bitset - std::bitset<64> is_nested; -}; - -using Paths = std::vector; - template static Field getValueAsField(const Element & element) { diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index f97571ce463..4f44f9220e3 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include #include @@ -31,7 +33,8 @@ namespace class FieldVisitorReplaceScalars : public StaticVisitor { public: - explicit FieldVisitorReplaceScalars(const Field & replacement_) : replacement(replacement_) + explicit FieldVisitorReplaceScalars(const Field & replacement_) + : replacement(replacement_) { } @@ -54,6 +57,41 @@ private: const Field & replacement; }; +bool tryInsertDefaultFromNested( + ColumnObject::SubcolumnsTree::LeafPtr entry, const ColumnObject::SubcolumnsTree & subcolumns) +{ + if (!entry->path.hasNested()) + return false; + + const auto * node = subcolumns.findLeaf(entry->path); + if (!node) + return false; + + const auto * node_nested = subcolumns.findParent(node, + [](const auto & candidate) { return candidate.isNested(); }); + + if (!node_nested) + return false; + + const auto * leaf = subcolumns.findLeaf(node_nested, + [&](const auto & candidate) + { + return candidate.column.size() == entry->column.size() + 1; + }); + + if (!leaf) + return false; + + auto last_field = leaf->column.getLastField(); + if (last_field.isNull()) + return false; + + auto default_scalar = getBaseTypeOfArray(leaf->column.getLeastCommonType())->getDefault(); + auto default_field = applyVisitor(FieldVisitorReplaceScalars(default_scalar), last_field); + entry->column.insert(std::move(default_field)); + return true; +} + } template @@ -91,34 +129,14 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & subcolumn.insert(std::move(values[i])); } - for (auto & [key, subcolumn] : column_object.getSubcolumns()) + const auto & subcolumns = column_object.getSubcolumns(); + for (const auto & entry : subcolumns) { - if (!paths_set.has(key.getPath())) + if (!paths_set.has(entry->path.getPath())) { - if (key.hasNested()) - { - auto nested_key = column_object.findSubcolumnForNested(key, subcolumn.size() + 1); - - if (nested_key) - { - const auto & subcolumn_nested = column_object.getSubcolumn(*nested_key); - auto last_field = subcolumn_nested.getLastField(); - if (last_field.isNull()) - continue; - - auto default_scalar = getBaseTypeOfArray(subcolumn_nested.getLeastCommonType())->getDefault(); - auto default_field = applyVisitor(FieldVisitorReplaceScalars(default_scalar), last_field); - subcolumn.insert(std::move(default_field)); - } - else - { - subcolumn.insertDefault(); - } - } - else - { - subcolumn.insertDefault(); - } + bool inserted = tryInsertDefaultFromNested(entry, subcolumns); + if (!inserted) + entry->column.insertDefault(); } } } @@ -208,26 +226,27 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( if (auto * stream = settings.getter(settings.path)) writeVarUInt(column_object.getSubcolumns().size(), *stream); - for (const auto & [key, subcolumn] : column_object.getSubcolumns()) + const auto & subcolumns = column_object.getSubcolumns().getLeaves(); + for (const auto & entry : subcolumns) { settings.path.back() = Substream::ObjectStructure; - settings.path.back().object_key_name = key.getPath(); + settings.path.back().object_key_name = entry->path.getPath(); - const auto & type = subcolumn.getLeastCommonType(); + const auto & type = entry->column.getLeastCommonType(); if (auto * stream = settings.getter(settings.path)) { - key.writeBinary(*stream); + entry->path.writeBinary(*stream); writeStringBinary(type->getName(), *stream); } settings.path.back() = Substream::ObjectElement; - settings.path.back().object_key_name = key.getPath(); + settings.path.back().object_key_name = entry->path.getPath(); if (auto * stream = settings.getter(settings.path)) { auto serialization = type->getDefaultSerialization(); serialization->serializeBinaryBulkWithMultipleStreams( - subcolumn.getFinalizedColumn(), offset, limit, settings, state); + entry->column.getFinalizedColumn(), offset, limit, settings, state); } } @@ -335,11 +354,11 @@ void SerializationObject::serializeTextImpl(const IColumn & column, size if (it != subcolumns.begin()) writeCString(",", ostr); - writeDoubleQuoted(it->first.getPath(), ostr); + writeDoubleQuoted((*it)->path.getPath(), ostr); writeChar(':', ostr); - auto serialization = it->second.getLeastCommonType()->getDefaultSerialization(); - serialization->serializeTextJSON(it->second.getFinalizedColumn(), row_num, ostr, settings); + auto serialization = (*it)->column.getLeastCommonType()->getDefaultSerialization(); + serialization->serializeTextJSON((*it)->column.getFinalizedColumn(), row_num, ostr, settings); } writeChar('}', ostr); } diff --git a/src/Functions/transform.cpp b/src/Functions/transform.cpp index a11ba9c7d74..de9f1a5ba05 100644 --- a/src/Functions/transform.cpp +++ b/src/Functions/transform.cpp @@ -117,7 +117,7 @@ public: + " has signature: transform(T, Array(T), Array(U), U) -> U; or transform(T, Array(T), Array(T)) -> T; where T and U are types.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT}; - return getLeastSupertype({type_x, type_arr_to_nested}); + return getLeastSupertype(DataTypes{type_x, type_arr_to_nested}); } else { diff --git a/src/Storages/WindowView/WindowViewProxyStorage.h b/src/Storages/WindowView/WindowViewProxyStorage.h index 55426bddd5d..53443938492 100644 --- a/src/Storages/WindowView/WindowViewProxyStorage.h +++ b/src/Storages/WindowView/WindowViewProxyStorage.h @@ -20,18 +20,17 @@ public: setInMemoryMetadata(storage_metadata); } -public: std::string getName() const override { return "WindowViewProxy"; } QueryProcessingStage::Enum - getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageMetadataPtr &, SelectQueryInfo &) const override + getQueryProcessingStage(ContextPtr, QueryProcessingStage::Enum, const StorageSnapshotPtr &, SelectQueryInfo &) const override { return to_stage; } Pipe read( const Names &, - const StorageMetadataPtr & /*metadata_snapshot*/, + const StorageSnapshotPtr & /*storage_snapshot*/, SelectQueryInfo & /*query_info*/, ContextPtr /*context*/, QueryProcessingStage::Enum /*processed_stage*/, From a70e5c1b2d7f254c8282c340a117d823bb58e72f Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 17 Dec 2021 18:08:34 +0300 Subject: [PATCH 0131/1647] support nested in json type (wip) --- src/Columns/ColumnObject.cpp | 73 +++++++++++++------ src/Columns/ColumnObject.h | 18 ++++- src/DataTypes/Serializations/DataPath.h | 43 +++++++---- .../Serializations/SerializationObject.cpp | 21 +++--- 4 files changed, 107 insertions(+), 48 deletions(-) diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 43a8e118a89..6d0635f1218 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -185,6 +185,20 @@ private: } +FieldInfo getFieldInfo(const Field & field) +{ + FieldVisitorToScalarType to_scalar_type_visitor; + applyVisitor(to_scalar_type_visitor, field); + + return + { + to_scalar_type_visitor.getScalarType(), + to_scalar_type_visitor.haveNulls(), + to_scalar_type_visitor.needConvertField(), + applyVisitor(FieldVisitorToNumberOfDimensions(), field), + }; +} + ColumnObject::Subcolumn::Subcolumn(MutableColumnPtr && data_, bool is_nullable_) : least_common_type(getDataTypeByColumn(*data_)) , is_nullable(is_nullable_) @@ -242,19 +256,22 @@ void ColumnObject::Subcolumn::checkTypes() const void ColumnObject::Subcolumn::insert(Field field) { - FieldVisitorToScalarType to_scalar_type_visitor; - applyVisitor(to_scalar_type_visitor, field); + auto info = getFieldInfo(field); + insert(std::move(field), std::move(info)); +} - auto base_type = to_scalar_type_visitor.getScalarType(); +void ColumnObject::Subcolumn::insert(Field field, FieldInfo info) +{ + auto base_type = info.scalar_type; - if (isNothing(base_type) && (!is_nullable || !to_scalar_type_visitor.haveNulls())) + if (isNothing(base_type) && (!is_nullable || !info.have_nulls)) { insertDefault(); return; } auto column_dim = getNumberOfDimensions(*least_common_type); - auto value_dim = applyVisitor(FieldVisitorToNumberOfDimensions(), field); + auto value_dim = info.num_dimensions; if (isNothing(least_common_type)) column_dim = value_dim; @@ -263,7 +280,6 @@ void ColumnObject::Subcolumn::insert(Field field) value_dim = column_dim; if (value_dim != column_dim) - throw Exception(ErrorCodes::NUMBER_OF_DIMENSIONS_MISMATHED, "Dimension of types mismatched between inserted value and column. " "Dimension of value: {}. Dimension of column: {}", value_dim, column_dim); @@ -272,7 +288,7 @@ void ColumnObject::Subcolumn::insert(Field field) base_type = makeNullable(base_type); DataTypePtr value_type; - if (!is_nullable && to_scalar_type_visitor.haveNulls()) + if (!is_nullable && info.have_nulls) { auto default_value = base_type->getDefault(); value_type = createArrayOfType(base_type, value_dim); @@ -301,7 +317,7 @@ void ColumnObject::Subcolumn::insert(Field field) } } - if (type_changed || to_scalar_type_visitor.needConvertField()) + if (type_changed || info.need_convert) { auto converted_field = convertFieldToTypeOrThrow(std::move(field), *value_type); data.back()->insert(std::move(converted_field)); @@ -441,6 +457,7 @@ ColumnObject::Subcolumn ColumnObject::Subcolumn::recreateWithDefaultValues() con { Subcolumn new_subcolumn; new_subcolumn.least_common_type = least_common_type; + new_subcolumn.is_nullable = is_nullable; new_subcolumn.num_of_defaults_in_prefix = num_of_defaults_in_prefix; new_subcolumn.data.reserve(data.size()); @@ -470,12 +487,15 @@ const ColumnPtr & ColumnObject::Subcolumn::getFinalizedColumnPtr() const ColumnObject::ColumnObject(bool is_nullable_) : is_nullable(is_nullable_) + , num_rows(0) { } ColumnObject::ColumnObject(SubcolumnsTree && subcolumns_, bool is_nullable_) - : subcolumns(std::move(subcolumns_)) - , is_nullable(is_nullable_) + : is_nullable(is_nullable_) + , subcolumns(std::move(subcolumns_)) + , num_rows(subcolumns.empty() ? 0 : (*subcolumns.begin())->column.size()) + { checkConsistency(); } @@ -485,16 +505,13 @@ void ColumnObject::checkConsistency() const if (subcolumns.empty()) return; - const auto & first_leaf = *subcolumns.begin(); - size_t first_size = first_leaf->column.size(); - for (const auto & leaf : subcolumns) { - if (first_size != leaf->column.size()) + if (num_rows != leaf->column.size()) { throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of subcolumns are inconsistent in ColumnObject." - " Subcolumn '{}' has {} rows, subcolumn '{}' has {} rows", - first_leaf->path.getPath(), first_size, leaf->path.getPath(), leaf->column.size()); + " Subcolumn '{}' has {} rows, but expected size is {}", + leaf->path.getPath(), leaf->column.size(), num_rows); } } } @@ -504,7 +521,7 @@ size_t ColumnObject::size() const #ifndef NDEBUG checkConsistency(); #endif - return subcolumns.empty() ? 0 : (*subcolumns.begin())->column.size(); + return num_rows; } MutableColumnPtr ColumnObject::cloneResized(size_t new_size) const @@ -543,6 +560,7 @@ void ColumnObject::forEachSubcolumn(ColumnCallback callback) void ColumnObject::insert(const Field & field) { + ++num_rows; const auto & object = field.get(); HashSet inserted; @@ -565,6 +583,7 @@ void ColumnObject::insert(const Field & field) void ColumnObject::insertDefault() { + ++num_rows; for (auto & entry : subcolumns) entry->column.insertDefault(); } @@ -596,6 +615,7 @@ void ColumnObject::get(size_t n, Field & res) const void ColumnObject::insertRangeFrom(const IColumn & src, size_t start, size_t length) { + num_rows += length; const auto & src_object = assert_cast(src); for (auto & entry : subcolumns) @@ -626,6 +646,7 @@ ColumnPtr ColumnObject::replicate(const Offsets & offsets) const void ColumnObject::popBack(size_t length) { + num_rows -= length; for (auto & entry : subcolumns) entry->column.popBack(length); } @@ -653,12 +674,15 @@ bool ColumnObject::hasSubcolumn(const Path & key) const void ColumnObject::addSubcolumn(const Path & key, size_t new_size, bool check_size) { + if (num_rows == 0) + num_rows = new_size; + if (check_size && new_size != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", key.getPath(), new_size, size()); - bool inserted = false; + std::optional inserted; if (key.hasNested()) { const auto * nested_node = subcolumns.findBestMatch(key); @@ -668,22 +692,27 @@ void ColumnObject::addSubcolumn(const Path & key, size_t new_size, bool check_si assert(leaf); auto new_subcolumn = leaf->column.recreateWithDefaultValues(); - if (new_subcolumn.size() == new_size + 1) - new_subcolumn.popBack(1); + if (new_subcolumn.size() > new_size) + new_subcolumn.popBack(new_subcolumn.size() - new_size); + else if (new_subcolumn.size() < new_size) + new_subcolumn.insertManyDefaults(new_size - new_subcolumn.size()); inserted = subcolumns.add(key, new_subcolumn); } } - if (!inserted) + if (!inserted.has_value()) inserted = subcolumns.add(key, Subcolumn(new_size, is_nullable)); - if (!inserted) + if (!*inserted) throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); } void ColumnObject::addSubcolumn(const Path & key, MutableColumnPtr && subcolumn, bool check_size) { + if (num_rows == 0) + num_rows = subcolumn->size(); + if (check_size && subcolumn->size() != size()) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index 41cdea125a0..b202e53c4a1 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -18,6 +18,16 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } +struct FieldInfo +{ + DataTypePtr scalar_type; + bool have_nulls; + bool need_convert; + size_t num_dimensions; +}; + +FieldInfo getFieldInfo(const Field & field); + class ColumnObject final : public COWHelper { public: @@ -37,6 +47,8 @@ public: void checkTypes() const; void insert(Field field); + void insert(Field field, FieldInfo info); + void insertDefault(); void insertManyDefaults(size_t length); void insertRangeFrom(const Subcolumn & src, size_t start, size_t length); @@ -65,8 +77,10 @@ public: private: // SubcolumnsMap subcolumns; + const bool is_nullable; + SubcolumnsTree subcolumns; - bool is_nullable; + size_t num_rows; public: static constexpr auto COLUMN_NAME_DUMMY = "_dummy"; @@ -81,6 +95,8 @@ public: const Subcolumn & getSubcolumn(const Path & key) const; Subcolumn & getSubcolumn(const Path & key); + void incrementNumRows() { ++num_rows; } + void addSubcolumn(const Path & key, size_t new_size, bool check_size = false); void addSubcolumn(const Path & key, MutableColumnPtr && subcolumn, bool check_size = false); diff --git a/src/DataTypes/Serializations/DataPath.h b/src/DataTypes/Serializations/DataPath.h index 35b97d5f938..446419760cf 100644 --- a/src/DataTypes/Serializations/DataPath.h +++ b/src/DataTypes/Serializations/DataPath.h @@ -143,27 +143,17 @@ public: const Node * findBestMatch(const Path & path) const { - if (!root) - return nullptr; + return findImpl(path, false); + } - auto parts = path.getParts(); - const Node * current_node = root.get(); - - for (const auto & part : parts) - { - auto it = current_node->children.find(part); - if (it == current_node->children.end()) - return current_node; - - current_node = it->second.get(); - } - - return current_node; + const Node * findExact(const Path & path) const + { + return findImpl(path, true); } const Leaf * findLeaf(const Path & path) const { - return typeid_cast(findBestMatch(path)); + return typeid_cast(findExact(path)); } using LeafPredicate = std::function; @@ -210,6 +200,27 @@ public: const_iterator end() const { return leaves.end(); } private: + const Node * findImpl(const Path & path, bool find_exact) const + { + if (!root) + return nullptr; + + auto parts = path.getParts(); + const Node * current_node = root.get(); + + for (const auto & part : parts) + { + auto it = current_node->children.find(part); + if (it == current_node->children.end()) + return find_exact ? nullptr : current_node; + + current_node = it->second.get(); + } + + return current_node; + + } + NodePtr root; Leaves leaves; }; diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 4f44f9220e3..3e0b1ad6e17 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -111,22 +111,25 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & assert(paths.size() == values.size()); HashSet paths_set; - for (const auto & path : paths) - paths_set.insert(path.getPath()); - - if (paths.size() != paths_set.size()) - throw Exception(ErrorCodes::INCORRECT_DATA, "Object has ambiguous paths"); - size_t column_size = column_object.size(); + for (size_t i = 0; i < paths.size(); ++i) { + auto field_info = getFieldInfo(values[i]); + if (isNothing(field_info.scalar_type)) + continue; + + if (!paths_set.insert(paths[i].getPath()).second) + throw Exception(ErrorCodes::INCORRECT_DATA, + "Object has ambiguous path: {}", paths[i].getPath()); + if (!column_object.hasSubcolumn(paths[i])) column_object.addSubcolumn(paths[i], column_size); auto & subcolumn = column_object.getSubcolumn(paths[i]); assert(subcolumn.size() == column_size); - subcolumn.insert(std::move(values[i])); + subcolumn.insert(std::move(values[i]), std::move(field_info)); } const auto & subcolumns = column_object.getSubcolumns(); @@ -139,6 +142,8 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & entry->column.insertDefault(); } } + + column_object.incrementNumRows(); } template @@ -240,8 +245,6 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( } settings.path.back() = Substream::ObjectElement; - settings.path.back().object_key_name = entry->path.getPath(); - if (auto * stream = settings.getter(settings.path)) { auto serialization = type->getDefaultSerialization(); From 37ba8004ff1ece618aee91d446a4c7cc23ccfdd7 Mon Sep 17 00:00:00 2001 From: liyang Date: Wed, 8 Dec 2021 02:40:59 +0000 Subject: [PATCH 0132/1647] Speep up mergetree starting up process --- programs/server/Server.cpp | 9 + programs/server/config.xml | 3 + src/CMakeLists.txt | 3 + src/Common/CurrentMetrics.cpp | 1 + src/Common/ProfileEvents.cpp | 8 + src/Functions/checkPartMetaCache.cpp | 157 ++++++++ .../registerFunctionsMiscellaneous.cpp | 2 + src/Interpreters/Context.cpp | 92 +++++ src/Interpreters/Context.h | 29 +- src/Processors/CMakeLists.txt | 4 +- src/Processors/examples/CMakeLists.txt | 2 + .../examples/merge_tree_meta_cache.cpp | 51 +++ src/Storages/MergeTree/IMergeTreeDataPart.cpp | 336 ++++++++++++++++-- src/Storages/MergeTree/IMergeTreeDataPart.h | 58 ++- src/Storages/MergeTree/KeyCondition.cpp | 2 +- src/Storages/MergeTree/KeyCondition.h | 2 + src/Storages/MergeTree/MergeTreeData.cpp | 8 + src/Storages/MergeTree/MergeTreeData.h | 2 + .../MergeTree/MergeTreeDataPartCompact.cpp | 13 + .../MergeTree/MergeTreeDataPartCompact.h | 4 + .../MergeTree/MergeTreeDataPartInMemory.cpp | 11 + .../MergeTree/MergeTreeDataPartInMemory.h | 4 + .../MergeTree/MergeTreeDataPartWide.cpp | 15 +- .../MergeTree/MergeTreeDataPartWide.h | 5 + src/Storages/MergeTree/MergeTreePartition.cpp | 22 +- src/Storages/MergeTree/MergeTreePartition.h | 5 +- src/Storages/MergeTree/PartMetaCache.cpp | 134 +++++++ src/Storages/MergeTree/PartMetaCache.h | 45 +++ .../StorageSystemMergeTreeMetaCache.cpp | 139 ++++++++ .../System/StorageSystemMergeTreeMetaCache.h | 29 ++ src/Storages/System/attachSystemTables.cpp | 2 + .../01233_check_part_meta_cache.reference | 28 ++ .../01233_check_part_meta_cache.sql | 123 +++++++ ..._check_part_meta_cache_in_atomic.reference | 28 ++ .../01233_check_part_meta_cache_in_atomic.sql | 124 +++++++ ...check_part_meta_cache_replicated.reference | 28 ++ ...01233_check_part_meta_cache_replicated.sql | 125 +++++++ ..._meta_cache_replicated_in_atomic.reference | 28 ++ ...k_part_meta_cache_replicated_in_atomic.sql | 125 +++++++ 39 files changed, 1767 insertions(+), 39 deletions(-) create mode 100644 src/Functions/checkPartMetaCache.cpp create mode 100644 src/Processors/examples/merge_tree_meta_cache.cpp create mode 100644 src/Storages/MergeTree/PartMetaCache.cpp create mode 100644 src/Storages/MergeTree/PartMetaCache.h create mode 100644 src/Storages/System/StorageSystemMergeTreeMetaCache.cpp create mode 100644 src/Storages/System/StorageSystemMergeTreeMetaCache.h create mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache.reference create mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache.sql create mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference create mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql create mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference create mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql create mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference create mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index 14075f9fbf2..fd2dc851ae7 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -747,6 +747,15 @@ if (ThreadFuzzer::instance().isEffective()) /// Directory with metadata of tables, which was marked as dropped by Atomic database fs::create_directories(path / "metadata_dropped/"); + + fs::create_directories(path / "rocksdb/"); + } + + + /// initialize meta file cache + { + size_t size = config().getUInt64("meta_file_cache_size", 256 << 20); + global_context->initializeMergeTreeMetaCache(path_str + "/" + "rocksdb", size); } if (config().has("interserver_http_port") && config().has("interserver_https_port")) diff --git a/programs/server/config.xml b/programs/server/config.xml index d88773a3fc4..470c4dfa35f 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -1291,4 +1291,7 @@ --> + + + 268435456 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7124961821e..42cd4f65f60 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -500,6 +500,9 @@ endif () if (USE_ROCKSDB) dbms_target_link_libraries(PUBLIC ${ROCKSDB_LIBRARY}) dbms_target_include_directories(SYSTEM BEFORE PUBLIC ${ROCKSDB_INCLUDE_DIR}) + + target_link_libraries (clickhouse_common_io PUBLIC ${ROCKSDB_LIBRARY}) + target_include_directories (clickhouse_common_io SYSTEM BEFORE PUBLIC ${ROCKSDB_INCLUDE_DIR}) endif() if (USE_LIBPQXX) diff --git a/src/Common/CurrentMetrics.cpp b/src/Common/CurrentMetrics.cpp index 5c9ba177b78..d214952deae 100644 --- a/src/Common/CurrentMetrics.cpp +++ b/src/Common/CurrentMetrics.cpp @@ -78,6 +78,7 @@ M(SyncDrainedConnections, "Number of connections drained synchronously.") \ M(ActiveSyncDrainedConnections, "Number of active connections drained synchronously.") \ M(AsynchronousReadWait, "Number of threads waiting for asynchronous read.") \ + M(ServerStartupSeconds, "Server start seconds") \ namespace CurrentMetrics { diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index 982523a3ef2..8b0144be842 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -274,6 +274,14 @@ M(ThreadPoolReaderPageCacheMissElapsedMicroseconds, "Time spent reading data inside the asynchronous job in ThreadPoolReader - when read was not done from page cache.") \ \ M(AsynchronousReadWaitMicroseconds, "Time spent in waiting for asynchronous reads.") \ + \ + M(RocksdbGet, "Number of rocksdb reads(used for file meta cache)") \ + M(RocksdbPut, "Number of rocksdb puts(used for file meta cache)") \ + M(RocksdbDelete, "Number of rocksdb deletes(used for file meta cache)") \ + M(RocksdbSeek, "Number of rocksdb seeks(used for file meta cache)") \ + M(MergeTreeMetaCacheHit, "Number of times the read of meta file was done from MergeTree meta cache") \ + M(MergeTreeMetaCacheMiss, "Number of times the read of meta file was not done from MergeTree meta cache") \ + \ namespace ProfileEvents diff --git a/src/Functions/checkPartMetaCache.cpp b/src/Functions/checkPartMetaCache.cpp new file mode 100644 index 00000000000..e0479a5fdcc --- /dev/null +++ b/src/Functions/checkPartMetaCache.cpp @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int ILLEGAL_COLUMN; +} + +class FunctionCheckPartMetaCache: public IFunction, WithContext +{ +public: + using uint128 = IMergeTreeDataPart::uint128; + using DataPartPtr = MergeTreeData::DataPartPtr; + using DataPartState = MergeTreeData::DataPartState; + using DataPartStates = MergeTreeData::DataPartStates; + + + static constexpr auto name = "checkPartMetaCache"; + static FunctionPtr create(ContextPtr context_) + { + return std::make_shared(context_); + } + + static constexpr DataPartStates part_states = { + DataPartState::Committed, + DataPartState::Temporary, + DataPartState::PreCommitted, + DataPartState::Outdated, + DataPartState::Deleting, + DataPartState::DeleteOnDestroy + }; + + explicit FunctionCheckPartMetaCache(ContextPtr context_): WithContext(context_) {} + + String getName() const override { return name; } + + bool isDeterministic() const override { return false; } + + bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; } + + bool isDeterministicInScopeOfQuery() const override { return false; } + + size_t getNumberOfArguments() const override { return 2; } + + DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override + { + for (const auto & argument : arguments) + { + if (!isString(argument)) + throw Exception("The argument of function " + getName() + " must have String type", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + DataTypePtr key_type = std::make_unique(); + DataTypePtr state_type = std::make_unique(); + DataTypePtr cache_checksum_type = std::make_unique(32); + DataTypePtr disk_checksum_type = std::make_unique(32); + DataTypePtr match_type = std::make_unique(); + DataTypePtr tuple_type = std::make_unique(DataTypes{key_type, state_type, cache_checksum_type, disk_checksum_type, match_type}); + return std::make_shared(tuple_type); + } + + ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override + { + // get database name + const auto * arg_database = arguments[0].column.get(); + const ColumnString * column_database = checkAndGetColumnConstData(arg_database); + if (! column_database) + throw Exception("The first argument of function " + getName() + " must be constant String", ErrorCodes::ILLEGAL_COLUMN); + String database_name = column_database->getDataAt(0).toString(); + + // get table name + const auto * arg_table = arguments[1].column.get(); + const ColumnString * column_table = checkAndGetColumnConstData(arg_table); + if (! column_table) + throw Exception("The second argument of function " + getName() + " must be constant String", ErrorCodes::ILLEGAL_COLUMN); + String table_name = column_table->getDataAt(0).toString(); + + // get storage + StorageID storage_id(database_name, table_name); + auto storage = DatabaseCatalog::instance().getTable(storage_id, getContext()); + auto data = std::dynamic_pointer_cast(storage); + if (! data) + throw Exception("The table in function " + getName() + " must be in MergeTree Family", ErrorCodes::ILLEGAL_COLUMN); + + // fill in result + auto col_result = result_type->createColumn(); + auto& col_arr = assert_cast(*col_result); + col_arr.reserve(1); + auto& col_tuple = assert_cast(col_arr.getData()); + col_tuple.reserve(data->fileNumberOfDataParts(part_states)); + auto& col_key = assert_cast(col_tuple.getColumn(0)); + auto& col_state = assert_cast(col_tuple.getColumn(1)); + auto& col_cache_checksum = assert_cast(col_tuple.getColumn(2)); + auto& col_disk_checksum = assert_cast(col_tuple.getColumn(3)); + auto& col_match = assert_cast(col_tuple.getColumn(4)); + auto parts = data->getDataParts(part_states); + for (const auto & part : parts) + executePart(part, col_key, col_state, col_cache_checksum, col_disk_checksum, col_match); + col_arr.getOffsets().push_back(col_tuple.size()); + return result_type->createColumnConst(input_rows_count, col_arr[0]); + } + + static void executePart(const DataPartPtr& part, ColumnString& col_key, ColumnString& col_state, + ColumnFixedString& col_cache_checksum, ColumnFixedString& col_disk_checksum, ColumnUInt8& col_match) + { + Strings keys; + auto state_view = part->stateString(); + String state(state_view.data(), state_view.size()); + std::vector cache_checksums; + std::vector disk_checksums; + uint8_t match = 0; + size_t file_number = part->fileNumberOfColumnsChecksumsIndexes(); + keys.reserve(file_number); + cache_checksums.reserve(file_number); + disk_checksums.reserve(file_number); + + part->checkMetaCache(keys, cache_checksums, disk_checksums); + for (size_t i = 0; i < keys.size(); ++i) + { + col_key.insert(keys[i]); + col_state.insert(state); + col_cache_checksum.insert(getHexUIntUppercase(cache_checksums[i].first) + getHexUIntUppercase(cache_checksums[i].second)); + col_disk_checksum.insert(getHexUIntUppercase(disk_checksums[i].first) + getHexUIntUppercase(disk_checksums[i].second)); + + match = cache_checksums[i] == disk_checksums[i] ? 1 : 0; + col_match.insertValue(match); + } + } +}; + +void registerFunctionCheckPartMetaCache(FunctionFactory & factory) +{ + factory.registerFunction(); +} + +} diff --git a/src/Functions/registerFunctionsMiscellaneous.cpp b/src/Functions/registerFunctionsMiscellaneous.cpp index 76d61ce509a..d613d7c85bd 100644 --- a/src/Functions/registerFunctionsMiscellaneous.cpp +++ b/src/Functions/registerFunctionsMiscellaneous.cpp @@ -80,6 +80,7 @@ void registerFunctionInitialQueryID(FunctionFactory & factory); void registerFunctionServerUUID(FunctionFactory &); void registerFunctionZooKeeperSessionUptime(FunctionFactory &); void registerFunctionGetOSKernelVersion(FunctionFactory &); +void registerFunctionCheckPartMetaCache(FunctionFactory &); #if USE_ICU void registerFunctionConvertCharset(FunctionFactory &); @@ -166,6 +167,7 @@ void registerFunctionsMiscellaneous(FunctionFactory & factory) registerFunctionServerUUID(factory); registerFunctionZooKeeperSessionUptime(factory); registerFunctionGetOSKernelVersion(factory); + registerFunctionCheckPartMetaCache(factory); #if USE_ICU registerFunctionConvertCharset(factory); diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index db1d6a37877..81db45c6461 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -94,6 +94,11 @@ namespace fs = std::filesystem; namespace ProfileEvents { extern const Event ContextLock; + extern const Event CompiledCacheSizeBytes; + extern const Event RocksdbPut; + extern const Event RocksdbGet; + extern const Event RocksdbDelete; + extern const Event RocksdbSeek; } namespace CurrentMetrics @@ -126,6 +131,7 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; extern const int INVALID_SETTING_VALUE; extern const int UNKNOWN_READ_METHOD; + extern const int SYSTEM_ERROR; } @@ -272,6 +278,9 @@ struct ContextSharedPart Context::ConfigReloadCallback config_reload_callback; + /// MergeTree metadata cache stored in rocksdb. + MergeTreeMetaCachePtr merge_tree_meta_cache; + ContextSharedPart() : access_control(std::make_unique()), macros(std::make_unique()) { @@ -382,6 +391,13 @@ struct ContextSharedPart trace_collector.reset(); /// Stop zookeeper connection zookeeper.reset(); + + /// Shutdown meta file cache + if (merge_tree_meta_cache) + { + merge_tree_meta_cache->shutdown(); + merge_tree_meta_cache.reset(); + } } /// Can be removed w/o context lock @@ -425,6 +441,57 @@ SharedContextHolder::SharedContextHolder(std::unique_ptr shar void SharedContextHolder::reset() { shared.reset(); } +MergeTreeMetaCache::Status MergeTreeMetaCache::put(const String & key, const String & value) +{ + auto options = rocksdb::WriteOptions(); + auto status = rocksdb->Put(options, key, value); + ProfileEvents::increment(ProfileEvents::RocksdbPut); + return status; +} + +MergeTreeMetaCache::Status MergeTreeMetaCache::del(const String & key) +{ + auto options = rocksdb::WriteOptions(); + auto status = rocksdb->Delete(options, key); + ProfileEvents::increment(ProfileEvents::RocksdbDelete); + LOG_TRACE(log, "Delete key:{} from MergeTreeMetaCache status:{}", key, status.ToString()); + return status; +} + +MergeTreeMetaCache::Status MergeTreeMetaCache::get(const String & key, String & value) +{ + auto status = rocksdb->Get(rocksdb::ReadOptions(), key, &value); + ProfileEvents::increment(ProfileEvents::RocksdbGet); + LOG_TRACE(log, "Get key:{} from MergeTreeMetaCache status:{}", key, status.ToString()); + return status; +} + +void MergeTreeMetaCache::getByPrefix(const String & prefix, Strings & keys, Strings & values) +{ + auto * it = rocksdb->NewIterator(rocksdb::ReadOptions()); + rocksdb::Slice target(prefix); + for (it->Seek(target); it->Valid(); it->Next()) + { + const auto key = it->key(); + if (!key.starts_with(target)) + break; + + const auto value = it->value(); + keys.emplace_back(key.data(), key.size()); + values.emplace_back(value.data(), value.size()); + } + LOG_TRACE(log, "Seek with prefix:{} from MergeTreeMetaCache items:{}", prefix, keys.size()); + ProfileEvents::increment(ProfileEvents::RocksdbSeek); +} + +void MergeTreeMetaCache::shutdown() +{ + if (rocksdb) + { + rocksdb->Close(); + } +} + ContextMutablePtr Context::createGlobal(ContextSharedPart * shared) { auto res = std::shared_ptr(new Context); @@ -2005,6 +2072,11 @@ zkutil::ZooKeeperPtr Context::getAuxiliaryZooKeeper(const String & name) const return zookeeper->second; } +MergeTreeMetaCachePtr Context::getMergeTreeMetaCache() const +{ + return shared->merge_tree_meta_cache; +} + void Context::resetZooKeeper() const { std::lock_guard lock(shared->zookeeper_mutex); @@ -2237,6 +2309,26 @@ void Context::initializeTraceCollector() shared->initializeTraceCollector(getTraceLog()); } +void Context::initializeMergeTreeMetaCache(const String & dir, size_t size) +{ + rocksdb::Options options; + rocksdb::BlockBasedTableOptions table_options; + rocksdb::DB * db; + + options.create_if_missing = true; + options.statistics = rocksdb::CreateDBStatistics(); + auto cache = rocksdb::NewLRUCache(size); + table_options.block_cache = cache; + options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options)); + rocksdb::Status status = rocksdb::DB::Open(options, dir, &db); + if (status != rocksdb::Status::OK()) + { + String message = "Fail to open rocksdb path at: " + dir + " status:" + status.ToString(); + throw Exception(message, ErrorCodes::SYSTEM_ERROR); + } + shared->merge_tree_meta_cache = std::make_shared(db); +} + bool Context::hasTraceCollector() const { return shared->hasTraceCollector(); diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index 823bc028c15..b39e06b0b0f 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include "config_core.h" @@ -29,7 +31,6 @@ namespace Poco::Net { class IPAddress; } namespace zkutil { class ZooKeeper; } - namespace DB { @@ -178,6 +179,27 @@ private: std::unique_ptr shared; }; +class MergeTreeMetaCache +{ +public: + using Status = rocksdb::Status; + + explicit MergeTreeMetaCache(rocksdb::DB * rocksdb_) : rocksdb{rocksdb_} { } + MergeTreeMetaCache(const MergeTreeMetaCache &) = delete; + MergeTreeMetaCache & operator=(const MergeTreeMetaCache &) = delete; + + Status put(const String & key, const String & value); + Status del(const String & key); + Status get(const String & key, String & value); + void getByPrefix(const String & prefix, Strings & keys, Strings & values); + + void shutdown(); +private: + std::unique_ptr rocksdb; + Poco::Logger * log = &Poco::Logger::get("MergeTreeMetaCache"); +}; +using MergeTreeMetaCachePtr = std::shared_ptr; + /** A set of known objects that can be used in the query. * Consists of a shared part (always common to all sessions and queries) * and copied part (which can be its own for each session or query). @@ -677,6 +699,9 @@ public: UInt32 getZooKeeperSessionUptime() const; + MergeTreeMetaCachePtr getMergeTreeMetaCache() const; + + #if USE_NURAFT std::shared_ptr & getKeeperDispatcher() const; #endif @@ -763,6 +788,8 @@ public: /// Call after initialization before using trace collector. void initializeTraceCollector(); + void initializeMergeTreeMetaCache(const String & dir, size_t size); + bool hasTraceCollector() const; /// Nullptr if the query log is not ready for this moment. diff --git a/src/Processors/CMakeLists.txt b/src/Processors/CMakeLists.txt index 7e965188b4c..7c9ad405432 100644 --- a/src/Processors/CMakeLists.txt +++ b/src/Processors/CMakeLists.txt @@ -1,4 +1,4 @@ -if (ENABLE_EXAMPLES) +#if (ENABLE_EXAMPLES) add_subdirectory(examples) -endif () +#endif () diff --git a/src/Processors/examples/CMakeLists.txt b/src/Processors/examples/CMakeLists.txt index e69de29bb2d..dcb640c383a 100644 --- a/src/Processors/examples/CMakeLists.txt +++ b/src/Processors/examples/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable (merge_tree_meta_cache merge_tree_meta_cache.cpp) +target_link_libraries (merge_tree_meta_cache PRIVATE dbms) diff --git a/src/Processors/examples/merge_tree_meta_cache.cpp b/src/Processors/examples/merge_tree_meta_cache.cpp new file mode 100644 index 00000000000..a394323bcb7 --- /dev/null +++ b/src/Processors/examples/merge_tree_meta_cache.cpp @@ -0,0 +1,51 @@ +#include +#include + +int main() +{ + using namespace DB; + auto shared_context = Context::createShared(); + auto global_context = Context::createGlobal(shared_context.get()); + global_context->makeGlobalContext(); + global_context->initializeMergeTreeMetaCache("./db/", 256 << 20); + + auto cache = global_context->getMergeTreeMetaCache(); + + std::vector files + = {"columns.txt", "checksums.txt", "primary.idx", "count.txt", "partition.dat", "minmax_p.idx", "default_compression_codec.txt"}; + String prefix = "data/test_meta_cache/check_part_meta_cache/201806_1_1_0_4/"; + + for (const auto & file : files) + { + auto status = cache->put(prefix + file, prefix + file); + std::cout << "put " << file << " " << status.ToString() << std::endl; + } + + for (const auto & file : files) + { + String value; + auto status = cache->get(prefix + file, value); + std::cout << "get " << file << " " << status.ToString() << " " << value << std::endl; + } + + + for (const auto & file : files) + { + auto status = cache->del(prefix + file); + std::cout << "del " << file << " " << status.ToString() << std::endl; + } + + for (const auto & file : files) + { + String value; + auto status = cache->get(prefix + file, value); + std::cout << "get " << file << " " << status.ToString() << " " << value << std::endl; + } + + Strings keys; + Strings values; + cache->getByPrefix(prefix, keys, values); + for (size_t i=0; i +#include #include #include +#include #include #include #include @@ -61,7 +63,8 @@ static std::unique_ptr openForReading(const DiskPtr & di return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); } -void IMergeTreeDataPart::MinMaxIndex::load(const MergeTreeData & data, const DiskPtr & disk_, const String & part_path) +void IMergeTreeDataPart::MinMaxIndex::load( + const MergeTreeData & data, const PartMetaCachePtr & cache, const DiskPtr & disk, const String & /*part_path*/) { auto metadata_snapshot = data.getInMemoryMetadataPtr(); const auto & partition_key = metadata_snapshot->getPartitionKey(); @@ -73,14 +76,15 @@ void IMergeTreeDataPart::MinMaxIndex::load(const MergeTreeData & data, const Dis hyperrectangle.reserve(minmax_idx_size); for (size_t i = 0; i < minmax_idx_size; ++i) { - String file_name = fs::path(part_path) / ("minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"); - auto file = openForReading(disk_, file_name); + String file_name = "minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"; + String value; + auto buf = cache->readOrSetMeta(disk, file_name, value); auto serialization = minmax_column_types[i]->getDefaultSerialization(); Field min_val; - serialization->deserializeBinary(min_val, *file); + serialization->deserializeBinary(min_val, *buf); Field max_val; - serialization->deserializeBinary(max_val, *file); + serialization->deserializeBinary(max_val, *buf); // NULL_LAST if (min_val.isNull()) @@ -181,6 +185,19 @@ void IMergeTreeDataPart::MinMaxIndex::merge(const MinMaxIndex & other) } } +void IMergeTreeDataPart::MinMaxIndex::appendFiles(const MergeTreeData & data, Strings & files) +{ + auto metadata_snapshot = data.getInMemoryMetadataPtr(); + const auto & partition_key = metadata_snapshot->getPartitionKey(); + auto minmax_column_names = data.getMinMaxColumnsNames(partition_key); + size_t minmax_idx_size = minmax_column_names.size(); + for (size_t i = 0; i < minmax_idx_size; ++i) + { + String file_name = "minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"; + files.push_back(file_name); + } +} + static void incrementStateMetric(IMergeTreeDataPart::State state) { @@ -284,6 +301,9 @@ IMergeTreeDataPart::IMergeTreeDataPart( , index_granularity_info(storage_, part_type_) , part_type(part_type_) , parent_part(parent_part_) + , meta_cache(std::make_shared( + storage.getContext()->getMergeTreeMetaCache(), storage.relative_data_path, relative_path, parent_part)) + { if (parent_part) state = State::Committed; @@ -309,6 +329,9 @@ IMergeTreeDataPart::IMergeTreeDataPart( , index_granularity_info(storage_, part_type_) , part_type(part_type_) , parent_part(parent_part_) + , meta_cache(std::make_shared( + storage.getContext()->getMergeTreeMetaCache(), storage.relative_data_path, relative_path, parent_part)) + { if (parent_part) state = State::Committed; @@ -631,6 +654,41 @@ void IMergeTreeDataPart::loadColumnsChecksumsIndexes(bool require_columns_checks loadDefaultCompressionCodec(); } +void IMergeTreeDataPart::appendFilesOfColumnsChecksumsIndexes(Strings & files, bool include_projection) const +{ + if (isStoredOnDisk()) + { + appendFilesOfUUID(files); + appendFilesOfColumns(files); + appendFilesOfChecksums(files); + appendFilesOfIndexGranularity(files); + appendFilesofIndex(files); + appendFilesOfRowsCount(files); + appendFilesOfPartitionAndMinMaxIndex(files); + appendFilesOfTTLInfos(files); + appendFilesOfDefaultCompressionCodec(files); + } + + if (!parent_part && include_projection) + { + for (const auto & [projection_name, projection_part] : projection_parts) + { + Strings projection_files; + projection_part->appendFilesOfColumnsChecksumsIndexes(projection_files, true); + for (const auto & projection_file : projection_files) + files.push_back(fs::path(projection_part->relative_path) / projection_file); + } + } +} + +size_t IMergeTreeDataPart::fileNumberOfColumnsChecksumsIndexes() const +{ + Strings files; + files.reserve(16); + appendFilesOfColumnsChecksumsIndexes(files, true); + return files.size(); +} + void IMergeTreeDataPart::loadProjections(bool require_columns_checksums, bool check_consistency) { auto metadata_snapshot = storage.getInMemoryMetadataPtr(); @@ -651,6 +709,11 @@ void IMergeTreeDataPart::loadIndexGranularity() throw Exception("Method 'loadIndexGranularity' is not implemented for part with type " + getType().toString(), ErrorCodes::NOT_IMPLEMENTED); } +void IMergeTreeDataPart::appendFilesOfIndexGranularity(Strings & /* files */) const +{ + throw Exception("Method 'appendFilesOfIndexGranularity' is not implemented for part with type " + getType().toString(), ErrorCodes::NOT_IMPLEMENTED); +} + void IMergeTreeDataPart::loadIndex() { /// It can be empty in case of mutations @@ -675,7 +738,8 @@ void IMergeTreeDataPart::loadIndex() } String index_path = fs::path(getFullRelativePath()) / "primary.idx"; - auto index_file = openForReading(volume->getDisk(), index_path); + String value; + auto index_buf = meta_cache->readOrSetMeta(volume->getDisk(), "primary.idx", value); size_t marks_count = index_granularity.getMarksCount(); @@ -685,7 +749,7 @@ void IMergeTreeDataPart::loadIndex() for (size_t i = 0; i < marks_count; ++i) //-V756 for (size_t j = 0; j < key_size; ++j) - key_serializations[j]->deserializeBinary(*loaded_index[j], *index_file); + key_serializations[j]->deserializeBinary(*loaded_index[j], *index_buf); for (size_t i = 0; i < key_size; ++i) { @@ -696,13 +760,27 @@ void IMergeTreeDataPart::loadIndex() ErrorCodes::CANNOT_READ_ALL_DATA); } - if (!index_file->eof()) + if (!index_buf->eof()) throw Exception("Index file " + fullPath(volume->getDisk(), index_path) + " is unexpectedly long", ErrorCodes::EXPECTED_END_OF_FILE); index.assign(std::make_move_iterator(loaded_index.begin()), std::make_move_iterator(loaded_index.end())); } } +void IMergeTreeDataPart::appendFilesofIndex(Strings & files) const +{ + auto metadata_snapshot = storage.getInMemoryMetadataPtr(); + if (parent_part) + metadata_snapshot = metadata_snapshot->projections.has(name) ? metadata_snapshot->projections.get(name).metadata : nullptr; + + if (!metadata_snapshot) + return; + + size_t key_size = metadata_snapshot->getPrimaryKeyColumns().size(); + if (key_size) + files.push_back("primary.idx"); +} + NameSet IMergeTreeDataPart::getFileNamesWithoutChecksums() const { if (!isStoredOnDisk()) @@ -726,23 +804,30 @@ void IMergeTreeDataPart::loadDefaultCompressionCodec() return; } + String v; String path = fs::path(getFullRelativePath()) / DEFAULT_COMPRESSION_CODEC_FILE_NAME; - if (!volume->getDisk()->exists(path)) + auto in_buf = meta_cache->readOrSetMeta(volume->getDisk(), DEFAULT_COMPRESSION_CODEC_FILE_NAME, v); + if (!in_buf) { default_codec = detectDefaultCompressionCodec(); } else { - auto file_buf = openForReading(volume->getDisk(), path); String codec_line; - readEscapedStringUntilEOL(codec_line, *file_buf); + readEscapedStringUntilEOL(codec_line, *in_buf); ReadBufferFromString buf(codec_line); if (!checkString("CODEC", buf)) { - LOG_WARNING(storage.log, "Cannot parse default codec for part {} from file {}, content '{}'. Default compression codec will be deduced automatically, from data on disk", name, path, codec_line); + LOG_WARNING( + storage.log, + "Cannot parse default codec for part {} from file {}, content '{}'. Default compression codec will be deduced " + "automatically, from data on disk", + name, + path, + codec_line); default_codec = detectDefaultCompressionCodec(); } @@ -760,6 +845,11 @@ void IMergeTreeDataPart::loadDefaultCompressionCodec() } } +void IMergeTreeDataPart::appendFilesOfDefaultCompressionCodec(Strings & files) const +{ + files.push_back(DEFAULT_COMPRESSION_CODEC_FILE_NAME); +} + CompressionCodecPtr IMergeTreeDataPart::detectDefaultCompressionCodec() const { /// In memory parts doesn't have any compression @@ -822,7 +912,7 @@ void IMergeTreeDataPart::loadPartitionAndMinMaxIndex() { String path = getFullRelativePath(); if (!parent_part) - partition.load(storage, volume->getDisk(), path); + partition.load(storage, meta_cache, volume->getDisk(), path); if (!isEmpty()) { @@ -830,7 +920,7 @@ void IMergeTreeDataPart::loadPartitionAndMinMaxIndex() // projection parts don't have minmax_idx, and it's always initialized minmax_idx->initialized = true; else - minmax_idx->load(storage, volume->getDisk(), path); + minmax_idx->load(storage, meta_cache, volume->getDisk(), path); } if (parent_part) return; @@ -845,13 +935,27 @@ void IMergeTreeDataPart::loadPartitionAndMinMaxIndex() ErrorCodes::CORRUPTED_DATA); } +void IMergeTreeDataPart::appendFilesOfPartitionAndMinMaxIndex(Strings & files) const +{ + if (storage.format_version < MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING && !parent_part) + return; + + if (!parent_part) + partition.appendFiles(storage, files); + + if (!isEmpty()) + if (!parent_part) + minmax_idx->appendFiles(storage, files); +} + void IMergeTreeDataPart::loadChecksums(bool require) { const String path = fs::path(getFullRelativePath()) / "checksums.txt"; - if (volume->getDisk()->exists(path)) + String value; + auto buf = meta_cache->readOrSetMeta(volume->getDisk(), "checksums.txt", value); + if (buf) { - auto buf = openForReading(volume->getDisk(), path); if (checksums.read(*buf)) { assertEOF(*buf); @@ -882,6 +986,11 @@ void IMergeTreeDataPart::loadChecksums(bool require) } } +void IMergeTreeDataPart::appendFilesOfChecksums(Strings & files) const +{ + files.push_back("checksums.txt"); +} + void IMergeTreeDataPart::loadRowsCount() { String path = fs::path(getFullRelativePath()) / "count.txt"; @@ -899,10 +1008,13 @@ void IMergeTreeDataPart::loadRowsCount() } else if (storage.format_version >= MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING || part_type == Type::COMPACT || parent_part) { - if (!volume->getDisk()->exists(path)) + String v; + auto buf = meta_cache->readOrSetMeta(volume->getDisk(), "count.txt", v); + if (!buf) throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); - read_rows_count(); + readIntText(rows_count, *buf); + assertEOF(*buf); #ifndef NDEBUG /// columns have to be loaded @@ -997,12 +1109,18 @@ void IMergeTreeDataPart::loadRowsCount() } } +void IMergeTreeDataPart::appendFilesOfRowsCount(Strings & files) const +{ + files.push_back("count.txt"); +} + void IMergeTreeDataPart::loadTTLInfos() { String path = fs::path(getFullRelativePath()) / "ttl.txt"; - if (volume->getDisk()->exists(path)) + String v; + auto in = meta_cache->readOrSetMeta(volume->getDisk(), "ttl.txt", v); + if (in) { - auto in = openForReading(volume->getDisk(), path); assertString("ttl format version: ", *in); size_t format_version; readText(format_version, *in); @@ -1024,19 +1142,30 @@ void IMergeTreeDataPart::loadTTLInfos() } } + +void IMergeTreeDataPart::appendFilesOfTTLInfos(Strings & files) const +{ + files.push_back("ttl.txt"); +} + void IMergeTreeDataPart::loadUUID() { + String v; String path = fs::path(getFullRelativePath()) / UUID_FILE_NAME; - - if (volume->getDisk()->exists(path)) + auto in = meta_cache->readOrSetMeta(volume->getDisk(), UUID_FILE_NAME, v); + if (in) { - auto in = openForReading(volume->getDisk(), path); readText(uuid, *in); if (uuid == UUIDHelpers::Nil) throw Exception("Unexpected empty " + String(UUID_FILE_NAME) + " in part: " + name, ErrorCodes::LOGICAL_ERROR); } } +void IMergeTreeDataPart::appendFilesOfUUID(Strings & files) const +{ + files.push_back(UUID_FILE_NAME); +} + void IMergeTreeDataPart::loadColumns(bool require) { String path = fs::path(getFullRelativePath()) / "columns.txt"; @@ -1045,7 +1174,9 @@ void IMergeTreeDataPart::loadColumns(bool require) metadata_snapshot = metadata_snapshot->projections.get(name).metadata; NamesAndTypesList loaded_columns; - if (!volume->getDisk()->exists(path)) + String v; + auto in = meta_cache->readOrSetMeta(volume->getDisk(), "columns.txt", v); + if (!in) { /// We can get list of columns only from columns.txt in compact parts. if (require || part_type == Type::COMPACT) @@ -1061,14 +1192,14 @@ void IMergeTreeDataPart::loadColumns(bool require) throw Exception("No columns in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); { - auto buf = volume->getDisk()->writeFile(path + ".tmp", 4096); - loaded_columns.writeText(*buf); + auto out = volume->getDisk()->writeFile(path + ".tmp", 4096); + loaded_columns.writeText(*out); } volume->getDisk()->moveFile(path + ".tmp", path); } else { - loaded_columns.readText(*volume->getDisk()->readFile(path)); + loaded_columns.readText(*in); for (const auto & column : loaded_columns) { @@ -1092,6 +1223,11 @@ void IMergeTreeDataPart::loadColumns(bool require) setColumns(loaded_columns, infos); } +void IMergeTreeDataPart::appendFilesOfColumns(Strings & files) const +{ + files.push_back("columns.txt"); +} + bool IMergeTreeDataPart::shallParticipateInMerges(const StoragePolicyPtr & storage_policy) const { /// `IMergeTreeDataPart::volume` describes space where current part belongs, and holds @@ -1142,9 +1278,14 @@ void IMergeTreeDataPart::renameTo(const String & new_relative_path, bool remove_ } } + modifyAllMetaCaches(ModifyCacheType::DROP, true); +// #ifndef NDEBUG + assertMetaCacheDropped(true); +// #endif volume->getDisk()->setLastModified(from, Poco::Timestamp::fromEpochTime(time(nullptr))); volume->getDisk()->moveDirectory(from, to); relative_path = new_relative_path; + modifyAllMetaCaches(ModifyCacheType::PUT, true); SyncGuardPtr sync_guard; if (storage.getSettings()->fsync_part_directory) @@ -1153,6 +1294,71 @@ void IMergeTreeDataPart::renameTo(const String & new_relative_path, bool remove_ storage.lockSharedData(*this); } + +void IMergeTreeDataPart::modifyAllMetaCaches(ModifyCacheType type, bool include_projection) const +{ + Strings files; + files.reserve(16); + appendFilesOfColumnsChecksumsIndexes(files, include_projection); + LOG_TRACE( + storage.log, + "part name:{} path:{} {} keys:{}", + name, + getFullRelativePath(), + modifyCacheTypeToString(type), + boost::algorithm::join(files, ", ")); + + switch (type) + { + case ModifyCacheType::PUT: + meta_cache->setMetas(volume->getDisk(), files); + break; + case ModifyCacheType::DROP: + meta_cache->dropMetas(files); + break; + } +} + + +void IMergeTreeDataPart::assertMetaCacheDropped(bool include_projection) const +{ + Strings files; + std::vector _; + meta_cache->getFilesAndCheckSums(files, _); + if (files.empty()) + return; + + for (const auto & file : files) + { + String file_name = fs::path(file).filename(); + /// file belongs to current part + if (fs::path(getFullRelativePath()) / file_name == file) + { + throw Exception( + ErrorCodes::LOGICAL_ERROR, "Data part {} with type {} with meta file {} still in cache", name, getType().toString(), file); + } + + /// file belongs to projection part of current part + if (!parent_part && include_projection) + { + for (const auto & [projection_name, projection_part] : projection_parts) + { + if (fs::path(projection_part->getFullRelativePath()) / file_name == file) + { + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Data part {} with type {} with meta file {} with projection name still in cache", + name, + getType().toString(), + file, + projection_name); + } + } + } + // LOG_WARNING(storage.log, "cache of file {} does't belong to any part", file); + } +} + std::optional IMergeTreeDataPart::keepSharedDataInDecoupledStorage() const { /// NOTE: It's needed for zero-copy replication @@ -1190,6 +1396,11 @@ void IMergeTreeDataPart::remove() const return; } + modifyAllMetaCaches(ModifyCacheType::DROP); +// #ifndef NDEBUG + assertMetaCacheDropped(); +// #endif + /** Atomic directory removal: * - rename directory to temporary name; * - remove it recursive. @@ -1293,6 +1504,11 @@ void IMergeTreeDataPart::remove() const void IMergeTreeDataPart::projectionRemove(const String & parent_to, bool keep_shared_data) const { + modifyAllMetaCaches(ModifyCacheType::DROP); +// #ifndef NDEBUG + assertMetaCacheDropped(); +// #endif + String to = parent_to + "/" + relative_path; auto disk = volume->getDisk(); if (checksums.empty()) @@ -1380,6 +1596,7 @@ String IMergeTreeDataPart::getRelativePathForDetachedPart(const String & prefix) return "detached/" + getRelativePathForPrefix(prefix, /* detached */ true); } + void IMergeTreeDataPart::renameToDetached(const String & prefix) const { renameTo(getRelativePathForDetachedPart(prefix), true); @@ -1632,6 +1849,71 @@ String IMergeTreeDataPart::getZeroLevelPartBlockID() const return info.partition_id + "_" + toString(hash_value.words[0]) + "_" + toString(hash_value.words[1]); } +IMergeTreeDataPart::uint128 IMergeTreeDataPart::getActualChecksumByFile(const String & file_path) const +{ + String file_name = std::filesystem::path(file_path).filename(); + const auto filenames_without_checksums = getFileNamesWithoutChecksums(); + auto it = checksums.files.find(file_name); + if (filenames_without_checksums.count(file_name) == 0 && it != checksums.files.end()) + { + return it->second.file_hash; + } + + if (!volume->getDisk()->exists(file_path)) + { + return {}; + } + std::unique_ptr in_file = volume->getDisk()->readFile(file_path); + HashingReadBuffer in_hash(*in_file); + + String value; + readStringUntilEOF(value, in_hash); + return in_hash.getHash(); +} + +void IMergeTreeDataPart::checkMetaCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const +{ + /// checkMetaCache only applies for normal part + if (isProjectionPart()) + return; + + /// the directory of projection part is under the directory of its parent part + const auto filenames_without_checksums = getFileNamesWithoutChecksums(); + meta_cache->getFilesAndCheckSums(files, cache_checksums); + for (const auto & file : files) + { + // std::cout << "check key:" << file << std::endl; + String file_name = fs::path(file).filename(); + + /// file belongs to normal part + if (fs::path(getFullRelativePath()) / file_name == file) + { + auto disk_checksum = getActualChecksumByFile(file); + disk_checksums.push_back(disk_checksum); + continue; + } + + /// file belongs to projection part + String proj_dir_name = fs::path(file).parent_path().filename(); + auto pos = proj_dir_name.find_last_of('.'); + if (pos == String::npos) + { + disk_checksums.push_back({}); + continue; + } + String proj_name = proj_dir_name.substr(0, pos); + auto it = projection_parts.find(proj_name); + if (it == projection_parts.end()) + { + disk_checksums.push_back({}); + continue; + } + + auto disk_checksum = it->second->getActualChecksumByFile(file); + disk_checksums.push_back(disk_checksum); + } +} + bool isCompactPart(const MergeTreeDataPartPtr & data_part) { return (data_part && data_part->getType() == MergeTreeDataPartType::COMPACT); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index ab08ca1c33a..ad072af10a5 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -44,6 +45,23 @@ class UncompressedCache; class IMergeTreeDataPart : public std::enable_shared_from_this { public: + enum ModifyCacheType + { + PUT, // override set + DROP, // remove keys + }; + + static String modifyCacheTypeToString(ModifyCacheType type) + { + switch (type) + { + case PUT: + return "PUT"; + case DROP: + return "DROP"; + } + } + static constexpr auto DATA_FILE_EXTENSION = ".bin"; using Checksums = MergeTreeDataPartChecksums; @@ -59,6 +77,7 @@ public: using IndexSizeByName = std::unordered_map; using Type = MergeTreeDataPartType; + using uint128 = PartMetaCache::uint128; IMergeTreeDataPart( @@ -138,6 +157,8 @@ public: /// Throws an exception if part is not stored in on-disk format. void assertOnDisk() const; + void assertMetaCacheDropped(bool include_projection = false) const; + void remove() const; void projectionRemove(const String & parent_to, bool keep_shared_data = false) const; @@ -145,6 +166,8 @@ public: /// Initialize columns (from columns.txt if exists, or create from column files if not). /// Load checksums from checksums.txt if exists. Load index if required. void loadColumnsChecksumsIndexes(bool require_columns_checksums, bool check_consistency); + void appendFilesOfColumnsChecksumsIndexes(Strings & files, bool include_projection = false) const; + size_t fileNumberOfColumnsChecksumsIndexes() const; String getMarksFileExtension() const { return index_granularity_info.marks_file_extension; } @@ -239,7 +262,7 @@ public: using TTLInfo = MergeTreeDataPartTTLInfo; using TTLInfos = MergeTreeDataPartTTLInfos; - TTLInfos ttl_infos; + mutable TTLInfos ttl_infos; /// Current state of the part. If the part is in working set already, it should be accessed via data_parts mutex void setState(State new_state) const; @@ -296,12 +319,13 @@ public: { } - void load(const MergeTreeData & data, const DiskPtr & disk_, const String & part_path); - void store(const MergeTreeData & data, const DiskPtr & disk_, const String & part_path, Checksums & checksums) const; + void load(const MergeTreeData & data, const PartMetaCachePtr & meta_cache, const DiskPtr & disk, const String & part_path); + void store(const MergeTreeData & data, const DiskPtr & disk, const String & part_path, Checksums & checksums) const; void store(const Names & column_names, const DataTypes & data_types, const DiskPtr & disk_, const String & part_path, Checksums & checksums) const; void update(const Block & block, const Names & column_names); void merge(const MinMaxIndex & other); + static void appendFiles(const MergeTreeData & data, Strings & files); }; using MinMaxIndexPtr = std::shared_ptr; @@ -351,6 +375,8 @@ public: /// storage and pass it to this method. virtual bool hasColumnFiles(const NameAndTypePair & /* column */) const { return false; } + virtual Strings getIndexGranularityFiles() const = 0; + /// Returns true if this part shall participate in merges according to /// settings of given storage policy. bool shallParticipateInMerges(const StoragePolicyPtr & storage_policy) const; @@ -363,6 +389,8 @@ public: String getRelativePathForPrefix(const String & prefix, bool detached = false) const; + virtual void checkMetaCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const; + bool isProjectionPart() const { return parent_part != nullptr; } const IMergeTreeDataPart * getParentPart() const { return parent_part; } @@ -434,6 +462,8 @@ protected: std::map> projection_parts; + mutable PartMetaCachePtr meta_cache; + void removeIfNeeded(); virtual void checkConsistency(bool require_part_metadata) const; @@ -457,40 +487,62 @@ private: /// Reads part unique identifier (if exists) from uuid.txt void loadUUID(); + void appendFilesOfUUID(Strings & files) const; + /// Reads columns names and types from columns.txt void loadColumns(bool require); + void appendFilesOfColumns(Strings & files) const; + /// If checksums.txt exists, reads file's checksums (and sizes) from it void loadChecksums(bool require); + void appendFilesOfChecksums(Strings & files) const; + /// Loads marks index granularity into memory virtual void loadIndexGranularity(); + virtual void appendFilesOfIndexGranularity(Strings & files) const; + /// Loads index file. void loadIndex(); + void appendFilesofIndex(Strings & files) const; + /// Load rows count for this part from disk (for the newer storage format version). /// For the older format version calculates rows count from the size of a column with a fixed size. void loadRowsCount(); + void appendFilesOfRowsCount(Strings & files) const; + /// Loads ttl infos in json format from file ttl.txt. If file doesn't exists assigns ttl infos with all zeros void loadTTLInfos(); + void appendFilesOfTTLInfos(Strings & files) const; + void loadPartitionAndMinMaxIndex(); void calculateColumnsSizesOnDisk(); void calculateSecondaryIndicesSizesOnDisk(); + void appendFilesOfPartitionAndMinMaxIndex(Strings & files) const; + /// Load default compression codec from file default_compression_codec.txt /// if it not exists tries to deduce codec from compressed column without /// any specifial compression. void loadDefaultCompressionCodec(); + void appendFilesOfDefaultCompressionCodec(Strings & files) const; + + void modifyAllMetaCaches(ModifyCacheType type, bool include_projection = false) const; + /// Found column without specific compression and return codec /// for this column with default parameters. CompressionCodecPtr detectDefaultCompressionCodec() const; + IMergeTreeDataPart::uint128 getActualChecksumByFile(const String & file_path) const; + mutable State state{State::Temporary}; }; diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 3d4e909cf60..10530f25927 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -53,7 +53,7 @@ String Range::toString() const /// Example: for `Hello\_World% ...` string it returns `Hello_World`, and for `%test%` returns an empty string. -static String extractFixedPrefixFromLikePattern(const String & like_pattern) +String extractFixedPrefixFromLikePattern(const String & like_pattern) { String fixed_prefix; diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index dee46ae52ce..c8d9fda77c4 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -442,4 +442,6 @@ private: bool strict; }; +String extractFixedPrefixFromLikePattern(const String & like_pattern); + } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 1b7be8ca98d..8d861e404f0 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1330,6 +1330,14 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) LOG_DEBUG(log, "Loaded data parts ({} items)", data_parts_indexes.size()); } +size_t MergeTreeData::fileNumberOfDataParts(const DataPartStates & states) const +{ + size_t result = 0; + auto parts = getDataParts(states); + for (const auto & part : parts) + result += part->fileNumberOfColumnsChecksumsIndexes(); + return result; +} /// Is the part directory old. /// True if its modification time and the modification time of all files inside it is less then threshold. diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 380c2f4f4c5..d349646ab88 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -427,6 +427,8 @@ public: /// Load the set of data parts from disk. Call once - immediately after the object is created. void loadDataParts(bool skip_sanity_checks); + size_t fileNumberOfDataParts(const DataPartStates & states) const; + String getLogName() const { return log_name; } Int64 getMaxBlockNumber() const; diff --git a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp index f4da730b1f0..e51b64b3842 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp @@ -91,6 +91,7 @@ void MergeTreeDataPartCompact::calculateEachColumnSizes(ColumnSizeByName & /*eac total_size.marks += mrk_checksum->second.file_size; } +// load marks from meta cache void MergeTreeDataPartCompact::loadIndexGranularity() { String full_path = getFullRelativePath(); @@ -192,4 +193,16 @@ MergeTreeDataPartCompact::~MergeTreeDataPartCompact() removeIfNeeded(); } +// Do not cache mark file, because cache other meta files is enough to speed up loading. +void MergeTreeDataPartCompact::appendFilesOfIndexGranularity(Strings& /* files */) const +{ +} + +// find all connected file and do modification +Strings MergeTreeDataPartCompact::getIndexGranularityFiles() const +{ + auto marks_file = index_granularity_info.getMarksFilePath("data"); + return {marks_file}; +} + } diff --git a/src/Storages/MergeTree/MergeTreeDataPartCompact.h b/src/Storages/MergeTree/MergeTreeDataPartCompact.h index 38bfa11652a..87066ab2ff0 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartCompact.h +++ b/src/Storages/MergeTree/MergeTreeDataPartCompact.h @@ -72,6 +72,10 @@ private: /// Compact parts doesn't support per column size, only total size void calculateEachColumnSizes(ColumnSizeByName & each_columns_size, ColumnSize & total_size) const override; + + void appendFilesOfIndexGranularity(Strings& files) const override; + + Strings getIndexGranularityFiles() const override; }; } diff --git a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp index 4ec53d88339..5c21ead3208 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp @@ -166,6 +166,17 @@ IMergeTreeDataPart::Checksum MergeTreeDataPartInMemory::calculateBlockChecksum() return checksum; } +// No mark files for part in memory +void MergeTreeDataPartInMemory::appendFilesOfIndexGranularity(Strings& /* files */) const +{ +} + +// No mark files for part in memory +Strings MergeTreeDataPartInMemory::getIndexGranularityFiles() const +{ + return {}; +} + DataPartInMemoryPtr asInMemoryPart(const MergeTreeDataPartPtr & part) { return std::dynamic_pointer_cast(part); diff --git a/src/Storages/MergeTree/MergeTreeDataPartInMemory.h b/src/Storages/MergeTree/MergeTreeDataPartInMemory.h index c5ee9ebd01f..4f83b54d402 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartInMemory.h +++ b/src/Storages/MergeTree/MergeTreeDataPartInMemory.h @@ -62,6 +62,10 @@ private: /// Calculates uncompressed sizes in memory. void calculateEachColumnSizes(ColumnSizeByName & each_columns_size, ColumnSize & total_size) const override; + + void appendFilesOfIndexGranularity(Strings & files) const override; + + Strings getIndexGranularityFiles() const override; }; using DataPartInMemoryPtr = std::shared_ptr; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp index b279c1aba6a..5132177aa5c 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp @@ -107,7 +107,6 @@ void MergeTreeDataPartWide::loadIndexGranularity() String full_path = getFullRelativePath(); index_granularity_info.changeGranularityIfRequired(volume->getDisk(), full_path); - if (columns.empty()) throw Exception("No columns in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); @@ -268,4 +267,18 @@ void MergeTreeDataPartWide::calculateEachColumnSizes(ColumnSizeByName & each_col } } +// Do not cache mark files of part, because cache other meta files is enough to speed up loading. +void MergeTreeDataPartWide::appendFilesOfIndexGranularity(Strings& /* files */) const +{ +} + +Strings MergeTreeDataPartWide::getIndexGranularityFiles() const +{ + if (columns.empty()) + return {}; + + auto marks_file = getFileNameForColumn(columns.front()); + return {marks_file}; +} + } diff --git a/src/Storages/MergeTree/MergeTreeDataPartWide.h b/src/Storages/MergeTree/MergeTreeDataPartWide.h index 4796143e11d..bf73d16d758 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWide.h +++ b/src/Storages/MergeTree/MergeTreeDataPartWide.h @@ -66,9 +66,14 @@ private: /// Loads marks index granularity into memory void loadIndexGranularity() override; + void appendFilesOfIndexGranularity(Strings & files) const override; + + Strings getIndexGranularityFiles() const override; + ColumnSize getColumnSizeImpl(const NameAndTypePair & column, std::unordered_set * processed_substreams) const; void calculateEachColumnSizes(ColumnSizeByName & each_columns_size, ColumnSize & total_size) const override; + }; } diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index 06fcb24f846..1d4e14c628b 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -160,11 +160,13 @@ namespace }; } +/* static std::unique_ptr openForReading(const DiskPtr & disk, const String & path) { size_t file_size = disk->getFileSize(path); return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); } +*/ String MergeTreePartition::getID(const MergeTreeData & storage) const { @@ -355,7 +357,7 @@ void MergeTreePartition::serializeText(const MergeTreeData & storage, WriteBuffe } } -void MergeTreePartition::load(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path) +void MergeTreePartition::load(const MergeTreeData & storage, const PartMetaCachePtr & meta_cache, const DiskPtr & disk, const String & part_path) { auto metadata_snapshot = storage.getInMemoryMetadataPtr(); if (!metadata_snapshot->hasPartitionKey()) @@ -363,10 +365,12 @@ void MergeTreePartition::load(const MergeTreeData & storage, const DiskPtr & dis const auto & partition_key_sample = adjustPartitionKey(metadata_snapshot, storage.getContext()).sample_block; auto partition_file_path = part_path + "partition.dat"; - auto file = openForReading(disk, partition_file_path); + + String v; + auto buf = meta_cache->readOrSetMeta(disk, "partition.dat", v); value.resize(partition_key_sample.columns()); for (size_t i = 0; i < partition_key_sample.columns(); ++i) - partition_key_sample.getByPosition(i).type->getDefaultSerialization()->deserializeBinary(value[i], *file); + partition_key_sample.getByPosition(i).type->getDefaultSerialization()->deserializeBinary(value[i], *buf); } void MergeTreePartition::store(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path, MergeTreeDataPartChecksums & checksums) const @@ -384,7 +388,9 @@ void MergeTreePartition::store(const Block & partition_key_sample, const DiskPtr auto out = disk->writeFile(part_path + "partition.dat"); HashingWriteBuffer out_hashing(*out); for (size_t i = 0; i < value.size(); ++i) + { partition_key_sample.getByPosition(i).type->getDefaultSerialization()->serializeBinary(value[i], out_hashing); + } out_hashing.next(); checksums.files["partition.dat"].file_size = out_hashing.count(); @@ -443,4 +449,14 @@ KeyDescription MergeTreePartition::adjustPartitionKey(const StorageMetadataPtr & return partition_key; } + +void MergeTreePartition::appendFiles(const MergeTreeData & storage, Strings& files) const +{ + auto metadata_snapshot = storage.getInMemoryMetadataPtr(); + if (!metadata_snapshot->hasPartitionKey()) + return; + + files.push_back("partition.dat"); +} + } diff --git a/src/Storages/MergeTree/MergeTreePartition.h b/src/Storages/MergeTree/MergeTreePartition.h index d501d615621..b8b5b301219 100644 --- a/src/Storages/MergeTree/MergeTreePartition.h +++ b/src/Storages/MergeTree/MergeTreePartition.h @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace DB @@ -37,7 +38,7 @@ public: void serializeText(const MergeTreeData & storage, WriteBuffer & out, const FormatSettings & format_settings) const; - void load(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path); + void load(const MergeTreeData & storage, const PartMetaCachePtr & meta_cache, const DiskPtr & disk, const String & part_path); void store(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path, MergeTreeDataPartChecksums & checksums) const; void store(const Block & partition_key_sample, const DiskPtr & disk, const String & part_path, MergeTreeDataPartChecksums & checksums) const; @@ -45,6 +46,8 @@ public: void create(const StorageMetadataPtr & metadata_snapshot, Block block, size_t row, ContextPtr context); + void appendFiles(const MergeTreeData & storage, Strings & files) const; + /// Adjust partition key and execute its expression on block. Return sample block according to used expression. static NamesAndTypesList executePartitionByExpression(const StorageMetadataPtr & metadata_snapshot, Block & block, ContextPtr context); diff --git a/src/Storages/MergeTree/PartMetaCache.cpp b/src/Storages/MergeTree/PartMetaCache.cpp new file mode 100644 index 00000000000..33c95d6963e --- /dev/null +++ b/src/Storages/MergeTree/PartMetaCache.cpp @@ -0,0 +1,134 @@ +#include "PartMetaCache.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace ProfileEvents +{ + extern const Event MergeTreeMetaCacheHit; + extern const Event MergeTreeMetaCacheMiss; +} + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +namespace DB +{ + +std::unique_ptr +PartMetaCache::readOrSetMeta(const DiskPtr & disk, const String & file_name, String & value) +{ + String file_path = fs::path(getFullRelativePath()) / file_name; + auto status = cache->get(file_path, value); + if (!status.ok()) + { + ProfileEvents::increment(ProfileEvents::MergeTreeMetaCacheMiss); + if (!disk->exists(file_path)) + { + return nullptr; + } + + auto in = disk->readFile(file_path); + if (in) + { + readStringUntilEOF(value, *in); + cache->put(file_path, value); + } + } + else + { + ProfileEvents::increment(ProfileEvents::MergeTreeMetaCacheHit); + } + return std::make_unique(value); +} + +void PartMetaCache::setMetas(const DiskPtr & disk, const Strings & file_names) +{ + String text; + String read_value; + for (const auto & file_name : file_names) + { + const String file_path = fs::path(getFullRelativePath()) / file_name; + if (!disk->exists(file_path)) + continue; + + auto in = disk->readFile(file_path); + if (!in) + continue; + + readStringUntilEOF(text, *in); + auto status = cache->put(file_path, text); + if (!status.ok()) + { + status = cache->get(file_path, read_value); + if (status.IsNotFound() || read_value == text) + continue; + throw Exception(ErrorCodes::LOGICAL_ERROR, "set meta failed status:{}, file_path:{}", status.ToString(), file_path); + } + } +} + +void PartMetaCache::dropMetas(const Strings & file_names) +{ + for (const auto & file_name : file_names) + { + String file_path = fs::path(getFullRelativePath()) / file_name; + auto status = cache->del(file_path); + if (!status.ok()) + { + String read_value; + status = cache->get(file_path, read_value); + if (status.IsNotFound()) + continue; + throw Exception(ErrorCodes::LOGICAL_ERROR, "drop meta failed status:{}, file_path:{}", status.ToString(), file_path); + } + } +} + +void PartMetaCache::setMeta(const String & file_name, const String & value) +{ + String file_path = fs::path(getFullRelativePath()) / file_name; + String read_value; + auto status = cache->get(file_path, read_value); + if (status == rocksdb::Status::OK() && value == read_value) + return; + + status = cache->put(file_path, value); + if (!status.ok()) + { + status = cache->get(file_path, read_value); + if (status.IsNotFound() || read_value == value) + return; + + throw Exception(ErrorCodes::LOGICAL_ERROR, "set meta failed status:{}, file_path:{}", status.ToString(), file_path); + } +} + +void PartMetaCache::getFilesAndCheckSums(Strings & files, std::vector & checksums) const +{ + String prefix = fs::path(getFullRelativePath()) / ""; + Strings values; + values.reserve(files.capacity()); + cache->getByPrefix(prefix, files, values); + size_t size = files.size(); + for (size_t i = 0; i < size; ++i) + { + ReadBufferFromString rbuf(values[i]); + HashingReadBuffer hbuf(rbuf); + checksums.push_back(hbuf.getHash()); + } +} + +String PartMetaCache::getFullRelativePath() const +{ + return fs::path(relative_data_path) / (parent_part ? parent_part->relative_path : "") / relative_path / ""; +} + +} diff --git a/src/Storages/MergeTree/PartMetaCache.h b/src/Storages/MergeTree/PartMetaCache.h new file mode 100644 index 00000000000..d6a86d86ba1 --- /dev/null +++ b/src/Storages/MergeTree/PartMetaCache.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + + +namespace DB +{ + +class SeekableReadBuffer; +class IMergeTreeDataPart; +class PartMetaCache; +using PartMetaCachePtr = std::shared_ptr; + +class PartMetaCache +{ +public: + using uint128 = CityHash_v1_0_2::uint128; + + PartMetaCache(const MergeTreeMetaCachePtr & cache_, const String & relative_data_path_, const String & relative_path_, const IMergeTreeDataPart * parent_part_) + : cache(cache_) + , relative_data_path(relative_data_path_) + , relative_path(relative_path_) + , parent_part(parent_part_) + { + } + + std::unique_ptr + readOrSetMeta(const DiskPtr & disk, const String & file_name, String & value); + void setMetas(const DiskPtr & disk, const Strings & file_names); + void dropMetas(const Strings & file_names); + void setMeta(const String & file_name, const String & value); + void getFilesAndCheckSums(Strings & file_names, std::vector & checksums) const; + +private: + std::string getFullRelativePath() const; + + MergeTreeMetaCachePtr cache; + const String & relative_data_path; // relative path of table to disk + const String & relative_path; // relative path of part to table + const IMergeTreeDataPart * parent_part; +}; + +} diff --git a/src/Storages/System/StorageSystemMergeTreeMetaCache.cpp b/src/Storages/System/StorageSystemMergeTreeMetaCache.cpp new file mode 100644 index 00000000000..45cf45edb31 --- /dev/null +++ b/src/Storages/System/StorageSystemMergeTreeMetaCache.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace DB +{ +namespace ErrorCodes +{ + extern const int BAD_ARGUMENTS; +} + +NamesAndTypesList StorageSystemMergeTreeMetaCache::getNamesAndTypes() +{ + return { + {"key", std::make_shared()}, + {"value", std::make_shared()}, + }; +} + +static bool extractKeyImpl(const IAST & elem, String & res, bool & precise) +{ + const auto * function = elem.as(); + if (!function) + return false; + + if (function->name == "and") + { + for (const auto & child : function->arguments->children) + { + bool tmp_precise = false; + if (extractKeyImpl(*child, res, tmp_precise)) + { + precise = tmp_precise; + return true; + } + } + return false; + } + + if (function->name == "equals" || function->name == "like") + { + const auto & args = function->arguments->as(); + const IAST * value; + + if (args.children.size() != 2) + return false; + + const ASTIdentifier * ident; + if ((ident = args.children.at(0)->as())) + value = args.children.at(1).get(); + else if ((ident = args.children.at(1)->as())) + value = args.children.at(0).get(); + else + return false; + + if (ident->name() != "key") + return false; + + const auto * literal = value->as(); + if (!literal) + return false; + + if (literal->value.getType() != Field::Types::String) + return false; + + res = literal->value.safeGet(); + precise = function->name == "equals"; + return true; + } + return false; +} + + +/** Retrieve from the query a condition of the form `key= 'key'`, from conjunctions in the WHERE clause. + */ +static String extractKey(const ASTPtr & query, bool& precise) +{ + const auto & select = query->as(); + if (!select.where()) + return ""; + + String res; + return extractKeyImpl(*select.where(), res, precise) ? res : ""; +} + + +void StorageSystemMergeTreeMetaCache::fillData(MutableColumns & res_columns, ContextPtr context, const SelectQueryInfo & query_info) const +{ + bool precise = false; + String key = extractKey(query_info.query, precise); + if (key.empty()) + throw Exception( + "SELECT from system.merge_tree_meta_cache table must contain condition like key = 'key' or key LIKE 'prefix%' in WHERE clause.", ErrorCodes::BAD_ARGUMENTS); + + auto cache = context->getMergeTreeMetaCache(); + if (precise) + { + String value; + if (cache->get(key, value) != MergeTreeMetaCache::Status::OK()) + return; + + size_t col_num = 0; + res_columns[col_num++]->insert(key); + res_columns[col_num++]->insert(value); + } + else + { + String target = extractFixedPrefixFromLikePattern(key); + if (target.empty()) + throw Exception( + "SELECT from system.merge_tree_meta_cache table must contain condition like key = 'key' or key LIKE 'prefix%' in WHERE clause.", ErrorCodes::BAD_ARGUMENTS); + + Strings keys; + Strings values; + keys.reserve(4096); + values.reserve(4096); + cache->getByPrefix(target, keys, values); + if (keys.empty()) + return; + + assert(keys.size() == values.size()); + for (size_t i = 0; i < keys.size(); ++i) + { + size_t col_num = 0; + res_columns[col_num++]->insert(keys[i]); + res_columns[col_num++]->insert(values[i]); + } + } +} + +} diff --git a/src/Storages/System/StorageSystemMergeTreeMetaCache.h b/src/Storages/System/StorageSystemMergeTreeMetaCache.h new file mode 100644 index 00000000000..a5f65862243 --- /dev/null +++ b/src/Storages/System/StorageSystemMergeTreeMetaCache.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + + +namespace DB +{ +class Context; + + +/** Implements `merge_tree_meta_cache` system table, which allows you to view the metacache data in rocksdb for debugging purposes. + */ +class StorageSystemMergeTreeMetaCache : public shared_ptr_helper, public IStorageSystemOneBlock +{ + friend struct shared_ptr_helper; + +public: + std::string getName() const override { return "SystemMergeTreeMetaCache"; } + + static NamesAndTypesList getNamesAndTypes(); + +protected: + using IStorageSystemOneBlock::IStorageSystemOneBlock; + + void fillData(MutableColumns & res_columns, ContextPtr context, const SelectQueryInfo & query_info) const override; +}; + +} diff --git a/src/Storages/System/attachSystemTables.cpp b/src/Storages/System/attachSystemTables.cpp index 023ced35a6b..0e7d4b624c5 100644 --- a/src/Storages/System/attachSystemTables.cpp +++ b/src/Storages/System/attachSystemTables.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #ifdef OS_LINUX #include @@ -129,6 +130,7 @@ void attachSystemTablesLocal(ContextPtr context, IDatabase & system_database) #endif #if USE_ROCKSDB attach(context, system_database, "rocksdb"); + attach(context, system_database, "merge_tree_meta_cache"); #endif } diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache.reference b/tests/queries/0_stateless/01233_check_part_meta_cache.reference new file mode 100644 index 00000000000..914add905ce --- /dev/null +++ b/tests/queries/0_stateless/01233_check_part_meta_cache.reference @@ -0,0 +1,28 @@ +0 0 +7 0 +14 0 +28 0 +42 0 +56 0 +70 0 +77 0 +63 0 +77 0 +84 0 +98 0 +122 0 +154 0 +122 0 +12 0 +24 0 +48 0 +72 0 +96 0 +120 0 +132 0 +108 0 +132 0 +144 0 +183 0 +235 0 +183 0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache.sql b/tests/queries/0_stateless/01233_check_part_meta_cache.sql new file mode 100644 index 00000000000..f0ca3b608d1 --- /dev/null +++ b/tests/queries/0_stateless/01233_check_part_meta_cache.sql @@ -0,0 +1,123 @@ +-- Create table under database with engine ordinary. +set mutations_sync = 1; +DROP DATABASE IF EXISTS test_meta_cache; +DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache; +CREATE DATABASE test_meta_cache ENGINE = Ordinary; +CREATE TABLE test_meta_cache.check_part_meta_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert first batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert second batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Update some data. +alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +alter table test_meta_cache.check_part_meta_cache delete where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache delete where k = 8; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert third batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Drop partitioin 201805 +alter table test_meta_cache.check_part_meta_cache drop partition 201805; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Optimize table. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_meta_cache.check_part_meta_cache FINAL; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add column. +alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete column. +alter table test_meta_cache.check_part_meta_cache drop column v1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add TTL info. +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Modify TTL info. +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Truncate table. +truncate table test_meta_cache.check_part_meta_cache; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Recreate table with projection. +drop table if exists test_meta_cache.check_part_meta_cache; +CREATE TABLE test_meta_cache.check_part_meta_cache( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; + +-- Insert first batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert second batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Update some data. +alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +alter table test_meta_cache.check_part_meta_cache delete where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache delete where k = 8; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert third batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Drop partitioin 201805 +alter table test_meta_cache.check_part_meta_cache drop partition 201805; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Optimize table. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_meta_cache.check_part_meta_cache FINAL; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add column. +alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add TTL info. +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Update TTL info. +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Truncate table. +truncate table test_meta_cache.check_part_meta_cache; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference new file mode 100644 index 00000000000..95de1ef56a9 --- /dev/null +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference @@ -0,0 +1,28 @@ +0 0 +7 0 +14 0 +28 0 +42 0 +56 0 +70 0 +77 0 +63 0 +77 0 +84 0 +98 0 +124 0 +150 0 +124 0 +12 0 +24 0 +48 0 +72 0 +96 0 +120 0 +132 0 +108 0 +132 0 +144 0 +183 0 +235 0 +183 0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql new file mode 100644 index 00000000000..b57caf55cb8 --- /dev/null +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql @@ -0,0 +1,124 @@ +-- Create table under database with engine atomic. +set mutations_sync = 1; +DROP DATABASE IF EXISTS test_meta_cache; +DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache SYNC; +CREATE DATABASE test_meta_cache ENGINE = Atomic; +CREATE TABLE test_meta_cache.check_part_meta_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert first batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert second batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Update some data. +alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +alter table test_meta_cache.check_part_meta_cache delete where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache delete where k = 8; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Drop partitioin 201805 +alter table test_meta_cache.check_part_meta_cache drop partition 201805; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Optimize table. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_meta_cache.check_part_meta_cache FINAL; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add column. +alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete column. +alter table test_meta_cache.check_part_meta_cache drop column v1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add TTL info. +alter table test_meta_cache.check_part_meta_cache modify TTL p + 30; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Modify TTL info. +alter table test_meta_cache.check_part_meta_cache modify TTL p + 60; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Truncate table. +truncate table test_meta_cache.check_part_meta_cache; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Recreate table with projection. +drop table if exists test_meta_cache.check_part_meta_cache SYNC; +CREATE TABLE test_meta_cache.check_part_meta_cache( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; + +-- Insert first batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- nsert second batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Update some data. +alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +alter table test_meta_cache.check_part_meta_cache delete where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache delete where k = 8; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Drop partitioin 201805 +alter table test_meta_cache.check_part_meta_cache drop partition 201805; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Optimize table. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_meta_cache.check_part_meta_cache FINAL; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add column. +alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add TTL info. +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Modify TTL info. +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Truncate table. +truncate table test_meta_cache.check_part_meta_cache; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference new file mode 100644 index 00000000000..2275537d212 --- /dev/null +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference @@ -0,0 +1,28 @@ +0 0 +7 0 +14 0 +28 0 +42 0 +56 0 +70 0 +77 0 +7 0 +14 0 +21 0 +35 0 +51 0 +67 0 +0 0 +12 0 +24 0 +48 0 +72 0 +96 0 +120 0 +132 0 +108 0 +132 0 +144 0 +183 0 +235 0 +183 0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql new file mode 100644 index 00000000000..6d08bb146a5 --- /dev/null +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql @@ -0,0 +1,125 @@ +-- Create table under database with engine ordinary. +set mutations_sync = 1; +set replication_alter_partitions_sync = 2; +DROP DATABASE IF EXISTS test_meta_cache on cluster preonline_hk5; +DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache on cluster preonline_hk5; +CREATE DATABASE test_meta_cache on cluster preonline_hk5 ENGINE = Ordinary; +CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/test_meta_cache/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert first batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert second batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Update some data. +alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +--Delete some data. +alter table test_meta_cache.check_part_meta_cache delete where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache delete where k = 8; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Drop partitioin 201805 +alter table test_meta_cache.check_part_meta_cache drop partition 201805; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Optimize table. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_meta_cache.check_part_meta_cache FINAL; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add column. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 add column v3 UInt64; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete column. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 drop column v1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add TTL info. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Modify TTL info. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Truncate table. +truncate table test_meta_cache.check_part_meta_cache; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Recreate table with projection. +drop table if exists test_meta_cache.check_part_meta_cache on cluster preonline_hk5; +CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; + +-- Insert first batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert second batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Update some data. +alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +--Delete some data. +alter table test_meta_cache.check_part_meta_cache delete where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache delete where k = 8; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Drop partitioin 201805 +alter table test_meta_cache.check_part_meta_cache drop partition 201805; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Optimize table. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_meta_cache.check_part_meta_cache FINAL; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add column. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 add column v3 UInt64; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add TTL info. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Modify TTL info. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Truncate table. +truncate table test_meta_cache.check_part_meta_cache; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference new file mode 100644 index 00000000000..2275537d212 --- /dev/null +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference @@ -0,0 +1,28 @@ +0 0 +7 0 +14 0 +28 0 +42 0 +56 0 +70 0 +77 0 +7 0 +14 0 +21 0 +35 0 +51 0 +67 0 +0 0 +12 0 +24 0 +48 0 +72 0 +96 0 +120 0 +132 0 +108 0 +132 0 +144 0 +183 0 +235 0 +183 0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql new file mode 100644 index 00000000000..c41d036cef1 --- /dev/null +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql @@ -0,0 +1,125 @@ +-- Create table under database with engine ordinary. +set mutations_sync = 1; +set replication_alter_partitions_sync = 2; +DROP DATABASE IF EXISTS test_meta_cache on cluster preonline_hk5; +DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache on cluster preonline_hk5; +CREATE DATABASE test_meta_cache on cluster preonline_hk5 ENGINE = Atomic; +CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/test_meta_cache/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert first batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert second batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Update some data. +alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +--Delete some data. +alter table test_meta_cache.check_part_meta_cache delete where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache delete where k = 8; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Drop partitioin 201805 +alter table test_meta_cache.check_part_meta_cache drop partition 201805; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Optimize table. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_meta_cache.check_part_meta_cache FINAL; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add column. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 add column v3 UInt64; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete column. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 drop column v1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add TTL info. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Modify TTL info. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Truncate table. +truncate table test_meta_cache.check_part_meta_cache; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Recreate table with projection. +drop table if exists test_meta_cache.check_part_meta_cache on cluster preonline_hk5; +CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; + +-- Insert first batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Insert second batch of data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Update some data. +alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +--Delete some data. +alter table test_meta_cache.check_part_meta_cache delete where k = 1; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +alter table test_meta_cache.check_part_meta_cache delete where k = 8; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Delete some data. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Drop partitioin 201805 +alter table test_meta_cache.check_part_meta_cache drop partition 201805; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Optimize table. +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_meta_cache.check_part_meta_cache FINAL; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add column. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 add column v3 UInt64; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Add TTL info. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Modify TTL info. +alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + +-- Truncate table. +truncate table test_meta_cache.check_part_meta_cache; +with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); + From a15444031ca9d12d76ebe0aa10fd26b402b68351 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 20 Dec 2021 21:53:05 +0300 Subject: [PATCH 0133/1647] add test --- src/Interpreters/MergeTreeTransaction.cpp | 3 - src/Storages/MergeTree/MergeTreeData.cpp | 4 +- src/Storages/StorageMergeTree.cpp | 2 +- src/Storages/StorageMergeTree.h | 1 - .../01167_isolation_hermitage.reference | 59 +++++++ .../0_stateless/01167_isolation_hermitage.sh | 166 ++++++++++++++++++ .../01168_mutations_isolation.reference | 61 ++++--- .../0_stateless/01168_mutations_isolation.sh | 40 +++-- .../01170_alter_partition_isolation.reference | 28 +-- tests/queries/0_stateless/transactions.lib | 40 ++++- 10 files changed, 332 insertions(+), 72 deletions(-) create mode 100644 tests/queries/0_stateless/01167_isolation_hermitage.reference create mode 100755 tests/queries/0_stateless/01167_isolation_hermitage.sh diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index 91e4b51ea07..662edd2cac8 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -134,10 +134,7 @@ bool MergeTreeTransaction::rollback() noexcept bool need_rollback = csn.compare_exchange_strong(expected, Tx::RolledBackCSN); if (!need_rollback) - { - /// TODO add assertions for the case when this method called from background operation return false; - } for (const auto & table_and_mutation : mutations) table_and_mutation.first->killMutation(table_and_mutation.second); diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 2fab4431134..6bfbaa0809c 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1684,8 +1684,8 @@ size_t MergeTreeData::clearEmptyParts() if (part->rows_count != 0) continue; - /// Do not drop empty part if it may be visible for some transaction (otherwise it may cause conflicts) - if (!part->versions.canBeRemoved(TransactionLog::instance().getOldestSnapshot())) + /// Do not try to drop empty part if it's locked by some transaction and do not try to drop uncommitted parts. + if (part->versions.maxtid_lock.load() || !part->versions.isVisible(TransactionLog::instance().getLatestSnapshot())) continue; dropPartNoWaitNoThrow(part->name); diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 3293f200f3e..38b9a5790e6 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -538,7 +538,7 @@ void StorageMergeTree::mutate(const MutationCommands & commands, ContextPtr quer Int64 version = startMutation(commands, query_context); - if (query_context->getSettingsRef().mutations_sync > 0) + if (query_context->getSettingsRef().mutations_sync > 0 || query_context->getCurrentTransaction()) waitForMutation(version); } diff --git a/src/Storages/StorageMergeTree.h b/src/Storages/StorageMergeTree.h index d960d4ceb17..858b550c38e 100644 --- a/src/Storages/StorageMergeTree.h +++ b/src/Storages/StorageMergeTree.h @@ -245,7 +245,6 @@ private: std::unique_ptr getDefaultSettings() const override; - friend class MergeTreeProjectionBlockOutputStream; friend class MergeTreeSink; friend class MergeTreeData; friend class MergePlainMergeTreeTask; diff --git a/tests/queries/0_stateless/01167_isolation_hermitage.reference b/tests/queries/0_stateless/01167_isolation_hermitage.reference new file mode 100644 index 00000000000..4488809f3ed --- /dev/null +++ b/tests/queries/0_stateless/01167_isolation_hermitage.reference @@ -0,0 +1,59 @@ +Serialization error +INVALID_TRANSACTION +INVALID_TRANSACTION +1 1 11 +1 2 21 +tx4 2 1 10 +tx4 2 2 20 +tx4 3 1 10 +tx4 3 2 20 +4 1 10 +4 2 20 +tx6 5 1 10 +tx6 5 2 20 +tx6 6 1 10 +tx6 6 2 20 +7 1 11 +7 2 20 +Serialization error +tx7 8 1 11 +tx7 8 2 20 +INVALID_TRANSACTION +INVALID_TRANSACTION +10 1 11 +10 2 20 +Serialization error +tx11 11 1 10 +tx11 11 2 20 +INVALID_TRANSACTION +tx11 12 1 10 +tx11 12 2 20 +INVALID_TRANSACTION +13 1 11 +13 2 19 +16 1 10 +16 2 20 +16 3 30 +Serialization error +INVALID_TRANSACTION +INVALID_TRANSACTION +18 1 20 +18 2 30 +tx16 19 1 10 +tx16 19 2 20 +tx17 20 1 10 +tx17 20 2 20 +Serialization error +INVALID_TRANSACTION +21 1 11 +21 2 20 +tx18 22 1 10 +tx19 23 1 10 +tx19 24 2 20 +tx18 25 2 20 +26 1 12 +26 2 18 +29 1 10 +29 2 20 +29 3 30 +29 4 42 diff --git a/tests/queries/0_stateless/01167_isolation_hermitage.sh b/tests/queries/0_stateless/01167_isolation_hermitage.sh new file mode 100755 index 00000000000..b08ac405ee8 --- /dev/null +++ b/tests/queries/0_stateless/01167_isolation_hermitage.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# Tags: no-fasttest, no-replicated-database +# Looks like server does not listen https port in fasttest +# FIXME Replicated database executes ALTERs in separate context, so transaction info is lost + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh +# shellcheck source=./transactions.lib +. "$CURDIR"/transactions.lib +set -e + +# https://github.com/ept/hermitage + +$CLICKHOUSE_CLIENT -q "drop table if exists test" +$CLICKHOUSE_CLIENT -q "create table test (id int, value int) engine=MergeTree order by id" + +function reset_table() +{ + $CLICKHOUSE_CLIENT -q "truncate table test;" + $CLICKHOUSE_CLIENT -q "insert into test (id, value) values (1, 10);" + $CLICKHOUSE_CLIENT -q "insert into test (id, value) values (2, 20);" +} + +# TODO update test after implementing Read Committed + +# G0 +reset_table +tx 1 "begin transaction" +tx 2 "begin transaction" +tx 1 "alter table test update value=11 where id=1" +tx 2 "alter table test update value=12 where id=1" | grep -Eo "Serialization error" | uniq +tx 1 "alter table test update value=21 where id=2" +tx 1 "commit" +tx 2 "alter table test update value=22 where id=2" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 2 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 2 "rollback" +$CLICKHOUSE_CLIENT -q "select 1, * from test order by id" + +# G1a +reset_table +tx_async 3 "begin transaction" +tx_async 4 "begin transaction" +tx_async 3 "alter table test update value=101 where id=1" +tx_async 4 "select 2, * from test order by id" +tx_async 3 "alter table test update value=11 where id=1" +tx_async 3 "rollback" +tx_async 4 "select 3, * from test order by id" +tx_async 4 "commit" +tx_wait 3 +tx_wait 4 +$CLICKHOUSE_CLIENT -q "select 4, * from test order by id" + +# G1b +reset_table +tx_async 5 "begin transaction" +tx_async 6 "begin transaction" +tx_async 5 "alter table test update value=101 where id=1" +tx_async 6 "select 5, * from test order by id" +tx_async 5 "alter table test update value=11 where id=1" +tx_async 5 "commit" +tx_async 6 "select 6, * from test order by id" +tx_async 6 "commit" +tx_wait 5 +tx_wait 6 +$CLICKHOUSE_CLIENT -q "select 7, * from test order by id" + +# G1c +# NOTE both transactions will succeed if we implement skipping of unaffected partitions/parts +reset_table +tx 7 "begin transaction" +tx 8 "begin transaction" +tx 7 "alter table test update value=11 where id=1" +tx 8 "alter table test update value=22 where id=2" | grep -Eo "Serialization error" | uniq +tx 7 "select 8, * from test order by id" +tx 8 "select 9, * from test order by id" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 7 "commit" +tx 8 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 8 "rollback" +$CLICKHOUSE_CLIENT -q "select 10, * from test order by id" + +# OTV +reset_table +tx 9 "begin transaction" +tx 10 "begin transaction" +tx 11 "begin transaction" +tx 9 "alter table test update value = 11 where id = 1" +tx 9 "alter table test update value = 19 where id = 2" +tx 10 "alter table test update value = 12 where id = 1" | grep -Eo "Serialization error" | uniq +tx 9 "commit" +tx 11 "select 11, * from test order by id" +tx 10 "alter table test update value = 18 where id = 2" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 11 "select 12, * from test order by id" +tx 10 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 10 "rollback" +tx 11 "commit" +$CLICKHOUSE_CLIENT -q "select 13, * from test order by id" + +# PMP +reset_table +tx_async 12 "begin transaction" +tx_async 13 "begin transaction" +tx_async 12 "select 14, * from test where value = 30" +tx_async 13 "insert into test (id, value) values (3, 30)" +tx_async 13 "commit" +tx_async 12 "select 15, * from test where value = 30" +tx_async 12 "commit" +tx_wait 12 +tx_wait 13 +$CLICKHOUSE_CLIENT -q "select 16, * from test order by id" + +# PMP write +reset_table +tx 14 "begin transaction" +tx 15 "begin transaction" +tx 14 "alter table test update value = value + 10 where 1" +tx 15 "alter table test delete where value = 20" | grep -Eo "Serialization error" | uniq +tx 14 "commit" +tx 15 "select 17, * from test order by id" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 15 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 15 "rollback" +$CLICKHOUSE_CLIENT -q "select 18, * from test order by id" + +# P4 +reset_table +tx 16 "begin transaction" +tx 17 "begin transaction" +tx 16 "select 19, * from test order by id" +tx 17 "select 20, * from test order by id" +tx 16 "alter table test update value = 11 where id = 1" +tx 17 "alter table test update value = 11 where id = 1" | grep -Eo "Serialization error" | uniq +tx 16 "commit" +tx 17 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq +tx 17 "rollback" +$CLICKHOUSE_CLIENT -q "select 21, * from test order by id" + +# G-single +reset_table +tx_async 18 "begin transaction" +tx_async 19 "begin transaction" +tx_async 18 "select 22, * from test where id = 1" +tx_async 19 "select 23, * from test where id = 1" +tx_async 19 "select 24, * from test where id = 2" +tx_async 19 "alter table test update value = 12 where id = 1" +tx_async 19 "alter table test update value = 18 where id = 2" +tx_async 19 "commit" +tx_async 18 "select 25, * from test where id = 2" +tx_async 18 "commit" +tx_wait 18 +tx_wait 19 +$CLICKHOUSE_CLIENT -q "select 26, * from test order by id" + +# G2 +reset_table +tx_async 20 "begin transaction" +tx_async 21 "begin transaction" +tx_async 20 "select 27, * from test where value % 3 = 0" +tx_async 21 "select 28, * from test where value % 3 = 0" +tx_async 20 "insert into test (id, value) values (3, 30)" +tx_async 21 "insert into test (id, value) values (4, 42)" +tx_async 20 "commit" +tx_async 21 "commit" +tx_wait 20 +tx_wait 21 +$CLICKHOUSE_CLIENT -q "select 29, * from test order by id" + diff --git a/tests/queries/0_stateless/01168_mutations_isolation.reference b/tests/queries/0_stateless/01168_mutations_isolation.reference index 56e7264b174..f7a1a707cfe 100644 --- a/tests/queries/0_stateless/01168_mutations_isolation.reference +++ b/tests/queries/0_stateless/01168_mutations_isolation.reference @@ -1,34 +1,37 @@ -1 10 all_1_1_0_4 -1 30 all_3_3_0_4 -2 1 all_1_1_0 -2 2 all_2_2_0 +tx2 1 10 all_1_1_0_4 +tx2 1 30 all_3_3_0_4 +tx1 2 1 all_1_1_0 +tx1 2 2 all_2_2_0 Serialization error INVALID_TRANSACTION -3 1 all_1_1_0 +tx3 3 1 all_1_1_0 Serialization error -4 2 all_1_1_0_8 -4 5 all_11_11_0 -4 6 all_7_7_0_8 -5 2 all_1_1_0_8 -5 5 all_11_11_0 -5 6 all_7_7_0_8 +INVALID_TRANSACTION +INVALID_TRANSACTION +tx5 4 2 all_1_1_0_8 +tx5 4 5 all_10_10_0 +tx5 4 6 all_7_7_0_8 +tx5 5 2 all_1_1_0_8 +tx5 5 5 all_10_10_0 +tx5 5 6 all_7_7_0_8 SERIALIZATION_ERROR -6 2 all_1_1_0_12 -6 6 all_7_7_0_12 -7 20 all_1_1_0_14 -7 40 all_15_15_0 -7 60 all_7_7_0_14 -7 80 all_13_13_0_14 -8 20 all_1_15_1_14 -8 40 all_1_15_1_14 -8 60 all_1_15_1_14 -8 80 all_1_15_1_14 +tx6 6 2 all_1_1_0_11 +tx6 6 6 all_7_7_0_11 +tx7 7 20 all_1_1_0_13 +tx7 7 40 all_14_14_0 +tx7 7 60 all_7_7_0_13 +tx7 7 80 all_12_12_0_13 +tx7 8 20 all_1_14_1_13 +tx7 8 40 all_1_14_1_13 +tx7 8 60 all_1_14_1_13 +tx7 8 80 all_1_14_1_13 +Serialization error INVALID_TRANSACTION -9 21 all_1_15_1_18 -9 41 all_1_15_1_18 -9 61 all_1_15_1_18 -9 81 all_1_15_1_18 -10 22 all_1_15_1_19 -10 42 all_1_15_1_19 -10 62 all_1_15_1_19 -10 82 all_1_15_1_19 +tx11 9 21 all_1_14_1_17 +tx11 9 41 all_1_14_1_17 +tx11 9 61 all_1_14_1_17 +tx11 9 81 all_1_14_1_17 +tx13 10 22 all_1_14_1_18 +tx13 10 42 all_1_14_1_18 +tx13 10 62 all_1_14_1_18 +tx13 10 82 all_1_14_1_18 diff --git a/tests/queries/0_stateless/01168_mutations_isolation.sh b/tests/queries/0_stateless/01168_mutations_isolation.sh index d4d76e91ee0..e6049e50131 100755 --- a/tests/queries/0_stateless/01168_mutations_isolation.sh +++ b/tests/queries/0_stateless/01168_mutations_isolation.sh @@ -18,10 +18,10 @@ tx 1 "begin transaction" tx 2 "begin transaction" tx 1 "insert into mt values (2)" tx 2 "insert into mt values (3)" -tx 2 "alter table mt update n=n*10 where 1 settings mutations_sync=1" +tx 2 "alter table mt update n=n*10 where 1" tx 2 "select 1, n, _part from mt order by n" tx 1 "select 2, n, _part from mt order by n" -tx 1 "alter table mt update n=n+1 where 1 settings mutations_sync=1" | grep -Eo "Serialization error" | uniq +tx 1 "alter table mt update n=n+1 where 1" | grep -Eo "Serialization error" | uniq tx 1 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq tx 2 "rollback" @@ -32,17 +32,17 @@ tx 4 "begin transaction" tx 3 "insert into mt values (2)" tx 4 "insert into mt values (3)" tx 4 "alter table mt update n=n*2 where 1" -tx 3 "alter table mt update n=n+42 where 1" -tx 3 "insert into mt values (4)" +tx 3 "alter table mt update n=n+42 where 1" | grep -Eo "Serialization error" | uniq +tx 3 "insert into mt values (4)" | grep -Eo "INVALID_TRANSACTION" | uniq tx 4 "insert into mt values (5)" -tx 3 "commit" | grep -Eo "Serialization error" | uniq +tx 3 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq tx 4 "commit" tx 5 "begin transaction" tx 5 "select 4, n, _part from mt order by n" tx 6 "begin transaction" -tx 6 "alter table mt delete where n%2=1 settings mutations_sync=1" +tx 6 "alter table mt delete where n%2=1" tx 5 "select 5, n, _part from mt order by n" tx 5 "alter table mt drop partition id 'all'" | grep -Eo "SERIALIZATION_ERROR" | uniq tx 6 "select 6, n, _part from mt order by n" @@ -56,29 +56,31 @@ tx 6 "commit" tx 7 "begin transaction" tx 7 "select 7, n, _part from mt order by n" tx 8 "begin transaction" -tx 8 "alter table mt update n = 0 where 1" -$CLICKHOUSE_CLIENT -q "kill mutation where database=currentDatabase() and mutation_id='mutation_16.txt' format Null" +tx 8 "alter table mt update n = 0 where 1" & +$CLICKHOUSE_CLIENT -q "kill mutation where database=currentDatabase() and mutation_id='mutation_15.txt' format Null" +wait tx 7 "optimize table mt final" tx 7 "select 8, n, _part from mt order by n" -tx 8 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq tx 8 "rollback" tx 10 "begin transaction" -tx 10 "alter table mt update n = 0 where 1" +tx 10 "alter table mt update n = 0 where 1" | grep -Eo "Serialization error" | uniq tx 7 "alter table mt update n=n+1 where 1" tx 10 "commit" | grep -Eo "INVALID_TRANSACTION" | uniq tx 10 "rollback" tx 7 "commit" -tx 11 "begin transaction" -tx 11 "select 9, n, _part from mt order by n" -tx 12 "begin transaction" -tx 11 "alter table mt update n=n+1 where 1" -tx 12 "alter table mt update n=n+1 where 1" -tx 11 "commit" >/dev/null -tx 12 "commit" >/dev/null +tx_async 11 "begin transaction" +tx_async 11 "select 9, n, _part from mt order by n" +tx_async 12 "begin transaction" +tx_async 11 "alter table mt update n=n+1 where 1" >/dev/null & +tx_async 12 "alter table mt update n=n+1 where 1" >/dev/null +tx_async 11 "commit" >/dev/null +tx_async 12 "commit" >/dev/null +tx_wait 11 +tx_wait 12 -tx 11 "begin transaction" -tx 11 "select 10, n, _part from mt order by n" +tx 13 "begin transaction" +tx 13 "select 10, n, _part from mt order by n" $CLICKHOUSE_CLIENT --database_atomic_wait_for_drop_and_detach_synchronously=0 -q "drop table mt" diff --git a/tests/queries/0_stateless/01170_alter_partition_isolation.reference b/tests/queries/0_stateless/01170_alter_partition_isolation.reference index fc772355a57..f384fc748d4 100644 --- a/tests/queries/0_stateless/01170_alter_partition_isolation.reference +++ b/tests/queries/0_stateless/01170_alter_partition_isolation.reference @@ -1,23 +1,23 @@ -1 1 -2 3 -3 2 -3 4 -4 3 +tx1 1 1 +tx1 2 3 +tx2 3 2 +tx2 3 4 +tx1 4 3 5 3 5 5 -6 3 -6 5 -6 6 -7 8 -8 3 -8 5 -8 7 -8 9 +tx4 6 3 +tx4 6 5 +tx4 6 6 +tx4 7 8 +tx3 8 3 +tx3 8 5 +tx3 8 7 +tx3 8 9 SERIALIZATION_ERROR INVALID_TRANSACTION -9 8 +tx4 9 8 10 8 diff --git a/tests/queries/0_stateless/transactions.lib b/tests/queries/0_stateless/transactions.lib index 2d3eafc784a..be8745e68a5 100755 --- a/tests/queries/0_stateless/transactions.lib +++ b/tests/queries/0_stateless/transactions.lib @@ -1,14 +1,48 @@ #!/usr/bin/env bash +# Useful to run function tx() { tx_num=$1 query=$2 + session="${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}_tx$tx_num" + query_id="${session}_${RANDOM}" url_without_session="https://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTPS}/?" - session="${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}_$tx_num" - url="${url_without_session}session_id=$session&database=$CLICKHOUSE_DATABASE" + url="${url_without_session}session_id=$session&query_id=$query_id&database=$CLICKHOUSE_DATABASE" - ${CLICKHOUSE_CURL} -m 30 -sSk "$url" --data "$query" + ${CLICKHOUSE_CURL} -m 60 -sSk "$url" --data "$query" | sed "s/^/tx$tx_num\t/" } +function tx_wait() { + tx_num=$1 + + session="${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}_tx$tx_num" + + # wait for previous query in transaction + count=0 + while [[ $($CLICKHOUSE_CLIENT -q "SELECT count() FROM system.processes WHERE query_id LIKE '$session%'") -gt 0 ]]; do + sleep 0.5 + count=$((count+1)) + if [ "$count" -gt 120 ]; then + echo "timeout while waiting for $tx_num" + break + fi + done; +} + +function tx_async() +{ + tx_num=$1 + query=$2 + + tx_wait $tx_num + + session="${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}_tx$tx_num" + query_id="${session}_${RANDOM}" + url_without_session="https://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTPS}/?" + url="${url_without_session}session_id=$session&query_id=$query_id&database=$CLICKHOUSE_DATABASE" + + # run query asynchronously + ${CLICKHOUSE_CURL} -m 60 -sSk "$url" --data "$query" | sed "s/^/tx$tx_num\t/" & +} From df12fdf612e65bc1dbc6fc42fc9201c326e73ace Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 22 Dec 2021 19:34:02 +0300 Subject: [PATCH 0134/1647] fix tests --- src/Storages/StorageMergeTree.cpp | 5 ++-- .../0_stateless/01168_mutations_isolation.sh | 2 +- tests/queries/0_stateless/transactions.lib | 23 ++++++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 38b9a5790e6..5a41b81252e 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1218,7 +1218,7 @@ size_t StorageMergeTree::clearOldMutations(bool truncate) auto versions_it = std::lower_bound( part_versions_with_names.begin(), part_versions_with_names.end(), needle); - if (versions_it != part_versions_with_names.begin()) + if (versions_it != part_versions_with_names.begin() || !it->second.tid.isPrehistoric()) { done_count = std::distance(begin_it, it); break; @@ -1235,7 +1235,8 @@ size_t StorageMergeTree::clearOldMutations(bool truncate) { const auto & tid = it->second.tid; if (!tid.isPrehistoric() && !TransactionLog::instance().getCSN(tid)) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot remove mutation {}, because transaction {} is not committed. It's a bug"); + throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot remove mutation {}, because transaction {} is not committed. It's a bug", + it->first, tid); mutations_to_delete.push_back(std::move(it->second)); it = current_mutations_by_version.erase(it); } diff --git a/tests/queries/0_stateless/01168_mutations_isolation.sh b/tests/queries/0_stateless/01168_mutations_isolation.sh index e6049e50131..e034b467358 100755 --- a/tests/queries/0_stateless/01168_mutations_isolation.sh +++ b/tests/queries/0_stateless/01168_mutations_isolation.sh @@ -56,7 +56,7 @@ tx 6 "commit" tx 7 "begin transaction" tx 7 "select 7, n, _part from mt order by n" tx 8 "begin transaction" -tx 8 "alter table mt update n = 0 where 1" & +tx 8 "alter table mt update n = 0 where 1" >/dev/null & $CLICKHOUSE_CLIENT -q "kill mutation where database=currentDatabase() and mutation_id='mutation_15.txt' format Null" wait tx 7 "optimize table mt final" diff --git a/tests/queries/0_stateless/transactions.lib b/tests/queries/0_stateless/transactions.lib index be8745e68a5..d4d349d8827 100755 --- a/tests/queries/0_stateless/transactions.lib +++ b/tests/queries/0_stateless/transactions.lib @@ -1,6 +1,8 @@ #!/usr/bin/env bash -# Useful to run +# shellcheck disable=SC2015 + +# Useful to run queries in parallel sessions function tx() { tx_num=$1 @@ -14,12 +16,24 @@ function tx() ${CLICKHOUSE_CURL} -m 60 -sSk "$url" --data "$query" | sed "s/^/tx$tx_num\t/" } +# Waits for the last query in session to finish function tx_wait() { tx_num=$1 session="${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}_tx$tx_num" + # try get pid of previous query + query_pid="" + tmp_file_name="${CLICKHOUSE_TMP}/tmp_tx_${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}" + query_id_and_pid=$(grep -F "$session" "$tmp_file_name" 2>/dev/null | tail -1) ||: + read -r query_id query_pid <<< "$query_id_and_pid" ||: + # wait for previous query in transaction + if [ -n "$query_pid" ]; then + timeout 5 tail --pid=$query_pid -f /dev/null && return ||: + fi + + # there is no pid (or maybe we got wrong one), so wait using system.processes (it's less reliable) count=0 while [[ $($CLICKHOUSE_CLIENT -q "SELECT count() FROM system.processes WHERE query_id LIKE '$session%'") -gt 0 ]]; do sleep 0.5 @@ -31,6 +45,7 @@ function tx_wait() { done; } +# Wait for previous query in session to finish, starts new one asynchronously function tx_async() { tx_num=$1 @@ -43,6 +58,12 @@ function tx_async() url_without_session="https://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTPS}/?" url="${url_without_session}session_id=$session&query_id=$query_id&database=$CLICKHOUSE_DATABASE" + # We cannot be sure that query will actually start execution and appear in system.processes before the next call to tx_wait + # Also we cannot use global map in bash to store last query_id for each tx_num, so we use tmp file... + tmp_file_name="${CLICKHOUSE_TMP}/tmp_tx_${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}" + # run query asynchronously ${CLICKHOUSE_CURL} -m 60 -sSk "$url" --data "$query" | sed "s/^/tx$tx_num\t/" & + query_pid=$! + echo -e "$query_id\t$query_pid" >> "$tmp_file_name" } From aa515b7126293b57f896fe3ef89ba38c250a318b Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 23 Dec 2021 15:28:40 +0300 Subject: [PATCH 0135/1647] support nested in json type (wip) --- src/Columns/ColumnObject.cpp | 126 ++++++++++-------- src/Columns/ColumnObject.h | 7 +- src/DataTypes/ObjectUtils.cpp | 20 +++ src/DataTypes/ObjectUtils.h | 3 + .../Serializations/SerializationObject.cpp | 7 +- .../0_stateless/01825_type_json_2.reference | 8 +- .../0_stateless/01825_type_json_5.reference | 4 +- .../01825_type_json_nullable.reference | 8 +- 8 files changed, 111 insertions(+), 72 deletions(-) diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 4578480a16c..ec6f9c7b590 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -45,14 +45,19 @@ Array createEmptyArrayField(size_t num_dimensions) return array; } -ColumnPtr recreateColumnWithDefaultValues(const ColumnPtr & column) +ColumnPtr recreateColumnWithDefaultValues( + const ColumnPtr & column, const DataTypePtr & scalar_type, size_t num_dimensions) { - if (const auto * column_array = checkAndGetColumn(column.get())) + const auto * column_array = checkAndGetColumn(column.get()); + if (column_array && num_dimensions) + { return ColumnArray::create( - recreateColumnWithDefaultValues(column_array->getDataPtr()), + recreateColumnWithDefaultValues( + column_array->getDataPtr(), scalar_type, num_dimensions - 1), IColumn::mutate(column_array->getOffsetsPtr())); - else - return column->cloneEmpty()->cloneResized(column->size()); + } + + return createArrayOfType(scalar_type, num_dimensions)->createColumn()->cloneResized(column->size()); } class FieldVisitorReplaceNull : public StaticVisitor @@ -454,16 +459,17 @@ Field ColumnObject::Subcolumn::getLastField() const return (*last_part)[last_part->size() - 1]; } -ColumnObject::Subcolumn ColumnObject::Subcolumn::recreateWithDefaultValues() const +ColumnObject::Subcolumn ColumnObject::Subcolumn::recreateWithDefaultValues(const FieldInfo & field_info) const { Subcolumn new_subcolumn; - new_subcolumn.least_common_type = least_common_type; + new_subcolumn.least_common_type = createArrayOfType(field_info.scalar_type, field_info.num_dimensions); new_subcolumn.is_nullable = is_nullable; new_subcolumn.num_of_defaults_in_prefix = num_of_defaults_in_prefix; new_subcolumn.data.reserve(data.size()); for (const auto & part : data) - new_subcolumn.data.push_back(recreateColumnWithDefaultValues(part)); + new_subcolumn.data.push_back(recreateColumnWithDefaultValues( + part, field_info.scalar_type, field_info.num_dimensions)); return new_subcolumn; } @@ -561,7 +567,6 @@ void ColumnObject::forEachSubcolumn(ColumnCallback callback) void ColumnObject::insert(const Field & field) { - ++num_rows; const auto & object = field.get(); HashSet inserted; @@ -580,13 +585,16 @@ void ColumnObject::insert(const Field & field) for (auto & entry : subcolumns) if (!inserted.has(entry->path.getPath())) entry->column.insertDefault(); + + ++num_rows; } void ColumnObject::insertDefault() { - ++num_rows; for (auto & entry : subcolumns) entry->column.insertDefault(); + + ++num_rows; } Field ColumnObject::operator[](size_t n) const @@ -616,7 +624,6 @@ void ColumnObject::get(size_t n, Field & res) const void ColumnObject::insertRangeFrom(const IColumn & src, size_t start, size_t length) { - num_rows += length; const auto & src_object = assert_cast(src); for (auto & entry : subcolumns) @@ -627,6 +634,7 @@ void ColumnObject::insertRangeFrom(const IColumn & src, size_t start, size_t len entry->column.insertManyDefaults(length); } + num_rows += length; finalize(); } @@ -639,7 +647,7 @@ ColumnPtr ColumnObject::replicate(const Offsets & offsets) const for (const auto & entry : subcolumns) { auto replicated_data = entry->column.data.back()->replicate(offsets)->assumeMutable(); - res_column->addSubcolumn(entry->path, std::move(replicated_data), is_nullable); + res_column->addSubcolumn(entry->path, std::move(replicated_data)); } return res_column; @@ -647,9 +655,10 @@ ColumnPtr ColumnObject::replicate(const Offsets & offsets) const void ColumnObject::popBack(size_t length) { - num_rows -= length; for (auto & entry : subcolumns) entry->column.popBack(length); + + num_rows -= length; } const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const Path & key) const @@ -673,55 +682,56 @@ bool ColumnObject::hasSubcolumn(const Path & key) const return subcolumns.findLeaf(key) != nullptr; } -void ColumnObject::addSubcolumn(const Path & key, size_t new_size, bool check_size) +void ColumnObject::addSubcolumn(const Path & key, MutableColumnPtr && subcolumn) { - if (num_rows == 0) - num_rows = new_size; - - if (check_size && new_size != size()) - throw Exception(ErrorCodes::LOGICAL_ERROR, - "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", - key.getPath(), new_size, size()); - - std::optional inserted; - if (key.hasNested()) - { - const auto * nested_node = subcolumns.findBestMatch(key); - if (nested_node && nested_node->isNested()) - { - const auto * leaf = subcolumns.findLeaf(nested_node, [&](const auto & ) { return true; }); - assert(leaf); - - auto new_subcolumn = leaf->column.recreateWithDefaultValues(); - if (new_subcolumn.size() > new_size) - new_subcolumn.popBack(new_subcolumn.size() - new_size); - else if (new_subcolumn.size() < new_size) - new_subcolumn.insertManyDefaults(new_size - new_subcolumn.size()); - - inserted = subcolumns.add(key, new_subcolumn); - } - } - - if (!inserted.has_value()) - inserted = subcolumns.add(key, Subcolumn(new_size, is_nullable)); - - if (!*inserted) - throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); -} - -void ColumnObject::addSubcolumn(const Path & key, MutableColumnPtr && subcolumn, bool check_size) -{ - if (num_rows == 0) - num_rows = subcolumn->size(); - - if (check_size && subcolumn->size() != size()) - throw Exception(ErrorCodes::LOGICAL_ERROR, - "Cannot add subcolumn '{}' with {} rows to ColumnObject with {} rows", - key.getPath(), subcolumn->size(), size()); - + size_t new_size = subcolumn->size(); bool inserted = subcolumns.add(key, Subcolumn(std::move(subcolumn), is_nullable)); if (!inserted) throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); + + if (num_rows == 0) + num_rows = new_size; +} + +void ColumnObject::addSubcolumn(const Path & key, size_t new_size) +{ + bool inserted = subcolumns.add(key, Subcolumn(new_size, is_nullable)); + if (!inserted) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); + + if (num_rows == 0) + num_rows = new_size; +} + +void ColumnObject::addNestedSubcolumn(const Path & key, const FieldInfo & field_info, size_t new_size) +{ + if (!key.hasNested()) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Cannot add Nested subcolumn, because path doesn't contain Nested"); + + bool inserted = false; + const auto * nested_node = subcolumns.findBestMatch(key); + if (nested_node && nested_node->isNested()) + { + const auto * leaf = subcolumns.findLeaf(nested_node, [&](const auto & ) { return true; }); + assert(leaf); + + auto new_subcolumn = leaf->column.recreateWithDefaultValues(field_info); + if (new_subcolumn.size() > new_size) + new_subcolumn.popBack(new_subcolumn.size() - new_size); + else if (new_subcolumn.size() < new_size) + new_subcolumn.insertManyDefaults(new_size - new_subcolumn.size()); + + inserted = subcolumns.add(key, new_subcolumn); + } + else + inserted = subcolumns.add(key, Subcolumn(new_size, is_nullable)); + + if (!inserted) + throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); + + if (num_rows == 0) + num_rows = new_size; } Paths ColumnObject::getKeys() const diff --git a/src/Columns/ColumnObject.h b/src/Columns/ColumnObject.h index b202e53c4a1..d781cef8997 100644 --- a/src/Columns/ColumnObject.h +++ b/src/Columns/ColumnObject.h @@ -57,7 +57,7 @@ public: void finalize(); Field getLastField() const; - Subcolumn recreateWithDefaultValues() const; + Subcolumn recreateWithDefaultValues(const FieldInfo & field_info) const; IColumn & getFinalizedColumn(); const IColumn & getFinalizedColumn() const; @@ -97,8 +97,9 @@ public: void incrementNumRows() { ++num_rows; } - void addSubcolumn(const Path & key, size_t new_size, bool check_size = false); - void addSubcolumn(const Path & key, MutableColumnPtr && subcolumn, bool check_size = false); + void addSubcolumn(const Path & key, MutableColumnPtr && subcolumn); + void addSubcolumn(const Path & key, size_t new_size); + void addNestedSubcolumn(const Path & key, const FieldInfo & field_info, size_t new_size); const SubcolumnsTree & getSubcolumns() const { return subcolumns; } SubcolumnsTree & getSubcolumns() { return subcolumns; } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 3db5b679757..09c66888b37 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -63,6 +63,26 @@ DataTypePtr getBaseTypeOfArray(const DataTypePtr & type) return last_array ? last_array->getNestedType() : type; } +ColumnPtr getBaseColumnOfArray(const ColumnPtr & column) +{ + const ColumnArray * last_array = nullptr; + const IColumn * current_column = column.get(); + while (const auto * column_array = checkAndGetColumn(current_column)) + { + current_column = &column_array->getData(); + last_array = column_array; + } + + return last_array ? last_array->getDataPtr() : column; +} + +ColumnPtr createArrayOfColumn(ColumnPtr column, size_t num_dimensions) +{ + for (size_t i = 0; i < num_dimensions; ++i) + column = ColumnArray::create(column); + return column; +} + DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension) { for (size_t i = 0; i < dimension; ++i) diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 869f396fde9..421470872d7 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -15,6 +15,9 @@ size_t getNumberOfDimensions(const IColumn & column); DataTypePtr getBaseTypeOfArray(const DataTypePtr & type); DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); +ColumnPtr getBaseColumnOfArray(const ColumnPtr & column); +ColumnPtr createArrayOfColumn(const ColumnPtr & column, size_t num_dimensions); + DataTypePtr getDataTypeByColumn(const IColumn & column); void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, const NamesAndTypesList & extended_storage_columns); void checkObjectHasNoAmbiguosPaths(const Paths & paths); diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 3e0b1ad6e17..4e7b721e2fb 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -124,7 +124,12 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & "Object has ambiguous path: {}", paths[i].getPath()); if (!column_object.hasSubcolumn(paths[i])) - column_object.addSubcolumn(paths[i], column_size); + { + if (paths[i].hasNested()) + column_object.addNestedSubcolumn(paths[i], field_info, column_size); + else + column_object.addSubcolumn(paths[i], column_size); + } auto & subcolumn = column_object.getSubcolumn(paths[i]); assert(subcolumn.size() == column_size); diff --git a/tests/queries/0_stateless/01825_type_json_2.reference b/tests/queries/0_stateless/01825_type_json_2.reference index 6afe2ebcb60..8524035a3a4 100644 --- a/tests/queries/0_stateless/01825_type_json_2.reference +++ b/tests/queries/0_stateless/01825_type_json_2.reference @@ -18,7 +18,7 @@ 1 ['1','2','3.3'] 2 ['a','4','b'] ============ -1 ([11,0],[0,22],[]) Tuple(`k1.k2` Array(Int8), `k1.k3` Array(Int8), `k1.k4` Array(Int8)) -2 ([],[33,0,55],[0,44,66]) Tuple(`k1.k2` Array(Int8), `k1.k3` Array(Int8), `k1.k4` Array(Int8)) -1 [11,0] [0,22] [] -2 [] [33,0,55] [0,44,66] +1 ([(11,0,0),(0,22,0)]) Tuple(k1 Nested(k2 Int8, k3 Int8, k4 Int8)) +2 ([(0,33,0),(0,0,44),(0,55,66)]) Tuple(k1 Nested(k2 Int8, k3 Int8, k4 Int8)) +1 [11,0] [0,22] [0,0] +2 [0,0,0] [33,0,55] [0,44,66] diff --git a/tests/queries/0_stateless/01825_type_json_5.reference b/tests/queries/0_stateless/01825_type_json_5.reference index 6b4dfab0643..4ac0aa26ffd 100644 --- a/tests/queries/0_stateless/01825_type_json_5.reference +++ b/tests/queries/0_stateless/01825_type_json_5.reference @@ -1,5 +1,5 @@ -{"a.c":2,"a.b":1} -{"s":{"a.c":2,"a.b":1}} +{"a.b":1,"a.c":2} +{"s":{"a.b":1,"a.c":2}} 1 [22,33] 2 qqq [44] Tuple(k1 Int8, k2 Tuple(k3 String, k4 Array(Int8))) diff --git a/tests/queries/0_stateless/01825_type_json_nullable.reference b/tests/queries/0_stateless/01825_type_json_nullable.reference index 7f1691a6469..587fb1b1bc9 100644 --- a/tests/queries/0_stateless/01825_type_json_nullable.reference +++ b/tests/queries/0_stateless/01825_type_json_nullable.reference @@ -11,7 +11,7 @@ 3 \N \N 10 4 \N 5 str ============ -1 ([11,NULL],[NULL,22],[]) Tuple(`k1.k2` Array(Nullable(Int8)), `k1.k3` Array(Nullable(Int8)), `k1.k4` Array(Nullable(Int8))) -2 ([],[33,NULL,55],[NULL,44,66]) Tuple(`k1.k2` Array(Nullable(Int8)), `k1.k3` Array(Nullable(Int8)), `k1.k4` Array(Nullable(Int8))) -1 [11,NULL] [NULL,22] [] -2 [] [33,NULL,55] [NULL,44,66] +1 ([(11,NULL,NULL),(NULL,22,NULL)]) Tuple(k1 Nested(k2 Nullable(Int8), k3 Nullable(Int8), k4 Nullable(Int8))) +2 ([(NULL,33,NULL),(NULL,NULL,44),(NULL,55,66)]) Tuple(k1 Nested(k2 Nullable(Int8), k3 Nullable(Int8), k4 Nullable(Int8))) +1 [11,NULL] [NULL,22] [NULL,NULL] +2 [NULL,NULL,NULL] [33,NULL,55] [NULL,44,66] From b54178e7232b9d141b9149c8433864f7bac23122 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 23 Dec 2021 22:02:27 +0300 Subject: [PATCH 0136/1647] fix tests --- src/Storages/MergeTree/MergeTreeData.cpp | 1 + .../0_stateless/01167_isolation_hermitage.sh | 4 ++-- .../01168_mutations_isolation.reference | 24 +++++++++---------- .../0_stateless/01168_mutations_isolation.sh | 8 ++++--- tests/queries/0_stateless/transactions.lib | 9 +++++++ 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 6bfbaa0809c..43b17f3c706 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1688,6 +1688,7 @@ size_t MergeTreeData::clearEmptyParts() if (part->versions.maxtid_lock.load() || !part->versions.isVisible(TransactionLog::instance().getLatestSnapshot())) continue; + LOG_TRACE(log, "Will drop empty part {}", part->name); dropPartNoWaitNoThrow(part->name); ++cleared_count; } diff --git a/tests/queries/0_stateless/01167_isolation_hermitage.sh b/tests/queries/0_stateless/01167_isolation_hermitage.sh index b08ac405ee8..09b34694da6 100755 --- a/tests/queries/0_stateless/01167_isolation_hermitage.sh +++ b/tests/queries/0_stateless/01167_isolation_hermitage.sh @@ -138,7 +138,7 @@ $CLICKHOUSE_CLIENT -q "select 21, * from test order by id" reset_table tx_async 18 "begin transaction" tx_async 19 "begin transaction" -tx_async 18 "select 22, * from test where id = 1" +tx_sync 18 "select 22, * from test where id = 1" tx_async 19 "select 23, * from test where id = 1" tx_async 19 "select 24, * from test where id = 2" tx_async 19 "alter table test update value = 12 where id = 1" @@ -154,7 +154,7 @@ $CLICKHOUSE_CLIENT -q "select 26, * from test order by id" reset_table tx_async 20 "begin transaction" tx_async 21 "begin transaction" -tx_async 20 "select 27, * from test where value % 3 = 0" +tx_sync 20 "select 27, * from test where value % 3 = 0" tx_async 21 "select 28, * from test where value % 3 = 0" tx_async 20 "insert into test (id, value) values (3, 30)" tx_async 21 "insert into test (id, value) values (4, 42)" diff --git a/tests/queries/0_stateless/01168_mutations_isolation.reference b/tests/queries/0_stateless/01168_mutations_isolation.reference index f7a1a707cfe..9daf4ac4c2d 100644 --- a/tests/queries/0_stateless/01168_mutations_isolation.reference +++ b/tests/queries/0_stateless/01168_mutations_isolation.reference @@ -21,17 +21,17 @@ tx7 7 20 all_1_1_0_13 tx7 7 40 all_14_14_0 tx7 7 60 all_7_7_0_13 tx7 7 80 all_12_12_0_13 -tx7 8 20 all_1_14_1_13 -tx7 8 40 all_1_14_1_13 -tx7 8 60 all_1_14_1_13 -tx7 8 80 all_1_14_1_13 +tx7 8 20 all_19_19_0 +tx7 8 40 all_17_17_0 +tx7 8 60 all_18_18_0 +tx7 8 80 all_16_16_0 Serialization error INVALID_TRANSACTION -tx11 9 21 all_1_14_1_17 -tx11 9 41 all_1_14_1_17 -tx11 9 61 all_1_14_1_17 -tx11 9 81 all_1_14_1_17 -tx13 10 22 all_1_14_1_18 -tx13 10 42 all_1_14_1_18 -tx13 10 62 all_1_14_1_18 -tx13 10 82 all_1_14_1_18 +tx11 9 21 all_19_19_0_21 +tx11 9 41 all_17_17_0_21 +tx11 9 61 all_18_18_0_21 +tx11 9 81 all_16_16_0_21 +tx13 10 22 all_19_19_0_22 +tx13 10 42 all_17_17_0_22 +tx13 10 62 all_18_18_0_22 +tx13 10 82 all_16_16_0_22 diff --git a/tests/queries/0_stateless/01168_mutations_isolation.sh b/tests/queries/0_stateless/01168_mutations_isolation.sh index e034b467358..7b56de20c8e 100755 --- a/tests/queries/0_stateless/01168_mutations_isolation.sh +++ b/tests/queries/0_stateless/01168_mutations_isolation.sh @@ -43,6 +43,7 @@ tx 5 "begin transaction" tx 5 "select 4, n, _part from mt order by n" tx 6 "begin transaction" tx 6 "alter table mt delete where n%2=1" +tx 6 "alter table mt drop part 'all_10_10_0_11'" tx 5 "select 5, n, _part from mt order by n" tx 5 "alter table mt drop partition id 'all'" | grep -Eo "SERIALIZATION_ERROR" | uniq tx 6 "select 6, n, _part from mt order by n" @@ -56,10 +57,11 @@ tx 6 "commit" tx 7 "begin transaction" tx 7 "select 7, n, _part from mt order by n" tx 8 "begin transaction" -tx 8 "alter table mt update n = 0 where 1" >/dev/null & +tx_async 8 "alter table mt update n = 0 where 1" >/dev/null $CLICKHOUSE_CLIENT -q "kill mutation where database=currentDatabase() and mutation_id='mutation_15.txt' format Null" -wait -tx 7 "optimize table mt final" +tx 7 "alter table mt detach partition id 'all'" +tx_wait 8 +tx 7 "alter table mt attach partition id 'all'" tx 7 "select 8, n, _part from mt order by n" tx 8 "rollback" tx 10 "begin transaction" diff --git a/tests/queries/0_stateless/transactions.lib b/tests/queries/0_stateless/transactions.lib index d4d349d8827..33b884ba6c8 100755 --- a/tests/queries/0_stateless/transactions.lib +++ b/tests/queries/0_stateless/transactions.lib @@ -67,3 +67,12 @@ function tx_async() query_pid=$! echo -e "$query_id\t$query_pid" >> "$tmp_file_name" } + +# Wait for previous query in session to finish, execute the next one synchronously +function tx_sync() +{ + tx_num=$1 + query=$2 + tx_wait $tx_num + tx $tx_num $query +} From 0fc8c259c79c73c1dde50b7677257cbcb22698bc Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 24 Dec 2021 16:14:17 +0300 Subject: [PATCH 0137/1647] fix tests --- .../01168_mutations_isolation.reference | 24 +++++++++---------- .../0_stateless/01168_mutations_isolation.sh | 5 ++-- tests/queries/0_stateless/transactions.lib | 6 ++--- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/tests/queries/0_stateless/01168_mutations_isolation.reference b/tests/queries/0_stateless/01168_mutations_isolation.reference index 9daf4ac4c2d..f7a1a707cfe 100644 --- a/tests/queries/0_stateless/01168_mutations_isolation.reference +++ b/tests/queries/0_stateless/01168_mutations_isolation.reference @@ -21,17 +21,17 @@ tx7 7 20 all_1_1_0_13 tx7 7 40 all_14_14_0 tx7 7 60 all_7_7_0_13 tx7 7 80 all_12_12_0_13 -tx7 8 20 all_19_19_0 -tx7 8 40 all_17_17_0 -tx7 8 60 all_18_18_0 -tx7 8 80 all_16_16_0 +tx7 8 20 all_1_14_1_13 +tx7 8 40 all_1_14_1_13 +tx7 8 60 all_1_14_1_13 +tx7 8 80 all_1_14_1_13 Serialization error INVALID_TRANSACTION -tx11 9 21 all_19_19_0_21 -tx11 9 41 all_17_17_0_21 -tx11 9 61 all_18_18_0_21 -tx11 9 81 all_16_16_0_21 -tx13 10 22 all_19_19_0_22 -tx13 10 42 all_17_17_0_22 -tx13 10 62 all_18_18_0_22 -tx13 10 82 all_16_16_0_22 +tx11 9 21 all_1_14_1_17 +tx11 9 41 all_1_14_1_17 +tx11 9 61 all_1_14_1_17 +tx11 9 81 all_1_14_1_17 +tx13 10 22 all_1_14_1_18 +tx13 10 42 all_1_14_1_18 +tx13 10 62 all_1_14_1_18 +tx13 10 82 all_1_14_1_18 diff --git a/tests/queries/0_stateless/01168_mutations_isolation.sh b/tests/queries/0_stateless/01168_mutations_isolation.sh index 7b56de20c8e..9a829d7a5e4 100755 --- a/tests/queries/0_stateless/01168_mutations_isolation.sh +++ b/tests/queries/0_stateless/01168_mutations_isolation.sh @@ -57,11 +57,10 @@ tx 6 "commit" tx 7 "begin transaction" tx 7 "select 7, n, _part from mt order by n" tx 8 "begin transaction" -tx_async 8 "alter table mt update n = 0 where 1" >/dev/null +tx_async 8 "alter table mt update n = 0 where 1" >/dev/null $CLICKHOUSE_CLIENT -q "kill mutation where database=currentDatabase() and mutation_id='mutation_15.txt' format Null" -tx 7 "alter table mt detach partition id 'all'" tx_wait 8 -tx 7 "alter table mt attach partition id 'all'" +tx 7 "optimize table mt final" tx 7 "select 8, n, _part from mt order by n" tx 8 "rollback" tx 10 "begin transaction" diff --git a/tests/queries/0_stateless/transactions.lib b/tests/queries/0_stateless/transactions.lib index 33b884ba6c8..521c56754bc 100755 --- a/tests/queries/0_stateless/transactions.lib +++ b/tests/queries/0_stateless/transactions.lib @@ -51,7 +51,7 @@ function tx_async() tx_num=$1 query=$2 - tx_wait $tx_num + tx_wait "$tx_num" session="${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}_tx$tx_num" query_id="${session}_${RANDOM}" @@ -73,6 +73,6 @@ function tx_sync() { tx_num=$1 query=$2 - tx_wait $tx_num - tx $tx_num $query + tx_wait "$tx_num" + tx "$tx_num" "$query" } From e7401d2a5ee3a5a765e50af64af1375c2af15344 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Mon, 27 Dec 2021 11:50:59 +0800 Subject: [PATCH 0138/1647] wrap rocksdb metacache related code with USE_ROCKSDB --- programs/server/Server.cpp | 2 ++ src/Common/CurrentMetrics.cpp | 1 - src/Common/ProfileEvents.cpp | 8 ++++---- src/Functions/checkPartMetaCache.cpp | 9 +++++---- src/Functions/registerFunctionsMiscellaneous.cpp | 6 ++++++ src/Interpreters/Context.cpp | 14 ++++++++++++-- src/Interpreters/Context.h | 10 ++++++++++ src/Processors/CMakeLists.txt | 4 ++-- src/Processors/examples/CMakeLists.txt | 6 ++++-- src/Storages/MergeTree/IMergeTreeDataPart.h | 2 +- .../MergeTree/MergeTreeDataPartCompact.cpp | 1 - src/Storages/MergeTree/MergeTreeDataPartWide.cpp | 1 + src/Storages/MergeTree/MergeTreePartition.cpp | 7 ------- src/Storages/MergeTree/PartMetaCache.cpp | 2 ++ src/Storages/MergeTree/PartMetaCache.h | 4 ++++ .../System/StorageSystemMergeTreeMetaCache.cpp | 6 +++++- .../System/StorageSystemMergeTreeMetaCache.h | 4 ++++ src/Storages/System/attachSystemTables.cpp | 2 +- .../01233_check_part_meta_cache_replicated.sql | 2 +- ..._check_part_meta_cache_replicated_in_atomic.sql | 2 +- 20 files changed, 65 insertions(+), 28 deletions(-) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index fd2dc851ae7..7228608c9f1 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -748,7 +748,9 @@ if (ThreadFuzzer::instance().isEffective()) /// Directory with metadata of tables, which was marked as dropped by Atomic database fs::create_directories(path / "metadata_dropped/"); +#if USE_ROCKSDB fs::create_directories(path / "rocksdb/"); +#endif } diff --git a/src/Common/CurrentMetrics.cpp b/src/Common/CurrentMetrics.cpp index d214952deae..5c9ba177b78 100644 --- a/src/Common/CurrentMetrics.cpp +++ b/src/Common/CurrentMetrics.cpp @@ -78,7 +78,6 @@ M(SyncDrainedConnections, "Number of connections drained synchronously.") \ M(ActiveSyncDrainedConnections, "Number of active connections drained synchronously.") \ M(AsynchronousReadWait, "Number of threads waiting for asynchronous read.") \ - M(ServerStartupSeconds, "Server start seconds") \ namespace CurrentMetrics { diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index af87ba10a31..cb9c6f594a6 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -277,10 +277,10 @@ \ M(AsynchronousReadWaitMicroseconds, "Time spent in waiting for asynchronous reads.") \ \ - M(RocksdbGet, "Number of rocksdb reads(used for file meta cache)") \ - M(RocksdbPut, "Number of rocksdb puts(used for file meta cache)") \ - M(RocksdbDelete, "Number of rocksdb deletes(used for file meta cache)") \ - M(RocksdbSeek, "Number of rocksdb seeks(used for file meta cache)") \ + M(RocksdbGet, "Number of rocksdb reads(used for merge tree metadata cache)") \ + M(RocksdbPut, "Number of rocksdb puts(used for merge tree metadata cache)") \ + M(RocksdbDelete, "Number of rocksdb deletes(used for merge tree metadata cache)") \ + M(RocksdbSeek, "Number of rocksdb seeks(used for merge tree metadata cache)") \ M(MergeTreeMetaCacheHit, "Number of times the read of meta file was done from MergeTree meta cache") \ M(MergeTreeMetaCacheMiss, "Number of times the read of meta file was not done from MergeTree meta cache") \ \ diff --git a/src/Functions/checkPartMetaCache.cpp b/src/Functions/checkPartMetaCache.cpp index e0479a5fdcc..e77b8ca0d50 100644 --- a/src/Functions/checkPartMetaCache.cpp +++ b/src/Functions/checkPartMetaCache.cpp @@ -1,6 +1,8 @@ +#include "config_core.h" + +#if USE_ROCKSDB + #include -#include -#include #include #include #include @@ -14,8 +16,6 @@ #include #include #include -#include -#include #include #include @@ -155,3 +155,4 @@ void registerFunctionCheckPartMetaCache(FunctionFactory & factory) } } +#endif diff --git a/src/Functions/registerFunctionsMiscellaneous.cpp b/src/Functions/registerFunctionsMiscellaneous.cpp index d613d7c85bd..77e3e109081 100644 --- a/src/Functions/registerFunctionsMiscellaneous.cpp +++ b/src/Functions/registerFunctionsMiscellaneous.cpp @@ -80,7 +80,10 @@ void registerFunctionInitialQueryID(FunctionFactory & factory); void registerFunctionServerUUID(FunctionFactory &); void registerFunctionZooKeeperSessionUptime(FunctionFactory &); void registerFunctionGetOSKernelVersion(FunctionFactory &); + +#if USE_ROCKSDB void registerFunctionCheckPartMetaCache(FunctionFactory &); +#endif #if USE_ICU void registerFunctionConvertCharset(FunctionFactory &); @@ -167,7 +170,10 @@ void registerFunctionsMiscellaneous(FunctionFactory & factory) registerFunctionServerUUID(factory); registerFunctionZooKeeperSessionUptime(factory); registerFunctionGetOSKernelVersion(factory); + +#if USE_ROCKSDB registerFunctionCheckPartMetaCache(factory); +#endif #if USE_ICU registerFunctionConvertCharset(factory); diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 81db45c6461..c5a5b3d1d49 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -94,11 +94,13 @@ namespace fs = std::filesystem; namespace ProfileEvents { extern const Event ContextLock; - extern const Event CompiledCacheSizeBytes; + +#if USE_ROCKSDB extern const Event RocksdbPut; extern const Event RocksdbGet; extern const Event RocksdbDelete; extern const Event RocksdbSeek; +#endif } namespace CurrentMetrics @@ -278,8 +280,10 @@ struct ContextSharedPart Context::ConfigReloadCallback config_reload_callback; +#if USE_ROCKSDB /// MergeTree metadata cache stored in rocksdb. MergeTreeMetaCachePtr merge_tree_meta_cache; +#endif ContextSharedPart() : access_control(std::make_unique()), macros(std::make_unique()) @@ -392,12 +396,14 @@ struct ContextSharedPart /// Stop zookeeper connection zookeeper.reset(); - /// Shutdown meta file cache +#if USE_ROCKSDB + /// Shutdown meta file cache if (merge_tree_meta_cache) { merge_tree_meta_cache->shutdown(); merge_tree_meta_cache.reset(); } +#endif } /// Can be removed w/o context lock @@ -441,6 +447,7 @@ SharedContextHolder::SharedContextHolder(std::unique_ptr shar void SharedContextHolder::reset() { shared.reset(); } +#if USE_ROCKSDB MergeTreeMetaCache::Status MergeTreeMetaCache::put(const String & key, const String & value) { auto options = rocksdb::WriteOptions(); @@ -491,6 +498,7 @@ void MergeTreeMetaCache::shutdown() rocksdb->Close(); } } +#endif ContextMutablePtr Context::createGlobal(ContextSharedPart * shared) { @@ -2309,6 +2317,7 @@ void Context::initializeTraceCollector() shared->initializeTraceCollector(getTraceLog()); } +#if USE_ROCKSDB void Context::initializeMergeTreeMetaCache(const String & dir, size_t size) { rocksdb::Options options; @@ -2328,6 +2337,7 @@ void Context::initializeMergeTreeMetaCache(const String & dir, size_t size) } shared->merge_tree_meta_cache = std::make_shared(db); } +#endif bool Context::hasTraceCollector() const { diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index b39e06b0b0f..bc191e80c9a 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -1,5 +1,6 @@ #pragma once +#include "config_core.h" #include #include #include @@ -15,8 +16,11 @@ #include #include #include + +#if USE_ROCKSDB #include #include +#endif #include "config_core.h" @@ -179,6 +183,7 @@ private: std::unique_ptr shared; }; +#if USE_ROCKSDB class MergeTreeMetaCache { public: @@ -199,6 +204,7 @@ private: Poco::Logger * log = &Poco::Logger::get("MergeTreeMetaCache"); }; using MergeTreeMetaCachePtr = std::shared_ptr; +#endif /** A set of known objects that can be used in the query. * Consists of a shared part (always common to all sessions and queries) @@ -699,7 +705,9 @@ public: UInt32 getZooKeeperSessionUptime() const; +#if USE_ROCKSDB MergeTreeMetaCachePtr getMergeTreeMetaCache() const; +#endif #if USE_NURAFT @@ -788,7 +796,9 @@ public: /// Call after initialization before using trace collector. void initializeTraceCollector(); +#if USE_ROCKSDB void initializeMergeTreeMetaCache(const String & dir, size_t size); +#endif bool hasTraceCollector() const; diff --git a/src/Processors/CMakeLists.txt b/src/Processors/CMakeLists.txt index 7c9ad405432..7e965188b4c 100644 --- a/src/Processors/CMakeLists.txt +++ b/src/Processors/CMakeLists.txt @@ -1,4 +1,4 @@ -#if (ENABLE_EXAMPLES) +if (ENABLE_EXAMPLES) add_subdirectory(examples) -#endif () +endif () diff --git a/src/Processors/examples/CMakeLists.txt b/src/Processors/examples/CMakeLists.txt index dcb640c383a..ceb022432a1 100644 --- a/src/Processors/examples/CMakeLists.txt +++ b/src/Processors/examples/CMakeLists.txt @@ -1,2 +1,4 @@ -add_executable (merge_tree_meta_cache merge_tree_meta_cache.cpp) -target_link_libraries (merge_tree_meta_cache PRIVATE dbms) +if (USE_ROCKSDB) + add_executable (merge_tree_meta_cache merge_tree_meta_cache.cpp) + target_link_libraries (merge_tree_meta_cache PRIVATE dbms) +endif() diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index ad072af10a5..0d2b2f57fd0 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -50,7 +50,7 @@ public: PUT, // override set DROP, // remove keys }; - + static String modifyCacheTypeToString(ModifyCacheType type) { switch (type) diff --git a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp index e51b64b3842..e5fbdd5cd19 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp @@ -91,7 +91,6 @@ void MergeTreeDataPartCompact::calculateEachColumnSizes(ColumnSizeByName & /*eac total_size.marks += mrk_checksum->second.file_size; } -// load marks from meta cache void MergeTreeDataPartCompact::loadIndexGranularity() { String full_path = getFullRelativePath(); diff --git a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp index 5132177aa5c..f6efdc5f05c 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp @@ -107,6 +107,7 @@ void MergeTreeDataPartWide::loadIndexGranularity() String full_path = getFullRelativePath(); index_granularity_info.changeGranularityIfRequired(volume->getDisk(), full_path); + if (columns.empty()) throw Exception("No columns in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index 1d4e14c628b..6b5bebd81f6 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -160,13 +160,6 @@ namespace }; } -/* -static std::unique_ptr openForReading(const DiskPtr & disk, const String & path) -{ - size_t file_size = disk->getFileSize(path); - return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); -} -*/ String MergeTreePartition::getID(const MergeTreeData & storage) const { diff --git a/src/Storages/MergeTree/PartMetaCache.cpp b/src/Storages/MergeTree/PartMetaCache.cpp index 33c95d6963e..fe8475d0dda 100644 --- a/src/Storages/MergeTree/PartMetaCache.cpp +++ b/src/Storages/MergeTree/PartMetaCache.cpp @@ -1,5 +1,6 @@ #include "PartMetaCache.h" +#if USE_ROCKSDB #include #include #include @@ -132,3 +133,4 @@ String PartMetaCache::getFullRelativePath() const } } +#endif diff --git a/src/Storages/MergeTree/PartMetaCache.h b/src/Storages/MergeTree/PartMetaCache.h index d6a86d86ba1..5ffd0413c4b 100644 --- a/src/Storages/MergeTree/PartMetaCache.h +++ b/src/Storages/MergeTree/PartMetaCache.h @@ -1,5 +1,8 @@ #pragma once +#include "config_core.h" + +#if USE_ROCKSDB #include #include #include @@ -43,3 +46,4 @@ private: }; } +#endif diff --git a/src/Storages/System/StorageSystemMergeTreeMetaCache.cpp b/src/Storages/System/StorageSystemMergeTreeMetaCache.cpp index 45cf45edb31..f53c32e5a42 100644 --- a/src/Storages/System/StorageSystemMergeTreeMetaCache.cpp +++ b/src/Storages/System/StorageSystemMergeTreeMetaCache.cpp @@ -1,3 +1,6 @@ +#include + +#if USE_ROCKSDB #include #include #include @@ -7,9 +10,9 @@ #include #include #include -#include #include #include + namespace DB { namespace ErrorCodes @@ -137,3 +140,4 @@ void StorageSystemMergeTreeMetaCache::fillData(MutableColumns & res_columns, Con } } +#endif diff --git a/src/Storages/System/StorageSystemMergeTreeMetaCache.h b/src/Storages/System/StorageSystemMergeTreeMetaCache.h index a5f65862243..c8e0f475cd8 100644 --- a/src/Storages/System/StorageSystemMergeTreeMetaCache.h +++ b/src/Storages/System/StorageSystemMergeTreeMetaCache.h @@ -1,5 +1,8 @@ #pragma once +#include "config_core.h" + +#if USE_ROCKSDB #include #include @@ -27,3 +30,4 @@ protected: }; } +#endif diff --git a/src/Storages/System/attachSystemTables.cpp b/src/Storages/System/attachSystemTables.cpp index 0e7d4b624c5..5f5a17069f3 100644 --- a/src/Storages/System/attachSystemTables.cpp +++ b/src/Storages/System/attachSystemTables.cpp @@ -68,7 +68,6 @@ #include #include #include -#include #ifdef OS_LINUX #include @@ -76,6 +75,7 @@ #if USE_ROCKSDB #include +#include #endif diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql index 6d08bb146a5..cb028e77d54 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql @@ -4,7 +4,7 @@ set replication_alter_partitions_sync = 2; DROP DATABASE IF EXISTS test_meta_cache on cluster preonline_hk5; DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache on cluster preonline_hk5; CREATE DATABASE test_meta_cache on cluster preonline_hk5 ENGINE = Ordinary; -CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/test_meta_cache/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; +CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/{database}/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Insert first batch of data. diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql index c41d036cef1..c56e2cb0a99 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql @@ -4,7 +4,7 @@ set replication_alter_partitions_sync = 2; DROP DATABASE IF EXISTS test_meta_cache on cluster preonline_hk5; DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache on cluster preonline_hk5; CREATE DATABASE test_meta_cache on cluster preonline_hk5 ENGINE = Atomic; -CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/test_meta_cache/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; +CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/{database}/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Insert first batch of data. From aa092eeffb8f09d9d65294bfa2a62cacc258e562 Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Mon, 27 Dec 2021 16:42:06 +0300 Subject: [PATCH 0139/1647] proper handle of 'max_rows_to_read' in case of reading in order of sorting key and limit --- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 14 ++++++++++-- .../MergeTree/MergeTreeSelectProcessor.cpp | 5 ----- ...5_read_in_order_max_rows_to_read.reference | 6 +++++ .../02155_read_in_order_max_rows_to_read.sql | 22 +++++++++++++++++++ 4 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.reference create mode 100644 tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.sql diff --git a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index cdedd37e14a..07ac6f5764b 100644 --- a/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -875,12 +875,22 @@ RangesInDataParts MergeTreeDataSelectExecutor::filterPartsByPrimaryKeyAndSkipInd { std::atomic total_rows{0}; + /// Do not check number of read rows if we have reading + /// in order of sorting key with limit. + /// In general case, when there exists WHERE clause + /// it's impossible to estimate number of rows precisely, + /// because we can stop reading at any time. + SizeLimits limits; - if (settings.read_overflow_mode == OverflowMode::THROW && settings.max_rows_to_read) + if (settings.read_overflow_mode == OverflowMode::THROW + && settings.max_rows_to_read + && !query_info.input_order_info) limits = SizeLimits(settings.max_rows_to_read, 0, settings.read_overflow_mode); SizeLimits leaf_limits; - if (settings.read_overflow_mode_leaf == OverflowMode::THROW && settings.max_rows_to_read_leaf) + if (settings.read_overflow_mode_leaf == OverflowMode::THROW + && settings.max_rows_to_read_leaf + && !query_info.input_order_info) leaf_limits = SizeLimits(settings.max_rows_to_read_leaf, 0, settings.read_overflow_mode_leaf); auto mark_cache = context->getIndexMarkCache(); diff --git a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp index 2d4d3617cee..332eb27094a 100644 --- a/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp +++ b/src/Storages/MergeTree/MergeTreeSelectProcessor.cpp @@ -37,11 +37,6 @@ MergeTreeSelectProcessor::MergeTreeSelectProcessor( has_limit_below_one_block(has_limit_below_one_block_), total_rows(data_part->index_granularity.getRowsCountInRanges(all_mark_ranges)) { - /// Actually it means that parallel reading from replicas enabled - /// and we have to collaborate with initiator. - /// In this case we won't set approximate rows, because it will be accounted multiple times - if (!extension_.has_value()) - addTotalRowsApprox(total_rows); ordered_names = header_without_virtual_columns.getNames(); } diff --git a/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.reference b/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.reference new file mode 100644 index 00000000000..b73ab43cabb --- /dev/null +++ b/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.reference @@ -0,0 +1,6 @@ +10 +0 +1 +2 +3 +4 diff --git a/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.sql b/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.sql new file mode 100644 index 00000000000..e82c78b5e42 --- /dev/null +++ b/tests/queries/0_stateless/02155_read_in_order_max_rows_to_read.sql @@ -0,0 +1,22 @@ +DROP TABLE IF EXISTS t_max_rows_to_read; + +CREATE TABLE t_max_rows_to_read (a UInt64) +ENGINE = MergeTree ORDER BY a +SETTINGS index_granularity = 4; + +INSERT INTO t_max_rows_to_read SELECT number FROM numbers(100); + +SET max_threads = 1; + +SELECT a FROM t_max_rows_to_read WHERE a = 10 SETTINGS max_rows_to_read = 4; + +SELECT a FROM t_max_rows_to_read ORDER BY a LIMIT 5 SETTINGS max_rows_to_read = 12; + +-- This should work, but actually it doesn't. Need to investigate. +-- SELECT a FROM t_max_rows_to_read WHERE a > 10 ORDER BY a LIMIT 5 SETTINGS max_rows_to_read = 20; + +SELECT a FROM t_max_rows_to_read ORDER BY a LIMIT 20 FORMAT Null SETTINGS max_rows_to_read = 12; -- { serverError 158 } +SELECT a FROM t_max_rows_to_read WHERE a > 10 ORDER BY a LIMIT 5 FORMAT Null SETTINGS max_rows_to_read = 12; -- { serverError 158 } +SELECT a FROM t_max_rows_to_read WHERE a = 10 OR a = 20 FORMAT Null SETTINGS max_rows_to_read = 4; -- { serverError 158 } + +DROP TABLE t_max_rows_to_read; From 30ffa57bd511f18252bea31e69b5cb6a08c134e0 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 28 Dec 2021 10:17:16 +0800 Subject: [PATCH 0140/1647] remove unused log --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 80d9d869772..5bc125c6787 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1355,7 +1355,6 @@ void IMergeTreeDataPart::assertMetaCacheDropped(bool include_projection) const } } } - // LOG_WARNING(storage.log, "cache of file {} does't belong to any part", file); } } From 7dab7caa9d44e21a5747c997d95252059f75a665 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 28 Dec 2021 11:57:43 +0800 Subject: [PATCH 0141/1647] wrap cache related code with USE_ROCKSDB --- programs/server/Server.cpp | 2 + src/Interpreters/Context.cpp | 2 + src/Processors/examples/CMakeLists.txt | 2 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 126 ++++++++++++++---- src/Storages/MergeTree/IMergeTreeDataPart.h | 22 ++- .../MergeTree/MergeTreeDataPartCompact.cpp | 4 +- .../MergeTree/MergeTreeDataPartInMemory.cpp | 4 +- src/Storages/MergeTree/MergeTreePartition.cpp | 20 ++- src/Storages/MergeTree/MergeTreePartition.h | 10 +- 9 files changed, 156 insertions(+), 36 deletions(-) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index 7228608c9f1..45f7834c96c 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -754,11 +754,13 @@ if (ThreadFuzzer::instance().isEffective()) } +#if USE_ROCKSDB /// initialize meta file cache { size_t size = config().getUInt64("meta_file_cache_size", 256 << 20); global_context->initializeMergeTreeMetaCache(path_str + "/" + "rocksdb", size); } +#endif if (config().has("interserver_http_port") && config().has("interserver_https_port")) throw Exception("Both http and https interserver ports are specified", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG); diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index c5a5b3d1d49..8b8c8f982fd 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2080,10 +2080,12 @@ zkutil::ZooKeeperPtr Context::getAuxiliaryZooKeeper(const String & name) const return zookeeper->second; } +#if USE_ROCKSDB MergeTreeMetaCachePtr Context::getMergeTreeMetaCache() const { return shared->merge_tree_meta_cache; } +#endif void Context::resetZooKeeper() const { diff --git a/src/Processors/examples/CMakeLists.txt b/src/Processors/examples/CMakeLists.txt index ceb022432a1..a07224d4462 100644 --- a/src/Processors/examples/CMakeLists.txt +++ b/src/Processors/examples/CMakeLists.txt @@ -1,4 +1,4 @@ if (USE_ROCKSDB) add_executable (merge_tree_meta_cache merge_tree_meta_cache.cpp) target_link_libraries (merge_tree_meta_cache PRIVATE dbms) -endif() +endif() \ No newline at end of file diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 5bc125c6787..0b5f9f19782 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -63,8 +63,13 @@ static std::unique_ptr openForReading(const DiskPtr & di return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); } +#if USE_ROCKSDB void IMergeTreeDataPart::MinMaxIndex::load( const MergeTreeData & data, const PartMetaCachePtr & cache, const DiskPtr & disk, const String & /*part_path*/) +#else +void IMergeTreeDataPart::MinMaxIndex::load( + const MergeTreeData & data, const DiskPtr & disk, const String & part_path) +#endif { auto metadata_snapshot = data.getInMemoryMetadataPtr(); const auto & partition_key = metadata_snapshot->getPartitionKey(); @@ -76,15 +81,20 @@ void IMergeTreeDataPart::MinMaxIndex::load( hyperrectangle.reserve(minmax_idx_size); for (size_t i = 0; i < minmax_idx_size; ++i) { +#if USE_ROCKSDB String file_name = "minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"; String value; - auto buf = cache->readOrSetMeta(disk, file_name, value); + auto file = cache->readOrSetMeta(disk, file_name, value); +#else + String file_name = fs::path(part_path) / ("minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"); + auto file = openForReading(disk, file_name); +#endif auto serialization = minmax_column_types[i]->getDefaultSerialization(); Field min_val; - serialization->deserializeBinary(min_val, *buf); + serialization->deserializeBinary(min_val, *file); Field max_val; - serialization->deserializeBinary(max_val, *buf); + serialization->deserializeBinary(max_val, *file); // NULL_LAST if (min_val.isNull()) @@ -301,9 +311,10 @@ IMergeTreeDataPart::IMergeTreeDataPart( , index_granularity_info(storage_, part_type_) , part_type(part_type_) , parent_part(parent_part_) +#if USE_ROCKSDB , meta_cache(std::make_shared( storage.getContext()->getMergeTreeMetaCache(), storage.relative_data_path, relative_path, parent_part)) - +#endif { if (parent_part) state = State::Committed; @@ -329,9 +340,10 @@ IMergeTreeDataPart::IMergeTreeDataPart( , index_granularity_info(storage_, part_type_) , part_type(part_type_) , parent_part(parent_part_) +#if USE_ROCKSDB , meta_cache(std::make_shared( storage.getContext()->getMergeTreeMetaCache(), storage.relative_data_path, relative_path, parent_part)) - +#endif { if (parent_part) state = State::Committed; @@ -738,8 +750,13 @@ void IMergeTreeDataPart::loadIndex() } String index_path = fs::path(getFullRelativePath()) / "primary.idx"; + +#if USE_ROCKSDB String value; - auto index_buf = meta_cache->readOrSetMeta(volume->getDisk(), "primary.idx", value); + auto index_file = meta_cache->readOrSetMeta(volume->getDisk(), "primary.idx", value); +#else + auto index_file = openForReading(volume->getDisk(), index_path); +#endif size_t marks_count = index_granularity.getMarksCount(); @@ -749,7 +766,7 @@ void IMergeTreeDataPart::loadIndex() for (size_t i = 0; i < marks_count; ++i) //-V756 for (size_t j = 0; j < key_size; ++j) - key_serializations[j]->deserializeBinary(*loaded_index[j], *index_buf); + key_serializations[j]->deserializeBinary(*loaded_index[j], *index_file); for (size_t i = 0; i < key_size; ++i) { @@ -760,7 +777,7 @@ void IMergeTreeDataPart::loadIndex() ErrorCodes::CANNOT_READ_ALL_DATA); } - if (!index_buf->eof()) + if (!index_file->eof()) throw Exception("Index file " + fullPath(volume->getDisk(), index_path) + " is unexpectedly long", ErrorCodes::EXPECTED_END_OF_FILE); index.assign(std::make_move_iterator(loaded_index.begin()), std::make_move_iterator(loaded_index.end())); @@ -804,18 +821,25 @@ void IMergeTreeDataPart::loadDefaultCompressionCodec() return; } - String v; String path = fs::path(getFullRelativePath()) / DEFAULT_COMPRESSION_CODEC_FILE_NAME; - auto in_buf = meta_cache->readOrSetMeta(volume->getDisk(), DEFAULT_COMPRESSION_CODEC_FILE_NAME, v); - if (!in_buf) + +#if USE_ROCKSDB + String v; + auto file_buf = meta_cache->readOrSetMeta(volume->getDisk(), DEFAULT_COMPRESSION_CODEC_FILE_NAME, v); + if (!file_buf) +#else + if (!volume->getDisk()->exists(path)) +#endif { default_codec = detectDefaultCompressionCodec(); } else { - +#if !USE_ROCKSDB + auto file_buf = openForReading(volume->getDisk(), path); +#endif String codec_line; - readEscapedStringUntilEOL(codec_line, *in_buf); + readEscapedStringUntilEOL(codec_line, *file_buf); ReadBufferFromString buf(codec_line); @@ -912,7 +936,11 @@ void IMergeTreeDataPart::loadPartitionAndMinMaxIndex() { String path = getFullRelativePath(); if (!parent_part) +#if USE_ROCKSDB partition.load(storage, meta_cache, volume->getDisk(), path); +#else + partition.load(storage, volume->getDisk(), path); +#endif if (!isEmpty()) { @@ -920,7 +948,11 @@ void IMergeTreeDataPart::loadPartitionAndMinMaxIndex() // projection parts don't have minmax_idx, and it's always initialized minmax_idx->initialized = true; else +#if USE_ROCKSDB minmax_idx->load(storage, meta_cache, volume->getDisk(), path); +#else + minmax_idx->load(storage, volume->getDisk(), path); +#endif } if (parent_part) return; @@ -950,12 +982,20 @@ void IMergeTreeDataPart::appendFilesOfPartitionAndMinMaxIndex(Strings & files) c void IMergeTreeDataPart::loadChecksums(bool require) { - const String path = fs::path(getFullRelativePath()) / "checksums.txt"; +#if USE_ROCKSDB String value; auto buf = meta_cache->readOrSetMeta(volume->getDisk(), "checksums.txt", value); if (buf) +#else + const String path = fs::path(getFullRelativePath()) / "checksums.txt"; + if (volume->getDisk()->exists(path)) +#endif { + +#if !USE_ROCKSDB + auto buf = openForReading(volume->getDisk(), path); +#endif if (checksums.read(*buf)) { assertEOF(*buf); @@ -1008,6 +1048,7 @@ void IMergeTreeDataPart::loadRowsCount() } else if (storage.format_version >= MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING || part_type == Type::COMPACT || parent_part) { +#if USE_ROCKSDB String v; auto buf = meta_cache->readOrSetMeta(volume->getDisk(), "count.txt", v); if (!buf) @@ -1015,6 +1056,12 @@ void IMergeTreeDataPart::loadRowsCount() readIntText(rows_count, *buf); assertEOF(*buf); +#else + if (!volume->getDisk()->exists(path)) + throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); + + read_rows_count(); +#endif #ifndef NDEBUG /// columns have to be loaded @@ -1117,10 +1164,18 @@ void IMergeTreeDataPart::appendFilesOfRowsCount(Strings & files) const void IMergeTreeDataPart::loadTTLInfos() { String path = fs::path(getFullRelativePath()) / "ttl.txt"; +#if USE_ROCKSDB String v; auto in = meta_cache->readOrSetMeta(volume->getDisk(), "ttl.txt", v); if (in) +#else + if (volume->getDisk()->exists(path)) +#endif { + +#if !USE_ROCKSDB + auto in = openForReading(volume->getDisk(), path); +#endif assertString("ttl format version: ", *in); size_t format_version; readText(format_version, *in); @@ -1150,11 +1205,18 @@ void IMergeTreeDataPart::appendFilesOfTTLInfos(Strings & files) const void IMergeTreeDataPart::loadUUID() { +#if USE_ROCKSDB String v; - String path = fs::path(getFullRelativePath()) / UUID_FILE_NAME; auto in = meta_cache->readOrSetMeta(volume->getDisk(), UUID_FILE_NAME, v); if (in) +#else + String path = fs::path(getFullRelativePath()) / UUID_FILE_NAME; + if (volume->getDisk()->exists(path)) +#endif { +#if !USE_ROCKSDB + auto in = openForReading(volume->getDisk(), path); +#endif readText(uuid, *in); if (uuid == UUIDHelpers::Nil) throw Exception("Unexpected empty " + String(UUID_FILE_NAME) + " in part: " + name, ErrorCodes::LOGICAL_ERROR); @@ -1174,9 +1236,13 @@ void IMergeTreeDataPart::loadColumns(bool require) metadata_snapshot = metadata_snapshot->projections.get(name).metadata; NamesAndTypesList loaded_columns; +#if USE_ROCKSDB String v; auto in = meta_cache->readOrSetMeta(volume->getDisk(), "columns.txt", v); if (!in) +#else + if (!volume->getDisk()->exists(path)) +#endif { /// We can get list of columns only from columns.txt in compact parts. if (require || part_type == Type::COMPACT) @@ -1192,14 +1258,18 @@ void IMergeTreeDataPart::loadColumns(bool require) throw Exception("No columns in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); { - auto out = volume->getDisk()->writeFile(path + ".tmp", 4096); - loaded_columns.writeText(*out); + auto buf = volume->getDisk()->writeFile(path + ".tmp", 4096); + loaded_columns.writeText(*buf); } volume->getDisk()->moveFile(path + ".tmp", path); } else { +#if USE_ROCKSDB loaded_columns.readText(*in); +#else + loaded_columns.readText(*volume->getDisk()->readFile(path)); +#endif for (const auto & column : loaded_columns) { @@ -1278,14 +1348,18 @@ void IMergeTreeDataPart::renameTo(const String & new_relative_path, bool remove_ } } +#if USE_ROCKSDB modifyAllMetaCaches(ModifyCacheType::DROP, true); -// #ifndef NDEBUG assertMetaCacheDropped(true); -// #endif +#endif + volume->getDisk()->setLastModified(from, Poco::Timestamp::fromEpochTime(time(nullptr))); volume->getDisk()->moveDirectory(from, to); relative_path = new_relative_path; + +#if USE_ROCKSDB modifyAllMetaCaches(ModifyCacheType::PUT, true); +#endif SyncGuardPtr sync_guard; if (storage.getSettings()->fsync_part_directory) @@ -1294,7 +1368,7 @@ void IMergeTreeDataPart::renameTo(const String & new_relative_path, bool remove_ storage.lockSharedData(*this); } - +#if USE_ROCKSDB void IMergeTreeDataPart::modifyAllMetaCaches(ModifyCacheType type, bool include_projection) const { Strings files; @@ -1357,6 +1431,7 @@ void IMergeTreeDataPart::assertMetaCacheDropped(bool include_projection) const } } } +#endif std::optional IMergeTreeDataPart::keepSharedDataInDecoupledStorage() const { @@ -1395,10 +1470,10 @@ void IMergeTreeDataPart::remove() const return; } +#if USE_ROCKSDB modifyAllMetaCaches(ModifyCacheType::DROP); -// #ifndef NDEBUG assertMetaCacheDropped(); -// #endif +#endif /** Atomic directory removal: * - rename directory to temporary name; @@ -1503,10 +1578,10 @@ void IMergeTreeDataPart::remove() const void IMergeTreeDataPart::projectionRemove(const String & parent_to, bool keep_shared_data) const { +#if USE_ROCKSDB modifyAllMetaCaches(ModifyCacheType::DROP); -// #ifndef NDEBUG assertMetaCacheDropped(); -// #endif +#endif String to = parent_to + "/" + relative_path; auto disk = volume->getDisk(); @@ -1595,7 +1670,6 @@ String IMergeTreeDataPart::getRelativePathForDetachedPart(const String & prefix) return "detached/" + getRelativePathForPrefix(prefix, /* detached */ true); } - void IMergeTreeDataPart::renameToDetached(const String & prefix) const { renameTo(getRelativePathForDetachedPart(prefix), true); @@ -1848,6 +1922,7 @@ String IMergeTreeDataPart::getZeroLevelPartBlockID() const return info.partition_id + "_" + toString(hash_value.words[0]) + "_" + toString(hash_value.words[1]); } +#if USE_ROCKSDB IMergeTreeDataPart::uint128 IMergeTreeDataPart::getActualChecksumByFile(const String & file_path) const { String file_name = std::filesystem::path(file_path).filename(); @@ -1912,6 +1987,7 @@ void IMergeTreeDataPart::checkMetaCache(Strings & files, std::vector & disk_checksums.push_back(disk_checksum); } } +#endif bool isCompactPart(const MergeTreeDataPartPtr & data_part) { diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 0d2b2f57fd0..0364012bfec 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -45,6 +45,8 @@ class UncompressedCache; class IMergeTreeDataPart : public std::enable_shared_from_this { public: + +#if USE_ROCKSDB enum ModifyCacheType { PUT, // override set @@ -62,6 +64,9 @@ public: } } + using uint128 = PartMetaCache::uint128; +#endif + static constexpr auto DATA_FILE_EXTENSION = ".bin"; using Checksums = MergeTreeDataPartChecksums; @@ -77,7 +82,6 @@ public: using IndexSizeByName = std::unordered_map; using Type = MergeTreeDataPartType; - using uint128 = PartMetaCache::uint128; IMergeTreeDataPart( @@ -157,7 +161,9 @@ public: /// Throws an exception if part is not stored in on-disk format. void assertOnDisk() const; +#if USE_ROCKSDB void assertMetaCacheDropped(bool include_projection = false) const; +#endif void remove() const; @@ -319,7 +325,12 @@ public: { } +#if USE_ROCKSDB void load(const MergeTreeData & data, const PartMetaCachePtr & meta_cache, const DiskPtr & disk, const String & part_path); +#else + void load(const MergeTreeData & data, const DiskPtr & disk, const String & part_path); +#endif + void store(const MergeTreeData & data, const DiskPtr & disk, const String & part_path, Checksums & checksums) const; void store(const Names & column_names, const DataTypes & data_types, const DiskPtr & disk_, const String & part_path, Checksums & checksums) const; @@ -389,7 +400,9 @@ public: String getRelativePathForPrefix(const String & prefix, bool detached = false) const; +#if USE_ROCKSDB virtual void checkMetaCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const; +#endif bool isProjectionPart() const { return parent_part != nullptr; } @@ -462,7 +475,9 @@ protected: std::map> projection_parts; +#if USE_ROCKSDB mutable PartMetaCachePtr meta_cache; +#endif void removeIfNeeded(); @@ -535,13 +550,14 @@ private: void appendFilesOfDefaultCompressionCodec(Strings & files) const; - void modifyAllMetaCaches(ModifyCacheType type, bool include_projection = false) const; - /// Found column without specific compression and return codec /// for this column with default parameters. CompressionCodecPtr detectDefaultCompressionCodec() const; +#if USE_ROCKSDB + void modifyAllMetaCaches(ModifyCacheType type, bool include_projection = false) const; IMergeTreeDataPart::uint128 getActualChecksumByFile(const String & file_path) const; +#endif mutable State state{State::Temporary}; }; diff --git a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp index e5fbdd5cd19..fa9996b3382 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp @@ -192,12 +192,12 @@ MergeTreeDataPartCompact::~MergeTreeDataPartCompact() removeIfNeeded(); } -// Do not cache mark file, because cache other meta files is enough to speed up loading. +/// Do not cache mark file, because cache other meta files is enough to speed up loading. void MergeTreeDataPartCompact::appendFilesOfIndexGranularity(Strings& /* files */) const { } -// find all connected file and do modification +/// find all connected file and do modification Strings MergeTreeDataPartCompact::getIndexGranularityFiles() const { auto marks_file = index_granularity_info.getMarksFilePath("data"); diff --git a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp index 5c21ead3208..f3c4b613078 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp @@ -166,12 +166,12 @@ IMergeTreeDataPart::Checksum MergeTreeDataPartInMemory::calculateBlockChecksum() return checksum; } -// No mark files for part in memory +/// No mark files for part in memory void MergeTreeDataPartInMemory::appendFilesOfIndexGranularity(Strings& /* files */) const { } -// No mark files for part in memory +/// No mark files for part in memory Strings MergeTreeDataPartInMemory::getIndexGranularityFiles() const { return {}; diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index 6b5bebd81f6..88a418b1129 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -160,6 +160,13 @@ namespace }; } +#if !USE_ROCKSDB +static std::unique_ptr openForReading(const DiskPtr & disk, const String & path) +{ + size_t file_size = disk->getFileSize(path); + return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); +} +#endif String MergeTreePartition::getID(const MergeTreeData & storage) const { @@ -350,7 +357,11 @@ void MergeTreePartition::serializeText(const MergeTreeData & storage, WriteBuffe } } +#if USE_ROCKSDB void MergeTreePartition::load(const MergeTreeData & storage, const PartMetaCachePtr & meta_cache, const DiskPtr & disk, const String & part_path) +#else +void MergeTreePartition::load(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path) +#endif { auto metadata_snapshot = storage.getInMemoryMetadataPtr(); if (!metadata_snapshot->hasPartitionKey()) @@ -359,11 +370,16 @@ void MergeTreePartition::load(const MergeTreeData & storage, const PartMetaCache const auto & partition_key_sample = adjustPartitionKey(metadata_snapshot, storage.getContext()).sample_block; auto partition_file_path = part_path + "partition.dat"; +#if USE_ROCKSDB String v; - auto buf = meta_cache->readOrSetMeta(disk, "partition.dat", v); + auto file = meta_cache->readOrSetMeta(disk, "partition.dat", v); +#else + auto file = openForReading(disk, partition_file_path); +#endif + value.resize(partition_key_sample.columns()); for (size_t i = 0; i < partition_key_sample.columns(); ++i) - partition_key_sample.getByPosition(i).type->getDefaultSerialization()->deserializeBinary(value[i], *buf); + partition_key_sample.getByPosition(i).type->getDefaultSerialization()->deserializeBinary(value[i], *file); } void MergeTreePartition::store(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path, MergeTreeDataPartChecksums & checksums) const diff --git a/src/Storages/MergeTree/MergeTreePartition.h b/src/Storages/MergeTree/MergeTreePartition.h index b8b5b301219..4920e658163 100644 --- a/src/Storages/MergeTree/MergeTreePartition.h +++ b/src/Storages/MergeTree/MergeTreePartition.h @@ -4,9 +4,12 @@ #include #include #include -#include #include +#if USE_ROCKSDB +#include +#endif + namespace DB { @@ -38,7 +41,12 @@ public: void serializeText(const MergeTreeData & storage, WriteBuffer & out, const FormatSettings & format_settings) const; +#if USE_ROCKSDB void load(const MergeTreeData & storage, const PartMetaCachePtr & meta_cache, const DiskPtr & disk, const String & part_path); +#else + void load(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path); +#endif + void store(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path, MergeTreeDataPartChecksums & checksums) const; void store(const Block & partition_key_sample, const DiskPtr & disk, const String & part_path, MergeTreeDataPartChecksums & checksums) const; From 22b9f9d3e5062380335043b6fa2197cc797e7773 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 28 Dec 2021 15:45:48 +0800 Subject: [PATCH 0142/1647] fix ut --- src/Interpreters/Context.h | 5 ++-- ...01233_check_part_meta_cache_replicated.sql | 28 ++++++++++--------- ...k_part_meta_cache_replicated_in_atomic.sql | 28 ++++++++++--------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index bc191e80c9a..dcc24999944 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -1,6 +1,5 @@ #pragma once -#include "config_core.h" #include #include #include @@ -17,13 +16,13 @@ #include #include +#include "config_core.h" + #if USE_ROCKSDB #include #include #endif -#include "config_core.h" - #include #include #include diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql index cb028e77d54..7722bba71dd 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql @@ -1,10 +1,12 @@ +-- Tags: zookeeper + -- Create table under database with engine ordinary. set mutations_sync = 1; set replication_alter_partitions_sync = 2; -DROP DATABASE IF EXISTS test_meta_cache on cluster preonline_hk5; -DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache on cluster preonline_hk5; -CREATE DATABASE test_meta_cache on cluster preonline_hk5 ENGINE = Ordinary; -CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/{database}/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; +DROP DATABASE IF EXISTS test_meta_cache; +DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache; +CREATE DATABASE test_meta_cache ENGINE = Ordinary; +CREATE TABLE test_meta_cache.check_part_meta_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Insert first batch of data. @@ -43,21 +45,21 @@ optimize table test_meta_cache.check_part_meta_cache FINAL; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 add column v3 UInt64; +alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Delete column. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 drop column v1; +alter table test_meta_cache.check_part_meta_cache drop column v1; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 10 YEAR; +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 15 YEAR; +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); @@ -66,8 +68,8 @@ truncate table test_meta_cache.check_part_meta_cache; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Recreate table with projection. -drop table if exists test_meta_cache.check_part_meta_cache on cluster preonline_hk5; -CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; +drop table if exists test_meta_cache.check_part_meta_cache ; +CREATE TABLE test_meta_cache.check_part_meta_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; -- Insert first batch of data. INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -105,17 +107,17 @@ optimize table test_meta_cache.check_part_meta_cache FINAL; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 add column v3 UInt64; +alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 10 YEAR; +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 15 YEAR; +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql index c56e2cb0a99..4dcc4478139 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql @@ -1,10 +1,12 @@ +-- Tags: zookeeper + -- Create table under database with engine ordinary. set mutations_sync = 1; set replication_alter_partitions_sync = 2; -DROP DATABASE IF EXISTS test_meta_cache on cluster preonline_hk5; -DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache on cluster preonline_hk5; -CREATE DATABASE test_meta_cache on cluster preonline_hk5 ENGINE = Atomic; -CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/{database}/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; +DROP DATABASE IF EXISTS test_meta_cache ; +DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache ; +CREATE DATABASE test_meta_cache ENGINE = Atomic; +CREATE TABLE test_meta_cache.check_part_meta_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Insert first batch of data. @@ -43,21 +45,21 @@ optimize table test_meta_cache.check_part_meta_cache FINAL; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 add column v3 UInt64; +alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Delete column. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 drop column v1; +alter table test_meta_cache.check_part_meta_cache drop column v1; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 10 YEAR; +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 15 YEAR; +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); @@ -66,8 +68,8 @@ truncate table test_meta_cache.check_part_meta_cache; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Recreate table with projection. -drop table if exists test_meta_cache.check_part_meta_cache on cluster preonline_hk5; -CREATE TABLE test_meta_cache.check_part_meta_cache on cluster preonline_hk5 ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; +drop table if exists test_meta_cache.check_part_meta_cache ; +CREATE TABLE test_meta_cache.check_part_meta_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; -- Insert first batch of data. INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -105,17 +107,17 @@ optimize table test_meta_cache.check_part_meta_cache FINAL; with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 add column v3 UInt64; +alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 10 YEAR; +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache on cluster preonline_hk5 modify TTL p + INTERVAL 15 YEAR; +alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); From 2a1fe52b9f2136405c3a5b2497e36be3528b4e74 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 28 Dec 2021 18:06:13 +0800 Subject: [PATCH 0143/1647] rename symbols && fix uts && add setting use_metadata_cache --- programs/server/Server.cpp | 2 +- src/Common/ProfileEvents.cpp | 14 +- ...taCache.cpp => checkPartMetadataCache.cpp} | 79 +++-- .../registerFunctionsMiscellaneous.cpp | 4 +- src/Interpreters/Context.cpp | 48 +-- src/Interpreters/Context.h | 16 +- src/Processors/examples/CMakeLists.txt | 4 +- ...ache.cpp => merge_tree_metadata_cache.cpp} | 6 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 286 ++++++++++++------ src/Storages/MergeTree/IMergeTreeDataPart.h | 17 +- src/Storages/MergeTree/MergeTreeData.cpp | 11 + src/Storages/MergeTree/MergeTreePartition.cpp | 18 +- src/Storages/MergeTree/MergeTreePartition.h | 4 +- src/Storages/MergeTree/MergeTreeSettings.h | 1 + ...artMetaCache.cpp => PartMetadataCache.cpp} | 22 +- .../{PartMetaCache.h => PartMetadataCache.h} | 10 +- ...> StorageSystemMergeTreeMetadataCache.cpp} | 14 +- ... => StorageSystemMergeTreeMetadataCache.h} | 8 +- src/Storages/System/attachSystemTables.cpp | 4 +- .../01233_check_part_meta_cache.sql | 140 ++++----- .../01233_check_part_meta_cache_in_atomic.sql | 140 ++++----- ...01233_check_part_meta_cache_replicated.sql | 139 ++++----- ...k_part_meta_cache_replicated_in_atomic.sql | 139 ++++----- 23 files changed, 634 insertions(+), 492 deletions(-) rename src/Functions/{checkPartMetaCache.cpp => checkPartMetadataCache.cpp} (68%) rename src/Processors/examples/{merge_tree_meta_cache.cpp => merge_tree_metadata_cache.cpp} (86%) rename src/Storages/MergeTree/{PartMetaCache.cpp => PartMetadataCache.cpp} (80%) rename src/Storages/MergeTree/{PartMetaCache.h => PartMetadataCache.h} (76%) rename src/Storages/System/{StorageSystemMergeTreeMetaCache.cpp => StorageSystemMergeTreeMetadataCache.cpp} (84%) rename src/Storages/System/{StorageSystemMergeTreeMetaCache.h => StorageSystemMergeTreeMetadataCache.h} (55%) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index 6cfdc2376d6..1e10af9dc07 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -823,7 +823,7 @@ if (ThreadFuzzer::instance().isEffective()) /// initialize meta file cache { size_t size = config().getUInt64("meta_file_cache_size", 256 << 20); - global_context->initializeMergeTreeMetaCache(path_str + "/" + "rocksdb", size); + global_context->initializeMergeTreeMetadataCache(path_str + "/" + "rocksdb", size); } #endif diff --git a/src/Common/ProfileEvents.cpp b/src/Common/ProfileEvents.cpp index e0383da29f4..45089312ed1 100644 --- a/src/Common/ProfileEvents.cpp +++ b/src/Common/ProfileEvents.cpp @@ -277,15 +277,15 @@ \ M(AsynchronousReadWaitMicroseconds, "Time spent in waiting for asynchronous reads.") \ \ - M(RocksdbGet, "Number of rocksdb reads(used for merge tree metadata cache)") \ - M(RocksdbPut, "Number of rocksdb puts(used for merge tree metadata cache)") \ - M(RocksdbDelete, "Number of rocksdb deletes(used for merge tree metadata cache)") \ - M(RocksdbSeek, "Number of rocksdb seeks(used for merge tree metadata cache)") \ - M(MergeTreeMetaCacheHit, "Number of times the read of meta file was done from MergeTree meta cache") \ - M(MergeTreeMetaCacheMiss, "Number of times the read of meta file was not done from MergeTree meta cache") \ - \ M(MainConfigLoads, "Number of times the main configuration was reloaded.") \ \ + M(MergeTreeMetadataCacheGet, "Number of rocksdb reads(used for merge tree metadata cache)") \ + M(MergeTreeMetadataCachePut, "Number of rocksdb puts(used for merge tree metadata cache)") \ + M(MergeTreeMetadataCacheDelete, "Number of rocksdb deletes(used for merge tree metadata cache)") \ + M(MergeTreeMetadataCacheSeek, "Number of rocksdb seeks(used for merge tree metadata cache)") \ + M(MergeTreeMetadataCacheHit, "Number of times the read of meta file was done from MergeTree metadata cache") \ + M(MergeTreeMetadataCacheMiss, "Number of times the read of meta file was not done from MergeTree metadata cache") \ + \ namespace ProfileEvents { diff --git a/src/Functions/checkPartMetaCache.cpp b/src/Functions/checkPartMetadataCache.cpp similarity index 68% rename from src/Functions/checkPartMetaCache.cpp rename to src/Functions/checkPartMetadataCache.cpp index e77b8ca0d50..2883424f996 100644 --- a/src/Functions/checkPartMetaCache.cpp +++ b/src/Functions/checkPartMetadataCache.cpp @@ -21,14 +21,13 @@ namespace DB { - namespace ErrorCodes { extern const int ILLEGAL_TYPE_OF_ARGUMENT; extern const int ILLEGAL_COLUMN; } -class FunctionCheckPartMetaCache: public IFunction, WithContext +class FunctionCheckPartMetadataCache : public IFunction, WithContext { public: using uint128 = IMergeTreeDataPart::uint128; @@ -37,22 +36,18 @@ public: using DataPartStates = MergeTreeData::DataPartStates; - static constexpr auto name = "checkPartMetaCache"; - static FunctionPtr create(ContextPtr context_) - { - return std::make_shared(context_); - } + static constexpr auto name = "checkPartMetadataCache"; + static FunctionPtr create(ContextPtr context_) { return std::make_shared(context_); } - static constexpr DataPartStates part_states = { - DataPartState::Committed, - DataPartState::Temporary, - DataPartState::PreCommitted, - DataPartState::Outdated, - DataPartState::Deleting, - DataPartState::DeleteOnDestroy - }; + static constexpr DataPartStates part_states + = {DataPartState::Committed, + DataPartState::Temporary, + DataPartState::PreCommitted, + DataPartState::Outdated, + DataPartState::Deleting, + DataPartState::DeleteOnDestroy}; - explicit FunctionCheckPartMetaCache(ContextPtr context_): WithContext(context_) {} + explicit FunctionCheckPartMetadataCache(ContextPtr context_) : WithContext(context_) { } String getName() const override { return name; } @@ -76,44 +71,44 @@ public: DataTypePtr cache_checksum_type = std::make_unique(32); DataTypePtr disk_checksum_type = std::make_unique(32); DataTypePtr match_type = std::make_unique(); - DataTypePtr tuple_type = std::make_unique(DataTypes{key_type, state_type, cache_checksum_type, disk_checksum_type, match_type}); + DataTypePtr tuple_type + = std::make_unique(DataTypes{key_type, state_type, cache_checksum_type, disk_checksum_type, match_type}); return std::make_shared(tuple_type); } ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override { - // get database name + /// Get database name const auto * arg_database = arguments[0].column.get(); - const ColumnString * column_database = checkAndGetColumnConstData(arg_database); - if (! column_database) + const ColumnString * column_database = checkAndGetColumnConstData(arg_database); + if (!column_database) throw Exception("The first argument of function " + getName() + " must be constant String", ErrorCodes::ILLEGAL_COLUMN); String database_name = column_database->getDataAt(0).toString(); - // get table name + /// Get table name const auto * arg_table = arguments[1].column.get(); const ColumnString * column_table = checkAndGetColumnConstData(arg_table); - if (! column_table) + if (!column_table) throw Exception("The second argument of function " + getName() + " must be constant String", ErrorCodes::ILLEGAL_COLUMN); String table_name = column_table->getDataAt(0).toString(); - // get storage + /// Get storage StorageID storage_id(database_name, table_name); auto storage = DatabaseCatalog::instance().getTable(storage_id, getContext()); auto data = std::dynamic_pointer_cast(storage); - if (! data) + if (!data || !data->getSettings()->use_metadata_cache) throw Exception("The table in function " + getName() + " must be in MergeTree Family", ErrorCodes::ILLEGAL_COLUMN); - // fill in result + /// Fill in result auto col_result = result_type->createColumn(); - auto& col_arr = assert_cast(*col_result); - col_arr.reserve(1); - auto& col_tuple = assert_cast(col_arr.getData()); + auto & col_arr = assert_cast(*col_result); + auto & col_tuple = assert_cast(col_arr.getData()); col_tuple.reserve(data->fileNumberOfDataParts(part_states)); - auto& col_key = assert_cast(col_tuple.getColumn(0)); - auto& col_state = assert_cast(col_tuple.getColumn(1)); - auto& col_cache_checksum = assert_cast(col_tuple.getColumn(2)); - auto& col_disk_checksum = assert_cast(col_tuple.getColumn(3)); - auto& col_match = assert_cast(col_tuple.getColumn(4)); + auto & col_key = assert_cast(col_tuple.getColumn(0)); + auto & col_state = assert_cast(col_tuple.getColumn(1)); + auto & col_cache_checksum = assert_cast(col_tuple.getColumn(2)); + auto & col_disk_checksum = assert_cast(col_tuple.getColumn(3)); + auto & col_match = assert_cast(col_tuple.getColumn(4)); auto parts = data->getDataParts(part_states); for (const auto & part : parts) executePart(part, col_key, col_state, col_cache_checksum, col_disk_checksum, col_match); @@ -121,21 +116,26 @@ public: return result_type->createColumnConst(input_rows_count, col_arr[0]); } - static void executePart(const DataPartPtr& part, ColumnString& col_key, ColumnString& col_state, - ColumnFixedString& col_cache_checksum, ColumnFixedString& col_disk_checksum, ColumnUInt8& col_match) + static void executePart( + const DataPartPtr & part, + ColumnString & col_key, + ColumnString & col_state, + ColumnFixedString & col_cache_checksum, + ColumnFixedString & col_disk_checksum, + ColumnUInt8 & col_match) { Strings keys; auto state_view = part->stateString(); String state(state_view.data(), state_view.size()); std::vector cache_checksums; std::vector disk_checksums; - uint8_t match = 0; + uint8_t match = 0; size_t file_number = part->fileNumberOfColumnsChecksumsIndexes(); keys.reserve(file_number); cache_checksums.reserve(file_number); disk_checksums.reserve(file_number); - part->checkMetaCache(keys, cache_checksums, disk_checksums); + part->checkMetadataCache(keys, cache_checksums, disk_checksums); for (size_t i = 0; i < keys.size(); ++i) { col_key.insert(keys[i]); @@ -149,10 +149,9 @@ public: } }; -void registerFunctionCheckPartMetaCache(FunctionFactory & factory) +void registerFunctionCheckPartMetadataCache(FunctionFactory & factory) { - factory.registerFunction(); + factory.registerFunction(); } - } #endif diff --git a/src/Functions/registerFunctionsMiscellaneous.cpp b/src/Functions/registerFunctionsMiscellaneous.cpp index 77e3e109081..297e6dfb452 100644 --- a/src/Functions/registerFunctionsMiscellaneous.cpp +++ b/src/Functions/registerFunctionsMiscellaneous.cpp @@ -82,7 +82,7 @@ void registerFunctionZooKeeperSessionUptime(FunctionFactory &); void registerFunctionGetOSKernelVersion(FunctionFactory &); #if USE_ROCKSDB -void registerFunctionCheckPartMetaCache(FunctionFactory &); +void registerFunctionCheckPartMetadataCache(FunctionFactory &); #endif #if USE_ICU @@ -172,7 +172,7 @@ void registerFunctionsMiscellaneous(FunctionFactory & factory) registerFunctionGetOSKernelVersion(factory); #if USE_ROCKSDB - registerFunctionCheckPartMetaCache(factory); + registerFunctionCheckPartMetadataCache(factory); #endif #if USE_ICU diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 4d9c571232e..4897cdccac9 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -97,10 +97,10 @@ namespace ProfileEvents extern const Event ContextLock; #if USE_ROCKSDB - extern const Event RocksdbPut; - extern const Event RocksdbGet; - extern const Event RocksdbDelete; - extern const Event RocksdbSeek; + extern const Event MergeTreeMetadataCachePut; + extern const Event MergeTreeMetadataCacheGet; + extern const Event MergeTreeMetadataCacheDelete; + extern const Event MergeTreeMetadataCacheSeek; #endif } @@ -284,7 +284,7 @@ struct ContextSharedPart #if USE_ROCKSDB /// MergeTree metadata cache stored in rocksdb. - MergeTreeMetaCachePtr merge_tree_meta_cache; + MergeTreeMetadataCachePtr merge_tree_metadata_cache; #endif ContextSharedPart() @@ -400,10 +400,10 @@ struct ContextSharedPart #if USE_ROCKSDB /// Shutdown meta file cache - if (merge_tree_meta_cache) + if (merge_tree_metadata_cache) { - merge_tree_meta_cache->shutdown(); - merge_tree_meta_cache.reset(); + merge_tree_metadata_cache->shutdown(); + merge_tree_metadata_cache.reset(); } #endif } @@ -450,32 +450,32 @@ SharedContextHolder::SharedContextHolder(std::unique_ptr shar void SharedContextHolder::reset() { shared.reset(); } #if USE_ROCKSDB -MergeTreeMetaCache::Status MergeTreeMetaCache::put(const String & key, const String & value) +MergeTreeMetadataCache::Status MergeTreeMetadataCache::put(const String & key, const String & value) { auto options = rocksdb::WriteOptions(); auto status = rocksdb->Put(options, key, value); - ProfileEvents::increment(ProfileEvents::RocksdbPut); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCachePut); return status; } -MergeTreeMetaCache::Status MergeTreeMetaCache::del(const String & key) +MergeTreeMetadataCache::Status MergeTreeMetadataCache::del(const String & key) { auto options = rocksdb::WriteOptions(); auto status = rocksdb->Delete(options, key); - ProfileEvents::increment(ProfileEvents::RocksdbDelete); - LOG_TRACE(log, "Delete key:{} from MergeTreeMetaCache status:{}", key, status.ToString()); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheDelete); + LOG_TRACE(log, "Delete key:{} from MergeTreeMetadataCache status:{}", key, status.ToString()); return status; } -MergeTreeMetaCache::Status MergeTreeMetaCache::get(const String & key, String & value) +MergeTreeMetadataCache::Status MergeTreeMetadataCache::get(const String & key, String & value) { auto status = rocksdb->Get(rocksdb::ReadOptions(), key, &value); - ProfileEvents::increment(ProfileEvents::RocksdbGet); - LOG_TRACE(log, "Get key:{} from MergeTreeMetaCache status:{}", key, status.ToString()); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheGet); + LOG_TRACE(log, "Get key:{} from MergeTreeMetadataCache status:{}", key, status.ToString()); return status; } -void MergeTreeMetaCache::getByPrefix(const String & prefix, Strings & keys, Strings & values) +void MergeTreeMetadataCache::getByPrefix(const String & prefix, Strings & keys, Strings & values) { auto * it = rocksdb->NewIterator(rocksdb::ReadOptions()); rocksdb::Slice target(prefix); @@ -489,11 +489,11 @@ void MergeTreeMetaCache::getByPrefix(const String & prefix, Strings & keys, Stri keys.emplace_back(key.data(), key.size()); values.emplace_back(value.data(), value.size()); } - LOG_TRACE(log, "Seek with prefix:{} from MergeTreeMetaCache items:{}", prefix, keys.size()); - ProfileEvents::increment(ProfileEvents::RocksdbSeek); + LOG_TRACE(log, "Seek with prefix:{} from MergeTreeMetadataCache items:{}", prefix, keys.size()); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheSeek); } -void MergeTreeMetaCache::shutdown() +void MergeTreeMetadataCache::shutdown() { if (rocksdb) { @@ -2083,9 +2083,9 @@ zkutil::ZooKeeperPtr Context::getAuxiliaryZooKeeper(const String & name) const } #if USE_ROCKSDB -MergeTreeMetaCachePtr Context::getMergeTreeMetaCache() const +MergeTreeMetadataCachePtr Context::getMergeTreeMetadataCache() const { - return shared->merge_tree_meta_cache; + return shared->merge_tree_metadata_cache; } #endif @@ -2333,7 +2333,7 @@ void Context::initializeTraceCollector() } #if USE_ROCKSDB -void Context::initializeMergeTreeMetaCache(const String & dir, size_t size) +void Context::initializeMergeTreeMetadataCache(const String & dir, size_t size) { rocksdb::Options options; rocksdb::BlockBasedTableOptions table_options; @@ -2350,7 +2350,7 @@ void Context::initializeMergeTreeMetaCache(const String & dir, size_t size) String message = "Fail to open rocksdb path at: " + dir + " status:" + status.ToString(); throw Exception(message, ErrorCodes::SYSTEM_ERROR); } - shared->merge_tree_meta_cache = std::make_shared(db); + shared->merge_tree_metadata_cache = std::make_shared(db); } #endif diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index e579fc9c4bf..ea606fdea4e 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -183,14 +183,14 @@ private: }; #if USE_ROCKSDB -class MergeTreeMetaCache +class MergeTreeMetadataCache { public: using Status = rocksdb::Status; - explicit MergeTreeMetaCache(rocksdb::DB * rocksdb_) : rocksdb{rocksdb_} { } - MergeTreeMetaCache(const MergeTreeMetaCache &) = delete; - MergeTreeMetaCache & operator=(const MergeTreeMetaCache &) = delete; + explicit MergeTreeMetadataCache(rocksdb::DB * rocksdb_) : rocksdb{rocksdb_} { } + MergeTreeMetadataCache(const MergeTreeMetadataCache &) = delete; + MergeTreeMetadataCache & operator=(const MergeTreeMetadataCache &) = delete; Status put(const String & key, const String & value); Status del(const String & key); @@ -200,9 +200,9 @@ public: void shutdown(); private: std::unique_ptr rocksdb; - Poco::Logger * log = &Poco::Logger::get("MergeTreeMetaCache"); + Poco::Logger * log = &Poco::Logger::get("MergeTreeMetadataCache"); }; -using MergeTreeMetaCachePtr = std::shared_ptr; +using MergeTreeMetadataCachePtr = std::shared_ptr; #endif /** A set of known objects that can be used in the query. @@ -705,7 +705,7 @@ public: UInt32 getZooKeeperSessionUptime() const; #if USE_ROCKSDB - MergeTreeMetaCachePtr getMergeTreeMetaCache() const; + MergeTreeMetadataCachePtr getMergeTreeMetadataCache() const; #endif @@ -799,7 +799,7 @@ public: void initializeTraceCollector(); #if USE_ROCKSDB - void initializeMergeTreeMetaCache(const String & dir, size_t size); + void initializeMergeTreeMetadataCache(const String & dir, size_t size); #endif bool hasTraceCollector() const; diff --git a/src/Processors/examples/CMakeLists.txt b/src/Processors/examples/CMakeLists.txt index a07224d4462..2b6b9128e4c 100644 --- a/src/Processors/examples/CMakeLists.txt +++ b/src/Processors/examples/CMakeLists.txt @@ -1,4 +1,4 @@ if (USE_ROCKSDB) - add_executable (merge_tree_meta_cache merge_tree_meta_cache.cpp) - target_link_libraries (merge_tree_meta_cache PRIVATE dbms) + add_executable (merge_tree_metadata_cache merge_tree_metadata_cache.cpp) + target_link_libraries (merge_tree_metadata_cache PRIVATE dbms) endif() \ No newline at end of file diff --git a/src/Processors/examples/merge_tree_meta_cache.cpp b/src/Processors/examples/merge_tree_metadata_cache.cpp similarity index 86% rename from src/Processors/examples/merge_tree_meta_cache.cpp rename to src/Processors/examples/merge_tree_metadata_cache.cpp index a394323bcb7..c726eb7ce5a 100644 --- a/src/Processors/examples/merge_tree_meta_cache.cpp +++ b/src/Processors/examples/merge_tree_metadata_cache.cpp @@ -7,13 +7,13 @@ int main() auto shared_context = Context::createShared(); auto global_context = Context::createGlobal(shared_context.get()); global_context->makeGlobalContext(); - global_context->initializeMergeTreeMetaCache("./db/", 256 << 20); + global_context->initializeMergeTreeMetadataCache("./db/", 256 << 20); - auto cache = global_context->getMergeTreeMetaCache(); + auto cache = global_context->getMergeTreeMetadataCache(); std::vector files = {"columns.txt", "checksums.txt", "primary.idx", "count.txt", "partition.dat", "minmax_p.idx", "default_compression_codec.txt"}; - String prefix = "data/test_meta_cache/check_part_meta_cache/201806_1_1_0_4/"; + String prefix = "data/test_metadata_cache/check_part_metadata_cache/201806_1_1_0_4/"; for (const auto & file : files) { diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 0b5f9f19782..b29886265f6 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -65,7 +65,7 @@ static std::unique_ptr openForReading(const DiskPtr & di #if USE_ROCKSDB void IMergeTreeDataPart::MinMaxIndex::load( - const MergeTreeData & data, const PartMetaCachePtr & cache, const DiskPtr & disk, const String & /*part_path*/) + const MergeTreeData & data, const PartMetadataCachePtr & cache, const DiskPtr & disk, const String & part_path) #else void IMergeTreeDataPart::MinMaxIndex::load( const MergeTreeData & data, const DiskPtr & disk, const String & part_path) @@ -78,16 +78,30 @@ void IMergeTreeDataPart::MinMaxIndex::load( auto minmax_column_types = data.getMinMaxColumnsTypes(partition_key); size_t minmax_idx_size = minmax_column_types.size(); + auto read_min_max_index = [&](size_t i) + { + String file_name = fs::path(part_path) / ("minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"); + auto file = openForReading(disk, file_name); + return file; + }; + hyperrectangle.reserve(minmax_idx_size); for (size_t i = 0; i < minmax_idx_size; ++i) { + std::unique_ptr file; #if USE_ROCKSDB - String file_name = "minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"; - String value; - auto file = cache->readOrSetMeta(disk, file_name, value); + if (cache) + { + String file_name = "minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"; + String value; + file = cache->readOrSetMeta(disk, file_name, value); + } + else + { + file = read_min_max_index(i); + } #else - String file_name = fs::path(part_path) / ("minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"); - auto file = openForReading(disk, file_name); + file = read_min_max_index(i); #endif auto serialization = minmax_column_types[i]->getDefaultSerialization(); @@ -311,10 +325,7 @@ IMergeTreeDataPart::IMergeTreeDataPart( , index_granularity_info(storage_, part_type_) , part_type(part_type_) , parent_part(parent_part_) -#if USE_ROCKSDB - , meta_cache(std::make_shared( - storage.getContext()->getMergeTreeMetaCache(), storage.relative_data_path, relative_path, parent_part)) -#endif + , use_metadata_cache(storage.getSettings()->use_metadata_cache) { if (parent_part) state = State::Committed; @@ -322,6 +333,12 @@ IMergeTreeDataPart::IMergeTreeDataPart( incrementTypeMetric(part_type); minmax_idx = std::make_shared(); + +#if USE_ROCKSDB + if (use_metadata_cache) + metadata_cache = std::make_shared( + storage.getContext()->getMergeTreeMetadataCache(), storage.relative_data_path, relative_path, parent_part); +#endif } IMergeTreeDataPart::IMergeTreeDataPart( @@ -340,10 +357,7 @@ IMergeTreeDataPart::IMergeTreeDataPart( , index_granularity_info(storage_, part_type_) , part_type(part_type_) , parent_part(parent_part_) -#if USE_ROCKSDB - , meta_cache(std::make_shared( - storage.getContext()->getMergeTreeMetaCache(), storage.relative_data_path, relative_path, parent_part)) -#endif + , use_metadata_cache(storage.getSettings()->use_metadata_cache) { if (parent_part) state = State::Committed; @@ -351,6 +365,12 @@ IMergeTreeDataPart::IMergeTreeDataPart( incrementTypeMetric(part_type); minmax_idx = std::make_shared(); + +#if USE_ROCKSDB + if (use_metadata_cache) + metadata_cache = std::make_shared( + storage.getContext()->getMergeTreeMetadataCache(), storage.relative_data_path, relative_path, parent_part); +#endif } IMergeTreeDataPart::~IMergeTreeDataPart() @@ -751,11 +771,19 @@ void IMergeTreeDataPart::loadIndex() String index_path = fs::path(getFullRelativePath()) / "primary.idx"; + std::unique_ptr index_file; #if USE_ROCKSDB - String value; - auto index_file = meta_cache->readOrSetMeta(volume->getDisk(), "primary.idx", value); + if (use_metadata_cache) + { + String value; + index_file = metadata_cache->readOrSetMeta(volume->getDisk(), "primary.idx", value); + } + else + { + index_file = openForReading(volume->getDisk(), index_path); + } #else - auto index_file = openForReading(volume->getDisk(), index_path); + index_file = openForReading(volume->getDisk(), index_path); #endif size_t marks_count = index_granularity.getMarksCount(); @@ -823,21 +851,33 @@ void IMergeTreeDataPart::loadDefaultCompressionCodec() String path = fs::path(getFullRelativePath()) / DEFAULT_COMPRESSION_CODEC_FILE_NAME; + bool exists = false; + std::unique_ptr file_buf; #if USE_ROCKSDB - String v; - auto file_buf = meta_cache->readOrSetMeta(volume->getDisk(), DEFAULT_COMPRESSION_CODEC_FILE_NAME, v); - if (!file_buf) + if (use_metadata_cache) + { + String v; + file_buf = metadata_cache->readOrSetMeta(volume->getDisk(), DEFAULT_COMPRESSION_CODEC_FILE_NAME, v); + exists = file_buf != nullptr; + } + else + { + exists = volume->getDisk()->exists(path); + if (exists) + file_buf = openForReading(volume->getDisk(), path); + } #else - if (!volume->getDisk()->exists(path)) + exists = volume->getDisk()->exists(path); + if (exists) + file_buf = openForReading(volume->getDisk(), path); #endif + + if (!exists) { default_codec = detectDefaultCompressionCodec(); } else { -#if !USE_ROCKSDB - auto file_buf = openForReading(volume->getDisk(), path); -#endif String codec_line; readEscapedStringUntilEOL(codec_line, *file_buf); @@ -937,7 +977,7 @@ void IMergeTreeDataPart::loadPartitionAndMinMaxIndex() String path = getFullRelativePath(); if (!parent_part) #if USE_ROCKSDB - partition.load(storage, meta_cache, volume->getDisk(), path); + partition.load(storage, metadata_cache, volume->getDisk(), path); #else partition.load(storage, volume->getDisk(), path); #endif @@ -949,7 +989,7 @@ void IMergeTreeDataPart::loadPartitionAndMinMaxIndex() minmax_idx->initialized = true; else #if USE_ROCKSDB - minmax_idx->load(storage, meta_cache, volume->getDisk(), path); + minmax_idx->load(storage, metadata_cache, volume->getDisk(), path); #else minmax_idx->load(storage, volume->getDisk(), path); #endif @@ -982,20 +1022,31 @@ void IMergeTreeDataPart::appendFilesOfPartitionAndMinMaxIndex(Strings & files) c void IMergeTreeDataPart::loadChecksums(bool require) { + const String path = fs::path(getFullRelativePath()) / "checksums.txt"; + bool exists = false; + std::unique_ptr buf; #if USE_ROCKSDB - String value; - auto buf = meta_cache->readOrSetMeta(volume->getDisk(), "checksums.txt", value); - if (buf) -#else - const String path = fs::path(getFullRelativePath()) / "checksums.txt"; - if (volume->getDisk()->exists(path)) -#endif + if (use_metadata_cache) { - -#if !USE_ROCKSDB - auto buf = openForReading(volume->getDisk(), path); + String value; + buf = metadata_cache->readOrSetMeta(volume->getDisk(), "checksums.txt", value); + exists = buf != nullptr; + } + else + { + exists = volume->getDisk()->exists(path); + if (exists) + buf = openForReading(volume->getDisk(), path); + } +#else + exists = volume->getDisk()->exists(path); + if (exists) + buf = openForReading(volume->getDisk(), path); #endif + + if (exists) + { if (checksums.read(*buf)) { assertEOF(*buf); @@ -1049,13 +1100,23 @@ void IMergeTreeDataPart::loadRowsCount() else if (storage.format_version >= MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING || part_type == Type::COMPACT || parent_part) { #if USE_ROCKSDB - String v; - auto buf = meta_cache->readOrSetMeta(volume->getDisk(), "count.txt", v); - if (!buf) - throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); + if (use_metadata_cache) + { + String v; + auto buf = metadata_cache->readOrSetMeta(volume->getDisk(), "count.txt", v); + if (!buf) + throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); - readIntText(rows_count, *buf); - assertEOF(*buf); + readIntText(rows_count, *buf); + assertEOF(*buf); + } + else + { + if (!volume->getDisk()->exists(path)) + throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); + + read_rows_count(); + } #else if (!volume->getDisk()->exists(path)) throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); @@ -1164,18 +1225,30 @@ void IMergeTreeDataPart::appendFilesOfRowsCount(Strings & files) const void IMergeTreeDataPart::loadTTLInfos() { String path = fs::path(getFullRelativePath()) / "ttl.txt"; -#if USE_ROCKSDB - String v; - auto in = meta_cache->readOrSetMeta(volume->getDisk(), "ttl.txt", v); - if (in) -#else - if (volume->getDisk()->exists(path)) -#endif - { + bool exists = false; + std::unique_ptr in; -#if !USE_ROCKSDB - auto in = openForReading(volume->getDisk(), path); +#if USE_ROCKSDB + if (use_metadata_cache) + { + String v; + in = metadata_cache->readOrSetMeta(volume->getDisk(), "ttl.txt", v); + exists = in != nullptr; + } + else + { + exists = volume->getDisk()->exists(path); + if (exists) + in = openForReading(volume->getDisk(), path); + } +#else + exists = volume->getDisk()->exists(path); + if (exists) + in = openForReading(volume->getDisk(), path); #endif + + if (exists) + { assertString("ttl format version: ", *in); size_t format_version; readText(format_version, *in); @@ -1205,18 +1278,31 @@ void IMergeTreeDataPart::appendFilesOfTTLInfos(Strings & files) const void IMergeTreeDataPart::loadUUID() { -#if USE_ROCKSDB - String v; - auto in = meta_cache->readOrSetMeta(volume->getDisk(), UUID_FILE_NAME, v); - if (in) -#else String path = fs::path(getFullRelativePath()) / UUID_FILE_NAME; - if (volume->getDisk()->exists(path)) -#endif + bool exists = false; + std::unique_ptr in; + +#if USE_ROCKSDB + if (use_metadata_cache) { -#if !USE_ROCKSDB - auto in = openForReading(volume->getDisk(), path); + String v; + in = metadata_cache->readOrSetMeta(volume->getDisk(), UUID_FILE_NAME, v); + exists = in != nullptr; + } + else + { + exists = volume->getDisk()->exists(path); + if (exists) + in = openForReading(volume->getDisk(), path); + } +#else + exists = volume->getDisk()->exists(path); + if (exists) + in = openForReading(volume->getDisk(), path); #endif + + if (exists) + { readText(uuid, *in); if (uuid == UUIDHelpers::Nil) throw Exception("Unexpected empty " + String(UUID_FILE_NAME) + " in part: " + name, ErrorCodes::LOGICAL_ERROR); @@ -1236,13 +1322,28 @@ void IMergeTreeDataPart::loadColumns(bool require) metadata_snapshot = metadata_snapshot->projections.get(name).metadata; NamesAndTypesList loaded_columns; + bool exists = false; + std::unique_ptr in; #if USE_ROCKSDB - String v; - auto in = meta_cache->readOrSetMeta(volume->getDisk(), "columns.txt", v); - if (!in) + if (use_metadata_cache) + { + String v; + in = metadata_cache->readOrSetMeta(volume->getDisk(), "columns.txt", v); + exists = in != nullptr; + } + else + { + exists = volume->getDisk()->exists(path); + if (exists) + in = openForReading(volume->getDisk(), path); + } #else - if (!volume->getDisk()->exists(path)) + exists = volume->getDisk()->exists(path); + if (exists) + in = openForReading(volume->getDisk(), path); #endif + + if (!exists) { /// We can get list of columns only from columns.txt in compact parts. if (require || part_type == Type::COMPACT) @@ -1265,11 +1366,7 @@ void IMergeTreeDataPart::loadColumns(bool require) } else { -#if USE_ROCKSDB loaded_columns.readText(*in); -#else - loaded_columns.readText(*volume->getDisk()->readFile(path)); -#endif for (const auto & column : loaded_columns) { @@ -1349,8 +1446,11 @@ void IMergeTreeDataPart::renameTo(const String & new_relative_path, bool remove_ } #if USE_ROCKSDB - modifyAllMetaCaches(ModifyCacheType::DROP, true); - assertMetaCacheDropped(true); + if (use_metadata_cache) + { + modifyAllMetadataCaches(ModifyCacheType::DROP, true); + assertMetadataCacheDropped(true); + } #endif volume->getDisk()->setLastModified(from, Poco::Timestamp::fromEpochTime(time(nullptr))); @@ -1358,7 +1458,10 @@ void IMergeTreeDataPart::renameTo(const String & new_relative_path, bool remove_ relative_path = new_relative_path; #if USE_ROCKSDB - modifyAllMetaCaches(ModifyCacheType::PUT, true); + if (use_metadata_cache) + { + modifyAllMetadataCaches(ModifyCacheType::PUT, true); + } #endif SyncGuardPtr sync_guard; @@ -1369,8 +1472,10 @@ void IMergeTreeDataPart::renameTo(const String & new_relative_path, bool remove_ } #if USE_ROCKSDB -void IMergeTreeDataPart::modifyAllMetaCaches(ModifyCacheType type, bool include_projection) const +void IMergeTreeDataPart::modifyAllMetadataCaches(ModifyCacheType type, bool include_projection) const { + assert(use_metadata_cache); + Strings files; files.reserve(16); appendFilesOfColumnsChecksumsIndexes(files, include_projection); @@ -1385,20 +1490,21 @@ void IMergeTreeDataPart::modifyAllMetaCaches(ModifyCacheType type, bool include_ switch (type) { case ModifyCacheType::PUT: - meta_cache->setMetas(volume->getDisk(), files); + metadata_cache->setMetas(volume->getDisk(), files); break; case ModifyCacheType::DROP: - meta_cache->dropMetas(files); + metadata_cache->dropMetas(files); break; } } - -void IMergeTreeDataPart::assertMetaCacheDropped(bool include_projection) const +void IMergeTreeDataPart::assertMetadataCacheDropped(bool include_projection) const { + assert(use_metadata_cache); + Strings files; std::vector _; - meta_cache->getFilesAndCheckSums(files, _); + metadata_cache->getFilesAndCheckSums(files, _); if (files.empty()) return; @@ -1471,8 +1577,11 @@ void IMergeTreeDataPart::remove() const } #if USE_ROCKSDB - modifyAllMetaCaches(ModifyCacheType::DROP); - assertMetaCacheDropped(); + if (use_metadata_cache) + { + modifyAllMetadataCaches(ModifyCacheType::DROP); + assertMetadataCacheDropped(); + } #endif /** Atomic directory removal: @@ -1579,8 +1688,11 @@ void IMergeTreeDataPart::remove() const void IMergeTreeDataPart::projectionRemove(const String & parent_to, bool keep_shared_data) const { #if USE_ROCKSDB - modifyAllMetaCaches(ModifyCacheType::DROP); - assertMetaCacheDropped(); + if (use_metadata_cache) + { + modifyAllMetadataCaches(ModifyCacheType::DROP); + assertMetadataCacheDropped(); + } #endif String to = parent_to + "/" + relative_path; @@ -1925,6 +2037,8 @@ String IMergeTreeDataPart::getZeroLevelPartBlockID() const #if USE_ROCKSDB IMergeTreeDataPart::uint128 IMergeTreeDataPart::getActualChecksumByFile(const String & file_path) const { + assert(use_metadata_cache); + String file_name = std::filesystem::path(file_path).filename(); const auto filenames_without_checksums = getFileNamesWithoutChecksums(); auto it = checksums.files.find(file_name); @@ -1945,15 +2059,17 @@ IMergeTreeDataPart::uint128 IMergeTreeDataPart::getActualChecksumByFile(const St return in_hash.getHash(); } -void IMergeTreeDataPart::checkMetaCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const +void IMergeTreeDataPart::checkMetadataCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const { - /// checkMetaCache only applies for normal part + assert(use_metadata_cache); + + /// Only applies for normal part if (isProjectionPart()) return; /// the directory of projection part is under the directory of its parent part const auto filenames_without_checksums = getFileNamesWithoutChecksums(); - meta_cache->getFilesAndCheckSums(files, cache_checksums); + metadata_cache->getFilesAndCheckSums(files, cache_checksums); for (const auto & file : files) { // std::cout << "check key:" << file << std::endl; diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 0364012bfec..2798f42b6c4 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include @@ -64,7 +64,7 @@ public: } } - using uint128 = PartMetaCache::uint128; + using uint128 = PartMetadataCache::uint128; #endif static constexpr auto DATA_FILE_EXTENSION = ".bin"; @@ -162,7 +162,7 @@ public: void assertOnDisk() const; #if USE_ROCKSDB - void assertMetaCacheDropped(bool include_projection = false) const; + void assertMetadataCacheDropped(bool include_projection = false) const; #endif void remove() const; @@ -326,7 +326,7 @@ public: } #if USE_ROCKSDB - void load(const MergeTreeData & data, const PartMetaCachePtr & meta_cache, const DiskPtr & disk, const String & part_path); + void load(const MergeTreeData & data, const PartMetadataCachePtr & metadata_cache, const DiskPtr & disk, const String & part_path); #else void load(const MergeTreeData & data, const DiskPtr & disk, const String & part_path); #endif @@ -401,7 +401,7 @@ public: String getRelativePathForPrefix(const String & prefix, bool detached = false) const; #if USE_ROCKSDB - virtual void checkMetaCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const; + virtual void checkMetadataCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const; #endif bool isProjectionPart() const { return parent_part != nullptr; } @@ -475,8 +475,11 @@ protected: std::map> projection_parts; + /// Disabled when USE_ROCKSDB is OFF, or use_metadata_cache is set true in merge tree settings + bool use_metadata_cache = false; + #if USE_ROCKSDB - mutable PartMetaCachePtr meta_cache; + mutable PartMetadataCachePtr metadata_cache; #endif void removeIfNeeded(); @@ -555,7 +558,7 @@ private: CompressionCodecPtr detectDefaultCompressionCodec() const; #if USE_ROCKSDB - void modifyAllMetaCaches(ModifyCacheType type, bool include_projection = false) const; + void modifyAllMetadataCaches(ModifyCacheType type, bool include_projection = false) const; IMergeTreeDataPart::uint128 getActualChecksumByFile(const String & file_path) const; #endif diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 8d861e404f0..6add439d1f9 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -317,6 +317,17 @@ MergeTreeData::MergeTreeData( LOG_WARNING(log, "{} Settings 'min_rows_for_wide_part', 'min_bytes_for_wide_part', " "'min_rows_for_compact_part' and 'min_bytes_for_compact_part' will be ignored.", reason); +#if !USE_ROCKSDB + if (settings->use_metadata_cache) + { + LOG_WARNING( + log, + "Can't use merge tree metadata cache if clickhouse was compiled without rocksdb." + "set use_metadata_cache to false forcely"); + settings->use_metadata_cache = false; + } +#endif + common_assignee_trigger = [this] (bool delay) noexcept { if (delay) diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index 88a418b1129..933248c9a1e 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -160,13 +160,11 @@ namespace }; } -#if !USE_ROCKSDB static std::unique_ptr openForReading(const DiskPtr & disk, const String & path) { size_t file_size = disk->getFileSize(path); return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); } -#endif String MergeTreePartition::getID(const MergeTreeData & storage) const { @@ -358,7 +356,7 @@ void MergeTreePartition::serializeText(const MergeTreeData & storage, WriteBuffe } #if USE_ROCKSDB -void MergeTreePartition::load(const MergeTreeData & storage, const PartMetaCachePtr & meta_cache, const DiskPtr & disk, const String & part_path) +void MergeTreePartition::load(const MergeTreeData & storage, const PartMetadataCachePtr & metadata_cache, const DiskPtr & disk, const String & part_path) #else void MergeTreePartition::load(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path) #endif @@ -370,11 +368,19 @@ void MergeTreePartition::load(const MergeTreeData & storage, const DiskPtr & dis const auto & partition_key_sample = adjustPartitionKey(metadata_snapshot, storage.getContext()).sample_block; auto partition_file_path = part_path + "partition.dat"; + std::unique_ptr file; #if USE_ROCKSDB - String v; - auto file = meta_cache->readOrSetMeta(disk, "partition.dat", v); + if (metadata_cache) + { + String v; + file = metadata_cache->readOrSetMeta(disk, "partition.dat", v); + } + else + { + file = openForReading(disk, partition_file_path); + } #else - auto file = openForReading(disk, partition_file_path); + file = openForReading(disk, partition_file_path); #endif value.resize(partition_key_sample.columns()); diff --git a/src/Storages/MergeTree/MergeTreePartition.h b/src/Storages/MergeTree/MergeTreePartition.h index 4920e658163..644ddd5ba88 100644 --- a/src/Storages/MergeTree/MergeTreePartition.h +++ b/src/Storages/MergeTree/MergeTreePartition.h @@ -7,7 +7,7 @@ #include #if USE_ROCKSDB -#include +#include #endif namespace DB @@ -42,7 +42,7 @@ public: void serializeText(const MergeTreeData & storage, WriteBuffer & out, const FormatSettings & format_settings) const; #if USE_ROCKSDB - void load(const MergeTreeData & storage, const PartMetaCachePtr & meta_cache, const DiskPtr & disk, const String & part_path); + void load(const MergeTreeData & storage, const PartMetadataCachePtr & metadata_cache, const DiskPtr & disk, const String & part_path); #else void load(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path); #endif diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index b991166b3b6..51831eecf84 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -137,6 +137,7 @@ struct Settings; /** Experimental/work in progress feature. Unsafe for production. */ \ 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) \ \ /** Obsolete settings. Kept for backward compatibility only. */ \ M(UInt64, min_relative_delay_to_yield_leadership, 120, "Obsolete setting, does nothing.", 0) \ diff --git a/src/Storages/MergeTree/PartMetaCache.cpp b/src/Storages/MergeTree/PartMetadataCache.cpp similarity index 80% rename from src/Storages/MergeTree/PartMetaCache.cpp rename to src/Storages/MergeTree/PartMetadataCache.cpp index fe8475d0dda..a8ab2c0bf2d 100644 --- a/src/Storages/MergeTree/PartMetaCache.cpp +++ b/src/Storages/MergeTree/PartMetadataCache.cpp @@ -1,4 +1,4 @@ -#include "PartMetaCache.h" +#include "PartMetadataCache.h" #if USE_ROCKSDB #include @@ -11,8 +11,8 @@ namespace ProfileEvents { - extern const Event MergeTreeMetaCacheHit; - extern const Event MergeTreeMetaCacheMiss; + extern const Event MergeTreeMetadataCacheHit; + extern const Event MergeTreeMetadataCacheMiss; } namespace ErrorCodes @@ -24,13 +24,13 @@ namespace DB { std::unique_ptr -PartMetaCache::readOrSetMeta(const DiskPtr & disk, const String & file_name, String & value) +PartMetadataCache::readOrSetMeta(const DiskPtr & disk, const String & file_name, String & value) { String file_path = fs::path(getFullRelativePath()) / file_name; auto status = cache->get(file_path, value); if (!status.ok()) { - ProfileEvents::increment(ProfileEvents::MergeTreeMetaCacheMiss); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheMiss); if (!disk->exists(file_path)) { return nullptr; @@ -45,12 +45,12 @@ PartMetaCache::readOrSetMeta(const DiskPtr & disk, const String & file_name, Str } else { - ProfileEvents::increment(ProfileEvents::MergeTreeMetaCacheHit); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheHit); } return std::make_unique(value); } -void PartMetaCache::setMetas(const DiskPtr & disk, const Strings & file_names) +void PartMetadataCache::setMetas(const DiskPtr & disk, const Strings & file_names) { String text; String read_value; @@ -76,7 +76,7 @@ void PartMetaCache::setMetas(const DiskPtr & disk, const Strings & file_names) } } -void PartMetaCache::dropMetas(const Strings & file_names) +void PartMetadataCache::dropMetas(const Strings & file_names) { for (const auto & file_name : file_names) { @@ -93,7 +93,7 @@ void PartMetaCache::dropMetas(const Strings & file_names) } } -void PartMetaCache::setMeta(const String & file_name, const String & value) +void PartMetadataCache::setMeta(const String & file_name, const String & value) { String file_path = fs::path(getFullRelativePath()) / file_name; String read_value; @@ -112,7 +112,7 @@ void PartMetaCache::setMeta(const String & file_name, const String & value) } } -void PartMetaCache::getFilesAndCheckSums(Strings & files, std::vector & checksums) const +void PartMetadataCache::getFilesAndCheckSums(Strings & files, std::vector & checksums) const { String prefix = fs::path(getFullRelativePath()) / ""; Strings values; @@ -127,7 +127,7 @@ void PartMetaCache::getFilesAndCheckSums(Strings & files, std::vector & } } -String PartMetaCache::getFullRelativePath() const +String PartMetadataCache::getFullRelativePath() const { return fs::path(relative_data_path) / (parent_part ? parent_part->relative_path : "") / relative_path / ""; } diff --git a/src/Storages/MergeTree/PartMetaCache.h b/src/Storages/MergeTree/PartMetadataCache.h similarity index 76% rename from src/Storages/MergeTree/PartMetaCache.h rename to src/Storages/MergeTree/PartMetadataCache.h index 5ffd0413c4b..97880f41b6c 100644 --- a/src/Storages/MergeTree/PartMetaCache.h +++ b/src/Storages/MergeTree/PartMetadataCache.h @@ -13,15 +13,15 @@ namespace DB class SeekableReadBuffer; class IMergeTreeDataPart; -class PartMetaCache; -using PartMetaCachePtr = std::shared_ptr; +class PartMetadataCache; +using PartMetadataCachePtr = std::shared_ptr; -class PartMetaCache +class PartMetadataCache { public: using uint128 = CityHash_v1_0_2::uint128; - PartMetaCache(const MergeTreeMetaCachePtr & cache_, const String & relative_data_path_, const String & relative_path_, const IMergeTreeDataPart * parent_part_) + PartMetadataCache(const MergeTreeMetadataCachePtr & cache_, const String & relative_data_path_, const String & relative_path_, const IMergeTreeDataPart * parent_part_) : cache(cache_) , relative_data_path(relative_data_path_) , relative_path(relative_path_) @@ -39,7 +39,7 @@ public: private: std::string getFullRelativePath() const; - MergeTreeMetaCachePtr cache; + MergeTreeMetadataCachePtr cache; const String & relative_data_path; // relative path of table to disk const String & relative_path; // relative path of part to table const IMergeTreeDataPart * parent_part; diff --git a/src/Storages/System/StorageSystemMergeTreeMetaCache.cpp b/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp similarity index 84% rename from src/Storages/System/StorageSystemMergeTreeMetaCache.cpp rename to src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp index f53c32e5a42..b62a7985c0c 100644 --- a/src/Storages/System/StorageSystemMergeTreeMetaCache.cpp +++ b/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp @@ -1,4 +1,4 @@ -#include +#include #if USE_ROCKSDB #include @@ -20,7 +20,7 @@ namespace ErrorCodes extern const int BAD_ARGUMENTS; } -NamesAndTypesList StorageSystemMergeTreeMetaCache::getNamesAndTypes() +NamesAndTypesList StorageSystemMergeTreeMetadataCache::getNamesAndTypes() { return { {"key", std::make_shared()}, @@ -95,19 +95,19 @@ static String extractKey(const ASTPtr & query, bool& precise) } -void StorageSystemMergeTreeMetaCache::fillData(MutableColumns & res_columns, ContextPtr context, const SelectQueryInfo & query_info) const +void StorageSystemMergeTreeMetadataCache::fillData(MutableColumns & res_columns, ContextPtr context, const SelectQueryInfo & query_info) const { bool precise = false; String key = extractKey(query_info.query, precise); if (key.empty()) throw Exception( - "SELECT from system.merge_tree_meta_cache table must contain condition like key = 'key' or key LIKE 'prefix%' in WHERE clause.", ErrorCodes::BAD_ARGUMENTS); + "SELECT from system.merge_tree_metadata_cache table must contain condition like key = 'key' or key LIKE 'prefix%' in WHERE clause.", ErrorCodes::BAD_ARGUMENTS); - auto cache = context->getMergeTreeMetaCache(); + auto cache = context->getMergeTreeMetadataCache(); if (precise) { String value; - if (cache->get(key, value) != MergeTreeMetaCache::Status::OK()) + if (cache->get(key, value) != MergeTreeMetadataCache::Status::OK()) return; size_t col_num = 0; @@ -119,7 +119,7 @@ void StorageSystemMergeTreeMetaCache::fillData(MutableColumns & res_columns, Con String target = extractFixedPrefixFromLikePattern(key); if (target.empty()) throw Exception( - "SELECT from system.merge_tree_meta_cache table must contain condition like key = 'key' or key LIKE 'prefix%' in WHERE clause.", ErrorCodes::BAD_ARGUMENTS); + "SELECT from system.merge_tree_metadata_cache table must contain condition like key = 'key' or key LIKE 'prefix%' in WHERE clause.", ErrorCodes::BAD_ARGUMENTS); Strings keys; Strings values; diff --git a/src/Storages/System/StorageSystemMergeTreeMetaCache.h b/src/Storages/System/StorageSystemMergeTreeMetadataCache.h similarity index 55% rename from src/Storages/System/StorageSystemMergeTreeMetaCache.h rename to src/Storages/System/StorageSystemMergeTreeMetadataCache.h index c8e0f475cd8..a61f996f4df 100644 --- a/src/Storages/System/StorageSystemMergeTreeMetaCache.h +++ b/src/Storages/System/StorageSystemMergeTreeMetadataCache.h @@ -12,14 +12,14 @@ namespace DB class Context; -/** Implements `merge_tree_meta_cache` system table, which allows you to view the metacache data in rocksdb for debugging purposes. +/** Implements `merge_tree_metadata_cache` system table, which allows you to view the metadata cache data in rocksdb for debugging purposes. */ -class StorageSystemMergeTreeMetaCache : public shared_ptr_helper, public IStorageSystemOneBlock +class StorageSystemMergeTreeMetadataCache : public shared_ptr_helper, public IStorageSystemOneBlock { - friend struct shared_ptr_helper; + friend struct shared_ptr_helper; public: - std::string getName() const override { return "SystemMergeTreeMetaCache"; } + std::string getName() const override { return "SystemMergeTreeMetadataCache"; } static NamesAndTypesList getNamesAndTypes(); diff --git a/src/Storages/System/attachSystemTables.cpp b/src/Storages/System/attachSystemTables.cpp index 5f5a17069f3..96ee000c0bf 100644 --- a/src/Storages/System/attachSystemTables.cpp +++ b/src/Storages/System/attachSystemTables.cpp @@ -75,7 +75,7 @@ #if USE_ROCKSDB #include -#include +#include #endif @@ -130,7 +130,7 @@ void attachSystemTablesLocal(ContextPtr context, IDatabase & system_database) #endif #if USE_ROCKSDB attach(context, system_database, "rocksdb"); - attach(context, system_database, "merge_tree_meta_cache"); + attach(context, system_database, "merge_tree_metadata_cache"); #endif } diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache.sql b/tests/queries/0_stateless/01233_check_part_meta_cache.sql index f0ca3b608d1..3c5d55a0069 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache.sql @@ -1,123 +1,125 @@ +--Tags: no-fasttest + -- Create table under database with engine ordinary. set mutations_sync = 1; -DROP DATABASE IF EXISTS test_meta_cache; -DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache; -CREATE DATABASE test_meta_cache ENGINE = Ordinary; -CREATE TABLE test_meta_cache.check_part_meta_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +DROP DATABASE IF EXISTS test_metadata_cache; +DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache; +CREATE DATABASE test_metadata_cache ENGINE = Ordinary; +CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert first batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert second batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Update some data. -alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -alter table test_meta_cache.check_part_meta_cache delete where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache delete where k = 8; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert third batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Drop partitioin 201805 -alter table test_meta_cache.check_part_meta_cache drop partition 201805; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Optimize table. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_meta_cache.check_part_meta_cache FINAL; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_metadata_cache.check_part_metadata_cache FINAL; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete column. -alter table test_meta_cache.check_part_meta_cache drop column v1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop column v1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Truncate table. -truncate table test_meta_cache.check_part_meta_cache; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +truncate table test_metadata_cache.check_part_metadata_cache; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Recreate table with projection. -drop table if exists test_meta_cache.check_part_meta_cache; -CREATE TABLE test_meta_cache.check_part_meta_cache( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; +drop table if exists test_metadata_cache.check_part_metadata_cache; +CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -- Insert first batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert second batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Update some data. -alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -alter table test_meta_cache.check_part_meta_cache delete where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache delete where k = 8; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert third batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Drop partitioin 201805 -alter table test_meta_cache.check_part_meta_cache drop partition 201805; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Optimize table. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_meta_cache.check_part_meta_cache FINAL; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_metadata_cache.check_part_metadata_cache FINAL; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Update TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Truncate table. -truncate table test_meta_cache.check_part_meta_cache; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +truncate table test_metadata_cache.check_part_metadata_cache; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql index b57caf55cb8..7c78721f692 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql @@ -1,124 +1,126 @@ +-- Tags: no-fasttest + -- Create table under database with engine atomic. set mutations_sync = 1; -DROP DATABASE IF EXISTS test_meta_cache; -DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache SYNC; -CREATE DATABASE test_meta_cache ENGINE = Atomic; -CREATE TABLE test_meta_cache.check_part_meta_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +DROP DATABASE IF EXISTS test_metadata_cache; +DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC; +CREATE DATABASE test_metadata_cache ENGINE = Atomic; +CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert first batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert second batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Update some data. -alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -alter table test_meta_cache.check_part_meta_cache delete where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache delete where k = 8; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Drop partitioin 201805 -alter table test_meta_cache.check_part_meta_cache drop partition 201805; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Optimize table. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_meta_cache.check_part_meta_cache FINAL; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_metadata_cache.check_part_metadata_cache FINAL; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete column. -alter table test_meta_cache.check_part_meta_cache drop column v1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop column v1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + 30; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + 30; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + 60; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + 60; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Truncate table. -truncate table test_meta_cache.check_part_meta_cache; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +truncate table test_metadata_cache.check_part_metadata_cache; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Recreate table with projection. -drop table if exists test_meta_cache.check_part_meta_cache SYNC; -CREATE TABLE test_meta_cache.check_part_meta_cache( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; +drop table if exists test_metadata_cache.check_part_metadata_cache SYNC; +CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -- Insert first batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- nsert second batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Update some data. -alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -alter table test_meta_cache.check_part_meta_cache delete where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache delete where k = 8; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Drop partitioin 201805 -alter table test_meta_cache.check_part_meta_cache drop partition 201805; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Optimize table. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_meta_cache.check_part_meta_cache FINAL; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_metadata_cache.check_part_metadata_cache FINAL; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Truncate table. -truncate table test_meta_cache.check_part_meta_cache; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +truncate table test_metadata_cache.check_part_metadata_cache; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql index 7722bba71dd..6111f20b599 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql @@ -1,127 +1,128 @@ +-- Tags: no-fasttest -- Tags: zookeeper -- Create table under database with engine ordinary. set mutations_sync = 1; set replication_alter_partitions_sync = 2; -DROP DATABASE IF EXISTS test_meta_cache; -DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache; -CREATE DATABASE test_meta_cache ENGINE = Ordinary; -CREATE TABLE test_meta_cache.check_part_meta_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +DROP DATABASE IF EXISTS test_metadata_cache; +DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache; +CREATE DATABASE test_metadata_cache ENGINE = Ordinary; +CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert first batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert second batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Update some data. -alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); --Delete some data. -alter table test_meta_cache.check_part_meta_cache delete where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache delete where k = 8; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Drop partitioin 201805 -alter table test_meta_cache.check_part_meta_cache drop partition 201805; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Optimize table. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_meta_cache.check_part_meta_cache FINAL; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_metadata_cache.check_part_metadata_cache FINAL; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete column. -alter table test_meta_cache.check_part_meta_cache drop column v1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop column v1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Truncate table. -truncate table test_meta_cache.check_part_meta_cache; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +truncate table test_metadata_cache.check_part_metadata_cache; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Recreate table with projection. -drop table if exists test_meta_cache.check_part_meta_cache ; -CREATE TABLE test_meta_cache.check_part_meta_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; +drop table if exists test_metadata_cache.check_part_metadata_cache ; +CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -- Insert first batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert second batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Update some data. -alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); --Delete some data. -alter table test_meta_cache.check_part_meta_cache delete where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache delete where k = 8; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Drop partitioin 201805 -alter table test_meta_cache.check_part_meta_cache drop partition 201805; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Optimize table. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_meta_cache.check_part_meta_cache FINAL; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_metadata_cache.check_part_metadata_cache FINAL; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Truncate table. -truncate table test_meta_cache.check_part_meta_cache; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +truncate table test_metadata_cache.check_part_metadata_cache; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql index 4dcc4478139..ee8ad61c97c 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql @@ -1,127 +1,128 @@ +-- Tags: no-fasttest -- Tags: zookeeper -- Create table under database with engine ordinary. set mutations_sync = 1; set replication_alter_partitions_sync = 2; -DROP DATABASE IF EXISTS test_meta_cache ; -DROP TABLE IF EXISTS test_meta_cache.check_part_meta_cache ; -CREATE DATABASE test_meta_cache ENGINE = Atomic; -CREATE TABLE test_meta_cache.check_part_meta_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_meta_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +DROP DATABASE IF EXISTS test_metadata_cache ; +DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache ; +CREATE DATABASE test_metadata_cache ENGINE = Atomic; +CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert first batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert second batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Update some data. -alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); --Delete some data. -alter table test_meta_cache.check_part_meta_cache delete where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache delete where k = 8; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Drop partitioin 201805 -alter table test_meta_cache.check_part_meta_cache drop partition 201805; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Optimize table. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_meta_cache.check_part_meta_cache FINAL; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_metadata_cache.check_part_metadata_cache FINAL; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete column. -alter table test_meta_cache.check_part_meta_cache drop column v1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop column v1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Truncate table. -truncate table test_meta_cache.check_part_meta_cache; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +truncate table test_metadata_cache.check_part_metadata_cache; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Recreate table with projection. -drop table if exists test_meta_cache.check_part_meta_cache ; -CREATE TABLE test_meta_cache.check_part_meta_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k; +drop table if exists test_metadata_cache.check_part_metadata_cache ; +CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -- Insert first batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Insert second batch of data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Update some data. -alter table test_meta_cache.check_part_meta_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); --Delete some data. -alter table test_meta_cache.check_part_meta_cache delete where k = 1; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -alter table test_meta_cache.check_part_meta_cache delete where k = 8; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Delete some data. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Drop partitioin 201805 -alter table test_meta_cache.check_part_meta_cache drop partition 201805; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Optimize table. -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_meta_cache.check_part_meta_cache FINAL; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +optimize table test_metadata_cache.check_part_metadata_cache FINAL; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add column. -alter table test_meta_cache.check_part_meta_cache add column v3 UInt64; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Add TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Modify TTL info. -alter table test_meta_cache.check_part_meta_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_meta_cache.check_part_meta_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); -- Truncate table. -truncate table test_meta_cache.check_part_meta_cache; -with arrayJoin(checkPartMetaCache('test_meta_cache', 'check_part_meta_cache')) as info select count(1), countIf(info.5 = 0); +truncate table test_metadata_cache.check_part_metadata_cache; +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); From f0b3c8121284ae3ca8e5fd079cc792810b6f7fc1 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Tue, 28 Dec 2021 14:23:35 +0300 Subject: [PATCH 0144/1647] add log in zookeeper --- src/Common/TransactionID.h | 2 +- src/Interpreters/Context.cpp | 2 + src/Interpreters/Context.h | 3 + src/Interpreters/TransactionLog.cpp | 210 +++++++++++++++++- src/Interpreters/TransactionLog.h | 27 ++- .../0_stateless/01168_mutations_isolation.sh | 3 +- 6 files changed, 232 insertions(+), 15 deletions(-) diff --git a/src/Common/TransactionID.h b/src/Common/TransactionID.h index 9037601ffad..991dab66e71 100644 --- a/src/Common/TransactionID.h +++ b/src/Common/TransactionID.h @@ -22,7 +22,7 @@ namespace Tx const CSN UnknownCSN = 0; const CSN PrehistoricCSN = 1; const CSN CommittingCSN = 2; /// TODO do we really need it? - const CSN MaxReservedCSN = 16; + const CSN MaxReservedCSN = 2; const LocalTID PrehistoricLocalTID = 1; const LocalTID MaxReservedLocalTID = 16; diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 24a5b220fb1..2fca325917f 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -435,6 +435,8 @@ ContextMutablePtr Context::createGlobal(ContextSharedPart * shared) void Context::initGlobal() { + assert(!global_context_instance); + global_context_instance = shared_from_this(); DatabaseCatalog::init(shared_from_this()); } diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index 69ea9353b54..b3fdc8fa8e4 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -303,6 +303,7 @@ private: /// A flag, used to distinguish between user query and internal query to a database engine (MaterializedPostgreSQL). bool is_internal_query = false; + inline static ContextPtr global_context_instance; public: // Top-level OpenTelemetry trace context for the query. Makes sense only for a query context. @@ -629,6 +630,8 @@ public: ContextMutablePtr getGlobalContext() const; + static ContextPtr getGlobalContextInstance() { return global_context_instance; } + bool hasGlobalContext() const { return !global_context.expired(); } bool isGlobalContext() const { diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index f084c9dbc3c..94cb8cc1827 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -1,6 +1,11 @@ #include #include +#include +#include +#include +#include #include +#include #include #include @@ -21,11 +26,188 @@ TransactionLog & TransactionLog::instance() TransactionLog::TransactionLog() : log(&Poco::Logger::get("TransactionLog")) { - latest_snapshot = Tx::MaxReservedCSN; - csn_counter = Tx::MaxReservedCSN; + global_context = Context::getGlobalContextInstance(); + zookeeper_path = "/test/clickhouse/txn_log"; + + loadLogFromZooKeeper(); + + updating_thread = ThreadFromGlobalPool(&TransactionLog::runUpdatingThread, this); +} + +TransactionLog::~TransactionLog() +{ + stop_flag.store(true); + log_updated_event->set(); + updating_thread.join(); +} + +UInt64 TransactionLog::parseCSN(const String & csn_node_name) +{ + ReadBufferFromString buf{csn_node_name}; + assertString("csn-", buf); + UInt64 res; + readText(res, buf); + assertEOF(buf); + return res; +} + +TransactionID TransactionLog::parseTID(const String & csn_node_content) +{ + TransactionID tid = Tx::EmptyTID; + if (csn_node_content.empty()) + return tid; + + ReadBufferFromString buf{csn_node_content}; + assertChar('(', buf); + readText(tid.start_csn, buf); + assertString(", ", buf); + readText(tid.local_tid, buf); + assertString(", ", buf); + readText(tid.host_id, buf); + assertChar(')', buf); + assertEOF(buf); + return tid; +} + +String TransactionLog::writeTID(const TransactionID & tid) +{ + WriteBufferFromOwnString buf; + writeChar('(', buf); + writeText(tid.start_csn, buf); + writeCString(", ", buf); + writeText(tid.local_tid, buf); + writeCString(", ", buf); + writeText(tid.host_id, buf); + writeChar(')', buf); + return buf.str(); +} + + +void TransactionLog::loadEntries(Strings::const_iterator beg, Strings::const_iterator end) +{ + std::vector> futures; + size_t entries_count = std::distance(beg, end); + if (!entries_count) + return; + + String last_entry = *std::prev(end); + LOG_TRACE(log, "Loading {} entries from {}: {}..{}", entries_count, zookeeper_path, *beg, last_entry); + futures.reserve(entries_count); + for (auto it = beg; it != end; ++it) + futures.emplace_back(zookeeper->asyncGet(fs::path(zookeeper_path) / *it)); + + std::vector> loaded; + loaded.reserve(entries_count); + auto it = beg; + for (size_t i = 0; i < entries_count; ++i, ++it) + { + auto res = futures[i].get(); + CSN csn = parseCSN(*it); + TransactionID tid = parseTID(res.data); + loaded.emplace_back(tid.getHash(), csn); + LOG_TEST(log, "Got entry {} -> {}", tid, csn); + } + futures.clear(); + + /// Use noexcept here to exit on unexpected exceptions (SIGABRT is better that broken state in memory) + auto insert = [&]() noexcept + { + for (const auto & entry : loaded) + if (entry.first != Tx::EmptyTID.getHash()) + tid_to_csn.emplace(entry.first, entry.second); + last_loaded_entry = last_entry; + latest_snapshot = loaded.back().second; + }; + + MemoryTracker::LockExceptionInThread blocker(VariableContext::Global); + std::lock_guard lock{commit_mutex}; + insert(); +} + +void TransactionLog::loadLogFromZooKeeper() +{ + assert(!zookeeper); + assert(tid_to_csn.empty()); + assert(last_loaded_entry.empty()); + zookeeper = global_context->getZooKeeper(); + + /// We do not write local_tid_counter to disk or zk and maintain it only in memory. + /// Create empty entry to allocate new CSN to safely start counting from the beginning and avoid TID duplication. + /// TODO It's possible to skip this step in come cases (especially for multi-host configuration). + Coordination::Error code = zookeeper->tryCreate(zookeeper_path + "/csn-", "", zkutil::CreateMode::PersistentSequential); + if (code != Coordination::Error::ZOK) + { + assert(code == Coordination::Error::ZNONODE); + zookeeper->createAncestors(zookeeper_path); + Coordination::Requests ops; + ops.emplace_back(zkutil::makeCreateRequest(zookeeper_path, "", zkutil::CreateMode::Persistent)); + for (size_t i = 0; i <= Tx::MaxReservedCSN; ++i) + ops.emplace_back(zkutil::makeCreateRequest(zookeeper_path + "/csn-", "", zkutil::CreateMode::PersistentSequential)); + Coordination::Responses res; + code = zookeeper->tryMulti(ops, res); + if (code != Coordination::Error::ZNODEEXISTS) + zkutil::KeeperMultiException::check(code, ops, res); + } + + /// TODO Split log into "subdirectories" to: + /// 1. fetch it more optimal way (avoid listing all CSNs on further incremental updates) + /// 2. simplify log rotation + /// 3. support 64-bit CSNs on top of Apache ZooKeeper (it uses Int32 for sequential numbers) + Strings entries_list = zookeeper->getChildren(zookeeper_path, nullptr, log_updated_event); + assert(!entries_list.empty()); + std::sort(entries_list.begin(), entries_list.end()); + loadEntries(entries_list.begin(), entries_list.end()); + assert(!last_loaded_entry.empty()); + assert(latest_snapshot == parseCSN(last_loaded_entry)); local_tid_counter = Tx::MaxReservedLocalTID; } +void TransactionLog::runUpdatingThread() +{ + while (true) + { + try + { + log_updated_event->wait(); + if (stop_flag.load()) + return; + + if (!zookeeper) + zookeeper = global_context->getZooKeeper(); + + loadNewEntries(); + } + catch (const Coordination::Exception & e) + { + LOG_ERROR(log, getCurrentExceptionMessage(true)); + /// TODO better backoff + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + if (Coordination::isHardwareError(e.code)) + zookeeper = nullptr; + log_updated_event->set(); + } + catch (...) + { + LOG_ERROR(log, getCurrentExceptionMessage(true)); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + log_updated_event->set(); + } + } +} + +void TransactionLog::loadNewEntries() +{ + Strings entries_list = zookeeper->getChildren(zookeeper_path, nullptr, log_updated_event); + assert(!entries_list.empty()); + std::sort(entries_list.begin(), entries_list.end()); + auto it = std::upper_bound(entries_list.begin(), entries_list.end(), last_loaded_entry); + loadEntries(it, entries_list.end()); + assert(last_loaded_entry == entries_list.back()); + assert(latest_snapshot == parseCSN(last_loaded_entry)); + latest_snapshot.notify_all(); +} + + Snapshot TransactionLog::getLatestSnapshot() const { return latest_snapshot.load(); @@ -61,16 +243,22 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) } else { - LOG_TRACE(log, "Committing transaction {}{}", txn->tid, txn->dumpDescription()); - std::lock_guard lock{commit_mutex}; - new_csn = 1 + csn_counter.fetch_add(1); - bool inserted = tid_to_csn.try_emplace(txn->tid.getHash(), new_csn).second; /// Commit point - if (!inserted) - throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} exists", txn->tid.getHash(), txn->tid); - latest_snapshot.store(new_csn, std::memory_order_relaxed); - } + LOG_TEST(log, "Committing transaction {}{}", txn->tid, txn->dumpDescription()); + /// TODO handle connection loss + /// TODO support batching + String path_created = zookeeper->create(zookeeper_path + "/csn-", writeTID(txn->tid), zkutil::CreateMode::PersistentSequential); /// Commit point + new_csn = parseCSN(path_created.substr(zookeeper_path.size() + 1)); + LOG_INFO(log, "Transaction {} committed with CSN={}", txn->tid, new_csn); - LOG_INFO(log, "Transaction {} committed with CSN={}", txn->tid, new_csn); + /// Wait for committed changes to become actually visible, so the next transaction will see changes + /// TODO it's optional, add a setting for this + auto current_latest_snapshot = latest_snapshot.load(); + while (current_latest_snapshot < new_csn) + { + latest_snapshot.wait(current_latest_snapshot); + current_latest_snapshot = latest_snapshot.load(); + } + } txn->afterCommit(new_csn); diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index c2edeafb523..44c6ccf53cc 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -1,6 +1,8 @@ #pragma once #include #include +#include +#include #include #include #include @@ -8,6 +10,8 @@ namespace DB { +using ZooKeeperPtr = std::shared_ptr; + class TransactionLog final : private boost::noncopyable { public: @@ -15,6 +19,8 @@ public: TransactionLog(); + ~TransactionLog(); + Snapshot getLatestSnapshot() const; /// Allocated TID, returns transaction object @@ -32,10 +38,20 @@ public: Snapshot getOldestSnapshot() const; private: + void loadLogFromZooKeeper(); + void runUpdatingThread(); + + void loadEntries(Strings::const_iterator beg, Strings::const_iterator end); + void loadNewEntries(); + + static UInt64 parseCSN(const String & csn_node_name); + static TransactionID parseTID(const String & csn_node_content); + static String writeTID(const TransactionID & tid); + + ContextPtr global_context; Poco::Logger * log; std::atomic latest_snapshot; - std::atomic csn_counter; std::atomic local_tid_counter; /// FIXME Transactions: it's probably a bad idea to use global mutex here @@ -45,6 +61,15 @@ private: mutable std::mutex running_list_mutex; std::unordered_map running_list; std::list snapshots_in_use; + + String zookeeper_path; + ZooKeeperPtr zookeeper; + String last_loaded_entry; + zkutil::EventPtr log_updated_event = std::make_shared(); + + std::atomic_bool stop_flag = false; + ThreadFromGlobalPool updating_thread; + }; } diff --git a/tests/queries/0_stateless/01168_mutations_isolation.sh b/tests/queries/0_stateless/01168_mutations_isolation.sh index 9a829d7a5e4..6b0fb15d7f7 100755 --- a/tests/queries/0_stateless/01168_mutations_isolation.sh +++ b/tests/queries/0_stateless/01168_mutations_isolation.sh @@ -59,10 +59,9 @@ tx 7 "select 7, n, _part from mt order by n" tx 8 "begin transaction" tx_async 8 "alter table mt update n = 0 where 1" >/dev/null $CLICKHOUSE_CLIENT -q "kill mutation where database=currentDatabase() and mutation_id='mutation_15.txt' format Null" -tx_wait 8 +tx_sync 8 "rollback" tx 7 "optimize table mt final" tx 7 "select 8, n, _part from mt order by n" -tx 8 "rollback" tx 10 "begin transaction" tx 10 "alter table mt update n = 0 where 1" | grep -Eo "Serialization error" | uniq tx 7 "alter table mt update n=n+1 where 1" From 98f37afc80df33de1d3eb9c1676804c5285ab422 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 28 Dec 2021 19:29:01 +0800 Subject: [PATCH 0145/1647] fix building --- programs/server/Server.cpp | 2 +- src/Functions/checkPartMetadataCache.cpp | 2 +- src/Interpreters/Context.cpp | 4 +- src/Interpreters/Context.h | 1 - src/Storages/MergeTree/IMergeTreeDataPart.cpp | 41 +++++++++---------- src/Storages/MergeTree/MergeTreeData.cpp | 5 ++- src/Storages/MergeTree/MergeTreeData.h | 1 + .../MergeTree/MergeTreeDataPartCompact.cpp | 3 +- .../MergeTree/MergeTreeDataPartWide.cpp | 2 +- src/Storages/MergeTree/MergeTreePartition.cpp | 2 +- src/Storages/MergeTree/PartMetadataCache.cpp | 9 ++-- src/Storages/MergeTree/PartMetadataCache.h | 10 ++--- .../StorageSystemMergeTreeMetadataCache.cpp | 3 +- .../StorageSystemMergeTreeMetadataCache.h | 3 +- 14 files changed, 42 insertions(+), 46 deletions(-) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index 1e10af9dc07..dee8a3ffd4e 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -820,7 +820,7 @@ if (ThreadFuzzer::instance().isEffective()) #if USE_ROCKSDB - /// initialize meta file cache + /// Initialize merge tree metadata cache { size_t size = config().getUInt64("meta_file_cache_size", 256 << 20); global_context->initializeMergeTreeMetadataCache(path_str + "/" + "rocksdb", size); diff --git a/src/Functions/checkPartMetadataCache.cpp b/src/Functions/checkPartMetadataCache.cpp index 2883424f996..87d84297226 100644 --- a/src/Functions/checkPartMetadataCache.cpp +++ b/src/Functions/checkPartMetadataCache.cpp @@ -99,7 +99,7 @@ public: if (!data || !data->getSettings()->use_metadata_cache) throw Exception("The table in function " + getName() + " must be in MergeTree Family", ErrorCodes::ILLEGAL_COLUMN); - /// Fill in result + /// Fill in checking results. auto col_result = result_type->createColumn(); auto & col_arr = assert_cast(*col_result); auto & col_tuple = assert_cast(col_arr.getData()); diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 4897cdccac9..76348a16710 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -283,7 +283,7 @@ struct ContextSharedPart Context::ConfigReloadCallback config_reload_callback; #if USE_ROCKSDB - /// MergeTree metadata cache stored in rocksdb. + /// Global merge tree metadata cache, stored in rocksdb. MergeTreeMetadataCachePtr merge_tree_metadata_cache; #endif @@ -399,7 +399,7 @@ struct ContextSharedPart zookeeper.reset(); #if USE_ROCKSDB - /// Shutdown meta file cache + /// Shutdown merge tree metadata cache if (merge_tree_metadata_cache) { merge_tree_metadata_cache->shutdown(); diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index ea606fdea4e..fe8df22b8fb 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -708,7 +708,6 @@ public: MergeTreeMetadataCachePtr getMergeTreeMetadataCache() const; #endif - #if USE_NURAFT std::shared_ptr & getKeeperDispatcher() const; #endif diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index b29886265f6..35125a7095f 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -90,11 +90,11 @@ void IMergeTreeDataPart::MinMaxIndex::load( { std::unique_ptr file; #if USE_ROCKSDB + String _; if (cache) { String file_name = "minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"; - String value; - file = cache->readOrSetMeta(disk, file_name, value); + file = cache->readOrSet(disk, file_name, _); } else { @@ -325,7 +325,7 @@ IMergeTreeDataPart::IMergeTreeDataPart( , index_granularity_info(storage_, part_type_) , part_type(part_type_) , parent_part(parent_part_) - , use_metadata_cache(storage.getSettings()->use_metadata_cache) + , use_metadata_cache(storage.use_metadata_cache) { if (parent_part) state = State::Committed; @@ -357,7 +357,7 @@ IMergeTreeDataPart::IMergeTreeDataPart( , index_granularity_info(storage_, part_type_) , part_type(part_type_) , parent_part(parent_part_) - , use_metadata_cache(storage.getSettings()->use_metadata_cache) + , use_metadata_cache(storage.use_metadata_cache) { if (parent_part) state = State::Committed; @@ -716,7 +716,6 @@ void IMergeTreeDataPart::appendFilesOfColumnsChecksumsIndexes(Strings & files, b size_t IMergeTreeDataPart::fileNumberOfColumnsChecksumsIndexes() const { Strings files; - files.reserve(16); appendFilesOfColumnsChecksumsIndexes(files, true); return files.size(); } @@ -773,10 +772,10 @@ void IMergeTreeDataPart::loadIndex() std::unique_ptr index_file; #if USE_ROCKSDB + String _; if (use_metadata_cache) { - String value; - index_file = metadata_cache->readOrSetMeta(volume->getDisk(), "primary.idx", value); + index_file = metadata_cache->readOrSet(volume->getDisk(), "primary.idx", _); } else { @@ -854,10 +853,10 @@ void IMergeTreeDataPart::loadDefaultCompressionCodec() bool exists = false; std::unique_ptr file_buf; #if USE_ROCKSDB + String _; if (use_metadata_cache) { - String v; - file_buf = metadata_cache->readOrSetMeta(volume->getDisk(), DEFAULT_COMPRESSION_CODEC_FILE_NAME, v); + file_buf = metadata_cache->readOrSet(volume->getDisk(), DEFAULT_COMPRESSION_CODEC_FILE_NAME, _); exists = file_buf != nullptr; } else @@ -1027,10 +1026,10 @@ void IMergeTreeDataPart::loadChecksums(bool require) std::unique_ptr buf; #if USE_ROCKSDB + String _; if (use_metadata_cache) { - String value; - buf = metadata_cache->readOrSetMeta(volume->getDisk(), "checksums.txt", value); + buf = metadata_cache->readOrSet(volume->getDisk(), "checksums.txt", _); exists = buf != nullptr; } else @@ -1100,10 +1099,10 @@ void IMergeTreeDataPart::loadRowsCount() else if (storage.format_version >= MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING || part_type == Type::COMPACT || parent_part) { #if USE_ROCKSDB + String _; if (use_metadata_cache) { - String v; - auto buf = metadata_cache->readOrSetMeta(volume->getDisk(), "count.txt", v); + auto buf = metadata_cache->readOrSet(volume->getDisk(), "count.txt", _); if (!buf) throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); @@ -1229,10 +1228,10 @@ void IMergeTreeDataPart::loadTTLInfos() std::unique_ptr in; #if USE_ROCKSDB + String _; if (use_metadata_cache) { - String v; - in = metadata_cache->readOrSetMeta(volume->getDisk(), "ttl.txt", v); + in = metadata_cache->readOrSet(volume->getDisk(), "ttl.txt", _); exists = in != nullptr; } else @@ -1283,10 +1282,10 @@ void IMergeTreeDataPart::loadUUID() std::unique_ptr in; #if USE_ROCKSDB + String _; if (use_metadata_cache) { - String v; - in = metadata_cache->readOrSetMeta(volume->getDisk(), UUID_FILE_NAME, v); + in = metadata_cache->readOrSet(volume->getDisk(), UUID_FILE_NAME, _); exists = in != nullptr; } else @@ -1325,10 +1324,10 @@ void IMergeTreeDataPart::loadColumns(bool require) bool exists = false; std::unique_ptr in; #if USE_ROCKSDB + String _; if (use_metadata_cache) { - String v; - in = metadata_cache->readOrSetMeta(volume->getDisk(), "columns.txt", v); + in = metadata_cache->readOrSet(volume->getDisk(), "columns.txt", _); exists = in != nullptr; } else @@ -1490,10 +1489,10 @@ void IMergeTreeDataPart::modifyAllMetadataCaches(ModifyCacheType type, bool incl switch (type) { case ModifyCacheType::PUT: - metadata_cache->setMetas(volume->getDisk(), files); + metadata_cache->batchSet(volume->getDisk(), files); break; case ModifyCacheType::DROP: - metadata_cache->dropMetas(files); + metadata_cache->batchDelete(files); break; } } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 6add439d1f9..d03b1e287e5 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -206,6 +206,7 @@ MergeTreeData::MergeTreeData( , parts_mover(this) , background_operations_assignee(*this, BackgroundJobsAssignee::Type::DataProcessing, getContext()) , background_moves_assignee(*this, BackgroundJobsAssignee::Type::Moving, getContext()) + , use_metadata_cache(getSettings()->use_metadata_cache) { context_->getGlobalContext()->initializeBackgroundExecutorsIfNeeded(); @@ -318,13 +319,13 @@ MergeTreeData::MergeTreeData( "'min_rows_for_compact_part' and 'min_bytes_for_compact_part' will be ignored.", reason); #if !USE_ROCKSDB - if (settings->use_metadata_cache) + if (use_metadata_cache) { LOG_WARNING( log, "Can't use merge tree metadata cache if clickhouse was compiled without rocksdb." "set use_metadata_cache to false forcely"); - settings->use_metadata_cache = false; + use_metadata_cache = false; } #endif diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index d349646ab88..468a5ccf72b 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -972,6 +972,7 @@ protected: /// And for ReplicatedMergeTree we don't have LogEntry type for this operation. BackgroundJobsAssignee background_operations_assignee; BackgroundJobsAssignee background_moves_assignee; + bool use_metadata_cache; /// Strongly connected with two fields above. /// Every task that is finished will ask to assign a new one into an executor. diff --git a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp index fa9996b3382..d294b69f79f 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp @@ -192,12 +192,11 @@ MergeTreeDataPartCompact::~MergeTreeDataPartCompact() removeIfNeeded(); } -/// Do not cache mark file, because cache other meta files is enough to speed up loading. +/// We don't cache mark file, because cache other metadata files is enough to speed up loading. void MergeTreeDataPartCompact::appendFilesOfIndexGranularity(Strings& /* files */) const { } -/// find all connected file and do modification Strings MergeTreeDataPartCompact::getIndexGranularityFiles() const { auto marks_file = index_granularity_info.getMarksFilePath("data"); diff --git a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp index f6efdc5f05c..f4aec7deb15 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp @@ -268,7 +268,7 @@ void MergeTreeDataPartWide::calculateEachColumnSizes(ColumnSizeByName & each_col } } -// Do not cache mark files of part, because cache other meta files is enough to speed up loading. +/// We don't cache mark files of part, because cache other meta files is enough to speed up loading. void MergeTreeDataPartWide::appendFilesOfIndexGranularity(Strings& /* files */) const { } diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index 933248c9a1e..6a394fa5baa 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -373,7 +373,7 @@ void MergeTreePartition::load(const MergeTreeData & storage, const DiskPtr & dis if (metadata_cache) { String v; - file = metadata_cache->readOrSetMeta(disk, "partition.dat", v); + file = metadata_cache->readOrSet(disk, "partition.dat", v); } else { diff --git a/src/Storages/MergeTree/PartMetadataCache.cpp b/src/Storages/MergeTree/PartMetadataCache.cpp index a8ab2c0bf2d..dcb6ce54378 100644 --- a/src/Storages/MergeTree/PartMetadataCache.cpp +++ b/src/Storages/MergeTree/PartMetadataCache.cpp @@ -24,7 +24,7 @@ namespace DB { std::unique_ptr -PartMetadataCache::readOrSetMeta(const DiskPtr & disk, const String & file_name, String & value) +PartMetadataCache::readOrSet(const DiskPtr & disk, const String & file_name, String & value) { String file_path = fs::path(getFullRelativePath()) / file_name; auto status = cache->get(file_path, value); @@ -50,7 +50,7 @@ PartMetadataCache::readOrSetMeta(const DiskPtr & disk, const String & file_name, return std::make_unique(value); } -void PartMetadataCache::setMetas(const DiskPtr & disk, const Strings & file_names) +void PartMetadataCache::batchSet(const DiskPtr & disk, const Strings & file_names) { String text; String read_value; @@ -76,7 +76,7 @@ void PartMetadataCache::setMetas(const DiskPtr & disk, const Strings & file_name } } -void PartMetadataCache::dropMetas(const Strings & file_names) +void PartMetadataCache::batchDelete(const Strings & file_names) { for (const auto & file_name : file_names) { @@ -93,7 +93,7 @@ void PartMetadataCache::dropMetas(const Strings & file_names) } } -void PartMetadataCache::setMeta(const String & file_name, const String & value) +void PartMetadataCache::set(const String & file_name, const String & value) { String file_path = fs::path(getFullRelativePath()) / file_name; String read_value; @@ -116,7 +116,6 @@ void PartMetadataCache::getFilesAndCheckSums(Strings & files, std::vectorgetByPrefix(prefix, files, values); size_t size = files.size(); for (size_t i = 0; i < size; ++i) diff --git a/src/Storages/MergeTree/PartMetadataCache.h b/src/Storages/MergeTree/PartMetadataCache.h index 97880f41b6c..91440da014c 100644 --- a/src/Storages/MergeTree/PartMetadataCache.h +++ b/src/Storages/MergeTree/PartMetadataCache.h @@ -30,11 +30,11 @@ public: } std::unique_ptr - readOrSetMeta(const DiskPtr & disk, const String & file_name, String & value); - void setMetas(const DiskPtr & disk, const Strings & file_names); - void dropMetas(const Strings & file_names); - void setMeta(const String & file_name, const String & value); - void getFilesAndCheckSums(Strings & file_names, std::vector & checksums) const; + readOrSet(const DiskPtr & disk, const String & file_name, String & value); + void batchSet(const DiskPtr & disk, const Strings & file_names); + void batchDelete(const Strings & file_names); + void set(const String & file_name, const String & value); + void getFilesAndCheckSums(Strings & files, std::vector & checksums) const; private: std::string getFullRelativePath() const; diff --git a/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp b/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp index b62a7985c0c..bf74f15e822 100644 --- a/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp +++ b/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp @@ -82,8 +82,7 @@ static bool extractKeyImpl(const IAST & elem, String & res, bool & precise) } -/** Retrieve from the query a condition of the form `key= 'key'`, from conjunctions in the WHERE clause. - */ +/// Retrieve from the query a condition of the form `key= 'key'`, from conjunctions in the WHERE clause. static String extractKey(const ASTPtr & query, bool& precise) { const auto & select = query->as(); diff --git a/src/Storages/System/StorageSystemMergeTreeMetadataCache.h b/src/Storages/System/StorageSystemMergeTreeMetadataCache.h index a61f996f4df..8169d1a83fb 100644 --- a/src/Storages/System/StorageSystemMergeTreeMetadataCache.h +++ b/src/Storages/System/StorageSystemMergeTreeMetadataCache.h @@ -12,8 +12,7 @@ namespace DB class Context; -/** Implements `merge_tree_metadata_cache` system table, which allows you to view the metadata cache data in rocksdb for debugging purposes. - */ +/// Implements `merge_tree_metadata_cache` system table, which allows you to view the metadata cache data in rocksdb for testing purposes. class StorageSystemMergeTreeMetadataCache : public shared_ptr_helper, public IStorageSystemOneBlock { friend struct shared_ptr_helper; From beb03d75ca71dcef7843820a667e49451f532bbf Mon Sep 17 00:00:00 2001 From: tavplubix Date: Tue, 28 Dec 2021 16:43:19 +0300 Subject: [PATCH 0146/1647] Update 01528_clickhouse_local_prepare_parts.sh --- .../queries/0_stateless/01528_clickhouse_local_prepare_parts.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/queries/0_stateless/01528_clickhouse_local_prepare_parts.sh b/tests/queries/0_stateless/01528_clickhouse_local_prepare_parts.sh index 95ecbf09cf5..e33f75b2b06 100755 --- a/tests/queries/0_stateless/01528_clickhouse_local_prepare_parts.sh +++ b/tests/queries/0_stateless/01528_clickhouse_local_prepare_parts.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 63dc6821d2d16c7100687ab83e47a70faf0cc5ec Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Wed, 29 Dec 2021 12:31:54 +0800 Subject: [PATCH 0147/1647] fix ut and some bug --- src/Interpreters/Context.cpp | 64 ++--------------- src/Interpreters/Context.h | 34 ++------- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 3 +- src/Storages/MergeTree/IMergeTreeDataPart.h | 2 - .../MergeTree/MergeTreeDataPartCompact.cpp | 6 -- .../MergeTree/MergeTreeDataPartCompact.h | 2 - .../MergeTree/MergeTreeDataPartInMemory.cpp | 6 -- .../MergeTree/MergeTreeDataPartInMemory.h | 2 - .../MergeTree/MergeTreeDataPartWide.cpp | 9 --- .../MergeTree/MergeTreeDataPartWide.h | 2 - .../MergeTree/MergeTreeMetadataCache.cpp | 70 +++++++++++++++++++ .../MergeTree/MergeTreeMetadataCache.h | 39 +++++++++++ src/Storages/MergeTree/MergeTreePartition.cpp | 4 +- src/Storages/MergeTree/PartMetadataCache.cpp | 1 + src/Storages/MergeTree/PartMetadataCache.h | 11 ++- .../StorageSystemMergeTreeMetadataCache.cpp | 1 + .../01233_check_part_meta_cache.reference | 56 +++++++-------- .../01233_check_part_meta_cache.sql | 56 +++++++-------- ..._check_part_meta_cache_in_atomic.reference | 56 +++++++-------- .../01233_check_part_meta_cache_in_atomic.sql | 56 +++++++-------- ...check_part_meta_cache_replicated.reference | 56 +++++++-------- ...01233_check_part_meta_cache_replicated.sql | 56 +++++++-------- ..._meta_cache_replicated_in_atomic.reference | 56 +++++++-------- ...k_part_meta_cache_replicated_in_atomic.sql | 62 ++++++++-------- 24 files changed, 360 insertions(+), 350 deletions(-) create mode 100644 src/Storages/MergeTree/MergeTreeMetadataCache.cpp create mode 100644 src/Storages/MergeTree/MergeTreeMetadataCache.h diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 76348a16710..3986b5bf822 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -84,24 +84,21 @@ #include #include #include +#include #include #include #include #include +#if USE_ROCKSDB +#include +#endif namespace fs = std::filesystem; namespace ProfileEvents { extern const Event ContextLock; - -#if USE_ROCKSDB - extern const Event MergeTreeMetadataCachePut; - extern const Event MergeTreeMetadataCacheGet; - extern const Event MergeTreeMetadataCacheDelete; - extern const Event MergeTreeMetadataCacheSeek; -#endif } namespace CurrentMetrics @@ -449,59 +446,6 @@ SharedContextHolder::SharedContextHolder(std::unique_ptr shar void SharedContextHolder::reset() { shared.reset(); } -#if USE_ROCKSDB -MergeTreeMetadataCache::Status MergeTreeMetadataCache::put(const String & key, const String & value) -{ - auto options = rocksdb::WriteOptions(); - auto status = rocksdb->Put(options, key, value); - ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCachePut); - return status; -} - -MergeTreeMetadataCache::Status MergeTreeMetadataCache::del(const String & key) -{ - auto options = rocksdb::WriteOptions(); - auto status = rocksdb->Delete(options, key); - ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheDelete); - LOG_TRACE(log, "Delete key:{} from MergeTreeMetadataCache status:{}", key, status.ToString()); - return status; -} - -MergeTreeMetadataCache::Status MergeTreeMetadataCache::get(const String & key, String & value) -{ - auto status = rocksdb->Get(rocksdb::ReadOptions(), key, &value); - ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheGet); - LOG_TRACE(log, "Get key:{} from MergeTreeMetadataCache status:{}", key, status.ToString()); - return status; -} - -void MergeTreeMetadataCache::getByPrefix(const String & prefix, Strings & keys, Strings & values) -{ - auto * it = rocksdb->NewIterator(rocksdb::ReadOptions()); - rocksdb::Slice target(prefix); - for (it->Seek(target); it->Valid(); it->Next()) - { - const auto key = it->key(); - if (!key.starts_with(target)) - break; - - const auto value = it->value(); - keys.emplace_back(key.data(), key.size()); - values.emplace_back(value.data(), value.size()); - } - LOG_TRACE(log, "Seek with prefix:{} from MergeTreeMetadataCache items:{}", prefix, keys.size()); - ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheSeek); -} - -void MergeTreeMetadataCache::shutdown() -{ - if (rocksdb) - { - rocksdb->Close(); - } -} -#endif - ContextMutablePtr Context::createGlobal(ContextSharedPart * shared) { auto res = std::shared_ptr(new Context); diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index fe8df22b8fb..b8616e8b634 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -16,12 +16,8 @@ #include #include -#include "config_core.h" -#if USE_ROCKSDB -#include -#include -#endif +#include "config_core.h" #include #include @@ -155,6 +151,12 @@ using ReadTaskCallback = std::function; using MergeTreeReadTaskCallback = std::function(PartitionReadRequest)>; + +#if USE_ROCKSDB +class MergeTreeMetadataCache; +using MergeTreeMetadataCachePtr = std::shared_ptr; +#endif + /// An empty interface for an arbitrary object that may be attached by a shared pointer /// to query context, when using ClickHouse as a library. struct IHostContext @@ -182,28 +184,6 @@ private: std::unique_ptr shared; }; -#if USE_ROCKSDB -class MergeTreeMetadataCache -{ -public: - using Status = rocksdb::Status; - - explicit MergeTreeMetadataCache(rocksdb::DB * rocksdb_) : rocksdb{rocksdb_} { } - MergeTreeMetadataCache(const MergeTreeMetadataCache &) = delete; - MergeTreeMetadataCache & operator=(const MergeTreeMetadataCache &) = delete; - - Status put(const String & key, const String & value); - Status del(const String & key); - Status get(const String & key, String & value); - void getByPrefix(const String & prefix, Strings & keys, Strings & values); - - void shutdown(); -private: - std::unique_ptr rocksdb; - Poco::Logger * log = &Poco::Logger::get("MergeTreeMetadataCache"); -}; -using MergeTreeMetadataCachePtr = std::shared_ptr; -#endif /** A set of known objects that can be used in the query. * Consists of a shared part (always common to all sessions and queries) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 35125a7095f..abcc52dd295 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -820,8 +820,7 @@ void IMergeTreeDataPart::appendFilesofIndex(Strings & files) const if (!metadata_snapshot) return; - size_t key_size = metadata_snapshot->getPrimaryKeyColumns().size(); - if (key_size) + if (metadata_snapshot->hasPrimaryKey()) files.push_back("primary.idx"); } diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 2798f42b6c4..a992d2dadd7 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -386,8 +386,6 @@ public: /// storage and pass it to this method. virtual bool hasColumnFiles(const NameAndTypePair & /* column */) const { return false; } - virtual Strings getIndexGranularityFiles() const = 0; - /// Returns true if this part shall participate in merges according to /// settings of given storage policy. bool shallParticipateInMerges(const StoragePolicyPtr & storage_policy) const; diff --git a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp index d294b69f79f..6a747960a40 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartCompact.cpp @@ -197,10 +197,4 @@ void MergeTreeDataPartCompact::appendFilesOfIndexGranularity(Strings& /* files * { } -Strings MergeTreeDataPartCompact::getIndexGranularityFiles() const -{ - auto marks_file = index_granularity_info.getMarksFilePath("data"); - return {marks_file}; -} - } diff --git a/src/Storages/MergeTree/MergeTreeDataPartCompact.h b/src/Storages/MergeTree/MergeTreeDataPartCompact.h index 87066ab2ff0..b96afc4b972 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartCompact.h +++ b/src/Storages/MergeTree/MergeTreeDataPartCompact.h @@ -74,8 +74,6 @@ private: void calculateEachColumnSizes(ColumnSizeByName & each_columns_size, ColumnSize & total_size) const override; void appendFilesOfIndexGranularity(Strings& files) const override; - - Strings getIndexGranularityFiles() const override; }; } diff --git a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp index f3c4b613078..e482f867e7b 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartInMemory.cpp @@ -171,12 +171,6 @@ void MergeTreeDataPartInMemory::appendFilesOfIndexGranularity(Strings& /* files { } -/// No mark files for part in memory -Strings MergeTreeDataPartInMemory::getIndexGranularityFiles() const -{ - return {}; -} - DataPartInMemoryPtr asInMemoryPart(const MergeTreeDataPartPtr & part) { return std::dynamic_pointer_cast(part); diff --git a/src/Storages/MergeTree/MergeTreeDataPartInMemory.h b/src/Storages/MergeTree/MergeTreeDataPartInMemory.h index 4f83b54d402..068b15d2bdc 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartInMemory.h +++ b/src/Storages/MergeTree/MergeTreeDataPartInMemory.h @@ -64,8 +64,6 @@ private: void calculateEachColumnSizes(ColumnSizeByName & each_columns_size, ColumnSize & total_size) const override; void appendFilesOfIndexGranularity(Strings & files) const override; - - Strings getIndexGranularityFiles() const override; }; using DataPartInMemoryPtr = std::shared_ptr; diff --git a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp index f4aec7deb15..1582a7f3274 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWide.cpp +++ b/src/Storages/MergeTree/MergeTreeDataPartWide.cpp @@ -273,13 +273,4 @@ void MergeTreeDataPartWide::appendFilesOfIndexGranularity(Strings& /* files */) { } -Strings MergeTreeDataPartWide::getIndexGranularityFiles() const -{ - if (columns.empty()) - return {}; - - auto marks_file = getFileNameForColumn(columns.front()); - return {marks_file}; -} - } diff --git a/src/Storages/MergeTree/MergeTreeDataPartWide.h b/src/Storages/MergeTree/MergeTreeDataPartWide.h index bf73d16d758..2b11fc4eb02 100644 --- a/src/Storages/MergeTree/MergeTreeDataPartWide.h +++ b/src/Storages/MergeTree/MergeTreeDataPartWide.h @@ -68,8 +68,6 @@ private: void appendFilesOfIndexGranularity(Strings & files) const override; - Strings getIndexGranularityFiles() const override; - ColumnSize getColumnSizeImpl(const NameAndTypePair & column, std::unordered_set * processed_substreams) const; void calculateEachColumnSizes(ColumnSizeByName & each_columns_size, ColumnSize & total_size) const override; diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp new file mode 100644 index 00000000000..d9dacadcead --- /dev/null +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp @@ -0,0 +1,70 @@ +#include "MergeTreeMetadataCache.h" + +#if USE_ROCKSDB +#include +#include + +namespace ProfileEvents +{ + extern const Event MergeTreeMetadataCachePut; + extern const Event MergeTreeMetadataCacheGet; + extern const Event MergeTreeMetadataCacheDelete; + extern const Event MergeTreeMetadataCacheSeek; +} + +namespace DB +{ +MergeTreeMetadataCache::Status MergeTreeMetadataCache::put(const String & key, const String & value) +{ + auto options = rocksdb::WriteOptions(); + auto status = rocksdb->Put(options, key, value); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCachePut); + return status; +} + +MergeTreeMetadataCache::Status MergeTreeMetadataCache::del(const String & key) +{ + auto options = rocksdb::WriteOptions(); + auto status = rocksdb->Delete(options, key); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheDelete); + LOG_TRACE(log, "Delete key:{} from MergeTreeMetadataCache status:{}", key, status.ToString()); + return status; +} + +MergeTreeMetadataCache::Status MergeTreeMetadataCache::get(const String & key, String & value) +{ + auto status = rocksdb->Get(rocksdb::ReadOptions(), key, &value); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheGet); + LOG_TRACE(log, "Get key:{} from MergeTreeMetadataCache status:{}", key, status.ToString()); + return status; +} + +void MergeTreeMetadataCache::getByPrefix(const String & prefix, Strings & keys, Strings & values) +{ + auto * it = rocksdb->NewIterator(rocksdb::ReadOptions()); + rocksdb::Slice target(prefix); + for (it->Seek(target); it->Valid(); it->Next()) + { + const auto key = it->key(); + if (!key.starts_with(target)) + break; + + const auto value = it->value(); + keys.emplace_back(key.data(), key.size()); + values.emplace_back(value.data(), value.size()); + } + LOG_TRACE(log, "Seek with prefix:{} from MergeTreeMetadataCache items:{}", prefix, keys.size()); + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheSeek); +} + +void MergeTreeMetadataCache::shutdown() +{ + if (rocksdb) + { + rocksdb->Close(); + } +} + +} + +#endif diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.h b/src/Storages/MergeTree/MergeTreeMetadataCache.h new file mode 100644 index 00000000000..00c783d881d --- /dev/null +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.h @@ -0,0 +1,39 @@ +#pragma once + + +#include +#include + +#include +#include +#include + +#include "config_core.h" +#if USE_ROCKSDB + +namespace DB +{ +class MergeTreeMetadataCache +{ +public: + using Status = rocksdb::Status; + + explicit MergeTreeMetadataCache(rocksdb::DB * rocksdb_) : rocksdb{rocksdb_} { } + MergeTreeMetadataCache(const MergeTreeMetadataCache &) = delete; + MergeTreeMetadataCache & operator=(const MergeTreeMetadataCache &) = delete; + + Status put(const String & key, const String & value); + Status del(const String & key); + Status get(const String & key, String & value); + void getByPrefix(const String & prefix, Strings & keys, Strings & values); + + void shutdown(); +private: + std::unique_ptr rocksdb; + Poco::Logger * log = &Poco::Logger::get("MergeTreeMetadataCache"); +}; + +using MergeTreeMetadataCachePtr = std::shared_ptr; +} + +#endif diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index 6a394fa5baa..f1a669eeb2f 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -370,10 +370,10 @@ void MergeTreePartition::load(const MergeTreeData & storage, const DiskPtr & dis std::unique_ptr file; #if USE_ROCKSDB + String _; if (metadata_cache) { - String v; - file = metadata_cache->readOrSet(disk, "partition.dat", v); + file = metadata_cache->readOrSet(disk, "partition.dat", _); } else { diff --git a/src/Storages/MergeTree/PartMetadataCache.cpp b/src/Storages/MergeTree/PartMetadataCache.cpp index dcb6ce54378..eee04d24405 100644 --- a/src/Storages/MergeTree/PartMetadataCache.cpp +++ b/src/Storages/MergeTree/PartMetadataCache.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace ProfileEvents { diff --git a/src/Storages/MergeTree/PartMetadataCache.h b/src/Storages/MergeTree/PartMetadataCache.h index 91440da014c..427b9524afd 100644 --- a/src/Storages/MergeTree/PartMetadataCache.h +++ b/src/Storages/MergeTree/PartMetadataCache.h @@ -5,7 +5,6 @@ #if USE_ROCKSDB #include #include -#include namespace DB @@ -13,8 +12,12 @@ namespace DB class SeekableReadBuffer; class IMergeTreeDataPart; -class PartMetadataCache; -using PartMetadataCachePtr = std::shared_ptr; + +class MergeTreeMetadataCache; +using MergeTreeMetadataCachePtr = std::shared_ptr; + +class IDisk; +using DiskPtr = std::shared_ptr; class PartMetadataCache { @@ -45,5 +48,7 @@ private: const IMergeTreeDataPart * parent_part; }; +using PartMetadataCachePtr = std::shared_ptr; + } #endif diff --git a/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp b/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp index bf74f15e822..275365648f3 100644 --- a/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp +++ b/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace DB diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache.reference b/tests/queries/0_stateless/01233_check_part_meta_cache.reference index 914add905ce..2bd6025fea2 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache.reference +++ b/tests/queries/0_stateless/01233_check_part_meta_cache.reference @@ -1,28 +1,28 @@ -0 0 -7 0 -14 0 -28 0 -42 0 -56 0 -70 0 -77 0 -63 0 -77 0 -84 0 -98 0 -122 0 -154 0 -122 0 -12 0 -24 0 -48 0 -72 0 -96 0 -120 0 -132 0 -108 0 -132 0 -144 0 -183 0 -235 0 -183 0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache.sql b/tests/queries/0_stateless/01233_check_part_meta_cache.sql index 3c5d55a0069..c15c2883436 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache.sql @@ -6,65 +6,65 @@ DROP DATABASE IF EXISTS test_metadata_cache; DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache; CREATE DATABASE test_metadata_cache ENGINE = Ordinary; CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert second batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Update some data. alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert third batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Drop partitioin 201805 alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Optimize table. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add column. alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete column. alter table test_metadata_cache.check_part_metadata_cache drop column v1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Modify TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Truncate table. truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Recreate table with projection. drop table if exists test_metadata_cache.check_part_metadata_cache; @@ -72,54 +72,54 @@ CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert second batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Update some data. alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert third batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Drop partitioin 201805 alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Optimize table. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add column. alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Update TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Truncate table. truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference index 95de1ef56a9..2bd6025fea2 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference @@ -1,28 +1,28 @@ -0 0 -7 0 -14 0 -28 0 -42 0 -56 0 -70 0 -77 0 -63 0 -77 0 -84 0 -98 0 -124 0 -150 0 -124 0 -12 0 -24 0 -48 0 -72 0 -96 0 -120 0 -132 0 -108 0 -132 0 -144 0 -183 0 -235 0 -183 0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql index 7c78721f692..6bd8425a8ea 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql @@ -6,65 +6,65 @@ DROP DATABASE IF EXISTS test_metadata_cache; DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC; CREATE DATABASE test_metadata_cache ENGINE = Atomic; CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert second batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Update some data. alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Drop partitioin 201805 alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Optimize table. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add column. alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete column. alter table test_metadata_cache.check_part_metadata_cache drop column v1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + 30; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Modify TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + 60; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Truncate table. truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Recreate table with projection. drop table if exists test_metadata_cache.check_part_metadata_cache SYNC; @@ -72,55 +72,55 @@ CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- nsert second batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Update some data. alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Drop partitioin 201805 alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Optimize table. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add column. alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Modify TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Truncate table. truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference index 2275537d212..2bd6025fea2 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference @@ -1,28 +1,28 @@ -0 0 -7 0 -14 0 -28 0 -42 0 -56 0 -70 0 -77 0 -7 0 -14 0 -21 0 -35 0 -51 0 -67 0 -0 0 -12 0 -24 0 -48 0 -72 0 -96 0 -120 0 -132 0 -108 0 -132 0 -144 0 -183 0 -235 0 -183 0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql index 6111f20b599..2c490c80d70 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql @@ -8,65 +8,65 @@ DROP DATABASE IF EXISTS test_metadata_cache; DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache; CREATE DATABASE test_metadata_cache ENGINE = Ordinary; CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert second batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Update some data. alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); --Delete some data. alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Drop partitioin 201805 alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Optimize table. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add column. alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete column. alter table test_metadata_cache.check_part_metadata_cache drop column v1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Modify TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Truncate table. truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Recreate table with projection. drop table if exists test_metadata_cache.check_part_metadata_cache ; @@ -74,55 +74,55 @@ CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert second batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Update some data. alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); --Delete some data. alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Drop partitioin 201805 alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Optimize table. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add column. alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Modify TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Truncate table. truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference index 2275537d212..2bd6025fea2 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference @@ -1,28 +1,28 @@ -0 0 -7 0 -14 0 -28 0 -42 0 -56 0 -70 0 -77 0 -7 0 -14 0 -21 0 -35 0 -51 0 -67 0 -0 0 -12 0 -24 0 -48 0 -72 0 -96 0 -120 0 -132 0 -108 0 -132 0 -144 0 -183 0 -235 0 -183 0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql index ee8ad61c97c..c7cc2a09899 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql @@ -5,124 +5,124 @@ set mutations_sync = 1; set replication_alter_partitions_sync = 2; DROP DATABASE IF EXISTS test_metadata_cache ; -DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache ; +DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC; CREATE DATABASE test_metadata_cache ENGINE = Atomic; CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert second batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Update some data. alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); --Delete some data. alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Drop partitioin 201805 alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Optimize table. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add column. alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete column. alter table test_metadata_cache.check_part_metadata_cache drop column v1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Modify TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Truncate table. truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Recreate table with projection. -drop table if exists test_metadata_cache.check_part_metadata_cache ; -CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; +drop table if exists test_metadata_cache.check_part_metadata_cache SYNC; +CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k TTL p + INTERVAL 15 YEAR settings use_metadata_cache = 1; -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert second batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Update some data. alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); --Delete some data. alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Delete some data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Drop partitioin 201805 alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Optimize table. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add column. alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Add TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Modify TTL info. alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Truncate table. truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select count(1), countIf(info.5 = 0); +with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); From e5ff05c50dc0fac888ae961804c0ace95be59c5f Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Wed, 29 Dec 2021 16:20:29 +0800 Subject: [PATCH 0148/1647] fix building --- src/Functions/checkPartMetadataCache.cpp | 1 - src/Storages/MergeTree/MergeTreeMetadataCache.h | 10 ++++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Functions/checkPartMetadataCache.cpp b/src/Functions/checkPartMetadataCache.cpp index 87d84297226..a57607d23f6 100644 --- a/src/Functions/checkPartMetadataCache.cpp +++ b/src/Functions/checkPartMetadataCache.cpp @@ -1,7 +1,6 @@ #include "config_core.h" #if USE_ROCKSDB - #include #include #include diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.h b/src/Storages/MergeTree/MergeTreeMetadataCache.h index 00c783d881d..f8d7c52cb06 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.h +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.h @@ -1,15 +1,13 @@ #pragma once +#include "config_core.h" -#include -#include - +#if USE_ROCKSDB #include #include #include - -#include "config_core.h" -#if USE_ROCKSDB +#include +#include namespace DB { From 0d91887cdc105f7eda79b98e077b69f7a9bd1802 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 30 Dec 2021 16:15:28 +0300 Subject: [PATCH 0149/1647] save versions of parts --- src/Common/TransactionID.cpp | 27 ++++++ src/Common/TransactionID.h | 6 ++ src/Functions/FunctionConstantBase.h | 4 +- .../FunctionsTransactionCounters.cpp | 56 +++++------ src/IO/ReadHelpers.cpp | 6 ++ src/IO/ReadHelpers.h | 1 + src/IO/WriteBufferFromFileDescriptor.cpp | 1 - src/Interpreters/MergeTreeTransaction.cpp | 9 ++ src/Interpreters/TransactionLog.cpp | 16 +--- src/Interpreters/TransactionLog.h | 2 +- .../TransactionVersionMetadata.cpp | 85 +++++++++++++++++ src/Interpreters/TransactionVersionMetadata.h | 7 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 79 +++++++++++++++ src/Storages/MergeTree/IMergeTreeDataPart.h | 4 + src/Storages/MergeTree/MergeTreeData.cpp | 95 +++++++++++++++---- .../01172_transaction_counters.reference | 7 ++ .../01172_transaction_counters.sql | 11 +++ 17 files changed, 354 insertions(+), 62 deletions(-) diff --git a/src/Common/TransactionID.cpp b/src/Common/TransactionID.cpp index 4cf93636c11..8a9894fbe53 100644 --- a/src/Common/TransactionID.cpp +++ b/src/Common/TransactionID.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include namespace DB { @@ -13,4 +15,29 @@ TIDHash TransactionID::getHash() const return hash.get64(); } + +void TransactionID::write(const TransactionID & tid, WriteBuffer & buf) +{ + writeChar('(', buf); + writeText(tid.start_csn, buf); + writeCString(", ", buf); + writeText(tid.local_tid, buf); + writeCString(", ", buf); + writeText(tid.host_id, buf); + writeChar(')', buf); +} + +TransactionID TransactionID::read(ReadBuffer & buf) +{ + TransactionID tid = Tx::EmptyTID; + assertChar('(', buf); + readText(tid.start_csn, buf); + assertString(", ", buf); + readText(tid.local_tid, buf); + assertString(", ", buf); + readText(tid.host_id, buf); + assertChar(')', buf); + return tid; +} + } diff --git a/src/Common/TransactionID.h b/src/Common/TransactionID.h index 991dab66e71..2652667ddd1 100644 --- a/src/Common/TransactionID.h +++ b/src/Common/TransactionID.h @@ -25,6 +25,7 @@ namespace Tx const CSN MaxReservedCSN = 2; const LocalTID PrehistoricLocalTID = 1; + const LocalTID DummyLocalTID = 1; const LocalTID MaxReservedLocalTID = 16; } @@ -57,12 +58,17 @@ struct TransactionID assert((local_tid == Tx::PrehistoricLocalTID) == (start_csn == Tx::PrehistoricCSN)); return local_tid == Tx::PrehistoricLocalTID; } + + + static void write(const TransactionID & tid, WriteBuffer & buf); + static TransactionID read(ReadBuffer & buf); }; namespace Tx { const TransactionID EmptyTID = {0, 0, UUIDHelpers::Nil}; const TransactionID PrehistoricTID = {PrehistoricCSN, PrehistoricLocalTID, UUIDHelpers::Nil}; + const TransactionID DummyTID = {PrehistoricCSN, DummyLocalTID, UUIDHelpers::Nil}; /// So far, that changes will never become visible const CSN RolledBackCSN = std::numeric_limits::max(); diff --git a/src/Functions/FunctionConstantBase.h b/src/Functions/FunctionConstantBase.h index 2d237c77256..c178b3a256e 100644 --- a/src/Functions/FunctionConstantBase.h +++ b/src/Functions/FunctionConstantBase.h @@ -41,9 +41,9 @@ public: bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; } - ColumnPtr executeImpl(const ColumnsWithTypeAndName &, const DataTypePtr &, size_t input_rows_count) const override + ColumnPtr executeImpl(const ColumnsWithTypeAndName &, const DataTypePtr & result_type, size_t input_rows_count) const override { - return ColumnT().createColumnConst(input_rows_count, constant_value); + return result_type->createColumnConst(input_rows_count, constant_value); } private: diff --git a/src/Functions/FunctionsTransactionCounters.cpp b/src/Functions/FunctionsTransactionCounters.cpp index f2547734e52..f553d0a8be1 100644 --- a/src/Functions/FunctionsTransactionCounters.cpp +++ b/src/Functions/FunctionsTransactionCounters.cpp @@ -1,6 +1,10 @@ +#include +#include +#include #include #include #include +#include namespace DB @@ -9,44 +13,40 @@ namespace DB namespace { -class FunctionTransactionID : public IFunction +class FunctionTransactionID : public FunctionConstantBase { public: static constexpr auto name = "transactionID"; - - static FunctionPtr create(ContextPtr context) - { - return std::make_shared(context->getCurrentTransaction()); - } - - explicit FunctionTransactionID(MergeTreeTransactionPtr && txn_) : txn(txn_) - { - } - - String getName() const override { return name; } - - size_t getNumberOfArguments() const override { return 0; } - - DataTypePtr getReturnTypeImpl(const DataTypes &) const override - { - return getTransactionIDDataType(); - } - - bool isDeterministic() const override { return false; } - bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; } - - ColumnPtr executeImpl(const ColumnsWithTypeAndName &, const DataTypePtr & result_type, size_t input_rows_count) const override + static Tuple getValue(const MergeTreeTransactionPtr & txn) { Tuple res; if (txn) res = {txn->tid.start_csn, txn->tid.local_tid, txn->tid.host_id}; else res = {UInt64(0), UInt64(0), UUIDHelpers::Nil}; - return result_type->createColumnConst(input_rows_count, res); + return res; } -private: - MergeTreeTransactionPtr txn; + DataTypePtr getReturnTypeImpl(const DataTypes & /*arguments*/) const override { return getTransactionIDDataType(); } + + static FunctionPtr create(ContextPtr context) { return std::make_shared(context); } + explicit FunctionTransactionID(ContextPtr context) : FunctionConstantBase(getValue(context->getCurrentTransaction()), context->isDistributed()) {} +}; + +class FunctionTransactionLatestSnapshot : public FunctionConstantBase +{ +public: + static constexpr auto name = "transactionLatestSnapshot"; + static FunctionPtr create(ContextPtr context) { return std::make_shared(context); } + explicit FunctionTransactionLatestSnapshot(ContextPtr context) : FunctionConstantBase(TransactionLog::instance().getLatestSnapshot(), context->isDistributed()) {} +}; + +class FunctionTransactionOldestSnapshot : public FunctionConstantBase +{ +public: + static constexpr auto name = "transactionOldestSnapshot"; + static FunctionPtr create(ContextPtr context) { return std::make_shared(context); } + explicit FunctionTransactionOldestSnapshot(ContextPtr context) : FunctionConstantBase(TransactionLog::instance().getOldestSnapshot(), context->isDistributed()) {} }; } @@ -54,6 +54,8 @@ private: void registerFunctionsTransactionCounters(FunctionFactory & factory) { factory.registerFunction(); + factory.registerFunction(); + factory.registerFunction(); } } diff --git a/src/IO/ReadHelpers.cpp b/src/IO/ReadHelpers.cpp index b0a6838b81e..ec66630415e 100644 --- a/src/IO/ReadHelpers.cpp +++ b/src/IO/ReadHelpers.cpp @@ -138,6 +138,12 @@ void assertEOF(ReadBuffer & buf) throwAtAssertionFailed("eof", buf); } +void assertNotEOF(ReadBuffer & buf) +{ + if (buf.eof()) + throw Exception("Attempt to read after eof", ErrorCodes::ATTEMPT_TO_READ_AFTER_EOF); +} + void assertStringCaseInsensitive(const char * s, ReadBuffer & buf) { diff --git a/src/IO/ReadHelpers.h b/src/IO/ReadHelpers.h index b2ad4035cdc..4223bce2d1c 100644 --- a/src/IO/ReadHelpers.h +++ b/src/IO/ReadHelpers.h @@ -163,6 +163,7 @@ void readVectorBinary(std::vector & v, ReadBuffer & buf, size_t MAX_VECTOR_SI void assertString(const char * s, ReadBuffer & buf); void assertEOF(ReadBuffer & buf); +void assertNotEOF(ReadBuffer & buf); [[noreturn]] void throwAtAssertionFailed(const char * s, ReadBuffer & buf); diff --git a/src/IO/WriteBufferFromFileDescriptor.cpp b/src/IO/WriteBufferFromFileDescriptor.cpp index b91114995e8..d3ca4a9fc32 100644 --- a/src/IO/WriteBufferFromFileDescriptor.cpp +++ b/src/IO/WriteBufferFromFileDescriptor.cpp @@ -133,7 +133,6 @@ off_t WriteBufferFromFileDescriptor::seek(off_t offset, int whence) // NOLINT return res; } - void WriteBufferFromFileDescriptor::truncate(off_t length) // NOLINT { int res = ftruncate(fd, length); diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index 662edd2cac8..82c2429210a 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -79,6 +79,7 @@ void MergeTreeTransaction::addNewPart(const StoragePtr & storage, const DataPart storages.insert(storage); creating_parts.push_back(new_part); + new_part->storeVersionMetadata(); } void MergeTreeTransaction::removeOldPart(const StoragePtr & storage, const DataPartPtr & part_to_remove) @@ -91,6 +92,7 @@ void MergeTreeTransaction::removeOldPart(const StoragePtr & storage, const DataP storages.insert(storage); removing_parts.push_back(part_to_remove); + part_to_remove->storeVersionMetadata(); } void MergeTreeTransaction::addMutation(const StoragePtr & table, const String & mutation_id) @@ -123,9 +125,16 @@ void MergeTreeTransaction::afterCommit(CSN assigned_csn) noexcept [[maybe_unused]] CSN prev_value = csn.exchange(assigned_csn); assert(prev_value == Tx::CommittingCSN); for (const auto & part : creating_parts) + { part->versions.mincsn.store(csn); + part->storeVersionMetadata(); + } + for (const auto & part : removing_parts) + { part->versions.maxcsn.store(csn); + part->storeVersionMetadata(); + } } bool MergeTreeTransaction::rollback() noexcept diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 94cb8cc1827..99a4c44fb5c 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -58,13 +58,7 @@ TransactionID TransactionLog::parseTID(const String & csn_node_content) return tid; ReadBufferFromString buf{csn_node_content}; - assertChar('(', buf); - readText(tid.start_csn, buf); - assertString(", ", buf); - readText(tid.local_tid, buf); - assertString(", ", buf); - readText(tid.host_id, buf); - assertChar(')', buf); + tid = TransactionID::read(buf); assertEOF(buf); return tid; } @@ -72,13 +66,7 @@ TransactionID TransactionLog::parseTID(const String & csn_node_content) String TransactionLog::writeTID(const TransactionID & tid) { WriteBufferFromOwnString buf; - writeChar('(', buf); - writeText(tid.start_csn, buf); - writeCString(", ", buf); - writeText(tid.local_tid, buf); - writeCString(", ", buf); - writeText(tid.host_id, buf); - writeChar(')', buf); + TransactionID::write(tid, buf); return buf.str(); } diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index 44c6ccf53cc..3b9660d46eb 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -22,6 +22,7 @@ public: ~TransactionLog(); Snapshot getLatestSnapshot() const; + Snapshot getOldestSnapshot() const; /// Allocated TID, returns transaction object MergeTreeTransactionPtr beginTransaction(); @@ -35,7 +36,6 @@ public: MergeTreeTransactionPtr tryGetRunningTransaction(const TIDHash & tid); - Snapshot getOldestSnapshot() const; private: void loadLogFromZooKeeper(); diff --git a/src/Interpreters/TransactionVersionMetadata.cpp b/src/Interpreters/TransactionVersionMetadata.cpp index 59d3b1ef4b9..755ad161d24 100644 --- a/src/Interpreters/TransactionVersionMetadata.cpp +++ b/src/Interpreters/TransactionVersionMetadata.cpp @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include //#include @@ -13,6 +16,7 @@ namespace ErrorCodes { extern const int SERIALIZATION_ERROR; extern const int LOGICAL_ERROR; + extern const int CANNOT_PARSE_TEXT; } /// It can be used for introspection purposes only @@ -234,6 +238,87 @@ bool VersionMetadata::canBeRemoved(Snapshot oldest_snapshot_version) return max <= oldest_snapshot_version; } +void VersionMetadata::write(WriteBuffer & buf) const +{ + writeCString("version: 1", buf); + writeCString("\nmintid: ", buf); + TransactionID::write(mintid, buf); + if (CSN min = mincsn.load()) + { + writeCString("\nmincsn: ", buf); + writeText(min, buf); + } + + if (!maxtid.isEmpty()) + { + writeCString("\nmaxtid: ", buf); + TransactionID::write(maxtid, buf); + if (CSN max = maxcsn.load()) + { + writeCString("\nmaxcsn: ", buf); + writeText(max, buf); + } + } +} + +void VersionMetadata::read(ReadBuffer & buf) +{ + assertString("version: 1", buf); + assertString("\nmintid: ", buf); + mintid = TransactionID::read(buf); + if (buf.eof()) + return; + + String name; + constexpr size_t size = 8; + name.resize(size); + + assertChar('\n', buf); + buf.readStrict(name.data(), size); + if (name == "mincsn: ") + { + UInt64 min; + readText(min, buf); + mincsn = min; + if (buf.eof()) + return; + } + + assertChar('\n', buf); + buf.readStrict(name.data(), size); + if (name == "maxtid: ") + { + maxtid = TransactionID::read(buf); + maxtid_lock = maxtid.getHash(); + if (buf.eof()) + return; + } + + assertChar('\n', buf); + buf.readStrict(name.data(), size); + if (name == "maxcsn: ") + { + if (maxtid.isEmpty()) + throw Exception(ErrorCodes::CANNOT_PARSE_TEXT, "Found maxcsn in metadata file, but maxtid is {}", maxtid); + UInt64 max; + readText(max, buf); + maxcsn = max; + } + + assertEOF(buf); +} + +String VersionMetadata::toString(bool one_line) const +{ + WriteBufferFromOwnString buf; + write(buf); + String res = buf.str(); + if (one_line) + std::replace(res.begin(), res.end(), '\n', ' '); + return res; +} + + DataTypePtr getTransactionIDDataType() { DataTypes types; diff --git a/src/Interpreters/TransactionVersionMetadata.h b/src/Interpreters/TransactionVersionMetadata.h index 48049da2a23..ed25a205d6a 100644 --- a/src/Interpreters/TransactionVersionMetadata.h +++ b/src/Interpreters/TransactionVersionMetadata.h @@ -6,7 +6,7 @@ namespace DB struct VersionMetadata { - const TransactionID mintid = Tx::EmptyTID; + TransactionID mintid = Tx::EmptyTID; TransactionID maxtid = Tx::EmptyTID; std::atomic maxtid_lock = 0; @@ -30,6 +30,11 @@ struct VersionMetadata void setMinTID(const TransactionID & tid); bool canBeRemoved(Snapshot oldest_snapshot_version); + + void write(WriteBuffer & buf) const; + void read(ReadBuffer & buf); + + String toString(bool one_line = true) const; }; DataTypePtr getTransactionIDDataType(); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 83328594363..f7f139ea3aa 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1092,6 +1092,84 @@ void IMergeTreeDataPart::loadColumns(bool require) setColumns(loaded_columns, infos); } + +void IMergeTreeDataPart::storeVersionMetadata() const +{ + assert(!versions.mintid.isEmpty()); + if (versions.mintid.isPrehistoric() && (versions.maxtid.isEmpty() || versions.maxtid.isPrehistoric())) + return; + + String version_file_name = fs::path(getFullRelativePath()) / TXN_VERSION_METADATA_FILE_NAME; + String tmp_version_file_name = version_file_name + ".tmp"; + DiskPtr disk = volume->getDisk(); + { + auto out = volume->getDisk()->writeFile(tmp_version_file_name, 4096, WriteMode::Rewrite); + versions.write(*out); + out->finalize(); + out->sync(); + } + + SyncGuardPtr sync_guard; + if (storage.getSettings()->fsync_part_directory) + sync_guard = volume->getDisk()->getDirectorySyncGuard(getFullRelativePath()); + disk->moveFile(tmp_version_file_name, version_file_name); +} + +void IMergeTreeDataPart::loadVersionMetadata() const +{ + String version_file_name = fs::path(getFullRelativePath()) / TXN_VERSION_METADATA_FILE_NAME; + String tmp_version_file_name = version_file_name + ".tmp"; + DiskPtr disk = volume->getDisk(); + + auto remove_tmp_file = [&]() + { + auto last_modified = disk->getLastModified(tmp_version_file_name); + auto buf = openForReading(disk, tmp_version_file_name); + String content; + readStringUntilEOF(content, *buf); + LOG_WARNING(storage.log, "Found file {} that was last modified on {}, has size {} and the following content: {}", + tmp_version_file_name, last_modified.epochTime(), content.size(), content); + disk->removeFile(tmp_version_file_name); + }; + + if (disk->exists(version_file_name)) + { + auto buf = openForReading(disk, version_file_name); + versions.read(*buf); + if (disk->exists(tmp_version_file_name)) + remove_tmp_file(); + return; + } + + /// Four (?) cases are possible: + /// 1. Part was created without transactions. + /// 2. Version metadata file was not renamed from *.tmp on part creation. + /// 3. Version metadata were written to *.tmp file, but hard restart happened before fsync + /// We must remove part in this case, but we cannot distinguish it from the first case. + /// TODO Write something to some checksummed file if part was created with transaction, + /// so part will be ether broken or known to be created by transaction. + /// 4. Fsyncs in storeVersionMetadata() work incorrectly. + + if (!disk->exists(tmp_version_file_name)) + { + /// Case 1 (or 3). + /// We do not have version metadata and transactions history for old parts, + /// so let's consider that such parts were created by some ancient transaction + /// and were committed with some prehistoric CSN. + versions.setMinTID(Tx::PrehistoricTID); + versions.mincsn = Tx::PrehistoricCSN; + return; + } + + /// Case 2. + /// Content of *.tmp file may be broken, just use fake TID. + /// Transaction was not committed if *.tmp file was not renamed, so we should complete rollback by removing part. + versions.setMinTID(Tx::DummyTID); + versions.mincsn = Tx::RolledBackCSN; + remove_tmp_file(); +} + + bool IMergeTreeDataPart::shallParticipateInMerges(const StoragePolicyPtr & storage_policy) const { /// `IMergeTreeDataPart::volume` describes space where current part belongs, and holds @@ -1276,6 +1354,7 @@ void IMergeTreeDataPart::remove() const disk->removeSharedFileIfExists(fs::path(to) / DEFAULT_COMPRESSION_CODEC_FILE_NAME, *keep_shared_data); disk->removeSharedFileIfExists(fs::path(to) / DELETE_ON_DESTROY_MARKER_FILE_NAME, *keep_shared_data); + disk->removeSharedFileIfExists(fs::path(to) / TXN_VERSION_METADATA_FILE_NAME, *keep_shared_data); disk->removeDirectory(to); } diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 440d0848b1e..fcee542c847 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -404,6 +404,8 @@ public: /// (number of rows, number of rows with default values, etc). static inline constexpr auto SERIALIZATION_FILE_NAME = "serialization.json"; + static inline constexpr auto TXN_VERSION_METADATA_FILE_NAME = "txn_version.txt"; + /// Checks that all TTLs (table min/max, column ttls, so on) for part /// calculated. Part without calculated TTL may exist if TTL was added after /// part creation (using alter query with materialize_ttl setting). @@ -413,6 +415,8 @@ public: /// Required for distinguish different copies of the same part on S3 String getUniqueId() const; + void storeVersionMetadata() const; + void loadVersionMetadata() const; protected: /// Total size of all columns, calculated once in calcuateColumnSizesOnDisk diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 43b17f3c706..369e6369759 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1277,14 +1277,83 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) for (auto & part : duplicate_parts_to_remove) part->remove(); - for (const auto & part : data_parts_by_state_and_info) + auto deactivate_part = [&] (DataPartIteratorByStateAndInfo it) { - /// We do not have version metadata and transactions history for old parts, - /// so let's consider that such parts were created by some ancient transaction - /// and were committed with some prehistoric CSN. - /// TODO Transactions: distinguish "prehistoric" parts from uncommitted parts in case of hard restart - part->versions.setMinTID(Tx::PrehistoricTID); - part->versions.mincsn.store(Tx::PrehistoricCSN, std::memory_order_relaxed); + (*it)->remove_time.store((*it)->modification_time, std::memory_order_relaxed); + modifyPartState(it, DataPartState::Outdated); + removePartContributionToDataVolume(*it); + }; + + /// All parts are in "Committed" state after loading + assert(std::find_if(data_parts_by_state_and_info.begin(), data_parts_by_state_and_info.end(), + [](const auto & part) + { + return part->getState() != DataPartState::Committed; + }) == data_parts_by_state_and_info.end()); + + auto iter = data_parts_by_state_and_info.begin(); + while (iter != data_parts_by_state_and_info.end() && (*iter)->getState() == DataPartState::Committed) + { + const DataPartPtr & part = *iter; + part->loadVersionMetadata(); + VersionMetadata & versions = part->versions; + + /// Check if CSNs were witten after committing transaction, update and write if needed. + bool versions_updated = false; + if (!versions.mintid.isEmpty() && !part->versions.mincsn) + { + auto min = TransactionLog::instance().getCSN(versions.mintid); + if (!min) + { + /// Transaction that created this part was not committed. Remove part. + min = Tx::RolledBackCSN; + } + LOG_TRACE(log, "Will fix version metadata of {} after unclean restart: part has mintid={}, setting mincsn={}", + part->name, versions.mintid, min); + versions.mincsn = min; + versions_updated = true; + } + if (!versions.maxtid.isEmpty() && !part->versions.maxcsn) + { + auto max = TransactionLog::instance().getCSN(versions.maxtid); + if (max) + { + LOG_TRACE(log, "Will fix version metadata of {} after unclean restart: part has maxtid={}, setting maxcsn={}", + part->name, versions.maxtid, max); + versions.maxcsn = max; + } + else + { + /// Transaction that tried to remove this part was not committed. Clear maxtid. + LOG_TRACE(log, "Will fix version metadata of {} after unclean restart: clearing maxtid={}", + part->name, versions.maxtid); + versions.unlockMaxTID(versions.maxtid); + } + versions_updated = true; + } + + /// Sanity checks + bool csn_order = !versions.maxcsn || versions.mincsn <= versions.maxcsn; + bool min_start_csn_order = versions.mintid.start_csn <= versions.mincsn; + bool max_start_csn_order = versions.maxtid.start_csn <= versions.maxcsn; + bool mincsn_known = versions.mincsn; + if (!csn_order || !min_start_csn_order || !max_start_csn_order || !mincsn_known) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Part {} has invalid versions metadata: {}", part->name, versions.toString()); + + if (versions_updated) + part->storeVersionMetadata(); + + /// Deactivate part if creation was not committed or if removal was. + if (versions.mincsn == Tx::RolledBackCSN || versions.maxcsn) + { + auto next_it = std::next(iter); + deactivate_part(iter); + iter = next_it; + } + else + { + ++iter; + } } /// Delete from the set of current parts those parts that are covered by another part (those parts that @@ -1297,15 +1366,6 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) auto prev_jt = data_parts_by_state_and_info.begin(); auto curr_jt = std::next(prev_jt); - auto deactivate_part = [&] (DataPartIteratorByStateAndInfo it) - { - (*it)->remove_time.store((*it)->modification_time, std::memory_order_relaxed); - modifyPartState(it, DataPartState::Outdated); - (*it)->versions.lockMaxTID(Tx::PrehistoricTID); - (*it)->versions.maxcsn.store(Tx::PrehistoricCSN, std::memory_order_relaxed); - removePartContributionToDataVolume(*it); - }; - (*prev_jt)->assertState({DataPartState::Committed}); while (curr_jt != data_parts_by_state_and_info.end() && (*curr_jt)->getState() == DataPartState::Committed) @@ -3245,6 +3305,8 @@ static void loadPartAndFixMetadataImpl(MergeTreeData::MutableDataPartPtr part) part->loadColumnsChecksumsIndexes(false, true); part->modification_time = disk->getLastModified(full_part_path).epochTime(); + disk->removeFileIfExists(fs::path(full_part_path) / IMergeTreeDataPart::DELETE_ON_DESTROY_MARKER_FILE_NAME); + disk->removeFileIfExists(fs::path(full_part_path) / IMergeTreeDataPart::TXN_VERSION_METADATA_FILE_NAME); } void MergeTreeData::calculateColumnAndSecondaryIndexSizesImpl() @@ -5228,6 +5290,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeData::cloneAndLoadDataPartOnSameDisk( LOG_DEBUG(log, "Cloning part {} to {}", fullPath(disk, src_part_path), fullPath(disk, dst_part_path)); localBackup(disk, src_part_path, dst_part_path); disk->removeFileIfExists(fs::path(dst_part_path) / IMergeTreeDataPart::DELETE_ON_DESTROY_MARKER_FILE_NAME); + disk->removeFileIfExists(fs::path(dst_part_path) / IMergeTreeDataPart::TXN_VERSION_METADATA_FILE_NAME); auto single_disk_volume = std::make_shared(disk->getName(), disk, 0); auto dst_data_part = createPart(dst_part_name, dst_part_info, single_disk_volume, tmp_dst_part_name); diff --git a/tests/queries/0_stateless/01172_transaction_counters.reference b/tests/queries/0_stateless/01172_transaction_counters.reference index 1f463a25c20..c1a551cb049 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.reference +++ b/tests/queries/0_stateless/01172_transaction_counters.reference @@ -9,3 +9,10 @@ 4 all_2_2_0 18446744073709551615 (0,0,'00000000-0000-0000-0000-000000000000') 0 4 all_3_3_0 0 (0,0,'00000000-0000-0000-0000-000000000000') 0 5 1 +6 all_1_1_0 0 +6 all_3_3_0 1 +6 all_4_4_0 1 +7 all_1_1_0 (0,0,'00000000-0000-0000-0000-000000000000') 0 +7 all_3_3_0 (0,0,'00000000-0000-0000-0000-000000000000') 0 +7 all_4_4_0 (0,0,'00000000-0000-0000-0000-000000000000') 0 +8 1 diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql index ca114643130..4ca0c3b3bf5 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.sql +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -21,4 +21,15 @@ select 4, name, mincsn, maxtid, maxcsn from system.parts where database=currentD select 5, transactionID().3 == serverUUID(); commit; +detach table txn_counters; +attach table txn_counters; + +begin transaction; +insert into txn_counters(n) values (4); +select 6, system.parts.name, txn_counters.mintid = system.parts.mintid from txn_counters join system.parts on txn_counters._part = system.parts.name where database=currentDatabase() and table='txn_counters' order by system.parts.name; +select 7, name, maxtid, maxcsn from system.parts where database=currentDatabase() and table='txn_counters' order by system.parts.name; +select 8, transactionID().3 == serverUUID(); +commit; + + drop table txn_counters; From 05dfc4eb25be9d0b761c798968fffc4c15da14c0 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Fri, 31 Dec 2021 11:13:38 +0800 Subject: [PATCH 0150/1647] fix building and stateless test --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 13 ++++++------- src/Storages/MergeTree/IMergeTreeDataPart.h | 12 ++++++------ .../0_stateless/01233_check_part_meta_cache.sql | 2 +- .../01233_check_part_meta_cache_in_atomic.sql | 2 +- .../01233_check_part_meta_cache_replicated.sql | 2 +- ...3_check_part_meta_cache_replicated_in_atomic.sql | 2 +- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index abcc52dd295..83e91e7ad27 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -907,7 +907,7 @@ void IMergeTreeDataPart::loadDefaultCompressionCodec() } } -void IMergeTreeDataPart::appendFilesOfDefaultCompressionCodec(Strings & files) const +void IMergeTreeDataPart::appendFilesOfDefaultCompressionCodec(Strings & files) { files.push_back(DEFAULT_COMPRESSION_CODEC_FILE_NAME); } @@ -1075,7 +1075,7 @@ void IMergeTreeDataPart::loadChecksums(bool require) } } -void IMergeTreeDataPart::appendFilesOfChecksums(Strings & files) const +void IMergeTreeDataPart::appendFilesOfChecksums(Strings & files) { files.push_back("checksums.txt"); } @@ -1215,7 +1215,7 @@ void IMergeTreeDataPart::loadRowsCount() } } -void IMergeTreeDataPart::appendFilesOfRowsCount(Strings & files) const +void IMergeTreeDataPart::appendFilesOfRowsCount(Strings & files) { files.push_back("count.txt"); } @@ -1269,7 +1269,7 @@ void IMergeTreeDataPart::loadTTLInfos() } -void IMergeTreeDataPart::appendFilesOfTTLInfos(Strings & files) const +void IMergeTreeDataPart::appendFilesOfTTLInfos(Strings & files) { files.push_back("ttl.txt"); } @@ -1307,7 +1307,7 @@ void IMergeTreeDataPart::loadUUID() } } -void IMergeTreeDataPart::appendFilesOfUUID(Strings & files) const +void IMergeTreeDataPart::appendFilesOfUUID(Strings & files) { files.push_back(UUID_FILE_NAME); } @@ -1388,7 +1388,7 @@ void IMergeTreeDataPart::loadColumns(bool require) setColumns(loaded_columns, infos); } -void IMergeTreeDataPart::appendFilesOfColumns(Strings & files) const +void IMergeTreeDataPart::appendFilesOfColumns(Strings & files) { files.push_back("columns.txt"); } @@ -1475,7 +1475,6 @@ void IMergeTreeDataPart::modifyAllMetadataCaches(ModifyCacheType type, bool incl assert(use_metadata_cache); Strings files; - files.reserve(16); appendFilesOfColumnsChecksumsIndexes(files, include_projection); LOG_TRACE( storage.log, diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index a992d2dadd7..1457a643b18 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -503,17 +503,17 @@ private: /// Reads part unique identifier (if exists) from uuid.txt void loadUUID(); - void appendFilesOfUUID(Strings & files) const; + static void appendFilesOfUUID(Strings & files); /// Reads columns names and types from columns.txt void loadColumns(bool require); - void appendFilesOfColumns(Strings & files) const; + static void appendFilesOfColumns(Strings & files); /// If checksums.txt exists, reads file's checksums (and sizes) from it void loadChecksums(bool require); - void appendFilesOfChecksums(Strings & files) const; + static void appendFilesOfChecksums(Strings & files); /// Loads marks index granularity into memory virtual void loadIndexGranularity(); @@ -529,12 +529,12 @@ private: /// For the older format version calculates rows count from the size of a column with a fixed size. void loadRowsCount(); - void appendFilesOfRowsCount(Strings & files) const; + static void appendFilesOfRowsCount(Strings & files); /// Loads ttl infos in json format from file ttl.txt. If file doesn't exists assigns ttl infos with all zeros void loadTTLInfos(); - void appendFilesOfTTLInfos(Strings & files) const; + static void appendFilesOfTTLInfos(Strings & files); void loadPartitionAndMinMaxIndex(); @@ -549,7 +549,7 @@ private: /// any specifial compression. void loadDefaultCompressionCodec(); - void appendFilesOfDefaultCompressionCodec(Strings & files) const; + static void appendFilesOfDefaultCompressionCodec(Strings & files); /// Found column without specific compression and return codec /// for this column with default parameters. diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache.sql b/tests/queries/0_stateless/01233_check_part_meta_cache.sql index c15c2883436..70de4e0de9e 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache.sql @@ -2,8 +2,8 @@ -- Create table under database with engine ordinary. set mutations_sync = 1; -DROP DATABASE IF EXISTS test_metadata_cache; DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache; +DROP DATABASE IF EXISTS test_metadata_cache; CREATE DATABASE test_metadata_cache ENGINE = Ordinary; CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql index 6bd8425a8ea..61452368b52 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql @@ -2,8 +2,8 @@ -- Create table under database with engine atomic. set mutations_sync = 1; -DROP DATABASE IF EXISTS test_metadata_cache; DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC; +DROP DATABASE IF EXISTS test_metadata_cache; CREATE DATABASE test_metadata_cache ENGINE = Atomic; CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql index 2c490c80d70..5aff175f97e 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql @@ -4,8 +4,8 @@ -- Create table under database with engine ordinary. set mutations_sync = 1; set replication_alter_partitions_sync = 2; -DROP DATABASE IF EXISTS test_metadata_cache; DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache; +DROP DATABASE IF EXISTS test_metadata_cache; CREATE DATABASE test_metadata_cache ENGINE = Ordinary; CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql index c7cc2a09899..7a7846bab05 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql @@ -4,8 +4,8 @@ -- Create table under database with engine ordinary. set mutations_sync = 1; set replication_alter_partitions_sync = 2; -DROP DATABASE IF EXISTS test_metadata_cache ; DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC; +DROP DATABASE IF EXISTS test_metadata_cache; CREATE DATABASE test_metadata_cache ENGINE = Atomic; CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); From 770ff591552bcc47461da06ef00650eda138351d Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Fri, 31 Dec 2021 13:32:00 +0800 Subject: [PATCH 0151/1647] fix building --- src/Storages/MergeTree/MergeTreePartition.cpp | 2 +- src/Storages/MergeTree/MergeTreePartition.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index f1a669eeb2f..8a85e374062 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -465,7 +465,7 @@ KeyDescription MergeTreePartition::adjustPartitionKey(const StorageMetadataPtr & } -void MergeTreePartition::appendFiles(const MergeTreeData & storage, Strings& files) const +void MergeTreePartition::appendFiles(const MergeTreeData & storage, Strings& files) { auto metadata_snapshot = storage.getInMemoryMetadataPtr(); if (!metadata_snapshot->hasPartitionKey()) diff --git a/src/Storages/MergeTree/MergeTreePartition.h b/src/Storages/MergeTree/MergeTreePartition.h index 644ddd5ba88..b1bf64550c6 100644 --- a/src/Storages/MergeTree/MergeTreePartition.h +++ b/src/Storages/MergeTree/MergeTreePartition.h @@ -54,7 +54,7 @@ public: void create(const StorageMetadataPtr & metadata_snapshot, Block block, size_t row, ContextPtr context); - void appendFiles(const MergeTreeData & storage, Strings & files) const; + static void appendFiles(const MergeTreeData & storage, Strings & files); /// Adjust partition key and execute its expression on block. Return sample block according to used expression. static NamesAndTypesList executePartitionByExpression(const StorageMetadataPtr & metadata_snapshot, Block & block, ContextPtr context); From e3da4f07a9779308a6d9eef4fabde3f681f3f4fe Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Sat, 1 Jan 2022 08:36:54 +0800 Subject: [PATCH 0152/1647] fix stateless test --- tests/queries/0_stateless/01233_check_part_meta_cache.sql | 2 +- .../0_stateless/01233_check_part_meta_cache_in_atomic.sql | 2 +- .../0_stateless/01233_check_part_meta_cache_replicated.sql | 7 +++---- .../01233_check_part_meta_cache_replicated_in_atomic.sql | 7 +++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache.sql b/tests/queries/0_stateless/01233_check_part_meta_cache.sql index 70de4e0de9e..6c1a1232cab 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache.sql @@ -1,4 +1,4 @@ ---Tags: no-fasttest +-- Tags: no-parallel, no-fasttest -- Create table under database with engine ordinary. set mutations_sync = 1; diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql index 61452368b52..af8d6f888a7 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql @@ -1,4 +1,4 @@ --- Tags: no-fasttest +-- Tags: no-parallel, no-fasttest -- Create table under database with engine atomic. set mutations_sync = 1; diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql index 5aff175f97e..955c9b49957 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql @@ -1,5 +1,4 @@ --- Tags: no-fasttest --- Tags: zookeeper +-- Tags: no-fasttest, no-parallel, zookeeper -- Create table under database with engine ordinary. set mutations_sync = 1; @@ -7,7 +6,7 @@ set replication_alter_partitions_sync = 2; DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache; DROP DATABASE IF EXISTS test_metadata_cache; CREATE DATABASE test_metadata_cache ENGINE = Ordinary; -CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; +CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/test_metadata_cache/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert first batch of data. @@ -70,7 +69,7 @@ with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadat -- Recreate table with projection. drop table if exists test_metadata_cache.check_part_metadata_cache ; -CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; +CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/test_metadata_cache/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql index 7a7846bab05..6943f721f70 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql @@ -1,5 +1,4 @@ --- Tags: no-fasttest --- Tags: zookeeper +-- Tags: no-fasttest, zookeeper, no-parallel -- Create table under database with engine ordinary. set mutations_sync = 1; @@ -7,7 +6,7 @@ set replication_alter_partitions_sync = 2; DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC; DROP DATABASE IF EXISTS test_metadata_cache; CREATE DATABASE test_metadata_cache ENGINE = Atomic; -CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; +CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/test_metadata_cache/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); -- Insert first batch of data. @@ -70,7 +69,7 @@ with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadat -- Recreate table with projection. drop table if exists test_metadata_cache.check_part_metadata_cache SYNC; -CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/{database}/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k TTL p + INTERVAL 15 YEAR settings use_metadata_cache = 1; +CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/test_metadata_cache/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k TTL p + INTERVAL 15 YEAR settings use_metadata_cache = 1; -- Insert first batch of data. INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); From eaf5a72fb79cfbdfb0f4fa227777deb9b5781ee1 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Sat, 1 Jan 2022 09:06:08 +0800 Subject: [PATCH 0153/1647] fix stateless test --- .../0_stateless/01233_check_part_meta_cache_replicated.sql | 1 + .../01233_check_part_meta_cache_replicated_in_atomic.sql | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql index 955c9b49957..bdb43c4e905 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql @@ -1,4 +1,5 @@ -- Tags: no-fasttest, no-parallel, zookeeper +-- Tag no-parallel: static zk path -- Create table under database with engine ordinary. set mutations_sync = 1; diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql index 6943f721f70..4e491c301f2 100644 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql +++ b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql @@ -1,4 +1,5 @@ -- Tags: no-fasttest, zookeeper, no-parallel +-- Tag no-parallel: static zk path -- Create table under database with engine ordinary. set mutations_sync = 1; From e13e1f5d7e164aec7b275d4bd1b883d0c2d1cbf4 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 4 Jan 2022 11:44:10 +0800 Subject: [PATCH 0154/1647] add unit test && use enum_name --- programs/server/Server.cpp | 12 ++- src/Functions/checkPartMetadataCache.cpp | 4 +- src/Interpreters/Context.cpp | 7 +- .../tests/gtest_merge_tree_metadata_cache.cpp | 80 +++++++++++++++++++ src/Processors/examples/CMakeLists.txt | 4 - .../examples/merge_tree_metadata_cache.cpp | 51 ------------ src/Storages/MergeTree/IMergeTreeDataPart.h | 17 ++-- 7 files changed, 102 insertions(+), 73 deletions(-) create mode 100644 src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp delete mode 100644 src/Processors/examples/merge_tree_metadata_cache.cpp diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index f4d388d559d..ed863271e11 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -823,7 +823,17 @@ if (ThreadFuzzer::instance().isEffective()) /// Initialize merge tree metadata cache { size_t size = config().getUInt64("meta_file_cache_size", 256 << 20); - global_context->initializeMergeTreeMetadataCache(path_str + "/" + "rocksdb", size); + + try + { + global_context->initializeMergeTreeMetadataCache(path_str + "/" + "rocksdb", size); + } + catch (...) + { + /// Rename rocksdb directory and reinitialize merge tree metadata cache + fs::rename(path / "rocksdb", path / "rocksdb.old"); + global_context->initializeMergeTreeMetadataCache(path_str + "/" + "rocksdb", size); + } } #endif diff --git a/src/Functions/checkPartMetadataCache.cpp b/src/Functions/checkPartMetadataCache.cpp index a57607d23f6..ddcb44f5372 100644 --- a/src/Functions/checkPartMetadataCache.cpp +++ b/src/Functions/checkPartMetadataCache.cpp @@ -39,9 +39,9 @@ public: static FunctionPtr create(ContextPtr context_) { return std::make_shared(context_); } static constexpr DataPartStates part_states - = {DataPartState::Committed, + = {DataPartState::Active, DataPartState::Temporary, - DataPartState::PreCommitted, + DataPartState::PreActive, DataPartState::Outdated, DataPartState::Deleting, DataPartState::DeleteOnDestroy}; diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 3986b5bf822..0cb81ba4056 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2289,11 +2289,10 @@ void Context::initializeMergeTreeMetadataCache(const String & dir, size_t size) table_options.block_cache = cache; options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options)); rocksdb::Status status = rocksdb::DB::Open(options, dir, &db); + if (status != rocksdb::Status::OK()) - { - String message = "Fail to open rocksdb path at: " + dir + " status:" + status.ToString(); - throw Exception(message, ErrorCodes::SYSTEM_ERROR); - } + throw Exception(ErrorCodes::SYSTEM_ERROR, "Fail to open rocksdb path at: {} status:{}", dir, status.ToString()); + shared->merge_tree_metadata_cache = std::make_shared(db); } #endif diff --git a/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp b/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp new file mode 100644 index 00000000000..0679cbf529a --- /dev/null +++ b/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp @@ -0,0 +1,80 @@ +#include + +#if USE_ROCKSDB +#include +#include +#include +#include + +using namespace DB; + +class MergeTreeMetadataCacheTest : public ::testing::Test +{ +public: + void SetUp() override + { + auto shared_context = Context::createShared(); + global_context = Context::createGlobal(shared_context.get()); + global_context->makeGlobalContext(); + global_context->initializeMergeTreeMetadataCache("./db/", 256 << 20); + cache = global_context->getMergeTreeMetadataCache(); + } + + void TearDown() override + { + global_context->shutdown(); + } + + ContextMutablePtr global_context; + MergeTreeMetadataCachePtr cache; +}; + +TEST_F(MergeTreeMetadataCacheTest, testCommon) +{ + std::vector files + = {"columns.txt", "checksums.txt", "primary.idx", "count.txt", "partition.dat", "minmax_p.idx", "default_compression_codec.txt"}; + String prefix = "data/test_metadata_cache/check_part_metadata_cache/201806_1_1_0_4/"; + + for (const auto & file : files) + { + auto status = cache->put(prefix + file, prefix + file); + ASSERT_EQ(status.code(), rocksdb::Status::Code::kOk); + } + + for (const auto & file : files) + { + String value; + auto status = cache->get(prefix + file, value); + ASSERT_EQ(status.code(), rocksdb::Status::Code::kOk); + ASSERT_EQ(value, prefix + file); + } + + Strings keys; + Strings values; + cache->getByPrefix(prefix, keys, values); + ASSERT_EQ(keys.size(), files.size()); + ASSERT_EQ(values.size(), files.size()); + for (size_t i=0; i < files.size(); ++i) + { + ASSERT_EQ(values[i], prefix + keys[i]); + } + + for (const auto & file : files) + { + auto status = cache->del(prefix + file); + ASSERT_EQ(status.code(), rocksdb::Status::Code::kOk); + } + + for (const auto & file : files) + { + String value; + auto status = cache->get(prefix + file, value); + ASSERT_EQ(status.code(), rocksdb::Status::Code::kNotFound); + } + + cache->getByPrefix(prefix, keys, values); + ASSERT_EQ(keys.size(), 0); + ASSERT_EQ(values.size(), 0); +} + +#endif diff --git a/src/Processors/examples/CMakeLists.txt b/src/Processors/examples/CMakeLists.txt index 2b6b9128e4c..e69de29bb2d 100644 --- a/src/Processors/examples/CMakeLists.txt +++ b/src/Processors/examples/CMakeLists.txt @@ -1,4 +0,0 @@ -if (USE_ROCKSDB) - add_executable (merge_tree_metadata_cache merge_tree_metadata_cache.cpp) - target_link_libraries (merge_tree_metadata_cache PRIVATE dbms) -endif() \ No newline at end of file diff --git a/src/Processors/examples/merge_tree_metadata_cache.cpp b/src/Processors/examples/merge_tree_metadata_cache.cpp deleted file mode 100644 index c726eb7ce5a..00000000000 --- a/src/Processors/examples/merge_tree_metadata_cache.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include - -int main() -{ - using namespace DB; - auto shared_context = Context::createShared(); - auto global_context = Context::createGlobal(shared_context.get()); - global_context->makeGlobalContext(); - global_context->initializeMergeTreeMetadataCache("./db/", 256 << 20); - - auto cache = global_context->getMergeTreeMetadataCache(); - - std::vector files - = {"columns.txt", "checksums.txt", "primary.idx", "count.txt", "partition.dat", "minmax_p.idx", "default_compression_codec.txt"}; - String prefix = "data/test_metadata_cache/check_part_metadata_cache/201806_1_1_0_4/"; - - for (const auto & file : files) - { - auto status = cache->put(prefix + file, prefix + file); - std::cout << "put " << file << " " << status.ToString() << std::endl; - } - - for (const auto & file : files) - { - String value; - auto status = cache->get(prefix + file, value); - std::cout << "get " << file << " " << status.ToString() << " " << value << std::endl; - } - - - for (const auto & file : files) - { - auto status = cache->del(prefix + file); - std::cout << "del " << file << " " << status.ToString() << std::endl; - } - - for (const auto & file : files) - { - String value; - auto status = cache->get(prefix + file, value); - std::cout << "get " << file << " " << status.ToString() << " " << value << std::endl; - } - - Strings keys; - Strings values; - cache->getByPrefix(prefix, keys, values); - for (size_t i=0; i #include #include #include @@ -47,21 +48,15 @@ class IMergeTreeDataPart : public std::enable_shared_from_this Date: Tue, 4 Jan 2022 11:48:07 +0800 Subject: [PATCH 0155/1647] remove redudant judge --- src/Storages/MergeTree/MergeTreeMetadataCache.cpp | 5 +---- src/Storages/MergeTree/MergeTreeMetadataCache.h | 6 +++++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp index d9dacadcead..7025a79018b 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp @@ -59,10 +59,7 @@ void MergeTreeMetadataCache::getByPrefix(const String & prefix, Strings & keys, void MergeTreeMetadataCache::shutdown() { - if (rocksdb) - { - rocksdb->Close(); - } + rocksdb->Close(); } } diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.h b/src/Storages/MergeTree/MergeTreeMetadataCache.h index f8d7c52cb06..286c7ebb08e 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.h +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.h @@ -16,7 +16,11 @@ class MergeTreeMetadataCache public: using Status = rocksdb::Status; - explicit MergeTreeMetadataCache(rocksdb::DB * rocksdb_) : rocksdb{rocksdb_} { } + explicit MergeTreeMetadataCache(rocksdb::DB * rocksdb_) : rocksdb{rocksdb_} + { + assert(rocksdb); + } + MergeTreeMetadataCache(const MergeTreeMetadataCache &) = delete; MergeTreeMetadataCache & operator=(const MergeTreeMetadataCache &) = delete; From 92b3f198aa6932b1d7205c727ed7a0828fbe1e59 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 4 Jan 2022 11:52:15 +0800 Subject: [PATCH 0156/1647] throw exception if clickhouse use merge tree metdata cache but not compiled with rocksdb --- src/Storages/MergeTree/MergeTreeData.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index fb04d406c98..d9d9388bdc2 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -321,11 +321,7 @@ MergeTreeData::MergeTreeData( #if !USE_ROCKSDB if (use_metadata_cache) { - LOG_WARNING( - log, - "Can't use merge tree metadata cache if clickhouse was compiled without rocksdb." - "set use_metadata_cache to false forcely"); - use_metadata_cache = false; + throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't use merge tree metadata cache if clickhouse was compiled without rocksdb." "set use_metadata_cache to false forcely"); } #endif From 94d1f7ccb1bc76c96d765f112286ecda1fafe0fa Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 4 Jan 2022 12:36:04 +0800 Subject: [PATCH 0157/1647] enable continue if cache data corrupted --- programs/server/Server.cpp | 23 +++++++++++++++-------- programs/server/config.xml | 7 +++++-- src/Core/SettingsEnums.h | 1 - src/Storages/MergeTree/MergeTreeData.cpp | 9 +++++---- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index ed863271e11..8641623ac07 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -812,17 +812,16 @@ if (ThreadFuzzer::instance().isEffective()) /// Directory with metadata of tables, which was marked as dropped by Atomic database fs::create_directories(path / "metadata_dropped/"); - -#if USE_ROCKSDB - fs::create_directories(path / "rocksdb/"); -#endif } #if USE_ROCKSDB /// Initialize merge tree metadata cache + if (config().has("merge_tree_metadata_cache")) { - size_t size = config().getUInt64("meta_file_cache_size", 256 << 20); + fs::create_directories(path / "rocksdb/"); + size_t size = config().getUInt64("merge_tree_metadata_cache.lru_cache_size", 256 << 20); + bool continue_if_corrupted = config().getBool("merge_tree_metadata_cache.continue_if_corrupted", false); try { @@ -830,9 +829,17 @@ if (ThreadFuzzer::instance().isEffective()) } catch (...) { - /// Rename rocksdb directory and reinitialize merge tree metadata cache - fs::rename(path / "rocksdb", path / "rocksdb.old"); - global_context->initializeMergeTreeMetadataCache(path_str + "/" + "rocksdb", size); + if (continue_if_corrupted) + { + /// Rename rocksdb directory and reinitialize merge tree metadata cache + time_t now = time(nullptr); + fs::rename(path / "rocksdb", path / ("rocksdb.old." + std::to_string(now))); + global_context->initializeMergeTreeMetadataCache(path_str + "/" + "rocksdb", size); + } + else + { + throw; + } } } #endif diff --git a/programs/server/config.xml b/programs/server/config.xml index 470c4dfa35f..c5f8e7eeb94 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -1292,6 +1292,9 @@ --> - - 268435456 + + diff --git a/src/Core/SettingsEnums.h b/src/Core/SettingsEnums.h index 106589f5d24..5d2640da319 100644 --- a/src/Core/SettingsEnums.h +++ b/src/Core/SettingsEnums.h @@ -171,5 +171,4 @@ DECLARE_SETTING_ENUM(ShortCircuitFunctionEvaluation) DECLARE_SETTING_ENUM_WITH_RENAME(EnumComparingMode, FormatSettings::EnumComparingMode) DECLARE_SETTING_ENUM_WITH_RENAME(EscapingRule, FormatSettings::EscapingRule) - } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index d9d9388bdc2..4e0a0a162f6 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -318,11 +318,12 @@ MergeTreeData::MergeTreeData( LOG_WARNING(log, "{} Settings 'min_rows_for_wide_part', 'min_bytes_for_wide_part', " "'min_rows_for_compact_part' and 'min_bytes_for_compact_part' will be ignored.", reason); -#if !USE_ROCKSDB if (use_metadata_cache) - { - throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't use merge tree metadata cache if clickhouse was compiled without rocksdb." "set use_metadata_cache to false forcely"); - } +#if USE_ROCKSDB + if (!getContext()->getMergeTreeMetadataCache()) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't use merge tree metadata cache if not config in config.xml"); +#else + throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't use merge tree metadata cache if clickhouse was compiled without rocksdb"); #endif common_assignee_trigger = [this] (bool delay) noexcept From 37c7c282d76ef77f204528bb0779ba17685e6fca Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 4 Jan 2022 13:41:11 +0800 Subject: [PATCH 0158/1647] fix unit test --- src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp | 3 ++- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 4 ++-- src/Storages/MergeTree/IMergeTreeDataPart.h | 2 +- src/Storages/MergeTree/MergeTreeMetadataCache.cpp | 4 ++++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp b/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp index 0679cbf529a..8371d7ff6b9 100644 --- a/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp +++ b/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp @@ -13,7 +13,7 @@ class MergeTreeMetadataCacheTest : public ::testing::Test public: void SetUp() override { - auto shared_context = Context::createShared(); + shared_context = Context::createShared(); global_context = Context::createGlobal(shared_context.get()); global_context->makeGlobalContext(); global_context->initializeMergeTreeMetadataCache("./db/", 256 << 20); @@ -25,6 +25,7 @@ public: global_context->shutdown(); } + SharedContextHolder shared_context; ContextMutablePtr global_context; MergeTreeMetadataCachePtr cache; }; diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index c0672f9e98f..2e71c22e144 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -700,7 +700,7 @@ void IMergeTreeDataPart::appendFilesOfColumnsChecksumsIndexes(Strings & files, b appendFilesOfColumns(files); appendFilesOfChecksums(files); appendFilesOfIndexGranularity(files); - appendFilesofIndex(files); + appendFilesOfIndex(files); appendFilesOfRowsCount(files); appendFilesOfPartitionAndMinMaxIndex(files); appendFilesOfTTLInfos(files); @@ -817,7 +817,7 @@ void IMergeTreeDataPart::loadIndex() } } -void IMergeTreeDataPart::appendFilesofIndex(Strings & files) const +void IMergeTreeDataPart::appendFilesOfIndex(Strings & files) const { auto metadata_snapshot = storage.getInMemoryMetadataPtr(); if (parent_part) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 150c258a4b6..0463d44abee 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -518,7 +518,7 @@ private: /// Loads index file. void loadIndex(); - void appendFilesofIndex(Strings & files) const; + void appendFilesOfIndex(Strings & files) const; /// Load rows count for this part from disk (for the newer storage format version). /// For the older format version calculates rows count from the size of a column with a fixed size. diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp index 7025a79018b..fa3825fd3be 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp @@ -17,6 +17,8 @@ namespace DB MergeTreeMetadataCache::Status MergeTreeMetadataCache::put(const String & key, const String & value) { auto options = rocksdb::WriteOptions(); + options.sync = true; + options.disableWAL = false; auto status = rocksdb->Put(options, key, value); ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCachePut); return status; @@ -25,6 +27,8 @@ MergeTreeMetadataCache::Status MergeTreeMetadataCache::put(const String & key, c MergeTreeMetadataCache::Status MergeTreeMetadataCache::del(const String & key) { auto options = rocksdb::WriteOptions(); + options.sync = true; + options.disableWAL = false; auto status = rocksdb->Delete(options, key); ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheDelete); LOG_TRACE(log, "Delete key:{} from MergeTreeMetadataCache status:{}", key, status.ToString()); From abefefc719712141d394b301c02b52774370655b Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 4 Jan 2022 18:17:23 +0800 Subject: [PATCH 0159/1647] support multi disk --- .../tests/gtest_merge_tree_metadata_cache.cpp | 31 ++++++++++--------- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 4 +-- src/Storages/MergeTree/PartMetadataCache.cpp | 31 ++++++++++++------- src/Storages/MergeTree/PartMetadataCache.h | 16 +++++++--- 4 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp b/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp index 8371d7ff6b9..1005739098c 100644 --- a/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp +++ b/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp @@ -20,10 +20,7 @@ public: cache = global_context->getMergeTreeMetadataCache(); } - void TearDown() override - { - global_context->shutdown(); - } + void TearDown() override { global_context->shutdown(); } SharedContextHolder shared_context; ContextMutablePtr global_context; @@ -50,14 +47,16 @@ TEST_F(MergeTreeMetadataCacheTest, testCommon) ASSERT_EQ(value, prefix + file); } - Strings keys; - Strings values; - cache->getByPrefix(prefix, keys, values); - ASSERT_EQ(keys.size(), files.size()); - ASSERT_EQ(values.size(), files.size()); - for (size_t i=0; i < files.size(); ++i) { - ASSERT_EQ(values[i], prefix + keys[i]); + Strings keys; + Strings values; + cache->getByPrefix(prefix, keys, values); + ASSERT_EQ(keys.size(), files.size()); + ASSERT_EQ(values.size(), files.size()); + for (size_t i = 0; i < files.size(); ++i) + { + ASSERT_EQ(values[i], keys[i]); + } } for (const auto & file : files) @@ -73,9 +72,13 @@ TEST_F(MergeTreeMetadataCacheTest, testCommon) ASSERT_EQ(status.code(), rocksdb::Status::Code::kNotFound); } - cache->getByPrefix(prefix, keys, values); - ASSERT_EQ(keys.size(), 0); - ASSERT_EQ(values.size(), 0); + { + Strings keys; + Strings values; + cache->getByPrefix(prefix, keys, values); + ASSERT_EQ(keys.size(), 0); + ASSERT_EQ(values.size(), 0); + } } #endif diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 2e71c22e144..c49232397c7 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -343,7 +343,7 @@ IMergeTreeDataPart::IMergeTreeDataPart( #if USE_ROCKSDB if (use_metadata_cache) metadata_cache = std::make_shared( - storage.getContext()->getMergeTreeMetadataCache(), storage.relative_data_path, relative_path, parent_part); + storage.getContext()->getMergeTreeMetadataCache(), volume->getDisk()->getPath(), storage.relative_data_path, relative_path, parent_part); #endif } @@ -375,7 +375,7 @@ IMergeTreeDataPart::IMergeTreeDataPart( #if USE_ROCKSDB if (use_metadata_cache) metadata_cache = std::make_shared( - storage.getContext()->getMergeTreeMetadataCache(), storage.relative_data_path, relative_path, parent_part); + storage.getContext()->getMergeTreeMetadataCache(), volume->getDisk()->getName(), storage.relative_data_path, relative_path, parent_part); #endif } diff --git a/src/Storages/MergeTree/PartMetadataCache.cpp b/src/Storages/MergeTree/PartMetadataCache.cpp index eee04d24405..eff6d9cc0a9 100644 --- a/src/Storages/MergeTree/PartMetadataCache.cpp +++ b/src/Storages/MergeTree/PartMetadataCache.cpp @@ -28,7 +28,8 @@ std::unique_ptr PartMetadataCache::readOrSet(const DiskPtr & disk, const String & file_name, String & value) { String file_path = fs::path(getFullRelativePath()) / file_name; - auto status = cache->get(file_path, value); + String key = getKey(file_path); + auto status = cache->get(key, value); if (!status.ok()) { ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheMiss); @@ -41,7 +42,7 @@ PartMetadataCache::readOrSet(const DiskPtr & disk, const String & file_name, Str if (in) { readStringUntilEOF(value, *in); - cache->put(file_path, value); + cache->put(key, value); } } else @@ -57,7 +58,8 @@ void PartMetadataCache::batchSet(const DiskPtr & disk, const Strings & file_name String read_value; for (const auto & file_name : file_names) { - const String file_path = fs::path(getFullRelativePath()) / file_name; + String file_path = fs::path(getFullRelativePath()) / file_name; + String key = getKey(file_path); if (!disk->exists(file_path)) continue; @@ -66,10 +68,10 @@ void PartMetadataCache::batchSet(const DiskPtr & disk, const Strings & file_name continue; readStringUntilEOF(text, *in); - auto status = cache->put(file_path, text); + auto status = cache->put(key, text); if (!status.ok()) { - status = cache->get(file_path, read_value); + status = cache->get(key, read_value); if (status.IsNotFound() || read_value == text) continue; throw Exception(ErrorCodes::LOGICAL_ERROR, "set meta failed status:{}, file_path:{}", status.ToString(), file_path); @@ -82,11 +84,12 @@ void PartMetadataCache::batchDelete(const Strings & file_names) for (const auto & file_name : file_names) { String file_path = fs::path(getFullRelativePath()) / file_name; - auto status = cache->del(file_path); + String key = getKey(file_path); + auto status = cache->del(key); if (!status.ok()) { String read_value; - status = cache->get(file_path, read_value); + status = cache->get(key, read_value); if (status.IsNotFound()) continue; throw Exception(ErrorCodes::LOGICAL_ERROR, "drop meta failed status:{}, file_path:{}", status.ToString(), file_path); @@ -97,15 +100,16 @@ void PartMetadataCache::batchDelete(const Strings & file_names) void PartMetadataCache::set(const String & file_name, const String & value) { String file_path = fs::path(getFullRelativePath()) / file_name; + String key = getKey(file_path); String read_value; - auto status = cache->get(file_path, read_value); + auto status = cache->get(key, read_value); if (status == rocksdb::Status::OK() && value == read_value) return; - status = cache->put(file_path, value); + status = cache->put(key, value); if (!status.ok()) { - status = cache->get(file_path, read_value); + status = cache->get(key, read_value); if (status.IsNotFound() || read_value == value) return; @@ -115,7 +119,7 @@ void PartMetadataCache::set(const String & file_name, const String & value) void PartMetadataCache::getFilesAndCheckSums(Strings & files, std::vector & checksums) const { - String prefix = fs::path(getFullRelativePath()) / ""; + String prefix = getKey(fs::path(getFullRelativePath()) / ""); Strings values; cache->getByPrefix(prefix, files, values); size_t size = files.size(); @@ -132,5 +136,10 @@ String PartMetadataCache::getFullRelativePath() const return fs::path(relative_data_path) / (parent_part ? parent_part->relative_path : "") / relative_path / ""; } +String PartMetadataCache::getKey(const String & file_path) const +{ + return disk_name + ":" + file_path; +} + } #endif diff --git a/src/Storages/MergeTree/PartMetadataCache.h b/src/Storages/MergeTree/PartMetadataCache.h index 427b9524afd..c2904738b9c 100644 --- a/src/Storages/MergeTree/PartMetadataCache.h +++ b/src/Storages/MergeTree/PartMetadataCache.h @@ -24,8 +24,14 @@ class PartMetadataCache public: using uint128 = CityHash_v1_0_2::uint128; - PartMetadataCache(const MergeTreeMetadataCachePtr & cache_, const String & relative_data_path_, const String & relative_path_, const IMergeTreeDataPart * parent_part_) + PartMetadataCache( + const MergeTreeMetadataCachePtr & cache_, + const String & disk_name_, + const String & relative_data_path_, + const String & relative_path_, + const IMergeTreeDataPart * parent_part_) : cache(cache_) + , disk_name(disk_name_) , relative_data_path(relative_data_path_) , relative_path(relative_path_) , parent_part(parent_part_) @@ -40,11 +46,13 @@ public: void getFilesAndCheckSums(Strings & files, std::vector & checksums) const; private: - std::string getFullRelativePath() const; + String getFullRelativePath() const; + String getKey(const String & file_path) const; MergeTreeMetadataCachePtr cache; - const String & relative_data_path; // relative path of table to disk - const String & relative_path; // relative path of part to table + const String & disk_name; + const String & relative_data_path; /// Relative path of table to disk + const String & relative_path; /// Relative path of part to table const IMergeTreeDataPart * parent_part; }; From c0a9c2b9161a994a0fc26e927c8d09ac8c0d596c Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Wed, 5 Jan 2022 19:51:50 +0800 Subject: [PATCH 0160/1647] refactor metadatacache to reduce using of USE_ROCKSDB --- src/Functions/checkPartMetadataCache.cpp | 2 +- src/IO/CMakeLists.txt | 4 +- src/IO/ReadBufferFromString.h | 7 + src/IO/examples/read_buffer.cpp | 31 +- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 349 +++--------------- src/Storages/MergeTree/IMergeTreeDataPart.h | 46 +-- .../MergeTree/IPartMetadataManager.cpp | 11 + src/Storages/MergeTree/IPartMetadataManager.h | 42 +++ src/Storages/MergeTree/MergeTreeData.h | 1 + src/Storages/MergeTree/MergeTreePartition.cpp | 15 +- src/Storages/MergeTree/MergeTreePartition.h | 11 +- src/Storages/MergeTree/PartMetadataCache.cpp | 145 -------- src/Storages/MergeTree/PartMetadataCache.h | 62 ---- .../MergeTree/PartMetadataManagerOrdinary.cpp | 33 ++ .../MergeTree/PartMetadataManagerOrdinary.h | 28 ++ .../PartMetadataManagerWithCache.cpp | 194 ++++++++++ .../MergeTree/PartMetadataManagerWithCache.h | 40 ++ 17 files changed, 451 insertions(+), 570 deletions(-) create mode 100644 src/Storages/MergeTree/IPartMetadataManager.cpp create mode 100644 src/Storages/MergeTree/IPartMetadataManager.h delete mode 100644 src/Storages/MergeTree/PartMetadataCache.cpp delete mode 100644 src/Storages/MergeTree/PartMetadataCache.h create mode 100644 src/Storages/MergeTree/PartMetadataManagerOrdinary.cpp create mode 100644 src/Storages/MergeTree/PartMetadataManagerOrdinary.h create mode 100644 src/Storages/MergeTree/PartMetadataManagerWithCache.cpp create mode 100644 src/Storages/MergeTree/PartMetadataManagerWithCache.h diff --git a/src/Functions/checkPartMetadataCache.cpp b/src/Functions/checkPartMetadataCache.cpp index ddcb44f5372..d65c14095f7 100644 --- a/src/Functions/checkPartMetadataCache.cpp +++ b/src/Functions/checkPartMetadataCache.cpp @@ -134,7 +134,7 @@ public: cache_checksums.reserve(file_number); disk_checksums.reserve(file_number); - part->checkMetadataCache(keys, cache_checksums, disk_checksums); + // part->checkMetadataCache(keys, cache_checksums, disk_checksums); for (size_t i = 0; i < keys.size(); ++i) { col_key.insert(keys[i]); diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index f676f415eea..970602896c9 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -1,3 +1,3 @@ -if (ENABLE_EXAMPLES) +#if (ENABLE_EXAMPLES) add_subdirectory (examples) -endif () +#endif () diff --git a/src/IO/ReadBufferFromString.h b/src/IO/ReadBufferFromString.h index 09646e9b41f..7ea6afc3543 100644 --- a/src/IO/ReadBufferFromString.h +++ b/src/IO/ReadBufferFromString.h @@ -15,4 +15,11 @@ public: explicit ReadBufferFromString(std::string_view s) : ReadBufferFromMemory(s.data(), s.size()) {} }; + +class ReadBufferFromOwnString : public String, public ReadBufferFromString +{ +public: + explicit ReadBufferFromOwnString(const String & s_): String(s_), ReadBufferFromString(*this) {} +}; + } diff --git a/src/IO/examples/read_buffer.cpp b/src/IO/examples/read_buffer.cpp index ea3da690ca5..85675c0d613 100644 --- a/src/IO/examples/read_buffer.cpp +++ b/src/IO/examples/read_buffer.cpp @@ -2,18 +2,15 @@ #include -#include -#include +#include #include +#include +#include - -int main(int, char **) +int readAndPrint(DB::ReadBuffer & in) { try { - std::string s = "-123456 123.456 вася пе\\tтя\t'\\'xyz\\\\'"; - DB::ReadBufferFromString in(s); - DB::Int64 a; DB::Float64 b; DB::String c, d; @@ -31,12 +28,32 @@ int main(int, char **) std::cout << a << ' ' << b << ' ' << c << '\t' << '\'' << d << '\'' << std::endl; std::cout << in.count() << std::endl; + return 0; } catch (const DB::Exception & e) { std::cerr << e.what() << ", " << e.displayText() << std::endl; return 1; } +} + +int main(int, char **) +{ + { + std::string s = "-123456 123.456 вася пе\\tтя\t'\\'xyz\\\\'"; + DB::ReadBufferFromString in(s); + if (readAndPrint(in)) + std::cout << "readAndPrint from ReadBufferFromString failed" << std::endl; + } + + + std::shared_ptr in; + { + std::string s = "-123456 123.456 вася пе\\tтя\t'\\'xyz\\\\'"; + in = std::make_shared(s); + } + if (readAndPrint(*in)) + std::cout << "readAndPrint from ReadBufferFromOwnString failed" << std::endl; return 0; } diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index c49232397c7..4c88580e069 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -59,19 +61,8 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; } -static std::unique_ptr openForReading(const DiskPtr & disk, const String & path) -{ - size_t file_size = disk->getFileSize(path); - return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); -} - -#if USE_ROCKSDB void IMergeTreeDataPart::MinMaxIndex::load( - const MergeTreeData & data, const PartMetadataCachePtr & cache, const DiskPtr & disk, const String & part_path) -#else -void IMergeTreeDataPart::MinMaxIndex::load( - const MergeTreeData & data, const DiskPtr & disk, const String & part_path) -#endif + const MergeTreeData & data, const PartMetadataManagerPtr & manager) { auto metadata_snapshot = data.getInMemoryMetadataPtr(); const auto & partition_key = metadata_snapshot->getPartitionKey(); @@ -80,31 +71,11 @@ void IMergeTreeDataPart::MinMaxIndex::load( auto minmax_column_types = data.getMinMaxColumnsTypes(partition_key); size_t minmax_idx_size = minmax_column_types.size(); - auto read_min_max_index = [&](size_t i) - { - String file_name = fs::path(part_path) / ("minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"); - auto file = openForReading(disk, file_name); - return file; - }; - hyperrectangle.reserve(minmax_idx_size); for (size_t i = 0; i < minmax_idx_size; ++i) { - std::unique_ptr file; -#if USE_ROCKSDB - String _; - if (cache) - { - String file_name = "minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"; - file = cache->readOrSet(disk, file_name, _); - } - else - { - file = read_min_max_index(i); - } -#else - file = read_min_max_index(i); -#endif + String file_name = "minmax_" + escapeForFileName(minmax_column_names[i]) + ".idx"; + auto file = manager->read(file_name); auto serialization = minmax_column_types[i]->getDefaultSerialization(); Field min_val; @@ -340,11 +311,7 @@ IMergeTreeDataPart::IMergeTreeDataPart( minmax_idx = std::make_shared(); -#if USE_ROCKSDB - if (use_metadata_cache) - metadata_cache = std::make_shared( - storage.getContext()->getMergeTreeMetadataCache(), volume->getDisk()->getPath(), storage.relative_data_path, relative_path, parent_part); -#endif + initializePartMetadataManager(); } IMergeTreeDataPart::IMergeTreeDataPart( @@ -371,12 +338,8 @@ IMergeTreeDataPart::IMergeTreeDataPart( incrementTypeMetric(part_type); minmax_idx = std::make_shared(); - -#if USE_ROCKSDB - if (use_metadata_cache) - metadata_cache = std::make_shared( - storage.getContext()->getMergeTreeMetadataCache(), volume->getDisk()->getName(), storage.relative_data_path, relative_path, parent_part); -#endif + + initializePartMetadataManager(); } IMergeTreeDataPart::~IMergeTreeDataPart() @@ -774,23 +737,9 @@ void IMergeTreeDataPart::loadIndex() loaded_index[i]->reserve(index_granularity.getMarksCount()); } - String index_path = fs::path(getFullRelativePath()) / "primary.idx"; - - std::unique_ptr index_file; -#if USE_ROCKSDB - String _; - if (use_metadata_cache) - { - index_file = metadata_cache->readOrSet(volume->getDisk(), "primary.idx", _); - } - else - { - index_file = openForReading(volume->getDisk(), index_path); - } -#else - index_file = openForReading(volume->getDisk(), index_path); -#endif - + String index_name = "primary.idx"; + String index_path = fs::path(getFullRelativePath()) / index_name; + auto index_file = metadata_manager->read(index_name); size_t marks_count = index_granularity.getMarksCount(); Serializations key_serializations(key_size); @@ -854,34 +803,14 @@ void IMergeTreeDataPart::loadDefaultCompressionCodec() } String path = fs::path(getFullRelativePath()) / DEFAULT_COMPRESSION_CODEC_FILE_NAME; - - bool exists = false; - std::unique_ptr file_buf; -#if USE_ROCKSDB - String _; - if (use_metadata_cache) - { - file_buf = metadata_cache->readOrSet(volume->getDisk(), DEFAULT_COMPRESSION_CODEC_FILE_NAME, _); - exists = file_buf != nullptr; - } - else - { - exists = volume->getDisk()->exists(path); - if (exists) - file_buf = openForReading(volume->getDisk(), path); - } -#else - exists = volume->getDisk()->exists(path); - if (exists) - file_buf = openForReading(volume->getDisk(), path); -#endif - + bool exists = metadata_manager->exists(DEFAULT_COMPRESSION_CODEC_FILE_NAME); if (!exists) { default_codec = detectDefaultCompressionCodec(); } else { + auto file_buf = metadata_manager->read(DEFAULT_COMPRESSION_CODEC_FILE_NAME); String codec_line; readEscapedStringUntilEOL(codec_line, *file_buf); @@ -980,11 +909,7 @@ void IMergeTreeDataPart::loadPartitionAndMinMaxIndex() { String path = getFullRelativePath(); if (!parent_part) -#if USE_ROCKSDB - partition.load(storage, metadata_cache, volume->getDisk(), path); -#else - partition.load(storage, volume->getDisk(), path); -#endif + partition.load(storage, metadata_manager); if (!isEmpty()) { @@ -992,11 +917,7 @@ void IMergeTreeDataPart::loadPartitionAndMinMaxIndex() // projection parts don't have minmax_idx, and it's always initialized minmax_idx->initialized = true; else -#if USE_ROCKSDB - minmax_idx->load(storage, metadata_cache, volume->getDisk(), path); -#else - minmax_idx->load(storage, volume->getDisk(), path); -#endif + minmax_idx->load(storage, metadata_manager); } if (parent_part) return; @@ -1027,30 +948,10 @@ void IMergeTreeDataPart::appendFilesOfPartitionAndMinMaxIndex(Strings & files) c void IMergeTreeDataPart::loadChecksums(bool require) { const String path = fs::path(getFullRelativePath()) / "checksums.txt"; - bool exists = false; - std::unique_ptr buf; - -#if USE_ROCKSDB - String _; - if (use_metadata_cache) - { - buf = metadata_cache->readOrSet(volume->getDisk(), "checksums.txt", _); - exists = buf != nullptr; - } - else - { - exists = volume->getDisk()->exists(path); - if (exists) - buf = openForReading(volume->getDisk(), path); - } -#else - exists = volume->getDisk()->exists(path); - if (exists) - buf = openForReading(volume->getDisk(), path); -#endif - + bool exists = metadata_manager->exists("checksums.txt"); if (exists) { + auto buf = metadata_manager->read("checksums.txt"); if (checksums.read(*buf)) { assertEOF(*buf); @@ -1092,7 +993,8 @@ void IMergeTreeDataPart::loadRowsCount() auto read_rows_count = [&]() { - auto buf = openForReading(volume->getDisk(), path); + // auto buf = openForReading(volume->getDisk(), path); + auto buf = metadata_manager->read("count.txt"); readIntText(rows_count, *buf); assertEOF(*buf); }; @@ -1103,30 +1005,11 @@ void IMergeTreeDataPart::loadRowsCount() } else if (storage.format_version >= MERGE_TREE_DATA_MIN_FORMAT_VERSION_WITH_CUSTOM_PARTITIONING || part_type == Type::COMPACT || parent_part) { -#if USE_ROCKSDB - String _; - if (use_metadata_cache) - { - auto buf = metadata_cache->readOrSet(volume->getDisk(), "count.txt", _); - if (!buf) - throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); - - readIntText(rows_count, *buf); - assertEOF(*buf); - } - else - { - if (!volume->getDisk()->exists(path)) - throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); - - read_rows_count(); - } -#else - if (!volume->getDisk()->exists(path)) + bool exists = metadata_manager->exists("count.txt"); + if (!exists) throw Exception("No count.txt in part " + name, ErrorCodes::NO_FILE_IN_DATA_PART); read_rows_count(); -#endif #ifndef NDEBUG /// columns have to be loaded @@ -1228,31 +1111,10 @@ void IMergeTreeDataPart::appendFilesOfRowsCount(Strings & files) void IMergeTreeDataPart::loadTTLInfos() { - String path = fs::path(getFullRelativePath()) / "ttl.txt"; - bool exists = false; - std::unique_ptr in; - -#if USE_ROCKSDB - String _; - if (use_metadata_cache) - { - in = metadata_cache->readOrSet(volume->getDisk(), "ttl.txt", _); - exists = in != nullptr; - } - else - { - exists = volume->getDisk()->exists(path); - if (exists) - in = openForReading(volume->getDisk(), path); - } -#else - exists = volume->getDisk()->exists(path); - if (exists) - in = openForReading(volume->getDisk(), path); -#endif - + bool exists = metadata_manager->exists("ttl.txt"); if (exists) { + auto in = metadata_manager->read("ttl.txt"); assertString("ttl format version: ", *in); size_t format_version; readText(format_version, *in); @@ -1282,31 +1144,10 @@ void IMergeTreeDataPart::appendFilesOfTTLInfos(Strings & files) void IMergeTreeDataPart::loadUUID() { - String path = fs::path(getFullRelativePath()) / UUID_FILE_NAME; - bool exists = false; - std::unique_ptr in; - -#if USE_ROCKSDB - String _; - if (use_metadata_cache) - { - in = metadata_cache->readOrSet(volume->getDisk(), UUID_FILE_NAME, _); - exists = in != nullptr; - } - else - { - exists = volume->getDisk()->exists(path); - if (exists) - in = openForReading(volume->getDisk(), path); - } -#else - exists = volume->getDisk()->exists(path); - if (exists) - in = openForReading(volume->getDisk(), path); -#endif - + bool exists = metadata_manager->exists(UUID_FILE_NAME); if (exists) { + auto in = metadata_manager->read(UUID_FILE_NAME); readText(uuid, *in); if (uuid == UUIDHelpers::Nil) throw Exception("Unexpected empty " + String(UUID_FILE_NAME) + " in part: " + name, ErrorCodes::LOGICAL_ERROR); @@ -1326,27 +1167,7 @@ void IMergeTreeDataPart::loadColumns(bool require) metadata_snapshot = metadata_snapshot->projections.get(name).metadata; NamesAndTypesList loaded_columns; - bool exists = false; - std::unique_ptr in; -#if USE_ROCKSDB - String _; - if (use_metadata_cache) - { - in = metadata_cache->readOrSet(volume->getDisk(), "columns.txt", _); - exists = in != nullptr; - } - else - { - exists = volume->getDisk()->exists(path); - if (exists) - in = openForReading(volume->getDisk(), path); - } -#else - exists = volume->getDisk()->exists(path); - if (exists) - in = openForReading(volume->getDisk(), path); -#endif - + bool exists = metadata_manager->exists("columns.txt"); if (!exists) { /// We can get list of columns only from columns.txt in compact parts. @@ -1370,6 +1191,7 @@ void IMergeTreeDataPart::loadColumns(bool require) } else { + auto in = metadata_manager->read("columns.txt"); loaded_columns.readText(*in); for (const auto & column : loaded_columns) @@ -1449,24 +1271,12 @@ void IMergeTreeDataPart::renameTo(const String & new_relative_path, bool remove_ } } -#if USE_ROCKSDB - if (use_metadata_cache) - { - modifyAllMetadataCaches(ModifyCacheType::DROP, true); - assertMetadataCacheDropped(true); - } -#endif - + metadata_manager->deleteAll(true); + metadata_manager->assertAllDeleted(true); volume->getDisk()->setLastModified(from, Poco::Timestamp::fromEpochTime(time(nullptr))); volume->getDisk()->moveDirectory(from, to); relative_path = new_relative_path; - -#if USE_ROCKSDB - if (use_metadata_cache) - { - modifyAllMetadataCaches(ModifyCacheType::PUT, true); - } -#endif + metadata_manager->updateAll(true); SyncGuardPtr sync_guard; if (storage.getSettings()->fsync_part_directory) @@ -1475,73 +1285,6 @@ void IMergeTreeDataPart::renameTo(const String & new_relative_path, bool remove_ storage.lockSharedData(*this); } -#if USE_ROCKSDB -void IMergeTreeDataPart::modifyAllMetadataCaches(ModifyCacheType type, bool include_projection) const -{ - assert(use_metadata_cache); - - Strings files; - appendFilesOfColumnsChecksumsIndexes(files, include_projection); - LOG_TRACE( - storage.log, - "part name:{} path:{} {} keys:{}", - name, - getFullRelativePath(), - modifyCacheTypeToString(type), - boost::algorithm::join(files, ", ")); - - switch (type) - { - case ModifyCacheType::PUT: - metadata_cache->batchSet(volume->getDisk(), files); - break; - case ModifyCacheType::DROP: - metadata_cache->batchDelete(files); - break; - } -} - -void IMergeTreeDataPart::assertMetadataCacheDropped(bool include_projection) const -{ - assert(use_metadata_cache); - - Strings files; - std::vector _; - metadata_cache->getFilesAndCheckSums(files, _); - if (files.empty()) - return; - - for (const auto & file : files) - { - String file_name = fs::path(file).filename(); - /// file belongs to current part - if (fs::path(getFullRelativePath()) / file_name == file) - { - throw Exception( - ErrorCodes::LOGICAL_ERROR, "Data part {} with type {} with meta file {} still in cache", name, getType().toString(), file); - } - - /// file belongs to projection part of current part - if (!parent_part && include_projection) - { - for (const auto & [projection_name, projection_part] : projection_parts) - { - if (fs::path(projection_part->getFullRelativePath()) / file_name == file) - { - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Data part {} with type {} with meta file {} with projection name still in cache", - name, - getType().toString(), - file, - projection_name); - } - } - } - } -} -#endif - std::optional IMergeTreeDataPart::keepSharedDataInDecoupledStorage() const { /// NOTE: It's needed for zero-copy replication @@ -1560,6 +1303,18 @@ std::optional IMergeTreeDataPart::keepSharedDataInDecoupledStorage() const return {}; } +void IMergeTreeDataPart::initializePartMetadataManager() +{ +#if USE_ROCKSDB + if (use_metadata_cache) + metadata_manager = std::make_shared(this, storage.getContext()->getMergeTreeMetadataCache()); + else + metadata_manager = std::make_shared(this); +#else + metadata_manager = std::make_shared(this); +#endif +} + void IMergeTreeDataPart::remove() const { std::optional keep_shared_data = keepSharedDataInDecoupledStorage(); @@ -1579,13 +1334,8 @@ void IMergeTreeDataPart::remove() const return; } -#if USE_ROCKSDB - if (use_metadata_cache) - { - modifyAllMetadataCaches(ModifyCacheType::DROP); - assertMetadataCacheDropped(); - } -#endif + metadata_manager->deleteAll(false); + metadata_manager->assertAllDeleted(false); /** Atomic directory removal: * - rename directory to temporary name; @@ -1690,13 +1440,8 @@ void IMergeTreeDataPart::remove() const void IMergeTreeDataPart::projectionRemove(const String & parent_to, bool keep_shared_data) const { -#if USE_ROCKSDB - if (use_metadata_cache) - { - modifyAllMetadataCaches(ModifyCacheType::DROP); - assertMetadataCacheDropped(); - } -#endif + metadata_manager->deleteAll(false); + metadata_manager->assertAllDeleted(false); String to = parent_to + "/" + relative_path; auto disk = volume->getDisk(); @@ -2037,6 +1782,7 @@ String IMergeTreeDataPart::getZeroLevelPartBlockID() const return info.partition_id + "_" + toString(hash_value.words[0]) + "_" + toString(hash_value.words[1]); } +/* #if USE_ROCKSDB IMergeTreeDataPart::uint128 IMergeTreeDataPart::getActualChecksumByFile(const String & file_path) const { @@ -2107,6 +1853,7 @@ void IMergeTreeDataPart::checkMetadataCache(Strings & files, std::vector #include #include -#include +#include #include @@ -46,22 +46,6 @@ class UncompressedCache; class IMergeTreeDataPart : public std::enable_shared_from_this { public: - -#if USE_ROCKSDB - enum ModifyCacheType : uint8_t - { - PUT = 1, /// Override set - DROP = 2, /// Remove keys - }; - - static constexpr std::string_view modifyCacheTypeToString(ModifyCacheType type) - { - return magic_enum::enum_name(type); - } - - using uint128 = PartMetadataCache::uint128; -#endif - static constexpr auto DATA_FILE_EXTENSION = ".bin"; using Checksums = MergeTreeDataPartChecksums; @@ -78,6 +62,8 @@ public: using Type = MergeTreeDataPartType; + using uint128 = IPartMetadataManager::uint128; + IMergeTreeDataPart( const MergeTreeData & storage_, @@ -156,10 +142,6 @@ public: /// Throws an exception if part is not stored in on-disk format. void assertOnDisk() const; -#if USE_ROCKSDB - void assertMetadataCacheDropped(bool include_projection = false) const; -#endif - void remove() const; void projectionRemove(const String & parent_to, bool keep_shared_data = false) const; @@ -320,11 +302,7 @@ public: { } -#if USE_ROCKSDB - void load(const MergeTreeData & data, const PartMetadataCachePtr & metadata_cache, const DiskPtr & disk, const String & part_path); -#else - void load(const MergeTreeData & data, const DiskPtr & disk, const String & part_path); -#endif + void load(const MergeTreeData & data, const PartMetadataManagerPtr & manager); void store(const MergeTreeData & data, const DiskPtr & disk, const String & part_path, Checksums & checksums) const; void store(const Names & column_names, const DataTypes & data_types, const DiskPtr & disk_, const String & part_path, Checksums & checksums) const; @@ -393,9 +371,7 @@ public: String getRelativePathForPrefix(const String & prefix, bool detached = false) const; -#if USE_ROCKSDB - virtual void checkMetadataCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const; -#endif + // virtual void checkMetadataCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const; bool isProjectionPart() const { return parent_part != nullptr; } @@ -471,9 +447,7 @@ protected: /// Disabled when USE_ROCKSDB is OFF, or use_metadata_cache is set true in merge tree settings bool use_metadata_cache = false; -#if USE_ROCKSDB - mutable PartMetadataCachePtr metadata_cache; -#endif + mutable PartMetadataManagerPtr metadata_manager; void removeIfNeeded(); @@ -488,6 +462,8 @@ protected: std::optional keepSharedDataInDecoupledStorage() const; + void initializePartMetadataManager(); + private: /// In compact parts order of columns is necessary NameToNumber column_name_to_position; @@ -550,10 +526,8 @@ private: /// for this column with default parameters. CompressionCodecPtr detectDefaultCompressionCodec() const; -#if USE_ROCKSDB - void modifyAllMetadataCaches(ModifyCacheType type, bool include_projection = false) const; - IMergeTreeDataPart::uint128 getActualChecksumByFile(const String & file_path) const; -#endif + // void modifyAllMetadataCaches(ModifyCacheType type, bool include_projection = false) const; + // IMergeTreeDataPart::uint128 getActualChecksumByFile(const String & file_path) const; mutable State state{State::Temporary}; }; diff --git a/src/Storages/MergeTree/IPartMetadataManager.cpp b/src/Storages/MergeTree/IPartMetadataManager.cpp new file mode 100644 index 00000000000..5e24ac2c0e1 --- /dev/null +++ b/src/Storages/MergeTree/IPartMetadataManager.cpp @@ -0,0 +1,11 @@ +#include "IPartMetadataManager.h" + +#include +#include + +namespace DB +{ +IPartMetadataManager::IPartMetadataManager(const IMergeTreeDataPart * part_) : part(part_), disk(part->volume->getDisk()) +{ +} +} diff --git a/src/Storages/MergeTree/IPartMetadataManager.h b/src/Storages/MergeTree/IPartMetadataManager.h new file mode 100644 index 00000000000..6db87e20f16 --- /dev/null +++ b/src/Storages/MergeTree/IPartMetadataManager.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +namespace DB +{ + +class IMergeTreeDataPart; + +class SeekableReadBuffer; + +class IDisk; +using DiskPtr = std::shared_ptr; + + +class IPartMetadataManager +{ +public: + using uint128 = CityHash_v1_0_2::uint128; + + explicit IPartMetadataManager(const IMergeTreeDataPart * part_); + + virtual ~IPartMetadataManager() = default; + + virtual std::unique_ptr read(const String & file_name) const = 0; + + virtual bool exists(const String & file_name) const = 0; + + virtual void deleteAll(bool include_projection) = 0; + + virtual void assertAllDeleted(bool include_projection) const = 0; + + virtual void updateAll(bool include_projection) = 0; + +protected: + const IMergeTreeDataPart * part; + const DiskPtr disk; +}; + +using PartMetadataManagerPtr = std::shared_ptr; +} diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 4a108390a85..47dcecaa860 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -895,6 +895,7 @@ protected: friend class StorageReplicatedMergeTree; friend class MergeTreeDataWriter; friend class MergeTask; + friend class IPartMetadataManager; bool require_part_metadata; diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index 8a85e374062..3d933303142 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -160,11 +160,13 @@ namespace }; } +/* static std::unique_ptr openForReading(const DiskPtr & disk, const String & path) { size_t file_size = disk->getFileSize(path); return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); } +*/ String MergeTreePartition::getID(const MergeTreeData & storage) const { @@ -355,21 +357,18 @@ void MergeTreePartition::serializeText(const MergeTreeData & storage, WriteBuffe } } -#if USE_ROCKSDB -void MergeTreePartition::load(const MergeTreeData & storage, const PartMetadataCachePtr & metadata_cache, const DiskPtr & disk, const String & part_path) -#else -void MergeTreePartition::load(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path) -#endif +void MergeTreePartition::load(const MergeTreeData & storage, const PartMetadataManagerPtr & manager) { auto metadata_snapshot = storage.getInMemoryMetadataPtr(); if (!metadata_snapshot->hasPartitionKey()) return; const auto & partition_key_sample = adjustPartitionKey(metadata_snapshot, storage.getContext()).sample_block; - auto partition_file_path = part_path + "partition.dat"; - std::unique_ptr file; +/* #if USE_ROCKSDB + std::unique_ptr file; + auto partition_file_path = part_path + "partition.dat"; String _; if (metadata_cache) { @@ -382,6 +381,8 @@ void MergeTreePartition::load(const MergeTreeData & storage, const DiskPtr & dis #else file = openForReading(disk, partition_file_path); #endif +*/ + auto file = manager->read("partition.dat"); value.resize(partition_key_sample.columns()); for (size_t i = 0; i < partition_key_sample.columns(); ++i) diff --git a/src/Storages/MergeTree/MergeTreePartition.h b/src/Storages/MergeTree/MergeTreePartition.h index b1bf64550c6..d408b7df6a1 100644 --- a/src/Storages/MergeTree/MergeTreePartition.h +++ b/src/Storages/MergeTree/MergeTreePartition.h @@ -4,12 +4,9 @@ #include #include #include +#include #include -#if USE_ROCKSDB -#include -#endif - namespace DB { @@ -41,11 +38,7 @@ public: void serializeText(const MergeTreeData & storage, WriteBuffer & out, const FormatSettings & format_settings) const; -#if USE_ROCKSDB - void load(const MergeTreeData & storage, const PartMetadataCachePtr & metadata_cache, const DiskPtr & disk, const String & part_path); -#else - void load(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path); -#endif + void load(const MergeTreeData & storage, const PartMetadataManagerPtr & manager); void store(const MergeTreeData & storage, const DiskPtr & disk, const String & part_path, MergeTreeDataPartChecksums & checksums) const; void store(const Block & partition_key_sample, const DiskPtr & disk, const String & part_path, MergeTreeDataPartChecksums & checksums) const; diff --git a/src/Storages/MergeTree/PartMetadataCache.cpp b/src/Storages/MergeTree/PartMetadataCache.cpp deleted file mode 100644 index eff6d9cc0a9..00000000000 --- a/src/Storages/MergeTree/PartMetadataCache.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "PartMetadataCache.h" - -#if USE_ROCKSDB -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ProfileEvents -{ - extern const Event MergeTreeMetadataCacheHit; - extern const Event MergeTreeMetadataCacheMiss; -} - -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - -namespace DB -{ - -std::unique_ptr -PartMetadataCache::readOrSet(const DiskPtr & disk, const String & file_name, String & value) -{ - String file_path = fs::path(getFullRelativePath()) / file_name; - String key = getKey(file_path); - auto status = cache->get(key, value); - if (!status.ok()) - { - ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheMiss); - if (!disk->exists(file_path)) - { - return nullptr; - } - - auto in = disk->readFile(file_path); - if (in) - { - readStringUntilEOF(value, *in); - cache->put(key, value); - } - } - else - { - ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheHit); - } - return std::make_unique(value); -} - -void PartMetadataCache::batchSet(const DiskPtr & disk, const Strings & file_names) -{ - String text; - String read_value; - for (const auto & file_name : file_names) - { - String file_path = fs::path(getFullRelativePath()) / file_name; - String key = getKey(file_path); - if (!disk->exists(file_path)) - continue; - - auto in = disk->readFile(file_path); - if (!in) - continue; - - readStringUntilEOF(text, *in); - auto status = cache->put(key, text); - if (!status.ok()) - { - status = cache->get(key, read_value); - if (status.IsNotFound() || read_value == text) - continue; - throw Exception(ErrorCodes::LOGICAL_ERROR, "set meta failed status:{}, file_path:{}", status.ToString(), file_path); - } - } -} - -void PartMetadataCache::batchDelete(const Strings & file_names) -{ - for (const auto & file_name : file_names) - { - String file_path = fs::path(getFullRelativePath()) / file_name; - String key = getKey(file_path); - auto status = cache->del(key); - if (!status.ok()) - { - String read_value; - status = cache->get(key, read_value); - if (status.IsNotFound()) - continue; - throw Exception(ErrorCodes::LOGICAL_ERROR, "drop meta failed status:{}, file_path:{}", status.ToString(), file_path); - } - } -} - -void PartMetadataCache::set(const String & file_name, const String & value) -{ - String file_path = fs::path(getFullRelativePath()) / file_name; - String key = getKey(file_path); - String read_value; - auto status = cache->get(key, read_value); - if (status == rocksdb::Status::OK() && value == read_value) - return; - - status = cache->put(key, value); - if (!status.ok()) - { - status = cache->get(key, read_value); - if (status.IsNotFound() || read_value == value) - return; - - throw Exception(ErrorCodes::LOGICAL_ERROR, "set meta failed status:{}, file_path:{}", status.ToString(), file_path); - } -} - -void PartMetadataCache::getFilesAndCheckSums(Strings & files, std::vector & checksums) const -{ - String prefix = getKey(fs::path(getFullRelativePath()) / ""); - Strings values; - cache->getByPrefix(prefix, files, values); - size_t size = files.size(); - for (size_t i = 0; i < size; ++i) - { - ReadBufferFromString rbuf(values[i]); - HashingReadBuffer hbuf(rbuf); - checksums.push_back(hbuf.getHash()); - } -} - -String PartMetadataCache::getFullRelativePath() const -{ - return fs::path(relative_data_path) / (parent_part ? parent_part->relative_path : "") / relative_path / ""; -} - -String PartMetadataCache::getKey(const String & file_path) const -{ - return disk_name + ":" + file_path; -} - -} -#endif diff --git a/src/Storages/MergeTree/PartMetadataCache.h b/src/Storages/MergeTree/PartMetadataCache.h deleted file mode 100644 index c2904738b9c..00000000000 --- a/src/Storages/MergeTree/PartMetadataCache.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "config_core.h" - -#if USE_ROCKSDB -#include -#include - - -namespace DB -{ - -class SeekableReadBuffer; -class IMergeTreeDataPart; - -class MergeTreeMetadataCache; -using MergeTreeMetadataCachePtr = std::shared_ptr; - -class IDisk; -using DiskPtr = std::shared_ptr; - -class PartMetadataCache -{ -public: - using uint128 = CityHash_v1_0_2::uint128; - - PartMetadataCache( - const MergeTreeMetadataCachePtr & cache_, - const String & disk_name_, - const String & relative_data_path_, - const String & relative_path_, - const IMergeTreeDataPart * parent_part_) - : cache(cache_) - , disk_name(disk_name_) - , relative_data_path(relative_data_path_) - , relative_path(relative_path_) - , parent_part(parent_part_) - { - } - - std::unique_ptr - readOrSet(const DiskPtr & disk, const String & file_name, String & value); - void batchSet(const DiskPtr & disk, const Strings & file_names); - void batchDelete(const Strings & file_names); - void set(const String & file_name, const String & value); - void getFilesAndCheckSums(Strings & files, std::vector & checksums) const; - -private: - String getFullRelativePath() const; - String getKey(const String & file_path) const; - - MergeTreeMetadataCachePtr cache; - const String & disk_name; - const String & relative_data_path; /// Relative path of table to disk - const String & relative_path; /// Relative path of part to table - const IMergeTreeDataPart * parent_part; -}; - -using PartMetadataCachePtr = std::shared_ptr; - -} -#endif diff --git a/src/Storages/MergeTree/PartMetadataManagerOrdinary.cpp b/src/Storages/MergeTree/PartMetadataManagerOrdinary.cpp new file mode 100644 index 00000000000..f12af590e7d --- /dev/null +++ b/src/Storages/MergeTree/PartMetadataManagerOrdinary.cpp @@ -0,0 +1,33 @@ +#include "PartMetadataManagerOrdinary.h" + +#include +#include +#include + +namespace DB +{ + +static std::unique_ptr openForReading(const DiskPtr & disk, const String & path) +{ + size_t file_size = disk->getFileSize(path); + return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); +} + +PartMetadataManagerOrdinary::PartMetadataManagerOrdinary(const IMergeTreeDataPart * part_) : IPartMetadataManager(part_) +{ +} + + +std::unique_ptr PartMetadataManagerOrdinary::read(const String & file_name) const +{ + String file_path = fs::path(part->getFullRelativePath() + "/" + file_name); + return openForReading(disk, file_path); +} + +bool PartMetadataManagerOrdinary::exists(const String & file_name) const +{ + return disk->exists(fs::path(part->getFullRelativePath()) / file_name); +} + + +} diff --git a/src/Storages/MergeTree/PartMetadataManagerOrdinary.h b/src/Storages/MergeTree/PartMetadataManagerOrdinary.h new file mode 100644 index 00000000000..5f236f6271d --- /dev/null +++ b/src/Storages/MergeTree/PartMetadataManagerOrdinary.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace DB +{ + +class PartMetadataManagerOrdinary : public IPartMetadataManager +{ +public: + explicit PartMetadataManagerOrdinary(const IMergeTreeDataPart * part_); + + ~PartMetadataManagerOrdinary() override = default; + + std::unique_ptr read(const String & file_name) const override; + + bool exists(const String & file_name) const override; + + void deleteAll(bool /*include_projection*/) override {} + + void assertAllDeleted(bool /*include_projection*/) const override {} + + void updateAll(bool /*include_projection*/) override {} + +}; + + +} diff --git a/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp b/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp new file mode 100644 index 00000000000..ce10f7b625c --- /dev/null +++ b/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp @@ -0,0 +1,194 @@ +#include "PartMetadataManagerWithCache.h" + +#if USE_ROCKSDB +#include +#include +#include + +namespace ProfileEvents +{ + extern const Event MergeTreeMetadataCacheHit; + extern const Event MergeTreeMetadataCacheMiss; +} + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +namespace DB +{ +PartMetadataManagerWithCache::PartMetadataManagerWithCache(const IMergeTreeDataPart * part_, const MergeTreeMetadataCachePtr & cache_) + : IPartMetadataManager(part_), cache(cache_) +{ +} + +String PartMetadataManagerWithCache::getKeyFromFilePath(const String & file_path) const +{ + return disk->getName() + ":" + file_path; +} + +String PartMetadataManagerWithCache::getFilePathFromKey(const String & key) const +{ + return key.substr(disk->getName().size() + 1); +} + +std::unique_ptr PartMetadataManagerWithCache::read(const String & file_name) const +{ + String file_path = fs::path(part->getFullRelativePath()) / file_name; + String key = getKeyFromFilePath(file_path); + String value; + auto status = cache->get(key, value); + if (!status.ok()) + { + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheMiss); + auto in = disk->readFile(file_path); + readStringUntilEOF(value, *in); + cache->put(key, value); + } + else + { + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheHit); + } + return std::make_unique(value); +} + +bool PartMetadataManagerWithCache::exists(const String & file_name) const +{ + String file_path = fs::path(part->getFullRelativePath()) / file_name; + String key = getKeyFromFilePath(file_path); + String value; + auto status = cache->get(key, value); + if (status.ok()) + { + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheHit); + return true; + } + else + { + ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheMiss); + return disk->exists(fs::path(part->getFullRelativePath()) / file_name); + } +} + +void PartMetadataManagerWithCache::deleteAll(bool include_projection) +{ + Strings file_names; + part->appendFilesOfColumnsChecksumsIndexes(file_names, include_projection); + + String value; + for (const auto & file_name : file_names) + { + String file_path = fs::path(part->getFullRelativePath()) / file_name; + String key = getKeyFromFilePath(file_path); + auto status = cache->del(key); + if (!status.ok()) + { + status = cache->get(key, value); + if (status.IsNotFound()) + continue; + + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "deleteAll failed include_projection:{} status:{}, file_path:{}", + include_projection, + status.ToString(), + file_path); + } + } +} + +void PartMetadataManagerWithCache::updateAll(bool include_projection) +{ + Strings file_names; + part->appendFilesOfColumnsChecksumsIndexes(file_names, include_projection); + + String value; + String read_value; + for (const auto & file_name : file_names) + { + String file_path = fs::path(part->getFullRelativePath()) / file_name; + if (!disk->exists(file_path)) + continue; + auto in = disk->readFile(file_path); + readStringUntilEOF(value, *in); + + String key = getKeyFromFilePath(file_path); + auto status = cache->put(key, value); + if (!status.ok()) + { + status = cache->get(key, read_value); + if (status.IsNotFound() || read_value == value) + continue; + + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "updateAll failed include_projection:{} status:{}, file_path:{}", + include_projection, + status.ToString(), + file_path); + } + } +} + +void PartMetadataManagerWithCache::assertAllDeleted(bool include_projection) const +{ + Strings keys; + std::vector _; + getKeysAndCheckSums(keys, _); + if (keys.empty()) + return; + + String file_path; + String file_name; + for (const auto & key : keys) + { + file_path = getFilePathFromKey(key); + file_name = fs::path(file_path).filename(); + + /// Metadata file belongs to current part + if (fs::path(part->getFullRelativePath()) / file_name == file_path) + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Data part {} with type {} with meta file {} still in cache", + part->name, + part->getType().toString(), + file_path); + + /// File belongs to projection part of current part + if (!part->isProjectionPart() && include_projection) + { + const auto & projection_parts = part->getProjectionParts(); + for (const auto & [projection_name, projection_part] : projection_parts) + { + if (fs::path(projection_part->getFullRelativePath()) / file_name == file_path) + { + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Data part {} with type {} with meta file {} with projection name still in cache", + part->name, + part->getType().toString(), + file_path, + projection_name); + } + } + } + } +} + +void PartMetadataManagerWithCache::getKeysAndCheckSums(Strings & keys, std::vector & checksums) const +{ + String prefix = getKeyFromFilePath(fs::path(part->getFullRelativePath()) / ""); + Strings values; + cache->getByPrefix(prefix, keys, values); + size_t size = keys.size(); + for (size_t i = 0; i < size; ++i) + { + ReadBufferFromString rbuf(values[i]); + HashingReadBuffer hbuf(rbuf); + checksums.push_back(hbuf.getHash()); + } +} + +} +#endif diff --git a/src/Storages/MergeTree/PartMetadataManagerWithCache.h b/src/Storages/MergeTree/PartMetadataManagerWithCache.h new file mode 100644 index 00000000000..76570b0684a --- /dev/null +++ b/src/Storages/MergeTree/PartMetadataManagerWithCache.h @@ -0,0 +1,40 @@ +#pragma once + +#include "config_core.h" + +#if USE_ROCKSDB +#include +#include + +namespace DB +{ + +class PartMetadataManagerWithCache : public IPartMetadataManager +{ +public: + PartMetadataManagerWithCache(const IMergeTreeDataPart * part_, const MergeTreeMetadataCachePtr & cache_); + + ~PartMetadataManagerWithCache() override = default; + + std::unique_ptr read(const String & file_name) const override; + + bool exists(const String & file_name) const override; + + void deleteAll(bool include_projection) override; + + void assertAllDeleted(bool include_projection) const override; + + void updateAll(bool include_projection) override; + +private: + String getKeyFromFilePath(const String & file_path) const; + String getFilePathFromKey(const String & key) const; + + void getKeysAndCheckSums(Strings & keys, std::vector & checksums) const; + + + MergeTreeMetadataCachePtr cache; +}; + +} +#endif From b91e0a533c0328167cf812c4f853d3a18cdbac84 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Wed, 5 Jan 2022 20:05:22 +0800 Subject: [PATCH 0161/1647] fix style --- 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 4c88580e069..99072ebcd41 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -338,7 +338,7 @@ IMergeTreeDataPart::IMergeTreeDataPart( incrementTypeMetric(part_type); minmax_idx = std::make_shared(); - + initializePartMetadataManager(); } From 83d064c24adb5e3b509050e610120e7c5a872707 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Thu, 6 Jan 2022 11:41:24 +0800 Subject: [PATCH 0162/1647] fix unit test and build error --- src/IO/CMakeLists.txt | 4 ++-- src/IO/examples/write_buffer.cpp | 1 + .../tests/gtest_merge_tree_metadata_cache.cpp | 16 ++++++---------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/IO/CMakeLists.txt b/src/IO/CMakeLists.txt index 970602896c9..f676f415eea 100644 --- a/src/IO/CMakeLists.txt +++ b/src/IO/CMakeLists.txt @@ -1,3 +1,3 @@ -#if (ENABLE_EXAMPLES) +if (ENABLE_EXAMPLES) add_subdirectory (examples) -#endif () +endif () diff --git a/src/IO/examples/write_buffer.cpp b/src/IO/examples/write_buffer.cpp index 5587b8aa1a2..bca0be24b1a 100644 --- a/src/IO/examples/write_buffer.cpp +++ b/src/IO/examples/write_buffer.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include diff --git a/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp b/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp index 1005739098c..839c54c63b2 100644 --- a/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp +++ b/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp @@ -1,9 +1,11 @@ -#include +#include "config_core.h" #if USE_ROCKSDB #include #include #include +#include +#include #include using namespace DB; @@ -13,17 +15,11 @@ class MergeTreeMetadataCacheTest : public ::testing::Test public: void SetUp() override { - shared_context = Context::createShared(); - global_context = Context::createGlobal(shared_context.get()); - global_context->makeGlobalContext(); - global_context->initializeMergeTreeMetadataCache("./db/", 256 << 20); - cache = global_context->getMergeTreeMetadataCache(); + const auto & context_holder = getContext(); + context_holder.context->initializeMergeTreeMetadataCache("./db/", 256 << 20); + cache = context_holder.context->getMergeTreeMetadataCache(); } - void TearDown() override { global_context->shutdown(); } - - SharedContextHolder shared_context; - ContextMutablePtr global_context; MergeTreeMetadataCachePtr cache; }; From 3803cc3d5eddf69fafc72f5181f25be65761c4e5 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Thu, 6 Jan 2022 15:42:12 +0800 Subject: [PATCH 0163/1647] fix bug --- programs/server/Server.cpp | 4 ++-- programs/server/config.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/programs/server/Server.cpp b/programs/server/Server.cpp index 8641623ac07..846a1960713 100644 --- a/programs/server/Server.cpp +++ b/programs/server/Server.cpp @@ -814,7 +814,6 @@ if (ThreadFuzzer::instance().isEffective()) fs::create_directories(path / "metadata_dropped/"); } - #if USE_ROCKSDB /// Initialize merge tree metadata cache if (config().has("merge_tree_metadata_cache")) @@ -822,9 +821,10 @@ if (ThreadFuzzer::instance().isEffective()) fs::create_directories(path / "rocksdb/"); size_t size = config().getUInt64("merge_tree_metadata_cache.lru_cache_size", 256 << 20); bool continue_if_corrupted = config().getBool("merge_tree_metadata_cache.continue_if_corrupted", false); - try { + LOG_DEBUG( + log, "Initiailizing merge tree metadata cache lru_cache_size:{} continue_if_corrupted:{}", size, continue_if_corrupted); global_context->initializeMergeTreeMetadataCache(path_str + "/" + "rocksdb", size); } catch (...) diff --git a/programs/server/config.xml b/programs/server/config.xml index c5f8e7eeb94..4f75e233b64 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -1293,8 +1293,8 @@ --> - + From cf413f16a8166af61c236e054be650ce7ffa908a Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Fri, 7 Jan 2022 18:37:08 +0800 Subject: [PATCH 0164/1647] remove function checkPartMetadataCache --- src/Functions/checkPartMetadataCache.cpp | 156 ------------------ .../registerFunctionsMiscellaneous.cpp | 8 - src/Storages/MergeTree/IMergeTreeDataPart.cpp | 51 +----- src/Storages/MergeTree/IMergeTreeDataPart.h | 12 +- src/Storages/MergeTree/IPartMetadataManager.h | 3 + .../MergeTree/PartMetadataManagerOrdinary.h | 1 + .../PartMetadataManagerWithCache.cpp | 83 +++++++++- .../MergeTree/PartMetadataManagerWithCache.h | 2 + .../ReplicatedMergeTreePartCheckThread.cpp | 1 + src/Storages/MergeTree/checkDataPart.cpp | 1 - src/Storages/StorageMergeTree.cpp | 3 + 11 files changed, 101 insertions(+), 220 deletions(-) delete mode 100644 src/Functions/checkPartMetadataCache.cpp diff --git a/src/Functions/checkPartMetadataCache.cpp b/src/Functions/checkPartMetadataCache.cpp deleted file mode 100644 index d65c14095f7..00000000000 --- a/src/Functions/checkPartMetadataCache.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include "config_core.h" - -#if USE_ROCKSDB -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace DB -{ -namespace ErrorCodes -{ - extern const int ILLEGAL_TYPE_OF_ARGUMENT; - extern const int ILLEGAL_COLUMN; -} - -class FunctionCheckPartMetadataCache : public IFunction, WithContext -{ -public: - using uint128 = IMergeTreeDataPart::uint128; - using DataPartPtr = MergeTreeData::DataPartPtr; - using DataPartState = MergeTreeData::DataPartState; - using DataPartStates = MergeTreeData::DataPartStates; - - - static constexpr auto name = "checkPartMetadataCache"; - static FunctionPtr create(ContextPtr context_) { return std::make_shared(context_); } - - static constexpr DataPartStates part_states - = {DataPartState::Active, - DataPartState::Temporary, - DataPartState::PreActive, - DataPartState::Outdated, - DataPartState::Deleting, - DataPartState::DeleteOnDestroy}; - - explicit FunctionCheckPartMetadataCache(ContextPtr context_) : WithContext(context_) { } - - String getName() const override { return name; } - - bool isDeterministic() const override { return false; } - - bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return false; } - - bool isDeterministicInScopeOfQuery() const override { return false; } - - size_t getNumberOfArguments() const override { return 2; } - - DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override - { - for (const auto & argument : arguments) - { - if (!isString(argument)) - throw Exception("The argument of function " + getName() + " must have String type", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - } - DataTypePtr key_type = std::make_unique(); - DataTypePtr state_type = std::make_unique(); - DataTypePtr cache_checksum_type = std::make_unique(32); - DataTypePtr disk_checksum_type = std::make_unique(32); - DataTypePtr match_type = std::make_unique(); - DataTypePtr tuple_type - = std::make_unique(DataTypes{key_type, state_type, cache_checksum_type, disk_checksum_type, match_type}); - return std::make_shared(tuple_type); - } - - ColumnPtr executeImpl(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count) const override - { - /// Get database name - const auto * arg_database = arguments[0].column.get(); - const ColumnString * column_database = checkAndGetColumnConstData(arg_database); - if (!column_database) - throw Exception("The first argument of function " + getName() + " must be constant String", ErrorCodes::ILLEGAL_COLUMN); - String database_name = column_database->getDataAt(0).toString(); - - /// Get table name - const auto * arg_table = arguments[1].column.get(); - const ColumnString * column_table = checkAndGetColumnConstData(arg_table); - if (!column_table) - throw Exception("The second argument of function " + getName() + " must be constant String", ErrorCodes::ILLEGAL_COLUMN); - String table_name = column_table->getDataAt(0).toString(); - - /// Get storage - StorageID storage_id(database_name, table_name); - auto storage = DatabaseCatalog::instance().getTable(storage_id, getContext()); - auto data = std::dynamic_pointer_cast(storage); - if (!data || !data->getSettings()->use_metadata_cache) - throw Exception("The table in function " + getName() + " must be in MergeTree Family", ErrorCodes::ILLEGAL_COLUMN); - - /// Fill in checking results. - auto col_result = result_type->createColumn(); - auto & col_arr = assert_cast(*col_result); - auto & col_tuple = assert_cast(col_arr.getData()); - col_tuple.reserve(data->fileNumberOfDataParts(part_states)); - auto & col_key = assert_cast(col_tuple.getColumn(0)); - auto & col_state = assert_cast(col_tuple.getColumn(1)); - auto & col_cache_checksum = assert_cast(col_tuple.getColumn(2)); - auto & col_disk_checksum = assert_cast(col_tuple.getColumn(3)); - auto & col_match = assert_cast(col_tuple.getColumn(4)); - auto parts = data->getDataParts(part_states); - for (const auto & part : parts) - executePart(part, col_key, col_state, col_cache_checksum, col_disk_checksum, col_match); - col_arr.getOffsets().push_back(col_tuple.size()); - return result_type->createColumnConst(input_rows_count, col_arr[0]); - } - - static void executePart( - const DataPartPtr & part, - ColumnString & col_key, - ColumnString & col_state, - ColumnFixedString & col_cache_checksum, - ColumnFixedString & col_disk_checksum, - ColumnUInt8 & col_match) - { - Strings keys; - auto state_view = part->stateString(); - String state(state_view.data(), state_view.size()); - std::vector cache_checksums; - std::vector disk_checksums; - uint8_t match = 0; - size_t file_number = part->fileNumberOfColumnsChecksumsIndexes(); - keys.reserve(file_number); - cache_checksums.reserve(file_number); - disk_checksums.reserve(file_number); - - // part->checkMetadataCache(keys, cache_checksums, disk_checksums); - for (size_t i = 0; i < keys.size(); ++i) - { - col_key.insert(keys[i]); - col_state.insert(state); - col_cache_checksum.insert(getHexUIntUppercase(cache_checksums[i].first) + getHexUIntUppercase(cache_checksums[i].second)); - col_disk_checksum.insert(getHexUIntUppercase(disk_checksums[i].first) + getHexUIntUppercase(disk_checksums[i].second)); - - match = cache_checksums[i] == disk_checksums[i] ? 1 : 0; - col_match.insertValue(match); - } - } -}; - -void registerFunctionCheckPartMetadataCache(FunctionFactory & factory) -{ - factory.registerFunction(); -} -} -#endif diff --git a/src/Functions/registerFunctionsMiscellaneous.cpp b/src/Functions/registerFunctionsMiscellaneous.cpp index 297e6dfb452..76d61ce509a 100644 --- a/src/Functions/registerFunctionsMiscellaneous.cpp +++ b/src/Functions/registerFunctionsMiscellaneous.cpp @@ -81,10 +81,6 @@ void registerFunctionServerUUID(FunctionFactory &); void registerFunctionZooKeeperSessionUptime(FunctionFactory &); void registerFunctionGetOSKernelVersion(FunctionFactory &); -#if USE_ROCKSDB -void registerFunctionCheckPartMetadataCache(FunctionFactory &); -#endif - #if USE_ICU void registerFunctionConvertCharset(FunctionFactory &); #endif @@ -171,10 +167,6 @@ void registerFunctionsMiscellaneous(FunctionFactory & factory) registerFunctionZooKeeperSessionUptime(factory); registerFunctionGetOSKernelVersion(factory); -#if USE_ROCKSDB - registerFunctionCheckPartMetadataCache(factory); -#endif - #if USE_ICU registerFunctionConvertCharset(factory); #endif diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 99072ebcd41..909c6e42c9e 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -61,8 +61,7 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; } -void IMergeTreeDataPart::MinMaxIndex::load( - const MergeTreeData & data, const PartMetadataManagerPtr & manager) +void IMergeTreeDataPart::MinMaxIndex::load(const MergeTreeData & data, const PartMetadataManagerPtr & manager) { auto metadata_snapshot = data.getInMemoryMetadataPtr(); const auto & partition_key = metadata_snapshot->getPartitionKey(); @@ -1782,8 +1781,6 @@ String IMergeTreeDataPart::getZeroLevelPartBlockID() const return info.partition_id + "_" + toString(hash_value.words[0]) + "_" + toString(hash_value.words[1]); } -/* -#if USE_ROCKSDB IMergeTreeDataPart::uint128 IMergeTreeDataPart::getActualChecksumByFile(const String & file_path) const { assert(use_metadata_cache); @@ -1808,52 +1805,10 @@ IMergeTreeDataPart::uint128 IMergeTreeDataPart::getActualChecksumByFile(const St return in_hash.getHash(); } -void IMergeTreeDataPart::checkMetadataCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const +std::unordered_map IMergeTreeDataPart::checkMetadata() const { - assert(use_metadata_cache); - - /// Only applies for normal part - if (isProjectionPart()) - return; - - /// the directory of projection part is under the directory of its parent part - const auto filenames_without_checksums = getFileNamesWithoutChecksums(); - metadata_cache->getFilesAndCheckSums(files, cache_checksums); - for (const auto & file : files) - { - // std::cout << "check key:" << file << std::endl; - String file_name = fs::path(file).filename(); - - /// file belongs to normal part - if (fs::path(getFullRelativePath()) / file_name == file) - { - auto disk_checksum = getActualChecksumByFile(file); - disk_checksums.push_back(disk_checksum); - continue; - } - - /// file belongs to projection part - String proj_dir_name = fs::path(file).parent_path().filename(); - auto pos = proj_dir_name.find_last_of('.'); - if (pos == String::npos) - { - disk_checksums.push_back({}); - continue; - } - String proj_name = proj_dir_name.substr(0, pos); - auto it = projection_parts.find(proj_name); - if (it == projection_parts.end()) - { - disk_checksums.push_back({}); - continue; - } - - auto disk_checksum = it->second->getActualChecksumByFile(file); - disk_checksums.push_back(disk_checksum); - } + return metadata_manager->check(); } -#endif -*/ bool isCompactPart(const MergeTreeDataPartPtr & data_part) { diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 2bb6f570b6a..ecbc2a32aa4 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -371,8 +371,6 @@ public: String getRelativePathForPrefix(const String & prefix, bool detached = false) const; - // virtual void checkMetadataCache(Strings & files, std::vector & cache_checksums, std::vector & disk_checksums) const; - bool isProjectionPart() const { return parent_part != nullptr; } const IMergeTreeDataPart * getParentPart() const { return parent_part; } @@ -418,6 +416,12 @@ public: /// Required for distinguish different copies of the same part on S3 String getUniqueId() const; + /// Get checksums of metadata file in part directory + IMergeTreeDataPart::uint128 getActualChecksumByFile(const String & file_path) const; + + /// Check metadata in cache is consistent with actual metadata on disk(if use_metadata_cache is true) + std::unordered_map checkMetadata() const; + protected: /// Total size of all columns, calculated once in calcuateColumnSizesOnDisk @@ -464,6 +468,7 @@ protected: void initializePartMetadataManager(); + private: /// In compact parts order of columns is necessary NameToNumber column_name_to_position; @@ -526,9 +531,6 @@ private: /// for this column with default parameters. CompressionCodecPtr detectDefaultCompressionCodec() const; - // void modifyAllMetadataCaches(ModifyCacheType type, bool include_projection = false) const; - // IMergeTreeDataPart::uint128 getActualChecksumByFile(const String & file_path) const; - mutable State state{State::Temporary}; }; diff --git a/src/Storages/MergeTree/IPartMetadataManager.h b/src/Storages/MergeTree/IPartMetadataManager.h index 6db87e20f16..17786c90761 100644 --- a/src/Storages/MergeTree/IPartMetadataManager.h +++ b/src/Storages/MergeTree/IPartMetadataManager.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -33,6 +34,8 @@ public: virtual void updateAll(bool include_projection) = 0; + virtual std::unordered_map check() const = 0; + protected: const IMergeTreeDataPart * part; const DiskPtr disk; diff --git a/src/Storages/MergeTree/PartMetadataManagerOrdinary.h b/src/Storages/MergeTree/PartMetadataManagerOrdinary.h index 5f236f6271d..a655431296a 100644 --- a/src/Storages/MergeTree/PartMetadataManagerOrdinary.h +++ b/src/Storages/MergeTree/PartMetadataManagerOrdinary.h @@ -22,6 +22,7 @@ public: void updateAll(bool /*include_projection*/) override {} + std::unordered_map check() const override { return {}; } }; diff --git a/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp b/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp index ce10f7b625c..b088f63c0d0 100644 --- a/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp +++ b/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp @@ -1,6 +1,8 @@ #include "PartMetadataManagerWithCache.h" #if USE_ROCKSDB +#include +#include #include #include #include @@ -11,13 +13,16 @@ namespace ProfileEvents extern const Event MergeTreeMetadataCacheMiss; } +namespace DB +{ + namespace ErrorCodes { extern const int LOGICAL_ERROR; + extern const int CORRUPTED_DATA; + extern const int NO_SUCH_PROJECTION_IN_TABLE; } -namespace DB -{ PartMetadataManagerWithCache::PartMetadataManagerWithCache(const IMergeTreeDataPart * part_, const MergeTreeMetadataCachePtr & cache_) : IPartMetadataManager(part_), cache(cache_) { @@ -190,5 +195,79 @@ void PartMetadataManagerWithCache::getKeysAndCheckSums(Strings & keys, std::vect } } +std::unordered_map PartMetadataManagerWithCache::check() const +{ + /// Only applies for normal part stored on disk + if (part->isProjectionPart() || !part->isStoredOnDisk()) + return {}; + + /// the directory of projection part is under the directory of its parent part + const auto filenames_without_checksums = part->getFileNamesWithoutChecksums(); + + std::unordered_map results; + Strings keys; + std::vector cache_checksums; + std::vector disk_checksums; + getKeysAndCheckSums(keys, cache_checksums); + for (size_t i = 0; i < keys.size(); ++i) + { + const auto & key = keys[i]; + String file_path = getFilePathFromKey(key); + String file_name = fs::path(file_path).filename(); + results.emplace(file_name, cache_checksums[i]); + + /// File belongs to normal part + if (fs::path(part->getFullRelativePath()) / file_name == file_path) + { + auto disk_checksum = part->getActualChecksumByFile(file_path); + if (disk_checksum != cache_checksums[i]) + throw Exception( + ErrorCodes::CORRUPTED_DATA, + "Checksums doesn't match in part {}. Expected: {}. Found {}.", + part->name, + getHexUIntUppercase(disk_checksum.first) + getHexUIntUppercase(disk_checksum.second), + getHexUIntUppercase(cache_checksums[i].first) + getHexUIntUppercase(cache_checksums[i].second)); + + disk_checksums.push_back(disk_checksum); + continue; + } + + /// File belongs to projection part + String proj_dir_name = fs::path(file_path).parent_path().filename(); + auto pos = proj_dir_name.find_last_of('.'); + if (pos == String::npos) + { + throw Exception( + ErrorCodes::NO_SUCH_PROJECTION_IN_TABLE, + "There is no projection in part: {} contains file: {} with directory name: {}", + part->name, + file_path, + proj_dir_name); + } + + String proj_name = proj_dir_name.substr(0, pos); + const auto & projection_parts = part->getProjectionParts(); + auto it = projection_parts.find(proj_name); + if (it == projection_parts.end()) + { + throw Exception( + ErrorCodes::NO_SUCH_PROJECTION_IN_TABLE, + "There is no projection {} in part: {} contains file: {}", + proj_name, part->name, file_path); + } + + auto disk_checksum = it->second->getActualChecksumByFile(file_path); + if (disk_checksum != cache_checksums[i]) + throw Exception( + ErrorCodes::CORRUPTED_DATA, + "Checksums doesn't match in projection part {} {}. Expected: {}. Found {}.", + part->name, proj_name, + getHexUIntUppercase(disk_checksum.first) + getHexUIntUppercase(disk_checksum.second), + getHexUIntUppercase(cache_checksums[i].first) + getHexUIntUppercase(cache_checksums[i].second)); + disk_checksums.push_back(disk_checksum); + } + return results; +} + } #endif diff --git a/src/Storages/MergeTree/PartMetadataManagerWithCache.h b/src/Storages/MergeTree/PartMetadataManagerWithCache.h index 76570b0684a..8b1472f5457 100644 --- a/src/Storages/MergeTree/PartMetadataManagerWithCache.h +++ b/src/Storages/MergeTree/PartMetadataManagerWithCache.h @@ -26,6 +26,8 @@ public: void updateAll(bool include_projection) override; + std::unordered_map check() const override; + private: String getKeyFromFilePath(const String & file_path) const; String getFilePathFromKey(const String & key) const; diff --git a/src/Storages/MergeTree/ReplicatedMergeTreePartCheckThread.cpp b/src/Storages/MergeTree/ReplicatedMergeTreePartCheckThread.cpp index 8fcaee66007..85d929b5ea4 100644 --- a/src/Storages/MergeTree/ReplicatedMergeTreePartCheckThread.cpp +++ b/src/Storages/MergeTree/ReplicatedMergeTreePartCheckThread.cpp @@ -399,6 +399,7 @@ CheckResult ReplicatedMergeTreePartCheckThread::checkPart(const String & part_na LOG_WARNING(log, "We have part {} covering part {}", part->name, part_name); } + part->checkMetadata(); return {part_name, true, ""}; } diff --git a/src/Storages/MergeTree/checkDataPart.cpp b/src/Storages/MergeTree/checkDataPart.cpp index eabd901eb24..0f35e30c5d0 100644 --- a/src/Storages/MergeTree/checkDataPart.cpp +++ b/src/Storages/MergeTree/checkDataPart.cpp @@ -270,7 +270,6 @@ IMergeTreeDataPart::Checksums checkDataPart( if (require_checksums || !checksums_txt.files.empty()) checksums_txt.checkEqual(checksums_data, check_uncompressed); - return checksums_data; } diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index 11815d9ceef..03a789a7725 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1612,6 +1612,8 @@ CheckResults StorageMergeTree::checkData(const ASTPtr & query, ContextPtr local_ auto out = disk->writeFile(tmp_checksums_path, 4096); part->checksums.write(*out); disk->moveFile(tmp_checksums_path, checksums_path); + + part->checkMetadata(); results.emplace_back(part->name, true, "Checksums recounted and written to disk."); } catch (const Exception & ex) @@ -1628,6 +1630,7 @@ CheckResults StorageMergeTree::checkData(const ASTPtr & query, ContextPtr local_ try { checkDataPart(part, true); + part->checkMetadata(); results.emplace_back(part->name, true, ""); } catch (const Exception & ex) From 3ed13e789612010ad55dcb10586e6d226c33ba27 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Fri, 7 Jan 2022 21:06:10 +0800 Subject: [PATCH 0165/1647] refactor stateless test and add move part/partition integration test --- programs/server/config.xml | 4 +- .../configs/logs_config.xml | 4 + tests/integration/test_multiple_disks/test.py | 17 +- ..._check_table_with_metadata_cache.reference | 672 ++++++++++++++++++ .../01233_check_table_with_metadata_cache.sh | 90 +++ 5 files changed, 780 insertions(+), 7 deletions(-) create mode 100644 tests/queries/0_stateless/01233_check_table_with_metadata_cache.reference create mode 100644 tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh diff --git a/programs/server/config.xml b/programs/server/config.xml index 4f75e233b64..ec49d516849 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -1293,8 +1293,8 @@ --> - + diff --git a/tests/integration/test_multiple_disks/configs/logs_config.xml b/tests/integration/test_multiple_disks/configs/logs_config.xml index 2ee8bb55f38..b0643c8bdad 100644 --- a/tests/integration/test_multiple_disks/configs/logs_config.xml +++ b/tests/integration/test_multiple_disks/configs/logs_config.xml @@ -14,4 +14,8 @@ part_log
500 + + 268435456 + true + diff --git a/tests/integration/test_multiple_disks/test.py b/tests/integration/test_multiple_disks/test.py index db541edde9c..cfce95fab69 100644 --- a/tests/integration/test_multiple_disks/test.py +++ b/tests/integration/test_multiple_disks/test.py @@ -632,12 +632,13 @@ def get_paths_for_partition_from_part_log(node, table, partition_id): return paths.strip().split('\n') -@pytest.mark.parametrize("name,engine", [ - pytest.param("altering_mt", "MergeTree()", id="mt"), +@pytest.mark.parametrize("name,engine,use_metadata_cache", [ + pytest.param("altering_mt", "MergeTree()", "false", id="mt"), + pytest.param("altering_mt", "MergeTree()", "true", id="mt_use_metadata_cache"), # ("altering_replicated_mt","ReplicatedMergeTree('/clickhouse/altering_replicated_mt', '1')",), # SYSTEM STOP MERGES doesn't disable merges assignments ]) -def test_alter_move(start_cluster, name, engine): +def test_alter_move(start_cluster, name, engine, use_metadata_cache): try: node1.query(""" CREATE TABLE IF NOT EXISTS {name} ( @@ -646,8 +647,8 @@ def test_alter_move(start_cluster, name, engine): ) ENGINE = {engine} ORDER BY tuple() PARTITION BY toYYYYMM(EventDate) - SETTINGS storage_policy='jbods_with_external' - """.format(name=name, engine=engine)) + SETTINGS storage_policy='jbods_with_external', use_metadata_cache={use_metadata_cache} + """.format(name=name, engine=engine, use_metadata_cache=use_metadata_cache)) node1.query("SYSTEM STOP MERGES {}".format(name)) # to avoid conflicts @@ -655,6 +656,8 @@ def test_alter_move(start_cluster, name, engine): node1.query("INSERT INTO {} VALUES(toDate('2019-03-16'), 66)".format(name)) node1.query("INSERT INTO {} VALUES(toDate('2019-04-10'), 42)".format(name)) node1.query("INSERT INTO {} VALUES(toDate('2019-04-11'), 43)".format(name)) + assert node1.query("CHECK TABLE " + name) == "1\n" + used_disks = get_used_disks_for_table(node1, name) assert all(d.startswith("jbod") for d in used_disks), "All writes should go to jbods" @@ -664,6 +667,7 @@ def test_alter_move(start_cluster, name, engine): time.sleep(1) node1.query("ALTER TABLE {} MOVE PART '{}' TO VOLUME 'external'".format(name, first_part)) + assert node1.query("CHECK TABLE " + name) == "1\n" disk = node1.query( "SELECT disk_name FROM system.parts WHERE table = '{}' and name = '{}' and active = 1".format(name, first_part)).strip() @@ -672,6 +676,7 @@ def test_alter_move(start_cluster, name, engine): time.sleep(1) node1.query("ALTER TABLE {} MOVE PART '{}' TO DISK 'jbod1'".format(name, first_part)) + assert node1.query("CHECK TABLE " + name) == "1\n" disk = node1.query( "SELECT disk_name FROM system.parts WHERE table = '{}' and name = '{}' and active = 1".format(name, first_part)).strip() @@ -680,6 +685,7 @@ def test_alter_move(start_cluster, name, engine): time.sleep(1) node1.query("ALTER TABLE {} MOVE PARTITION 201904 TO VOLUME 'external'".format(name)) + assert node1.query("CHECK TABLE " + name) == "1\n" disks = node1.query( "SELECT disk_name FROM system.parts WHERE table = '{}' and partition = '201904' and active = 1".format( name)).strip().split('\n') @@ -690,6 +696,7 @@ def test_alter_move(start_cluster, name, engine): time.sleep(1) node1.query("ALTER TABLE {} MOVE PARTITION 201904 TO DISK 'jbod2'".format(name)) + assert node1.query("CHECK TABLE " + name) == "1\n" disks = node1.query( "SELECT disk_name FROM system.parts WHERE table = '{}' and partition = '201904' and active = 1".format( name)).strip().split('\n') diff --git a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.reference b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.reference new file mode 100644 index 00000000000..5957d23fe82 --- /dev/null +++ b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.reference @@ -0,0 +1,672 @@ +database engine:Ordinary; table engine:MergeTree; use metadata cache:false; use projection:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:MergeTree; use metadata cache:false; use projection:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:MergeTree; use metadata cache:true; use projection:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:MergeTree; use metadata cache:true; use projection:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:MergeTree; use metadata cache:false; use projection:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:MergeTree; use metadata cache:false; use projection:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:MergeTree; use metadata cache:true; use projection:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:MergeTree; use metadata cache:true; use projection:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 diff --git a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh new file mode 100644 index 00000000000..5a3fd98c3be --- /dev/null +++ b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CURDIR"/../shell_config.sh + +table_engines="ReplicatedMergeTree" +database_engines="Ordinary Atomic" +use_metadata_caches="false true" +use_projections="false true" + +for table_engine in $table_engines; do + for database_engine in $database_engines; do + for use_metadata_cache in $use_metadata_caches; do + for use_projection in $use_projections; do + echo "database engine:${database_engine}; table engine:${table_engine}; use metadata cache:${use_metadata_cache}; use projection:${use_projection}" + + ${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC;" + ${CLICKHOUSE_CLIENT} --query "DROP DATABASE IF EXISTS test_metadata_cache;" + ${CLICKHOUSE_CLIENT} --query "CREATE DATABASE test_metadata_cache ENGINE = ${database_engine};" + + table_engine_clause="" + if [[ "$table_engine" == "ReplicatedMergeTree" ]]; then + table_engine_clause="ENGINE ReplicatedMergeTree('/clickhouse/tables/${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}/test_metadata_cache/check_part_metadata_cache', 'r1')" + elif [[ "$table_engine" == "MergeTree" ]]; then + table_engine_clause="ENGINE MergeTree()" + fi + + projection_clause="" + if [[ "$use_projection" == "true" ]]; then + projection_clause=", projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)" + fi + ${CLICKHOUSE_CLIENT} --query "CREATE TABLE test_metadata_cache.check_part_metadata_cache (p Date, k UInt64, v1 UInt64, v2 Int64${projection_clause}) $table_engine_clause PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = ${use_metadata_cache};" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Insert first batch of data. + ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000);" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Insert second batch of data. + ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000);" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # First update. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Second update. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # First delete. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Second delete. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Insert third batch of data. + ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000);" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Drop one partition. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Add column. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Delete column. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Add TTL. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Modify TTL. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Truncate table. + ${CLICKHOUSE_CLIENT} --echo --query "TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + done + done + done +done From 3d0e960bc0d6250c634811172d6cc019d36ae9ab Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Fri, 7 Jan 2022 21:07:06 +0800 Subject: [PATCH 0166/1647] fix stateless test --- .../0_stateless/01233_check_table_with_metadata_cache.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh index 5a3fd98c3be..017c7977745 100644 --- a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh +++ b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh @@ -4,7 +4,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh -table_engines="ReplicatedMergeTree" +table_engines="MergeTree ReplicatedMergeTree" database_engines="Ordinary Atomic" use_metadata_caches="false true" use_projections="false true" From fdea2bc9caa2f2b1757d93768e0afe9ac2f30a15 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Sat, 8 Jan 2022 09:55:02 +0800 Subject: [PATCH 0167/1647] remove old stateless tests --- .../01233_check_part_meta_cache.reference | 28 ---- .../01233_check_part_meta_cache.sql | 125 ----------------- ..._check_part_meta_cache_in_atomic.reference | 28 ---- .../01233_check_part_meta_cache_in_atomic.sql | 126 ----------------- ...check_part_meta_cache_replicated.reference | 28 ---- ...01233_check_part_meta_cache_replicated.sql | 128 ------------------ ..._meta_cache_replicated_in_atomic.reference | 28 ---- ...k_part_meta_cache_replicated_in_atomic.sql | 128 ------------------ 8 files changed, 619 deletions(-) delete mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache.reference delete mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache.sql delete mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference delete mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql delete mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference delete mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql delete mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference delete mode 100644 tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache.reference b/tests/queries/0_stateless/01233_check_part_meta_cache.reference deleted file mode 100644 index 2bd6025fea2..00000000000 --- a/tests/queries/0_stateless/01233_check_part_meta_cache.reference +++ /dev/null @@ -1,28 +0,0 @@ -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache.sql b/tests/queries/0_stateless/01233_check_part_meta_cache.sql deleted file mode 100644 index 6c1a1232cab..00000000000 --- a/tests/queries/0_stateless/01233_check_part_meta_cache.sql +++ /dev/null @@ -1,125 +0,0 @@ --- Tags: no-parallel, no-fasttest - --- Create table under database with engine ordinary. -set mutations_sync = 1; -DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache; -DROP DATABASE IF EXISTS test_metadata_cache; -CREATE DATABASE test_metadata_cache ENGINE = Ordinary; -CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert first batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert second batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Update some data. -alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert third batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Drop partitioin 201805 -alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Optimize table. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add column. -alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete column. -alter table test_metadata_cache.check_part_metadata_cache drop column v1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Modify TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Truncate table. -truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Recreate table with projection. -drop table if exists test_metadata_cache.check_part_metadata_cache; -CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; - --- Insert first batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert second batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Update some data. -alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert third batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Drop partitioin 201805 -alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Optimize table. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add column. -alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Update TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Truncate table. -truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference deleted file mode 100644 index 2bd6025fea2..00000000000 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.reference +++ /dev/null @@ -1,28 +0,0 @@ -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql deleted file mode 100644 index af8d6f888a7..00000000000 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_in_atomic.sql +++ /dev/null @@ -1,126 +0,0 @@ --- Tags: no-parallel, no-fasttest - --- Create table under database with engine atomic. -set mutations_sync = 1; -DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC; -DROP DATABASE IF EXISTS test_metadata_cache; -CREATE DATABASE test_metadata_cache ENGINE = Atomic; -CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert first batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert second batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Update some data. -alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Drop partitioin 201805 -alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Optimize table. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add column. -alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete column. -alter table test_metadata_cache.check_part_metadata_cache drop column v1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + 30; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Modify TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + 60; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Truncate table. -truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Recreate table with projection. -drop table if exists test_metadata_cache.check_part_metadata_cache SYNC; -CREATE TABLE test_metadata_cache.check_part_metadata_cache( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE MergeTree() PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; - --- Insert first batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- nsert second batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Update some data. -alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Drop partitioin 201805 -alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Optimize table. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add column. -alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Modify TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Truncate table. -truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference deleted file mode 100644 index 2bd6025fea2..00000000000 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.reference +++ /dev/null @@ -1,28 +0,0 @@ -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql deleted file mode 100644 index bdb43c4e905..00000000000 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated.sql +++ /dev/null @@ -1,128 +0,0 @@ --- Tags: no-fasttest, no-parallel, zookeeper --- Tag no-parallel: static zk path - --- Create table under database with engine ordinary. -set mutations_sync = 1; -set replication_alter_partitions_sync = 2; -DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache; -DROP DATABASE IF EXISTS test_metadata_cache; -CREATE DATABASE test_metadata_cache ENGINE = Ordinary; -CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/test_metadata_cache/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert first batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert second batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Update some data. -alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - ---Delete some data. -alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Drop partitioin 201805 -alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Optimize table. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add column. -alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete column. -alter table test_metadata_cache.check_part_metadata_cache drop column v1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Modify TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Truncate table. -truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Recreate table with projection. -drop table if exists test_metadata_cache.check_part_metadata_cache ; -CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/test_metadata_cache/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; - --- Insert first batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert second batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Update some data. -alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - ---Delete some data. -alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Drop partitioin 201805 -alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Optimize table. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add column. -alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Modify TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Truncate table. -truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference deleted file mode 100644 index 2bd6025fea2..00000000000 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.reference +++ /dev/null @@ -1,28 +0,0 @@ -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 -0 diff --git a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql b/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql deleted file mode 100644 index 4e491c301f2..00000000000 --- a/tests/queries/0_stateless/01233_check_part_meta_cache_replicated_in_atomic.sql +++ /dev/null @@ -1,128 +0,0 @@ --- Tags: no-fasttest, zookeeper, no-parallel --- Tag no-parallel: static zk path - --- Create table under database with engine ordinary. -set mutations_sync = 1; -set replication_alter_partitions_sync = 2; -DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC; -DROP DATABASE IF EXISTS test_metadata_cache; -CREATE DATABASE test_metadata_cache ENGINE = Atomic; -CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64) ENGINE ReplicatedMergeTree('/clickhouse/tables/test_metadata_cache/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert first batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert second batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Update some data. -alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - ---Delete some data. -alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Drop partitioin 201805 -alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Optimize table. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add column. -alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete column. -alter table test_metadata_cache.check_part_metadata_cache drop column v1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Modify TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v2, v3) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Truncate table. -truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Recreate table with projection. -drop table if exists test_metadata_cache.check_part_metadata_cache SYNC; -CREATE TABLE test_metadata_cache.check_part_metadata_cache ( p Date, k UInt64, v1 UInt64, v2 Int64, projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)) ENGINE ReplicatedMergeTree('/clickhouse/tables/test_metadata_cache/check_part_metadata_cache', '{replica}') PARTITION BY toYYYYMM(p) ORDER BY k TTL p + INTERVAL 15 YEAR settings use_metadata_cache = 1; - --- Insert first batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Insert second batch of data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Update some data. -alter table test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - ---Delete some data. -alter table test_metadata_cache.check_part_metadata_cache delete where k = 1; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - -alter table test_metadata_cache.check_part_metadata_cache delete where k = 8; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Delete some data. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Drop partitioin 201805 -alter table test_metadata_cache.check_part_metadata_cache drop partition 201805; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Optimize table. -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -optimize table test_metadata_cache.check_part_metadata_cache FINAL; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add column. -alter table test_metadata_cache.check_part_metadata_cache add column v3 UInt64; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Add TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Modify TTL info. -alter table test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR; -INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - --- Truncate table. -truncate table test_metadata_cache.check_part_metadata_cache; -with arrayJoin(checkPartMetadataCache('test_metadata_cache', 'check_part_metadata_cache')) as info select countIf(info.5 = 0); - From 51e544b447cf23c5dc0a4a01136dda3364c6a364 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Sat, 8 Jan 2022 10:11:21 +0800 Subject: [PATCH 0168/1647] fix stateless test --- .../0_stateless/01233_check_table_with_metadata_cache.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) mode change 100644 => 100755 tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh diff --git a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh old mode 100644 new mode 100755 index 017c7977745..e4b8e2f6792 --- a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh +++ b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Tags: no-fasttest +# Tag no-fasttest: setting use_metadata_cache=true is not supported in fasttest, because clickhouse binary in fasttest is build without RocksDB. CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh @@ -21,7 +23,7 @@ for table_engine in $table_engines; do table_engine_clause="" if [[ "$table_engine" == "ReplicatedMergeTree" ]]; then - table_engine_clause="ENGINE ReplicatedMergeTree('/clickhouse/tables/${CLICKHOUSE_TEST_ZOOKEEPER_PREFIX}/test_metadata_cache/check_part_metadata_cache', 'r1')" + table_engine_clause="ENGINE ReplicatedMergeTree('/clickhouse/tables/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX/test_metadata_cache/check_part_metadata_cache', 'r1')" elif [[ "$table_engine" == "MergeTree" ]]; then table_engine_clause="ENGINE MergeTree()" fi From 5bff268f84d029261a9e75bccf2111450299723e Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Mon, 10 Jan 2022 11:45:06 +0800 Subject: [PATCH 0169/1647] optimize code as advices --- src/Interpreters/Context.cpp | 6 +++++- src/Storages/MergeTree/IMergeTreeDataPart.h | 1 - src/Storages/MergeTree/IPartMetadataManager.h | 16 +++++++++++++++- src/Storages/MergeTree/MergeTreePartition.cpp | 8 -------- .../MergeTree/PartMetadataManagerWithCache.h | 14 ++++++++++++++ 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 0cb81ba4056..d51da66872f 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2291,7 +2291,11 @@ void Context::initializeMergeTreeMetadataCache(const String & dir, size_t size) rocksdb::Status status = rocksdb::DB::Open(options, dir, &db); if (status != rocksdb::Status::OK()) - throw Exception(ErrorCodes::SYSTEM_ERROR, "Fail to open rocksdb path at: {} status:{}", dir, status.ToString()); + throw Exception( + ErrorCodes::SYSTEM_ERROR, + "Fail to open rocksdb path at: {} status:{}. You can try to remove the cache (this will not affect any table data).", + dir, + status.ToString()); shared->merge_tree_metadata_cache = std::make_shared(db); } diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index 7e222607cb6..c8b693727da 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include diff --git a/src/Storages/MergeTree/IPartMetadataManager.h b/src/Storages/MergeTree/IPartMetadataManager.h index 17786c90761..876000de412 100644 --- a/src/Storages/MergeTree/IPartMetadataManager.h +++ b/src/Storages/MergeTree/IPartMetadataManager.h @@ -14,7 +14,12 @@ class SeekableReadBuffer; class IDisk; using DiskPtr = std::shared_ptr; - +/// Interface for managing metadata of merge tree part. +/// IPartMetadataManager has two implementations: +/// - PartMetadataManagerOrdinary: manage metadata from disk directly. deleteAll/assertAllDeleted/updateAll/check +/// are all empty implementations because they are not needed for PartMetadataManagerOrdinary(those operations +/// are done implicitly when removing or renaming part directory). +/// - PartMetadataManagerWithCache: manage metadata from RocksDB cache and disk. class IPartMetadataManager { public: @@ -24,16 +29,25 @@ public: virtual ~IPartMetadataManager() = default; + /// Read metadata content and return SeekableReadBuffer object. virtual std::unique_ptr read(const String & file_name) const = 0; + /// Return true if metadata exists in part. virtual bool exists(const String & file_name) const = 0; + /// Delete all metadatas in part. + /// If include_projection is true, also delete metadatas in projection parts. virtual void deleteAll(bool include_projection) = 0; + /// Assert that all metadatas in part are deleted. + /// If include_projection is true, also assert that all metadatas in projection parts are deleted. virtual void assertAllDeleted(bool include_projection) const = 0; + /// Update all metadatas in part. + /// If include_projection is true, also update metadatas in projection parts. virtual void updateAll(bool include_projection) = 0; + /// Check all metadatas in part. virtual std::unordered_map check() const = 0; protected: diff --git a/src/Storages/MergeTree/MergeTreePartition.cpp b/src/Storages/MergeTree/MergeTreePartition.cpp index 3d933303142..c16840ed1bf 100644 --- a/src/Storages/MergeTree/MergeTreePartition.cpp +++ b/src/Storages/MergeTree/MergeTreePartition.cpp @@ -160,14 +160,6 @@ namespace }; } -/* -static std::unique_ptr openForReading(const DiskPtr & disk, const String & path) -{ - size_t file_size = disk->getFileSize(path); - return disk->readFile(path, ReadSettings().adjustBufferSize(file_size), file_size); -} -*/ - String MergeTreePartition::getID(const MergeTreeData & storage) const { return getID(storage.getInMemoryMetadataPtr()->getPartitionKey().sample_block); diff --git a/src/Storages/MergeTree/PartMetadataManagerWithCache.h b/src/Storages/MergeTree/PartMetadataManagerWithCache.h index 8b1472f5457..06e7a85ba2b 100644 --- a/src/Storages/MergeTree/PartMetadataManagerWithCache.h +++ b/src/Storages/MergeTree/PartMetadataManagerWithCache.h @@ -9,6 +9,8 @@ namespace DB { +/// PartMetadataManagerWithCache stores metadatas of part in RocksDB as cache layer to speed up +/// loading process of merge tree table. class PartMetadataManagerWithCache : public IPartMetadataManager { public: @@ -16,22 +18,34 @@ public: ~PartMetadataManagerWithCache() override = default; + /// First read the metadata from RocksDB cache, then from disk. std::unique_ptr read(const String & file_name) const override; + /// First judge existence of the metadata in RocksDB cache, then in disk. bool exists(const String & file_name) const override; + /// Delete all metadatas in part from RocksDB cache. void deleteAll(bool include_projection) override; + /// Assert all metadatas in part from RocksDB cache are deleted. void assertAllDeleted(bool include_projection) const override; + /// Update all metadatas in part from RocksDB cache. + /// Need to be called after part directory is renamed. void updateAll(bool include_projection) override; + /// Check if all metadatas in part from RocksDB cache are up to date. std::unordered_map check() const override; private: + /// Get cache key from path of metadata file. + /// Format: :relative/full/path/of/metadata/file String getKeyFromFilePath(const String & file_path) const; + + /// Get metadata file path from cache key. String getFilePathFromKey(const String & key) const; + /// Get cache keys and checksums of corresponding metadata in a part(including projection parts) void getKeysAndCheckSums(Strings & keys, std::vector & checksums) const; From 3d409a4526b3ba7331e61460d320783c8371edb1 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Mon, 10 Jan 2022 12:20:39 +0800 Subject: [PATCH 0170/1647] add metrics MergeTreeMetadataCacheSize --- src/Interpreters/AsynchronousMetrics.cpp | 11 +++++++++++ src/Storages/MergeTree/MergeTreeMetadataCache.cpp | 7 +++++++ src/Storages/MergeTree/MergeTreeMetadataCache.h | 1 + 3 files changed, 19 insertions(+) diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index d1c5fbebbc7..451014755b8 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -573,6 +574,15 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti } } +#if USE_ROCKSDB + { + if (auto metadata_cache = getContext()->getMergeTreeMetadataCache()) + { + new_values["MergeTreeMetadataCacheSize"] = metadata_cache->getEstimateNumKeys(); + } + } +#endif + #if USE_EMBEDDED_COMPILER { if (auto * compiled_expression_cache = CompiledExpressionCacheFactory::instance().tryGetCache()) @@ -583,6 +593,7 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti } #endif + new_values["Uptime"] = getContext()->getUptimeSeconds(); /// Process process memory usage according to OS diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp index fa3825fd3be..5df9850b318 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp @@ -61,6 +61,13 @@ void MergeTreeMetadataCache::getByPrefix(const String & prefix, Strings & keys, ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheSeek); } +uint64_t MergeTreeMetadataCache::getEstimateNumKeys() const +{ + uint64_t keys = 0; + rocksdb->GetAggregatedIntProperty("rocksdb.estimate-num-keys", &keys); + return keys; +} + void MergeTreeMetadataCache::shutdown() { rocksdb->Close(); diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.h b/src/Storages/MergeTree/MergeTreeMetadataCache.h index 286c7ebb08e..a389d9a14ad 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.h +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.h @@ -28,6 +28,7 @@ public: Status del(const String & key); Status get(const String & key, String & value); void getByPrefix(const String & prefix, Strings & keys, Strings & values); + uint64_t getEstimateNumKeys() const; void shutdown(); private: From 7c7f45bce23a73052fddd1b83efa64e0cf253c3a Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Mon, 10 Jan 2022 12:32:38 +0800 Subject: [PATCH 0171/1647] fix comment --- src/Storages/MergeTree/PartMetadataManagerWithCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp b/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp index b088f63c0d0..3d68497f5b0 100644 --- a/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp +++ b/src/Storages/MergeTree/PartMetadataManagerWithCache.cpp @@ -201,7 +201,7 @@ std::unordered_map PartMetadataManagerWit if (part->isProjectionPart() || !part->isStoredOnDisk()) return {}; - /// the directory of projection part is under the directory of its parent part + /// The directory of projection part is under the directory of its parent part const auto filenames_without_checksums = part->getFileNamesWithoutChecksums(); std::unordered_map results; From 2cae8d552cf2ddc05a8db66359f4e014ddf95f4e Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Mon, 10 Jan 2022 19:17:12 +0800 Subject: [PATCH 0172/1647] remove useless code --- src/Storages/MergeTree/IMergeTreeDataPart.cpp | 7 ------- src/Storages/MergeTree/IMergeTreeDataPart.h | 1 - src/Storages/MergeTree/MergeTreeData.cpp | 9 --------- src/Storages/MergeTree/MergeTreeData.h | 2 -- 4 files changed, 19 deletions(-) diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index c8995309c4b..a2cceb540af 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -681,13 +681,6 @@ void IMergeTreeDataPart::appendFilesOfColumnsChecksumsIndexes(Strings & files, b } } -size_t IMergeTreeDataPart::fileNumberOfColumnsChecksumsIndexes() const -{ - Strings files; - appendFilesOfColumnsChecksumsIndexes(files, true); - return files.size(); -} - void IMergeTreeDataPart::loadProjections(bool require_columns_checksums, bool check_consistency) { auto metadata_snapshot = storage.getInMemoryMetadataPtr(); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.h b/src/Storages/MergeTree/IMergeTreeDataPart.h index c8b693727da..c0c61c6ca0a 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.h +++ b/src/Storages/MergeTree/IMergeTreeDataPart.h @@ -149,7 +149,6 @@ public: /// Load checksums from checksums.txt if exists. Load index if required. void loadColumnsChecksumsIndexes(bool require_columns_checksums, bool check_consistency); void appendFilesOfColumnsChecksumsIndexes(Strings & files, bool include_projection = false) const; - size_t fileNumberOfColumnsChecksumsIndexes() const; String getMarksFileExtension() const { return index_granularity_info.marks_file_extension; } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 7ee8e8f196b..a18b7125e54 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1341,15 +1341,6 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) LOG_DEBUG(log, "Loaded data parts ({} items)", data_parts_indexes.size()); } -size_t MergeTreeData::fileNumberOfDataParts(const DataPartStates & states) const -{ - size_t result = 0; - auto parts = getDataParts(states); - for (const auto & part : parts) - result += part->fileNumberOfColumnsChecksumsIndexes(); - return result; -} - /// Is the part directory old. /// True if its modification time and the modification time of all files inside it is less then threshold. /// (Only files on the first level of nesting are considered). diff --git a/src/Storages/MergeTree/MergeTreeData.h b/src/Storages/MergeTree/MergeTreeData.h index 80d0f0a9bff..cc6b1c78b74 100644 --- a/src/Storages/MergeTree/MergeTreeData.h +++ b/src/Storages/MergeTree/MergeTreeData.h @@ -427,8 +427,6 @@ public: /// Load the set of data parts from disk. Call once - immediately after the object is created. void loadDataParts(bool skip_sanity_checks); - size_t fileNumberOfDataParts(const DataPartStates & states) const; - String getLogName() const { return log_name; } Int64 getMaxBlockNumber() const; From 7e227040cb0e25f6ac8eaba688e43708080123f0 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Mon, 10 Jan 2022 19:52:54 +0800 Subject: [PATCH 0173/1647] optimize getMergeTreeMetadataCache --- src/Interpreters/Context.cpp | 4 ++++ src/Storages/MergeTree/MergeTreeData.cpp | 5 +---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index d51da66872f..d8bdb024462 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2029,6 +2029,10 @@ zkutil::ZooKeeperPtr Context::getAuxiliaryZooKeeper(const String & name) const #if USE_ROCKSDB MergeTreeMetadataCachePtr Context::getMergeTreeMetadataCache() const { + if (!shared->merge_tree_metadata_cache) + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Merge tree metadata cache is not initialized, please add config merge_tree_metadata_cache in config.xml and restart"); return shared->merge_tree_metadata_cache; } #endif diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index a18b7125e54..1168335647f 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -319,11 +319,8 @@ MergeTreeData::MergeTreeData( LOG_WARNING(log, "{} Settings 'min_rows_for_wide_part', 'min_bytes_for_wide_part', " "'min_rows_for_compact_part' and 'min_bytes_for_compact_part' will be ignored.", reason); +#if !USE_ROCKSDB if (use_metadata_cache) -#if USE_ROCKSDB - if (!getContext()->getMergeTreeMetadataCache()) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't use merge tree metadata cache if not config in config.xml"); -#else throw Exception(ErrorCodes::LOGICAL_ERROR, "Can't use merge tree metadata cache if clickhouse was compiled without rocksdb"); #endif From 158fbaaa29283717ade4f381efe826b809e0644c Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Mon, 10 Jan 2022 20:07:01 +0300 Subject: [PATCH 0174/1647] fix --- src/IO/ReadHelpers.cpp | 1 + src/Storages/StorageMergeTree.cpp | 3 +-- .../0_stateless/01528_clickhouse_local_prepare_parts.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/IO/ReadHelpers.cpp b/src/IO/ReadHelpers.cpp index 610e6e4b9da..3bf31f11531 100644 --- a/src/IO/ReadHelpers.cpp +++ b/src/IO/ReadHelpers.cpp @@ -27,6 +27,7 @@ namespace ErrorCodes extern const int CANNOT_PARSE_DATETIME; extern const int CANNOT_PARSE_DATE; extern const int INCORRECT_DATA; + extern const int ATTEMPT_TO_READ_AFTER_EOF; } template diff --git a/src/Storages/StorageMergeTree.cpp b/src/Storages/StorageMergeTree.cpp index d5215442a13..30d95054d3d 100644 --- a/src/Storages/StorageMergeTree.cpp +++ b/src/Storages/StorageMergeTree.cpp @@ -1201,7 +1201,6 @@ size_t StorageMergeTree::clearOldMutations(bool truncate) auto end_it = current_mutations_by_version.end(); auto begin_it = current_mutations_by_version.begin(); - size_t to_delete_count = std::distance(begin_it, end_it); if (std::optional min_version = getMinPartDataVersion()) end_it = current_mutations_by_version.upper_bound(*min_version); @@ -1228,7 +1227,7 @@ size_t StorageMergeTree::clearOldMutations(bool truncate) if (done_count <= finished_mutations_to_keep) return 0; - to_delete_count = done_count - finished_mutations_to_keep; + size_t to_delete_count = done_count - finished_mutations_to_keep; auto it = begin_it; for (size_t i = 0; i < to_delete_count; ++i) diff --git a/tests/queries/0_stateless/01528_clickhouse_local_prepare_parts.sh b/tests/queries/0_stateless/01528_clickhouse_local_prepare_parts.sh index e33f75b2b06..48edf41edd7 100755 --- a/tests/queries/0_stateless/01528_clickhouse_local_prepare_parts.sh +++ b/tests/queries/0_stateless/01528_clickhouse_local_prepare_parts.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-fasttest +# Tags: disabled CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh From 90a92dd14a7bae2b780bfad82dd327bc3f8a5bf7 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 11 Jan 2022 11:09:52 +0800 Subject: [PATCH 0175/1647] fix stateless test --- src/Interpreters/AsynchronousMetrics.cpp | 2 +- src/Interpreters/Context.cpp | 8 +++++++- src/Interpreters/Context.h | 1 + tests/queries/0_stateless/01161_all_system_tables.sh | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 451014755b8..4b9bcd42e51 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -576,7 +576,7 @@ void AsynchronousMetrics::update(std::chrono::system_clock::time_point update_ti #if USE_ROCKSDB { - if (auto metadata_cache = getContext()->getMergeTreeMetadataCache()) + if (auto metadata_cache = getContext()->tryGetMergeTreeMetadataCache()) { new_values["MergeTreeMetadataCacheSize"] = metadata_cache->getEstimateNumKeys(); } diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index d8bdb024462..925f3048262 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2029,10 +2029,16 @@ zkutil::ZooKeeperPtr Context::getAuxiliaryZooKeeper(const String & name) const #if USE_ROCKSDB MergeTreeMetadataCachePtr Context::getMergeTreeMetadataCache() const { - if (!shared->merge_tree_metadata_cache) + auto cache = tryGetMergeTreeMetadataCache(); + if (!cache) throw Exception( ErrorCodes::LOGICAL_ERROR, "Merge tree metadata cache is not initialized, please add config merge_tree_metadata_cache in config.xml and restart"); + return cache; +} + +MergeTreeMetadataCachePtr Context::tryGetMergeTreeMetadataCache() const +{ return shared->merge_tree_metadata_cache; } #endif diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index b8616e8b634..1d618ba9ac6 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -686,6 +686,7 @@ public: #if USE_ROCKSDB MergeTreeMetadataCachePtr getMergeTreeMetadataCache() const; + MergeTreeMetadataCachePtr tryGetMergeTreeMetadataCache() const; #endif #if USE_NURAFT diff --git a/tests/queries/0_stateless/01161_all_system_tables.sh b/tests/queries/0_stateless/01161_all_system_tables.sh index 9b19cc97d16..1a653763ad3 100755 --- a/tests/queries/0_stateless/01161_all_system_tables.sh +++ b/tests/queries/0_stateless/01161_all_system_tables.sh @@ -12,7 +12,7 @@ function run_selects() { thread_num=$1 readarray -t tables_arr < <(${CLICKHOUSE_CLIENT} -q "SELECT database || '.' || name FROM system.tables - WHERE database in ('system', 'information_schema', 'INFORMATION_SCHEMA') and name!='zookeeper' + WHERE database in ('system', 'information_schema', 'INFORMATION_SCHEMA') and name!='zookeeper' and name!='merge_tree_metadata_cache' AND sipHash64(name || toString($RAND)) % $THREADS = $thread_num") for t in "${tables_arr[@]}" From d31871d253f70ee79b3cae3e382b1f7ec02aee10 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 11 Jan 2022 15:17:00 +0800 Subject: [PATCH 0176/1647] commit again --- src/Common/tests/gtest_global_context.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Common/tests/gtest_global_context.h b/src/Common/tests/gtest_global_context.h index 9bd7c2490d6..1b7eacf6f11 100644 --- a/src/Common/tests/gtest_global_context.h +++ b/src/Common/tests/gtest_global_context.h @@ -18,8 +18,11 @@ struct ContextHolder ContextHolder(ContextHolder &&) = default; }; -inline const ContextHolder & getContext() +const ContextHolder & getContext() { - static ContextHolder holder; - return holder; + static ContextHolder * holder; + static std::once_flag once; + std::call_once(once, [&]() { holder = new ContextHolder(); }); + return *holder; } + From 2b8853db279bc1b55a10ff21b2b07a3a08fc1276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=89=AC?= <654010905@qq.com> Date: Tue, 11 Jan 2022 20:30:40 -0600 Subject: [PATCH 0177/1647] Update tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh Co-authored-by: Azat Khuzhin --- .../0_stateless/01233_check_table_with_metadata_cache.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh index aedecb0328f..efa94e9775a 100755 --- a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh +++ b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # Tags: no-fasttest # Tag no-fasttest: setting use_metadata_cache=true is not supported in fasttest, because clickhouse binary in fasttest is build without RocksDB. - +# To suppress Warning messages from CHECK TABLE +CLICKHOUSE_CLIENT_SERVER_LOGS_LEVEL=error CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh . "$CURDIR"/../shell_config.sh From 6567cd7abc185d0496fd850c5d193efeb90d2109 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Thu, 13 Jan 2022 15:27:41 +0800 Subject: [PATCH 0178/1647] fix all the stateless test --- src/Common/tests/gtest_global_context.cpp | 17 ++++++++--- src/Common/tests/gtest_global_context.h | 11 +++++++ src/Interpreters/Context.cpp | 21 +------------- .../MergeTree/MergeTreeMetadataCache.cpp | 29 +++++++++++++++++++ .../MergeTree/MergeTreeMetadataCache.h | 2 ++ .../tests/gtest_merge_tree_metadata_cache.cpp | 10 +++++-- 6 files changed, 63 insertions(+), 27 deletions(-) rename src/{Interpreters => Storages/MergeTree}/tests/gtest_merge_tree_metadata_cache.cpp (90%) diff --git a/src/Common/tests/gtest_global_context.cpp b/src/Common/tests/gtest_global_context.cpp index 9b51e1bb99c..ec86c953c5b 100644 --- a/src/Common/tests/gtest_global_context.cpp +++ b/src/Common/tests/gtest_global_context.cpp @@ -2,8 +2,17 @@ const ContextHolder & getContext() { - static ContextHolder * holder; - static std::once_flag once; - std::call_once(once, [&]() { holder = new ContextHolder(); }); - return *holder; + return getMutableContext(); +} + +ContextHolder & getMutableContext() +{ + static ContextHolder holder; + return holder; +} + +void destroyContext() +{ + auto & holder = getMutableContext(); + return holder.destroy(); } diff --git a/src/Common/tests/gtest_global_context.h b/src/Common/tests/gtest_global_context.h index 7756be7ce9b..f846a0dbe4f 100644 --- a/src/Common/tests/gtest_global_context.h +++ b/src/Common/tests/gtest_global_context.h @@ -16,6 +16,17 @@ struct ContextHolder } ContextHolder(ContextHolder &&) = default; + + void destroy() + { + context->shutdown(); + context.reset(); + shared_context.reset(); + } }; const ContextHolder & getContext(); + +ContextHolder & getMutableContext(); + +void destroyContext(); diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 925f3048262..111ffc9b3f8 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -131,7 +131,6 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; extern const int INVALID_SETTING_VALUE; extern const int UNKNOWN_READ_METHOD; - extern const int SYSTEM_ERROR; } @@ -2289,25 +2288,7 @@ void Context::initializeTraceCollector() #if USE_ROCKSDB void Context::initializeMergeTreeMetadataCache(const String & dir, size_t size) { - rocksdb::Options options; - rocksdb::BlockBasedTableOptions table_options; - rocksdb::DB * db; - - options.create_if_missing = true; - options.statistics = rocksdb::CreateDBStatistics(); - auto cache = rocksdb::NewLRUCache(size); - table_options.block_cache = cache; - options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options)); - rocksdb::Status status = rocksdb::DB::Open(options, dir, &db); - - if (status != rocksdb::Status::OK()) - throw Exception( - ErrorCodes::SYSTEM_ERROR, - "Fail to open rocksdb path at: {} status:{}. You can try to remove the cache (this will not affect any table data).", - dir, - status.ToString()); - - shared->merge_tree_metadata_cache = std::make_shared(db); + shared->merge_tree_metadata_cache = MergeTreeMetadataCache::create(dir, size); } #endif diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp index 5df9850b318..555838204ff 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp @@ -14,6 +14,33 @@ namespace ProfileEvents namespace DB { +namespace ErrorCodes +{ + extern const int SYSTEM_ERROR; +} + +std::unique_ptr MergeTreeMetadataCache::create(const String & dir, size_t size) +{ + assert(size != 0); + rocksdb::Options options; + rocksdb::BlockBasedTableOptions table_options; + rocksdb::DB * db; + + options.create_if_missing = true; + options.statistics = rocksdb::CreateDBStatistics(); + auto cache = rocksdb::NewLRUCache(size); + table_options.block_cache = cache; + options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options)); + rocksdb::Status status = rocksdb::DB::Open(options, dir, &db); + if (status != rocksdb::Status::OK()) + throw Exception( + ErrorCodes::SYSTEM_ERROR, + "Fail to open rocksdb path at: {} status:{}. You can try to remove the cache (this will not affect any table data).", + dir, + status.ToString()); + return std::make_unique(db); +} + MergeTreeMetadataCache::Status MergeTreeMetadataCache::put(const String & key, const String & value) { auto options = rocksdb::WriteOptions(); @@ -59,6 +86,7 @@ void MergeTreeMetadataCache::getByPrefix(const String & prefix, Strings & keys, } LOG_TRACE(log, "Seek with prefix:{} from MergeTreeMetadataCache items:{}", prefix, keys.size()); ProfileEvents::increment(ProfileEvents::MergeTreeMetadataCacheSeek); + delete it; } uint64_t MergeTreeMetadataCache::getEstimateNumKeys() const @@ -71,6 +99,7 @@ uint64_t MergeTreeMetadataCache::getEstimateNumKeys() const void MergeTreeMetadataCache::shutdown() { rocksdb->Close(); + rocksdb.reset(); } } diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.h b/src/Storages/MergeTree/MergeTreeMetadataCache.h index a389d9a14ad..8e40eaf7310 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.h +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.h @@ -21,6 +21,8 @@ public: assert(rocksdb); } + static std::unique_ptr create(const String & dir, size_t size); + MergeTreeMetadataCache(const MergeTreeMetadataCache &) = delete; MergeTreeMetadataCache & operator=(const MergeTreeMetadataCache &) = delete; diff --git a/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp b/src/Storages/MergeTree/tests/gtest_merge_tree_metadata_cache.cpp similarity index 90% rename from src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp rename to src/Storages/MergeTree/tests/gtest_merge_tree_metadata_cache.cpp index 839c54c63b2..f513d1b2553 100644 --- a/src/Interpreters/tests/gtest_merge_tree_metadata_cache.cpp +++ b/src/Storages/MergeTree/tests/gtest_merge_tree_metadata_cache.cpp @@ -15,9 +15,13 @@ class MergeTreeMetadataCacheTest : public ::testing::Test public: void SetUp() override { - const auto & context_holder = getContext(); - context_holder.context->initializeMergeTreeMetadataCache("./db/", 256 << 20); - cache = context_holder.context->getMergeTreeMetadataCache(); + cache = MergeTreeMetadataCache::create("./db/", 268435456); + } + + void TearDown() override + { + cache->shutdown(); + cache.reset(); } MergeTreeMetadataCachePtr cache; From e1a41fc69413a1dad8c398981bcb537201eb9f8a Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 14 Jan 2022 17:03:00 +0300 Subject: [PATCH 0179/1647] add system log for introspection --- docker/test/stateless/run.sh | 2 +- src/Interpreters/Context.cpp | 11 +++ src/Interpreters/Context.h | 2 + src/Interpreters/InterpreterSystemQuery.cpp | 4 +- src/Interpreters/MergeTreeTransaction.cpp | 21 ++--- src/Interpreters/SystemLog.cpp | 5 ++ src/Interpreters/SystemLog.h | 3 + src/Interpreters/TransactionLog.cpp | 45 ++++++++-- src/Interpreters/TransactionLog.h | 2 + .../TransactionVersionMetadata.cpp | 72 +++++++++++---- src/Interpreters/TransactionVersionMetadata.h | 26 +++++- src/Interpreters/TransactionsInfoLog.cpp | 88 +++++++++++++++++++ src/Interpreters/TransactionsInfoLog.h | 54 ++++++++++++ src/Storages/MergeTree/IMergeTreeDataPart.cpp | 6 +- src/Storages/MergeTree/MergeTreeData.cpp | 4 +- tests/config/config.d/transactions.xml | 7 ++ .../configs/no_system_log.xml | 1 + .../01172_transaction_counters.reference | 22 +++++ .../01172_transaction_counters.sql | 11 +++ 19 files changed, 342 insertions(+), 44 deletions(-) create mode 100644 src/Interpreters/TransactionsInfoLog.cpp create mode 100644 src/Interpreters/TransactionsInfoLog.h diff --git a/docker/test/stateless/run.sh b/docker/test/stateless/run.sh index d6d9f189e89..307d9ccea7c 100755 --- a/docker/test/stateless/run.sh +++ b/docker/test/stateless/run.sh @@ -159,7 +159,7 @@ tar -chf /test_output/coordination.tar /var/lib/clickhouse/coordination ||: # Replace the engine with Ordinary to avoid extra symlinks stuff in artifacts. # (so that clickhouse-local --path can read it w/o extra care). sed -i -e "s/ATTACH DATABASE _ UUID '[^']*'/ATTACH DATABASE system/" -e "s/Atomic/Ordinary/" /var/lib/clickhouse/metadata/system.sql -for table in text_log query_log zookeeper_log trace_log; do +for table in text_log query_log zookeeper_log trace_log transactions_info_log; do sed -i "s/ATTACH TABLE _ UUID '[^']*'/ATTACH TABLE $table/" /var/lib/clickhouse/metadata/system/${table}.sql tar -chf /test_output/${table}_dump.tar /var/lib/clickhouse/metadata/system.sql /var/lib/clickhouse/metadata/system/${table}.sql /var/lib/clickhouse/data/system/${table} ||: done diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index c24187ae5f7..3a661f26a36 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -2383,6 +2383,17 @@ std::shared_ptr Context::getZooKeeperLog() const } +std::shared_ptr Context::getTransactionsInfoLog() const +{ + auto lock = getLock(); + + if (!shared->system_logs) + return {}; + + return shared->system_logs->transactions_info_log; +} + + CompressionCodecPtr Context::chooseCompressionCodec(size_t part_size, double part_size_ratio) const { auto lock = getLock(); diff --git a/src/Interpreters/Context.h b/src/Interpreters/Context.h index cd503aef7c1..295d35ce6cf 100644 --- a/src/Interpreters/Context.h +++ b/src/Interpreters/Context.h @@ -80,6 +80,7 @@ class AsynchronousMetricLog; class OpenTelemetrySpanLog; class ZooKeeperLog; class SessionLog; +class TransactionsInfoLog; struct MergeTreeSettings; class StorageS3Settings; class IDatabase; @@ -790,6 +791,7 @@ public: std::shared_ptr getOpenTelemetrySpanLog() const; std::shared_ptr getZooKeeperLog() const; std::shared_ptr getSessionLog() const; + std::shared_ptr getTransactionsInfoLog() const; /// Returns an object used to log operations with parts if it possible. /// Provide table name to make required checks. diff --git a/src/Interpreters/InterpreterSystemQuery.cpp b/src/Interpreters/InterpreterSystemQuery.cpp index 123ff6ba2ca..e3b5478bc76 100644 --- a/src/Interpreters/InterpreterSystemQuery.cpp +++ b/src/Interpreters/InterpreterSystemQuery.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -446,7 +447,8 @@ BlockIO InterpreterSystemQuery::execute() [&] { if (auto opentelemetry_span_log = getContext()->getOpenTelemetrySpanLog()) opentelemetry_span_log->flush(true); }, [&] { if (auto query_views_log = getContext()->getQueryViewsLog()) query_views_log->flush(true); }, [&] { if (auto zookeeper_log = getContext()->getZooKeeperLog()) zookeeper_log->flush(true); }, - [&] { if (auto session_log = getContext()->getSessionLog()) session_log->flush(true); } + [&] { if (auto session_log = getContext()->getSessionLog()) session_log->flush(true); }, + [&] { if (auto transactions_info_log = getContext()->getTransactionsInfoLog()) transactions_info_log->flush(true); } ); break; } diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index 82c2429210a..ae221e4b1a0 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -33,7 +33,7 @@ void MergeTreeTransaction::addNewPart(const StoragePtr & storage, const DataPart { TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; - new_part->versions.setMinTID(tid); + new_part->versions.setMinTID(tid, TransactionInfoContext{storage->getStorageID(), new_part->name}); if (txn) txn->addNewPart(storage, new_part); } @@ -41,10 +41,8 @@ void MergeTreeTransaction::addNewPart(const StoragePtr & storage, const DataPart void MergeTreeTransaction::removeOldPart(const StoragePtr & storage, const DataPartPtr & part_to_remove, MergeTreeTransaction * txn) { TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; - String error_context = fmt::format("Table: {}, part name: {}", - part_to_remove->storage.getStorageID().getNameForLogs(), - part_to_remove->name); - part_to_remove->versions.lockMaxTID(tid, error_context); + TransactionInfoContext context{storage->getStorageID(), part_to_remove->name}; + part_to_remove->versions.lockMaxTID(tid, context); if (txn) txn->removeOldPart(storage, part_to_remove); } @@ -53,17 +51,16 @@ void MergeTreeTransaction::addNewPartAndRemoveCovered(const StoragePtr & storage { TransactionID tid = txn ? txn->tid : Tx::PrehistoricTID; - new_part->versions.setMinTID(tid); + TransactionInfoContext context{storage->getStorageID(), new_part->name}; + new_part->versions.setMinTID(tid, context); if (txn) txn->addNewPart(storage, new_part); - String error_context = fmt::format("Table: {}, covering part name: {}", - new_part->storage.getStorageID().getNameForLogs(), - new_part->name); - error_context += ", part_name: {}"; + context.covering_part = std::move(context.part_name); for (const auto & covered : covered_parts) { - covered->versions.lockMaxTID(tid, fmt::format(error_context, covered->name)); + context.part_name = covered->name; + covered->versions.lockMaxTID(tid, context); if (txn) txn->removeOldPart(storage, covered); } @@ -152,7 +149,7 @@ bool MergeTreeTransaction::rollback() noexcept part->versions.mincsn.store(Tx::RolledBackCSN); for (const auto & part : removing_parts) - part->versions.unlockMaxTID(tid); + part->versions.unlockMaxTID(tid, TransactionInfoContext{part->storage.getStorageID(), part->name}); /// FIXME const_cast for (const auto & part : creating_parts) diff --git a/src/Interpreters/SystemLog.cpp b/src/Interpreters/SystemLog.cpp index fc2a5b620e2..bbcc99923ea 100644 --- a/src/Interpreters/SystemLog.cpp +++ b/src/Interpreters/SystemLog.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -130,6 +131,8 @@ SystemLogs::SystemLogs(ContextPtr global_context, const Poco::Util::AbstractConf query_views_log = createSystemLog(global_context, "system", "query_views_log", config, "query_views_log"); zookeeper_log = createSystemLog(global_context, "system", "zookeeper_log", config, "zookeeper_log"); session_log = createSystemLog(global_context, "system", "session_log", config, "session_log"); + transactions_info_log = createSystemLog( + global_context, "system", "transactions_info_log", config, "transactions_info_log"); if (query_log) logs.emplace_back(query_log.get()); @@ -155,6 +158,8 @@ SystemLogs::SystemLogs(ContextPtr global_context, const Poco::Util::AbstractConf logs.emplace_back(zookeeper_log.get()); if (session_log) logs.emplace_back(session_log.get()); + if (transactions_info_log) + logs.emplace_back(transactions_info_log.get()); try { diff --git a/src/Interpreters/SystemLog.h b/src/Interpreters/SystemLog.h index d6342e3973e..865b48e5913 100644 --- a/src/Interpreters/SystemLog.h +++ b/src/Interpreters/SystemLog.h @@ -78,6 +78,7 @@ class OpenTelemetrySpanLog; class QueryViewsLog; class ZooKeeperLog; class SessionLog; +class TransactionsInfoLog; class ISystemLog @@ -125,6 +126,8 @@ struct SystemLogs std::shared_ptr zookeeper_log; /// Login, LogOut and Login failure events std::shared_ptr session_log; + /// Events related to transactions + std::shared_ptr transactions_info_log; std::vector logs; }; diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 99a4c44fb5c..11b119c86c1 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -14,13 +15,34 @@ namespace DB namespace ErrorCodes { -extern const int LOGICAL_ERROR; + extern const int LOGICAL_ERROR; +} + +static void tryWriteEventToSystemLog(Poco::Logger * log, ContextPtr context, + TransactionsInfoLogElement::Type type, const TransactionID & tid, CSN csn = Tx::UnknownCSN) +try +{ + auto system_log = context->getTransactionsInfoLog(); + if (!system_log) + return; + + TransactionsInfoLogElement elem; + elem.type = type; + elem.tid = tid; + elem.csn = csn; + elem.fillCommonFields(nullptr); + system_log->add(elem); +} +catch (...) +{ + tryLogCurrentException(log); } TransactionLog & TransactionLog::instance() { - static TransactionLog inst; - return inst; + /// Use unique_ptr to avoid races on initialization retries if exceptions was thrown from ctor + static std::unique_ptr inst = std::make_unique(); + return *inst; } TransactionLog::TransactionLog() @@ -214,7 +236,10 @@ MergeTreeTransactionPtr TransactionLog::beginTransaction() throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} exists", txn->tid.getHash(), txn->tid); txn->snapshot_in_use_it = snapshots_in_use.insert(snapshots_in_use.end(), snapshot); } - LOG_TRACE(log, "Beginning transaction {} ({})", txn->tid, txn->tid.getHash()); + + LOG_TEST(log, "Beginning transaction {} ({})", txn->tid, txn->tid.getHash()); + tryWriteEventToSystemLog(log, global_context, TransactionsInfoLogElement::BEGIN, txn->tid); + return txn; } @@ -226,8 +251,9 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) /// TODO Transactions: reset local_tid_counter if (txn->isReadOnly()) { - LOG_TRACE(log, "Closing readonly transaction {}", txn->tid); + LOG_TEST(log, "Closing readonly transaction {}", txn->tid); new_csn = txn->snapshot; + tryWriteEventToSystemLog(log, global_context, TransactionsInfoLogElement::COMMIT, txn->tid, new_csn); } else { @@ -236,7 +262,9 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) /// TODO support batching String path_created = zookeeper->create(zookeeper_path + "/csn-", writeTID(txn->tid), zkutil::CreateMode::PersistentSequential); /// Commit point new_csn = parseCSN(path_created.substr(zookeeper_path.size() + 1)); + LOG_INFO(log, "Transaction {} committed with CSN={}", txn->tid, new_csn); + tryWriteEventToSystemLog(log, global_context, TransactionsInfoLogElement::COMMIT, txn->tid, new_csn); /// Wait for committed changes to become actually visible, so the next transaction will see changes /// TODO it's optional, add a setting for this @@ -257,13 +285,16 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) throw Exception(ErrorCodes::LOGICAL_ERROR, "I's a bug: TID {} {} doesn't exist", txn->tid.getHash(), txn->tid); snapshots_in_use.erase(txn->snapshot_in_use_it); } + return new_csn; } void TransactionLog::rollbackTransaction(const MergeTreeTransactionPtr & txn) noexcept { LOG_TRACE(log, "Rolling back transaction {}", txn->tid); - if (txn->rollback()) + if (!txn->rollback()) + return; + { std::lock_guard lock{running_list_mutex}; bool removed = running_list.erase(txn->tid.getHash()); @@ -271,6 +302,8 @@ void TransactionLog::rollbackTransaction(const MergeTreeTransactionPtr & txn) no abort(); snapshots_in_use.erase(txn->snapshot_in_use_it); } + + tryWriteEventToSystemLog(log, global_context, TransactionsInfoLogElement::ROLLBACK, txn->tid); } MergeTreeTransactionPtr TransactionLog::tryGetRunningTransaction(const TIDHash & tid) diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index 3b9660d46eb..90b36793935 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -10,6 +10,8 @@ namespace DB { +class TransactionsInfoLog; +using TransactionsInfoLogPtr = std::shared_ptr; using ZooKeeperPtr = std::shared_ptr; class TransactionLog final : private boost::noncopyable diff --git a/src/Interpreters/TransactionVersionMetadata.cpp b/src/Interpreters/TransactionVersionMetadata.cpp index 755ad161d24..1e2b0596d93 100644 --- a/src/Interpreters/TransactionVersionMetadata.cpp +++ b/src/Interpreters/TransactionVersionMetadata.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -6,8 +7,7 @@ #include #include #include - -//#include +#include namespace DB { @@ -19,6 +19,32 @@ namespace ErrorCodes extern const int CANNOT_PARSE_TEXT; } +static void tryWriteEventToSystemLog(Poco::Logger * log, + TransactionsInfoLogElement::Type type, const TransactionID & tid, + const TransactionInfoContext & context) +try +{ + auto system_log = Context::getGlobalContextInstance()->getTransactionsInfoLog(); + if (!system_log) + return; + + TransactionsInfoLogElement elem; + elem.type = type; + elem.tid = tid; + elem.fillCommonFields(&context); + system_log->add(elem); +} +catch (...) +{ + tryLogCurrentException(log); +} + +VersionMetadata::VersionMetadata() +{ + /// It would be better to make it static, but static loggers do not work for some reason (initialization order?) + log = &Poco::Logger::get("VersionMetadata"); +} + /// It can be used for introspection purposes only TransactionID VersionMetadata::getMaxTID() const { @@ -40,21 +66,26 @@ TransactionID VersionMetadata::getMaxTID() const return Tx::EmptyTID; } -void VersionMetadata::lockMaxTID(const TransactionID & tid, const String & error_context) +void VersionMetadata::lockMaxTID(const TransactionID & tid, const TransactionInfoContext & context) { - //LOG_TRACE(&Poco::Logger::get("WTF"), "Trying to lock maxtid by {}: {}\n{}", tid, error_context, StackTrace().toString()); + LOG_TEST(log, "Trying to lock maxtid by {}, table: {}, part: {}", tid, context.table.getNameForLogs(), context.part_name); TIDHash locked_by = 0; - if (tryLockMaxTID(tid, &locked_by)) + if (tryLockMaxTID(tid, context, &locked_by)) return; + String part_desc; + if (context.covering_part.empty()) + part_desc = context.part_name; + else + part_desc = fmt::format("{} (covered by {})", context.part_name, context.covering_part); throw Exception(ErrorCodes::SERIALIZATION_ERROR, "Serialization error: " - "Transaction {} tried to remove data part, " - "but it's locked ({}) by another transaction {} which is currently removing this part. {}", - tid, locked_by, getMaxTID(), error_context); + "Transaction {} tried to remove data part {} from {}, " + "but it's locked by another transaction (TID: {}, TIDH: {}) which is currently removing this part.", + tid, part_desc, context.table.getNameForLogs(), getMaxTID(), locked_by); } -bool VersionMetadata::tryLockMaxTID(const TransactionID & tid, TIDHash * locked_by_id) +bool VersionMetadata::tryLockMaxTID(const TransactionID & tid, const TransactionInfoContext & context, TIDHash * locked_by_id) { assert(!tid.isEmpty()); TIDHash max_lock_value = tid.getHash(); @@ -66,6 +97,7 @@ bool VersionMetadata::tryLockMaxTID(const TransactionID & tid, TIDHash * locked_ { /// Don't need to lock part for queries without transaction //FIXME Transactions: why is it possible? + LOG_TEST(log, "Assuming maxtid is locked by {}, table: {}, part: {}", tid, context.table.getNameForLogs(), context.part_name); return true; } @@ -75,12 +107,13 @@ bool VersionMetadata::tryLockMaxTID(const TransactionID & tid, TIDHash * locked_ } maxtid = tid; + tryWriteEventToSystemLog(log, TransactionsInfoLogElement::LOCK_PART, tid, context); return true; } -void VersionMetadata::unlockMaxTID(const TransactionID & tid) +void VersionMetadata::unlockMaxTID(const TransactionID & tid, const TransactionInfoContext & context) { - //LOG_TRACE(&Poco::Logger::get("WTF"), "Unlocking maxtid by {}", tid); + LOG_TEST(log, "Unlocking maxtid by {}", tid); assert(!tid.isEmpty()); TIDHash max_lock_value = tid.getHash(); TIDHash locked_by = maxtid_lock.load(); @@ -99,6 +132,8 @@ void VersionMetadata::unlockMaxTID(const TransactionID & tid) bool unlocked = maxtid_lock.compare_exchange_strong(locked_by, 0); if (!unlocked) throw_cannot_unlock(); + + tryWriteEventToSystemLog(log, TransactionsInfoLogElement::UNLOCK_PART, tid, context); } bool VersionMetadata::isMaxTIDLocked() const @@ -106,12 +141,14 @@ bool VersionMetadata::isMaxTIDLocked() const return maxtid_lock.load() != 0; } -void VersionMetadata::setMinTID(const TransactionID & tid) +void VersionMetadata::setMinTID(const TransactionID & tid, const TransactionInfoContext & context) { /// TODO Transactions: initialize it in constructor on part creation and remove this method /// FIXME ReplicatedMergeTreeBlockOutputStream may add one part multiple times assert(mintid.isEmpty() || mintid == tid); const_cast(mintid) = tid; + + tryWriteEventToSystemLog(log, TransactionsInfoLogElement::ADD_PART, tid, context); } bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) @@ -121,13 +158,12 @@ bool VersionMetadata::isVisible(const MergeTreeTransaction & txn) bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current_tid) { - //Poco::Logger * log = &Poco::Logger::get("WTF"); assert(!mintid.isEmpty()); CSN min = mincsn.load(std::memory_order_relaxed); TIDHash max_lock = maxtid_lock.load(std::memory_order_relaxed); CSN max = maxcsn.load(std::memory_order_relaxed); - //LOG_TRACE(log, "Checking if mintid {} mincsn {} maxtidhash {} maxcsn {} visible for {} {}", mintid, min, max_lock, max, snapshot_version, current_tid); + //LOG_TEST(log, "Checking if mintid {} mincsn {} maxtidhash {} maxcsn {} visible for {} {}", mintid, min, max_lock, max, snapshot_version, current_tid); [[maybe_unused]] bool had_mincsn = min; [[maybe_unused]] bool had_maxtid = max_lock; @@ -184,7 +220,6 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current /// But for long-running writing transactions we will always do /// CNS lookup and get 0 (UnknownCSN) until the transaction is committer/rolled back. min = TransactionLog::instance().getCSN(mintid); - //LOG_TRACE(log, "Got min {}", min); if (!min) return false; /// Part creation is not committed yet @@ -195,7 +230,6 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current if (max_lock) { max = TransactionLog::instance().getCSN(max_lock); - //LOG_TRACE(log, "Got ax {}", max); if (max) maxcsn.store(max, std::memory_order_relaxed); } @@ -206,11 +240,13 @@ bool VersionMetadata::isVisible(Snapshot snapshot_version, TransactionID current bool VersionMetadata::canBeRemoved(Snapshot oldest_snapshot_version) { CSN min = mincsn.load(std::memory_order_relaxed); + /// We can safely remove part if its creation was rolled back if (min == Tx::RolledBackCSN) return true; if (!min) { + /// Cannot remove part if its creation not committed yet min = TransactionLog::instance().getCSN(mintid); if (min) mincsn.store(min, std::memory_order_relaxed); @@ -218,16 +254,19 @@ bool VersionMetadata::canBeRemoved(Snapshot oldest_snapshot_version) return false; } + /// Part is probably visible for some transactions (part is too new or the oldest snapshot is too old) if (oldest_snapshot_version < min) return false; TIDHash max_lock = maxtid_lock.load(std::memory_order_relaxed); + /// Part is active if (!max_lock) return false; CSN max = maxcsn.load(std::memory_order_relaxed); if (!max) { + /// Part removal is not committed yet max = TransactionLog::instance().getCSN(max_lock); if (max) maxcsn.store(max, std::memory_order_relaxed); @@ -235,6 +274,7 @@ bool VersionMetadata::canBeRemoved(Snapshot oldest_snapshot_version) return false; } + /// We can safely remove part if all running transactions were started after part removal was committed return max <= oldest_snapshot_version; } diff --git a/src/Interpreters/TransactionVersionMetadata.h b/src/Interpreters/TransactionVersionMetadata.h index ed25a205d6a..6065bbe4ed1 100644 --- a/src/Interpreters/TransactionVersionMetadata.h +++ b/src/Interpreters/TransactionVersionMetadata.h @@ -1,9 +1,24 @@ #pragma once #include +#include + +namespace Poco +{ +class Logger; +} namespace DB { +struct TransactionInfoContext +{ + StorageID table = StorageID::createEmpty(); + String part_name; + String covering_part; + + TransactionInfoContext(StorageID id, String part) : table(std::move(id)), part_name(std::move(part)) {} +}; + struct VersionMetadata { TransactionID mintid = Tx::EmptyTID; @@ -20,14 +35,14 @@ struct VersionMetadata TransactionID getMinTID() const { return mintid; } TransactionID getMaxTID() const; - bool tryLockMaxTID(const TransactionID & tid, TIDHash * locked_by_id = nullptr); - void lockMaxTID(const TransactionID & tid, const String & error_context = {}); - void unlockMaxTID(const TransactionID & tid); + bool tryLockMaxTID(const TransactionID & tid, const TransactionInfoContext & context, TIDHash * locked_by_id = nullptr); + void lockMaxTID(const TransactionID & tid, const TransactionInfoContext & context); + void unlockMaxTID(const TransactionID & tid, const TransactionInfoContext & context); bool isMaxTIDLocked() const; /// It can be called only from MergeTreeTransaction or on server startup - void setMinTID(const TransactionID & tid); + void setMinTID(const TransactionID & tid, const TransactionInfoContext & context); bool canBeRemoved(Snapshot oldest_snapshot_version); @@ -35,6 +50,9 @@ struct VersionMetadata void read(ReadBuffer & buf); String toString(bool one_line = true) const; + + Poco::Logger * log; + VersionMetadata(); }; DataTypePtr getTransactionIDDataType(); diff --git a/src/Interpreters/TransactionsInfoLog.cpp b/src/Interpreters/TransactionsInfoLog.cpp new file mode 100644 index 00000000000..ec80e8db942 --- /dev/null +++ b/src/Interpreters/TransactionsInfoLog.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +NamesAndTypesList TransactionsInfoLogElement::getNamesAndTypes() +{ + auto type_enum = std::make_shared( + DataTypeEnum8::Values + { + {"Begin", static_cast(BEGIN)}, + {"Commit", static_cast(COMMIT)}, + {"Rollback", static_cast(ROLLBACK)}, + + {"AddPart", static_cast(ADD_PART)}, + {"LockPart", static_cast(LOCK_PART)}, + {"UnlockPart", static_cast(UNLOCK_PART)}, + }); + + return + { + {"type", std::move(type_enum)}, + {"event_date", std::make_shared()}, + {"event_time", std::make_shared(6)}, + {"thread_id", std::make_shared()}, + + {"query_id", std::make_shared()}, + {"tid", getTransactionIDDataType()}, + {"tid_hash", std::make_shared()}, + + {"csn", std::make_shared()}, + + {"database", std::make_shared()}, + {"table", std::make_shared()}, + {"uuid", std::make_shared()}, + {"part", std::make_shared()}, + }; +} + +void TransactionsInfoLogElement::fillCommonFields(const TransactionInfoContext * context) +{ + event_time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + thread_id = getThreadId(); + + query_id = CurrentThread::getQueryId().toString(); + + if (!context) + return; + + table = context->table; + part_name = context->part_name; +} + +void TransactionsInfoLogElement::appendToBlock(MutableColumns & columns) const +{ + assert(type != UNKNOWN); + assert(!tid.isEmpty()); + size_t i = 0; + + columns[i++]->insert(type); + auto event_time_seconds = event_time / 1000000; + columns[i++]->insert(DateLUT::instance().toDayNum(event_time_seconds).toUnderType()); + columns[i++]->insert(event_time); + columns[i++]->insert(thread_id); + + columns[i++]->insert(query_id); + columns[i++]->insert(Tuple{tid.start_csn, tid.local_tid, tid.host_id}); + columns[i++]->insert(tid.getHash()); + + columns[i++]->insert(csn); + + columns[i++]->insert(table.database_name); + columns[i++]->insert(table.table_name); + columns[i++]->insert(table.uuid); + columns[i++]->insert(part_name); +} + +} diff --git a/src/Interpreters/TransactionsInfoLog.h b/src/Interpreters/TransactionsInfoLog.h new file mode 100644 index 00000000000..8b48b54aa88 --- /dev/null +++ b/src/Interpreters/TransactionsInfoLog.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include + +namespace DB +{ + +struct TransactionInfoContext; + +struct TransactionsInfoLogElement +{ + enum Type + { + UNKNOWN = 0, + + BEGIN = 1, + COMMIT = 2, + ROLLBACK = 3, + + ADD_PART = 10, + LOCK_PART = 11, + UNLOCK_PART = 12, + }; + + Type type = UNKNOWN; + Decimal64 event_time = 0; + UInt64 thread_id; + + String query_id; + TransactionID tid = Tx::EmptyTID; + + /// For COMMIT events + CSN csn = Tx::UnknownCSN; + + /// For *_PART events + StorageID table = StorageID::createEmpty(); + String part_name; + + static std::string name() { return "TransactionsInfoLog"; } + static NamesAndTypesList getNamesAndTypes(); + static NamesAndAliases getNamesAndAliases() { return {}; } + void appendToBlock(MutableColumns & columns) const; + + void fillCommonFields(const TransactionInfoContext * context = nullptr); +}; + +class TransactionsInfoLog : public SystemLog +{ + using SystemLog::SystemLog; +}; + +} diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index 84477b03cf7..caa2745ea3b 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1156,13 +1156,15 @@ void IMergeTreeDataPart::loadVersionMetadata() const /// so part will be ether broken or known to be created by transaction. /// 4. Fsyncs in storeVersionMetadata() work incorrectly. + TransactionInfoContext txn_context{storage.getStorageID(), name}; + if (!disk->exists(tmp_version_file_name)) { /// Case 1 (or 3). /// We do not have version metadata and transactions history for old parts, /// so let's consider that such parts were created by some ancient transaction /// and were committed with some prehistoric CSN. - versions.setMinTID(Tx::PrehistoricTID); + versions.setMinTID(Tx::PrehistoricTID, txn_context); versions.mincsn = Tx::PrehistoricCSN; return; } @@ -1170,7 +1172,7 @@ void IMergeTreeDataPart::loadVersionMetadata() const /// Case 2. /// Content of *.tmp file may be broken, just use fake TID. /// Transaction was not committed if *.tmp file was not renamed, so we should complete rollback by removing part. - versions.setMinTID(Tx::DummyTID); + versions.setMinTID(Tx::DummyTID, txn_context); versions.mincsn = Tx::RolledBackCSN; remove_tmp_file(); } diff --git a/src/Storages/MergeTree/MergeTreeData.cpp b/src/Storages/MergeTree/MergeTreeData.cpp index 4c66a4d5aa5..cad6ad0c5cf 100644 --- a/src/Storages/MergeTree/MergeTreeData.cpp +++ b/src/Storages/MergeTree/MergeTreeData.cpp @@ -1329,7 +1329,7 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) /// Transaction that tried to remove this part was not committed. Clear maxtid. LOG_TRACE(log, "Will fix version metadata of {} after unclean restart: clearing maxtid={}", part->name, versions.maxtid); - versions.unlockMaxTID(versions.maxtid); + versions.unlockMaxTID(versions.maxtid, TransactionInfoContext{getStorageID(), part->name}); } versions_updated = true; } @@ -4445,7 +4445,7 @@ void MergeTreeData::Transaction::rollback() DataPartPtr covering_part; DataPartsVector covered_parts = data.getActivePartsToReplace(part->info, part->name, covering_part, lock); for (auto & covered : covered_parts) - covered->versions.unlockMaxTID(Tx::PrehistoricTID); + covered->versions.unlockMaxTID(Tx::PrehistoricTID, TransactionInfoContext{data.getStorageID(), covered->name}); } } diff --git a/tests/config/config.d/transactions.xml b/tests/config/config.d/transactions.xml index 731a312ed58..b0d57ad651d 100644 --- a/tests/config/config.d/transactions.xml +++ b/tests/config/config.d/transactions.xml @@ -1,3 +1,10 @@ <_enable_experimental_mvcc_prototype_test_helper_dev>42 + + + system + transactions_info_log
+ 7500 +
+
diff --git a/tests/integration/test_MemoryTracking/configs/no_system_log.xml b/tests/integration/test_MemoryTracking/configs/no_system_log.xml index bd1b9f9a49e..3218dae4dc7 100644 --- a/tests/integration/test_MemoryTracking/configs/no_system_log.xml +++ b/tests/integration/test_MemoryTracking/configs/no_system_log.xml @@ -14,4 +14,5 @@ + diff --git a/tests/queries/0_stateless/01172_transaction_counters.reference b/tests/queries/0_stateless/01172_transaction_counters.reference index c1a551cb049..0ab65e5dc9a 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.reference +++ b/tests/queries/0_stateless/01172_transaction_counters.reference @@ -16,3 +16,25 @@ 7 all_3_3_0 (0,0,'00000000-0000-0000-0000-000000000000') 0 7 all_4_4_0 (0,0,'00000000-0000-0000-0000-000000000000') 0 8 1 +1 1 AddPart 1 1 1 1 all_1_1_0 +2 1 Begin 1 1 1 1 +2 1 AddPart 1 1 1 1 all_2_2_0 +2 1 Rollback 1 1 1 1 +3 1 Begin 1 1 1 1 +3 1 AddPart 1 1 1 1 all_3_3_0 +3 1 Commit 1 1 1 0 +1 1 AddPart 1 1 1 1 all_1_1_0 +4 1 Begin 1 1 1 1 +4 1 AddPart 1 1 1 1 all_4_4_0 +4 1 Commit 1 1 1 0 +5 1 Begin 1 1 1 1 +5 1 AddPart 1 1 1 1 all_5_5_0 +5 1 LockPart 1 1 1 1 all_1_1_0 +5 1 LockPart 1 1 1 1 all_3_3_0 +5 1 LockPart 1 1 1 1 all_4_4_0 +5 1 LockPart 1 1 1 1 all_5_5_0 +5 1 UnlockPart 1 1 1 1 all_1_1_0 +5 1 UnlockPart 1 1 1 1 all_3_3_0 +5 1 UnlockPart 1 1 1 1 all_4_4_0 +5 1 UnlockPart 1 1 1 1 all_5_5_0 +5 1 Rollback 1 1 1 1 diff --git a/tests/queries/0_stateless/01172_transaction_counters.sql b/tests/queries/0_stateless/01172_transaction_counters.sql index 4ca0c3b3bf5..89a3a08f57b 100644 --- a/tests/queries/0_stateless/01172_transaction_counters.sql +++ b/tests/queries/0_stateless/01172_transaction_counters.sql @@ -31,5 +31,16 @@ select 7, name, maxtid, maxcsn from system.parts where database=currentDatabase( select 8, transactionID().3 == serverUUID(); commit; +begin transaction; +insert into txn_counters(n) values (5); +alter table txn_counters drop partition id 'all'; +rollback; + +system flush logs; +select indexOf((select arraySort(groupUniqArray(tid)) from system.transactions_info_log where database=currentDatabase() and table='txn_counters'), tid), + (toDecimal64(now64(6), 6) - toDecimal64(event_time, 6)) < 100, type, thread_id!=0, length(query_id)=length(queryID()), tid_hash!=0, csn=0, part +from system.transactions_info_log +where tid in (select tid from system.transactions_info_log where database=currentDatabase() and table='txn_counters' and not (tid.1=1 and tid.2=1)) +or (database=currentDatabase() and table='txn_counters') order by event_time; drop table txn_counters; From 7feb01480478bd46f4eed651f15649bc361f57d4 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Fri, 14 Jan 2022 23:26:18 +0300 Subject: [PATCH 0180/1647] rebuild docker image --- docker/test/integration/base/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/integration/base/Dockerfile b/docker/test/integration/base/Dockerfile index 91b26735fe5..2ee8d369dc2 100644 --- a/docker/test/integration/base/Dockerfile +++ b/docker/test/integration/base/Dockerfile @@ -1,4 +1,4 @@ -# rebuild in #33610 +# rebuild in #24258 # docker build -t clickhouse/integration-test . ARG FROM_TAG=latest FROM clickhouse/test-base:$FROM_TAG From 5132411e00de5c917afc23fcec7ea96786fb78da Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Sat, 15 Jan 2022 14:07:40 +0300 Subject: [PATCH 0181/1647] rebuild docker image --- docker/test/integration/base/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/integration/base/Dockerfile b/docker/test/integration/base/Dockerfile index 2ee8d369dc2..81129233f68 100644 --- a/docker/test/integration/base/Dockerfile +++ b/docker/test/integration/base/Dockerfile @@ -57,7 +57,7 @@ RUN echo $'tickTime=2500 \n\ tickTime=2500 \n\ dataDir=/zookeeper \n\ clientPort=2181 \n\ -maxClientCnxns=80' > /opt/zookeeper/conf/zoo.cfg +maxClientCnxns=81' > /opt/zookeeper/conf/zoo.cfg RUN mkdir /zookeeper && chmod -R 777 /zookeeper ENV TZ=Europe/Moscow From 9a518d789f1b14a31c767da4e6309087491adcba Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Sat, 15 Jan 2022 16:21:43 +0300 Subject: [PATCH 0182/1647] rebuild docker image --- docker/test/base/Dockerfile | 2 +- docker/test/integration/base/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/test/base/Dockerfile b/docker/test/base/Dockerfile index 6beab2e5bb7..11881365318 100644 --- a/docker/test/base/Dockerfile +++ b/docker/test/base/Dockerfile @@ -1,4 +1,4 @@ -# rebuild in #33610 +# rebuild in #24258 # docker build -t clickhouse/test-base . ARG FROM_TAG=latest FROM clickhouse/test-util:$FROM_TAG diff --git a/docker/test/integration/base/Dockerfile b/docker/test/integration/base/Dockerfile index 81129233f68..2ee8d369dc2 100644 --- a/docker/test/integration/base/Dockerfile +++ b/docker/test/integration/base/Dockerfile @@ -57,7 +57,7 @@ RUN echo $'tickTime=2500 \n\ tickTime=2500 \n\ dataDir=/zookeeper \n\ clientPort=2181 \n\ -maxClientCnxns=81' > /opt/zookeeper/conf/zoo.cfg +maxClientCnxns=80' > /opt/zookeeper/conf/zoo.cfg RUN mkdir /zookeeper && chmod -R 777 /zookeeper ENV TZ=Europe/Moscow From d237099729e38f8fd02b52ff01fb7e9785ceddc3 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Sat, 15 Jan 2022 22:38:25 +0300 Subject: [PATCH 0183/1647] rebuild docker image --- docker/test/util/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/test/util/Dockerfile b/docker/test/util/Dockerfile index d9827260acb..c83643a4069 100644 --- a/docker/test/util/Dockerfile +++ b/docker/test/util/Dockerfile @@ -1,4 +1,4 @@ -# rebuild in #33610 +# rebuild in #24258 # docker build -t clickhouse/test-util . FROM ubuntu:20.04 From cfb18e7ea0972101c52ee5861de40a065d53aad5 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Mon, 17 Jan 2022 10:05:36 +0800 Subject: [PATCH 0184/1647] remove useless header --- src/Storages/MergeTree/tests/gtest_merge_tree_metadata_cache.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Storages/MergeTree/tests/gtest_merge_tree_metadata_cache.cpp b/src/Storages/MergeTree/tests/gtest_merge_tree_metadata_cache.cpp index f513d1b2553..33a82845545 100644 --- a/src/Storages/MergeTree/tests/gtest_merge_tree_metadata_cache.cpp +++ b/src/Storages/MergeTree/tests/gtest_merge_tree_metadata_cache.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include using namespace DB; From e64ba110ef8e0ac6cb719e2ca4a9a253b54d4549 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Mon, 17 Jan 2022 19:55:44 +0800 Subject: [PATCH 0185/1647] fix path error --- src/Storages/MergeTree/PartMetadataManagerOrdinary.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/PartMetadataManagerOrdinary.cpp b/src/Storages/MergeTree/PartMetadataManagerOrdinary.cpp index f12af590e7d..184521cb6cf 100644 --- a/src/Storages/MergeTree/PartMetadataManagerOrdinary.cpp +++ b/src/Storages/MergeTree/PartMetadataManagerOrdinary.cpp @@ -20,7 +20,7 @@ PartMetadataManagerOrdinary::PartMetadataManagerOrdinary(const IMergeTreeDataPar std::unique_ptr PartMetadataManagerOrdinary::read(const String & file_name) const { - String file_path = fs::path(part->getFullRelativePath() + "/" + file_name); + String file_path = fs::path(part->getFullRelativePath()) / file_name; return openForReading(disk, file_path); } From 55382ccb1edb7682e78f3794a8e3ae019ee65d4b Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 18 Jan 2022 10:37:37 +0800 Subject: [PATCH 0186/1647] fix stateless ests --- .../01233_check_table_with_metadata_cache.sh | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh index efa94e9775a..07833f4cb39 100755 --- a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh +++ b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh @@ -12,6 +12,13 @@ database_engines=(Ordinary Atomic) use_metadata_caches=(false true) use_projections=(false true) +function materialize_projection_if_needed() +{ + if [[ "$use_projection" == "true" ]]; then + ${CLICKHOUSE_CLIENT} --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache MATERIALIZE PROJECTION p1" + fi +} + for table_engine in "${table_engines[@]}"; do for database_engine in "${database_engines[@]}"; do for use_metadata_cache in "${use_metadata_caches[@]}"; do @@ -34,58 +41,72 @@ for table_engine in "${table_engines[@]}"; do projection_clause=", projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)" fi ${CLICKHOUSE_CLIENT} --query "CREATE TABLE test_metadata_cache.check_part_metadata_cache (p Date, k UInt64, v1 UInt64, v2 Int64${projection_clause}) $table_engine_clause PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = ${use_metadata_cache};" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Insert first batch of data. ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000);" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Insert second batch of data. ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000);" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # First update. ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Second update. ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # First delete. ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Second delete. ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Insert third batch of data. ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000);" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Drop one partition. ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Add column. ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Delete column. ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Add TTL. ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Modify TTL. ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" # Truncate table. ${CLICKHOUSE_CLIENT} --echo --query "TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache;" + materialize_projection_if_needed ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" done done From d73d4e831e20d44b31a24e10fd0df4f5cfcf5240 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Wed, 19 Jan 2022 17:08:01 +0800 Subject: [PATCH 0187/1647] fix bug of check table when create data part with wide format and projection --- src/Storages/MergeTree/checkDataPart.cpp | 2 +- .../0_stateless/01710_projection_part_check.reference | 1 + tests/queries/0_stateless/01710_projection_part_check.sql | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Storages/MergeTree/checkDataPart.cpp b/src/Storages/MergeTree/checkDataPart.cpp index 0f35e30c5d0..075e9e9fbc8 100644 --- a/src/Storages/MergeTree/checkDataPart.cpp +++ b/src/Storages/MergeTree/checkDataPart.cpp @@ -153,7 +153,7 @@ IMergeTreeDataPart::Checksums checkDataPart( [&](const ISerialization::SubstreamPath & substream_path) { String projection_file_name = ISerialization::getFileNameForStream(projection_column, substream_path) + ".bin"; - checksums_data.files[projection_file_name] = checksum_compressed_file(disk, projection_path + projection_file_name); + projection_checksums_data.files[projection_file_name] = checksum_compressed_file(disk, projection_path + projection_file_name); }); } } diff --git a/tests/queries/0_stateless/01710_projection_part_check.reference b/tests/queries/0_stateless/01710_projection_part_check.reference index 2f7ad3359c0..813e663bdfc 100644 --- a/tests/queries/0_stateless/01710_projection_part_check.reference +++ b/tests/queries/0_stateless/01710_projection_part_check.reference @@ -1,2 +1,3 @@ all_1_1_0 1 all_2_2_0 1 +201805_1_1_0 1 diff --git a/tests/queries/0_stateless/01710_projection_part_check.sql b/tests/queries/0_stateless/01710_projection_part_check.sql index 39fb6a89fc8..142b1363d3c 100644 --- a/tests/queries/0_stateless/01710_projection_part_check.sql +++ b/tests/queries/0_stateless/01710_projection_part_check.sql @@ -8,3 +8,11 @@ insert into tp select number, number from numbers(5); check table tp settings check_query_single_value_result=0; drop table if exists tp; + +CREATE TABLE tp (`p` Date, `k` UInt64, `v1` UInt64, `v2` Int64, PROJECTION p1 ( SELECT p, sum(k), sum(v1), sum(v2) GROUP BY p) ) ENGINE = MergeTree PARTITION BY toYYYYMM(p) ORDER BY k SETTINGS min_bytes_for_wide_part = 0; + +INSERT INTO tp (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); + +CHECK TABLE tp settings check_query_single_value_result=0; + +DROP TABLE if exists tp; \ No newline at end of file From 9253ed8836bb274cc82685f0e6ad6160b80e1d96 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Wed, 19 Jan 2022 18:28:09 +0800 Subject: [PATCH 0188/1647] fix bug of checkPartData --- ..._check_table_with_metadata_cache.reference | 704 +++++++++++++++++- .../01233_check_table_with_metadata_cache.sh | 139 ++-- 2 files changed, 751 insertions(+), 92 deletions(-) diff --git a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.reference b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.reference index 5957d23fe82..c3206dcb9cb 100644 --- a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.reference +++ b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.reference @@ -1,4 +1,4 @@ -database engine:Ordinary; table engine:MergeTree; use metadata cache:false; use projection:false +database engine:Ordinary; table engine:MergeTree; use metadata cache:false; use projection:false; use_compact_data_part:false CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -40,7 +40,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Ordinary; table engine:MergeTree; use metadata cache:false; use projection:true +database engine:Ordinary; table engine:MergeTree; use metadata cache:false; use projection:false; use_compact_data_part:true CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -82,7 +82,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Ordinary; table engine:MergeTree; use metadata cache:true; use projection:false +database engine:Ordinary; table engine:MergeTree; use metadata cache:false; use projection:true; use_compact_data_part:false CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -124,7 +124,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Ordinary; table engine:MergeTree; use metadata cache:true; use projection:true +database engine:Ordinary; table engine:MergeTree; use metadata cache:false; use projection:true; use_compact_data_part:true CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -166,7 +166,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Atomic; table engine:MergeTree; use metadata cache:false; use projection:false +database engine:Ordinary; table engine:MergeTree; use metadata cache:true; use projection:false; use_compact_data_part:false CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -208,7 +208,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Atomic; table engine:MergeTree; use metadata cache:false; use projection:true +database engine:Ordinary; table engine:MergeTree; use metadata cache:true; use projection:false; use_compact_data_part:true CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -250,7 +250,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Atomic; table engine:MergeTree; use metadata cache:true; use projection:false +database engine:Ordinary; table engine:MergeTree; use metadata cache:true; use projection:true; use_compact_data_part:false CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -292,7 +292,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Atomic; table engine:MergeTree; use metadata cache:true; use projection:true +database engine:Ordinary; table engine:MergeTree; use metadata cache:true; use projection:true; use_compact_data_part:true CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -334,7 +334,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:false +database engine:Atomic; table engine:MergeTree; use metadata cache:false; use projection:false; use_compact_data_part:false CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -376,7 +376,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:true +database engine:Atomic; table engine:MergeTree; use metadata cache:false; use projection:false; use_compact_data_part:true CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -418,7 +418,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:false +database engine:Atomic; table engine:MergeTree; use metadata cache:false; use projection:true; use_compact_data_part:false CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -460,7 +460,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:true +database engine:Atomic; table engine:MergeTree; use metadata cache:false; use projection:true; use_compact_data_part:true CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -502,7 +502,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:false +database engine:Atomic; table engine:MergeTree; use metadata cache:true; use projection:false; use_compact_data_part:false CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -544,7 +544,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:true +database engine:Atomic; table engine:MergeTree; use metadata cache:true; use projection:false; use_compact_data_part:true CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -586,7 +586,7 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:false +database engine:Atomic; table engine:MergeTree; use metadata cache:true; use projection:true; use_compact_data_part:false CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); @@ -628,7 +628,679 @@ CHECK TABLE test_metadata_cache.check_part_metadata_cache; TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 -database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:true +database engine:Atomic; table engine:MergeTree; use metadata cache:true; use projection:true; use_compact_data_part:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:false; use_compact_data_part:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:false; use_compact_data_part:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:true; use_compact_data_part:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:true; use_compact_data_part:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:false; use_compact_data_part:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:false; use_compact_data_part:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:true; use_compact_data_part:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Ordinary; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:true; use_compact_data_part:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:false; use_compact_data_part:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:false; use_compact_data_part:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:true; use_compact_data_part:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:false; use projection:true; use_compact_data_part:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:false; use_compact_data_part:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:false; use_compact_data_part:true +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:true; use_compact_data_part:false +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000); +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache; +CHECK TABLE test_metadata_cache.check_part_metadata_cache; +1 +database engine:Atomic; table engine:ReplicatedMergeTree; use metadata cache:true; use projection:true; use_compact_data_part:true CHECK TABLE test_metadata_cache.check_part_metadata_cache; 1 INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000); diff --git a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh index 07833f4cb39..468a259ac66 100755 --- a/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh +++ b/tests/queries/0_stateless/01233_check_table_with_metadata_cache.sh @@ -11,103 +11,90 @@ table_engines=(MergeTree ReplicatedMergeTree) database_engines=(Ordinary Atomic) use_metadata_caches=(false true) use_projections=(false true) - -function materialize_projection_if_needed() -{ - if [[ "$use_projection" == "true" ]]; then - ${CLICKHOUSE_CLIENT} --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache MATERIALIZE PROJECTION p1" - fi -} +use_compact_data_parts=(false true) for table_engine in "${table_engines[@]}"; do for database_engine in "${database_engines[@]}"; do for use_metadata_cache in "${use_metadata_caches[@]}"; do for use_projection in "${use_projections[@]}"; do - echo "database engine:${database_engine}; table engine:${table_engine}; use metadata cache:${use_metadata_cache}; use projection:${use_projection}" + for use_compact_data_part in "${use_compact_data_parts[@]}"; do + echo "database engine:${database_engine}; table engine:${table_engine}; use metadata cache:${use_metadata_cache}; use projection:${use_projection}; use_compact_data_part:${use_compact_data_part}" - ${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC;" - ${CLICKHOUSE_CLIENT} --query "DROP DATABASE IF EXISTS test_metadata_cache;" - ${CLICKHOUSE_CLIENT} --query "CREATE DATABASE test_metadata_cache ENGINE = ${database_engine};" + ${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS test_metadata_cache.check_part_metadata_cache SYNC;" + ${CLICKHOUSE_CLIENT} --query "DROP DATABASE IF EXISTS test_metadata_cache;" + ${CLICKHOUSE_CLIENT} --query "CREATE DATABASE test_metadata_cache ENGINE = ${database_engine};" - table_engine_clause="" - if [[ "$table_engine" == "ReplicatedMergeTree" ]]; then - table_engine_clause="ENGINE ReplicatedMergeTree('/clickhouse/tables/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX/test_metadata_cache/check_part_metadata_cache', 'r1')" - elif [[ "$table_engine" == "MergeTree" ]]; then - table_engine_clause="ENGINE MergeTree()" - fi + table_engine_clause="" + if [[ "$table_engine" == "ReplicatedMergeTree" ]]; then + table_engine_clause="ENGINE ReplicatedMergeTree('/clickhouse/tables/$CLICKHOUSE_TEST_ZOOKEEPER_PREFIX/test_metadata_cache/check_part_metadata_cache', 'r1')" + elif [[ "$table_engine" == "MergeTree" ]]; then + table_engine_clause="ENGINE MergeTree()" + fi - projection_clause="" - if [[ "$use_projection" == "true" ]]; then - projection_clause=", projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)" - fi - ${CLICKHOUSE_CLIENT} --query "CREATE TABLE test_metadata_cache.check_part_metadata_cache (p Date, k UInt64, v1 UInt64, v2 Int64${projection_clause}) $table_engine_clause PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = ${use_metadata_cache};" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + projection_clause="" + if [[ "$use_projection" == "true" ]]; then + projection_clause=", projection p1 (select p, sum(k), sum(v1), sum(v2) group by p)" + fi - # Insert first batch of data. - ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000);" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + compact_data_part_clause=", min_bytes_for_wide_part = 10485760" + if [[ $use_compact_data_part == "true" ]]; then + compact_data_part_clause=", min_bytes_for_wide_part = 0" + fi + ${CLICKHOUSE_CLIENT} --query "CREATE TABLE test_metadata_cache.check_part_metadata_cache (p Date, k UInt64, v1 UInt64, v2 Int64${projection_clause}) $table_engine_clause PARTITION BY toYYYYMM(p) ORDER BY k settings use_metadata_cache = ${use_metadata_cache} ${compact_data_part_clause}" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Insert second batch of data. - ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000);" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Insert first batch of data. + ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 1, 1000, 2000), ('2018-05-16', 2, 3000, 4000), ('2018-05-17', 3, 5000, 6000), ('2018-05-18', 4, 7000, 8000);" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # First update. - ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Insert second batch of data. + ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-05-15', 5, 1000, 2000), ('2018-05-16', 6, 3000, 4000), ('2018-05-17', 7, 5000, 6000), ('2018-05-18', 8, 7000, 8000);" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Second update. - ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # First update. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache update v1 = 2001 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # First delete. - ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Second update. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache update v2 = 4002 where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Second delete. - ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # First delete. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 1 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Insert third batch of data. - ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000);" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Second delete. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache delete where k = 8 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Drop one partition. - ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Insert third batch of data. + ${CLICKHOUSE_CLIENT} --echo --query "INSERT INTO test_metadata_cache.check_part_metadata_cache (p, k, v1, v2) VALUES ('2018-06-15', 5, 1000, 2000), ('2018-06-16', 6, 3000, 4000), ('2018-06-17', 7, 5000, 6000), ('2018-06-18', 8, 7000, 8000);" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Add column. - ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Drop one partition. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache drop partition 201805 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Delete column. - ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Add column. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache add column v3 UInt64 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Add TTL. - ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Delete column. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache drop column v3 settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Modify TTL. - ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Add TTL. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 10 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" - # Truncate table. - ${CLICKHOUSE_CLIENT} --echo --query "TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache;" - materialize_projection_if_needed - ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + # Modify TTL. + ${CLICKHOUSE_CLIENT} --echo --query "ALTER TABLE test_metadata_cache.check_part_metadata_cache modify TTL p + INTERVAL 15 YEAR settings mutations_sync = 1, replication_alter_partitions_sync = 1;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + + # Truncate table. + ${CLICKHOUSE_CLIENT} --echo --query "TRUNCATE TABLE test_metadata_cache.check_part_metadata_cache;" + ${CLICKHOUSE_CLIENT} --echo --query "CHECK TABLE test_metadata_cache.check_part_metadata_cache;" + done done done done From 74580121033a8f07e89d1a38bdabb6c7b61f556e Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 19 Jan 2022 21:29:31 +0300 Subject: [PATCH 0189/1647] some fixes, add test with restart --- src/Interpreters/Context.cpp | 3 + src/Interpreters/MergeTreeTransaction.cpp | 2 +- src/Interpreters/TransactionLog.cpp | 17 +++-- src/Interpreters/TransactionLog.h | 43 ++++++++++- .../TransactionVersionMetadata.cpp | 16 ++-- src/Interpreters/TransactionsInfoLog.cpp | 1 - src/Storages/MergeTree/IMergeTreeDataPart.cpp | 6 ++ .../MergeTree/MutatePlainMergeTreeTask.cpp | 4 +- tests/integration/helpers/cluster.py | 3 +- .../integration/test_transactions/__init__.py | 0 .../configs/transactions.xml | 12 +++ tests/integration/test_transactions/test.py | 76 +++++++++++++++++++ 12 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 tests/integration/test_transactions/__init__.py create mode 100644 tests/integration/test_transactions/configs/transactions.xml create mode 100644 tests/integration/test_transactions/test.py diff --git a/src/Interpreters/Context.cpp b/src/Interpreters/Context.cpp index 3a661f26a36..15ecd70a233 100644 --- a/src/Interpreters/Context.cpp +++ b/src/Interpreters/Context.cpp @@ -87,6 +87,7 @@ #include #include #include +#include #include @@ -341,6 +342,8 @@ struct ContextSharedPart if (common_executor) common_executor->wait(); + TransactionLog::shutdownIfAny(); + std::unique_ptr delete_system_logs; { auto lock = std::lock_guard(mutex); diff --git a/src/Interpreters/MergeTreeTransaction.cpp b/src/Interpreters/MergeTreeTransaction.cpp index ae221e4b1a0..6e6a69c81d8 100644 --- a/src/Interpreters/MergeTreeTransaction.cpp +++ b/src/Interpreters/MergeTreeTransaction.cpp @@ -148,7 +148,7 @@ bool MergeTreeTransaction::rollback() noexcept for (const auto & part : creating_parts) part->versions.mincsn.store(Tx::RolledBackCSN); - for (const auto & part : removing_parts) + for (const auto & part : removing_parts) /// TODO update metadata file part->versions.unlockMaxTID(tid, TransactionInfoContext{part->storage.getStorageID(), part->name}); /// FIXME const_cast diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 11b119c86c1..9af21ac7e1f 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -38,12 +38,6 @@ catch (...) tryLogCurrentException(log); } -TransactionLog & TransactionLog::instance() -{ - /// Use unique_ptr to avoid races on initialization retries if exceptions was thrown from ctor - static std::unique_ptr inst = std::make_unique(); - return *inst; -} TransactionLog::TransactionLog() : log(&Poco::Logger::get("TransactionLog")) @@ -58,8 +52,15 @@ TransactionLog::TransactionLog() TransactionLog::~TransactionLog() { - stop_flag.store(true); + shutdown(); +} + +void TransactionLog::shutdown() +{ + if (stop_flag.exchange(true)) + return; log_updated_event->set(); + latest_snapshot.notify_all(); updating_thread.join(); } @@ -269,7 +270,7 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) /// Wait for committed changes to become actually visible, so the next transaction will see changes /// TODO it's optional, add a setting for this auto current_latest_snapshot = latest_snapshot.load(); - while (current_latest_snapshot < new_csn) + while (current_latest_snapshot < new_csn && !stop_flag) { latest_snapshot.wait(current_latest_snapshot); current_latest_snapshot = latest_snapshot.load(); diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index 90b36793935..d6ab1b3d63c 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -10,19 +10,57 @@ namespace DB { +/// We want to create a TransactionLog object lazily and avoid creation if it's not needed. +/// But we also want to call shutdown() in a specific place to avoid race conditions. +/// We cannot simply use return-static-variable pattern, +/// because a call to shutdown() may construct unnecessary object in this case. +template +class SingletonHelper : private boost::noncopyable +{ +public: + static Derived & instance() + { + Derived * ptr = instance_raw_ptr.load(); + if (likely(ptr)) + return *ptr; + + std::lock_guard lock{instance_mutex}; + if (!instance_holder.has_value()) + { + instance_holder.emplace(); + instance_raw_ptr = &instance_holder.value(); + } + return instance_holder.value(); + } + + static void shutdownIfAny() + { + std::lock_guard lock{instance_mutex}; + if (instance_holder.has_value()) + instance_holder->shutdown(); + } + +private: + static inline std::atomic instance_raw_ptr; + static inline std::optional instance_holder; + static inline std::mutex instance_mutex; +}; + class TransactionsInfoLog; using TransactionsInfoLogPtr = std::shared_ptr; using ZooKeeperPtr = std::shared_ptr; -class TransactionLog final : private boost::noncopyable +class TransactionLog final : public SingletonHelper { public: - static TransactionLog & instance(); + //static TransactionLog & instance(); TransactionLog(); ~TransactionLog(); + void shutdown(); + Snapshot getLatestSnapshot() const; Snapshot getOldestSnapshot() const; @@ -71,7 +109,6 @@ private: std::atomic_bool stop_flag = false; ThreadFromGlobalPool updating_thread; - }; } diff --git a/src/Interpreters/TransactionVersionMetadata.cpp b/src/Interpreters/TransactionVersionMetadata.cpp index 1e2b0596d93..abe4a7b529d 100644 --- a/src/Interpreters/TransactionVersionMetadata.cpp +++ b/src/Interpreters/TransactionVersionMetadata.cpp @@ -113,7 +113,7 @@ bool VersionMetadata::tryLockMaxTID(const TransactionID & tid, const Transaction void VersionMetadata::unlockMaxTID(const TransactionID & tid, const TransactionInfoContext & context) { - LOG_TEST(log, "Unlocking maxtid by {}", tid); + LOG_TEST(log, "Unlocking maxtid by {}, table: {}, part: {}", tid, context.table.getNameForLogs(), context.part_name); assert(!tid.isEmpty()); TIDHash max_lock_value = tid.getHash(); TIDHash locked_by = maxtid_lock.load(); @@ -289,8 +289,10 @@ void VersionMetadata::write(WriteBuffer & buf) const writeText(min, buf); } - if (!maxtid.isEmpty()) + if (maxtid_lock) { + assert(!maxtid.isEmpty()); + assert(maxtid.getHash() == maxtid_lock); writeCString("\nmaxtid: ", buf); TransactionID::write(maxtid, buf); if (CSN max = maxcsn.load()) @@ -322,20 +324,22 @@ void VersionMetadata::read(ReadBuffer & buf) mincsn = min; if (buf.eof()) return; + + assertChar('\n', buf); + buf.readStrict(name.data(), size); } - assertChar('\n', buf); - buf.readStrict(name.data(), size); if (name == "maxtid: ") { maxtid = TransactionID::read(buf); maxtid_lock = maxtid.getHash(); if (buf.eof()) return; + + assertChar('\n', buf); + buf.readStrict(name.data(), size); } - assertChar('\n', buf); - buf.readStrict(name.data(), size); if (name == "maxcsn: ") { if (maxtid.isEmpty()) diff --git a/src/Interpreters/TransactionsInfoLog.cpp b/src/Interpreters/TransactionsInfoLog.cpp index ec80e8db942..890bddeedd7 100644 --- a/src/Interpreters/TransactionsInfoLog.cpp +++ b/src/Interpreters/TransactionsInfoLog.cpp @@ -64,7 +64,6 @@ void TransactionsInfoLogElement::fillCommonFields(const TransactionInfoContext * void TransactionsInfoLogElement::appendToBlock(MutableColumns & columns) const { assert(type != UNKNOWN); - assert(!tid.isEmpty()); size_t i = 0; columns[i++]->insert(type); diff --git a/src/Storages/MergeTree/IMergeTreeDataPart.cpp b/src/Storages/MergeTree/IMergeTreeDataPart.cpp index caa2745ea3b..37524b93f82 100644 --- a/src/Storages/MergeTree/IMergeTreeDataPart.cpp +++ b/src/Storages/MergeTree/IMergeTreeDataPart.cpp @@ -1122,6 +1122,7 @@ void IMergeTreeDataPart::storeVersionMetadata() const } void IMergeTreeDataPart::loadVersionMetadata() const +try { String version_file_name = fs::path(getFullRelativePath()) / TXN_VERSION_METADATA_FILE_NAME; String tmp_version_file_name = version_file_name + ".tmp"; @@ -1176,6 +1177,11 @@ void IMergeTreeDataPart::loadVersionMetadata() const versions.mincsn = Tx::RolledBackCSN; remove_tmp_file(); } +catch (Exception & e) +{ + e.addMessage("While loading version metadata from table {} part {}", storage.getStorageID().getNameForLogs(), name); + throw; +} bool IMergeTreeDataPart::shallParticipateInMerges(const StoragePolicyPtr & storage_policy) const diff --git a/src/Storages/MergeTree/MutatePlainMergeTreeTask.cpp b/src/Storages/MergeTree/MutatePlainMergeTreeTask.cpp index 4e2ca8aee54..7388ba2790d 100644 --- a/src/Storages/MergeTree/MutatePlainMergeTreeTask.cpp +++ b/src/Storages/MergeTree/MutatePlainMergeTreeTask.cpp @@ -97,7 +97,9 @@ bool MutatePlainMergeTreeTask::executeStep() { if (merge_mutate_entry->txn) merge_mutate_entry->txn->onException(); - storage.updateMutationEntriesErrors(future_part, false, getCurrentExceptionMessage(false)); + String exception_message = getCurrentExceptionMessage(false); + LOG_ERROR(&Poco::Logger::get("MutatePlainMergeTreeTask"), exception_message); + storage.updateMutationEntriesErrors(future_part, false, exception_message); write_part_log(ExecutionStatus::fromCurrentException()); return false; } diff --git a/tests/integration/helpers/cluster.py b/tests/integration/helpers/cluster.py index dd2028f36a3..d5383bd6ea8 100644 --- a/tests/integration/helpers/cluster.py +++ b/tests/integration/helpers/cluster.py @@ -2085,7 +2085,8 @@ class ClickHouseInstance: else: params = params.copy() - params["query"] = sql + if sql is not None: + params["query"] = sql auth = None if user and password: diff --git a/tests/integration/test_transactions/__init__.py b/tests/integration/test_transactions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/test_transactions/configs/transactions.xml b/tests/integration/test_transactions/configs/transactions.xml new file mode 100644 index 00000000000..5c250f623f7 --- /dev/null +++ b/tests/integration/test_transactions/configs/transactions.xml @@ -0,0 +1,12 @@ + + <_enable_experimental_mvcc_prototype_test_helper_dev>42 + + + + + + system + transactions_info_log
+ 7500 +
+
diff --git a/tests/integration/test_transactions/test.py b/tests/integration/test_transactions/test.py new file mode 100644 index 00000000000..963dbfdbf61 --- /dev/null +++ b/tests/integration/test_transactions/test.py @@ -0,0 +1,76 @@ +import pytest +from helpers.cluster import ClickHouseCluster + +cluster = ClickHouseCluster(__file__) +node = cluster.add_instance("node", main_configs=["configs/transactions.xml"], stay_alive=True, with_zookeeper=True) + + +@pytest.fixture(scope="module") +def start_cluster(): + try: + cluster.start() + yield cluster + finally: + cluster.shutdown() + + +def tx(session, query): + params = {'session_id': 'session_{}'.format(session)} + return node.http_query(None, data=query, params=params) + + +def test_rollback_unfinished_on_restart(start_cluster): + node.query('create table mt (n int, m int) engine=MergeTree order by n partition by n % 2') + node.query('insert into mt values (1, 10), (2, 20)') + tid0 = "(1,1,'00000000-0000-0000-0000-000000000000')" + + tx(1, 'begin transaction') + tid1 = tx(1, 'select transactionID()').strip() + tx(1, "alter table mt drop partition id '1'") + tx(1, 'commit') + + tx(1, 'begin transaction') + tid2 = tx(1, 'select transactionID()').strip() + tx(1, 'insert into mt values (3, 30), (4, 40)') + tx(1, 'commit') + + node.query('system flush logs') + csn1 = node.query("select csn from system.transactions_info_log where type='Commit' and tid={}".format(tid1)).strip() + csn2 = node.query("select csn from system.transactions_info_log where type='Commit' and tid={}".format(tid2)).strip() + + tx(1, 'begin transaction') + tid3 = tx(1, 'select transactionID()').strip() + tx(1, 'insert into mt values (5, 50)') + tx(1, "alter table mt update m = m+n in partition id '1' where 1") + + tx(2, 'begin transaction') + tid4 = tx(2, 'select transactionID()').strip() + tx(2, "optimize table mt partition id '0' final settings optimize_throw_if_noop = 1") + + tx(3, 'begin transaction') + tid5 = tx(3, 'select transactionID()').strip() + tx(3, 'insert into mt values (6, 70)') + + node.restart_clickhouse(kill=True) + + assert node.query('select *, _part from mt order by n') == '2\t20\t0_2_2_0\n3\t30\t1_3_3_0\n4\t40\t0_4_4_0\n' + res = node.query("select name, active, mintid, 'csn' || toString(mincsn), maxtid, 'csn' || toString(maxcsn) from system.parts where table='mt' order by name") + res = res.replace(tid0, 'tid0') + res = res.replace(tid1, 'tid1').replace('csn' + csn1, 'csn_1') + res = res.replace(tid2, 'tid2').replace('csn' + csn2, 'csn_2') + res = res.replace(tid3, 'tid3') + res = res.replace(tid4, 'tid4') + res = res.replace(tid5, 'tid5') + assert res == "0_2_2_0\t1\ttid0\tcsn1\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ + "0_2_4_1\t0\ttid4\tcsn18446744073709551615\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ + "0_4_4_0\t1\ttid2\tcsn_2\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ + "0_7_7_0\t0\ttid5\tcsn18446744073709551615\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ + "1_3_3_0\t1\ttid2\tcsn_2\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ + "1_3_3_0_6\t0\ttid3\tcsn18446744073709551615\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ + "1_5_5_0\t0\ttid3\tcsn18446744073709551615\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ + "1_5_5_0_6\t0\ttid3\tcsn18446744073709551615\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" + + + + + From ba5844f1d13978d61c1f320e3fef9ab96af4c5fa Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Wed, 19 Jan 2022 23:16:05 +0300 Subject: [PATCH 0190/1647] fix build after merge --- src/Interpreters/TransactionLog.cpp | 2 +- src/Interpreters/TransactionVersionMetadata.cpp | 1 + src/Interpreters/TransactionsInfoLog.cpp | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 9af21ac7e1f..5223b6415bc 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -130,7 +130,7 @@ void TransactionLog::loadEntries(Strings::const_iterator beg, Strings::const_ite latest_snapshot = loaded.back().second; }; - MemoryTracker::LockExceptionInThread blocker(VariableContext::Global); + LockMemoryExceptionInThread lock_memory_tracker(VariableContext::Global); std::lock_guard lock{commit_mutex}; insert(); } diff --git a/src/Interpreters/TransactionVersionMetadata.cpp b/src/Interpreters/TransactionVersionMetadata.cpp index abe4a7b529d..7bba0ab668f 100644 --- a/src/Interpreters/TransactionVersionMetadata.cpp +++ b/src/Interpreters/TransactionVersionMetadata.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Interpreters/TransactionsInfoLog.cpp b/src/Interpreters/TransactionsInfoLog.cpp index 890bddeedd7..c126e38b5b3 100644 --- a/src/Interpreters/TransactionsInfoLog.cpp +++ b/src/Interpreters/TransactionsInfoLog.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #include From d5bde26bc10b4df135995339c9d093bc331bb940 Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 20 Jan 2022 15:22:00 +0300 Subject: [PATCH 0191/1647] 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 3fe5f1be849..b695695460f 100755 --- a/docker/test/stress/run.sh +++ b/docker/test/stress/run.sh @@ -307,7 +307,7 @@ then stop # Error messages (we should ignore some errors) - zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "DDLWorker: Cannot parse DDL task query" -e "RaftInstance: failed to accept a rpc connection due to error 125" -e "UNKNOWN_DATABASE" -e "NETWORK_ERROR" -e "UNKNOWN_TABLE" \ + zgrep -Fav -e "Code: 236. DB::Exception: Cancelled merging parts" -e "REPLICA_IS_ALREADY_ACTIVE" -e "DDLWorker: Cannot parse DDL task query" -e "RaftInstance: failed to accept a rpc connection due to error 125" -e "UNKNOWN_DATABASE" -e "NETWORK_ERROR" -e "UNKNOWN_TABLE" -e "ZooKeeperClient" -e "KEEPER_EXCEPTION" -e "DirectoryMonitor" \ /var/log/clickhouse-server/clickhouse-server.log | zgrep -Fa "" > /dev/null \ && echo -e 'Error message in clickhouse-server.log\tFAIL' >> /test_output/backward_compatibility_check_results.tsv \ || echo -e 'No Error messages in clickhouse-server.log\tOK' >> /test_output/backward_compatibility_check_results.tsv From c021fb2bd5f3afcf6e1e09d9409fcf1efdd92ad8 Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 20 Jan 2022 18:23:44 +0300 Subject: [PATCH 0192/1647] fix runtime linking error (lol) --- src/Interpreters/SystemLog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Interpreters/SystemLog.cpp b/src/Interpreters/SystemLog.cpp index a4eacd73605..44e599fd629 100644 --- a/src/Interpreters/SystemLog.cpp +++ b/src/Interpreters/SystemLog.cpp @@ -642,5 +642,6 @@ template class SystemLog; template class SystemLog; template class SystemLog; template class SystemLog; +template class SystemLog; } From 13857ceb697a59ff137b6adac7d104baedd8d4de Mon Sep 17 00:00:00 2001 From: Alexander Tokmakov Date: Thu, 20 Jan 2022 21:15:23 +0300 Subject: [PATCH 0193/1647] fixes --- src/Interpreters/TransactionLog.cpp | 28 +++++++++++++++---- src/Interpreters/TransactionLog.h | 5 ++-- .../configs/transactions.xml | 1 + tests/integration/test_transactions/test.py | 4 +++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/Interpreters/TransactionLog.cpp b/src/Interpreters/TransactionLog.cpp index 5223b6415bc..105a62c87f9 100644 --- a/src/Interpreters/TransactionLog.cpp +++ b/src/Interpreters/TransactionLog.cpp @@ -62,6 +62,16 @@ void TransactionLog::shutdown() log_updated_event->set(); latest_snapshot.notify_all(); updating_thread.join(); + + std::lock_guard lock{mutex}; + /// This is required to... you'll never guess - avoid race condition inside Poco::Logger (Coordination::ZooKeeper::log) + zookeeper.reset(); +} + +ZooKeeperPtr TransactionLog::getZooKeeper() const +{ + std::lock_guard lock{mutex}; + return zookeeper; } UInt64 TransactionLog::parseCSN(const String & csn_node_name) @@ -131,7 +141,7 @@ void TransactionLog::loadEntries(Strings::const_iterator beg, Strings::const_ite }; LockMemoryExceptionInThread lock_memory_tracker(VariableContext::Global); - std::lock_guard lock{commit_mutex}; + std::lock_guard lock{mutex}; insert(); } @@ -184,7 +194,11 @@ void TransactionLog::runUpdatingThread() return; if (!zookeeper) - zookeeper = global_context->getZooKeeper(); + { + auto new_zookeeper = global_context->getZooKeeper(); + std::lock_guard lock{mutex}; + zookeeper = new_zookeeper; + } loadNewEntries(); } @@ -194,7 +208,10 @@ void TransactionLog::runUpdatingThread() /// TODO better backoff std::this_thread::sleep_for(std::chrono::milliseconds(1000)); if (Coordination::isHardwareError(e.code)) - zookeeper = nullptr; + { + std::lock_guard lock{mutex}; + zookeeper.reset(); + } log_updated_event->set(); } catch (...) @@ -261,7 +278,8 @@ CSN TransactionLog::commitTransaction(const MergeTreeTransactionPtr & txn) LOG_TEST(log, "Committing transaction {}{}", txn->tid, txn->dumpDescription()); /// TODO handle connection loss /// TODO support batching - String path_created = zookeeper->create(zookeeper_path + "/csn-", writeTID(txn->tid), zkutil::CreateMode::PersistentSequential); /// Commit point + auto current_zookeeper = getZooKeeper(); + String path_created = current_zookeeper->create(zookeeper_path + "/csn-", writeTID(txn->tid), zkutil::CreateMode::PersistentSequential); /// Commit point new_csn = parseCSN(path_created.substr(zookeeper_path.size() + 1)); LOG_INFO(log, "Transaction {} committed with CSN={}", txn->tid, new_csn); @@ -328,7 +346,7 @@ CSN TransactionLog::getCSN(const TIDHash & tid) const if (tid == Tx::PrehistoricTID.getHash()) return Tx::PrehistoricCSN; - std::lock_guard lock{commit_mutex}; + std::lock_guard lock{mutex}; auto it = tid_to_csn.find(tid); if (it == tid_to_csn.end()) return Tx::UnknownCSN; diff --git a/src/Interpreters/TransactionLog.h b/src/Interpreters/TransactionLog.h index d6ab1b3d63c..f2599a4d104 100644 --- a/src/Interpreters/TransactionLog.h +++ b/src/Interpreters/TransactionLog.h @@ -88,14 +88,15 @@ private: static TransactionID parseTID(const String & csn_node_content); static String writeTID(const TransactionID & tid); + ZooKeeperPtr getZooKeeper() const; + ContextPtr global_context; Poco::Logger * log; std::atomic latest_snapshot; std::atomic local_tid_counter; - /// FIXME Transactions: it's probably a bad idea to use global mutex here - mutable std::mutex commit_mutex; + mutable std::mutex mutex; std::unordered_map tid_to_csn; mutable std::mutex running_list_mutex; diff --git a/tests/integration/test_transactions/configs/transactions.xml b/tests/integration/test_transactions/configs/transactions.xml index 5c250f623f7..620515cd7bd 100644 --- a/tests/integration/test_transactions/configs/transactions.xml +++ b/tests/integration/test_transactions/configs/transactions.xml @@ -2,6 +2,7 @@ <_enable_experimental_mvcc_prototype_test_helper_dev>42 + 100500 diff --git a/tests/integration/test_transactions/test.py b/tests/integration/test_transactions/test.py index 963dbfdbf61..97f1fa7b1f6 100644 --- a/tests/integration/test_transactions/test.py +++ b/tests/integration/test_transactions/test.py @@ -24,6 +24,9 @@ def test_rollback_unfinished_on_restart(start_cluster): node.query('insert into mt values (1, 10), (2, 20)') tid0 = "(1,1,'00000000-0000-0000-0000-000000000000')" + # it will hold a snapshot and avoid parts cleanup + tx(0, 'begin transaction') + tx(1, 'begin transaction') tid1 = tx(1, 'select transactionID()').strip() tx(1, "alter table mt drop partition id '1'") @@ -65,6 +68,7 @@ def test_rollback_unfinished_on_restart(start_cluster): "0_2_4_1\t0\ttid4\tcsn18446744073709551615\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ "0_4_4_0\t1\ttid2\tcsn_2\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ "0_7_7_0\t0\ttid5\tcsn18446744073709551615\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ + "1_1_1_0\t0\ttid0\tcsn1\ttid1\tcsn_1\n" \ "1_3_3_0\t1\ttid2\tcsn_2\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ "1_3_3_0_6\t0\ttid3\tcsn18446744073709551615\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ "1_5_5_0\t0\ttid3\tcsn18446744073709551615\t(0,0,'00000000-0000-0000-0000-000000000000')\tcsn0\n" \ From 751d9e6a447f8bba1fade3e5dab0473cacc2d79a Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Fri, 21 Jan 2022 17:55:26 +0300 Subject: [PATCH 0194/1647] support nested in json type (wip) --- src/Columns/ColumnObject.cpp | 21 +++----- src/DataTypes/ObjectUtils.cpp | 13 +++++ src/DataTypes/ObjectUtils.h | 1 + .../Serializations/SerializationObject.cpp | 54 +++++++++++++------ 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index ec6f9c7b590..36531d86335 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -32,19 +32,6 @@ namespace ErrorCodes namespace { -Array createEmptyArrayField(size_t num_dimensions) -{ - Array array; - Array * current_array = &array; - for (size_t i = 1; i < num_dimensions; ++i) - { - current_array->push_back(Array()); - current_array = ¤t_array->back().get(); - } - - return array; -} - ColumnPtr recreateColumnWithDefaultValues( const ColumnPtr & column, const DataTypePtr & scalar_type, size_t num_dimensions) { @@ -269,7 +256,7 @@ void ColumnObject::Subcolumn::insert(Field field, FieldInfo info) { auto base_type = info.scalar_type; - if (isNothing(base_type) && (!is_nullable || !info.have_nulls)) + if (isNothing(base_type) && info.num_dimensions == 0) { insertDefault(); return; @@ -467,9 +454,13 @@ ColumnObject::Subcolumn ColumnObject::Subcolumn::recreateWithDefaultValues(const new_subcolumn.num_of_defaults_in_prefix = num_of_defaults_in_prefix; new_subcolumn.data.reserve(data.size()); + auto scalar_type = field_info.scalar_type; + if (new_subcolumn.is_nullable) + scalar_type = makeNullable(scalar_type); + for (const auto & part : data) new_subcolumn.data.push_back(recreateColumnWithDefaultValues( - part, field_info.scalar_type, field_info.num_dimensions)); + part, scalar_type, field_info.num_dimensions)); return new_subcolumn; } diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 09c66888b37..516b44d3dc6 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -90,6 +90,19 @@ DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension) return type; } +Array createEmptyArrayField(size_t num_dimensions) +{ + Array array; + Array * current_array = &array; + for (size_t i = 1; i < num_dimensions; ++i) + { + current_array->push_back(Array()); + current_array = ¤t_array->back().get(); + } + + return array; +} + DataTypePtr getDataTypeByColumn(const IColumn & column) { auto idx = column.getDataType(); diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 421470872d7..4f13882f4b8 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -14,6 +14,7 @@ size_t getNumberOfDimensions(const IDataType & type); size_t getNumberOfDimensions(const IColumn & column); DataTypePtr getBaseTypeOfArray(const DataTypePtr & type); DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); +Array createEmptyArrayField(size_t num_dimensions); ColumnPtr getBaseColumnOfArray(const ColumnPtr & column); ColumnPtr createArrayOfColumn(const ColumnPtr & column, size_t num_dimensions); diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index 4e7b721e2fb..aa8fc459aa1 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -33,8 +33,8 @@ namespace class FieldVisitorReplaceScalars : public StaticVisitor { public: - explicit FieldVisitorReplaceScalars(const Field & replacement_) - : replacement(replacement_) + explicit FieldVisitorReplaceScalars(const Field & replacement_, size_t num_dimensions_to_keep_) + : replacement(replacement_), num_dimensions_to_keep(num_dimensions_to_keep_) { } @@ -43,6 +43,9 @@ public: { if constexpr (std::is_same_v) { + if (num_dimensions_to_keep == 0) + return replacement; + const size_t size = x.size(); Array res(size); for (size_t i = 0; i < size; ++i) @@ -55,6 +58,7 @@ public: private: const Field & replacement; + size_t num_dimensions_to_keep; }; bool tryInsertDefaultFromNested( @@ -63,21 +67,30 @@ bool tryInsertDefaultFromNested( if (!entry->path.hasNested()) return false; - const auto * node = subcolumns.findLeaf(entry->path); - if (!node) - return false; + const ColumnObject::SubcolumnsTree::Node * node = subcolumns.findLeaf(entry->path); + const ColumnObject::SubcolumnsTree::Leaf * leaf = nullptr; + size_t num_skipped_nested = 0; - const auto * node_nested = subcolumns.findParent(node, - [](const auto & candidate) { return candidate.isNested(); }); + while (node) + { + const auto * node_nested = subcolumns.findParent(node, + [](const auto & candidate) { return candidate.isNested(); }); - if (!node_nested) - return false; + if (!node_nested) + break; - const auto * leaf = subcolumns.findLeaf(node_nested, - [&](const auto & candidate) - { - return candidate.column.size() == entry->column.size() + 1; - }); + leaf = subcolumns.findLeaf(node_nested, + [&](const auto & candidate) + { + return candidate.column.size() == entry->column.size() + 1; + }); + + if (leaf) + break; + + node = node_nested->parent; + ++num_skipped_nested; + } if (!leaf) return false; @@ -86,9 +99,18 @@ bool tryInsertDefaultFromNested( if (last_field.isNull()) return false; - auto default_scalar = getBaseTypeOfArray(leaf->column.getLeastCommonType())->getDefault(); - auto default_field = applyVisitor(FieldVisitorReplaceScalars(default_scalar), last_field); + const auto & least_common_type = entry->column.getLeastCommonType(); + size_t num_dimensions = getNumberOfDimensions(*least_common_type); + assert(num_skipped_nested < num_dimensions); + + size_t num_dimensions_to_keep = num_dimensions - num_skipped_nested; + auto default_scalar = num_skipped_nested + ? createEmptyArrayField(num_skipped_nested) + : getBaseTypeOfArray(least_common_type)->getDefault(); + + auto default_field = applyVisitor(FieldVisitorReplaceScalars(default_scalar, num_dimensions_to_keep), last_field); entry->column.insert(std::move(default_field)); + return true; } From 3e5af3023d50368125c09a1263e2878656c92eda Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 22 Jan 2022 03:19:47 +0300 Subject: [PATCH 0195/1647] mark test data as binary --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index bcc7d57b904..a23f027122b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ contrib/* linguist-vendored *.h linguist-language=C++ +tests/queries/0_stateless/data_json/* binary From b78415908623e8ff5390e9895d83be97ac8b696e Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Sat, 22 Jan 2022 03:20:20 +0300 Subject: [PATCH 0196/1647] add more tests for JSON type --- .../queries/0_stateless/01825_type_json_1.sql | 2 + .../queries/0_stateless/01825_type_json_2.sql | 2 + .../queries/0_stateless/01825_type_json_3.sql | 2 + .../queries/0_stateless/01825_type_json_4.sh | 1 + .../queries/0_stateless/01825_type_json_5.sql | 2 + .../queries/0_stateless/01825_type_json_6.sh | 1 + .../queries/0_stateless/01825_type_json_7.sh | 1 + .../0_stateless/01825_type_json_btc.reference | 5 +++ .../0_stateless/01825_type_json_btc.sh | 23 ++++++++++ .../0_stateless/01825_type_json_describe.sql | 2 + .../01825_type_json_distributed.sql | 2 + .../0_stateless/01825_type_json_field.sql | 2 + .../01825_type_json_insert_select.sql | 2 + .../01825_type_json_nbagames.reference | 12 ++++++ .../0_stateless/01825_type_json_nbagames.sh | 40 ++++++++++++++++++ .../0_stateless/01825_type_json_nullable.sql | 2 + .../01825_type_json_schema_race_long.sh | 1 + .../data_json/btc_transactions.json | Bin 0 -> 210270 bytes .../data_json/nbagames_sample.json | Bin 0 -> 6381971 bytes 19 files changed, 102 insertions(+) create mode 100644 tests/queries/0_stateless/01825_type_json_btc.reference create mode 100755 tests/queries/0_stateless/01825_type_json_btc.sh create mode 100644 tests/queries/0_stateless/01825_type_json_nbagames.reference create mode 100755 tests/queries/0_stateless/01825_type_json_nbagames.sh create mode 100644 tests/queries/0_stateless/data_json/btc_transactions.json create mode 100644 tests/queries/0_stateless/data_json/nbagames_sample.json diff --git a/tests/queries/0_stateless/01825_type_json_1.sql b/tests/queries/0_stateless/01825_type_json_1.sql index 73110cd866d..233d0fae1e8 100644 --- a/tests/queries/0_stateless/01825_type_json_1.sql +++ b/tests/queries/0_stateless/01825_type_json_1.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + DROP TABLE IF EXISTS t_json; CREATE TABLE t_json(id UInt64, data Object('JSON')) diff --git a/tests/queries/0_stateless/01825_type_json_2.sql b/tests/queries/0_stateless/01825_type_json_2.sql index 126ae297e1b..cf5a74ad1e8 100644 --- a/tests/queries/0_stateless/01825_type_json_2.sql +++ b/tests/queries/0_stateless/01825_type_json_2.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + DROP TABLE IF EXISTS t_json_2; CREATE TABLE t_json_2(id UInt64, data Object('JSON')) diff --git a/tests/queries/0_stateless/01825_type_json_3.sql b/tests/queries/0_stateless/01825_type_json_3.sql index 93dab846c17..279460a6dcf 100644 --- a/tests/queries/0_stateless/01825_type_json_3.sql +++ b/tests/queries/0_stateless/01825_type_json_3.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + DROP TABLE IF EXISTS t_json_3; CREATE TABLE t_json_3(id UInt64, data JSON) diff --git a/tests/queries/0_stateless/01825_type_json_4.sh b/tests/queries/0_stateless/01825_type_json_4.sh index 8eeb8b926e9..062c77677ac 100755 --- a/tests/queries/0_stateless/01825_type_json_4.sh +++ b/tests/queries/0_stateless/01825_type_json_4.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/01825_type_json_5.sql b/tests/queries/0_stateless/01825_type_json_5.sql index 7d9498e454b..eeea03432b4 100644 --- a/tests/queries/0_stateless/01825_type_json_5.sql +++ b/tests/queries/0_stateless/01825_type_json_5.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + SELECT '{"a": {"b": 1, "c": 2}}'::JSON AS s; SELECT '{"a": {"b": 1, "c": 2}}'::JSON AS s format JSONEachRow; diff --git a/tests/queries/0_stateless/01825_type_json_6.sh b/tests/queries/0_stateless/01825_type_json_6.sh index 837b0d2290c..b992023fee0 100755 --- a/tests/queries/0_stateless/01825_type_json_6.sh +++ b/tests/queries/0_stateless/01825_type_json_6.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/01825_type_json_7.sh b/tests/queries/0_stateless/01825_type_json_7.sh index 24de546b206..41708d80ed7 100755 --- a/tests/queries/0_stateless/01825_type_json_7.sh +++ b/tests/queries/0_stateless/01825_type_json_7.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/01825_type_json_btc.reference b/tests/queries/0_stateless/01825_type_json_btc.reference new file mode 100644 index 00000000000..d3d852b633f --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_btc.reference @@ -0,0 +1,5 @@ +100 +data Tuple(double_spend UInt64, fee Int32, hash String, inputs Nested(index Int8, prev_out Tuple(addr String, n Int16, script String, spending_outpoints Nested(n Int8, tx_index Int64), spent UInt64, tx_index Int64, type Int8, value Int64), script String, sequence Int64, witness String), lock_time Int32, out Nested(addr String, n Int8, script String, spending_outpoints Nested(n Int8, tx_index Int64), spent UInt64, tx_index Int64, type Int8, value Int64), rbf UInt64, relayed_by String, size Int16, time Int32, tx_index Int64, ver Int8, vin_sz Int8, vout_sz Int8, weight Int16) +8174.56 2680 +2.32 1 +[[],[(0,359661801933760)]] diff --git a/tests/queries/0_stateless/01825_type_json_btc.sh b/tests/queries/0_stateless/01825_type_json_btc.sh new file mode 100755 index 00000000000..b189f16c16b --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_btc.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Tags: no-fasttest + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS btc" + +${CLICKHOUSE_CLIENT} -q "CREATE TABLE btc (data JSON) ENGINE = MergeTree ORDER BY tuple()" + +cat $CUR_DIR/data_json/btc_transactions.json | ${CLICKHOUSE_CLIENT} -q "INSERT INTO btc FORMAT JSONAsObject" --input_format_parallel_parsing 0 + +${CLICKHOUSE_CLIENT} -q "SELECT count() FROM btc" +${CLICKHOUSE_CLIENT} -q "DESC btc SETTINGS describe_extend_object_types = 1" + +${CLICKHOUSE_CLIENT} -q "SELECT avg(data.fee), median(data.fee) FROM btc" + +${CLICKHOUSE_CLIENT} -q "SELECT avg(length(data.inputs.prev_out.spending_outpoints) AS outpoints_length), median(outpoints_length) FROM btc" + +${CLICKHOUSE_CLIENT} -q "SELECT data.out.spending_outpoints AS outpoints FROM btc WHERE arrayExists(x -> notEmpty(x), outpoints)" + +${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS btc" diff --git a/tests/queries/0_stateless/01825_type_json_describe.sql b/tests/queries/0_stateless/01825_type_json_describe.sql index fa002e9cbff..f4aabaed715 100644 --- a/tests/queries/0_stateless/01825_type_json_describe.sql +++ b/tests/queries/0_stateless/01825_type_json_describe.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + DROP TABLE IF EXISTS t_json_desc; CREATE TABLE t_json_desc (data JSON) ENGINE = MergeTree ORDER BY tuple(); diff --git a/tests/queries/0_stateless/01825_type_json_distributed.sql b/tests/queries/0_stateless/01825_type_json_distributed.sql index faa0717e21e..a528352e938 100644 --- a/tests/queries/0_stateless/01825_type_json_distributed.sql +++ b/tests/queries/0_stateless/01825_type_json_distributed.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + DROP TABLE IF EXISTS t_json_local; DROP TABLE IF EXISTS t_json_dist; diff --git a/tests/queries/0_stateless/01825_type_json_field.sql b/tests/queries/0_stateless/01825_type_json_field.sql index b1e00b3ee96..f6df156030b 100644 --- a/tests/queries/0_stateless/01825_type_json_field.sql +++ b/tests/queries/0_stateless/01825_type_json_field.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + DROP TABLE IF EXISTS t_json_field; CREATE TABLE t_json_field (id UInt32, data JSON) diff --git a/tests/queries/0_stateless/01825_type_json_insert_select.sql b/tests/queries/0_stateless/01825_type_json_insert_select.sql index da0545993b2..878c106ca1e 100644 --- a/tests/queries/0_stateless/01825_type_json_insert_select.sql +++ b/tests/queries/0_stateless/01825_type_json_insert_select.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + DROP TABLE IF EXISTS type_json_src; DROP TABLE IF EXISTS type_json_dst; diff --git a/tests/queries/0_stateless/01825_type_json_nbagames.reference b/tests/queries/0_stateless/01825_type_json_nbagames.reference new file mode 100644 index 00000000000..467d74b5155 --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_nbagames.reference @@ -0,0 +1,12 @@ +1000 +data Tuple(`_id.$oid` String, `date.$date` String, teams Nested(abbreviation String, city String, home UInt64, name String, players Nested(ast Int8, blk Int8, drb Int8, fg Int8, fg3 Int8, fg3_pct String, fg3a Int8, fg_pct String, fga Int8, ft Int8, ft_pct String, fta Int8, mp String, orb Int8, pf Int8, player String, pts Int8, stl Int8, tov Int8, trb Int8), results Tuple(ast Int8, blk Int8, drb Int8, fg Int8, fg3 Int8, fg3_pct String, fg3a Int8, fg_pct String, fga Int8, ft Int8, ft_pct String, fta Int8, mp Int16, orb Int8, pf Int8, pts Int16, stl Int8, tov Int8, trb Int8), score Int16, won Int8)) +Boston Celtics 70 +Los Angeles Lakers 64 +Milwaukee Bucks 61 +Philadelphia 76ers 57 +Atlanta Hawks 55 +Larry Bird 10 +Clyde Drexler 4 +Magic Johnson 3 +Alvin Robertson 3 +Fat Lever 2 diff --git a/tests/queries/0_stateless/01825_type_json_nbagames.sh b/tests/queries/0_stateless/01825_type_json_nbagames.sh new file mode 100755 index 00000000000..6abec0a92fe --- /dev/null +++ b/tests/queries/0_stateless/01825_type_json_nbagames.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Tags: no-fasttest + +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +# shellcheck source=../shell_config.sh +. "$CUR_DIR"/../shell_config.sh + +${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS nbagames" + +${CLICKHOUSE_CLIENT} -q "CREATE TABLE nbagames (data JSON) ENGINE = MergeTree ORDER BY tuple()" + +cat $CUR_DIR/data_json/nbagames_sample.json | ${CLICKHOUSE_CLIENT} -q "INSERT INTO nbagames FORMAT JSONAsObject" --input_format_parallel_parsing 0 + +${CLICKHOUSE_CLIENT} -q "SELECT count() FROM nbagames" +${CLICKHOUSE_CLIENT} -q "DESC nbagames SETTINGS describe_extend_object_types = 1" + +${CLICKHOUSE_CLIENT} -q \ + "SELECT teams.name AS name, sum(teams.won) AS wins FROM nbagames \ + ARRAY JOIN data.teams AS teams GROUP BY name \ + ORDER BY wins DESC LIMIT 5;" + +${CLICKHOUSE_CLIENT} -q \ +"SELECT player, sum(triple_double) AS triple_doubles FROM \ +( \ + SELECT \ + tupleElement(players, 'player') AS player, \ + ((tupleElement(players, 'pts') >= 10) + \ + (tupleElement(players, 'ast') >= 10) + \ + (tupleElement(players, 'blk') >= 10) + \ + (tupleElement(players, 'stl') >= 10) + \ + (tupleElement(players, 'trb') >= 10)) >= 3 AS triple_double \ + FROM \ + ( \ + SELECT arrayJoin(arrayJoin(data.teams.players)) as players from nbagames \ + ) \ +) \ +GROUP BY player ORDER BY triple_doubles DESC LIMIT 5" + + +${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS nbagames" diff --git a/tests/queries/0_stateless/01825_type_json_nullable.sql b/tests/queries/0_stateless/01825_type_json_nullable.sql index a89bebe82c2..5c4a364b81a 100644 --- a/tests/queries/0_stateless/01825_type_json_nullable.sql +++ b/tests/queries/0_stateless/01825_type_json_nullable.sql @@ -1,3 +1,5 @@ +-- Tags: no-fasttest + DROP TABLE IF EXISTS t_json_null; CREATE TABLE t_json_null(id UInt64, data Object('JSON', 'Null')) diff --git a/tests/queries/0_stateless/01825_type_json_schema_race_long.sh b/tests/queries/0_stateless/01825_type_json_schema_race_long.sh index 3bd3e0388dc..b0e2ec95be2 100755 --- a/tests/queries/0_stateless/01825_type_json_schema_race_long.sh +++ b/tests/queries/0_stateless/01825_type_json_schema_race_long.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Tags: no-fasttest set -e diff --git a/tests/queries/0_stateless/data_json/btc_transactions.json b/tests/queries/0_stateless/data_json/btc_transactions.json new file mode 100644 index 0000000000000000000000000000000000000000..136f8ea29c1f94164d27b6b4ed75da733370825a GIT binary patch literal 210270 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 W@VpL;UuzB~u(z8*GUWgJ|NQ^w4WU&4 literal 0 HcmV?d00001 diff --git a/tests/queries/0_stateless/data_json/nbagames_sample.json b/tests/queries/0_stateless/data_json/nbagames_sample.json new file mode 100644 index 0000000000000000000000000000000000000000..9ae75d64f846b3d2d5f2e3cdd7a6cd631579f12f GIT binary patch literal 6381971 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 D{@CJN?F5U(&{s6Z=Fm;vi$GM=?8p2 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 z

@UwU9n6yB|L0%j?41j;Rr93=or(oDHj$)r`!5AJVu$ZY?pJSs(o;gKkVOa_s& zc{ZPmeqz|bsPhU@y+Z*|RVeHi!3>)gsaDPm;`%5AGRS$$7uT}H@Lh?4>lt-S=!@ho zi@;a+NO*6R_V)wXfPwT5vn#-^z+YM80(looos5tE+!}(v)$#brUZq|F4vz|SrYzsxq2@<6MVop!Om#b z8N;NMMk_wZ`FvUIK|qytJSa0?M32LgUn&A|2uAVC)q44f%h#5E2$ACxW);Zl3?FE7 zPIdj6TX<~^2W^&au@(@oYr*j|_yvj29T*4+fZ0d6T zRl%g>YN3S)qSxohD$n$1dvRc zQ6>_2XU&6b6v~L%K|={a<^f-vJ!Z5C+)kZCg|P<37UKD1t&txA=u@1pwP93Bk^~I*9692JX~gs67x2xxa@( zmX@LRfY<3&*IVGWSw>uLfLlrnkkknTrliTr3)u|72a{<28VA(NND=1d?ZRPC%joT< ztZF&nK%K2R;raY+zv-Jh1uZ2M^^e zWm~eOPqkP9m9-at+v`gwu93Xo!FwD z`)+QJV?ODW>dcnYDK(E#s#b8HZIk`XLvZiFM=gwg(^naAEWlvEHxdl2I^={JJD-nb zj|ZHnt}sv}GEir>yNgyIA4{QZH{&N1*=@gE-bnYjY)ANMwbF$t2^<8*W}5hoVS^Hw zK!sjFJY-Oy=?w~&{Wg8VCx@O3I`~4{q+5041PAwi_D4I>NhUPHOoy)9dbz!l1*9*R zo9%@k%_AXdnQ~$sH}(dxYA|$!fL==_s~ONm>rg|=jb2K&8$+T~bjFc750xEgxr9g9 zn~-YFLoevQAHrb!_Df8 zEJ$jke@zrnR|_h_9I-YE_^Zl;UN!l!%Z-+!3F7-G zhZie>0b4scCg72p3HYmFP|XeUk^PSxO88K&Igs6L#)cByg6GAHr3|08*B!iw#|^W8 zJ>?r=T{C#poSwuVnLVJb%xChzU-J~MA6?BPcM}dPXso|fWyIP2T|HS%CDP4K;?ZwD z$h>pMZ{%~5dymV}1bo19Kr+JC9k3SG_HfTB-$giLjX%D1XlF2i?7dCzc^O4zf2mTL zf$Aq5Ve?l$CUgY=vr&P>Tjnd|K{vdPSfuSc zR2*{Z`BU5_60OsWD0RP@-?x|7pT0YR@O@+s3SMeFIVp0ub?Q|j!Q_R3gI+mO^tQy0 zU1VsoX7IqXWM418ye$tH#)r&W$OEMWjN2%27@Q_<)tt!I!IIBU$L}0_dC=}xBEdIj zUy)-i3Gs099V#YsQOs+%jsTyud>)zP^9(@L(ZRjaR-C*=L?@9D-8dA)^C0U|v*fzat6X z6;DlEn=>-F&~+gMC7Z~wt5ZBeV>cY3Qym1TtwU8JSO*v+-egcc#)X~X*OOwhGhau! zfT?>{B(JNiaNT!(;S8)u-FGi;-W8+re#{9aPk{z5txF$FeIOm zK)X0PyxwvU_QmSzx|p8Q z=m{XFRB+yTzAJ*DKgw0>1$KnEAMWCI z3Tr}75t-Ra2cNCK$imWpm6HU#A9uD2vKTaz1WRcEEcWbBPyX3@PBR%$67RjbBP~av zqX<82dasw?MYA`}-QhvJItwB$>dHe<((A@OKSWbj-&P+#`M95-t+mU65tsqu?P(=rVND4}?k|bN=#DzYnKY<@VgWdv zh|4OC3#`)khm1X4$f|sghGPa1sJcu-jOW0>WflFagwAG08Qj>oEXE`;Qb~Shat*CGu5n^q!&>;^+#Dd@7UQwrWs0z zL4zC^-9fn6eS~@_7`)10=vc!m@xO(8_hWE?&peBBETNA8udUG_sc$oKpZdqlmC8rj zgD%OIlobFhu_B0P){B}ItsX_xtEL4$kZ2L?10Se?$g~(y&PWS<+i&WUDtQZ660!i2 zp*c3Yy4-$|V^<^!D~9a7S4EwS7gP>)B$IfN;n1tua#4EKY#@AHHjt43_$bIl7mA0n z&JP2E z4pk+GC?jekZksB^RnRC@GkSQr;(5$+xaQYl?{B|90P2|-f=hK1;e-GnW?(=^j1ajK zbx%SZ6)U{liOl$(w})=udLN2Fir3rki>K=_Q{4sEjuX6`(}S8xu_q2)!w8%h#<*1% zd~b3Xd&}1pQH;$x#cYd22(8SpwvTY4N|zOn-+x?QRcuVX9H^AjjWji~--XC==hS<0o0KyJKYUocSYcv1wm5LC^pj^8 zyr@9o1qM!B#(@IV?whUmBYMWvOou3+kSPyvr-KAFi7^c7w@#o!+Iyqph6tv=+zR+WZQ?szuvdN4bgx zHLtaTrM^uVQ}&c*I8Zz<6@B*|f_(C!WgxOxVewp!)+y#n;Dd&OV+GxKGl+30;L;Q` zqp&9{%3MdC1Oyo0a457TBF?Qt8Pqm-m2HEwWyG^yVY_F)ZewS7AC|1QQP8@h*n6Uo z+E_z56iQ7ax{dC%oV{PM~3sSH?lq8>g`+41bL)ZN2dz`gtkSntW-yZW3IQzPC!k9Zz1uf zqtG2ZVN8`x;>A|B#{2hHmIiYXsKWve)*9)IB4B}1)`ZAd?5hr)-QZD>-9{07CVr&K z5XkU)i6BaBqCAb;DI`fy8O5ZepGDophs=CEX-k0{0)wGrLn%SmGicFH9J)3^Kr$Pw z@IO&dZwdGj;0Oa1`{|6TsYlBC6d{udG4|Ff2HC_Tx1tCv5W z9(_2v_hI#OSjKcTI)vorw#{0`#ND=1mRZhSLj$#wOczSyGDQoS1}84V(ss^Mj`Mk# z$^(tNGY?8{c`s`N7nC?A9GqD`W=STxQd>zTIIcF~hma0gb8B@JH)WY1L&`EI@3D+Z zR$lTJnfB?KA4hZ3l(fHiKr1V$f{-!zDUKTeNQEKTL~kuqXK_4rLW9UjsYMPVS@!uE zR+@dJ!r-t_jp$;5+bP@ZdJD9+^O?Q1OlaTQZ+fbfSq1LY0TlkK0n`XJ#sa7=E%@f# z{}O3|+dH8EUJi;KOL&~;062B}9mWvwkRjrXMRfuSs4_4&k~m0REitwZ+}LMWi3XEh zM`v{xK2$Tq8An1?pEr!DR6^{Sdujgh?pSxTS$)m1r(-b6Sb2%+X4Rf--z4_H9wfy)i*6qw^Sn zft4pANR13bZw(Lp-l#y#98((M!!EHrE_TzHJ5{H@XytHhE6*u_tNDBR#254w|G(LSyepJhp z26Zq{4QrY{d@Q^xBVDaBUI$9~#3!XlT6tiYg1gX1mhBUU;)1*Tm)A1p`((ZOBHbe> zQi2B0)x<?3hqBm=AIRx#c3AdPO<2x{Z7u(=^Gw*d;&%%EWX zt8upA!O{FA|W`9PS40IXA6Ob1_OR=w!+J1 z7Kj%&^g;frG2Oni3hx|yY2Y5!+KoO9i0*R3zy@8Yk0q$(CZB#i0=}3u1D7TlWeNy3 z*K`0niUW&1--(xf=(MwWJ*RP1Hp-!tfT=pW&Tgvhi zyf_T2K2a=1gSg}%_x|RCoHW97xt`w03cucvn}ftzNyvOq;0^;HR~0h7ktTxW<9Sy-fy~R6uc{p-mrMha$E$dULCpe@|SW7 z+u;eS*~u^fU3+)|h#3Wts;7Q+?LMYXGBj4-Ne}4zg@%L2b$CI8@|5<88GiXnpal){IF;1;SwV3W7Qo2SRUE0_`kg&<-k3LY9Fa83MS<=I~JVQuw%d zx|Ka73vF~Jfs!X=aM=u{(A+f=>Lh>!rM(?W?aVQHy#RcBMq=n_RyLqSDU9g43%tUi z!7Wx_3FZ3>8etQL27ysDLQi_zc`6394tYr@{T}la{mx^)Iztirhsf>(MmNkxn>#Sh z*Y7V@-xn`s;?m`M^#KJl%kf9?wDIW2mw4XfLMabC{`7=Hy@#`=Vh;_-R6-*d3ffc+ zC7_{epj(GpI``?o;N`9m6JIkhvZ?6R?4F4<)w8b_cs>Oea;b4P{7&j|c=O zL_fG(-4y#J>=cTC0|-#lLTgXe+z*FajW_jk4V24y54zHq+s~`3&psgs*v17^&$z%# z70ZADFg6TuX{P_Ye$JK($mKNQxX^JzHH~eqX#^eZP>L%STxFk^zCA z)B#oiqD_WLKUS$rX!!6>{Mm$#6AAhy_S998i46hnrdi80$x>)}U={+H?62DhZe?v|4 z(9$vFC?$U7v@#T-nfEbcGINCHnK^R6$@)O(VKmTJa?Y4mR-C|cmdAik=m4Ero*n8^ z2Fv?!TOIe2&->tav_V+cl=ks|40k+|{1M(_z@M-)%;c=f`cTDMleFU0Wve)@0j2n= zA?8rFY09VfhFb?`C&i+C)grt^y>@;ud9o_eIqAA;1jJ6A?{EIVfaLpfs_BvQP^6)b zr{MDjOjv)7=M4#%ZBpl~N^H1x+Bb&8kQ-~%LZF&G$LT>u0tHP0To7oyLb-qM3TWTjdpSOY-gC4-&4&yE%RiqFy&Vc5 zO+KpMV;8hQ2M#+*$1gT(0H(>GpmhxhP${_+Txv)glmZp4*^o68z;@D`7|AJS*)f!% zy+SZ}pwwqdVVhXUGkPZaPLwTV6HFeoWKhiU>4AgCFEnEK;( zl84|!aZ3$@R~d#B*2SZUoD*&HA*ci3s`&27J_U7HE&k`|6g_1~QEbt?<4jM{RBidMGPV8wNrSsI_VSqMc z(e@bKhzf;uZj;B*Vwy^a3{D;#078axh|MyZA33~s7}oEcj1I4*P3iX8f0_$p+Q}Bs zYbINa^JWYoEiZQ`cxbDMBq+w@GIkFML@A|0Q8QWISK1kP}FAp z)ZcKK`*fQ^LUs)v=w^x>LQL)Ofa#=sOD>fV^mlvk;Y2sXu$Y>dsz%}~MHE$>1Iw;; za~3Dv-Vh>_-DZE!Gujwa#YRm|}6Gg0jC-C_p(Uitj*-E+{5^;qa_@q_>=i^`)+kKieGhR3} zP`jie<-9(8rAEU|PLTVZ2%E02vo^jv{2D^4utou3B~}E{AQw1G1rIqm+S^Z~@}m(| z+cAoBRkLMT>axNfO1Htm%CdMwa z4SYnsh+Yl$5p@E$Q;Z!}tz;y4m67P~FpNsnqyUa)9AR!k+c+{O@ZCL(qXz|FyeqWq zpL&WWb#~F&VHkkcj>BOP`XW%rnhsj?As(LZM|-KpbUK8ID&#bIwoogVse zg?qUH!Q8GgTL^c$1 zm$B~6JKrwLwAVH%fSt!^|L$EsuH>c(#c+<%3lRu$%u_j^R!&v?ZL|E3n{UOYaNO0e zW+p0Xwi&%QZjGtbSa=(I3Y9a1M+$3i*>ZCRenYU2l4@87Z{+m)#p;_ZVtTRJe)s$Q zGP50pBIG>|2~?yah6cK`Wh#ZO8s}^D0$T8O*=O}<0UUMpI^ZB(rkqRnPUc(sZV^zd z1_E_7PK3Tfyw)gzLRLU%GtUMh?QE?;(BP))bzH52GBh*c*UQbA#ZC4FE_K%F9k!1J9eQv7b6kX>ro;L|~ zSWF6RSy~3R_~jnaTseh~fRYdqhP3`qVer#Tk3%;)134-tisU4M3GhmW9Iwg~NAUw= z1?@poV1lBZ0b!XV?$oT%`hyjFB2mV*+5v#q(bAkoq~{a~6viU_NC@z>nF8y|KA9^d zn+)L@J<8iDgd+qIQ5eDwFL-?oLpBbUd%g=z)l-E)NoKfZGvmkag56E za;9RrHynOb)4|~odPMN%8V9ZY(0*+kcKObpS}H1oMvP^B-4j(%KxZ8HH*y%4Y%;r) z!$&rb6(DmhnP}w{OO!n#cx{bFW(AAhMuP!ir$?le2|zxB6`_ZWHK#zJSPlbSqVV|B zX4?h;KQx`33jltq9b^i{Y{z-4L?-~iYPSH;r1zhb#(DE|(X7$i8^8m+W#4KjCPAAJrJ(uVx2Twr5G* zUpcIFIy$WM>+0J=(s@~NviP-^W&Zl?S5$ghmI($l2b7L+#~1*&V$q{*Hx5xeWfQ6n zsBPq#Ygx`r4D^9P?hfvpX!58o!i_4~@QQo#Oy@J1uS8Ix&azB1$pae1F)!62_I8D1 z?X1uqCQe1MN{$YUfAf@F>gUo`TVzx~Lln7E}=Nf*) z78wuLGCga1@zKeGU!a86Hur${B!&e4=r{rJj1%l1ASLu_wtu*D?7Loc4j>gWVv$l& z#RBd<4H1#;N$hjAl9OGrji?O4%U-HL#eveEQ*Sf0e4b*)$J}ut2hieeebvb_8glHI}qBDw8J2DMd8zC z6nMzEpfyLK_cIFTT*JgQaw~&ykVvCIfHVd~T$z)}5P*=O*30i3nc(~FbD3g!fj~7) zHBmR?L%;%`7;_+die~DCRe{KU6rb|~k#bUn0>w;Fgo$ez3RKH+;<+A8CkI%B1;2o- zwhX+-kOJ=*S0jS-$CdNe9$d=i5}WTA)o{}J^4;a~i=3^!r4YBrlg{ryL{)R7|1(skJ#!4D zXa@S6NMmh)<}*5KFFzaMSiN|Ykh^w&E~lLza;G*8boH%Xtd2AkR14)8mYQf8=)=S< z^+UPRcoM5_;4^eGK{|9X?d17m&Meiy22VTRVI7_pC#htGQXig8Hz}FcAJ3UW#bZQi zj4iY59x4M`By8le*6J{FfG><`wIc)#%qdjt4^uJAwJZgSt)qP4gN_c#(JLINL4spy zf0(Y;d33q@=3F?(!3+z0GSLK=&7Kga)~IVT54o(S(J5DEWWn7L1XS0_b$?JP_MD@L zO~rb>l1|dq;?1Ykjqej-0~j1=b%O)nZ^MB)*3{5I239gc4VC3)I_&%!9T(ry%zgTK zUnL(n_LWr6Z z&Tga|*X-aGO=9ry#NUtxzAuwNu*}eCYiH;*!b8Rll(N!J-0z?m zZ@zDeT`JIkMhM!3#`}1QArR)$?Lt7Yn2UgNg^q(Sm_Dq~4JGg%m;0fQY#|9?d7>&F z8Wo30wbxc3Qk3}-W;w1zg_({(m}Ja)Gqwx}Je1>>7Ef0fmwrY{8vy)uKLfxo=j*}# z%|EsB=~#fc27YJ#s}ZG~*Zs&rrK7M`>CWh&()ntM1C(Sth_9>5bJ-L1T_@dqd>>Hf zbd#}kJKY4So9U+CZyend<`euvC8kL8&EWKBzcA&VrdKd4>V6ST|EwMD8ZNLWtC4AP zT)?!DNoN{Ta;;wM1c>Sw?@u}N#uVfVh44E8!b6x_#HB%iOAVR!1)w*U!Z$m&Sf*A8 zaZ+j(VrblIMhO2~4qlga|1aM?T3*ObEue)Z;(lF@a|sL=EWZk>2>)$p;3-3cU03XW z(A;9KPY6MR&IY9{GU&)?#Td)Q8Jkq zdUabQD6%sgM2HZ8k8*#ipXP03ViPBo5pmpCr&pt?* zQcNP06axq-`UeF7cMO33r~w{w;nI`U<#O{L;R5zQCp59V5g~Tzp^rb9++^DmpR%zS=5+tH49GQLxDf zfzo0w8L|i1&nm*a?o0$;Wg;jB|Bx9ZQD}?fiXzS zA~ISQK1pekJ_IDXY|zag&t=(vVG{VJ0paN2fC%%Mai<0Xf7LijujbMr_roxwgh9@j zniISwiqOobu`kNgYT_y$AEiPt2s~dSkPSkw<|2@r*yM45fbW~4jv=we?-T|Cl5HYo zRH4czv*VbaUY+$h85$Uz2v0Q)$}(WGdkP{0`xRa$G&_Z2uxXE ztq>vA&kDh-%mj_;&t(Sf_admrH_ayS3$qCzXh4KCw+)20Kbmd?h}_)m72QY0%u$`W zK0S_!gN7Li6wc9sBdCvMr&u4wwS|(1SZ83#2ekZ{LEx7iK13Mw(ZTFA)LkYH4s{-9eki!ch+E)8o5`F7m%PRv^4U8 ztioA5U0(cqv-Z7#&M*L?83yRxRtP*~K;WSiQ_7`A5hIKk0U|qrnBh#K!(jj+(}8eg zd3YsjRa@&X+H@cw&FluXG!$@YD6p)z2`YQNfheBS$&6Mdl>I5AXe}oX4>rs7rK~r& z`f?)&=6IjsXJiq9YSL&mil!OOUpazycXR~p_vO_mnJIz&KOWr3AhDzpQu}KEkKfN9 z;O}k#4e?S+v&wDN(ah}uG+<<0fLx|vQVjvxa5B;-x=STbZBq+1i!-_%?FCY?$a;GB zXH7_q60qw=mW5wg{|8EyW@~ad#`F+>Pcfs*vQF65VHCPTz5EawK35wAsbspKc+8HS z?%JkxrVEVBhZx@3M1OAmNr)n|LZZ2xN$@zB*Ebv4B^b0w188l9geOhOMer5QX*zMB z4O1KpmDy079u@16vbP8kb=blT&sjhn*Qud+$_9dxb&^oLTyEqvN)Lq#AMk^n&H|rq zutNT7q)*UlAw7aed`lE~lTC$N}gz1Ty6THftc(_^1>5H<_fh;^JhLF6&z;Ei{ zdgg8qcu6O!WF002Q&}pf!4T%2^kI<2d7IUh^bnTA24KKwL>V#+e4w@smYOP|v}l^f z7zkjN1VVq3(rATc)gboNmSK9^t z`eR~BzB$`P&`Tg?Sj639|4*+ql^KT?+goIC_K?`1aL;=$z-|8*$fgQF+}_X&JzHn6ZX4859d80D-ye5AR~ zVI;N=hLMrrRTfGNFu!~!$Nk9mA8j1)15-&}DjULB(vQIb1^S>&z^7-7q=hhwm8Q4k zRj&Rko|^S?d*wSG@Jy|a_^Sm`q4K!%=aXRn%7;DESmjpbUoJ0hB*lEN^>eVMRK6TK z$_Y$0di3zq`rY#5mWfR}t$FzL7gRK-H9sS6OKHv2(Ia+DSrfp7Q>Kr~s4Sn*h}!W= zjNYg^AG7;8JuLQ{$bL#kr<^#Kqet9Tv60unTUsQG%BH-AH8=kT-!ctSAZ3zRNa&_$ssR!^BKOJVs+4JG zt%N|kKk#EiBP_VV^EE~AQ#BJtUTrJUsy0_(g;fOEK=`_BACVlmZRChu#VBYq+p{rm znI$IUjE)%i=p0@+kA>=+VrW)FW061Z5!e3-TXuy^FCzisxFF`)@|#SIk{v+b%LFlp z1X|yaaGXGWjT8Og9^Wz~aQR~*TE01xg1pj>AlymOY!?MQx*ldhVf{*4*3nB#V|2pe45-4m8d?TFbRs#1%eVaY4KzwqwNot7oUBE@2tadf|o;jQwF&f z$^OV;gX6I9=4^D>;P2PVPm5>EfBzf$S&bI_{Y_YI!_5;qVKwUA94=sdYr+W6m~0_O zPh(*e@4iy(}2mgm>9&%TVextQq? zMK>r|fAtb@Hf<=->@^gyB=r&$^*wa4w~ZSam!TMOn6Xck2()sF2ivz8V-EwW4hYs> zfxt_Wy_ESCs%${uA(IGY0t`e$h}oRzO*Y``G7ck~TZAqklv5o9Ug3Nt7py;CmV9Vu z6R;OUA3OIMyQ~95oZ)3hQys(9q`v%rP;k* z9QfjF6eHr$pL~LPIjeZJe228^;$mIsB%G_sqyvJN8aRdiaonka0RFcE;?{HY0wGv= zngy?N|1igvW$tR&z|KX{tb3JHgzT!#2mDlF(5tytp#j!DTNi|$I99q<{l8tV-eVM1 z_H^{&GoBx1LCCMs-ZcwuTm2z9u#5_5PS=D0L6!u9kr^O+7syD_`MVq0%g_Z34gxf; zDFXg#4ni1EW)KM2s}AyCF5XLQwp_s6bU9NM%K!4~BYfBOkfW?AlrJa#;QQ@J-i2D*O-QGCx^t2Pr^YvMn4({A5PK}2TY6SCKb8pEU3OjU=qvFOp2vL$@ zHh{{{2@P*vFi&Sn=k#X>Z78qr2Toqhav8a6+3ZPJJD=Hzz`@D#KUS9)i@$AFvej&{ z!75-H4PX1C*r>kIAtSM;o9@o;;F}#0y6dDTpX2y+b@{e6(S^Gz)Z&B!v;$c1z7{9C z)Z38auH`BwPO!|mW4Onmk0Y}JZ*#1W*)A8?oxPY{JWq8}SA|!#;Q&xI9Lk4VM6!wc z3J0uL(U@!^u|E_Iq#6-SMAlb)k#zUX`s)31L4b(JQDwDa_#k-kY6-d11Iy+eyab&-NvO9Ry67 zfjg;Jv^%#P#dV4yJ94cqB*H(xmJWcoH$`Z}u4z+H2ZdA6;7m{eg96W|*g`Hfv`{T= zQbXIlJ*YUbkGPFQ+-?G;Z>IlTYVc$!OD>8Pi)hFi1Hz$^#$PgscDw0N-5wAMMwSUe zAl)D`VgNAHT;hxXo=-iexKsqH2-kwyX?^6UZ+-?@_wmH&Y-H zQeU~FJZ;^2{@C~yU2_g#FS^F>O$Ja0Lji!O3~0yh9-Uyex3dDkYFX@nV*2>{-jPn! zF^`D?nJ^xQQAFHQ4A3%87$0pVv|j(;8?L+wSwFWQt3?6tGLs0iE!sXp8yEy1Va1p* zmfIlX^E2|^xFIy_-^%@Eulo1jCBnYGmT;=A3;1oF`G$$2C|~U;wdw$YvEY1G+&L^J zk}DPPKJe9%f&k#T_*V%7pg26^<$xKZI61~%SpI!4P;Wh&0x z&?yWm14%Uv3PpAkI;Z}{O6J{s#6m^KiO!Kg&HqemE$Q6a;iaycJ{BLC*=Xc%Z3Cl5n}r7+-Q21BF+ zZYdo?Ed3l7pl7!-KP zenBOjqKYJ6GQHdUFrYvV&~c))E|5C^dh>qq`|9mm#|1oGn?#=rw*WwE*iI}{$BG4% zK=B6v@B-&Q{d+OUCkz0mZ4zx5*l@Q{hCE8R7Y-q6$UNXxRt20A$IV}sUpxj7 z&SY&XloaK1lT?7-B+81+xP)YOGlgChAZ!cv) z@2jkW+ZKnrI8TMw8Zx}p7$ z%TKHbqe&SKyu!gDtXMp}`GO%vuh@Ru1^TKAjnG5x(7^K*DMrv3^WKAwMkLUxKC&0ULBUs4eIRjeSQmyD z+Z#Fk_P0yFT^Sy(6#~^Tg$T(m8oPEIEGJ`_G0&MAva^fb* z73%Hu0rN(21eTmH==*^1)F>nRCIQSVc~EZ5#^F}Meds05T%cIyp!T_}pO-ZS7waoI zCLY#h(G`9$ia|AIlA_mXgC=+O8Z^@vQO7LImg1Qf)6F}`#P!) zp(J3dJtfP$tDk&V4AngzFKA@WjH^z;2uW8VIIH~r$D6h6wc!N9lLfyuI6yh`79pf` z>4>8+e)b7P7~NtUdW-?PE#Qx7JDW zqd8oFV%4MoHb2Hh0JT?$pRqlQ+SoGGY=ET4^xC4X?5i4vAsx!_ZLpvhkO!pmb?W89hi^rlRJ78YJ@w z0Tr4}RU&}#NaiDbmOgViSJlT;+-G7A8hC9TTq_#}eVZ?rNe1J%BI@I4agatCs^Gex zYY~#H6VD_JyIMTjUdS0Z<%(`c479EhgO^H-cp?;T)mSJAfQ52owpJu9R4n+q>Dg9y zei{nnjDu5>eqTw1&7CowRN%o{Dezabk1E90fVS#4&fC_^+IRCF+93tHQ@f5C(pp=f z!INHPKYl_gDKjfOJ-l<*tCs#M+v(|{pCRN=H=sqkQ19-f!|Q12W~OWx6=~Mopog+2 zX+p`9Hn9uH8Zze=wv#=3t!hT9?0}K#(3_0h72BO08bsPFq!q_IBLpe(Q4@udnN}T7 zT@ljRVbO0V*W$!bg1HV{BM304?az^ySWQS)!|VhoAY@l$bvkYyzJ4n+ZXk|z(d08mIp zfyBrt^mpFGPezqHq(1n1jRQ-|Oc3zTXGEsF0>QUTcVMx*Fq=ildu-UZ+IkbMHyY7! z1yer$10SeWLX{Hw##n5AM3d;QAgD16LBTFE#6>!p3;~4hwwrEb#i;`?n+YJ^TXO-= zS81^Rv41v@0+zk!(2YdTF%yZS?p}MuB%8&f-j0)K$=WI$5|j9#w(tY^fNHo1H(QS? z1a6x`#8c5TDS}S6_aEK8#yWHq?UDzUe7<3%_&^PVWSOoGp*H|URFULWW+4I2j71_D-}G_>q|!kroiz^RA}8=m&<*~^MtIfJ5Aoi? zNpWJ^J+`X)Ez;dt=UtmkWkD|7jdHRK@~KTam6mdyhWGJzvq6+0*&x1RVl;kaTU(p5 zP3{@Oq=9R7+b+hp3DyHr(P8qz);bH)86s}zSJ|qxTB27g!t(=M0NOrid4YkZ!*zbmJ^pF{&>c~ za1I==PhKks6nHxDa<05{j6B-X z$bpL1mJ)`L@cuD0@YOw{{W7E>OjJ$18UV|-R>d?&-DQ-ZmII=7P&PhO>xJV)$p2uu zH3|T*;sh+n_XtD>LgKsO)ou>R7kRbl9^uwS0<@Y5t0W!|%k5eC5A zAouvLpKMIYAtMN~gCTXU)`SQ`fpRt@JI;(5JsR0rhQlFP2G~+_f&``uQFhGDdottA zqsmuQBBEk;Cml~e!%gPjV`I*EdOFm%W&}Y|#SGSgJ2etoe@6p%IT-j@e_5#!d>t7< z$;pgp_YVaRir+LnXz#>u{Ge6{hk^LeGR~cID)N4-5KhA?gmypkmr zTis@(+Pvw}<2QhBSk-&Ty&u~FHtOCCu#dq^Ykzn&3$TwhY+!)xA_%2L_L)Q0+L=Om za>pN=b@(&oMU{aON+!a=-8(vq%cKEcYDL(L8+5@vFD=6M+g|e8d?Ob-m=vCJ%8hC2 z6{_Y>I^dJFA&{?>1P!U?O2WZuZ+CxUnE}Y!QH2eYH^ThoYAvT?$l|0gOF!M*gM(Ta zHclNwOo>X@P;dpLy39Y>O$07PC~!Lkl->L{1P=5QFP7^sZ&yWty?dJ4VHjvfI~f?b zOa>yC)zo}aP(mXG3ri=;K7=+DlsJPye=r1^2i>J>q+2$OthdSv+)k_rSl|;)jgUnt7weTw zT>KOaqyq+VQ40w(R2&%2LKFfBChqT%ey1cVPSHX^{KSOxoJCX)iXar(KuC~mAg{4I z#>*uZJ3CUK0(Eqam$W&7MyRzib7D^zQm2!`fRbyf#n<&If!juj!!XA?Qvy&r^LVyh zEgs!`S?Uo5<}#pfqk0+GfVbBwkzvrQ*)HQ&iB_1^oB`lfCIUMjo~+-NOLFi~O@zKZ z;Q9Y|2nHFKy2RV=xymgGF#NN+Hf z5{nf7vE0aK7{ynK-@_M=QQRT%y9d{u^1i8SayaKInI+mR1-7&#iuIf`G1Q2&Vwdw} z?&ZKX#QNcI;M|X14y@o;)+Ue>GeK6|7zhl^Q}f(D1OJ4hn`X>cG3j%RbPCO$UF`;< zrq1_QNqSYAY^UNu^0PP{q*V#ZNhoVdbV7$1m03rYQ2%V2ueaY9Ph}_0VlxUNP@5)R zVz?ZJZXkyQn$pOSebq<|Gv>Iv!zt5Mx<4a=Ovp&QTy3xu<(r$!uf1&6jngAl~ z2|~H#VlEMa&t~&e7=WP@2Il+^mLJPQFC7MG;<_s+=mK7E5TLe|F4(YZlL&*y?Ikz1 zSC@5`gQ9aoxHyxL7_@TU$L~KbFRRr^e*DD2;mjiRfq-&jj8JYg(HN7-k`biU^xpFR zA{=rfo5f@7*t7Wk(=r}}DwV381@X{`D0B~^e6==c)o)=IrPBmao5QPZZHU`9Wg?&+ z!7L=%jaza!yI||9k3db6!z>5V@cuCj@YQ{MM3L{){Z1!BT!Mtst3F0gi9=w1UtRf~ z7w||;0{k^aQY8Se*4h$CU4~900PNC*6cnj0%mtx4aWH`JXe}VTWU~KZz1qA7uPm-~ zSdak~8Du1(96uUD_aC_qBMu?SDIp^pSv}p}VC+bCYL_kBWSB&%S7j1UfBQTBK4cPy z$KZ8JCCWKkFpKsOk`qNZvy@2WF!~gL?qLn5r^O<3+Ap|LpaOQtHy0jl;H)Fu1A>gJY(YNi9N8Bx@oDfFp0j(?N5wYJ3w>yah zZZ$>W^>Xv|(?T}t`C3MfehVwG!@xmVk~D^x0iQ55@RXsU83g>pzF-vE$f#Z&gepYN zJ-cUy$7mr7B(Jike7gL+T76mkwpsqi&9@?s?u?qC2nmhQ??e1*rOfVfU*jm?$iWk; z;zH?R3X7TsK9-muI-;B>8(jN=k8C~r|;iSSrk)?vBJzF<(TgMQBAZILw`3seC9EC6f zKQzyQs|>}nHFh?WeK5m8^7w(72W_J2beJ^lo>4H(gLIya#4t*xK+wjf2b4!4*UUjR zs^mb>Gm;mtS8`k#?&z!x7gMA3b*|aB3w$YgQMpvRP%vgIM0W;8AVX%2MG^90Q;2wO zcvcAVBC|n{oRp2cE{hHS{Lqo}p(KWiW$1pvL$z4quL^};&4%Jug9ZJwF9HZ63hR%H z$DcM^M+TI*b{F`oiDYz}O$69vx3P!PU1;Qhm;_DXy=F>AD=LJ!X*7gZzwFEP^}A0p znkuJ1eEe9RlmvOJrNNOQ>0U)r~LC8i_XipfKQ$Sw!fL#GR%* z-rsDbGw#Jow$uIpnR~M(IgTY+@cn*8%8?f7hrUyS+E9gg!$#s@9~92P?WdLmDgk68 zQHiOAAlTCXKBj8s;pXPxQI*zqLD@6*US0EZ?a5H4%M44;VGP1;FF_fQB75} zysjEO`gOfJ7pUb((`$T46lmQUKl=6T9ZHb1zB`B>Q`Wcj01{|yO#(Yga-Gw@Behnl zlwIZ^eMs_k{k=MRce~isZU0Vgnkx6ZYjdcSqugswNm}H7jM9vvcv;H*SodU&v_bXM ze&*o#;v5vcrltmQlopby&CN|+A%UMtOT07$b$i_j#c??b>rTO9beCEB`sPx0shyq4 zXut0l@3)Ae6>X+qeSc|N5z;{iu;<+F>y2V2- zn!#}Y12G^>iIG!M3f=O-zGDmCV8Ydp>+pOP>}#w$*^_s-whhdi z*d`pR<8GeR_Us67P{Uhxa<*D5zWtc}60$(*MXltH7y%Acv6cxAy*ZW)L>OGcTgMoo zwVTX{;6vmiDx5?(V-OjE+a?wJA<{vMs8j?XWJjY&wW{MHx|IuP+t zEfmTF&|riSY{||n!Xf0~!fO@EfmIoZ4jIDkEtw1e%2~zJ#pU{|L|~sK)cd@cdmJ_r zA_V@Z=U+$&G|0xFcR%1j2DFSqcCnESL7#9C!WjkXG-?zf55)V&FkqqE!POWS2H_wV z)Jts5g>DW=Qe<5~ztHTTi_6bznb_r(0fnwXz+cTEg0FBO;1em-mP_@V_~S0)D@7MF zt%{vPd|ft(p%cj>Z24G>!hcDa-eZ`OWW+4FG=BR|FtXvY*ceCCV<_ zF@&H2#cK-Va~W)1T+SY@Ws?%W!@DB_TfcG@_I?%gcVTu?aa^ zLk*QcH@{Q#UCR?Jf@WXq_C3XM69|;8nM~+u(JwwcnHMo*XQ983o`Y}XHej_Ba2$+4 zjPw?7BE#XaCj%qcO%InD$-w7@#zoKrM|WA(P>TXwNP=^Pfa8W}US>Ln=NOh^Xx7tTz=R8~AxgQ2Ezi%7b+ z_JX34X+gcwpaFLbhB#j>n+6{8FmQ?ceK`Zer10hUp&mf>&-W22@wffjBGR9_*rv7_fRfFzSwiag(HV_#zx>PL>zw4jW!Ln=6F`)l*wvTE;89ER67cZ zy+Z&1UKU9Pt8yw_`1}=dc z&Q9>WQ3KB_HJnzkMg73p;F{YbB;`6yx39SZlcnO+=3;9&j|NCtO%QL!EiE|Gkw7xE z`lVcyI0EU~6!C?Hc|h>;B6(u(Q_*-=iQ2*!!HQ7>*O6a*#9ryQpK z{ztVe%i(}u*5VoD0xzxQ0uU-4^lB;&dCYlyj3B^(k0ltk9V^k1)Q4D@b>2&MQOO;V zz)uwkz&Smz+bj3z=@NprRpt9(su=KTa^dy-X2s2`!U6>RU^KuBY(O2y*svo8gI^&V zNU4@lv@r-S14Xca3Rl@4WF660S+mpV`N<&Q$F&CyE(7JOLC8eVtEncT;u#`*g<|j& zSw55rxNY_jP(T@)OnAN!4BoBRe(V&lsab$mn2V4Ok8-5M$Mb*XaEbW7k>QfF`G-q6 zKfj+l6C342nPlX+fjdH{(9s|Ffm$R9 z#c<5@j2$a<6!kdLmB1Xut zxbcBC$HdGkT;PQ8$gCpB0;JopK!Y0=jP;my;+H!>QEH?qV=}48P$r{+jyV z#?eCukse(xzC$*W>7ej68u)3JOm6FM2NZ*SH91(?+mNGoR1mSvW)fgP3}{voPY-A1 znPCJgd37zLCc+CjM!karE3bnl&UJ*a8gH#hk#W$gslIdXu$XP(j6~z9ti0X9vNlTNT6|? zWxT*r_gAa;i_7zngyX9Qg=2+-BIKNSsAdKJs-g7=D~1fAqZDQTOm?LxjQ6BTW)%*! zd9sj@gJ(DbF}RTzBCB$Z@5Y2HxDlrmxHLCH9#bh;@X1~1At)mK*emVpt704-C#@1AbLyKK1V3zx{)nZxnTeeX^G0bI%K9o^* zj%OPk>g&bD^7q-U0$Lo5U?e03m=A>!*?!Nq_12cbPuy!(65mqsAG= z{L$Vu7A!FJNe4ovW03?{$-r()#X?DY;J~cdk z)S$pe6HRbw*-*J~u1yP#YlmR8jl?F?D7C8VwsN4XXrM$1jnFrgZG#-i0nO97>~eSW z^_xtzUwCFX$3>~G=1&GE4s$SKpcp07a-!a z5E@2nK;i!Cx4CR~cRv5>DFI_M1HmuNK!Bm)u+@}e{Pa)hGUbaiA%qdi%d!{!h4jsMgWc3 zrUCB!=?JJ1fK>!^6AeP>fZtdkiL1;4o*%uO{kmG;eE9rD+8VcYOO7@S_-h*m+^J!} zUsW9Ssu@_15J7Kc_d!MAt2RN!hAG84a^4(&|?r9DmZ4yF$^7QTz8DlOIFH&JF1`rSmnmIsTQzs}Z zo*V$}-C{&0Sr#(0*UR`bmRLa-4F#<^L6AT0;u(wGPjmHAE|*ruh!M9t5hHDYfl`PN zp3Y?-|Fh+nul~przptCag_jCTh@Ek#)&drsu`>%k-6@(&40WrS?qvi$ zb+%Sqy-g~R6!?yl$X^$i^VRw6$)esY+EarNsL41qwu%=rHAZA*p_IY*jRQFdCL zVbMg;fJTk}=HbL8`^>kpjhAd^w!Zu-XI`L8Nk7`56R=DkGX&CYW}r;!G8y|#HabGqpDhJ*

}=_)i(Xi2m~_VjkQi_wV{{0cD|H@2F@BX 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

F;f4o@0`5a?cN=aJ*M~g<`^W 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

BEJj{kG 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�< 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


dWGC{kzzT_hew< zXmqntMws($`~qH zGhIU7^+zwE{0=o7v>6R?0y}Wu+D?=c_lS4P!FAex+%9Ma;ZhWE#!%RJkp?bg@_Zp1 zZ1-lJqIq`~)!P{GRT<{#cWzQ*)Ix0To|cj`26&t^kQngdbOItIf_Nph5WJGp3JG|Z zvc?xiv0q;QdRtUe@hxjc;C0$*F&0Vj*ih3IX+$1AVhWi%P|Cz<)d_N?`Ufe)jjFh11eGeH*40Q) z!w5=`hKwxAB2Jqu?gwMQDND6*l;uE_;Q!nTUm7(Lpk|stA*ch&P#6MLYFVVJ6Ar%q z7Id)Hyc725wT)nH?qb>7Fpv^ zVc1e3Qu7oBCBhJjxhOJu)H=oJmWS|0juCIxSD$BZR{to*s2~8Yy=6aM0P)9kLQUkJ z(NiV^;ms&eYO4q`E^Wkiw=&L`x8f|{tUiA^U)^@<#{B-3MTANO9#Dg@QC2bPEDrqQ z2pI$>DH(*Ql{nooiHt;02U)`g>qyFf{`W%aGF~3qjBxT0atd5uOXUxf2Nlh(RN{03 zBRCFx+Gafcmn)g~Ev1>+v$L-#%y=~L70f2j?}=E_0p%2V6A92fp~^Ktfd{j{>FRB)C~7`W*^ghe2Xwzxf)4%B3s@ zxA^G0Uh$lo3TTKyPz_XZ(R|gRaI%z5qh;-$lUs@LPu8+8TszP|c@`%qz|>Npzs&>% zw8OkOV`(MkK6O{W2=I_%*&sGMX;8V9gy^mP2;4&*31aM|t?Q@J!+fK%CdBQ|Rkg}! zpC3dA5rKFq+JAti+b3Y;pZXQ>_Z04PETOUb8WDx>S7_6cjz)_hxYHJO!h=KGke%Gr zc^^a|$o9_BkrouynFDiE?Rl6ugm+T0;!$Sj|62UMk{L6vPi3IxYUv|fE?#W#5E1p~ z)bDmbFg|3%O7;5#J1Wg&Gu@dN@xE;xg8ONlm-&!_;Tn$aygD?;Y1RwbI%IaTlI=sB z`|_1^8mJh{s(|?yRZ|On6<7z%Q3_@&&Gc_NS1t2mt zZn{WxcaS8eU5AdhH$KEQ(QrzQBZ$PKiI__PAkcmJR zciiW|{LXrb#YBaP!Cil%0@27ep=9{?^=;7`?c#h~U-u$}xDL&%VE{u^C-tal4^b!a zJl`2z&N(H$rp0%0u_L@oGv3GOd>N6w{?)3h7GL0nMk%pTu`E+UkD6F$AlshLlt!Fu zG(}z!2R#~m=ZH?mJwq}OcqQjFKCC`VAR%jCeHKKV|Adpezd~%piV-CAREYL@(<~0W zrmBntKhbT!Ee%*#PVtLT0E7xaI`A{TlLAvsB4*f@MbAHp-79W z0qJjEyusgdF9IXiPI3Y6G`kUcdroIdIB+3@9S%Gm22xvc0b%yCaB$$e;2-id?V;W? zqlm4!-CirXfaIAhP$G4gm|ZqKnLoaNatXLT`2;FJH@U#O)#8{Pr8cc82zQC&{d?Wx zuynSaITl{fiGpmWDtk`Ll>D`q1Yb8sI}9C&QUa%pB5_v(14O9m#yXf{df0G$SPV{a ze2}NO9_LU8Kyy>r5DEbl#n#}Iw>Pq!&EplvEcx*Ve&ta7xW*zxP=qs6Lna@%5(_^wZ)8G@*7HB#4HTVa}(s5MW}gz+W|%8j%aS zz0M%V{bG|LQ_0N}&QLwZOEf`vbRpr>&Fp14y3u#YIy#_HCKI7g0feh%LhBBAY2`&z zGsi&a6-K3s~VkQGJMM13gd)cFq|L8Z)(z&(cs-P8f{8+(+xu6eCYbuU$JoH%}v>} z0FAAIz>f`xka*%u%?Q3KjYGLHnGvZN`$I9{dI=wHWi+Fo%+;nsi9nfeJ-(9o^y1rP z)v@yKe08xnle*veYB4)H>CUv|{jxj_GNxLM*ec~bL9BC}53%rGYke(a$?D~waM`Fv z?S>u|pk0fUN)w_-i}1 zbG!f<%slYmDz?)cq ze)@UM<$GmSBPjuOx>$aG`!_(CyI2@|r$i%+-@>PCcd@`;O%!lZ3Ln5FRfV~88TwbJ zkZG-&UKh)*FR3#jFo~qFh+d6fLK`a^}BT1)YOpLzonr5K7_UH5RG{z=FY`LNS>T#fSu^xk?CTG7gUJaJIN} zA3qyW@WJm&?s+0DZu?9CR-1>K1h8<%f&2M#;h4`nnEPRUI=7rWB%lt5 zWME1Q&?n_xAqT}Nr3H_2ZRqF4r{(hV?Cr(kf8TyBhTi&Jck$dh#*Q1-b zO&-uF@NSL~uU9vxGN4h$vaLRTl+H&dn9ec?fcaJVy1j(o2$Gr_-ce_Iy@kS1dM;8~e4AiTo?A=$+@ikJwHEUfoTc*6MiTjhsKE<+-g6>YOSK}XzCLRL%^p-fgh0NQpqB#-VG;d5mCxQ_NiDH(08tpkQ)g2|)FSdE`Bq`PIiXzKg{nLCH{71Xm74s98ZW$D+EzGr@C|2{B$r6XF2J z$atbKzbm7GcX4P)*Mdw6ygd6|wrushK3^%1>c-#@Xe-HXA~MxJsDz3EZ?L&qhB^^`tzrp? z91mn!kJH&-7r!sAB=B-Xzz=F3;IGDmU==u1TLu2A`e3HupeW_OJ<2eD-x%TY&RHu7 zlsZnFF%E>P2tRzQ4#3eOVI(qex<3G!0NVjDkM-(T~DBg?)6iTJ8xFREDzk3PHt zi(F;_Eo^5Nz^`3Fhu)bTpfYMvqz%vk`{yH>g`<1Blkwot$CAhD7I0UNy79U1;gR2x zy1U>JiJ(;gy33SXI5(i%P;2z;lZ*Li3Ekme~1RC+fGdw$50oA;}w zfI(Px`eSxNg^;4yDuz5=%lPY+EPRAP>0z`f7nw4Nzzr;hg0nJ>4DqB-xEv_-ZV?pP z_lEV5R2DXkB*>e4inzXNrUZ|&O328{vu}dK%}u#@1tVC)0iWG)z^@I5P%uT?%tnBl zaUaa17G{Tu|r0IH_j-u)ge?G@CxcOiA0vjlhgIhjbtF#vZU@u?=A4W8U+0MzfrOs z97RTfM;QeS@0AUZu`HG2!6p!tX{Kd#RX>u9B9`7dY?byz${H84iV>l%^1Uaop5ggJ zzV{FzSxWc9h$(!EX1>RS$jlN=Fs&oRC%{U0GdTIiNZ)OS=#{hJ>`OEt1;%xSKXC~VS7VJa=YYT;V z-I?OxVU-Ja=`Y^Xr@#8a+lzJ_7JP7HW1zGyiWycrP%QAC35p}fF!Jz(QG9P+FrHF49hX?~sCm6zvC9;5a0poEnJYC7oA+J}mHTm*4StZf;q=ARV z3H})V@pny#-Szw;nl2XaGntioK9c_;~*1SwbGZ%)y=siJKk~Lnfh*& z@c|H(2nzgke^HK;qTFuoFXDnw_}D zCA;o$_>g1KLx}=!HvxsN*Tma8n~+tnXYbZhGQYX;OlV^OEX`Sj0S9di@KSL zl=hIBz}sbU=od%>3~W5Dfk&mQkVz>r7-i*CJv_0d0)A^SNa#@JIYU0PS!deehX*iN z`3H55f0ch9;D`VDPycuJ(|^d&R`?>fQi@fM)dT*`42!8u5=P);%E-Sf73MCzFj@@uaMz;3Wr5W1Rj5&AzR}^a zPVm#{fw_??WRYR{>F-e6VNwd3ZXX`6oBVpeo`OL{ z2Z7V(AmYF*wA6A;966Ja8BN!+MCR4axnH2ek43_54H_Oc%Yv*U*7~R{0KqA3>Lu6= zmvZk60-zZd6~HZrP|iM>SSH@MD=n=YD`O61U2f+o@M(=DXoNulZ*1}qx{+|bA%W{F zRxsh;B9tt*Ro%^TgC1q8NCS1$D)0%66*1?guWW`53(2)V&mR4Hc6;$ZUPzf{;Q{fT zthALX*$SPkz^dkkG6LKf8^)O{!|m?(!(+clphJZ$+r)ds+!Jrxw3Wx-WI)c06+=gl zV)a1zX;Kj!woN6Y;rNj)1<&7t9vg31nB-OIfu|*UgrYIElJp=XSv1nB8Lz_REi&6o z#i<5pW>D;gS_J4*GeZRc%urM&M-=4{Mm0EwRndX;`&B}O-6h4NohLi>G*`5K6o_CU zZ5&i6X(4jI4kx04s99w-7*_D#rVBpC*?7;UZRnuZ)8|E|fmf+fpky?YPqcT84cfHH zY}j!AJHvD}3cR8jMVz&TcT@z3g)<7w(V4xx{UTdP_?|{E$3S4qE3phF0rDFNc$T>j z{PbQyc3FtPOHCMwu^W_uO{X#tjtqj-AdqgoT4FB|FOu@SnhE%EFT{57Y}K9M&Q04Z zkFjJ5b~o-%KmcZ6#D#fU_11v^Dp*ZoM)O3Q{mhhkvDVWu1&>l?s+A2n`G2M8F4S2$ zS_HfNnVBd*lMWwa%hMMi?#e^9!oI;CXJ7P!UEHkZWtQqKgWZGPz%kz0`R_8S0fCd- z>W-_$EnKYI+YHNB&>=DA!O>ttCgi~`!ox)=`;kL6(eJENnhfp?jjw9}(nDqe{Ima=wcOV{S zq`;w(0>3xg2*m+hRBHsk9l_YrHhR;qHzGyckDOGXP7;w!iji#t+s1+48xEoSwXG2u z2Y$rq#xYcqwn}!waG8t)-p27^WVbwg`C24!r#22aGscJQaLAQG6-(Y_Lcc;cj-Xy- zLJT#ta#dRg0vS+65y4^L^$Y|&ph|g^7R~T_#Bu5=5`j0^y#1l85x;Kw3R(2}600)& zeIZK{$N;jt`&OJV1cSJX2mq#20T{s`5`lNw9EiY!3{-FbPRZt` zJuA)>@N+W-zc2epNi2rc>P&J5d9b_djN&04phW;1!IiH}gPJtgxizUw(v>OP7BVU+&iDCdOEu4;> zD>&5*r%zV8kdF*Lzmm}i&MtgRjZba>Q7tvm3UPxACeffOh|(EX(;eo~17AMZT|u?{ zAWv~4b%uc~iUvM#O230z;*_F6T(U^m;*htWW%^bz)Rx0B6J6xjb!5F8#Ij8f@;Q!e zgi?ua=Fd9xxVQTg4vIMmZa5!4lLsUXX{qQDds(`Gyj$E}%-%^4Yq>Z+jnUv}b0e&h z(dI@*L(OKY`yAqC4IxGhrJHv~B=Dk5N2=4 zkw{9QNDxli2yeGZrm-7rgks4EJ2+C=wo6J8@0Z^$Weos7Mgb44Ie}lBo`&QTuT~?W zTKFSs5t@f)D|^@-_#2B6c+;j{=w5yAWU%86hZR~#uE61Yv96gX2Okx8LPO(gE}s=Ym!@Lo>tv3vCXb!ZG!CgeaFQ+FPF1F+iHqSFQ9l-TW?4TO*jAz|NfHKDz!uhIa z>6gXzxkM*2R8S&)S!hlITDf~QPvw`B$M}1mr-Gn7h5DQJEA7I3jfwcoDb|PaYxR8J zmCx0kyEtYT6=QLXUXT@de&{1RKTkzJPQLPJu4#b@6dtOE+3i@uv(h{jG`;2?RXV8< z1=$KH*N|L4PsMQ&hlYYm(LFp>>Mc`y%-w1Yf^7ck*1?vmEj~NOcXm;XIATiWWg{U59OdW8&p`ca{ z6r46_OPw~{mE)9aTJfHQ0$<8`>z!lUP*9=B!thhvBM(1p263|Yw5-e^i(198_^ch& zvW;h$Y{aC`0fNIyC=3N)69E~c#eh|qY|DFOq2Mj5$R%_=<3qJ!xIL5kDN-L>;J=O) zcuI`|38JhBwgK&_S&?0a>xI`pI#vubwA4OuD?bw9JM_ed0y@=!X-?3I=d!U*V@J`> znuz$ZaUx8>26M+^Ip8=wj@h|aFxyv&0K$hgw&0dnM2g3b5x8oU5uteQ7y&YwS>P@j zL^wtm9NF{lrn!sC2$ht4y32^D6RB%f?E^}kQk;ri&h4(=wG75NO=RdKFrcBj%y7=ZZUgtmYOY@)jX;R8K zKOu*)e`XC`w#FE#q5F+IChX3)OGnX(t${&vh60E57!|;mWECVXOF?sby>`Jgl0h%c zb}=tECRb|!tGI}xz^t~sG@5MVf^#M_Kjja6P9BZ^UMWS`=}yXPokSPZI)sNyE=T|82{@py6dsf;E3 zNOZiQ0m4HiT5z9uM$L{KXzS7BKzqLDyP22?Dj4)H4vd3ht!*;g{Khwlw@HEPl@udLL?6ONx@@$I2JhKN1E*7P70|%PRc7D~orLx`>%&`(0uq8v z+<1%{YA=(iky37TCxmMA;$gpY1vhdqH23IhBS1S*O7usw0gMz_$e~hVO9Xnw3B_V? zG$XdL@l=iOmY$&2Xyy^ZZM<7e1_gk@1wolih8`3&yKETkguLICfcJ7_n6f{eV}sa3 zSxfd|ZBsa8lTYa$PNet+*QaQA zw3O@!iBD!Q;OAzWxL}KVz)(2R5|I)Su*QJqsN3rqy*SxO&qaTpYlqL5>#L8;+4Cj# z9d=~_E*D%&wToy%aEB+<)~R94@b2yWXEgFZ^4$t#7^RxfX6qbA4_M|SFJoRS`f$)q z*#Kra3$@WRaSO>^7tY{I)S+D{q~aY72*(Pg0&PRicSsSPIDYR-W{L3Xw}|cLek(3- zi^enqyO-Z&r`^|!Q(rf9wgLbR1Rko}nyA&vi^*DDI&K%!QA9EwqTj;l6vcE`gvTdx ze0Y0>739{a5?x%a%Kc?KBSoBY1Uvw?kpk~xQo*;r&q$FpC`bvRAb?{k8G}-Gd{=h` zYC!Q~5Nz=!ROC6Ir)J-xDu-N2BaHu#0g>YnW3ZbpX*V__@UKb=)q;DHIop1=# zHSnV5IdPP|2KU!_vcCGG9E9wL89F3**klB>wPwUtTB$!8C_ikaWF;fDh>dMyFE9d4 zP$YPik>GM*?-u_k7s+#S*~TH1iX0>*M9m0w- z+P8pyv#R>hR?Gf83?tJeNF1rEKV9Bjtyed*cPp%A+|DtcKKlSCF^pE(-Ge5#a||@T zonrv3hR1X&2@JOkq~;I(_H?@sXkQvvs{hD3a%`nmOEU0$Rm)@i)X}kbjXfTVtIKJ9HXtAJsQK2={^&JvI(dtrk!&|}4EoQk zk&mU%Q5K0>eg1V>jJG9d22NWhcf)|&b_AhBz{^wErY9K(`##BFaM$i$Nb#1E0pGTR zMUEhXP`+3%XHRcGFLb>uqe;>6l39YErbHA*DT{X5?&MU}nilwhswAGjt!-Ezk~&T` z2Vl#a_rnOBPGEGyh(L_LUauDCvkxK-#irceXEiRaJ0h+Cl!4(#%!cpOy`|5 z6g%BrX7|Gg1++$0A|UZn=5>9WeYjeEkqJKw7i0T9xt-I%&)d~>TvRgy55AZGEHkgH zm$bbM1>scdz!@V%9B-Oo0Fo|@kSf{DOs2n<(G;F$GT|V=Uk!o~LV$_}0iI;h6tqJj zAg8xbiNk$m3(GRQDwKojWDG|3913*6EtC#;WrqU?o<6y~T9%7Q`Vk6kB;tZcXn)NH zezbdek8;+E#*=`8Jj+l-?}5|CiM_5!;39{`-?_GGYg-XKwss$m7hxKY0|SS~i!2jV zxXHZ8ZH!qs9^!lFlMShT;60Olgg!SSKu?N`92S_PAf=h}^_8#HwQ(RpR4zk@o5KMY z84i3~9jk!%o{WR6RPWaj%#MR>%be&)M4&^4fJZr)_^_5GHJ`|s{9nucxLwM{mTRx0 z10je6&eS5ocO?i4$K;eNePiVUKA%PsyzyNi1bN69;Ma}@PtL_l%-+bJA~%y}zgE2o z7S2~42~U@ovaQqX&F#mJy)o%epS{7C%uznWrWECGIw+mHB-m$w8m7!Qi}hUXv-u%n zy;Kc&h+TKe#f~24ufhI$Z<v z{p=%NLn{5Cbo{<1V^$`ubZ%ji#ab?mx7&ejcjRrHe406lcXph7bg}%$?AhhVi`Dfx zh>=2DeAU`{_v06cS?#gtY-yKz)TD!Y=k{pq`Jpb|YULm@g$W<+JR4@{NXSH}#vrr( zEs_4q;zm{^6Z>&6bPC3}y%8j2I(RLE0T-EF05*+bwuB)K`%oBYWki8Db|{pI4jz`= zb{JcTAF4%=8Cj>_EoDf0W6&+*rkW5y(~#JxTAGm<(T6cYltS&g5&{(2y{k6hbiyJe zz}Yq^4jGGI7T>THR$S?oa$->6x2ZUgp$L6!U_vbu84BEF3&(xG9`MkMKvzYx2m=)bi)d-w1E`&Y=oKf$|Bj+)sWGV}r#zg}0wAy## zx8^%T#)~txLhzj#OyT?%3Q^8sB90TrA;`W20LKQSv*Gj?JjxI}zF4fTX73l*pKoQw z3hy;=;V23cYnS~fn$P@+-6%)caY0`5X1VyBqy0-+{T@{L>11(wCC9Qy=Imu!our!e zd-CWBaLO4L%RikA3m>7~kpiViJ`-1&I{N*{w=IYr`HuHv*r#bDyrNI9j*EE-oVK6_ zQKVONlNwROkq><+&hRO~DNb@4I4ALY=@kR5gh7Tt1%R$?OG79{ab#XXKfemjA)5mn zuT#bM%a0#L5k8zRZ@nn`#qo(p6*L52i3ikBxEK<5*+LTz4*ke;OR#~h(jt4*ez81+ z(-{i9l7oVN5|g*eGO-`BhEHoWLDNzaUiPBk%?t`e5M~(a<0+3@LeX0t5f5=z5km%? zPNC$cP`p~4$XaNi#rpG?5)MBf+`$3yH#l}TqK1M%E5Z9uFGBQ|0;l<9hZ*6(=@dbA z14^{UGeah+$T+26mse*Zt58o95j0}dWrP74c(_pmG&E|E%J*SL8CokJfUE}DgN;Ne za5_QJ)r(*xy8P7#8OvAHA<&vSw8meJ6d~orqieayoS@QA=R^sFTlR`~&ZG!QhBJ{k zoDpBF8A^3`CC9596nLu1g@YobTi{v^g#v(&xTP0OOBY#X3eTUMVT7=fRzXLK&*$vp z?ak^MT5qAwp+j!|fl1(F| z=VXIb0r24DDN4tQGh?mC? zmWixJj0ZIm9JezZUp!?vY`J{`*MG!(BDv+!{v$Y{+90(WiubjcmbH!wgh6(-=S1b$#= zJkz7lJ{6hCrJr&VCEcdF>dm1Db12aqm8k#)WJ;!7i>G=b|GU2MOZ)f@MDR6he;qpf z(cGFI`k_G&4d0##4@vaV^Q4`s9q1=Ap2il~d1lN6Xv0Q%n zHv7xP`kUWp*DukCUl<;wit-~w_qfRLz(sZ9XEuU%sHo>s zr+D2=sj?yg8B0iX4F$Jwn(^fPN+jd8te#iQU-LtyoQ?E3;h;cED=1Vvnl>^a5U|rjRC*eJ;IuS?i$$=$j#%r-GBk!Q86GIxn3+2 z_dbeb%F6IpUL5c-4Ffj3>NN;i9$H&#rIH_duq6-fHFa+{$O7I*F(4WagXQ|S-)2AG zqFe1w9^fuH!t>*4amFyf11eikG^W~unhB!_-k`;Sfh(0X1{zQWTHOY+r1AQ*^_-k9 z7ac0F?Mel*Ld`*_PzJh~!Xc9Ye`LqOqMUyr11rZm4j!-BWBNq47hj#S4qSB`Jbrru zq;p)&T5O7|!@w*wzTIsAo*M^TV9lu<)~V|19cC2S*$;(rb(FY+pU8S(3;n4gxLxRM zDVuzwa%vK%fHwkbyownFEyBiS`DIzykD4MSu*ays&*3KKQLbU(eqdHRa?9FaSeee<{ z3)(J#;Rs<0F|<}~za2Fl^u|@&pq$|*5ec48P=x$H>qCIzrHq=ql!fqqmQ7TgMEV>9 zzc8jaFhc0mPPzE)=qs5LOfQVyfA_Vxzc4;U(^*KT=%s{Cn86r&4gi4pOf)E24GvW( z|D1H|aMyP!xoJ71TrLtvUf^`1MK_EjEZIWducgyK=Cv&(nk+(UTvub^coA|f09MBj znHPH0^qqtAvz__y8N6zi9VwKamJg+^ssF^w8xnE%u}llV_E^BqX+AKBDyF zSxEworKlFlhtRj^%mSa@e220CA8<>#Or6OJ0^uYGw6eZJ;I!FAL;|Opa>-G9vh!3K zTKisB@}SBuLWdgA`T>Sft{J2CsErLHVt|G;G2r|YDM-m6@4|aJyJ&;3k;70E7~Xoa z^VA??Ji%xyLl~OYZ9j9&m#Z7EjJVh|q22ai3Am^xz zvz!Oue9Ce*?LBbeJzm~a$tO8Zp@D@EKlI_tLuwsAiA6H`%46Gm zSe-V@{;E#J@F-x7C-$<@$GtUK|ua)i{Di z)=2Dz8abF$Ly?gfF^n-%421#86f;KJ1UZ(G$k9(tif%}chPlw?iByDdX6W&jieSia z+762xX@;|CcZ~*#OcU8M^-bk(4PUJIG4QMp2VOMA^}$9mJMaY^95M+-Vz}9d)%96O zebKmDw;dQE#c5lKng(9}J5ysLBiRcp_~LEUNWd%y!+V)<@aXbP{7O+G?Zk0FGihik z6}YW20(ex#5C-IvLm25u_G4eL5{|%dm;X%5HLG#xk|)iOCTWUF1wL7_jc!a6q(b^nmY1@jtPESYy#Dp} z$}fGl>$CztGwJLz15nq@;P<0e;QSUoq@1Ni8=F9kAevbv@Tr`6Fw1$4jfRV<#f}ze zh+)7()yv>LaHgh(wj2@xAg5!Hy@GK9cWF@~U#OhGgN+kGE=dITBLqsE+}tjtAoTod z;mI%+2i&yA;oe**Ln{cq!oVA6x6zI#y1niAb)>>;0iozvZ6knonm~kf=Pm>ki>bk- zh8WwVdYekZ)32Pg`TA6(vH$=tTzg9gKs8*-(wRTA7in*_7wO&kdU?6}$L!>`vxL{X zpWgyumt8?H+SwIa-t0p1bNd-k{uJ|B8SAI3z?ZYdvt~gT{vTXjJTBn z0WXsnfnQe{BgYsdXw2j|aiFUJDknS;!Aamfn;G{DnZ$QJ zdv^8}8K#56&+Lb387t5pV@0S6LuU*LXC46MUgnV^W7P>JsYHUnW}S%P5>6W@;`YgS z(Pm!tSV}O0LUSh4oe^|E!|}qQ!7zswf7q%K^Pg1>7z;5Bgt)0B4L3 zyPGcez)5;1GI+)+xTcqQT0;wR3jK@mCYfF176w45ULGqeQQh> zyN%Gpes?O>XF?kV{8c4#I}~#3VoJ$7XBgV%(4BZeEV3Y^HG$vBid|(-D&VX|0Y5h~ zROy68?^UnK$#QXXBXdDdZoe$APS%&b0b(c59-%QVxX05{a32PU;Z!@i2j0d?a3{qv z47_NXbNtq18m*MW?0W|h>I{xL^b7PJ=LpwUsvT~Wzn@AK&nrF4&J!xMM)>gbDSUf? zmrR1hP(`iL&%M}ZENg~+4HNN0#9+6E16o-HF$&1k1b1g{eL_Y{IzM_Vjc2+5xG*N} z@s=ULJ3F0vv5@8DPF9~j`;|xUUY$3Lh{7xW0CzQPJkD%CGlA^7(>_BahcDF3b7TXQ z#l_j|=Ve_Qb}kD)uOs|W`*MbXcQit%1%N!b-xkqJZzMT&FXK=dggnjUy1!Ly;DZhU z`uo-5vdH;dsskz+5IofQCHSpB83-cW2Lh)Jh`kWU;gqrhk$^Ld|0R9h;=o^@3L>ke z@9Snkqtq82bR1#+wiTp7DC_%RAFbP7C z8_%OmfWJt%@JS|@%jaKzz5cXv9M}Yca$uO{GJj^5iBM{E#V!mRkg=bu_3Y%!ZD&;4 z$)giAJEvA0>XlNfa#R{5;m*to=4N`huf7ox{M~{EibG>fWV~()n{l~W{+#nmdU+N6do2Vxy`(SO3V&*;1dD>NS2w2;NBU^mPep=Q zB(h{I!mtxC&B%d|YbK&&3>i9U5H;CN3^8UHo@S#Lof#c#Z;MqOK+BX|1TZo+fS3y+ zA4$*p_3ZiT;`92d=tn{0j2QT_fkBQ~mr}?$ajG^IwR{jmDKZ(1`wy;~~pH#zhnO`lC2(Gy(+#yjNIVN(FC6N|0<`HH*RBc=;q0A+)AR(1+m5IicWgi`LZ$eU2`P^}}VCeGB1$kw5XFuS}sLX+uvZrKq|EjG}n;m&+#WF&VMYf zPG^5z{Jyx7K+FMwXBs7(Oz=>F2px>heE^f>KIHH|eF=FvAp(&pL7Q`wNPsC2c$ox5 zH`vBSjuP+JQmb6ailD_>(Rkt%%V_fnJVVYUqjWx=FTL}`k4AB*dX}WSHYGV# z@6wy)*V(_XF=$vimUREyYUzPD|Nat2nbRNkuB7y5M)p=Q4lg~c zkLpl{0mr?Hg9m%X2y-S41VE#jA(q)Wuzi)a{Vw^5L#8r2@#UOKy<6Onn$NzL^K$MU zDHOVv09x(1)G*;`aDK~BrmPTJoIl(%i*{BueqZ8AR&?x#$!92DW;z_ZLil5xub3j4kOkvy)Fyb8L%I(#^WsLf06G0s zEL0504Fmk34sAoS<4nTw0Egv`o?nbnM%^%}@?hC!3z)uH53_ z*bhN)`%Q9d3Rf80IX={7Q&l7PA%Ch2MQ%Z6o7bd5THgwro^%kO-YCx%0ygSg!QE;# zfJUeZSS5WU6v%WJJC%how<(Hsv<#vULu&6TVoaS4f=(kF#3RXmC2f@v88Y_Kk1zGx zfjErtc~kyXM%8GCVFEEUY5*z4WP})FVN_B+tpnsRim+v8F%*xC67tyOE5eX#{Ln~& zqYQ=2)%!GiwkVasxlD%wKr|FWs^wfJ9%Lxw+*=o~nC>W!_WYDB8^*bDI2orQ;9DpL zcwdLXOIe&oc1XThUlv^|_=>eYIxfT&q|x9p17X_SMj@&MKeV@xhXoQENe*dSW<)d( zyltXHh_1WVfsrZ8ZxTN&iygH5*iKSGixZ9&VN?drjKQ&Gijhhzv0DZ0adO#7F+!0n z+XdN??E*eiwp%PW5p#@i2+%~-V}?F9hX5K|se|f7Q!q!>VeaE6*A>lpER$g;b;hYY z;AwYqfq-NfKAhi*ZB*GNorc+lLjpfGg$O}4FsiwrEC-FZY(?%j#1)g0aq3s`}tt}E`oG8aC;Y}t4lCk)ZLEurQ19})`!Grbb zr_y2I^VU*HJk&5k=8ZE30-lf}D!eu8lrY9lZEg&N)JLMykP{2M-rdp=^bwz==`vp| zWxeD&rNHBA4DjgfOoHEVG7&P>6@<(LQY_m2&ZtJ>I@8E*c7;1Q82%!iDQU2dg8^5K z(jny9xV|AaXv*h=0jbYT`2S10sS8=%^falib zxGKLyHdl2bJ;n0uvReN_c1Q2_B0YWd3QfqpNYv$&>22DJ#0b+q0%&i#q`f>UOtd1k zN;A+QHQ5=))oXoNmnfx^8~FOv62@L|aS zag8a_KI0T4@ipk+Cnw=YwLxlYaV?{a$-=ZV-}Q+u-%?$ak}~7ewaETZ`{1jeE+wz~ z4ch@3k08raxik)J(pwob{>95|eu^#<;CY$4) z%5*JX0wUoJ4t!waipz0AL<^g);ZUOhH_Rpmh4It(1}ReW;AeL!&R{+4TyfSRk95vE zJGe@*1FfU=^{JF2|V8XN*FQK#sPm-15reXLo|?`VOt~>3$>*zi+e}=3|SNl zmjd(d*+6(v71zXt|FTfvl^hTfdn{+qWSbyAM#jN_A142rVF-~O?pf;uAip!&xvMOc zT&0B+xMhnHV=~e0cLtIk3R&(>1`I7^8&Kc7M(AtR#E*>>K{av4&;WWV#F9fr!3k=%THR|yjHaBxM=-sN;|baPRV9#o||I8F<_%2B8LkHW~)7!KyCg zJO^GwGU0*wV;H2(^pp)$8DhYD++`E7Pc64Uh=ZGoE~ZRoW^E2bPWPu%^8H4f{Zbi^)Q#RW~`sm>EZ}9u-1nv@6iR$Wh2e>Pl4xI@lT=dH zMHMg#RaqDAR@*}M5@Wqx8R{bNTyqg|QEdui%SA}XqEv)r7Q%0`0DvES-wCT77P}#M0C+Vm z@PjJJP~FU?F~*8senF$lu3{&-lp2ffgbYTNnG_tNgud2qzH^N|yfrEC_+%|*M=M%f zFvWX0$0?mCx3bam7hd@5^4>g7d=o$AW#7hOBxaE0;kkpP%W}uq}82d z!qU=b7kH)tfnS&(aS9RQX%tFo!^i^R{f2>(rEbU{)Oq58e%yO^R7R*$#ry)N6Du|~ zjaN&Vx%EpqS83m|0$-p8Bb2AwDxu*+2q-6GQ7ib{jA+M?qe|Q1}}ym)Z(oS2M#L8O-hX6PzEdmlu!<4TWP~MfNL?>Exxh|`G?p*)hIz}*}pkO_agkWBb%=y>$* zlRQxOiJwog4m?;R3AK~SYFSQTqc?5^5rfWX3W&#cpn$43#q#A+m$<fD9 zsE#Xn% zlo$(KJ!n7s%LD4WdI_UM&K`zQv|0sFhmubplv`>)pw4JGsX;`(^k~oTj=;94H0&KB z5L#Q3N)FH_IN>5?w|cqAQGSo1n@RD5plq&W(?KcZQi99P91O`<4J&mqs?lW zR1OS5PaBIt<7}vo{wO!IbQUzO&7DQutrQq46jktWdHHP?cUVW`>sS&uX$KB?yBY?@ z!OSjvu@Lw6br^cP&eZo1Op;>+fnYg*QE~!pG*0XXx0!7t0HMp`$u_@Nt82emsh^(G z=0xmT!Ho?J;9~ZX!O)}0!9#ZucG#l}DR=t)Od-dhI#4HY3+FsvF0oMH>(ie%+MJW3 zp$Z&lF5xp76hNht3KfmXP^8|YI|d{S7FGw8Vdp(Gp=lnSjnnj$NZ%SMF0QVFo?@Y)GxzoV^*g5oh5 zh0jgP}J9yw{ zQ$D2E*VskXDPMUs*IpKNQ%qeN*$q2~OqJ-lZ34azP#BM<^ad|vhBqHgZ}2_|2sx|* z7C+5;It00XKtN(MmY)qr$x(23BM5c07A+~R)Ie4~` zW6RiO|Jc()q1{}fVv3WY;8C{F_cAr(o4^pacp}pc2B4S7f!k|Z! zJyq$ibI?M5<)9^7SslX#R^%9t){&u*Rh^bHY)VGQ6bn@1Dgy$fG#~(@0TKGtP?In# z!9^7iY(qQM8%uHKhea1_j$8{Ys|0`Eeng zBC*3EbfMs;H4ZtJ-4cg`-sE$`4`k5PhGV4)6o7UeyWp?JhM*2muNnn4 z26(Y8)uF5gsYK#-(PfnRE+9{2=#Gn_ePl8`J3Euboa$~QDpIYJ4vCE_#W~$oOn#I= z1hc@y5)xtDSk?zzu`8-v66ANuaNtHWMGEV7GyX$VE?t(2x5un*XDtr-pgww z+AA8=9QsZ@8X~HQ22L9q{Uiq;(4iq4Yn(~C`>UUi0T)o41_819M>!nDaRP^x%h=L1 zdMMx-cPeGD3cS2J3w$!yerzzZx?Cd9^ZiJGa#SaSw`fDaH@(M*((QA+U<`N>Z9b2( zJaR>f$K1pc4>LQ!MRn`(*Bm^RIgTYWn{^%?e|Ug$rTl}^)xXNW5AehP{HOmr`{_Sc zXK(~^^v`QO{OMrtx4p%K!`OLrG}?LeWtZyX*wiP*qvAk$v;zaNqcq7GuNB@tycUz7lNs4AUKeIrJ z$4{w~hvXu2Az<+2^22;1$wfH@0T+#W&k#i78`couE_VtdcPu@OWEMdl*m&wC$mwFJ zXN%>{M~SGtGywACOXRSQq{BQ6#{=MzJg6Kh2+D}2GDM|R%4CO7_O51|pzUO#q**(#^?wxen1Q$r{TNwx|eWo3B0lr=G5ECEL~fzN>fIvFh-7-57OE~>#$afCdml{9HFCX=a= z>O6V7-HVg`sqVCp48&{6xF2p7UyBiHz`IrlKxhIP=ztGjBcScxUm$Z-pb*ezCO{Cp z1YT0TMBu_hSylYiT1wS7MJEbgzXkz!F%YVWI72*N^`rdl>QYvtoy(Ah_q`zhZ!aGg zLH>Q%Sc>s?orc3`+r9+e-y}-c8Kz}U*&|XZoJH!S7+QUv?UQRox_Iv3{ID1#2Ef%q z%OD??YmOVOwakt!H{|gQGeCyZ$w4zzF@3)V#r*14PN(r?13}dp*HT`m`|ECbScy5{ zlzFTAlw1FoxV#lx-EvpD+}g#>^2_{c{kwE>gBz&=gzr)_6Mr>$I7dm+)A{ufSQ#lC z9(eYh>S5mlk8VG2kSQsM$#xLZ+x>CQsYx9;(7Z&K3`dA%aO*L(Qld2VdYc>X&dR|v zy@8}mQ5k|Krr0(hg0I?)ZU3>@T+RNvT1YpWAN}Avl^hz5903hnRHK3Gw+1z1-s||D zu5V}3ji;zD*Q3E$`k~!%@a1Gg%U0mR9$a0Duola0({F{shn!Q;vL&v}oH$Siq)j zA7rsQRJmWLXf|daN5{I-zu5=vjkKV1%1}gSg3}3$Ztg?3lCgLr>pICwU$UyNOvZS3 zTP)@3>EZBjW)fn(HXfNC_~mpw6sj;%D2Qf+0;dg%ez=Plb7sOpgg?(^M#ha__YAkH zJu30g8d1mu(a73H?n;W>!2M$WAegj>R_*Zd*ZDW;PI&QNjHD)()6Swwh>|AU3QnuP#DQ5;Jq9bE^cn;n;TgJf9|IzwVB``2wkpi2-FCmmU=W5 zf~Gm}R#_Q%ORPLSVqPt23Ze^pYSmauWhAfVx9i;vgG(5ROG&L2$;6lvkkLeM6 z$rj$sP{8jEg$#l&+}O^xV!VJG6qVxM7W8yC*%bN(p}4RWqb6!1w9cu)>-p--$C<1e z`=yK`kDMk_1vGrZeUc`IQCbcVeDsupvuRKSy81RAH0jR#ijR?l^%ly+~@p<;^YW~mLuSJr@m|{3yu>4mENL;WQu^i<}gf z$(-n}JWk!sq#&TR(+o3{G9d^_CdBi_S6LqA`D*>mPuuGlhNDD?o7x~`O6&60iqptPh=HwIaaNv_ zbHAXO+il*qMa)+wO}8KhX48o7-Eb++nc32lf(9ky8(q)L*&?27AY=^2f_r7#E<->p zr5S?b%*OtrP5lMUpf}D{?XuZCnON0DLo|!~);6iCj91r`XRRHVOLb`(&ZR%!k-+7<-Il<@~3Er~yA^53MLywxm8L7c9?x<15 zw{`Tfz#`SbO)^rL@RtQOZZ8E^4Lc7bWnM|l3=5rEF+ zbB-^XT0Vy$nk_(LlT7B|@>D6_;iOh$Un`f?jFG5QuXtz@{B& zwG@}6iU(Vs+@$;R?Ax|af5Jf0tmqo#_Jfsh<6083w_sgwy5GE4uIR=4wd66B&T(A| zrMksnCgQU!R111Mm0(AEPp_n3|r4v9buXC62-T4a$J38uzqkxG9W zJg6psWdu00B8HVXox;ixJh1hxo~38E(#!mE`T5Jo<*hG@cnhJU&^U^yC@I1)K3r5& z0)O2aJd8n61`cW#g9pw~@D^$ucw|Ch$HLIlTiH~5BO{HMmme18vPHPtl*qv`0zWlI z@VyyM@?*9wV+@AVd&hcO>w{z%ts+n&2qvyeLJ=$jFRC#FUd~y@^SR7$zLrAi?C167 zvak=9LXtm`U)8c1dY5qPniM)^1f?;E}virP$tgu;80{+^ zj~17*>#yu8^vBn-YU(?e@ar0keh%uWf$yLevZoIzN8)F(z&p95D~bh98y0bVQlY%2bZ7Amh+)I8WBzk`=I3K` zJz-^MF?@(oqJ0tEA$2f-^#(h>R8nXXQQ3Dqn*HFynv8s1VxazJIvY#L=E{iLe)a2v z|BA-x-FY|v!Rt9rJ`ndML$75=IN6fik0s<-teTYg+9@?GO%rI2p#hSAN7W9IO~k?n zyP=+(g^HG%{TJ3Fl(9pyC|$7^0B%(S;aO4w(Wb?nqnhr5Un*AvD-Mp1{4#JLdGz^?h zd}%Z#S!1 z)yYDgHz-I2O@hDO+sQ?;qAf~k8knuns2uS@)?vFKU{bjz`V z11&FZWXN_Y7Pzbi15Gk8c8c^Bg2Axctk?!dYNUbz)GeUmQ3m74#w;rsxGn|zp&t?` zHe*~&5*F7XbJL5`KpP9(#jx0^GN9l1yT$oRh2eJF%0*%JR#pifo4nJg=2uB6u-9BnfOxu`dGRl9XaPzMqaxP=QSu0ys<|bXI}y9W})a``&$@ zOt6$^E+H-E3pfe#w6{uFfZR+l~?szFUd37^dTfED>f$hOQ>YtvSI^#Ue;T zd-p+KA;k}BBtnJM(E`^eTHw!$#GZE1>shK;VT);~RWSZFR>Wn#@JdZ2I{Q%`K}~CQ zBr29x1T27c9ZaiTK)m?w?XZa{VrR-ynEDR=Y33E7O9iiQP{j3=y9UzUqLfR9)JWjV zCRZ8yT^%a`N@4}xTx)VXn%oQB9oa}7&gRihxZ?uWyXr*|@qd&gR8RF~m8o+&>Wg_v*Sbh3?IYY)Bc2Ys(A0jPRiL-f?{Ny%8KbWUbO zS)in;P0TYxNiM4dl{3fEo$Wcd)*eDS(bfn&RXL$YlW{0lOzBHR={FoB4#R>e83)|V zg_6>>hSDTb&2oyTvko-UJO>R$lo%ph+1E{0x1YVd{qUhT3;Xetm(aVR z8|9EbJO#Kr_oILd_Bn89lC5(JsM%A-k*U+q+5BlS4cqM;V(OKzJU{XMC@#t?`cZIy zi|}ZsY2q9(HstA@RFw`v%_PyI6E)h3txSXm=bbuL(lHl)cBGTbRkGbg4a#0-<%ndT zgj}y{pbu7yKW0y^KFD51ez~j8ZXphuP#T0u)b`02RzAr2J)r3A2Y~OmHQzn%sx`im zv&}aLuMcLAZ?Alr*q6*HOI6(?OtQs24FOzVMKL4sTr7jBtYw;?ocV#3+_$q`2P*{| zw@EHMl!~)V;S)J1{yM*rsdScd^zyk~f943jBLj^%?{QC3lb=isHtph(dO5mF$=`1@Jojy1;_zkHKh>SY=u-!WR%wt;? z5&9bt>1RA~0q_h+2xqu}i}Wb{Yqj*7Wpv_O$BhuzIRj~;p}0)uMv79@ap2vXsbBOP zo*E(4$0>E40SW1Nmwi(&WZ+a;Cv8W#xQ+25_zj$?tpuQ`m5d+}d7+nP)hv5bD8IYQ z#bE#1zJ3-t3H5 zzrMzYaA7qXn!@T`>&A#e%^2{p%w-(oR^~HCoJKSUV!jh@+X!Y%KF?+mO{IM!xY~SO8AJ;K<1Vb*WvB)lCB;6Rp;`GRmVRXUN z?pVY~8{gJ+0!JB(XUk7Z8EY)7$bMaX_~-{4`vt%pHF#*%^fJ5zWHlN)S;NA#D_h$EG{ zgpl=PWvgw3AO#^@DvqC;OrW*Nf0TxS78?#Ytg=vs0n^<_cj+%8oF#Ku38^MTSU8n5 zXyxOw=lJ^bLRJ-)C64^K6cz((PT)QUMNrJPM*J{J(B?7{WR;?TGv+s9FH@HeT<2;2 zN4JQMX4GkhbgPshb_!`3_sU}RL+SX{$MxcB`N!IT!BdQU*0o+icrmGDA)*a8L_J4qIWh)X2Xql zxN$N;N4)XvrX2?O+*L$3Vv^esI6n22V;#p&od(!WpGLyE{<=2!NKUAq&Sk%=TbW_G zSuT9;>*Ig4&+tL3%q~L-z}Y1pVR%qcSsK#Ff3nLuxmRG3^Bt1Sv7wCjfm90WLdg&x zPWcdg)^ph-^4sjC%rNvp40%-xl+)@k*Axw{eU{Pz;VquOPZglp;OCHcX*ut^Cz@r?^Jy=N*WcJ{)teSc@;FBh6!J$QRJJmu+VA4V3^XV?@v{?SjMYh2<`enkn zFFZA6Jp=@By&-_>&1K^7E(mDi zGrm;i5P?(y{b5LTGXgxyGS#U7m&|tu!!l0CxERVf@MusHL zBGTXyg#xU!z#)vd?;`Q!Qb4$R|4CN<2KE+U!URXjP)GF|y#L6``EOyU_50Dv`NjPG zMizFHEwpFPu$wwsS7j6zVMU|+Nb*xgu}8@qek=u#_!ySxvvAec#evMdgrd;Kv#gCa zCL=q_S4T=w^%!>{WYBIFimY#l@J z6W(RSR&SOwZ`{WMrxO<4z7%R^77Q(szWG-6q;xV){I;zZc(eiGSb;-h#eTFnII~rH zj1@GtiN%<>b!DbSfa1yJrRYUnH}~C2jumLKfw5EetwM1J$v7{tpv6tdw8GRqnnWwM z=1_c5ip`gz4ugi*I^hTrWD~6&B?PH4Ijl@|Cgp?L10W^SvSqZ(Foz+($V}i-HVUmp zNeAOs7gFM3h5#;40R|2=z6dP9MPmry{B)tr;g&ANoCfU5*?i2`) zp^E?`$%mWy4VMzKv}!8=Ra%v^n5w(vVllswg=eI@L>6qlSYIvQcXFI3Vbvl?U&?WI z?lr-yn>j-JETZLUtORe%yWjZH0XSEddC51q#2flbOgGrYQzjceBSc)H+!$P|Z zlAVHCenFB$v1uYQMvheBQJ1ZR7{qUz%Z@#1_-bkw0S6sR`wv-eZs9x9oWamhZJU6A zbfRVjo~o?SAZ{`ScbkF{xR5a*E3(tFJrRCc)O$+%vE~j49$IyzJLUmumgI5(LLr$9 z#E!iMgWj4<%45wi;;cEmoDw2{krt$UCEHwm@st2@YNbGX%q(^)kY-9SL7nczPl~<1 zTvwVpk3l2?6!>BWMNDjPstLsd+2%`1LQXCzyas}!g99NH%K>g}7kt&bXhUzXeZYY7 zqEx^cYk%xS!zpb)kFs5;HvH%Mw%BmKEe#HW5Y7Up8UhtX=$SHNvPF~`O;;ZKG>BbC zw1bwb;98tm$@u-J_1Ei9OUH+{O^_L?4#8Cb;2I13Rb}$WV$r5U=rHKA0i8!O>jF!& zbY#IPHYt>fBcd#l|13UK9y92pG@fX!m`qT$IL_iejD^&^LVuM{m)qvtR}MgIs=3#MD;{>HEaKZHrR*o2KqZNV9IDgUlNMzRq3s8?@C-L% zabzHIn3a=lgpjyUY9SSy-!Nx!v-m7f$pc1v+;O9}h2X}<3I_#=tg?xBZ->=GEY#_8 z-f^Hk$yG;{!9cq1Q)*+E)i@ky8*I8QhmcVS$py#l)W!jDwkDk0VE7KbNTFb!X#t|T zP~!1ov-l*-#7d`upLyZCk?@hN1i`~*T!{zNXpqKtNi#~RV8X_N=>(++2Ez7JHt})} z35@9dhm1#&aU0G!JP-_|Dqe(MRtE$gP^I5|bF~iBjKLKSBeTsqv}BP_cQ+Qoi#fGu zn@1dM&*ry)kPvJ!dm<|U_#oR4=fy9L5QIbt5%Nx)sZ}DI$DV@Ot4RTN2SJP~aN1nQ zVdzi8siy1r`&U`?e>H#S*x*FMfq*-hREBH|XKJg^rbFfxj;X1};0-jk%2t7Q8UX>P z69_>ca7sbID>Kp-0(x5^>aRpp_}E+HpT2(a81cQo5EFvMa_1M+(G-H4u&zRo99pIaRlc?LaN# zBW2kr={J!3R;kY~zy2M6A5x$D(38nsDgeiLiQgxY%EUY-sdjVXdTB=bxLBt1@F#v) zeiIu&l`lOz(K##nQ5dW1S1EEIO?W6(oEm9y3!84h`_#uFKRy-3p#T~h3OUpt!C;J$ z-3hfjQ!*~ni65$U@+b#&uh*9{dJ?sO&D;;7JudDe#Z@(4`S^GT`0ik&> z9;){`47(tJ5M#5Mvk4QA#l0(=eM#68elaA z!DDJ9&>lk~R1Tey;36}UjD$L~osiH=vl1Sbv-jlg69$LG;W3#LJj#0UeDQU8J$t@d zfAf6`Xx1nU{ao0=fV$542jvI_6$XUPye|8X5mH3!z#BFL{s%JD_Da@&liKsj_s2V#aL&WgB9;c4 zwAe`|#^Q={2)6pqiAaQ8xQj&E1z@u&l0Xos777PM(93O}V}urCK$Jx@`V{H}5zdro z`_KNq<7kI74+*K3A7)QJuGY?Dv=P9A+a3dFYK7p7Zu6aE2$bW{cCs zlkR0XKv{XfSJd(HH3=L9!CU+o2o#<1*;;J^gy!S*R1J8PedW(GBIS2k2vL?poXbYj zMVBKk9mPNlp@!X%}|ct_jfW`n(>UoEkKSv%Q${NxQV9FomL*3DD4x#J`yrjlj@U}{P> zHM>GvPDx-*Ho-;bU)CMzZ`zudn6CxCqPM`_2?c3Yv?85bEc+ ze@%+46C)-vrW1otiK4laP8cH^d`y3-p0HnPX;v6jKkp&1vBH|$elHp`uMS24Q^|TvT%bG`f=mO@7e= z2;6RhJ#<|LNs+1WW-aRlKayJ9yE3)(7QxS7W!jA|ru8d@{UCrm6X)0-td-pbqMYo7QB|#^1ppN_EUBc7UNBKZH6p ze$z}Wv=q56`jDJ4;CHn#h#-^+#bb@%t4<`h$N22I6vQ=1srr$g$02m=;7y_Zu5)v^ z-L!oN$VZCpb6D&5dQ)3S*~o`W41AiV#VW`2OTEyZu};m#EYB`>A(MQ5GoF>1CU70YzEpq3~*C{p03P&g!x!k`eas+k18nBU5{zzVT5ESBN5`d2M3Q1WPex7|uErp&VI_P$D-^!zyk4 zfM_2FU~hgSZ~|wVD#0Gc*|Xa(vLS$uzL zV@_t+EVo@kfAP04RfUUZr8s-n61q!wfL$sEX~2wcccMPDSpgW5woY~Igs0P8D5hoY_QS z#3T;FA^>t{RR)6Ew+Ni>L{ij)R$=Vb{B|{awOPoHQa+M8bO|8LsSY1PoQ(&VIN;Eb zV9ugpC2Q<^R02-U{6;Bmz!tSPgE0(3tyD6unM+4OEErYgAVOC#dUX5wb6O?OsKTe# zF*bp*9);jI92|}qd}Rhxdv++41u{jKra_`PFj6L~VBljWVuaBccqO$B!ji$bSn5!| z_0@Y>QrgeY@OFcC*4zk@C7w~o5?L%XF`aBBcN=~l%Z21Z%R*kuRIYC_*>!PS_A;UA z#tDal<3t$2hX>RssHdc4%K3flqg*fqHz6YkuVX}_jxqu-V~mKKVxTd~2OlS9Fw>O|lKE~EgHFr0gyD zNlPr6XqyF^VrJ3rYy(ne7C5xXibs>p;^_1zjX|lFnGL*g)(2jrOu!jqLddxA>!wb! zukGo&TuKKRjj}*GSIW+XA+xEv7k(CBB!eU`Zsyo0R(5_^t~)&pKVQ5>Tk_zcpAdwl z%xBjg2G;V8l4!Mk4V^{L(C&n~(kA6L(LOo7@4ks2J)~JsPJPa8S8n|>3{dscl&KG0 zIHQ^)+##htY`T2{T#_6Wk5!UR7}54JABaM2n5s*V>OJGza(bm({)?DqQs$z8wjZaB znI~bO2u?Mr`RaBfE8ov9r037q41KJR_SDgB--pA7jSvdJU7)z{x6j!p?}?o)l%`hR zgYiQTxoOHH`Tun>*4WS7f&CjCpng)TAXjjlH8rR&w2wQ)G~HoD6B6SP)X;;sK)F~g z7GEU9i*pm&;)Nik9pV8rgTv8-B72uiusgJjF0pSZIBk{^^J1J*OQD=+C-z7-6_O=T zZedhzhma5B`dTvi zy3`0VxgAkxxS)OYdf5;oxJZI$7~<+tc}*4F#lbW9ks1UwBDeqN#aiOqUv4pE+gSvz zA8mhd8MwY~f3^rkVzNc_BHX}(7);=_Nn{v8!`8QIM7As9+mA9Z|KnorjRK6RvA~_o zUx-bV!S1i~Pgp+aNA@cmmP7N;F`OT7w7_;@Vt5=VKI}#)yCuyY$nGIm%k}Ky%WWsV zzIbo}3gr0u5E|5puhD)fI^V8z%!|w(m|ttz3TKQ9fG%l*_9svjMgoWtYmf=O$Ic?o zHKN1~tL;P5Qt@gRJM6g4JEYi}&9*O&S55u^t+7u&J`KDbKmnIK4bUi)UXBsc^j}@Y zAPEQOwr!y{A8(UF>+Vz)ddw^fviaQdirXq}Zpsy(7}6U!1Vp>qVCpHHEMo>o2Ry{+ zklCKr#mn!C5}o+(`8F^K$ihz41=MCmLv_@x;+2&WXp3 zYnj9klo)#&#)&QXW|hJmIq=RofQmYa(EA5tKq5@jeNEpR$v5om!|YSQAZED-lX)^Pv0`06_rK%P-jgz-Cgv;l!b z6AOMi#!;m9bW)TBWH+1(By@$-hy+e20|`|LnuXS7!7``~Ek4MKDzjgz`S8BS&5;33G#n=ODBzdVQ*Ref z@dmqR7@>ZQUpEts2dm#Ui%YD$Dp|&dj|;C3_))D6ghLG?r~|GalSwvVvO4UKK{uKr z8abM>{WD~L#(7bHX}2GLH4apheqA7671GmQc9H+HCmk`{hwe) zDbg>;5aD&2fj+~ZTC9&~LR9M$74Bx*HxzSh43S=%W7v~ZKWc<4Gg)7k8f(7t+`2(j ztGKk2YIe7f4lj~|eYQLalCwmH*Y+942kQf%nw9FQ5Xh*znM}(N$ykmvL`GSt50aoO z0Ei{bXT|z)*Jr)>CgbT=txe&$rg4Ht?p%j;aZ(i?Qo}(4Cp$Y^TI; z;-SjaEDaqT-}BQK`f=_U7Ga1wn$y(qXUk8^**~!3^4En(I9@B64}MciI5=QvghCmv zui2p(fZH1XH_?cSp*oY&=2t^d zL+KO`sFBFBv1eeDRz1_ehRO8=5JFIs1%pSYAVcUvJ>2Gvx;&#haBj_5M7`f zWdf&7GUMCmCTuV2mWs>0nA(*Bv1^uy6g`rG;SM_ss9hUm*uKAlBX-(Eb#hsXgoNBXt{ zY(Td?zy^3Gj>jqoaFw z1r3}_Y}hzqfIBc~LO&aTH7o$P5kgDoWm~~*(o0Yxf_c1{U){>^zlUqT zN+RChjHB%@`Xw?v$Or)}HE{UBgb1}lJhu};Wf<^boZEb~T1s#H>-9TXJhSMJ_gxC4 zLFGF`1c8f8C>$1w0E~kX@pR2hbK#W{u8>ZYGbLg;>A;ACmIy(sa4K812^3O7{tbil zoq6~a+z|uMF!Kn+a9}v4z@ZXDkDBHY!)x4Xm-U7Lx(lxZH*go@#$o7f!n1}tXRaa8 zj}ICs7)^T^i6ZnA$D;EFyHjA-zJ$lOmn&JE9=q+xza0ngyxLRXuQ`k=hv?)uzN(`? z`nY^Q{~&vRNqkSQTrG9;=$F6Y;X`!KVcsdY-*p`NA;j3e0=%KQD;8XpiN_;DBev<% z@%cUXJq~|TJ;KMOdU|O(yQf3et$vQzYDD+kO2N{8+{{81Hr=52(LH>(VZUVtu3T7i zkr4(%7Y$;ja`B)$B!Vt5JF2ikADWP2moPOS?nT=lVwYbxx>uZN^J=ra{NOFqyDFeo zt5X<@4&A8X$fl`BO*kSY^wONg92VAm3 zAu|-bv+^5pqdh4Z#-Vn325*H|tND=4gMMbZ$383;KXSG5-JuAfJ^0ilgiS-9ypTtz z6Sq0R*QHMAQPVI~2;1E#y=Cf%u%kqG^6k!>KRum)onKwaULuzt=b!OPrl3fdYU9B3 zjS-<%+*SyU=V8u!M)qY1V866CtX&A{Zc>6nWMs41e=Un@2s`u)inehahAao98?%lP zEVij-H`AfTWoG2S&-|ynQqs(TNA@5ftBY)ZUR+`R&XunCk%|C#c|!xwtfivwI&gne zP0&kKQ;()vNN%47w!BNT3eYu?xCRtj)8s{}4PpFr5#M!W(-}#?!l?xgD-W`-THIyE zWrP7QrZAu#jt*ST@l~vz1GSnCw3Fsw8C+l6hBka~`_nx~nrv&<}(7lxCEJbnqG4eFzX8qX7=h61XX}$^vW9otp7Bkvu8(;&SV0S~Wcf zpJzwD3xQKv8%YK|fJp}nd<+E~8Vb2?uHz@B zTSM9cz^sAEO3NBTNQp0DP9hf0(Y&TAJX>H$KC8vPR_w>)qiF^Wo{(%IXd}#|76w46 zgwT~Hrekq>&re6!JeF9f%NV#2l>w&>jN?!YqcAeQ#zjsdAFpMFpXL91D>KHItFRR_ zyT^NvY=KoRL2K!gm`Q6qLEpfO5~3XS)(*Gt*o-V?)* zFm)oyLv%#4881PqxK>?WzL+LQkJRQf!+hrKqX=hxx13yv-2u5}ly2j628Dd%P z){Y;Hdd&a=uCIjxCi5LZAf_~p0K%ZRmCx*UyaP~Y7;j{~M5&r%qOT`|g8_#o5QILu4O$sErn^acAxorhdc(AE+Zu@= z4m6|MENH|`JoyRRPz;~5vrb-V8rga9mfK8Y=bh=ETwXT!48T)r(;z6yi7;UY*VjN$ z+zEt2GFczWPR3l6;f*s02W2Nb2$(1l%ExGd#Sl2k8lk;w&*4Wug!!%=v2aK$zU+acqlV=ih0if-8g~Io#HGUHaXFTf&9G-480B|70w}& z2vHSUXq@O$f_mXmxSBna1@el;L;?0F2vVafdlM{}ueuV3Rc|J&=ebvB0^@DtBVNA_ z^KH-&%@aUL#Gi2-Y*342s_yypNio;*EZe9qn&XT#3B39*rWX1|%CY#aGQ zt_#hr;n0td1W7-0n-N1fP#F>77zm>)A7@V<;ka)LsVgBkU?9P<>saHFbu8PKioY6P zLcI`eH$-q~R-(fgeuNJr!r44xT!YhwMjREvu#V&)O|oL`u$U!GD%lPWE!q$699Pxs zP)-DPsGeuN7%QkB7nAgLbB0!oH*CYI%Td3038J=2tZ3cduv(Z2cup+WP(7>>;ug=t;qJVClY07 zlZBHIXC(TRh!a%+p>hI#(s81*mLU$#KZxUCIKA&psn&LKcj7>gYnf zdL=Hd2{5(oxc)9}&$+RN5V&2dBtnc-4e-e1E@dZMoUwhF^7qFz&IUTG{Nk!@pTCUo zhe&>>9)zQx_S$dvC@ktDGCQ)UkK4gu(!N~G1X4Fb7_9^#+x-YoDw6?dpjm)!o5axR zgsQGWVA`0Y;-NQH!%61$AM)wbLti>}lYKeY!1XE}oZA=>WCr zP2yLudhn^dncyM~8qZF&2HVr28h5d~4xEnQu$Z34PHjad> z9VqZL3E3&8)?&mRV(KA9rvqhRN(UMZ<@`)nL{dX>+=wAHQJ`c5JSJ{Y=D87612;Bu zfDu(t%~q(PFeG$Es!VdLrt1ay!lq2g-K+&}mD1f(GoNZ0#TT>%7q`)e869)2xaDj9!)Mjl-+DZvPFfCFqD}FUZI(3y?=T!O_{hkv3`aV=c)ALWNjKWrPJ+udU)o($9eWt zwh3((v2z=;Z`lqW)XH|CZu(K!A(OHlKc3ddvAB(ae?mp>B|FYr<2QzaE0^yZ*Ot4h zc0-pe@(KVV8&l`FNdO3C5a1}Q^XTjh09P9){%ZKdImb9vTPGAvX)(fzF_|ulf^P72 zh~-8AISz)J#TsAH0n(kC03Oz0IJFKP2o4JzRz`kia1i>uo6priw+oLj$^2+ zGPEPJ2n3$tI1z!sX#*n8U+MzUo3w);jJl0}+KZp2paL&US|~v#i$rg2Bi^pBl{lS{ z=u(21WUcH@OK=u~pO}U4ut^F$e9S`d%ju+Wu_bS?yHth}GG0$@0(^1$kb2dsc{+IB zC>KIG2-nvDXtVE<^>#+lDI7xtL7-G6amHxSEd&vc%mSwU7oRr^zYjHlssX^=d)dyE zi7DTyTj^x9Tj|ki@%sWZnq}zh@AH-H3CVP>o&^8=2>=`>!5{vFfUlis0lKY;79`Nr zK)Zd^OHj(D*UnGQ{ra(fY6_(`E5bq2P-Q}Qvj}Gmh?G;3#a7!V$KR7%^zp&2Jxbt- zA)u|M4eSX(3IjB1vNgSM!;n)PbnC;%VT2Z5q^SpQ*PGSl?BT)euQK`^q%d&!84Vj~ zqhW*lv_mIgY-9HT?K`gCgT~ad03sR= zA)jF9SuKnjB;XtB(PSrC0>K1rckd9pPr%lG$n+39D)~hK@HE4K1gPfhh4d1)uOYxs ztpdZN$q@8LA_Y~55fe_Q&@p(3EDX5Fxycs`?xOPMcJrlN1fWd^+{fs!QwNfA0BisU zr^gAyvR$eWIe64~dT^5w3ZGGgkSRlfyE!PHTz;5u>cwIm5Nv(LiT?Oar;P}sd!AbjNYbeK-%RJ z?WpM}^c7?jc$5Y5*+TZ-n>{c5gp&q5tacM2g>V2k8r)eLdg`cz80?`xVK0^%@N%k< zK^dg80Q=E@E!N51o>PYC84a#^aCaEa{Jy0cEkI><;z7RyF<8SxS;p$sT4r$G-1v~e zQ2`C9Q>fr2pfk0X0734SO%@gEER6&=$Bt1NaXMv_-647bh{wy#r`d}odXM5lcVJB& zHqbZG5^b({>{mF;0-Cr5sm*g1c$aU3Ntf0rF&dc2hdw4k{~t!%i#jxV3&$l*S| zAq0S{IY|;}g@cwfJ*7D}elmKmR_W9V_J7K5#b|&&vLY*i!SdT(=kPs*M9bH5`I*w1onH{UOdv5pi+Mptey#=*2XRQMfmG2hRMlAZXi$f0@ zxM@BEM;QyP2V#?yav27E&Kd|}qB;?PK$C0Z$fQt6+WvD$WYmA^kMajU{>j(G+^TN? z--Y;5HE}voBV>GCINr{&L5eKh^ZT;U3N*Zi15cQu6{-+JR65;(lv;t8(%A<@H!EgD z0;dxaA>-*Hk*ejds>LaAw^}c7Bg0~+u|a%!4J^=%HWu4l$iN(SB#}6~r@)81+6d-m zaaUPi7{a%&QE-fqUgk2&wOCeST>kxXE-8d0D6)mfzop~99Ns^__a8qU{>Pp*^W!iT z<8-uU{Ml}J{;L{4PUe!UPHD_8~{*K$!rcM`176|I5|IN zgrN1Ha|R?DUaNg%Avr(MKwJY%PO(s{C}&}SMyi$6nxSNlR@fJJC3D`!h=0lon91uk zKCWNQ3C*n~lKHba&Jh%}iS0q=@oNu+p;hQ#%VzZHxOrcjf`l3N6pak9}-B+{<>Pos!kv9 zPKhe696(vr0wJ1aSoEEzvk#k#f)A;vOb#Q;-erdciPrZQIBotSVu4eN1zP3;3Eg1q z;%50}ezpExhMPM;w&D@F0sE@CK|23;=U?3*ld_QmD_OSX>PFVLl5zKvj24yC?MYaO zxIw@g4$2$hP+%t0F7aW&h((URXxr}p+kY~XC@k$fwJKP2ACxZxXcs-4X{)c zt!i6I8-+}Y?~OtbjA8DtBH{ZJx=UoxT0AG_QZ;)fJJ78^6oTPGD*VKtfC3s6VG07U zt_i`sd`GEZFb?@F}wGAu`XV$7AUHT=6oY$ z91N;65uw}x%~er}Yi4D?$16^?&6XKt>TlP}`A61?=Dr-@*EGTxGI;RgDXz;+Nl+`D zYMTdTfS1LZx64RG`^0I}s5t5zr<4%5n~U!#QqSkh&+isR{~sD%s}xbO-ku;+xNl7j zB|039l4LRzrQgbi!5jCXz-fabmQlN`c)FG?mTy1L%ZeE;Pc|cm#t8>R=m!PTKLCdx zP%X=x;8Bie5XpR#joX)BZr^{JJJ;n$pW`<*7-6gkz^lQ)Pw!S!zRQrxe!nK-WEK; z{05FPCEl#BzR3{G&tKjNH#{R89MnmT>hRs$l8NRTBODysGRTD0O{VL_nfIWF@N^p1 zDJlbKQ8RHm2}L*FN6Q=-|6u=kV`p7I8NZE2un$~R+XpbclYNw;$=kJf!H`1(34E{) z44D(bbv&7GWajzpX0givBK+1E5ioEX;TVxI(4)zOC}(8==w_JT#-KkBAy9!ex~~ng~pCL&)_p01UO8wCEl4D!MER|W>)k|Q)qUlL3f2SyrDA&PDzo`WIpl5qw4nK zhem;&+K=nC{dzQ&0{pb9jl99`Z4V_7{JL2jd-PEps5GzzsOZTaI+J;Z4osCBQ9=*_B4a07whZAwQN5Z z_Sxp7j#?kc84Do;7d-VnMWBaxUAcF;sCMrpRDqLuaW=>J+62$1q5w! zz9Mx#Ka_fDIUe6H>;(C&GmFD8D*?BzLnr+8PNCB<=r7D++&zqpsduIak8(`?aQ*Jx zw`v$VzNL`^5E~F+yNO7s89PJ4p&5!=2egyCo*p}t;gjn`&@f`4MU1C$+Gr8?1js<3 zVU8A0Z&yp%d;AHuC-h;2AK!-`*I0xOH{7@8M8*Q=_t1f3 z%^`8IK>w>$rZx*%3?HIy?!~d9jYO!%;J&qd;<;)adm>Q|%up(TWV~^9rSwL1V$mI= z5>SwF!=I&d36W))d&9bui(mEz^Fp>xqzmp@JbHv5?yDW|@P-ZmT{1Op*NKa22a21R zM)p%ohX4-mf`C#abfNI3nFDQ0*fqb9?z9ccF0w5~xs&f!0D>Xl`dS$83P7L$HD&iC zD+CQ)3dqO*ARUV9PYZ7ZZ3OUFqktGenPd5srs_v}x%fK!&oz2+uVhphA5`@tz5LH- z_?)>P35J+*ot@_-xK9E7Ox8#()nwm!Y=9yTYw)&%q*xz{%6L9~a$F=kxm)Ef}y{%}L6%1iYf};ekuV7F>nlNM`(hOM5 zCX>AkTU;<3un1#8!14|h_@P2U3&}yfYqr(qFy(MS^nEwp z!f2tQ03Z~SCN0L4qRe}h2h*%04vHZW7{tL~optE+rH66bSQpGX`#^KrK@8rpW(41y zk~vZY7zLs?u#&*;Mu`y5P$*mBtn5HPm$ez{T{8W0wT=}qg0^`$FdQrJ34Ta^LzFWX zBRUbFzz@xT;wW2(1h&{@`%1>G6hZ;+m`;!is+!I`!t`rkZLDYlp^!{Ak8%WpQ%o9O zMuN<$GrewXM;oWU<+J$Ii?eL zLm?1?G-xS=)Wk3@XCwP%ZtqaT>zh1?Rg}?w3g~}im&!?)mT@-PrIOxgN{VsFt@Kik zv15Mdsi>h^fFhEx0S&VLTU(pGZ_+hgP$!;0y{ouW$Op_XIZ1F6$?DNHO# z25tnNI#k$nBR(q1DaY_4z?!hh>NQ&9CuV?T1_Nx(V9>fYVNh9{UK>3<(KYNfh+>tp zIo9Cd-ce_<`_oVg%l3fQIXBTwGJr@OPdZgN3~`im6!;K{6mqB@6HM77pgg(fGfp>) zk20Gb#gMtGv%rg5KpCeZP#2XBa*@NLnPmXEQGXfATJ9{3tY2&Dl`iElpLI8p;+hCyH*A5sT{ z@&S)EKE#!sG9Pe}<3lH|?o0sprV*-R5TM#)szw5dJtOP0Ge5zVy>%0+V_}qerb9rX zlV){L0GQ@7LzURkp{Wmh8b$!Is5<+`(4>CYE$(d3~g;gD*b!u(yk61e7$ z(GuU$JUC}ATQK83M>NrcL)*^5ER@phcELM2_RAY_86pjGnO^vi=KLog<(}X{jWD0t zfqfgAscy5aO;5&%_cac(b@mNTMkl4&^C$zqh-UBKixbSdDqng;tw*7cFGQ@1ObZwp&VSs^~)HhcYdEWR+m9==m; zBTQPE11jyCXs9Z+$JCwlJUSuEVQzMWRo)X**S zD8r|(4MjN*JoKnZjc6z;D_IT74kvOS^%GEs7HU?NT27||`^dd&Rcq1hQk8-dld+NwO z%fH-WMy$gDhHf|jk~-82S@AX;*o5izf#e!uLP%@NIE)DAVUP>Fr~-j|IN{XcGjA4H z{MiA)vfsFJb}J=-I>lLXsA1NKWsEVR6bV%ZFoIOeNDNw2Ga^VtW&}*k0r7OPS$~$8 z^yBho@o_QVZXIZmsb&yOJfpS_MPPXO$*fSA!ceN+8PUaoh-AxnwUEeMR(FxrM}J>< z+rXnnHKH0#gxw}&d9~fT7Y_H@fID_CoR4-dT+H8Z=AUKo!Fu*g2BfryFkL)&1gKr& zfv6)T9=rB004?JHex6Fn*;V-6x6L@RZ`=}bpWOF(hs<4&CurhP^)KB2N$32TVc@ka z@i@V+QvU*tK|7nESwkd=cejs@crfK1xIE<@UGtigll8#*C=aIC;LPpptb zRml-F##Hj;?8K>q8`4FbGZG^XBbL?)011IW(Lupg6W^}aVPPK`7S`*03V4LcJoqLFjxb&WLZGn(r6R*&vBbAkFx^%4I4M++-8ibeERMB;)PT)7qpz;NlIQn(mD=e7O!>I@y+$)B@I-Q`c^=$*E6B)uN1zLnU4!Owb#FNWQQOtUNf}j8G zzyOt!M=2vqoTP9j%3YfgM*#)=NGTE2^4A-w&|t8}mm<2x^)(@Y zrC|`nvW)>>l@{UnK*4k4RkG%H+-TV?tG;uHQZpyoK*Rxl83=I3(SdH`;bwmGFWkuD z3pA^S0rxN%f?eQ@Q31`Y+$U)~Ij|n)Bx$;(y0|+6K^^g8N`-&`3Nz1Ei{+JPg40F{ zq84O$IE>*c2kqn-wkobaMqZD}l)o=9>bf1?K7Q~VU%-X8KsAN8yVli@-~{Yb;Ltt= zja$v~WW+9GWT%%7Dtu7JGxvF#a$SYDhp@2g}fi^_D9KnG0WG0E%_|5k#och5ty4;7>#&BV_$!y!|v%nkE>66eG zN?mH{^u_%1Or}tMk}a^DW%{j6C`%PF<3`JPKy8~eCPK0Iq9sK=-3=r{V|T+MPFJ8| z5)0hJiPY;g^40b1;cEW=lPvb?0O_oIg%4~@36*3#q{c%D0UBvw*K}LaJlIlCk6uWp zhLY_lEf=Qj04=wAaooZoB8x7p7Qf9d-pg#u&tgG1Yn@WKT`k^t*ES$HQ`<@=h#oZo z8MBqLmz=1oRLJ8TJ>oz+Ih(KumXg^4vvgSKSenP{D;b}?Q?O6NQ5g^9z-!jz(C-X# zj9?MTf!FogQC7^{sAS%=uN^pTh{WYh(GF8j9OZyt{P|_QU_34#B}Pb!LAe?XZ5P1U zGbuj3S0sX&83BlX;&apu{PCq64h;Mse7If8NNr~#er~oCQ5cSyNI(K-3<+>AIaN7S zpNgC8TP!JLRD}ZIl5?3A{nC0Q1tFk_(qJ+v9?SZspJuWFz`rY1%8?;NjPkd zq5uFj7V0VS;(9byE>uZE5tX;f81R}Z2|Nrkfgd%E;_qLj18y~c=h}{IYm2~Nt?m6d z7Z({1aJZER+Q4RqY>pN*&?TF8&w@iy1aH%9KUYh9yO3!cS0BpW6&T*#VIX7gPzT5Y zN*bsdfKiIa;5{>3$_1QG!PWn*)@0SwE)>{*M;1bRFBNCU2HdE&1pL+5u+yl+%mzK0 zY6$@b)q%mVwpqijy$O<%EM`w+fPxP$@xpiKK?Z;yZhG2RMh^5hyIVupRVAw-kFpwy z4$6=xS@?P(gEaiY9b1i|>Q2%0?nm~foQBC7_eXnEp0BTEaq|z0l`P;ROTK>*y>F+S z&qI&OefYUf#tHb^D_}Y2h;NmgqecfHE=N`E=pL0agRLK&9{I89XGjLMFF^-JWpg+v zV``?8stU!VaF%~f9zj$Kn%Q!rekb8P9`8><(`&a#5u+x4?_h~m=9-_zW7Y|0wPoN73=__XVI5R&)A7jd&Xl`=*=s$}RhH)Tormj43M*{N5>{2m>y&HsGbp zuu-ba6E>C03WUno(P7G=qq~<90F*pc@1qhoQ9gn*P_!A{kE=P9KI6de#RX2piReWb0 zfVC4!8djZN2KR|~P(JV|WAOC$O2*8~X4Fer?kLnKGY)LL`cSm1onk{bY8?2w#6gcH z^C5)@HXd9aqI}p5hY(ofM~VZFG7f*;%&$I4Og*djyv3EH8Zj~5=8I2v79|cO)$aL4 zcMN!d;koQ`E5o>Dg2 z9o|VHpi9q!B|8v{TD@M&@Q|Cb`@k>k3Iyxg=uTlBo~LN+nFOBLSiuSmg6opNgj(gv z<9rR0pw-jH;Q%j|H}5}&Kp(GOYd7wbh|-Pk@X5?RGBGsdpU#V8KQGx$*(b;jite1I zxB@cXPI*CwI9_ZpM&#$^>hrqjJ@Atg@MB{IUcjIT;higxKu-(`94af6AJd^I!aF8T zEenbHA6_}dy4{GM{K!zeZy$3rHBcyXoQteCiw zD+kC{)L89jvAFsw<4s-WgSQ?fL|8TtqEe%3eT$hYS{;%%nV1Ogs*_I_GI{CIdi}|90pD&EgCLSXr{;n( z09?TNJxW7&QifgIZ-WVCN0E6^5y3l;Vi4@XVSvNRWA29mKFV+EBzS`fdeG}v#C@DF zK3M%Gt2S_D)n9Hud|Y^EiAzT@5FV77j>~5L%+4h-lK+x-y;;mZUk~yAl}yjUfBfZ_ zHRt9@yezAP%7`PmvRZ8TmtX&mzYhtPPJ~GrRv5|%W;atTj#)HIfPNbbIKoi_r@M44 z-8=TH?Q%^o6NT8JP)3r>2^uMubCx894aVXWAp7j_cF7gsa*GU(IZLgVVZoIc2yi6^ zf(ALv{xxQp94Yh)rRpVm`4!%#lV7pbOesRN@Omyg@yuj6W1NZMyaPy(puwT#{y`e? z1H%G`hJ|JlAm^U(W6Gf|e#Gl56$q`*DGqfykH(L9Zo(qOk7$m%N!-E-#4qzti^XS| zi!lG^?bo6xeP*aN(IH9CeoXUz_`rQ!nWm6THrNPS`li; z*+fW?48!xqS4lOW$64@rkl_GGj1u^3GLSOoZc~DMzDxZ)rA=xd+WH|9$vDuxDh6n1 z!XVU*+45H9K_bYj#kbju_m6hjZ<%;BV@SMTZ5MoNfA0IvkM+JDS>w!141F048+|g=maukZ2(mr86L<)#;e)$h2-4D znjzqfi332a#bKw(?|7{G3q6_&f)a&!6>qS6OdVWiZa)c11E8?&4MkN?l0b!TWYpd-3D}T02Cw`*a&{B8*WH-#NLe9K-l- z1LI{5#!`LPEE5#)L(I#yUC($^eh{mP_)>F&a!Cq8ZbPuP6ND+ zVSmI?Fujl#7a9g&u3?aMNt@MVVvJnydyhaba^ZDZqz{ry)Ar*_n7fK=JmH7Ug2(@w z|Gt#Yy%+Bv&t>gvzpXGFOl_DBot*}ktDAzZ?mc4aW&^@7HX^FHEa>*mPKEXzj2v5OliJW0-q~#q>3N3CnqT zl;xq1L>O$}MZ#9{{AF=*jJNI>hx6n?B^(`aSh){v9qAB($iaDM&UNw&$_YZcdqi^-QXMqG+-C{I1KBj9 z>^MP&TKf#NBze>_)5DqCg=%w$SbVZTICnwo(%3;B<&*wI77HEZtf}PTS+7bOc)$@t z7%27r)B5Z6r={~5XhCfh_-k?*B@TGaF=?R0O~%2|gcgy7lHAI!GhbMv5d;ElXp-TL z#J7_4%HV6k!$AR!uc7Fhg`)&6GPB@!yi4-m+b@zg&Pb3t3Ifg;CAv8du5rrnTAD8V zEnx`8REZ2FbB*PHWV!ran2Yglv|Rr8oB7At)A_%+x5>ynjk1e|iT=W?C zhya4xeq7|N0Q<-MGQTSGTt@%34&dpAO6XH}FyQ_b41Dj&k{F_;eh}St4>7Z1<6DJ5 zPTGAkFQ+KtGu9{o%H$YAlmL%fdj>pIO+xdl$wA|3xpr<&u@#X_hx;}VmSh7$Z_ z27>00vxx`NlPCikC2W%N#5d;}{8ByijT%AYMutWhZGxApO@k6n->_#sacaA&YqVrD zvM3FJIXj0DH`z}yX(kyAxCki^$K7=O{3Ns|sYZq(l)->k%?q0KU5cWoyN*js5vKA2 zA0{Q4p_9EU6VK+Gl}yE1t&0U~d{#wqP)<0V2%NzE4G3J6oQ51)Rt2Q?(aT=hf%nU# z=+9`6oWKv;q0`4(fuh=+&@Tz%IN_iOh4O7$fnRozh~85{%QA5Hm2*Sw0S#(`!LBsf zYWV%9u5#k#Y6A&i-~qK(fGx@kobQ0aAh6SGP>w&L4o^!jf@)^-;87L|j=^c{(kQQ8 zI}p-COE7&HaFLmZHhz$}>25T+N0rPB1Mzjnk!n^d6Cj|;M4&^40k7tQiAVE|$mPZR zTUl}3xef@O!Qj|{yO`$)a*4ava;Yr;&WR?m%@DLRiXf6~bZgK7cTce~-LCn#ev}0v zifA{VZ%S{WN6jc=n=+IJq2GT%bQ=ua&J%nY$AVY$_Y3hAvgSikg4+s1)p>$Ers_F) zyttAT&1NreKYZv71$zACCEy=AP4?R@gbTJ94Pi58jjS+RhC@@DZjUD?nzN`q-e(|0 zUDA8!?{IKt6Ohd!vq_fO4ep@&%(=otc7yjb=HZeg!!$w@AS#f3y=zdx&$PggYD3J~ z&A#z$F^HVeRI?-;LpZ#*DbmjS0seJjB8BBJ3<83J)j-gG z?h?x$ANi%5bsriMAq4gj2p7S{87H7fGtqmnTKqA4a`i!0-|)L)_*IJ?7GREH5hj=8 zB7*|Ys#3i@InkYNLXe$FBxj~3f%h~@#6|A#B8o&vao*0qT`e$Twp^2F`%s6&Fvp@T z)R`5)XOE~QCH2u=Q};6xZC`cNbyh*8-yFO?m_5F|@{@?2qjp@tbEi0JZ8o%IKYh$> z-wS~_ftc;4yQXdAppA>$OVGAjV$dnrqT?r_dZYe2L~VG0Sp*KvB5p|GsuIm0j)vXIXnLDUqp7a= z(cegS=l-Hcb52owDdSX}Z9r6O+i)=K6w7AYpr%capn7pEQO$gawt>?|h`veyN@p9| zfhcQU&c7|ZO5k2K68LMI5NK&_8ruFL1pj_W@WvU5wzKF?rlwM4rQ{TgC%;RX*iivb zr~$xV4S*`1*U?e6Y+E4P}Mpk&enM?iqYiE7r2XDgq$ee;bv9IJ90keI29HvAf z$I+)Fy0|T+yLaZtSDdRNf>5@iyDJNw`K}U9X;5P*7OTp+1Vh7=OR(YgxqU9-2!A{} z0IOZ^%1U6f3z~|!4TChYOydQPEYpM{$YRt3oHji;4%sc*WhoVovMoGb%fc&5S*l3} ziLSyz$v&8-CTe2gY$6Q9+U76EKpArrfkE->7uxOGMVh23ItOVdEE0IIXwt3X##~2i+XTB?uHa9JFI1a;)x=1`*U`{^+ zO0Sc?GtH3#@U%SzKbIkaA5=)d{-#G!nfz)dybGbgLwxX1k^&#YND%|d3!oDf1rPw=7~z0$3esl;o@6-CrkQn<%+pNxQBc!}wxIOqkGcoD~mcKi@`*y{p;Da+5+vN(;8 zsgZoS26+p~J3zy~xC z@KY<%IT{3y;XuGe6$t+=Gzi@!Y&kRTkY{3<99fD-mh9U2G;=EvLQ(-8F(Nn&R1F40 zo~{c$PeDkXE_Ay|4nsdl7l+<Ulmq)C5lsS5(Rd0kwOtju z4ihitOoF|K4>=LSd-iLyT7 z(}*r7d}r3Zpw%!YCyj)UGu!mD_!&1kf8M}{mof@6yGm-r7{$1J4O;l#qivMj&oINF zFgE~5P1H5NH*}V6wGJWRchtiY_z2MCZUG6gZur2xH0dPNot# za^l_DP92EV+Ks;&6g%~%DFBd`J)ub4S5~lha`))|IDkMFwEB{sOGGOpUp_ByeCH}T zXcz$6B%v9D*wNhf8h7SH*`=5>|J~^j`wg;EkQkW`GFY;;sl1;*?KBhZH9CY|HP}pT z8CfJ0lgXOdOaAdrBZh&~W*89(yi(IJwr?xX)@zNB^iby!#vb6J8VJCnYRNaB9N8VD z>2_)tAa*mvA4;zlM96i9!(q2#incg`xXWhh!E>~+z+dl#MH(NgSddxM3S%ME$0t!r z;ME)yoKr4afG*!H=C^;iqzBKe#S(vQ`;8w1M0N+}L*@BRRO;RtdAE4A2(8 z=_nnY+X#2q0nqx{)tlsq`Q)@SM9)U6 zA^?&UCoyI;8L6@?hXa0NFyOCh1a$W59?Vk=@9xaKz1v5Ks2PFL9MoMjZloySbP6Lw zeUy1cYw)Z_P!E@zV)+@ovRMShP@N_2pL8+QL4dz`OB@;;Iw%dKP++EN!Z2^ZbTPY2 z63bPCQn*>C76AzSPziyXIUivQ@w}RE%8`19fZwnHERC!)Bp3KW9XasbSrbxJCSy@< z$ZOrqG&p(QNDPGsCsu^m0pGSMl`>@DO7g6F@!QUJig<=GBZLlZVq`E>0VacSWJQLG z7-}qoi46NnD=}b|35(!1G8T9_M-1&>eDVI5`O0s2;gbx<3z~}tlmUr$&Y1&BJt7iO zF|r-B7jf$?@CRjb%xH%X$6+cv9&VCZ0--~49!RaQzya6S_JQx7gtV;6;HAtwpb91x z{BAT4hXdc=xetvI+Dkl?eeX&GSO>9mN|D6X9EZGt5h8XYWToIyPPKG%#rZ1qEVglQ zeCYS9IS<0uy+gm+KzOwb8d@u;&Q3^yz;hE2-TE;ua+V<(2C{G25C#Qgd>pGE&g2?qs!s?Jo8CW}OwUt~&2!{+jn3~zUO zxC;tpf~omj)t%-x#F;&p^<_J|-aLO$j-R{_pPDkgUAxja%h;?Dj4>{9s*{rO*hR8R z(c>I=9iAL}cdZFPK7D-dH*Y)qiN9AzRfTXzy11EnY`lwlsv_0nJQ9mrU|;5n*z}}9 zv^y*O;hR{1a!MoYWqNqCT9l|F+v1gMckM*46ld3`rWTsfN(!bB;MpmP4ZfN!Z+WSw zvZ(j9EJd`r`RM0}cA{83za70fG#n`yW)KaH<0c#EaWOZI-?BNB^dN<~A83vuLYCx!_Qm{z?D$zHd%jzZ0Bnon969i?DzsOcOee=Z-}Ub1 zp%CSa#n4AxnMs5qlY>Vdid$K$YB75r)~SZlB^8mwR4&4yvSMnFk}HTPSDbXF6vc{U z6a&9XPjCH+07s+-PMfjBT~P4|r3Oio@p$-cgKbYRP{jGOLy+3waKvz`5oRyK|I|i; zXKfAawrS{{Mp8yzWD~&~yHqDF?N?1-1b++(HeXEye>GzC13YIUn)5;~^kYvpk)xv{ z^|oBZjDDGF!P_|t={l055ar;9P5IUu0^IA6Dup0ixAqvQQ4eMKvYVEVcl# zjS{6>r2y|9?H+|;bks_E)S#LV4jvs;Yt%S8I233jQ*`2oT1MbRlnI~3^k1Dr9b#4qZKh~$E07Ua^MKeMY%cE1_vYJ0r6*~E%L`6h~F+6pPUzcZ8RHAz;vKW zzR~$4< z8<)Am(-i?8WqJJD^-`9Gd?Vk#-24O|)u169Rqzn1BeShvx4HbN4>P1*C}^y zLNVer&i&rB4;B{up;K8+s&fveRZ-)BB+7jE-A+NZZY;44tI*L54#u;jT}y} zyY0nX2EqGLY*2$*BZ!77rf5VNURxwv^)ftK$pATwZxhP>K3~b+C+#Tr(eo!E%EiY@ zQEoY+4DZnna?v<5PkhBhNh&Sv({Gc5+lV_G98gSnC`3iA%-2QETw#QF%2pbt9rtGK z(06HILt>__AMqasKG>gvTxW>mTR%Dw@EZdmmxZ+GIbEdA%5E0Crp7ZAJ=grRqLYP> zGt)fjiWH+fN6Ym$*%vtYapo|F49&5!%rGC}5D*(5@B@QFKP-Hgf-+g6j?W86&il1A zD*7&%l_7B)dP4Dfmhs>yBVjWvKa@*3cluiJO-!Uhh>Lfu9haH_kkG)tiA1yuyz0m{ zAj$^ZQ*p`mm8LLMihUeB~cY7fi7jv9=vEaf^ve?&`V!ve13jDY(i$nvFi^lAO z4VbPJrR&OZE}7$mBEhAEz{5H&JR`xQY#mQ7Kg>50*p(Y6!K_E!SBPYA)7m&P8%X3f z4BLz&aXt>A;>|M@Q7LdbVbQIX1uP`*nY~)evNkuxun9jt8C7X+xvGTTDPhDu=RH%;IZV8UO&J%n0#5ZaT&cMWuu4u1yjJ^WXru`aC#F6z+*rJI^Y>K9WoI7er=%`qeD5Qh_~CllPPp5 zWEjv4$A)JMnb|0nbniEDaZQA>{big##PdJ0h<+aCt4NSMQbc!^{h2J+^F`K#ZRZ%z zA3VZW$d&YBDY!6}316X|WH=JIB*O+UQ6MJ-Q^2|iri#5W+_pK#F`7ERujz>D16Qb1 zfha2J%<3(cqFq>)(OC?Rqh*ZyrwkU1Lf2@RUm5_fQ=0-M{Vowy=`XW~&>$kml4U;v zYLupq$3Y$Op4$G9Vmw;NJ}K{ImGeKohC&FQT3ZEvo>U>o0zRT)0gTOK=EA9>GTBR% zyOO#^wgVVhNRxOSm$$zA)#n)mpemjo#$d{+1r8G$a(L(A zD!q-QgHl9#;Iz@BzZx7`=bXn&*=J4w`J>nl00cKXam2vwlb!5RD9gN1gPCk5-92>h zQX^3%PA43KXt4DyGkLw1Sqg7ImIIUh*fzArEW`m3O6qvB=>;fgoPb3tC%&_Rpfw{n z;B>+v)MwBHa~?R#d6(oG=(m=C{c|phX~M0hPFl36R!;{;81(221h`BAV_Wi-Mkr}~ z=?Zr890y3hFES3`!3!!LJj!?sW4h+$geC_Deqqeup%zj@LJkhpUX>q^UKKFz(FJ$h z>50Vac_7Y<-N*1UMvR@)uE%p(fPA%@{X=HBUCC4)A8q3fpyNBCxYFDNNn&eF6`CJxD;|nyt@*();9<11L{fJH$ z8pcTD#nd!-luhI5t?VMR!M?4R9~R}jCLmTz1%BUlAUHF|hdpsfWhj*i8r$xC2&KzR z2Oeb@-p*Gm?5gu{^G#N)c7iz-1l)HF1b{T14E)Fn0B^860KsM8*G(R9`(9Z(bN1x& z>wI(R!z%n{lm}A1Ez?k!^Hp6=kAd{H1opCctxP6s=Yo&Jw3b7L*Ax4jIC{#y1+>Tv z^N6yl{Y@^WA+daa(=gmv2UV&6&vER@x!+pG?=hql%~zfs>XnuXf+4EgWh#hQG;hwY zlol{>`3??~lY&Z_9UgOpL@yP@%?t%R+ECCWnE9~$aXKRhudJ+OOhi@&rxn9noHnl& zJ6Ukb0Kic;PMy*6czrcrU6x}L)3v)ern9!|Ypq&Yft{V$rJYH-TpJbiSP1t$Kg*4=XCdRawVZ^|{ zV{IAe0*vw?+M>p}4Y%MtTmmvvU{Q;E4&15ZjJ!H4yl#MiqK=M?Pd6^eFQtX=-H^VHgWuLn*-{ zrw=lVT~^4JahO*h%FakUs-^^fZb*co8#q%V!55}T;QVw-sL^ofK5x)sL)$9CWH14uWb_i7*W{2>AR+W5-zM|+|IMbLh?VqxQhdTt*)X5k%Kbv z)8y_??0meB1@`bo+Kc7wvzIOV>e{I$Xx#2~gwh%9acD`FTS4Juq?c*Xv5s>*EvI~D zzi<+Tpxn;(6j!Pu_EOCw#8s&_%$6s{yD#p$9Z#4~nS@>=T=CtGh!AV*&vE03L)Zqw zS+Ne-#Ij%<*~qY22X~_VSqNL3c6x6>7f=ms3^jA+I|c*3P%1pg;hlOAy1O(I)*LbR zmrjUVxZ>?p4$&}&LvvSOfL{9oLZVqj$h8qT)NH_CE!Sq%V0z$udg_PzlEOMN#TE?6 zjxi02RmaHhl9Gtv8Z#pRrbFUEdt2WVG@}Lsw=wS!k_enJFo2kGLJ@!&?K|%Gp6y_* zb(llmknT}~=#0g=%>q;`d_L9nkpv|hM|;OgKkk>!SMzXDW9UTz>1rfs(J_yJ2uZXs zG(w$WJ9zG;5Ig6UvAZ=JN1#M!e`9{ZB%ku)7BxCb1f9T})%Jnw%|5jCV8;|7hV^(D zFKP2}4<_wSCGHhNR1WdM%s67l8lK*antGeL0y;y&DW+43&<~FX)JWhicS2$)%n%6* z&+d`q;jR^^X&acLQKk?(C6#<@+JzoXP9l2!X+$^EA*bjjmhzq{o9YS#Udd@?Z<}Lq zur>@whu}EcbWj@rllMfT9HK%yn?Q#!It6d9Hh~*C5PG{_yK)O!QkSZQ4k4OeTuCQM z)r0bK@pUE(GF~sf&0a1fahID_)6!o)eTl#4!F25PNKtJVOo!KLhPB+T#_SRrn8Mm@ zn0^Up{c9HMyaK)owhG&z<+9I?PxJ39ANhIteKR!=&QEj+F=Zyl^0mTRAZ(umO-f-c zn{M=TJt+LMS>nlP4+>t~P{2!8;oL}QKjyIR9s2fDe>IE&wOqe{v`m~%QB0_F;PslM ze6V`AkU>+=$^?(q*V-_FqXDy%VX8Ue%jqYcEK#X`p)rgGF1TF)BJ>vEVaZ{IwV$a= zjPJ>b9Kt-5`L?=)Xt^c5kMW&#;y#8%KZwEg$ui_nJr+aqJwn@Jbu_-!ghAIwT{;ku z48yNCvgR|E0+;SIpYeMbz@@tI6&yG2TRU!kH~K6Z^>i4%tK+5?kPHC@84dGQdc0+& zC%?QAUfyiNF#&&VyKOY_2S9LGOh?Z)K@;=Z*c}uUyfz!(f`Lo={4C?du0JiISEid= z{Z;5f16Z|6sy4!yzqdtLN7~>|qD=DqR(%Mey^Iw5B!0D?i|#v(oMrIPdYVIV+(q}e zs!I4A9yssQoI&V1>%w(sI0kY=NJbPB{8rCVBl2-EU(H@irCg>Xf5hOBVz`taUx^2p zW;iDj(jK(dh=Qk9(#bq!x@M$w#u8!BU8OUCa*(F;Yczq+lvolbYh+An;n;jzNr&3k z(CzFO&3EGASQ3l|?^cV4@&QJ}?5&MOESTYK{al5!o-5&nSwSL6a5@1JiX?b1B}o9} znNE3>t;!`TbsBQGRYZ2mfifNdb$a(fhH9@wFwg?;&@q)yi`lRL*R%Dex9JRtP?ZmZ za5y2wq1lJF9GuXjsX>-TqWg5Agzk1cUYpOO98^49zk7$ZD{gMeVYaxecBuHPIn-+W z*u#yu|SpU)2mgFa1T|@K#mu4U5EJ5$HhWN!zKd) zv<3$;P`^QBnuE50at#JwnYq}6SOT~5vBV%LYA$GknhQwlN)-obe;Q?nBS%~}E{g^l zyeRkK1r@Q)A-HI_jyoCCp`6j+8rMYfK>hKt1|#%sNip-K{0II-}R z3jD_CfOaLZ=#F&7(~?h>!#i~`l)X;bFvu=Phg5r3I^a5kqTkIx6UnM6gIr~Hn`mce z;947negO}+uTa2E_K2^CvGUXh6$OxsG;=5<9Z?kUu!KVJrI`=`g*QY&g3Q%ICagF@ z;Gwl$5R|4`;t6IKxX6&;3sWs|eh(zdA$O6N++j#r|DQgGBPX4BM!zKzLk!K43YD9cge!Gt9Le+I$}*On~2gu^~USo z`KcQhx+6l*p1*qv=45m2r+=CJPy8fSOeLCZ__(Bq zkj20#)ivEiVj--s=duSViV7p6iYivnbg@8X>C>CEMt!?ldX4SuH%3{(!1r@ZG{$bx zypR=#){S&mC6GV+G*vuK!C*kkoIA(l@XFDyDo5p76b z$X}h|*mYS92Yc#r1ZOA3!Bs|%pe!OJxSb&hZcP`GwEpyZxviFH?4NZA`BP^@oa$tQ zsVzicCW}dLh59&M5uAMk0fxcx;=%gY#ntS=WjQtLtOq_;Xp$`22@4Jj~EU?K61Y3_3HY%T;J!T9tQ?~-&PhFZv_LNt9**X_z=P1TUtJa zJy?1CvBu2M0JM%3PcG8|3|H?zT@^D6&WyH!NUVsSE}hvwGpBsN+!-s?*9{e?Ki%B$ zDEBv;H0Ri<<9Yh(Z>57>)bW%X?*hSQLirRTlvx0-WCz=Xf~DdIJJ@n)gxXMAVv<2m zt~Rgg-W{JiJV8RLPC8l2N=%Y@=mnT z``0<=ETZ6ZW1)JbD+*83L6GS{yI1SQf8Ts7CSkolMQtF#2Sq~+XP9L2XFlSo%E_j= z6Ex7C#0?wAJrP&VbqFDRay=lD4REC#Vzv-1Y`PNMe>WcC#`Owyz|jZ8F#~@0!cAasDmz~?+MGF}1$K9=2wkPt9pP%bPt^nCd(74$q`Z@&Be6MaO=&I>8o=BI9gY+H^KTtycfI_!;n1CUH-Z*RVW|8LTW@%A)a4h|7l*7VtBDfK_Q@auP ztK;_hx5y+9+{yEijrpFBmp@dkO!2|PSHC?P>ZI0*qRmDRxe4hY11xpcnH zM!uuj{6&7LpQUOdGq)-Y9rNCaoX(E?vkB=FbU=*rtw?Q~af1Pp%M zF0Ot}%b%CCcZ;-fU~xnfdN2hCyt)>TKNtt$0aP$wY>c375g!6~)Q1Q-;Mbxe0uISs ztUk=1d|YpwOzZ_9I1qS#2mpNZ0FlV1Kpd%w0E{)97uV@56zrX{Ew+Zh*9JLIRWE5= zSIwqeEZ%R^+1yvx8`j}d_f!ivF5)i8L+1$s?q2TX%R7~2Lb)fB$lY+})E zJO6>#v|W3e`dTy%i-GEpAuLXh{p=dUP~iey0aH$W+kF~>9CKo*tIS%xStgT>T7YG- z>u<-i2Ne~Hadz9FaG@``?LyxnC`K0`cPE!l!x}RRLwlm_B5k7wEH*LF@tFW8G{92f zh$Us{xH1Emf0TP$o~OsqQL;km8>9UE81oq#ff!N~@b>$#{7ILWgS@NR8%{ilYM_%BEun!+}2j>0MuT zD$!#rWxUIph$n+y=4?o)Mu)~~Mmt>Hltd%#g64b~AM7~6KzCIz!cOUUsFn-BDSS9k zW0ua0vmSh()0nw{Uw~lqGZJB13A#@0C$5YXPcAQ;CoB4$(j686Td@d%Uz-&FV%g}& zIYBmX{_3;!vfS@@uUv%EDLh{rP@s1k^RM$aHi8Rcv;XXk3Xd%?JGGeX=k=^}8z{J1bdDDxRI)cceIld!jKWBh!sIIb1xWHj<8m z#+d$GT}Bc)$i#cp7Tz6Pr2>LBGy-{hs|u~N6Ynw6WVefxvPU7B(E#}2EL0pou?hzC z7|b@j_?{MauAAWmUZ)BYKUa8oDaam&91s+a;(<%$1v3KzhjZN&UsRwHQ=NW3NR zjITB$(O-iw5A&?_5*S_uCHZXiX_d}MNJVSkmLEQPC(<9xgb~hc!KJ_v7rVCM5Dd`z z0U;56*vUcM5!NdjW->8eKD_D%oAXeXV@@RU6wYowT7SPx=httSzm)-&k2LXHM zJXA{x8%$=%)pTOqexYmsY)^`e35p~MA8k;rh2hC_2jIe>l!REbqclL4y?(?1E#%fg z(~DzOMcM@H5=U}X$9g&loMeFLDjCOphuSF$qXX!2NV0fdY^y>@TY;tp=At(-(m6X9 zlICbrQ*Wkics8F?Y=Mb2?^4iSDIbVHwRS6r2DDi%8kFikMKlDJomaJsLMkXu0u&fp zHKVvP$&e%rypTdNpxy=rBUq2NU>Fc0A(M5)@m)XbOlGyb%biV+BTfWm+tsH&u#s1*%?7M`jJfgkLRHg}!qY$w{=AC5N3+tFfY5n!g`5f9lR z0xdhrY3IdnWr;8BOw9-fMdfa1*S4Nf!J@E~2EDjR``uq`u2%0mlPXUhKr@=dd)TCk z8xv>*^=#bHl7q{#cS{e%pfe%rs*O+Y`$W1wPeeQ&lht!gxeG`Aq>6WV>C6g4xkKa5 zQsH%qKQ6P?218X*pAGuE=(;i!zflyVkYlgEyN{dMmEGsoMCFY}DLdyOAAm**4pieN zsXwLTGiDd7bcBYpK(Tjk=T6 zc}SU>qy{Wx55s?KZi+Rx;HY9j`K;>4g3C>@0 z#Y(@IY@6us#hE6IoX{hHA!73PR5G&kr9*hCmJ^DMSoh%lW8@VG&_PwGbbpVW>{FoS z+_Xy?ppA|h#xS7Ckej)wE5QEOO6m!XpD*i z@YCBw$0j?}6h;el$;k$W@fX1XcNB+ErhvvY8Icbgef=?Q7v!}B-c`#6@enOB3Iw*5 zvLRQKL!m!sngNY*Tu9RlpT5s7P=LE10{m8`A~+PdQ>zE*IOJKbZV3Tt)rtn&Jm}6f zkP)UCaNJT_y0j@Kq0!))1NiGd699HhGy)ME-}#Uw!mpdA;m74>`(fD#gvS6-uG_kh zAn(tdX<3AITl2x0mV@?Rz5@fBr&z-N%OFm(jE|EU%756z)Hhi$m#qemNBUB3|CEQe@b(x z8ssZ_=Ii+I-r8R>FM%ze?l_i})@)h3b}a)XHUZ7fu2N72Xo$!Iy6p5}ci&+cT2%-b zKSc(nNs&1YQ?KZCtrBU=XcZ2ePdK}K>a@Z!ny*~om7_utV>v$|o?mHX2#T@0`LK&8 z@MFb-mtr7;^N5evv=B0YLWe?8gqw$~P>D%X0?=}qI@@wPAkfDeh!AOF^oKaXe8F`2 z$g~&z;N$~Cve#dV+X0}F9fv8ExYqj*^Lw?eBp}VG3IkuGfamw7OojUGnYLYyhE5}) z9B(gU`CeBj_U=XPw1a~nxQxWC#=x|L9(Fr96hC{`J00}X|CJ|0N&|bBorY8iR$)M!mQN)9?G1<9VWv}=K~tdrH9siNv>pP=VeUkpX%;-Xg0hRkDvHFd&`em+EN4ww$yIN( z>(nL@kSNoE5u%GW7#zL`h9G21pobJj-d|d9pqDaUVT`V6?CwpUIIoIGj;BKKExY~y zY5B`9v$r?ft7766kJr`{f6b0%+#tj4y37qEuhL>AjQS6MyIy>p{j~Ucxx?)c#TkjDx?j<&bV|!-tu62)6H_VQ*1})_oRrV ziumHZ3EI-cBJJLq789oZ@UNf&GDDzN6$=7TB|`f%1(2P}b&K8P+wJEfMhQ@#^ciNq44QL=Un)YY-al0&;;Zz5ev@nBOM66YC^>Ig` zz!#JmjN5OcDMHLgp(8Qm$|NMO*%x9!!>Lo-ucgB)oEP^D#n{y@;C?wDnIJ$wCnzaG z>68P)i3pxo!@;Y`-hHVlTmdrQ+~)*tXZJpMFl0ijPUh{$n*_zfRJUEo2Z>nAhl662 z;v|X53E*=;MP2F2QQF3PF~`A;L2H_7f?N4qT&K;jO8bFED+rDc7j>$( zb7I})j?@Dh^Kdt=yD|e!a^moM@qU@aA)UcqOtQP~GfeEpb~q$a7JJ-kT^kJ8GE#L+5_cz5v-f43q%>fCl5fzG$%2;5Qn^Y=NB z5YGTYKc^)Vmx%-Erc93Z>})o_@0Vnp!|LgDy2KPaO>%Va)H{%VRmvRWsjE`x*{e2W zOp#ZmppB~ZVA69Sfv>36n~$9_!RHhOs%}~W4JjsD#re5km&5Hz1%UAm^jJrU(onev zegOtWpi1%ta{y~u>qDah9p32*eg>#+N2im=y-}u;xNsqlG5cLLn4D!}0wC$WT_|iH;!~#An=T#~4T9b_WTJr63U~ z@nbq9CfUmMs@yFYf>=X={^XFdtVzWC6$!LL#X^(cnC2@9_yO% z5*PrIqA+@^JIooSwJ47l-~9qcIz@^DAjzHuE=6UAn9`|CT&jSWs*tOxDwAeWY$snZ z-e!bpMEqLx1L@$z;^s0%)2SSBf&b!rnuEhHY9ipT3P1{|N_W~ESo*nXsrD{4e!vMS zpnmphLm?0v(ROPc8$moB1)e{mSq@=la~yVdr;`*tyW`VeK0Pjq%Y-wlHDP7%Aa>YO zb!w@!vr~(nXX+7e&3@wV*{dB)kK=8%Ok&^#1To|XVNg!yI@1*(h#DKWRWjqzAn0O~ zn!Tez{07&=MDa=QX&Wz$x#9tWC@ajJ8NfpXuwC&mKP#Mk^h2C{a?Gq(ST#5R1$rY; zpq(FM@GM z-~&pDFkyiA*CN6pq=$=N9$>b5bJ~Eyt}1k^KYFmoz`Eu&2KnWUQAnH33YWWOUAz z@_ayVYCeP-(l#GBY}64d4p912B%_=%5f<#;VS+*75-#BFDik5k!5z^KyfT@$=Byov%4RAnJNpp>LP9>9na1?l_lbq7Uv#-1BydiGGg)=I;K_^boodJe6|C`UTu;0 zLtw2vm9l6HzSE&`@7SLtg+Wj>Vo5a`+eG8pG95538Dzgg@fcrHIGi#iGGaf2qG+RG ziednbA=f%@=@27NYGn_ES{bz+4u~!-f^f(b({sS_0q2pSRoS*UZa6S_DPRP*vJHkn z08+E{Z9_5yMh>I|46z)OWaKz3-bIVll;X-{Buy~Qo^7t)FD9+!jt9oz^|itTI1+9N zJDPc_AsEWQlLAMFB5)#RBXC=#V{~qqsZu#@iPCdX$tATK;GB^@=`H)!Pe#dYOh=tsu!QczV zPbGxZob{borW7vTZ?3OXH1hQtD$)Uf=LP}jO9OsdNyA}!!Q@@Ufo~a)LdZpRQDA-E zLvFa`Ai!wX5a1;mdQ`J18r@Y>DOgthk4-8df0p*~>co97|M3jpwz%&OzLev>(X%ON zv=R}rpyS}SuwuQAQc-QzE~IgAOZr?M*xgU3Sryd)mJDLp=2nIs-Mi!GI=R9@ zY)HzTyzqC$p0S=z7vO^m{>k7@UJeFaDh$#cm{+`Xx;*Ye7iFWFYeB+*%5yHFmGGQu zrO_qCuNT+YZe;f4TR8K;>1cFKV;o*KXE_}d=zj&pVUl??(qbQ#NTBfe6o9alv zB%8hY<`k8mNpVo{a+et6x~HV)!G1>x2wGMW=~A3Hk(0?zT}*S=9%5OHNk&kVEQ`Sb zkc)LG8z5gT8ze|mKR6`tvC3q~NKe)e*%ZXkCc5l8&+ssaYIoLY@O7=*Q6ghjBJIHQDL9-uQz2@wDQE!yNHq-{5jgTBO%#CYOG&W-rXc6m8_w27sJ z7|B{T2uD*kaHrOo#s@Zfx*Ji>su>1+vC+YlZmlM|(N5q4wsC%l#W-Kf{jA$a;IG-` zF6&F%NJ#%Ne21~YdblU#+a(YL34ASkmgzJSkR`7~NSDG0w9PRB>sWD?R?3C=)L!=p zj5qN!;ewo+qV;?1x@rORC~HDD=)oSN+02mot9@IA@Uh$fvXCN|=Nh6G*!V>~tDX(1*nc!#4#IzR^5stU&oB(vjrk4h_I889HUeYciad8KNS5LBqVJO@*$lNf9*kVy263L z%Vq$!Pc#PDFaSan4G{>4MnHk@s4PfCfc=4PJMYGQgLOpE+?+Bad1%7s z1X0EWPoz9a1JI61pU?d+U-zBfuHqow;Z;V4ag7X383$28k)J&3E_-u+2_57tyD@k? zj{x&ys_4IhY<@dqP;iKthuN*8lY2!VNOypGT9DWQ*{=qOn;2m&%(5>c$fBQI7{AFv0|cR}P5fChhU^^d=PF0(&I{kW)h7 zw`xO1#XV+AMP*D@6ur2IddCPcU_s)x&iG~1nCfY&?>?s3Cv5@l)usc32xzI3fe}&z zc&L^R{8dOHSChdgeSd*q7fKiy)47JEeExmso7 zui2}ZHuuE#PY!@=q{WO*M4_8BYoM@2p6KW0;^sH!OE^Wzu2+eXs^aj~;!|2scKK&c zJiQMK+|CClo?d@ktrwT+c&Lx7#q8c)U6)ctU9X@175%oTi|gV$Q5WdcUgieCR2FcV zv&rH^(NxG~QyOCCP&i|;M5!M9D!1XNgO!Y}po=sR!^umXeDYfY(9~xGN!_R(1C@Y^ z(pU7%M_mX56b5ucVK5XXB$F!;di%!)Z^}eRBhS# zCQyJt3PlV@4GLZvFMhgNuhJG_Pg0_yNUSrJQ`3SB5iEi$f&QzII4HovKgGWsuQK{Y zka&l4C{hW>&)?I0TzP0IK&^3b>U7iu1D+QgB=nd}i8A1jC~o}hLR{{L=7`$lmC@m1 zdAUd@g}?o{#c^`Zo!}Q02X?-4Ek_09h69*%fX?j28+@I)mPh%y%7PpRC1Dyf(BU=b zoRX^WOIaX*Q`msF*QUcz5c!zQhMt4X7dsR(YXk*gr264FENH^5W`0J}5|0*XPx{Mp zUe=I6C+fIsl-ZE}?_z`LR-=amUu;NF+X@P}qbPJkPvGb@17q;C=k}+i=K@}&T)<_G zK;>b3hqjt#c(hJ&3>@&382ojyPUma$W0kx6==qb<-7O{=%33S5tL^PVt<`QG=axlJ z&7GnWzFIET7HmjS4gD2YOETAU5XF?c6BAE@+CI8>|I`f)SN_P@lv4~4JEbQ~YHNyt zZikKm6YSZ0VIHLC?V@M3)sp_uwL4Hvot^ke7VS1(81+k62lm@&`&-z)XUB$UhI?1ohXFEHYbDT96=9`+6F5MOD zangm_ngI|-%L(hd%{Cpx{_tp)igbJ49h#?H;iY&qA-nEG1o)|QXWTf@x$7Ph7pC8$ z*N@xTy9-{wiATWV*)q+z%$}DxfJwCt1D>7u2S2EZ0Zc?yP^hP??y}AbXv-!BMA%e! z!~wTEIG{-)BV>oUHM%G>rPloxm)@swjKBvJiQw+z{WTDxC?OScHQAb}j<>HXg5ym?J|FinhWO1^@!Kc{&$LGfo^QPAmjwzn+E@)D9Hm*tZ;eqOMAlQop1>DYFL)bXb^aDo6>4$V;=KA~W?RNDg zEf8LSAUS911OsxU5aH)J>L{m}I(kyruxtHvvpK(8puzV|vzzgtKD4xHGgxf+e6jlc z^Rmzp`-_>5W2O-34G~0`-&B?oL15D zo6if0(Q`cN%x9YxVLd1%VF-yKQQ4)qR9^_0Mo0=p%6y4kjY};kVX}oLgCQ5bdC16bE3gI8cgN+9rrhR+qb_ zf?-DmzRszKEIsN?q!y@9#QJO_1g{JVE?Ic7!0K3UMAMrFg(4_R2Lmn@23)FJ!K=wI z^!A?NiyaI(Laks3-h`nLl;qpZ6>2^|fB8AN6%GgdKyh$%z+a6HWu3Zuv{eTAF{;Ec zubH;+?CuTq_(9kcm^t6<>W)kwa}HfOiEd_2ih`!9rqLDY6vbsuUQ8CsqdPr(bao<1 zVb#~Bu=OA{d-9(8#4;#62D|0y+L-VS*^~L zin?1a)8z}PqNws5Df70$(tmmj*~twD2~bD3(xoqO81;h>H>-3Ej$@@?#q4N_*C;f> zX5mh)t#56+bADX7Fm>KDCXI~<;GDDDkC)r!r?hw7_DkX1f_f^O?728FPBsGwU_q^_ zjS%>yPI{`=-f`Jk=k`kFQeRVM$O$ye8E<=!kOH?eC%QQ@G{&KU1vP2)5>+ z$M02nkR}30$dx&6I5?Wzzo8Gx`m)Nqln;k7LlMf2|+95!Qn)WHD?%3 zMS@zQs*(s**?51Ags2Ic(R5!GhU$*S$cw%ZCfV)YSu}i)vmxC|iC8#;ABvRl%KW?J zcPwX5Ql-8R{wSDJNP+ebLE#*VC?ES$sM&>8BO(X@UcSh%pqz<-;Er-)bg4cLQA-EO zr6pg^kaloDZ?lh4=8Ev;8VBj}uFcbhBm1v{!9)nUQW2OSD^jts0S_{EFrfX)2I>V7 z4E$A57^N)@1-wKBL;h&)<&d^4nJ^KBFzbr%iahYj$-^>L%lwiK=}uKtpVJd~PU>^< z!CEHpb48&l84;iU&&;M@hMMeTNB##5TH?geBqJ;i-ks!kk&e;ChR^Bv^|X8BO{YfX z;=u)aZ6(Fq zqLg3~`c_$??nWm>*P(G=&t4ET7?5P&A$XM}0pUQ;oO}IXd!5#{{kU3x-V`wxDN(Bj z;-W5PDA{pnfPIPvejs2RN{#Y-2@AkQNOnF(bfj>*1BP*fVq(gYCse&yUB6G0(M4JW zzfdsnlbRVJ_F@5;Qp15kHZxB)!GXZ<{pLtn1ga=>JRtpJdC3z4*@%C8Dig0I)C5Wd5hu<>m$qCw?|yZN^qVTzE5-oz`>F2@XFDy+aAWko#e z5x1Jo6^vZ8P+))?TgZ5rTCEe`nWZD?R3uLc9}8xO?TG#yRn!)-dcY*AMJrq2z6 zP(>NetFLDjmRD&$VfOOo!-r*xV$!=CN_yu$o4O9}Eu^m?*UNKx1{P#|A1N&7&jLt@mxoJPp&|G}NuE*n{=* zx7m}c4=EK?Z1mzxHU>sfajGe9^nx$caw*0MFyMt9-4xA~{K%NlJ!FBDF(`JkRAQ^p z*ler9lpiouD&WdA)n69ZsrWic4`9s_BLFTCw8 zFQn2m-N01o)5X>A>yy}2j9E{an} zeRU<;UDIJy-rVRgpd_N<6r4psIT65|Wm#QV$YBEBurW#H$DTjnNU9TPuaY~Z=+N1+E)+;37aZlzB@YtZa7`0gG zU)TSx9EAR-x%Sm|_Ga<*^Uc?<(>C_0;+nL_HoPxgrEr-x@PtbIKeHeHJzxKDeEM^2{KTM02-+Xh*kptqUR^~Kd(>~&zW|GT9C*(7a@DtmERc8<2Fsm?wdt;y|`J= zUZ<(CujR5uJf@m}>Q8JV{+dI~az+_x;8RJNuMCrzjX90g7%b7@&f(0w0!)&!JK=AGi46sP8$3ARDa{vEY@_ z;$n4qwfsJNkxm-Y8!zr?`4Jq!@LFju@hzw#a3k|81xej!Pglv1|sZk zXoZ(KWBDePXMM-QwVT3Mz*9926c+In!m8G`gcvF0YO<=x5z|3Ic07{{ZD{gAHphRn z{C<^E;@)GxI4TN2J}XdM3JM{v+K0k{qLCG$pkS#de9p}agv1+2n*;4IO@ht{66m34 zL&^z%PYeB)+xNbf)jQEt3{@}@Vk&%~R+th(Kw(N5YCFOll8f&CspBj@CM_9xIA z)f1rxqb(lNd*HwkoGu-CphZvMBNMGgcVZbxIyJ!sN^zere6KbcGjQ;xaF+OfnMxaj)a>h;>K^@f|zQiGn@lQo(H+54?cL;wi-7%m0T{+H08maxo zgxQmK=cjHwxav^`q&s7TH{|&Md^cNZ3_>>3-fN=(DuutI4SvT?r=RhARZC{Aq+?AC z*z8VWdFjMa%Q4Q8E9-r!me4b0W1KZ230vi1Qs{AUz524a+WeXh`Ej)LN}6O8+wFK6 z{Cmd2H4f5yNRpWE$4=#C+|F=>h!3}#K7N`*N(*sj zZ&zP0)3O66AjcTV+JXUB1tTPgfk4d+)0+qSwcYhe6Tk>*5e$%k5p>0)bSl878TP5z zeDKP`FFL?yRZ1Kd$3aZc*%}NJ6a2K33E0WH8WQ%?)rIv`XHXd=Smo06S0@E1Qif=m zj)1~3O;=xkzS;VXz8o{qqZ*Ci=%WV*f-xi}w*sPnKpa}@yxYg$ILrFteIiFu7Su+; zDT_dfwtUFw(hurWVhDvYDZu6#Y5bg!JZ`DRgE^qte9fj6~2AXi`&YaoLP*@j1~AWnN_{PwL}cj8?qDE8V}2w8JrR_g;{ z0Lfp3$L!H|-6z0GXm8t^#OrAR65xs406+L*yk( z;y!_d8I0gG0H>zY@a!fT%a^Oq>G+)+pDy-i^EoJZsUdi99MHCZric(_heh65vBILC zj37l!Lf&lBfsH(I%lS{fNX-EO;mS(1oQQ`2Dh_z4(wb!SbO|XPD9c3z0SQLKY0c4R z)~B7#u$5-|&wnpce#&D3KG%MPlaJt31F)JC`0K6mbU7O-&ZH0l;zoUJl!(d#w^cr3 zp_*w;G|rijpKj73=xv&OUZs-}%FRLAVBk$kj1X$I1;mtvz%tdRv~Bw7R~`gLce^tn z=D>Ii$8x=%{XHdRu2PucGc)+GQUkwIL*mc~v+;PSrUw2h)Ho20GNBE~WqyeUx=#(K zI!7eds(;_juiNLjYSoAI5)qVadP+4#MY;N01#^H z?m%1SX{S(Ihh{Y+fKx|MNV<;8iKuUAo57_@yA`xjZY;mDyhoWIIj5f>36eNG_jQ{v z86J8lGCpMxfDg#%a6<*1cRFU*m5o3lj{r!TT^feID&}9&F+cAF!KkenrHL`XAAS&+ zD6kz;#$y2hx3iBE>dy@VG|#Cm+0!T;8FBs5=L9&EwaRkVY#@J%r<4-FQgzmWO6%^i zueM}?muM}~UtKV8JA)BY>n5#0%z^Q2kyaRgo;~>a@@D;?Kc!WE3%_uQ)2fO|TVlgZ z95=pDf}r(ERj9rcon3d#bcv8~=zUJPx7Y9Cu%HPlCM7I8Wgge9c>fO#R0%@2 z+WsFtQ5!Z>8*(*OpYIZ5qR!n?CZg82Y`C2{5{eITs~HsJa|EB?B*pIiKeT<29mgRk za7q+(Qg%?s-71A}DwFX7hB?FIu^8_Sf8Sy;`uk0hQ1dg^PEJ5fWkmqOu>zP>V#1bB zj|zI}Wg={(2u2<)P)@{?MY^1LaP;U%b|-LzXuH68y`m6inBi7aRi-(6YiyzF+7k0- zy>4gOAI0;Sxi4>avdh{oVAu4qxyx2|9vlZwjRbSL#-bP@F)80{{=+U0(DonB^xQcS zhjYG^R5%TGBArkt4w(9w_>f3N>12aUA`(olXD6FoSLRaAsCSyvoNyduZ91lo; zgCmTeQ<}P_g!#eD*M;?+u#IFeQZnuNi@l-70~WYa2yEL{S1rzSw+-s4j3@ z1tf&!XlK)p_;I<}en>lBEpaTh0|G-@13@WiDhwwY82HMymiTQK(%d}&{!^Kr`X1ibK1$0$K;Qf0Qr^CSrAy-pzuz?mv@GTUJfP+1# zR@8uqn+)R}YDaNpGceo&?a^lYdzoAIzBN^3RUaJU3`Yq(U%8fQL|Y2N{6}#rM3>lT zmkDN?C89CjoSp<)ps|7mzU9#1ftIfqzm>JI_+%{}ghj+-RH9wFb-1#$(H2_peG_Rh zI@6w)IlWqZDi8N`Sb$S1BD@qV!W0Zps&yv*DmqiHrizHzlu*_qfEaJGajCqkzy>ry z*uX234Mv?8pVJWrPFLb9gJei)h%(+#*S<=wzy7#cUafw^wtH=l|Mk;X_^{R0(6h@P z|JbxTJq#TJj8oRu;^BgibEmI`rRLuq?c2rUzAr5qJU;L49S)lg2R`5O_)RvMEzTal zcWU9gXQviJmX-4XJAg@d0Rr{P0qLMW{HQpnwWjsP-uO>%Qr$pWo?PSzo%u$8YR@+K z`*^;VN#nzSt`@-)JuRYZ^0u3Rpx+SPbFNbz>xJYJi;|%h8j91Lt)z5TTgFIKe9e zLMB?CTzl|RoKG&(LjJ4wpVC$rPF=KT6&wyB62|Lm9E1SC0XTI;WXEq!3CNF3DW078 zvsZ1RDToObu!somWDyBf+Xe%#Ohm-BOS@cXNrS3QfK~GWe^ovN@o;=-i$@=aAq&#W z2#muZ=u5+Z;$ax@{6L%@;F*ADYWEg@Z6kmt4Jk`DdAeKGTeLn3f#5~pH%&d6gPhlC ztKYKF3_q!PfWImVRTYgo^W!EI&feZZ8`D3KhW|+a-oX$5=iky6TK~0569uxF)q_YyZTiW|a<+4A#s`el7*Q_K;j~aN@{TKZQJ9wZ> zlvO%1fl0GLz8DBcIV!7Y5SKi$|E(!lIe%&PW|^!tFw%V%!EptGf*=WWyd_(c5(qHx zK$#d+4#n=W7Fq<|V98|x0=Kg~bi+>c%TeN|6^=JeaaOs#C{%0hOF1Bd)j%6-Mv%NT zqwK?>lhK`RHpn+)MMML)Ga5lvqFtgL0vZq2sbW8s?4%=RiUXMZxn&fKiWs4e4Ii)Z zV2f{4(3d@hJu&A|J}Fpu0q_-xxS)gmZwW{~g(|HXk7uhmZRv!B)^sYt*w&j zdU$sVZsGN|^XZSDyoIPZcNZvTcXw<)-5;NaMmPLj`e#B>87H3JdM)stU_2vwyvo4!<5P=iZVoec+# zGYaWaeLIm@`gRovnN*qs5RrBuocc)xSpiJ#%?CXOJ^wBi(3qIw6;5gW_3LW!k(sih z?$$eZ7|je1E`^yvYhj>j^(~_dJ2v3_52^3$vZ>=wYe*zWF^5D>rU@IoryF*8#4Cgk z7!`*E7qUNJtUmv|EQ$<|@v_>aQ(kq-FU)`92SXZ5P-%Mo(Y>RYa)D6x+V%xPQGD+ zUcjt;IrLvbQGT*>13B~qHe5aCM$kE? z7@IVUZnH%)?AhvvvfpGh7yZ~E-6_onajwxrPces3A(z9@f8NP(ASPrz@1 z4KFkM%upGs`_5Q(piaeCbj+`Obts@^iUJrT*5D7;gaX74r|2@`w)zLrMM9HA4+M2M zw5cTCr}BWyI!PK7A|R|4BF%ctg9uM7$Q;lGM|a1iKn99*x=JB9OPw88edI}PAYOvA zkfAgMXw^V46e17<;lv;iy=r5=&!i&q0=F|Sg5TKX#n4u~{;YNkWELA-ijfGXleh73 zUXwu6)D?q(XhK0_tEp~Nn0m+0MMeTGrtUOKVbMS{#8qt(5j6qjBEr%HA}USMGCO5( zH1E~*@xeYja9i0C7kZ*29j(#XZWI{SDgy*mW`rdxpik{ji+lhNsmYt}MCIgyGK2hT zX47+0u}T)kW7-W1^29ue-sX}%A>*XP*_I$BxKm>xnsN^}28E=^r;E@LiolCq@smJ- z)?``){Ymj)yp6`Wc)PPxvyZYI68L_$vgOPI5U%wnMqqD{wNFaQK#Stm0wbzWCnPb8 zj9VfkyfR|E9oUE#kJhdx!4bs~^hO&IgpsW}W-Hm9$ zv07W;*UF1grTO@-W^j2m6$Kk>)fRY5ixj*46+}YNPL+_c^*f!dHvzoj;N-)xVH6I= z2DzGwgAgSIT_jfDcsLA#XMr||SfF=KNwha##ft_Zs1_;M?xUQYzFK_dnSMX~m<~Tm z^H^#7=j-L{U8=B6dvmpCSAM*Bhd$fv${kQCM?zy~SLh9?wgRDZI>cznT{$~b;!$5w z23`7iUhZNco*pfuT$=qq(`gzdC-Nu4&T@7IKhtsH!06;JyPh=?x{;ka$HLRW(|m1a68C ztVSISg((FZ+97i;PG3_<_u&!d6e#XgSV3BY!g40PzauCf^3u9qF2AIsf?@%UlUH0S z93)ONPlTbU*$q^DhzH@Z%MKK#pl&^aV!Zwq`YuIfL_^# z^_4e%tLj4J9)f)!5@FtrZ<)r4=<%9}5EHZKm{xhnr3;VJ|8B}+0Y?mUPl-X15n_yr zl{tL-VeGC%f(*{u1RXxHO1Ofzc0*NMraw8ZJ_>_yrNqgbZw&*h*ZtuhCr$YE#< zDA7SNx#tzAEP)_7NV~oQ#qAsfbfaao&Owpf-n6LY?Q-#kmeosQ=4bym)hI05P7fIGDv zVZ$jPLXM^#8N#8|oCK4MKqrbKWepKTapJQ|h~sYIU674*Eam2Na>YKbu9qK|3y%d} zr<@=FA|4Klpe*p_+KS-$y@BBn7G+RKK#2ukY|_D32oAWDaR|{dZV4ZPV5Gea(rIf? zS>%pasb_)bYYa$@rX|JmH3q^0I)>@~bh+pULHM2{L>q)KBX1b+%1j5(G*4;Pw2=7M z;z)02Ht-WQ8@v=0lAn@V<;{l=z2ys! zpS%Qq7I831)QLDSq|GgncnE+}0OFceBd1Iy(=K#qDurk@srX#PadslBo+TK*Z>h6G zkM*Wnq^n)5kM{X7+DRRAI?n1l&6x&K>tc{=C9-$1!4$=T8cbkFf1EA~N8JjQ(Ke!A z%oXI}FN^C`afzepQ&V%OfzPN$z@_5COOb_ODIq(xgFbiNaLX4WChKRHVo_mMP@fqpdxm9A{Y1w5@=0E zBBW5+|5lFbaoQa0d)jaI!NFjTnGp7dr_l%!3ei z7h8A?I z8wcE>{VEy`1zw5?4HX=16!6!*@l{k5_=sQTR>oK&6AkGwF%nYXK_x}d6J1VlrTmk$ z-HI0tyhoA14{Pf&v93*+F3r7loK3@9ty<6j3>oG(+)sQy=x~D z_(78hWuPf>9Yepg3UU|jxlMT|oixE%Cx?7xxg8qRjQIQ}xX{V!(s5yrNambe604<` zwVu_*E27oy_WWGdp{u$!eJuxB7%h+Po%{WNxQ<@9SIHmoVU>>PV~%^%<)7oxX|aSMl?&EIG#R0&Y{r5+g8(w;gmcf?3K*MOLh+7_ z(3nyQ8DXeb@%oz*x=&-5QtDQuQvQt9)e5GL#of+KDwmg7=TTL*!hmJT}IQejzB7{94 zKrJ5zhg>z~V;>y5#gPGyK#d3oZf7_`NZgf?-oXOsU9C6qSJf7MPZ!XukZ}Cy*#O55 zIfk@Fxr(34m&$_R8Zkny$OF-xUBW}ojx^8OpuwtPK(`czKnmQcNx^Z80igWRO15n@ zRddP%W-PNJh*2QM)@26T=4146@9l#&oU(Ajfl+9iQ9M-RK*1&s+Igr?Hh<)9;Uxs* z6zL#WJE#mwaV&6K1tac%ivbfofp$1PaLsz-w22NSh>Dnxe@Z0WH6Ii?CmGr65F{{S z9VFQQR!*RIG7-jbP&R^!z_)8o$nb**l{nwrLZC z{f5-(h^o}ig*t!Ybldrza;ekecyPMy#cKU+ag+8sN+*!KmtE1*NoCvRudBuNDy{NN zBlN?Izs6(!AX+*23Kf1ppdEcV(+P}o+<`x9^hJRdE|AbpKN#~RZz#0c$^Oy&zR#eZ zo6TUjs7(HAHsDuVkE^8~Hi2AWEXIfY7gY!V7AF zkpALX!Gj(|1i>_%8Vh;AP{?90QsHJ<1drW=_98(6w^c#J5X=+=uPoxwGbUbauHL7O ziGP6s%EQlqY)fTV+Ejh zYC`ID9^HIS=lB+SqO&6}ey(7I2&@f;@E!aRSPt?qduxOFa!b+27TZkv=}2Iy)Bs@)b176r01T*>oXs7{hdj*QPMdsP9|_#fNQ8J4x0*=2 zN#zya)3L_OnsL`FT3*#__Y&`RJ)Xl z+1@O_U!|&YZ$#1a+L^$wl?@@EjP|LbK!4SQSf1v<$;j?bdZSQ?MbQw@CKDd4)4mC_ z=V_;+f(gfHe~kiJq3SZ|OWdmUr7&QRiWm}#dG8cZe9xKCA^p&$1cuI00-Gp&PrEiP zx5cjAUOy0ks+KK4;TVCzQbv%+3^~~J>F#BzA1s$UiV*!xnb8^tLfTd&%|QNTwJfD# zulWq~kIrH^slZ#ruM#wx7GsDGc~PG@6Er(?vBzN$_De&2D~TXilR+pa+$dt>K?u$RnjjeP%3P_RmZ_xidD=z-!=X+T9#N&>`j5Z1 zRU_^U^`A|jEC?p*>Qbh!OfV3^MzY&7qmka@1c4jN{It1R?l%quW0$cl=a_cEOf}8) za`|ockInYe?AcXPJp4)(=DhsJGvL1~E96<>Im#J3#f0YRFhBrvkRyf(x|!2GniJD8 z*?BCx#iQ!piwN88`Kb?b?$Pd%jk=h}o;;t6=c_>ugjf8QGRScQ!b@4k#EvWX&;*q| z`p-(fLl!ZaQf4$VnTtnfl*sJcuL}a*L7athB}YGHqSB{e zxmcAA1;xZDQ7M5RC?!liJEtWQ!ik@Rz4CyK^SoS=V31%q9VFEALI&JXAGUwMB3kF< z^UWstZx>&%PUV}^QGNqpXRX0DDbB z0TYGID^u977uVaQM`lmHrD)Fq;>m%&s^nmV6_DeQ9)nQSYTHCbt|ptUa=y*{7rxHi zdgGUH1GkkM{X76T=(zEGbCVYByxRK0AqaTw(Gn3+-J=rd!iHNow7qzjFE$hcBQVwq zLJW7&10h5JL8f0HE>`8KX@0IAzsQ;tmjXl(5J*L>D+~xU;D8C0(u)xi?1DxIM9v?n z*+?H#AmTC=cE6=JSzeeHv|WEo3Db|o+Rya1niFV?LJ(%bn6@?+0 zs5FnelWgdV6O6xXmzSvm_ZPVLPDg-VH4bElMtkK%G&)~v2q6N-aDWo~a42a(i!|Q3 zJq~khXh+k#eX_+?aX)Tvu6`+zz%Obf@KM#30@`_qMkrcu0GzF<58-B_2> zVb&src{p?QDhB@e++hDhtc%yy{g+r%zs|_3cfaDnzc3WwW z&g94*uTi-_rF)e@tf&-(H;Rr^8Akc3J#xgD1x%`$k(M(&4hAaTyR%=Oz#(+!h_+dWO!Tczethm`E zQqS<%SzYm>92zlGj9X$d0~#;Br&S5-)^^#Iw5nq8;?0ra}n3(f3+ua>T@pqTSPtNVznD zQbrii2MlG(?y_a7f@Xdlcz% zZ=f|K6us#K@XsL;tp{#*pg`-I(YH*#!xt+g1DdF|91e&?i86tl-MK1%e6e`HO9I8*Q4kZk9m4>W#qc29*{aV7z_pE zG{nNDwRmN*@S9~i3uu`zH4 zgb+siV4%!;jAWr1GGivefXiHY$-#(J`MH?jhI zKzYaG6sR1iCxUbLd~uO>_W>m~)zOpaQVj=sr*MSYDaQ)1s4`^-qLKB02#6VV!fdSQ z`!+FsN6KWRsu)d*zJvrMUR|dN79`NV_%s2- za3uwqAQO>JSAjBD#^#ZGrx^k=%?nYbi9S$9 zm?yZ){)k5@(qp&J%KdivQc|Jb{YsDg6KCoa--8*`k|t)dGif*$3A z4>zmr)@ufivf@L-An*{WW-g#@Bk)&+<1pQDlzXU90BX?<=!bJ0`cv_zVdqS6N*IAA zITiV4g9(`p=3?G|`n>R)Vfm>C2L~^kaG;$vJJ@F0iUZLo{YjFIypSssqrC7172IGrZ&Y4&uHTq&PJJx1#l4FHp~6}S{)1SevjoH*S{t~)2f zy3t8blyJDrI@xA;?AE}k?o5rH>SJFlaCGxFl(-#Z2x@NN^NL2_vqW#JuuCDB>QWtd zjx35|l08tt0n(Xq2s*>WB+%lg#rIh{((Zjq@WDY-^MW@i40v8ifzBulA(4vbhXM_b z-(jZY_G`mOAGq;wySSXiYHX)1+E4`fz(X|@_^a3v2St>ReJlNXrTKi1la3sp2`lhH zWyNurq6M9r;l|@dN=rORn^F5rmD3as30{h(2&HWkkPvuAEnsp=gu4Y0&x4(9e7K1K ztGV8#RSGFhRTghR0oJnNUS` zX>+7}zzt5{Y*GUGy7cP&YE!~eIsCzNtMjZi$@I_4UW)H}A=G3I!Yz!Knk(LrCD4(fX8&fs-8cn=m z7iu{1=QCOUopl^u1B(Z1-N;rF1;L{U!hk+FIyo)QpyFEyfT&2bH23cLO=jW5sFjF* zwOI+g%<09SQvQ5)u}V2~G$5JyB!HC4hyY+ z5-wn5G8SRhkq9tvOjJxu>T=+x%T%wwEkiDEHOY=@9zN6~fYUWI*k&@rmvTV^C6{W~ zl_6^6gqO~-@TVdhc!@a={k(DXr3?;q%u(ZiEq+Zqcg$YAPkZR6?>cC6dW)oaf z2$~`?;6@GbA^O$gIW;iY>0A*HJeM8H;6xZ7cxB=tM=AV$yC~OgVC-rxI3R*#v^{HK z02(gZv#B$8o#;%el)~XARYvR0Sf37@nxuHJo}uE?35B0f!O!YUL=XxYnz7D9nWIbF$j2n@KBVF)oNelAqNz&Jhmmn0FN(%xSQgw40FpH>bD z0H(6xlmyD}8w3 zAi!mIv2ZD-;{WpSA~+A$ee%J&pEsX=opN$tJOKEGa)1Lb!r(ZN00{AgxboFh0#?SR z1tDoA$n0hB*u0lNmB;4Yhf|on`%pK8mNngbKaqez%l@3WaZu8yl00sTWYp4lgI}I- zcGs6*-{qUcg#jo|E|2cslT3-oCW8(U&SD|~rb8IXbMn-OWyckFD7UPR(P?GgDWiK8 z<*(?LUxA9(*0Lx{8P5**v;#`%=W~9KhZGiE zDlAZ3*`So0y5Iw~h~TdRi`bGTEJNBN8;0oMM@aJ0e$ks~X7+5u6oNs57CA@~JGS4~ zsZQuyEFQz#6bh#;cqv|8&=$a}h5{cJD1<9bDE2WVn}?VLAkHo!lAjYeK*;pm|nG8g4{+YNMC5WYffICWu(IC*hdiDQ2 zO9cr<{qYD4N`L?1HmTqB_p}ec1Ok_f>m_KA4s81OeEq}m{qwv3dFS{)PtM-nK}?eV zfn@wg`WI(y&;IA%(&PWNN{4*Q4>xl8!`aC%CyP7hCt;1k+2CZt->w%QXFn}|{~Zuj zQwV>18>SFgfu4hmvRDZt*o-l_0g4I%lc5@9=)?iKapn}HGQmi^8)KPLBgdWHI6L;c zXdd6OD1)9(;b@;0YoPI_MwB531W5Tc)ugV0_SN^yPG@V0hm`eq$8L248Y&LbA;OUU z*m1A->BjEy+@mBR)*jGcwi4&q`zf6^TOOduL9J~B`lYah#9&()7!Cs==a5$HZYWB2 znKrO=k*EaR&K4p>TxgSc*Ss=!>Oq=gO_PHuQ<|pY{C*8QxUbfXKs0lt)Qm3r1TIPn zi@9j*+cDQZQe;CY22&;?XeTIR2FT6@6+F<^j7PATh6gEPc%&2j)~PCGowobI5asD5 ze5hsy{+i81h`aFGA%n@GY@;zmja*Y+Au&!oLV}QeZ;1yoW65t|S&D}NKd52Aw-ts^ zWek861<*q|Axwax;P_2Pq2vP^HDN{H=mHEbUV5@93yd8C>~q~Ac` z!1%kn!7krCN+Lp<5DnFw6s}An-hRAEB2n)l+IFZN7y3xxp~`6BeQGq67CX~vimo$_ zl6Qcp#3E?S-Bg1p2!NjzM3_@T6Pj>v`%JBIb)w;yf8)0~d525!s6!Br?q+8qT~@>Z zTRJv$NI_&zS&+b>ifD+CG!U&-Wk3Rh)ifQ5DrOhgoA;ke5pl5vI#CCmK~*{tal|mg z!A?w95~sbx>+uoiO%Y8&0e3ol0DetAaFhIcKW+ztqXRyW4Mvznzz1qR2n+W3RD*md zyh_Sg%W|582@+_tA`vq)rYLx2iXv^Vn{39#`H!7{rNr$xoD~UZYbb~k~C0RzAi0i6pC3j@q?9qDlCDo4+?JDkj z9=5)^kDlhRZ|vL>3$2upAh4=xIX_#9rUsPt4oo#&!rq-CJ~}Nnh`-M_aZu=hJ3SR_ z7k9W45iRDHIH|AH#JBRq0eY^`LpNpn1QKKeMT4!T|0{HkpImBFQX(8Yd!R!*zk4F; zOD31B`@&Acsk<{>f4fF@-}KW@H;Ii~OnYB`Se7fWkI{NX2OU*(&>98jei`iG)wQ-3 zu>=~eTB&cezg&I(@^Mvce&+YCbV7k)SD^^oT(zMXMF?Si zv)245pMoeZE%l5j$kY(0AEOI{?`9~vaWA=HD&i4K&XeK#4V(Tt;b_YSK9QAYS3lqr zm3J>8@Z>accdk(!JJXikGTWFTqh^pCVQQz-5ospnDq)cJf?lWP=jF~movBwRBq7)V zP%0$&V78#?@>VmBJ%0w@aLfpn6V1%T2&w|@Y^sWPSSp)bnupu(DO2W%fq|?I2gbZj zjJ6rYclS;!4avy-Hb+OqgW=F8K>@9GoX8bJj}{-i+*3x>> z6Wvg!pCpXiU7{Z-rAi3<{v*^is;WnoMA2EoCjaKympDy@k>9uW5Q3#7Bu@~^$C8#q_9NoJk9=Y)16k4SfbeVK;_sQ&1``iawo`)VE zRQqsB<=Bh5yhNZA8HRKzy3&C_I*$xAO81iRjE^(=r`4ff-d-%OZmyHj-0XLyC>YH| zDSB8t*fOFRvSXn{)0c{IhVaDk;J%NySd1y@QohsC;QY+jbs7tPh&v49hhj76Z<~5Z zd5UzJW=b5V;>JQro;oe*)Pr-TVohHg5n%!mIgk|PuO_QymQ%4Omrf(jiw(N{9|ex*TlQ zp5WT&(9DaSI_`@^f|T`9%y;}kZ2G+o$Cm46RcAv7Wyh9XSM2Gjs6yhkjOQ8YH^Q;S zfGG;5pJXXy8-xKi9+i$QOWnkyqbu(1_Wfh;((!w$o#Oe!K(WotO$zi@|NCayIFR|& zi6<#32swbll!D`hLqwD>M(~d=CWFrKcw}`ZY6{;~IHFUBK@vE4WlmixuD$%8pt!yc zvnyzNt*M;#3Z)|Gf32yQ(YaMleeNqPEh!=v-Q3&mCQosHTl`Rvz~DKB{cMS1W3J!v z$swOncbwp*n(^S#I3zG4l}97K%~YM8_CfP;4;@XB5(=QRUAdg3c4>kIHz)!ru&K|p zO-d2@Zx+x}{qVGMra>okkJao=@9T7ZS{IDq*EE;;qjbEH4Z|h2o zxmxltpMVxB8}KC+3kadGK|0vPV#s)uSyPb(z#xF^6hwy&;g}tRfLEp_B@^%*D&oV} zvUCb#IOx#?MTzH!q~c$KLwEU7P?BkH(aYJp<v{Mf6pjeER3dP^ZeuBO zOfRmz*oVWyBQTgMM|)~rC=OEapQnOwdY6IfFB<8aK9EVq zoneR>50@v7Idh9CFiy^Ol*5Fu-r3nlZf{VBTTUL1gM|caH5%qZD+D{G<|r{_A$M${ zzhpoNFLpq*E#y3uVcI3nos&dNq>@URf}a zcGuaaqWiS^A1;&=3-oGGTLw?MEfsi+pdeS1`B3hLKrJ&~DEfzk1Qc*C$YnZt{ zY10%31MHx}FiI@&;vq8N{`3+Q+C$Nie8G4Ky4o=yfFpD2qhjz-O#=K?WueNDi`QCZ z$6tS3trwSRpQw+k#q8eQUcLS6r+-CPokNQe&kpVA6gf1o4gxM!H_7xo!@ApS<}^Pk zvVi0@$D=v~?p&)x)T0G|#Ap=-_fqg7C6s9G&Z9-wGxZ$zWXZLtqWPdl>tMj|GYsic zMDoCl@ZJ04O75dK3sF*|9S@o&8Cw&YFBUgxm$?+%i6aKQ7~R#TXX3>+Q!a zwpa9gz!wz^6Zd!HWOipODat&&>E*89<7(3QzEV2#K?s_ z=%Av&OVJP^TfUDCXu)*f&e$$6@GavVypS#Kk|CXkm>m72EPa~P@t}=Bp9gq88&US# zrGnh$&h(MXS}TX&`u#D>ZJhon00@OCM>&K3(IV|rlGg7zX~1(T47jXmkU%JN#on78 zmoUD2&=y89VSGNAFt($gxx*jFxvH+d2rF8TS=iC>utE6tRz^^Kst_P;*|mhnDb{i> zmra!r-6_$hJEo-BWpR=;DP$AvwTG>%KSf8f{8N-<;bGHb7AwJ%n6rmn?74tOWIYH5 z<}?`HNL43aL2SRM)uqls9fbjxiUO~iAPmXF?Y16>&NX`0w2|D-3&n9|K`};7Gk@Iyg@SEd`DFD}voEtty9 z=PxB149^q}@}e%CjAjDkUwaK~HNB?+Pja7Vl;ugT@PeL*c@9V2z0OaA_h9Tm@0{6u zvrK0P0g`38suP@7JeW8(#de7v)4N~pfj~s8D?^Wa(9A+^j#wKW6&+V&&Rz*-n?i-1{Fq9A>T zq7giHUPRkaguSe~2>i5J&YrE8>wjwe{ln zZWjI38}!Dq=*OUJ_VLTI5VW!B;xoaj!vUemEcOIZjy?IXT<>EiP? zxjC-{^uDh%pTqlTx%jl(6i+5Wvy0E3>ljcqWpCDv!YLDhD$NyIZ~s2AVU-rnn$bL)@KMMo7KC!^s6AGEGULok3)9jL9l zOM(tZ!$g&u+kE{;>+hG#Su$I{1y2TJs#L*G6brOK^;N(EU#PJFj1v4YZf%Q)sO9E* zn4LCOW(WfGjPOA>1ztIIxSw5_5cwDyWQn2iZ1riC_UK6GzkOS#V=~dOJj2p<^a;(r zI1ryxg&|4;?Wd4W$D<5+NHuB-Qq3%RL<6@o7(t8UmIw;9hJo>DvA)E{Ekk)@Cnor{ zQe)y=0^2?viac8q{W>DshXS`1is;-KF9?c3A^Xrh+N{&Y42~3Fqsj>0TI-82>js&s zWdvW{JE;;)i2P`0ucMp@2H+Xx&d@lz->n@w3CUAKgI9*ePwCvR*NfjmW&jV>U=SA( zkN^fCRxt3>Y*BY$h*6p9;vJ1VRVE4unxL%cZ_yZ7k*Y;DpOdGPJda`}ol_M~J{%H3 zb>bmK0uLz?W>gPEB0KXUAEqj(lZ_G7zEw^jI{avuXo*0EXK5*A9s4=Sa1iiP$PggF zONS(bZEmZGeUi~jr=v#$_5-&wA-ZKFM8yO{k^--jZ(67cJXK4E1EWrEz^p+ttR1w_R9hY2j)cABt=`h^}&h24+v29oI4Ed~>!9_=;M|Q)Wtg zo;F@wON5w8=yl3-TOx-1sqRJ$RdGgHQg{r^RUYPD4+5IXB?Fi*eDL9Bl@?q(v+0w_ z#G_iw+1CyjhRT;Z(6^l5^R-JXb(}Ks98!hU1rl?WD4>`u%{Rs)bexLBPn94}D}?<3 zfo)9){B*B_y-!7!b?7dSu9qeh=jT%Gq={2dUP%$xwW1>;7zDVQ(CW+ za%JADXfljrE2T9UI0sXC+;q+y?KRufsQJ@zxEn^muFixP!PEq`{_?)Vi9&fHv z$WgkLZC}bsNtju1SO73(geXg>O=mhdn9g(zFyg38pi&t&RmD!ys+om&LLU*Jx(KZJ zDV;x7*PeP(kO@Kx2Sm`B=<<+gIQxO7cpK_QUW}+b;Iu%o;F*w0x(Y8c@v!#@8eouW zi<1zMcbS2GD$-URzpi|t2Gx>E2ob0xq@PwtU$RTVe%~S(FC)+c1SGC`Hd64)lxA|j zma`}6080-8hOkD#Q6ePOai`W3_^VhDxtbbriP(gsI$vc>;4^iMQ6|*!8?hL?vd$+B zxmjPPIBS`9iS(6xcy17Ysvuk3*ead;=;P}B;zP1YX(zt6Yx^kHVp9iX*LG|zcIRMS zo@^^)Fc-Dm9E6>6(ZV6oWhR#M`+i}a->w82!wKfm{bB<)9&IIpS@^chhsubsh1t1f z*A;iPA^Wz_)#}^M2eZh~c(rcAUj>ArS?7Qu+7u6&vMg@eydbsSts zw&H**W9E;`&Gy4`_Tv&YrXaaOG;fP0dZ+*evxNbwMU%ruM`i*mA|Uf)aoGeAJ^c(y zZ!#e0E~tB0whNI zt732vkPj1LUn2qAJXtXaI*0hQVGhN5kKQzFG}$ox-g=k;>&`75*ptAMXk5Rd=!Lt1B_vc$8OHy=LqwzYozKd}QhKfKDWU zemj)H+iQ^!R}soXOyy)Bp-hNQL?HSICJg9@w~`BbJ z5dv)7sgW@4Aec-=!mOd@pwd^GlLc)Z8Ou*760Lc~*UNMo1b0P+7nVMflTU_%iNOhg%L7{k8Z7uLu)s=w{ zcx}xG>HnXA1Yc}OwCNBQ{^K`dECL2xzV_tT#g(T5o~{AFUloAa4$iwD@(gUiE74A3c8p zR$G{N*e362w~hz0T{+GpZj$P8BcdGLnO=ia(;f1_?*7sIu54W{of`aFAO!H z`UlknC1oNCTk8C|cbjdR$a{D+OXt4(iXgwu7=EmQ9)7LJ^d%JiS4Pn3x=nP`-EWOP zfn!2W919ZwR42|eD0pQ~%!?&m+vLQ2DW;D6A_R1;o+}94>H+UdCTz#*LCNTx1Br$> zg}p1juJm;b_3&|lfmg-`CPN=CK9p<5AkekGCMyJnQT~f`o(fI+?p&EsFdQEjnJ{*K z_YM<^w*M0M-b3pJ0&>D2c(zXlPz&k6!`bUiIt1goI8NG?K~V~-7%vnH;{CNi03MlP9|(De z1fuj{91x@RV_b59A2#Xmc(wgBd$CG8wl3rGO|;83Ezmo)7NHgjkPV?B5;8T;3ybcl zXUBWBCtv@=#6&Il{#;-=e2}W3Kfjf$T6Ab)S_}~!0eXk(*`cLI_ zk@!rLgd-jbiWW4D#goeqi*26Y-~ zJIpbXjhF(SfQSSOcv>L^wQ-&yLDO~U=Zx_WjVZ+On{G18ooeaOiTikPPyvVq0w{VH zRJ_@wt-NBW>kq#KrgcOSTn9%73{mAe$Ouo?3gu>)W+P~dO)DH_yo3h$SPm#cY=zDU z47@VckQQ4cx1qJYxl@p|k(Kihpg{jsGVr`YA=dsjZd7R~7_8A=5?Y5veon9el`|G0 z|6y2Q^c*Fg-E6OCFIS(_2D{~UJ?NS$N?c}f!KFBq@#IH*Lb1R%6bs7pZJ@YyvOFGw z*@dEikT$1{6%5A<;?fQ;92mG%Fi2&BAy-Wqi3$^6@mnC2n_(I^_)6o3IU{wf)fE)H z!;$0VX8SpjwrbSxUlGGn&#= zoR5~5X)RwWQ~dv^d$T4vj-)&EUB9A?CNn)q(}v#A4ZSrM0>nZ>Xv|RaBr9|mff5Q` zxV;dAk?Fr*KgT^XJUk+@DyvC;_nMK$WM@`p#>wNym)QP=Y2FA-l3#IXI5R=(%uGTL zTN{l`48^5MjIng1XC-(&2S(^p0(lIIxDNmtV{Q~jSv+M=&?`)J|A&mTzmm1zeG=+N zuR1h%sCE*HQ#domjS)1WnJ9!gu1M0jTgP)ZL%Z>DkY{p+Y~uEQbm`!YfQ2c+WvOwT^QIK}Cs^!4@9PTlWF|%aLO{4%q@?LmILZ7bOzU2*+!!OX- zCZ?8+ajhOT%``%wXk}s^*;n8+8;03H%z=N&A6q0O(k>+?pQ?|~q0NpKD_4JyB zm#?ip0PoeRbx_cTs+c;S_tnTlwAlZ`QD zl#Cw_;4BnGG?L&ZhC@&*aL{l-JItx`18#EaW7@rcbf~9wp(&!tXiu#pGbb(2wlUhv zMuA&693J0Yd~S7ww~JE3)^dh5Ww%sNN~%>f)MLJolMU$X+&R|O;e<8~GG(tl-@;61WRv<)M! zNzqjZE<%S*zho|{pD%{z)ga(Di4f)LjQHL)2$>H|=ce~;QqCH10jVfwirPe>MQN(#|x!vfb=u|H&|<37h^qPutQR*Z<+{XY4R0Wh8zh9{f!i22LQUDpro$t11Lt?aqi`Kch=%7F+;No# z#bPR6P9ed&Icf+bpC#_9*KWZ>N3EoL%lRiR5;^w+>X=8G zRp7T4UE_PSLx?sW8c5>%?Ob0k{Dgx-OUwhaM%H_aE4JWO6^kH}Sj+aithBoPDuMRg zIZeM_OB;^QAtOM`IVdFWq^K^FcA2Cux z82~O2(F_Bj();~ac%5KWBGI)!P z$qmdUB{z&Lv>Tj?x?@;>T&z%Y%3IN2AL^lVROZ;#Dtm)v5CWUCrYxo0#`O^3Fa>H3 z{6qHg+Go6hCiw0XxM4FzyqKW?R;np7@Z1%Hvj0S5HoU;rzPg{*&Q>N3g7llrm32$&!y0&*+ z24mk~?X8?rZxy-ZcC z;qY4%?_eH4x%S=K^1-&Jmj%;293=!*N97WbX5YP+`We(X@R})<4Q$9b;N2V>&aQ8l zH?!x<)fXv{!jGf@BDijp4WUMiC)7ybmF^YAl%0$j361FnnTRPbPIsUn05lAmNRH~c zYSD9I5gG|4uW3N)^!HxjPufK_}$ThW;`&4BFZDb)dL| zZZ?X5!MpXPOj(lc0E?-%Xl!i}xQRiqRc@0Z(4)x^lpUmtP3`V~k?Kya}Tz#A0eO|~ST@uH$fz|G@Z+?o) z-9v#=Y#r7F#rJFn*HA$-1~^R<1dV+)xK^Dc7mOBzwCxQ8Cv~Y_D&NO^x?JPj4NcO~ z?ao`A?icIbXN@d|Cb6EQKC|3C7EMnQ;66Nix6J~M=@fd=HEv)qppgax)Ky)C0|ECJ zi5yxk1Lu4NvV^BAOet(VSaga`s)8$EDQvJyE|dMHuush3lyx%TU$ct;0>^sZih$26|H zqU(Uqt5Lw0HfDr@wrwN9!|sQ|(XrZ)4%Q;6=r+`W42Bj-fzv6F3Wc$3CSbFJ;_duy zHG3;kSavu2QMOJ$`cUAaI)=bw?*&DfV7S#$6_f#rVfLR22A-Rc2!fHVBalM3i+OVQ zMTTpAECd60OJXXAbxgU{#NSMd9kuhQSWDR%2T{&iF_LM7L7n(vQz@iJN$RCPE*C*M z+enZgDxJYVz?Yg8{C4U~<$Nc)9>&yk`ry0!kQ3dEMl=$fPSgl+WNP5Oospn^{#}MG zEUquM$h#aIAp>$-AiTju2h#0S2&TdAHOWJ_$T~<0m5Hr-RdBsJk3SFr z#bWRqM5#)^yN#z! z(=DeO;a=LdtD1;mi+T@4+&ew;s~HvXebA%qwW&;*j%PA~OuC_D{k7Y2eD*8!`@fm5M4Fs+ z`aTufWIZS*REC^=;0cwNQbULx05T)mar)T)(o-?wM;a{L3HSTn-gOX;jZ zwVDxGC3a-QkRQi+DT>Izh#1A;w7KiJh6&zDB_fyyqPPvaA2>=lD1x`zq!qAFi{fyp z*UQ|DPE=dsk;6Cwox?aV`nSq$^~Zw*Bp$Cn$VO)}!q^W7!5185L@t>_-!Twk@3dH{!BvRjW>)wHO+Iiu#QE*|;&W*lekL})WpXAs)bJ$~L-29T zHt?`2tY;&pt7W-Zfy?*Es!WE#2`eGIX#~%KQ|3K!ls(6j`L$Ta`Nf^o%$!;xATUZe zAb6-i^!r@#C<8+4W03hBDREFP-ywINIL^2)5kw^8@cz@?^-4B^SeE`1!e5&PertpX z6+=e|999v%$$|^+2VT>%JH^wI5R{o#X44UeZXJXMk};6Q0550nbUcFN0$N*RfZrMh zK_1!|XvUF|20SKf4vZj{C3|rf;d3W?K{K4O z%+d*fhRYB?^^TLnq%@ku5=*>}Iz@u&f`k52M!^nf{H;MquVmEZmCoq)W${ibh1(<@ z2!XSU7=d z^OE!n)v$w57-xn*h6VXOJ%ldHFS!=HwUNXnYX|_1gnE&qrl;!*naV61=6DnF9f}SI z+{8>I)LnqI!GMd*LbUOq1Q|UYhD3a&gl1B>U4S8Hr*$R~`k1qY1RO9V>6LUa&;248 zte(|T6Cu$=B8|z%rP8-XmJ-}CMrk5BPFRM7o1dQuqhY+-(rUgN1 zCfaCetrB>wMv_i3^^bW_N^V9csp!cj`IY^t3ng$S)u2Lhg;Rv+2v((VipJH z)KK8p$(fe*I5f3}B9p?}4=ZO2wj7_+OEG!lj6z1UQi`~nP!|fBC2=|Xb@AbYGYyDI z?KfOV5&gyn6cmriF0@yJB9a+}HW&I;INC?%!uiGe_EzHX8(HD37;w-=0lzjbR9#3G z)K?uyKV4l)kHCC}WVSOU<)@#Yq8&po5_eok!G0JuhxRw4ea1cwjZzkZgon;Hrx5b! z(2s?2Q|+rb|Do*=XpBT$-ReQYdzfj09ceKy92@iS%OuKB90UE3vfK;v@y%*`%#Nz1s|AL?IFt0sghnQ095AIa;p_ehNc2KEh=cD) zOo%BB5Hci<4s*r$^;`Ll>~<&N?CkyW=2F%l^ibegwMaX83WZ^uF(|-tr zwcA^)h2q7UPFu?#=eNtR^Q-mmvPzFbq8;DkR_(af0Reg#5US2l2ep&QI=x@)6~pjw z`$AyfQ4x}?(-O+b{QBlPMNT!)F0&0DY5)`bm9q^zq>Ar%C4|kp()gYg1>!(hok*0n zftNSi*bh@shyZP)!eTBP0bmJ=^+&G~P}SOT;|WHHko)3HEfV-^70Cg!Z3FyrNs?XU zGL9j|E2((|Bwj48KeU#)^PA^5PT<#V+t?J;YSuH^Hp*PbK|zi=Qluf4Dwz0?Niaj5 zIFkamaAkm}-?%^Mn~Nv1=apq7|n$q1Khth4OIq4Qh3W9FgVdNs1*$H6s-$w2SP}2 zK-#8Ra9Nh;SiiTJXzd(^GYT}yj3VT`Z4fdM`1;d9=%qJXVGtKQ#E+VuQ$`XmuB7+q z{8swdKim~vs^Cm5ln#hcGsQ#3*svoI$rC6WsJqiVMsOHeCW%Tmiu2pMxhxO+{Ce&? zmeJlikit)m4zm7bIY`bjneF;fPIuZ?Jx)my|2NW)vdd!0`z%g(k}G`4c5(&5G7iA* zQW!}cV+H7F#7D%=RDNqWCF!^rqIXI@X}vh67$?VmN)lI_Y9v@Wzv30CAB8QqPmW7c z8i6-YesOfQWJEQ!AwXR36)|*IN{+4XCRqKM{$ROtlbct)L>mnwd?v%li9=WxwvCB? zVYmvmVY*6|;^0DutucukY;~owy=ZbLZU8rpD_h0Ywz}OtoMwuRV&R;$%Yj2IhvBpZ zQHLRj%0dY+9XoonCpBVxe_s5AN`P03R%au5Y;b_saI z!UvSydNEPSPNTPMva=6zp_*aPM*<8qBq(vJn|Ss6+DAqL&u~Z#%3Ie+gZIQ6X(WMf z<4Dn7TpdrWy(chiTL;dJ5kiNSO~>K%*l*S9CTHP+9*Co2p%@T9r5Ir}4tc=5GgwD| z3PzZ10mB*dBhP2!pIzc

n8R!EUiQ#8<4Lt}rclE7xWVnb!gb5Y5-QAvcvc^eGv_XaRD zq-+;;Cd;TgLb-s~@x_@He1&3wJBC4b#a1AMY!P!ID+LgI`?NV^a2q|j2?7OUTg$2F zgtWPeN9Ieit`!Z!oI9G(w41tj?DP5eg+~CNsS&_SOd`Zml(}>G@6ELNKUT80z>6!f zNc=$M+Q0tCi^8>kh|Ep$?8_o4e8273<4&!m^$M3cCjvMG`f|({bg-H!DmYt9?2OO29+R>NouBeTj zPx({rJbGZ#co?_{t)|`QRo2fkl_z#e6jGelMbm3KIt3l7V9>Oh4s^bi4RXI@sv;TK zOP?jS%BRY;M{8UrY={(%{3jE0LRbsFS7EyGTbifgT@(6oNR_UZj3~2t!P>Z zhSGgo?KJGvr9d##`h*iWS3*EK1E zYma8uq)@WM%+laaQ&3P!@<7?Z)zk|J8YoD z7!tiCIT_}VNYAOhbV$Ui`z{hl0ldelh^2S$j0eb6M|}PHRzPBQ6ql+f?jjnU@>;$; zV?j6-2i!46L>zFdDH-o2Lw+-RFZ0g@ADm>gaR@2`IM;koX$K$ls;MtG8PQTYlnIla z`~9xj2$J!1eI;9*JzL*i{W@h&D}thVkS#+T$61xgpb!Qg{U14Ta1eHNIv<@lIK!DZvdrY{ zR`zo`TS=hNjx)}lhAD)dR%5{VGNFJxB_|s|ns%TApaz3O>i7Q}c;8-(adhGX4Zj#8 zI#nmdn(qmVF&K*^*U6VjqC-g~;wv`rlh=HskA^}v6%GTFjlmxr6NnVdR=#!Ye&45z z8#_@Yw6cT$M~Y}=H-cB^k~x>!K{U(YVboYl1>R)fgrqUEcVn7zM-RKq1vy8Gv1~RM zdk+r$HeYN`a zm*yj|BgP%LoqWu0BnrklG~Tbi%%uAKizLd5EKwT|$BYn)Iyiuz=?ztrcXTOA-GL@- zQK)keYE#gnrXdw&AxBgGV|7~`B7?7)!XOu_S*8$k;PoaT_-Um+@sdt@VoZG|4_ehH z2;E$t5($CUBtnGRtt=ja5Q@X!WsT>;69Rg=j>++XWLCO~bUc)-sa$S!wa0+y?UX@1 z?DS?g_=-g-Xk3#I?`09ZY?C2dpGkhx*^tgO6P1SQ%t@s=c)mgcjqy_oc|6-iZ0>$2 z#^>uZ9)xs@up@8+bzAd=tZBy#SnGI0&dLnh^&csv*E%l@1EV9V3n{m1Hm$XhJjm)J0oom{svC z!1J|kKpT3ohI#hYtip%+^(U#6#0sFtx3a=q=WeQrg%4+s!Dv@A1ap;U7A63 zR>}6yvLT<${KgYx918g2##pD#2bBPzpv<76#-rQV?Y&YqT>Li;i{R7ZM@=8*+3j^} zok}}x>P%LU47}g?fQL*$Xs0*$_G1d-PJVq{aE;a|22>M1K_&%-+~&D-XhHJYXrZen z6v49xea3`>vDpsVVc&1d;0}{jg*j;2k!4H4dS@hfm670d0_q9B`wC;969Fd4d}JVk zKZ}OdK;WlpGnE%jx!Bu~CEMTMcP3NKBnZZrXHA*iUbC3;?G)y8!3b*NW)S>bRH zT~QVfWsm;PoL9IQomY4=UoPh|X(F4Mt-HlbPacIWp1AXSC&mDw+T}|y3nmu;FNGMH z3dbf6&X8uMzd>IfANc}>he`v!h~B$n#QhV$E`bLejE5LZ!KM(yZ+Z`1 zM#30$jVAbwWWm5%Ib;m9NVcshHziB&7dB%gbDYW!Ra>I%Db5H*OrUeqoqD&zVV0}e zSH^78EgkSUqb?*)QaxtP6XSbAqIv+xsjRGRS_|dPQ^gmL0qys zA^YjdE|Kd{uEOBfR0Nn^yAvVUZ~}r4RL&*6fHpOzJC}|Y_=ulSD8ovL2sFe4QOXB;o4kn-ZlFa9 z1X}Ej#uGVP`Lit5Sj%*+2LfNJtp?fA^d&GvCL4IjD3N7DA=%n`ma?JUr?Ql9cp%bq zwR0KVDhdQegOFs=(DJ?~t1Bt#Dz}^ltThyPktx!^iFVY%w$rWp(P|<{2PI5#z@0>h zF!h*mK*Jm%ewKB-%! zmJMirPwi}nO?}7(vLc}Ics;+6=qv2-gvSjGT$;Y@GXi(U42N=KYvCv}5>z!zQsHw> zI68y~sxm`>p>c%ZiPIm%xz;4FQxd>?loO%m)4_nRRc2#H7?SKO9h3yn5tCrp?<&f` zgeMCrYx#9{cK&I-_#&zlAnA(MHVpWAJ1gN}z@@>!)^5t(Zgi}Q4H770WCY-LjEItf z+omT%4HH3Vc@pA}FJ@1d%VO&@P_QNh-ky;tLk)H7t2u?!PnYNOkK(n-gaT(Rs_BH& z=YPczp(mM?)b`CXBiIR4xX9QaJmo_sd%B z?8fMod$3=W9{hX^heCjNI4zZS<9jpzTrRBZc=?bG17|;|DGXGtt+RYu>CD#ZI=S!4 zhy4r(U!Cn1A*~GfVbIt3Q4@)0i`Dw06k0CUk{5AmsCnzkx8lLe$In$b&@M*?Zd-EpMPeQ&85pM1 zLjc2&{FuxJRh>IJx4nIiw-Als>yrj8?^Yb!h3tB@{%3g(SSKHV{&iHQLj+)M8 zkd8Y;ie=GgE6mAfZ*v{;+xhQh)Cah0v2Yj!ZHeb=u>kbyNq{Lkh{bVHWXZ(J7CwGh zicMi?5#h~Rvt%;4vg8qYPC7cpQx1s`|6+7%U8((tM5~-^Z9a#*Dr+;c^`z#>WEe_3 zR8Qh#rYHMlWI<2bX6SLh2K2xffp40dh2NS~h?7-%x8}x9%c9?Ze-F;L`~!>eZ}RUw z{P4g2MO5#<%TXqRoLv3uN-uvpKKS)ue(#sVP@TUR&7wbEU87FtR8G|H73M#FbB4js zj`cnCJ2}>&P6s$NbLcE^Hxgh7l4q@BPmAdU!WiLfVp_DOWh(lpyM5wMlkqup%9|s@ zeS1>LK*gqPD9dz602K^gYGAj|uG^49zLRIo;ny_S(P)_t3_&dl^gdK8I7>mL=EIA@ zA}R>vxeG6j_U*(v%7eueK$ank$H26xVc;aB5?k+MTbGx)KG+23k_{jLbIlGY>e-Lz$a@T!f~Px z1b%9q$Ux}TZjr{sz%vp9qt+n?ny6^-D%%g;68%g{(&fluzq@RQ84i$1&Ly5V6T+)I zI+qyLZ5L}q?=oWnz?_g^HsL~6MvF21EVUYr9r$dmFz}*ePs{Zh=yk0#RZ~L3ori^& zi2BSf)CT7wohYP|vZdbK@208(6sS#~ol33W<@M5QP9JiR3RQY0%r>EWH4@rsAYt8i zmjepWeO14XJ2keJVQ`zM(1UUt!o-U(Y@97pP zkq6m?@v}aWesbgwU+(VsgJzl5#(eYv7-sbREWivlmo)m!Y87GedDKiMcit9}Hht>M#lh2tR z;<1{TXsDqRY!~j-LYd*vt0o#FLb=zCdq^AS=%*b}&4@z?4T|XLQ!TafBC@e5PMCf4 z%f;>TfBh_?GxtRj3^EK6r=~FWhDca&$wYSziRx*moZRIwuw#%6%AC52;zeDhEE!%A zEjey?5Xl;gSJ{%knt!}Jm%}|iU6tuFyvv{kE9={C&G@bfXvf zr0fCVz?YiFd?Zs8yu3adm!D;B;r!XcD{yqWmJ_E%LJ){MH7T^$jtTB^fMPl+G73tM z;T4e-xNW3}_4;^6)7gD-E8FG2lLg7eY;YR~^vfs_LO|$s!vPPO`Xq-rFRoYA7Z9<+ zof#I}b`nPSi(CnRKHZuhcH=<2$hmfD&7~Y95z49By3=tY=*~@?P?gctozapgk%sZ^ z?{$iAI%C@QY!1SNZ01D3;_>&jWEzjRnWSK&+Y@iog@X`H z=fi%OZ9*HGa>0X#XD@EQ%2M3T6HX3XYv=q&*y##E`ZtuLh0x` zkN(e`QxMN+j!EiK`rj~@6@6c>l85tnxgiyoKheO=u`4QgQWj_XbXqc6d=E1 zX7OmWSjSmOe8N!B?xMYH`SuF2tR-YCRTKzR_NueYs9TNO<}Mrsfp7@OL(WXjvA7MK3-e;Od-_0fr0lM7{O?w{RRatt7x3z;`Y?&ZonIH zNabFN14iAb5sxJ!A*d+=HFRa0Y%f|?S^CwV4hc}RLkl2nP@t6+6uX(vyfTz=mi8Yc zqx@wdp#@eHaK}*SFAhgX92;c+#Dy$)6cDaFA26o1uE1Xngy2!5kF{g~aZMcS)wV9g zuw$rt5UL&qLCY8n&nXUg&T!Zdjud*J&LjYJIOt53GW)%zy#|Qk}pa`*d z8w!peed-+(Gb!_=P-r#=P@qR%L8MujN-$yyod8fU0wv^Z(b)?*s=jclsik*>0lYM+ zP+1t-y}g1cEoo*1@pX*o>W4rGv`q=YtL#^@Zs{?%TXbx|;MFLQ7iKfcNd%0J$%R9K zV@5J49$U*r8e}DdQlKFh(Vc)4nqY(_8eJI9zTV0n%!^-saUj5;)F7Z0*;bUUwRy2s z68){DK~WnudoPE;%efuP`9FJ!^tW%`p$V>X2G}JxcWNdbG^x#&X1X~bK9f#%`DvZO zanU>ot+dai7dsc60^_Ss zF@QBI@$-!nz|oIU0%uSE@WjuDyWJbq%Nrhy)Wq#%!9x-xa}ytPI{RnYkgTdK+;{9G zAl2+vaB~4|ZMxW8Wdvc_T10zm^l1q_70mIgOjlfXiXSQ)qP||NS6>#h7a}w=_ecgc=dnYFkA?Uvo{jCCSN<1UDDmtR=+)A3$O{0+%X384~uQQ#R5S z6TZaJBTYUi8fdej(f9CZ*fSchzDs^=iOs^ld@W}cJVDS=LxY!Q&4R2r^@)a>QAexp zR39)b^$9a91lNI)7Vat-PGv+l4~Rv@E+b~^=ny8M2zm_1o9eqR=FP&$)^k)Ieb z@av>Grnot5dh~fC9>YA5Ns2<3;V4}lo#dUKOzukwzT=?Kqk&FW%aR!0nm7?b&kTtm zA?RLhP3WBx3B_bj{iA7eE*zM;#YKQ!UJX&caz@;e#UJgpKbo-Ng|3{|NkIEALeU$a1b&L6zYfz0P zBv*2}5dP0kXR^K1Bb=FWxtd96(<$0HdvsRD8uVk^u?CQ97U^@>%A^W_l3B|`O^u;B zN6t>*iDJ@q80dC^`thOeU!xk~gc3y}Ft6z|0{0&l!A3TlMXk#%N)OK(mA6?TU@)9uH`t>($m@Pl$HjzhvoIqAr+}c>$-|ynTKGY> z4}2xLkxz?n-b{GEfqJ2;Qwe?)_;1tzXhsbMfdV3b+(AcnT`2U`iN**N2O&?Cxq&hF z+*o3RG&@(Zm|%c_7&(|nufPcFH-JD5h6WgS03+>UqlyC3ue2b_PE3w%cV1%!T9ZPJ zU^eJFwK9k5MAfF#^8!Gc3PIOQgM?Bp2Zhs|sx1M9g0ijuuy=S|ER5#+TE=q_wMs(b zvPY7EP>>+mf^sP=D-^_^@+t%UsZ|EvV^D<5dK-$&3J&P@dDNs7&7nlAlokrg;EF^M z!RO}CPp@V=bO&+vq;{zAKxS&_?RRdA`^MA$a~_$F&Am!>K-1qVt1r#q_( z!b(Qqa}za1P?~6n*->0&N^m_empy(fH%!M@4GR3&pa9YaMae zNN>`+*1Qa$4Ok2jn@R_oY>bFuc!r`7z%z*mvg{alR;t->M>Pbma`$O89i75(UZP2%GGz)-9YpIz@|z>6V8W_7nkyq9Qb8V+e%bo6`2j3E;OK zwLMtjUPifCqRXR{&EZvwI%3*Ugl5hsL_lDiNg;b~=)-49mI@y+_>Wj9ViKLBPMJ)G z)Zs@p8>vO^qMr`$`I;&>(-OooT5#MpULJN8RM6KXCiaF731mE0D=3dlnG|e@dBT#!1%#doSd8&SY_m&o@gs_v9k1ImCBr7|_Eu3;?J$ zS~|o@d^()is3D4FJ^SR@mc)n^*`!0u;n$gk_p%+5&zbR?H5!f-!Gr_m8VxEmDho(Z z2aPca*`Kydx$oM=zz6M68-}(yV!T^?{3s`pzW=ni^}Y;xT;ssFxasbLFSXLt-h&nD z_-|{%_l^$_6(+j4ZWa>Xb57lustC1*U~1Ff*KHkScI6vBnS45@CGg8+O>n6|grY-7 z2t1!ms9b9H48ZO{h=Zcu&;db+vzmxN;I`?^IL(wPK_SU*&C`nuVMLu4fMKjTK~xlq zARD+-bAlsB+1Qa2WrDt>9D1?e+6dUfqbA5Yh#?*t&svThOpwtef6|Bvsy1$gtDe>#)U?mYf#(%*>g zQUv#B&aCZ^&a9m-7r!l7%Xd2eZN8NAhH3t*!1DCvQ}lfZEaik7`M)W$EbI7iryW>A z)tmT3d74$^B5GBEezgkJvFL&vFC5wNaw)LHcYbw+e8-1=LE{4igBDN(H$j%GXb`56 zWp_zDeIyzL#=glH@KyKFk}0u)F4@>0F*Du^Ezul<0l%+82)4W}2b+B7kRT?8)CTHB zXE4i3TMSH!slpTr?Lf;azFU2tJ&%Wzbv9HbOllFroB@59+9coyN(rFaF$tT%DE9|& zu{PhHy`^FEbmap;OJGb5wXN>>V`&~aJ6mf=jK2{jq|4E;8VuEoFb`J3Mh9By@8n}h z_-VLJ(tGhJc1?6ANd)Q04u!&j+X;@TPPA;4u)LT(-kbeJ7AiQU+1VYy0TN6Gx^94= zza}a4HNXX&+H6yZ$07e}HGF>jV#31ex(PhHRm28{1n4QUi)M1@tMuEd$l_3Pn z;S?nHFvSKZ_CUjO3-zCHvxoS;f^&eRz+ z?x|brB(7tyKu1S}B&2R}fzJQO+`Bc$aV*`T@BS4N3PBv|h0MWIArcA~}3iRl1x+2#_s!fo`TDH0PWVW>}tszZ&-rM@P`w!c6^O zXAeE>bSfSd8=$*BP`du?okmWY-PTXPTHQ&{-`iq7t=|Y9bxGNuL#6EVLN#kH0CJ-O zucpdT-XD&V#4vDsN$Ph3qe?Djd%u!Bj?cfzl;V}|4+rH&RVj3bqqa2$IZjbDCSy>l z5?#L90U&iuNGny;4DeivRl41uw1X^7m|QNWM93D$VWmL^f|{gm8r5kB@pz~PB9lQO znG8gj^`e$A$bCkFPOOgDf-{MKZB)aF8TJ zOy^@-ytalTi-ln@RV?n6<;KB7R=w3tdiGg1{+9g^-z?@=e((gvuN4zjF-}ZO@SI|z zEWYQw;)FghaKhc+CE*V}VQ5FS4_;;ac(z8W`+R+O{oCToADXaHGbbi^4hprIY#w9I zqwG7+K9p!>AKD*K4sNsygmj8t$`L~`&)o)wGSx{5&35BSk9ImtKk`p1< ztL5$NodilN-J7NwOM3e31Dat-vG&;&nsTgd$C427nkc}OEYa1S$>=YRt?gT~Fo;_k zOOiMEp`_S9%vWh>iB@tRbnnrjU--@r?na8ma#>6TZ#;}8xkhJ~e4S(+a|qdV^#--^ z`zzyR1_OASU_c>>!j%1$gDmWoYm<1ii4LgIz??;N`-b~d74a?SDt^9M&OdW{Ua=h@ zPn|TCL@U?HR`8sgf(RW1nx1qXANjFGoeE1n&H*uVE7e;W;JOcwSHZbs_w#xvwYQL#w0Uqt>H&Zqh=WTASlX0^zltZB)DA{9R z%Ei;Wuk&KlGc>WGfG-*fsFk4*rd;5m8U=>LsuOa5m;T``S0&No)G1epu0f+F1Uxh2 z)qMH&?i$uC8UErBkNS9WX#Sok&O$emj#< zgD{;LpEN?y7OIph%)`P%H6gV6_W{E8*;lie+94BS2bgYC9iCw#&e`31QD$49^Qck8 zT@lebwch~X>Nfz=E>_XYE~48!DJJ)$_=yeO`B;Gsav(e6$$EmKU;9j1s0h(l2xl3p zj=Sq*jH=Zh@IJMXA^dJDM1Kg0T9HMP2Zp?!%k)H_e&Zn{gu?*UGnEV@iFl|sip&Q^ zV{$)4Z*+M(2)fe^@N*{&A6DG=;On=K<+4o&0?J=U4_oz0Wf1i0o*>Y&S{Zkn2f-Xr z4;2O!#$j;wS;B?c8`(4U){mdy`I-l$gyz~?@qo=mZ>d+4At-xlNv1ymfSk_13X__g4TPm-pPKFjjOJKuiClx__Y#%S6K%*1xdz}{ZUeQq^>)dV=7Qh zQnUoF8aw;vt;FZQ%>T_PkX1_e&kv`l;E>WiMtqtwx@||z4p3S{9uC1G6x-Vm(Ay>) zP4>l_FJDSt4qi2F-A7kVAe={`Fz&N?8KAg47tLJfE-^rHIN%2f2Nw4}aeMCw$cRH)YN0qFOinn2a22I%hH7$9 z&Xw%9aV5Jl!8nGLU#@GL#0QKY5ec+GElA355JFR#rctQsw8u))*sv19ZPCS)z%e2% zFjpj8s1}!6M~;NNEk{fI&VG&-G@Pb;f_2qc;HUQ*Og((4o9!4D z%7ww$JQk)R6)O(9W6EeKXB`OF7C)-fAN-_ZhXjC3v?zy_@lcHf?OHp2h)I(bQ7YA@ zvH&LPD;i1%fI*lu7$lbhfjgb}9k-eR3&xFJ%&%`2K5PY8W*jJ0QcqkeAGYf6(FzUi zH`!Sa6M`cGpr7yjJF!jxMFAR@Pzb|_C{a@+=(W#pR~KJOz4RMc1HxJ`L)e0rul+_= z31vo;4`V9PI}md_E_6{KE3!a*_x1UK75e+V%SD!b4@is{5bT<68?XIY6M=L9Xi(?A^z307ZITBi;H2|Qn` zLuP|oNRthl)S*o2P=w}UtKl_j8~C)@M%>X4x0(v^ZZ0RpyvD4HVq_W5kFp|YL>mi5 z;4jb!(k-*1t&(BPEJJ`_J3?@%$hN6+d~iV~eqiE&Dzy#6LC`b|e1C_SGIe&VVRWg` zhG1KVDNE19U!Gsw$#Lrr0@!lxDTqdM5ZOV1%M?$t;$XpuMX>xfQiR|7k)l!?h~uB1 z+UX8mFXlfMesOl&4oU>d@j@hVf9F`C=*!5ijxSbs7$lTk*d!v7y>kTFDzSX=_MiCs zkXY{TgZiD!5>41%cm~gGatPj7<8wg8;>M96dQh^?jEW=c%2X@Z=SJ_g@QiH9S}-gz zFip&{bU7aroyQg};0;6O;LILBO=j=h!1%&5)V+p*0UrG$JFTZjOXaL9H#oL+r|(e^ z0|x-=p96*W^Q&*4XXmmjS=q_sj|HLysDng>Q}`Ub0zj)hBm+ERp;t{0Ii?4tBu*R! zI0}d8fzZvU@%`%ZT14^n#o2o9MX?h#IvB!O2sETdL3sdG#4mSn4BdQ+?{fx`;8KGC z<|)e#10o=)LV<>JjNows3z?N!CK4VAz-YGO)p09A(5Y!cMPqJTN0bP4COM-zwgsmq z5o#B}m+#DYkXeca?xbX*8!e#u92Tz@-({}RtE<%yKfQ+iYNG*Dm}&%VsY#*LN1=B# zjq+>-*WnOJk$kEGkwx;Sm}H|!P5aQk&~UaOU5DWS;A%>Qc}8vHP?NZaair?Sbb_SG zTQBH6e9*vg_I|PSe1LV=FwjUeatd@ng=;z}#W&DllU*G3&Y%sH$YW{B3EWP` z(d~{y37v7A%H9>%vixu^3*L*pJwa!~gCMBuAVYSFB-KzRdyaC5gz#=3N%oIl1yl}& z=j-|P?{a$DLZ+!Mp_-5!3ouY{0|J+|eM}rKQ|+7#M6aisxMurkYh{>MK@K!Ksh77G zvgl4`>n>&Iqv8}b$A`8|NY6_dS6d`o4VpY(U0n+2&Tr?pzJ}7 zYMbKRa!wrXw8LDK+$Kcm(J*Ih;mUsqoI#-d2Iqkp*C9IK!f}UiVODfDno%jR~@<49KXW@ z1qg!`DhM<~KZRPYR7mo z5A2DUj~;Fy#~wP->a52?O%0C_^5Txcfe#uSy7M~up~y_eBMp;8w@!*>INGumX9uDT z3I*_T`mNiOoqv_3^d1CfGq^PAb|CDB)fO(s13!{f$fYL4ngS36Gnbm%mJiccI~zGE zh@rqv>o1LmXrW5YIPd&wvH135_Kz6xqR3_=xHLFW(02crGZB2t(9nuQCII9vX;3ld z?ABTU{bC%UcY0P_sD~mok+5Y3cQ_K%69aXweL6u=Ee%-^sy`O&?PwgmQCED<0ns*& zgD~=(=@5b&+s$z?mwi|~2>3xw2M5AdfsK*?bWmbUR*16aT)pPtgf=O}!M1$^vRC;h zQOm2V`PxVQjtKM>Rg@cufQB9;!j2fEgkM#XkWPql!(^yT1eC_9notI~ zZ|7gIN!VZ6clj-ox*9p&RQFhULrjn zH)nXn*q|3qb9IhE05l8&%GZ5*O1I7ZLq7;yX9}gmk_wbg5#Uu0rT?{9UrUg%_!>4n zMjdLufWNjwM=+q)MEq5QCYz--*)f!&=(aRaZ5XH!yFqX}#nNG@J+pzKWX1x!jGW3L zo9obl?nD8XMiVCrA&dq}H5S<}^lCB`WhWt16KW%0mwjYJ0=E+q!BL_%$_hN>oWz-g zO1Cp?Y$~3zM5EnB=D-MDWVlmUTlKMNqyA8MalkBW%j#q6d7?G=pL9#?JIH)!f!nf@r`*6CR;69Zje7z(bB6FBa>M zt@AyY>DKO(u&DbCSl~`g4$x9nW5@hEo&2Oxgl+w@eK;9HsZ5Ii!>{unUuEvzhvm)X zyo{>xV2uM_Wa=4=#Bl9XN2SX(3#j_I$p+mL1%j zSfCwiUJ#Qe5V%tVfxoJK=+#spR7G~#ud6E%1R}HH{Pu3XzLlAr#Skn?J6ijG?;L(= zR&n3j=Yba@pti8JID}XNg=xlBPu4#~8rZRY@x?KqjR6W_rVuc|^J5J7lTCr(W@R8# zv~v=7%nHKzGwvt`yvi(~5y-r~+oIp4t&R9;MxacwETgH$5Y9d?FXo?CvnSHE)1D)9 z_Tpz$AtyR?|0&Vgdi4d|kTn8IZ$vy$?*!@`QB2wc;A(B1iRQ(59wz|7rpMqruJ>n{ zc58!tcv4%V7&t%_p-sWUr9_9dHp@eylK{JL{*WH2m*}{N7M5-RD9{q= zB?}<#k*pLXnFjP=0Lj}b!CWg2k4;c*{3R-utKx5D8 zFqGZn`zXG$KwZ=j%kHJ&KO=!WXBHXzLQ%Y?EJ^Rr`jhN5vXB83-x=z6A4Y`|LvX2{ zDf9!PXf+n9M!;f6gDCp}+0#x&phC_ZMqG4+BGUqebF_FVC)>yr+6zBP2#l^h6h3Y! zgqh`dd9BVYEMs|E@pMA;W|nWyhY-TD>aCHqw`+7>t!8o**427>`Kjm|MmwlN0d32T|0Yrl$jH0#4 zpauabsB+WEkZM?^cB+~gic%|G*I1k>m67084qu)xt}(WH{l!lN;gZPOY2c@ZLrCrL z3pJB!DQxdJEtG&XJSj(UFs1=Qp3F8ypkwky7*~ z-v4_3q(~~pR&)c!=I9AmJsS_G)8;F2V72$mZ>s&&WzyWEPy0YN-X@V5tS`ZuEyGq_`20o3;D+5DEy=fWgfJs=|FigNJLDy zal2y~8IGWpFJ%LsYgw2hdzD@L&HFkFln70;SDY$Pvm(pHj(U06o0A5rI+b9Lo@yoT zBpgE5Fv@QZ6IWRwsFm;UIBNuVWPP9IL$%ZBs{_7VW1uz1`*y^jx6>{YfaL z%a`XEs!fQkBkETRi8yf>`Np&Y<;&r;-v}CaY7kT&??H!@MJoW=S@6XffB`7A3XsLD zA`T#Cb>Nk!!&*iVWh0ayz7NWE$l-aDN+%6lb;xBd=+)FnitVcqXv=TUf&Z?WvQp;a z*J=#Ba<#v^l)h;><7STkIv(J;S{Crv>@dr;DrYg(fXS=XjZAR)v_NmT#3|pt^(KhD zIz2-fhH(=vX-LB+TQ0d^Pl-_y&6pA#Qb!Gwd{W0neVMZ4;=!?Nm6OcOYK~ayAy8@_ z!ibfdWqyOdYtv||qpHk`r=X3fTf_p9N--?sHdAFUfj`mOdatk+=Twk4SGrDfxH|vb?Jv}zVIh57;EZJHVdsoYU{@=Mw6>|h7T6Hwz zl`8azwWSJw)tHVlJ3X#VJv?fs{h4BY*L4^II~1wux@1gK&Stx5ZLkU4YBZ=_P3MI> zMh_6d=s`Mfa$aNX=#8V0Y?&KDxAED;j4l=^v17)|I~nMIv;6w)^YYFIxz3BT_G&jy zxF%*hjum)cu`+3>ZQF~Ni}3gPrdGH=L<1@n_)J10j3S^K$_xPN(0H?2e-$+TD0XG> zyRPE-It>n4PuoH?%!f`mEKm}U#TY5lmP~3PxzCt^bC(o_UW5sR*+K#iXA9Za>_Udu z{`el2l(s!On3n>ZFw%%-Fblz@SqOnrJ#4K&~9*ObK?iou@{5!|A1JADIpWcq0x$ zF!3XcCvcT4SdfC0pRY{J<{9dj`o#-2;KuF zHoNGLx)PQQ#%oM5UdXoH^J3^3HK+-J-=>(W>^H=nF~h+99af{x8sr8mCO@tZn7 z<;m6j;!AKEs99|t2xd$qLf-(MuR+OC74GjK5(g)Kl!fdfC#b?sEK{(dlmN9%EF&1C z0+j-C;h+ff?(uxBmkPi=0?ZUAsRPZkw-};zp~%MZ(_t88 z$-F?>928Gwy7?E`BXuoZ7)~(#>U zVLYB(AS>{YgF<%vn!Q`B?bScYbAr6K%A4mh&zCLWm*2`xp!+eWuz0;P70kQi(+dnQ{TcqE`CDbA2f+PMm8sS$Nd(tx-l)8HWb<)ldV@U;IUe7i3W5O>ZLA zn_u4G(=OzPX{L~W%VCB?SdEz{er~Pd7H9Zn=#_!)r<%Fm~Oi2d?P|cA7P;ih3#{NnQmC!|f)3O*zlp=p{Ci=PUVoKuAMN`{(}&tsboD@cTs740 z&FFFGiu0Vj6l$~T*64V%sUU-iB!lcZ?uFVY_*g`xb8*_C_Qo*cz%T3KqB|7>qLsBV zECoixbfRq-xC#Oq#KjV}p6T`K`eMCY6iJ{HM>w^HIT5Qz_`$SMRAfKkza&N?)7f5fx9uhr zIL{e^D%`2bfxjv_lqTdxgk#fqTyLE;U*1Opw^JYy_DRDnuvWWyfP`eW~g}(p^vMMuzq*9T<9RndwUGK8t6w5$oua-BmUp`({ zN0p?2IuNw3VGuOJu>ln~tGF))Sp{`KlANOd2xgHVw2;Cf5$pmbRe``eoIqezi)^>N zT*%n0Ljue)wRCJCD9VPd;%n0PuNZ|xL7ZK5*bq!2V?g7`iDz2y6h~a}n1R5W+Z<~7 ztI;9M1;s$Tkaxmca215uW zHo>5POxDOD9c2uG&riuWw_MO5egCp2{rkm_YdPq_8w3hnbAfntQm!GvsRkL|eO_M8 zFBfv|)p9<2^su*@;a%JX>H!=>%6_&SXW;Y|BPD*Gk{?K;1~P|qyW7S6MBVk99i8mc z_H1fy4(#+GwoJ~whmUo0Tnp^LVkKue&cN_RCy|hvHnoT~PkMykramBMLfQa_kI`Ld zY`%ek`1K5&I#e5H*6OkJN9)+0DDZIKL%^9F%1a{b5IE@_nh*yXSaD(yg1Oi8JDG^K zn4NuIEQ;MdIBI0@;Ip;!4s(j|iQ0K95rCYsW3q`JJoagzTe%(Ln7v2V->PizQ?(-) zbXQFvr}*W)94Ytxf&jzxb?4r*rH93r7cBR<@xYWhxdqcvDx( zw=m|{t09{XsW95iu;|YyK{*{3IHvve#p&g0<*WjYSyKWZZwGn!a7_t(Nv%S!roxcb zgFzk|aYB$6#;93sx(*c90U(>DuVtRc2eAr2&V=V{2=Lc71OTUofbB)cu^R+xFuRN? zgg&8e^)=;-8$xZYeQ=xY+H0K=Li~xfNAo$~kZnbE(b+&Z>qTiD17zZ*XJ_^E*8RuNqc)_u?&BmM1VhfKO~E z7N~x6$pUwLs)Gd1^spXMbr945yhb{Y2N}z4*y%AoKK6%cJko%PSubCe9pv7_N4lky zlHm}yRS}EK0%fr3qwZ)kUgoSpPau0Umx;H|BkUvSs_g;4HG7Ctu@MH;f{52B zA@pjpPbl~HP-I)(VPUgj7bf)g+$9%sPY@;`zqpe{!7{YurGd+`4GXk#VoAoLA3;M1 zjTLM}?F!mW9{09Ac6e~4l|&u}R-hni7@)jk#jo=#=^d@6A$MjkIa+L$Yi1~5Ks(I7 z?aoUN`jC}Ip~(91Qyf$3LZK^I>VWM@W?l3 zt)$*447ig5$1pt~x0;MdDdxN7?QOZJ39VV1hvP)(B7_puIAl&R@SBXoUb!PRi8U>T zj=KP%Jrs(NPn=%;F8ctTUY0Za{2+=;CL9j^WCEW`&O5n5aG;+Oq@z4_QMEt zmlV$~WiaXb;!7DzqxMD!*1qaG-zu8SgwU(WuA?_cX#x(R|0UxPT*nhRkxJIVN{YGq z`mLO_)pn$4TXUpAB~ZHBI`BPZ#g1T<;|O%dY8|MPk0{E06*>K`BNf>`P$uU*bX#f} zbG6RxG>gUYg7sHk;Kv3<7G9kn)CVdiA;t}BJh-R48n0XP9c)z-w z{bLmd8d0rUFY%6?YWST4aHobrYYe}^B2U-La!7@*+a7^#u0<$vOoahwY%JN!wH*%} z0HO~u*J4&v4W>L1Y-KF>Y;kqFykN{$KVwoS5CBjlzkVM5qRr7d898=$z~eX$afHntLM@Q zWhvi5&5e0*X(^D!!kHa@o1=N84VSCM>F+Z@0eaa-{K zYI2c%K!KG{v7)NA6_Q`lLO!ZQ@zQ$B%W3JTaBZ6`G7*8~b>iCyh+;R?bMNqBF`mNP z1O%<%I1vHCZBy(xen531AR(eTzgXSg&VF57mXVzgY4Cj`3|^D?Qo;eCYB-eckg6R+ zyTkoM3bAp+c`x~%gQDL*8j%1vlMv~iFt%PqO5b90_QtXujDt`R+Ah>lDuSp7BmnwY(<(^^q zlKB^>o(_rqkP5c-0w|hO&&G6u!HqP+cXcTqrrhm^mRqp4983-?jrnhf}B8-TW6;zP=X z5dbzxL^bbKS_8+bgqMs}T>kG(+= z{Jbq#SZxY6Sql#{5}cm;mOVo*eyUvMiQf_nbx7e;=pmyWv}P*(B6~p1&X=+elrsk3 zjgC6jarJ)i4)7a)e$23Uc;dGTaFZ}pBZrtF2Ny;JheQ~8MoE-d2Rkx z;%jE=i6!`ykpw6j7Wiw@#~43>Ry8dEqh(c)< z5RxPVA_p4ErdYEVi^lQo4irZa2TGqG_@2oHE)|lIm~s;&y?%A#-A4qs6C&MQ0~M;` z>;I4q_I)J9q1oDZ5SM0B-sT6;-xx`#n`}pwgQ(Jr=BSb=XCU~BDvF2`bh2Qe9u9;5 zAud!#-d$v$}C*{C_J>9N!O9!wy`3fy3j@A^eP@a;qp$W@|d%tTK1S7kSv<2^uf|s{qw|{+VT988Y{G9O!SCyQ zCyaulWM&zFE0F@fRVYa3=}B-IzGPDH#aW_b9EaN}jtfeKTWS@&%I@mJT6X$)zmmG}M3GRl%BEkstS%nGdwV14dhd`4KV4nO=1;P* zxc6EBz-WPzHL%!bs7<*5z0~SWcHHGW5ejKjDpD4pi4M?8VO*FSkx79PIjPX2^|(cv z$H4{nXv`XiFxe6{9%ICg)=>%t6nx^szJt(r&e}J}UccR4%Fye_k6t7Ie9eZfL$>W=OfJiamQa!neDPKq5p$dj1B&G6phM6v<~JBe@q}=}y(0ulV4fp%58%KmgGkPn8kAMe={;fXd^r zR7J8MtjfM!V5Y42B=+rJ&%ZA4KR?MV9Pxp(H+P>t^>*8R`Yi0W`v5ge$;A$1DX?4P z0XNt(b)>$xOk+&ekY#p22Z4z;v$3#hCzK{wuQOy1(G z%;Xtec5%d67p5f{anV#p1mKKm0*YxkuxESIWTi1DQ7WTbqJi&i6ovjm6_nA*;y>oM zGl{5+JzJb_0Bi;cFE!r^(@@a-wJNG}Bt#0vWLZ2u^!osCTAq3bb(xthDd?`krZ{(v zm1npRU^u;6{4slW{Yi$I{rn_uQ zg({e6NRSNo9Y9h{Cfi3Zm<~vUlme}tfCxgt>Qg8J5bqYZi`H)B4hVeIG?SQ27&y`D z(|^iKI(@Wb5kxt7r0TG>)99|zL9v`fa8=4PS-IttOMp3Q6I)Hr!duiP0NoA%lzmTp z-NZaQ%)(&Z$ppO2p`cS(kz4I7t`mR^sP9I6Dhd%5&l}F>QON!Eec?PJW9o&JS-S zHz<|)EHYGc8`eBoA--Wc#O(wuVmacVoiVveZBX;tRjIoM8x@Vo0)2E`Opl_@(Jt}5 zjhRh<;d!u0?(fUFLR*YoQ;@!3ySeg=?p*Z85K zfWI0exC`F@p8b?Eez}vxK`spil9!AyIdFhvgB}7_RR)^AA5ty4JxDF>WRUMT%NU7W zx}AZsb7w5Hf>R|TN2!|d@+}ixcL-pzb>L-=5@`i~+|bL# z^^O(1RGkP&;Q3lCwf>-jV}-)gpIA&qbgU>xnl-nqdO=G8(54Srp%3M^2I+C)%e!(ywLPOA+p)SuEsDl7(N?94bn@?8Pc;q4u197D<| z1WxFX;_C}+a^>e+<9js_eG5SY)w-F(2?s>9kdfdvw{sjd;7fAq%XcUja3>{uK__s_ zVkul@!IT4+rRfUo8RL+75z{_-6>f z#{vR5N=T*=U;c)@t(`vLgS9;1*M@?OE|xopA(llIR-#zuSIb=2AH2x3@}8wa5Q2Nar}f$?p((4q3+}!6h(0lz3#b zxYROwYu`EkpmxE`CpiYcfEUFz1K4+Rctkxk9#uI8UtC*6Mq$_Zy4Z<=LpwTlKwHbI z5aMgtvnfN|HWwHg0=UY`U}K4&u8Qp#I;&Br5*k2+BetA0fHcMjtv>jzb(oy|?>#E0 zz|^*lM0ZrRA3ZSh%t*Xn$zESE%ixHhsRJcJJ3*W}g!mdj*No7R0YL1?h_pennuHrr zqnTw!WQU?Af!ir?2q{;W60h~#HR&S#w)DLr{#0_vY%&*IDk(z7<*;ayLhWQ~d_Ck3 ziiMiQ*8U>yl$%iqba=T~-hP(RMei{2t(pwPLIDV5z$ zK5T+x+iscRJ(YR%i4ovUlKU>&C_?> zSk9X3k7~I3KfC-iUsqd-|(X=4OZZdy3h3tuf2^`jd7D18gkMj9RK~%8eg0)P%C*d-Ey8-dt40DuOHv(tHtJtn z2EMy7jvMn<-54%Xq6}%XMf?=^GR};^LkysU#|C*5aTD8BW_e=w>#J zrsUA0z2mvNPa9DX5DcJJt`j6bs3wBjMvZ=mPm<&i|NTm~zd!#by@adpE{t)Vq;y%; z41leic!onMGFdFj4#Ix&J4Ag7gwJ)lo#w$tYy$=vT>26gCV>RCm5?Q>qX7%NxDII8 zcBF=0HL=KXn>r5#FuR0uZVala0X~X2LJZta#0VY7_=5U$-N*l~O%tE=Z%UwYGQ z(W+)CsI-a3Rs#tc4y|Jojxo&`#u$Q)@O93MBns6g64oZ6K@|yNl1)Sg5N1zgv$uFg zB&uw@z%L94RLp<~D-hbe(CWjwU7vtaBsYAjg((Kx+x454dxpAsrhHLGV@xqZClNtt zO&XH*d)K$K4{}s@L5qD9zlMXzsF?&fP>0$~G8|}+&g`j8W>U_eqV{Yb`**V+B9dt# zeInB3zS=JC@2dsw)G%c8Fe|7k z^4i6l#r(>jpM%fUis`5z5>XDUq~QMV99Fp)9acG?U#w+uiiAwFmuV4NHKuZYdIkh@ zzQHK9oo}G}&3uC=BpL;v5ot(8^#F}_gx_{-VT=SvC;nKilWYjORdJ@l-iaN+rBpb~ zL1vk2&_}2!prk3+@C&-}_mpeUKdA8^)B4T9+@Ml^Z!b5fgsK_>s&aUC#}NDQVG&|8 zWTz0Q=Nt?v%A$)O`hy4rAj`q~#ivh_1Aq9uxGSbo!hUO8AWDi%Si%a-YCX`DByvE^{4QJK%dafiKtLL$%C11JdM%bDQQehISpZjWT|4 zi~lXk99|O1!TTu#3q2$#V6%sW^Nlejqnyg-&z!?=>lmV+i#jl9*fH&x0!DeTPSy#& zBg;hFTO5Qystdvgp0)Mi$5jzHxVQnIOWJ|Urh&k8Y6DR>39Z2AchJhxKGZ1ix?mpt z4V>bZMfhQx3+Og}dnFsyT$a1Uw%sN{Q6xe(j+)j;WYtt!OpdJ6wkO(mOu3O8MYf6yA)Q24@aa`;Xnvp<5ZJBol zD8TBD@4$h4Pvuxfs7;D6+Kh0f5t0ks8N=aE772XL;n3E}{)S#u%xo3EtQNDER|{G6 z?jS%L)=a=JbGBK=)H#B!`qE#l?zkB0{YtVp8Mu)ozCYWC4F$KM0K+&2QRJtA!^8jYu-C5d8y4n)n)@b8!gYu=q*WLceK%xjgr<| zJ3GW>a#AR-!GB0g>;<^6*ct^5<><6n^y%Y#N00pGy$4!ryf9d1*ykN}NV#4JVW>@mXCKRQATJzz=*R?~Ncl%4A2 zOUrrn@i+|0FB+!lyEuR9)!oJ1hx?qTRGX>eN$4zWW08rW6`btDOV^cQ*h;A4%IgdP z>ftG|x|GAdup8xi?(=`-Pop4&&UAcWOG2;$ZqX8@wmlz0jz~Pz+i|D~)o^khBN^OI z9z0AkB>=PzG>n7d)#AH^GOw;yKl}{SeIRd0fXYcXaH%2P#LiNde6mdB3Th~$^@mKr zN*xkmS|RF~fCw^y+O*9=wkO33({;I^4velT;Y1<;fjc!NwDpIAw}IHyJGg&R^a^C- zpsi(}kx|oL5syabB&1Z57AUu)1x~w?6J*{mWQH&9B!808XwxEub!`J-syYedo>usgG9oyR*UQ@rnHeBM2}|iBDOTJ%p>R0hO>Of)Th*+ zBytVo#GwgLbl0Jyb)0R8(^!6*J^Os+PoMvDZ~!{W2)&xjh;jshwAy|IA*4F2HpKyj zaR5A*y{vzilQCp9{#=erDRuzFqxa`S#)0jpdTB<|p!Ig+(Mkn~Fkin>CuH>l6lA3i zj~KMjIKP#346?QCTE-NdR>B%bf%seIQBeZbCbZI9S3eA^b*nFXbjVb)i>Isar7++J zo9bWYeU{w)l|w1>u(y}2WEt7p>l_E;DP1tRDRMZn1Ll*^$X*9^NiyID(s}L{X@NV) z*6^_o8u&mntUg_%tMvKu>g#IlyM;KAQ|kjNX+#OR7U*wCz!6kKZ8~{iIuxP*JV({) zJO|8jh@sTL?F2>$qEJ+`4qW99GLELCu+sSoPSLEvz$XlhFxUp8tF443rdVi%J=sc1 zEHV@-7+ol`O8j&X5)2ecp}^Z56f*sM_F6W&`n>q=y{4xHt6z1>gbq_YWJmyZqlKyj z?0Yu@$&Ia&$+xsdVE2v>HCH!MI1@PtGnN=oGk^$CXaLbzo`G5zE818@wQykIvI0Y8 zU@|Srppt%wSSUdTS_J3eSysnHStA;gxjZI??oNVbQR4A60||G0SHo|p6%Mk0BI+|g5$w%7MbUt=t{@}NwK;gErGR_>z z2oQiQP%_(CIznW-ir zAqZHcKlY$8LGE^t$o7fWb3&y@ZNHgcUlid!3SK*K2Sk`7f;%-QSX@nq<^B#1yt~aa zjVFuLUV5+wV(S3~ao{iRzJ8OrriEHF^Q-0BNmYOXMXRBp`%_Y|?9=U|)N3^g$U}f4 zng?zhDdI+8XdV>^G@m0y+V$yOb*7GsXYk^r9k^8AH8QOZ7GPdnwGpLfJ*I=vor$gv zJ!|E|ecet^iNUNhp%HQ))TbHR=~|v=m*3~>%fgSNLZi$GgH3HC$-M(?$u<_7g1ce7 zIz%-}J7*{-M>>+Fc+@FCKLhOXC3oVXxAw?M;51**{KJSz_u zLJ?~^Xq6cT$^9K%NLtGmnLt>bVWj9xVL)LL3|$XG{mgdpWU;;$iD*qncJ34vGA!`> zB%37`c&NsLO-Fai=4>m)2q$t|D;Sv+L^*@OcPJ3JV?cz}u(+doiT51g*NW26s8IdISW$MVZ70nzAscZAFGK=9V$wRptVYiDaj;pRa;vYJY*h z8UP{j!<`xc{PjMAj!giR3Il$bDHQ_28c>=h3!Yuh-Yu^$zR1iSe9=S!->D&>KG?Wj zT(V=i-QPLPBuB-J3^To6UVWe6eUa1Up2*@fDqfXuzW%v9pK_nG*E;zoYTsV5gF8k5 zXoUq&nh$DN&D2nZKn){1hrI(!6U{~HqeBz#9q;=&eUG8(HXR|j2suQi*k>6pQb{QD zO?n0P0dROM<(q!u9(;m{A0rdv{V^@yG~@;b?N{yaNrM51Bp6U&qF}N04e#c6SF?B0$~UDsI46nrOW5F2?IDaLz>ydfc;2AMjzNRT zCK7|F86T7{jt}LWMpQu2(5w7lTE(H#fL1pLaTq#oUqbV3;_L0WjRXUl(Ps}b3LLh;PDG(@< zGY@R6`(ySGY^qwY!6DIRLl}`pL)A!VXC+(?D=|5y-b?#JDiYLRB0-3ANF7dj#&gLm zf@)?UP+|wfDRw=Yy^8w-bR-i$HnWHmvwh`iMytQV8Rtb#ItEO#i!eQSw6^iz;6%J7&G9!pc#v%37 zRsGl*2>imL3SMed5jv)Drv@SuLa&-YY%da7DEYc<8qsCow%NvhcO*I>!9J0T8m;BjYaoMLmK2V%Amh(dhan}Yik%k#KDd*5jfB9-ACqZ3&`8xL^b>LJA zzp7Kfpi+(#fY~^qDBL5C>5ix{8gOJtp`XHm(wkVvDVG@y6xmrQH#9x{bhnf<1HizP z?zE9`flQb+gbx@qaA`=8$21O&awog3-okWz!^QW*7=r?VI;My&^a`;0qy_CF`)?gt zdb+xvi(Hp`=HX+tV&nH_G!wC)_ibCrrdX!Tci4@@2sh%9o>M&V!GuQ`X~PejeTK5? zQx< z7czNhE_)Vi9$mr*j2FR^qp~#^$_A*#j_Ubmta%LG0oh7;<*nq$>|fHAESn^C)WXri zArTh0LC0z&GA&%zV+_eIUh=sj0B42ka)Vdy;}S>lS4Jv zi?2c#+|_^!0$n5F>>?-x?u;RUB5Wy_3R*Usk`#&oI7YT*G@@MMwlSj%2Z6~NAu;)4 z_UuXyD0fKUJ@-dK>yKh9JEq1HxptcN@GUK`(GMtsQf5*F6vSId;x~J_SX`A`{s8(K z3H&;zKmQG91lb{exKndNn|F64h6?VK|8+qCpw2S>`CUfluI3-75TTlFsyW_vgN~1P zI(_M5SlcG?;mDw)nEQIQyp=vLY^Xw#SA&jEpM5}`-6U6JWbKp_m^H^75AYjndibA| za^|)_(g2%gU>1=re$cL#%BB=wd9-*Iamh;b?ky7j*!hLZ}5TKHVkB6t6kb*m7f&kWZoCG*2xAKe&zmG~W z(Ut2FHJ}bq?41*aqwA-Pd`sE*`)eC(uMm zKX9p>2;n3isu9qX0-V6tcd(C?QpaHg(k3Gi*?`-|hS-CSTTK{bnC*5ZXYQ2!5U|5q zC!B2rCODnImrOQUT*~I3oo?cLu1_Wcf!hW|S@{tBaR*YAbA44929EVffksPM1c^XFni9dj^s~G5qD;2%%^C@fF$w>4l zv6WKphwK}rX|jQn8eh+G!pFwox=#iN0Vru!p#ljq>V9xCbb0nn7_1h`Q3Nznn1&pQu zsB@Y+-n#r)dW(&^AP|(Bh*(}zjj^3CWU;xV^yhcd{dq21dR(+~p7Up?0K?@xU~kHK zc3Id5k7t}b%#CrxfK*$<2-=`jC#X9*d8{o)YCg`~)a~tTQQa-_V%MiQ^+(m!y!`GM&Nf zxANkLLAOzcCME)fc(4DEv#CEhz4FuP2!aY0n%M9Ng0%?_XCe?lL1r+K5y9F!+}Gh% zLxP}^97>bn9a-7bI&j;RyN?7a=GbAU@4Q%W@ltohjTk5#jvfIH5U*y3f`EqH(N@Zk zzdzcRTz;MZ1zKc3MuOy+<`)@@o}DjcJle6M9pj>|Mv4&d{a^ zjTSlkV+xTCWG|guY^*?aJ7F$br0LN=tiH}-;nJ^zj?C&X-3T=47IhPP$qyA3d-c{ zuLlKRoRyMJSWN>>nJxKrB&i%L4^)l>{Lz{q{f zqyk^Hss5n{0KZWZ@G6twAEJq0W=}BVZ}t7=%hFi{p43}v1@K|Qe;`Xc!@ZxF$)AS$pY~d(^76){l;yHMCA;P;4$&M zfq@bmH?oDW6{knkDJrsvn&6TZ8e3=jl-o!$mN#0&E&I_dO_ymKlE0hFF1w$dRQeru zfMhZpT((1MJXJG;u^;6EVC@h`m0)Bn@HwXwP9tz9!4Q@upp_H`US&?m-guwnT)$gC z%@yAowT-Z-4^=e)Py}NG%SixsHI3flSH3TM&Hm8q)*~vKRGi9`6pw?~P%484lCUbH zLWmrIZ4CsT*Q7!@(Tp564at|AM5sLsL3+lb&4!p%plO4Kg({q?38z)$Q zg#s^1R#6Veqm61PvO*{*O(a5$jh4ToSGK&Zwc+s6M5ap><6NMdAhz^X0m79zg$i=g3D(!oFuRCEa5a}x}I zHXR6XHjBZ!s(y!liWRWn(9)T#+*#gSd67T^jv7YL2-I|p5W68U@LlbKtZLasHlPZL z?j+v;1g5~(lbtsbLSQH}3=Qb3ah2yXKTr;aIKQ3W;y3`=1b@}pC;j>PuV7>j_aC67 zDcs+70h1Fdpq8S266oI1{?U;Sb025fw(j&C(LA)XShQL8;VD$G%4i=jt6FgyTpCwl z&5ebKy)0-!xNNd?#0YMSVHH%|K)}cD*F}0%Ogpz#TSK($?6W(2>L}0>&DE4^Op6cwUPE!yB*QsdBbDGN(Zg!jkP`S`xH1#_rN z1LFK@vG^uS^uwuCfUBkj+SaTl-~faS2RvkE;!~K=f8G;d2{vo*3ETM`{*S&5VJa)_y0G z@%=OrJY^uD_>IJ`3~?O>xUAA14R&|3inJ%BMY*)EiSGO$%%Gx0v<4D%bC(Z``KKbT z@e`C$y;>wfhzGXRA^~P8CDfY`4)WR!zGh-~G_%Zwvl~UhHu9o>J{K{`ykIW;1Z$^@ zz9Oif@d6D|0};km+Rj5Qg8gQMq{)k&QlG6r=%2Y7jN|!Y_1kZ<&n$XdUBHjx*9w8Z znp6fhping%wBd)mb-Gga2A8(NAWR*?Z&V+6m3bgp@an4spP!{$tgnG8w2MoLdJvVYbt2XcejUb#={q9 zr%9|71Kdfy09&5L#RD9X0e@V~@BY|m5BP!+p~`pO&c9%Z{a-nva#60>IoKZ^QF*nx zk<+L?Ew1F`lvndFHfUBY+IbcBiFk}krYtMWUjqr6gDM=|NTCy+N_kf95z?ch+LADk z+5CfwY0jRG4*jZ~lk6|)#hi!Odt^tUnpNP*R-T0qs5bBoY2hNOZsre_H&?(_A=d00 z7VY2#wJl^PxnsxqQMof7%BJ07VK^l_TCG2BrwFp!FGeh~I=-HNT3$2;5&WVI`UUM1 zB2LY$0@xcC@Il51MF6@0zdKzU_qvOhlogpYDX35=P|gHJ2%W&!W+3@;E+@-;ot=KX zyu12;ei6%=`-Six5DJ3=g2pzogdGxqTCx$jOzDMOY6Qh*Z5xPsg!U4qR8I>g@f9Tp z3gv90t&}m21d2_ssb^7LJYV?UvCaL)VPwe}2nt$}0&TGiDavJBtT;HPU<{my_JP~U zV}!}qxTQGoD%;1)JDhVXQ?-hfzAm|NTtID%3!&c_FRi)27GtlJUQG=mvyL)&pzgFX z&B3{vOlBCuG_7nJD3Hszb>om#Amf zkNoCV_LXLlB{RrGqy%m!od|<$grGG9ou=oUtybk;87{RT2C5LKp0T2D9r&18hYBU& zP+%srqJ$!+SA;BwQV|N=?m&TCP3w3)zmo|ni&;D`-XVdSCC`dW8+8p)r85u*h4SH^ z%s6s1>uW0^E(%7U7J#L49QfnC zMU&j3SfC&q{YLDK$q=A4PAN|J&i3?(HWyywV}<}8uXFBxx`_`}2;h(RMu0F*Kp@CN zMgS~z2=D;M*~`0cGKtU6W5oAr8sIf%69Pgxrs6{SYGCE(>&xZ*dOmwMzvzYfKfig3 zHW)@!PVlW1>Tf%sf>PIEokjPwOf}M`N^KJDqX&N1^`b{`99 z*pBUhW)zF#Vyk@l$b=9-4x?n7;8lj?&SW4(avn@VmjkGZeq-mz@$;G* zVSP#)2PFikv7>ILEg=<$9PFzD8Kff0CT^QnY`rbsll2cdYlt%!%2f%D5^XC9qm2OB zD1l3}5|)^xEMrGH4D;1V2ZGwt zOFAFbN_;&TMR#oi@yH4xgNzH=9}&}fu5Uixt$m*W9!;^9T$)5UAf!*A40A1%{X4tT z4@SGv&pt0N=AUHuTp3g5(vzwe{p`ihfN%({A0y~Vq4l=C=-i3PBmt-;NnjYLla=si z69ps0AZ|He(hd=Q)ZIStvE>OGK!XT7OCvw|sF;+`9&#l_5nH2%<}TZsEe=XlMVhGm;0Lj!N6hbI`q2>h(thOQdJEoESes2Q8y-tnj4m<~5y!;^>O1z$5cy;vV zg1T#lK}3`hp~tk10*#p{sMSxlj-IpJo)2M$5=x|e;8ivbnSikVw3x|4_}`1^IiO9_ zjV2JGgAlK+>5zd?VoV027e4XDStZ*3G6W4Mf+-}fG6rwgx*NN!@V#0u<&+_37-&Gl z0pB!2c)QqUnv&80af3RKCJsZA)Uk^04)t4s(OxSaU+$1%mDwgtb3$*-LBN0g4e3U_|4+`Or}D~d4RH&lmu_`wkoWB^UE9jJ%_ajn^IW2ZI7{Q1z^>P zYPCh0wiz`_n`|(VKAZykR|L1pkHL_lec-m)M??ZuP*%`VyyLo%DWz93LF7lf!?eu~uOnr6 zhv#eIV98_FgF=Zn8|pJnH!?r?p6t%av8rq6pqup2VlpC6=j-oF+1O#V@?)Q9={mY| z%m|(w6|P~(%oyWV7%p0QqY~MR}5T*=eQt&E6AUi)xt|Oed4u|h%5K#WwJVNI= zxK@LZxu9?~-RKwyhXn}zF_ktEw(dBWSu$TF)~L6&btXXqXbSAO5MaQa8U|$m1ykWujL@C;*Qzw z$)lpXoVBL9(6+LBj8TK%t>maXeiCiMrp=vwUMz}k5I+L}A2xXKbBiW}GPrmFq%u}$ zGQ@xZ4bzMy>-uL)45(n3A!NLUkf36tWpfrA~BN^c#B7{jP{`iH3}x2#_;SP%5>QT|?}n6F>ZJM~ILlRg7(HQY$F{34s&?N_q}nIo({{$e6H=-LDUAL3W35aE|Z-{cSG4=Fzqnnu-h$yO1Zr2e1U;h zS+ldo4$aPiun|p4VEwhJY}J(x;FJ|R3P&0}(;f+$8{-JRxN9S5Y%`I54-u;6pm?(S z_z?kBxmBj`E^On_kEc+I$~tz%p%+$?Uk-@Wou;;t9mr1+2v)yk8Ulpu6nT2NS~)(@ zg(TM^^)fy>`m zni(N@sofF5m$s3>U+;qi^-sltui8Gz2ms*M7E<9VdkQ&8MYh|HBd&OSwDwyKuw^%a zrrE(9lC>)WDvIzrip9`*(eV>>7TywId!q;c=Ci0`d>zkeg>sBOeOknEG0oN%m*RzdwG*lCel{_T0f-7g2xDIVklB=1JQ(d;Il|M~ zz6D?!I3c=4L)U(b_C!+Hbdkvc-AQj>d}YR9s>4p{Mh7WQ3iiAC52@i4Yx!k4`NSBcOpF(U;@)R7lRQeq74A zVC%&nS4CG5mpYjBpnNqT!HePJH6B_u-~lK*Iv(**Ny!ag?*EiS-9V5C2W}@g!kj9w zR_!G)L}z24$&he~27X{@;I*|+55cZ87F?QyW;E1nCfiEcbEfqNxZ5MKA9|Bfq^5G5 z$yx2+N+myA9Gxht-CXgsM|3>Tr&>&Mal@=HkmjGTcV*1$_R2JJCa}I z%z$dngwKe9eTp}5*$n*gdP4)3l}O;D9myOc#Jwlk?S~A2m2V})w#Cg$PVaEEfRWX{ z!&$~wv{0fC@-{urIx4ybP$*4_Q4kkrMnd4W5hCt)lnKGBObFfn3ZZ)8HxLBWT1j#! zBkEMZqW!;fwBcxUwBet(a*X zX6MjevY5(O#(na$Bmg6q63b>LS9S04KAx%-0hk&vace+eJmx9bh&xC^v6W~JCG~1> zs!@;baTu8kD|Se+>@D|> zi>;2xYeOO)&Yd}d#&Jlzma&!Dvw67{9LIHPZG=V`B|^U`UOL8zU2UT{w9qBvY$)d@ z_D&w_P@NiwTMPbEf8GvS*dcLxxt7)QGubF!4t@0V^Zj5GAy8HT(qtedEAUW_2K7v{ zak<}-%Tc8$cFR|J1j*=&Gb{KC#Q}Gcw+x#<;g*I^0S0uWzrZvVKQfQH)_g##8vNn*fXBLuGh*F+k+{w5CsAV1%pRAomABRl} zF@nR!5TdXPim&!rN)bI4soPnxK!uZSgz?vG8^M8cKDP8JUwkR`(s@uv3cRK*5V%td z1l#T&0+Ah~#S*Ol)(X)D0p)Up5RY*&`+5HRT=uo`Cs3gUYmF#DC>MDmdab(DPtl=% zI~NW5uG1i@u=e!BD-bdFq(1-|Q&_ugPda&ZP~#|M6| zMmHOs40kf!Ri`)^oz*4+XdXvKnNWH8L*`)L-pUeeCr>r*J<*6LcC8N^O!3 z3%S1o7xwLlFSSUG-(Zn+03HdOz^_ceb19Gu`96)2NT6&EiKnXznQr{M4L=!2oG>{Q z@NpwZ=nup@YZUNPWrX^z$%N>3gQ0w0qEkPS!a)8d9Kz%fK&0^tts`gqvQvdjuY4sZ zqhEgtI~;QgR80z0+TiG~y#?>gd9xMo)jeADeYjph0-afr1zqvL?F2^{R>`yoaJ&Q% zx=6u~gKmZ*MELlCF#})tvof7t977e(NVH?xgRt0=+R(C%53-5p?7f^AANDDYv!B6gbNR#Pa>zujHVeqDV0=omp?Rpa0k zBAA7v1i-4yf&w=khZL%++sIuZXjre8NmvyI7t+Sw@@yJGA7tv-?B(kEqS%(sZ&H9? zrpN-9+RYaH2Fg{_0YAN0meaEgzBsd?tq$>!;S2$!!4=7~(GEcUR$XEvcwM)elJ|COEOi!des zc(jxJ>Ed?1THemyEfEi}iL0FA>9Y@Dk;^Gybe)_6n6&%K@guVXw3NxgVU8FMY0Jt2 za~DI$<}iuEy2{zsZt6j~1^7u;1s;CnW#N(aq?#F1b zq}bU}|9G^kd>_B6<$#Q3+;k5R4rR-GdAn_Q#;GA{4|Ky>6Z&01{oDe8JA?T7W+|g9 zAA|&RuM2*14jrU+2cmW+452d&xHZtw=7qCF^0q^6(cQs?Z+1|WbG4kWjJ?DJfB{s{ z25$59YQFsXaZ!w+b-KeGQ}%8}z?u|tmM{?e;$t11JfpK!IhjZ1DLx18Hts^@O zSrupkktaikDY;(5aYEoYp_!340~!EdC!S(Fo59Rd_> zv)B*a#Mvz9JhDjgG}YI0IhfG*%Q=C>=Tc~aOB=s0hteDbxHJf~;xG#`db(L8;Zq3I zUk+B5*)WoF$Jr++oyr3W=j`IcN^*;{`PXkB#cMcU0o-fDASjB&RvM|YfTH7<)5*X_ zRuuRGii2JaacP?ffd}}F)}L3I2TUtot%}V;@W^Q08D9QxnbLAI6lhEoSm4Pl*uCyu zDF}5x`-~kz-bfc*u}p1a^+gBDoXS#|YOqAsx%|H10)#Uekhxm+79X6wdWPD%^ad)D z(wlN75K7n1ZeS`<8?}RFvG>5J)uGKa=;0FeTI%ojlvOG4S+m}R5 zuRUir9g3?&uwDQ0J~GMykqD(c06EQ7A9jI2;hZ*GmxmmS>4&yENew<=NQ5XJwX6Mf z27+PtWO>Tn0h|RyG^vK_t_H)3PRaujL^?ZbUS*Mft=p5xrWD_b9T|y!9kP%g>ZL*$ z>!84;aYDT`+Hl8^B{j8ig6|tVgX{TdyQ+9RD2m_oRoMOh$yyembmmc`N*1#4QXvTG zKHhF@KvN`@luH{{=GA0H6E&tXhGbc9=Yw6yBX8}OfZRX=sN4pY$l)}iS6v&>VMtI{_dMO;dCLMiUFi* z5VYz5K_%jzRzYRVR<#I+o0&WO=6P0#k0##^L4fMXd?{4EZU5~ zUv2gYuO>6%Uc21~e~Ff`5E54zfu~EEHT#;0{%_00l|MfiSdFrwT2?Rw@mmd<96b2& z0L7Gl&{^?c<=+SR;s5zhk}v(wrJO$d-)D05pI3VM)A7M?2lEFX zUS%jaQ_#!FwovwZ*&1$23{iEO@aRN$inMMoNMG`oVFD5>P7Hvjvx(EI#UHb0*PpJI zH~yR)KYJRLGi>mhgiYDy!&yJItI!}Ch1XPTIvByPiH@znVF*a8Br{GvLg|`u zgB%Ydo10e$gYuh(1go!Mq(dU)+YSj_8WNcge|EZ!k2pefQmZg64?k+6AWN1p5$#Rb zunD$Y(}B!rCReC#tqpiyg9cTJCLN-;Q2WU~piHpb5P|xQ{9t$Ihe(MG1`6n;lEbO8 z$l~8uOTQzkUw(w&nnXA>`XLl*W0YVN{O66E@pVnxNQhMm5y#HxDOzJCve-s?$IoRS zj2{a>#*W$;EpTZ-5EG?Ef1;)X0+;uSC3{+lf^0O6xGQRA1EnH+i)UAumomB|C@D}0 zMCc{QL$z(NteYIhCfgYHjR_|pYzIV`tD1q}RR)6Ff)gTOQh3rnh!mphsyj$EZTAPXEu~9cs`cDCncPZsh=`3ugJC3ue=URs0gy$~qXNERLA3WLt?JvnR`Sk>#Rc zYsU=$4V*af$B_bMG&pipkMDPS!8WSXzMm7u?tAU|;M*M-<=h`s!IGT*oG`+YB}#1T zdU?|t+{c5pPT{WxNC@WuLao+{0vLhPb?{IzYLA}W^T3`U)sW3*Xs;8=e_dRb%WnL# z0{pgi+@UWF=4&Y6d1FNmY84L(g-Uo9ikzXCQs@KOMpAg9{b5-sV8I)`cbox*A1M;S zdus@DD)Z@m|0sg-nhk`c8P{%N!$@2^%7${K32aa;gSa}&C}jd)Gs}pZ{G*`F0RR1p z$AY|@ugm;@A1^Z~sGS=FT;Mb)Swk(oV#U0AJXTx9 zR3@;&?vd&#NkUkwNDMj}EdsaAB7#A%>SRGMi1Yc^nM{kjkeQOs8k{k(-YN|EsR=`% zLE9Ma3qha(J~Dv@T?8sG@%vJC3H>Zl)R$#34-DlWC4!((P6pCI{;wP?_${ojcr-d# z@OpXmeSY^vme}Dyq;_WU`sc7_n_cjo%mQuD94_EoAM*|Huqj-iZ>Jd?xn3~x*ku-( z$erXF$ya2Axf(F`#r51h!8IPpY0-Q1GA9GB7i_`+| zH5fEjVm85|Ym#f14Rh{=O^>v0HjGF>neM8eXuL>Kz-`loxUK*tQylP+%Pr3JWZ2iT z0ByZo{N}|0ZEAJ_coRmr)Cj5{Pov6)Np_XCQ`2N$d3>VVP8l40VXHSRvrFMXArc&6 zUq^yKog^^XS?Eq>XLn!a9Iaw`rk|XJ7B@8Tb3-HU(F9(Zr2syIgH1?7*)eiBDs~9Y zV32z`o{V7Nb^;^JJV5EpMsStEcsIYhn!OVpC{Il4EG_^|4T><1f^QoVxHKeGCtxg! z$#huye3&m zIn)TGYB;oFpoXf)eW`)MPo>7;wzHHFU;dD#jf?e#&p-XdJ-QVQt~@nLC{SsG0+(hb zZhN~O1*1&Dp-Kt`O5&36q1R8v6Ob7x;>ianT~jYkF)e5IN)~w*M&kU4Q%(=WR^8|< zuZ9>X`Q#4d;fO``BQ&&$ZY0iBLUo#6Rkwrv2PUR?^MK^lYC%v`E&9o)qeK=9{BZ{$ z?lq{gZEt!yIw&scMl+6L5ONOGwpNIYfnH5kh&06@rk=qnq8r6+GmDUbqa4ZwUS-#z z=WO((0IV~FvqVTBi~JB};7mtC=XbA?{K{U^Sj#}&B2K#KmiU4Gy~ z8c^9JiaY)2DBqO1LR2EZxY`yKXp1~nr^PN7(meYwIql3(mEov{wn-WLqph?>n6;!H zAiuQ&)zv~dy|%tVc9Qmjf9J!US*kpXSvcUp7^0E^7Y z_p8fm*;DTIh4}Ge;yr#*TPOZ%ybQg^a1&;oc*q!`Ui%)Q-6K2CPPveST)i5g*h}Zb zsn3F=Nl0uP<8rTR8TgYUxV?RLCIY~;4Tv!Qjgr+e1x(a3TwG_0m1{9*kq#*6kMiwu z4w;)4p(c_aOr45#HW<*zT7_>pUg%P>yNhDEy*C)Ncby0Zw}sam7O1w7BHM>X{!Piw zeL;$adbnK0GW&5Gn*}4;>kc%orsoYUApDL^1yD4NkW%AshKCMiXCzzy8>e9+;Q_Y0ZUi#ytt1iPhJ;sBai~$uffnZ-rz{1P$++LMN6T`9)!>3SRy)8k4Tt`j$)}$ptT$#r&ki($!ycP zECluIi(TGB2sZVu;{#)8XcecLNUmWFb+PRWSGAOe947 zc}DSW=3U;i#cEHasDF5`~H#w2*%C8#yJHbX~=|}0uPxG07?s@ zajE&$NKP@z1sg^*-$-N`KaxwycMJs- z)lkp`Vne_*hC**4>-I@iphE^CBvr5G=W}=l?CBQrDxXp~N&ra%Bc7oR_SeoqZ31ZQ zXdP*FfpS7I!0Wc6#6cKo#E+U$Bai)fdnHr0F2fWh{G|4gghYF%0x7_p+B9g&_vtS0 z?F73W4BZ|Fl-32$I^g=@M|r+tJ9Ktb2t&`{Tg`;54hqK9(0T8;IM={!grPbyIEox& zS9w*Oy~paeMxJ|%-8XAQP)f8r zTj_8Rfmna@<7p81+F=0Bjyj-?#yEkm?t(*a)Oi9Yy36;d78wbh_$|keeusJwafMu` zutF~D?zpXDXr%#kjRl_^Ll5JHDw9NWd(RmU(PPD1W`XSuUtTSi*PaXbSd9SQVO)?- zmvY8dj$5nYlXE$8=u`sQ>*Z=DJ5p=9(uir#(FQK21%FdayUW51z@GX7vvvBQR`n~- zw;q;4T4$Ix;h;i$N5x@HV+%8mA09FuP`T=!9Zl!0DIUHx5D>*R{1MqF2I83%w~&L~ zxK|Js3fzvg^VtoDbK2AB-K8P`HjC+Gd4B2xQtw zs1=|Y@xEOYyO>nCp94A~+oc6sz-ZB*CKLplaenF{^_q>3n#DLMoMMHZQQR>LK`E<1 zUj<`wK-23cj!2Y?5zyQTiSF=!;Kb<~Ui|s;>g#IlrQ1jO4ha;%%p)vDKJSJ{F8y10?!9&c|yFTVTi7u2f#m%|{8AaQ8A z20^P1%V7DZgOCn;QHkiU!Ovo%4g)5cpa>~&7mD|DoDw7lfXu&oDE#0eUTeMt6*4{q z%fLf56cn|;BHN+*IE_e~+b9rTBZ69Xf%s)1r{f3`U&_G~7vDM@c-hSQoI>ETQV2&0 z*1=6A+!#lcew40gLTDEjW}tLU;~)^PWPH^NB^t6e4F^Mzhc*nE4!FOA&lrZ}*-=hI zBLdVNM~Bo`pmYG{W*2eod8Py4bk0-eM=kUi)bG+iL*z)6>x{6LLP=nW1YhTnC}S#AHz5)FhVXIa176{fIKBF9y^te7Wy97N zcb`5lyi7P+I52pr*0EL2qxHTA7^#vmrs9h;7;Uey?SwHL2y=dSefj&Ux2;thf@<`@ zWj2532*Tsh5rkb%7ld7_Cjo5#jp&&~#6rB_HoTae#)g+WKKZ(4pS)uhGjC{JGq_|mGLFL&# zr0h>ti@{Xu2u?g>TmX7WM{%jK!&Vsvn~WKTUQN!u%h&;hYWBxOE&zEmi~dO<0fy$0 za_!z2XA%JhBqTKvfS{U$3dLj?N+M(!_+sZgQv|Ng!&^j1v``LnpSa2v(L7#I9+{#G z_-+ltU&e(1LOYI(edj1m(_M@l#M-tAnn%+n+Bjq~C?b=|(6b40E0ZDG1a42wF3>&M zIAahhS`z}*F>wf!(@^D_5E%%)nhZo@yE#qyh>8PWmw{*#qPqkhzjg*;o9&151@X|R zJXU!{K40*^VWZn?nGgie@YB!Nm&^I}eD-dB(K`m?=QmGLhhZ4yF+4#E?YAC9!H=vF zoVRxCQ2Zf()*?eBwrP&w(Xn5BeZm-#uBuvt9(qafp;mm)pk7B&pura3v*w07>XY0O z9x>`2a*aVRyhqIq1klyOdwN#7DUCqeo_Zq&#ZIj*tmVi|=Hp&Dt(u=S!;CS0MgawP ztbDOpe{A&(9^$!CV(tfzg{B&_%^d^U^3=zD4P!xg6wvh*_lLgYx}1y!TFzmC;}1@+ zFU5L_lR-MGQ6PgxmC!4O*Vag|f4<+7W6t~ekhHxBF>m17ib$Ol0!oF zY?Y0zW*?R}m-AxV7C*_H08|leSeb)Q)L5WQ8&~FxVUc?NRIxKD_+n=v$;DMka9oH& zv2H=}uf_U$_K&Ltwj@A_QU?LPWcGo}ItdOFL)t*F=|~C_N-#TW<`@vA>(c5E@5n&# z6$(NY%AaC4X$FB;Ihd1JMh^Ltwbq~K>oAKBPu4ntznXD`zCp-9O^A$xn#p7rp1gq) zLWL4IY)^=8M=4(6Y~#uLhnU8%m}h@c>|X1lI%gVa_cjv1$dJJEsfS1|?-kkJ%d5VWkW8jT4+!Ay97o4Aww*~R zQ|K15v(xPSR`zZEc(?WoI#C6)4bnix5`Q&D1dVWvz@;%F1EE)wf#@BFN~6mf5gCEo zi4kGQ6}Q@1-|gLeeJdj>#lluJaIFyzhib-<1+&|AobT^)BE-dmu*s8*HT@qc=j{SR zq@o<03O+>FXP=iB^G_?;;_2#&LnGB3rLz}5gGxhiz5l>R*IRa+a|*kO0#Hj7(BKKh znzG1S`I2Mr<0Id9{0JY=M5pRJfArwc*#!^qRw|U8=Xith0EA7xku|plXT)IM@xV3t z5xH-|ngO>H5AovKOTdU!8#U0LYzf^drMl5awuQa423mb4C+V9l#EoOKEueVLQ9NJB zzV}aLIg%erBhm>PdESsgeN7WWSPjSw30xW>axl4TSlv5rPh+WpGf@<9+kl91H5tGV zijd^!!FIWly%394-hLY}Cy)4oK@s9=JXDi{zMg(!M^cnKV;U2t#STFpQ3W*%xI#yX zv@OTm`Im6!9?CvSi~X&pYoQI)Lh$8IpZTWzvy7#b5WoY?>KtdJ25u*6bbAQ{ATMR= zxy;eI@Lm%QT0?=Fwd1HZEmZqZY|7m9bgR4Ye|s3VUe^13v08tUi3p3e91`IoDW7f; zfGUCr0|=l|Z6cW!BUo%=MM|ZtlZ@|V&kXQ)v>4{9t8_x25-Wn4;1$iTQ%>Z2u?pvm z`N>M4QF1D{)VMPA7UCfT17$Sn%&t@^GTDjr!l{Tu**k;67!L8Aj4T%@k&_F~PB~kx z|6NYNMh$8M!Ecj+1Xh5tW31Q>h;08#3BG0fVWocZ8Bk!y>AU$KWv>kIG!UF+@NytS zISbuFIEHz?dp(Sc_Jaon5+?% zGa4S!W%WWlhXQJDC}?%*ZLH?FR%r4Jkxg3lu-p-!fr0iS%a^=XC0C~yz=D%jg>HS^ zQWS_s4sKp9zbt3}noGCg_k~QGa^g)y6GZZJLkMp&fI_H;rmW4BvO-!o@s6x;w%Fdf zrK{d(29HR3P!U0q5+dChRCvg#mQF}N6ZtMSqVw`i463ffpjxPI4F}6hI7TAhP5kI~ zD$|-=8jzOZ)xIpOMMs0QL%ypYmy4OKBKo6TWan3wIXFr@=<-!U~KyNWf@;dpI!Ceqfhap`zMG z6dGG&f%_;HArEl0z(v`aq(jLAaKC-?fInceUAL4-WK2f65?J){odr#4F5=`v0SzE4 zF&r2;+?o`z+|e0@o4rKrZQzZ)S}0%>Xc25gmaa_e|p0Jszh910)gXtHLO zoi2oP_qaC{mhrK~ao}Z~Lde`~1gC`?#Z{^gI8-v=E~*cqzXcc7T1n>8ZQ%TLG6+xh zS;wZE1L8$o^OGeswi&`j+ge{n{W-e-D@(~AMoY>2tr&_g*2y$*syPgs8~N(TM_`ea zKq4mu)^is`sVrE6Fibj^kWwynrymx3h&~{vXa~BciCubnG$&(37}=pN(L+&A=HyLH zHSjXIob0P0aA(SS$f9z7&HzE_bAyKYg?}ik#71gAa@By44!a0qY>N#InatEJF6nLy@P`ycn7{@t&PG4K%o{vj-VAq8lHnA zF%DtVfYia!u^*Z)1KfA_5pnnqK9m{;j+~=RM1Jq_=3bC(T-fPL!+5nEU(l!N1x2(*N% zk{_S z1P_PuDaV&;hb+$2oREe=A8~$q7&%lG_NpLA^M|D75zXf86?7{H^ zNo)K7T=_(aU@DGm^BD%H|;MRjOx=0rNI%0NKQ%hfPmw4+vKf0tW_YXT(Q{$S9v@Z;!-NyBWe78jqx z-l|)}nov%63~`X~rJ4CMtx(O1nAqo4P~-wLlUycO;i8q%qcc=+R+2}miiCp;IkpKJ zju#V};B6fyT&wQvLN!P5qN-hiBCx(Xw2_9vU<8@T#gLveqp)}&blFP* z0v}U>h&59-pJ9I>$FnrlF3nC~q@~Gxd8|_5)q*U5OOpbh+ZX~2sMu6bK9oYKB3Pg0bc7d0Otq552>UYWu)16^TTPGS-ur?$4|o9)>}v7o(NK zAJdu?sVtg?8sB1h?=%8^-B~d2$Mg3Ha%?avmru@tW|-;)7~7F1&eRgYVVq(ba|s`* zmGZeQ${N07$2R5<{Zuc`$dd9ISHyG1k(P3hLYokxI~ort?Gx)al$&(B%m5qY()ciWumc2vm008C0+9^7LoSj^wt;xn&n~$Eadnk z5$5|0eM{?dWqf?NI*>rCvqXe$Hh6|wBc$zcpd+NP#&iO!yn<<^m@%*_N9ekEA}%9H zZD2eR1oQlAx%~P)P3{T1tGIdzL8wXvVVqf8GQ}JK$B6$JpR5OG3KvQdG7!+bOlS8ZdqoSJ1$fOl*8@t3*@H$i7=$pGV2=iuk zo|eG%lUn@Bl0ZfCRc9r^NPu+>g>WGliCB&)=O`=YzSBW+7(zjiEzFEi;B*HHn5*a} z-p;W?R=_>~vbz0*SIYe{XmhO>1Vy_X0xR(B8VYGOpxBcYWl>c-od#(8P@ugTicnz2 zn>L}4-q?4`#m8bkBwb-`AdVFQiZ&}COyW!U6?cm@O66a0xgKspP7K8+hlIkc-y3TZz0toq;%hp#>YgoWdbx2ZhDfwwdB;4K6L zoXHq;OEv+6pJlAZ`%h`NnNQ1wCj@R+8wdWH6*R~X6RlKQPV3p-GzMCAr~zUs9HE-%sL_Oj4QZRn7&Xe8 z5iunif{R&5PL_ljCK5j!g@OD;fEI$bI01jMyk4(vXX!uxdyy7<*s9=;8KL6=aF1EZ zp3LZG#^ju{lK#Y_P*}weg%`9JQB5>dV(pt8(KgV5UX_AXQEAF_aY{#B;2` zp&E&KbQu-WriH~X?I{hp0HBs)sv#^fZj^XHWwUAoJi2!BcAY9GjBp$+I#YRZ69psG z2^<)>s8S4^zqewDVGN<7(SlZ1hj}5?#n0PPfyFj0-yl6o9_EAp_OYH1ZkNJxLQd{*n3`u5Zg3n)p#2OW<>;GVD|ppste*eDS{+hu}92 z2bzcB@bg{TyJs_dvRPezT$Ww-Z5Z%-iy_J$7nkDvmEA6qYxUOtc)k2KOIdE3iZFY< zOq~kp{j1eKUjO_We|H@&Xl6U*!kKn|3p`gHQ|fe37=+)TNJ%mHxiO3AZccPOt_*W< zwv}$-?_w65=9o)^zDml>Dd$_@vVQav7D_ersybT$jzWM#b&?t<00O@7bX(|7=ij}z zC9eE|H)%@4le@Hvervl8mt&xD3Xa1j4%P{&YP7SzRc4xF-LS`}EwG0Kh+ zz{LcDv-y8aA@$AY6*#I~@yma7zz7v};8cSld;l}{#G<>RdNhr^e~&=nh0 zL3{DMNL$&b38C1Vrn$77AIL!)smc=}oM;mS#FXHYjhHSS-F}8(EC+cgln?}Bo)hx! z7!Fd!Fge4Pq_^DNxnnA%MAai1l5xCPEusd*NX=@@i-ZhR>;smER=6JPew`*(Ch*0C z9C&3X8r+8R$%?BD7V9oL2}OJA3^XJhc*!wC;j2&Q#9^_1l4P7ff*AMh1gDh~hr1@9 zDbW13x}H5r-RiBA^;c*DrS645XalE@rQMVuc z=WxKM%<%>eCGkq8QO1~0HVMhA@7JUC^_Pp!{pxxg#Es9NL%lGz2CZqvdYu2S9|RoL zK!?R^`652nLn~8hVDQU>h2>EcU)RyO-GJNs~XclB>Sr_^BK3F^n`;ueY!n$>pgjP7bvrP>k_0f&gF zdJSLG>?{)_J;rxAWEUl{NbCz#khyyWf28~oRB)ZjR@^S7i^{9T$JIqE)WI+6R#L3AZD{a5rsA|DK>}wwP90yg8|bifwqBPb zmqAKJ!f6*?QjrMNR6M*!LO6it3q?o_X{i&8p+Y7|NGLS$=HTeDHy1a3lQ%Lb`sNb% zUdL+*9Jqxu7ulxs;>V@0Bl=j+VZlSu@sJE`!$MG*DjUvzD4e{@?zN*3=HVR$0&>H` zJl5CWF1~yy2B-q3+Awevr9;1JgrBMhG3gvJjIk&$j7!Axn@hs9ts)LN+H@=-DbmBjU9+TdUysa-JPNZ;;hDXYpP*zPtFalrR#8pV_E8|=Mzu`U*qZkLqN zgsnz_i_|DcVmeRo@!p**MhD*5=+L%_IG(8MDRjWy*G-zJ={Q(|#-?D@mi8 z{2%Mh=h=(v?mW@g|M3D$vT8CNa!xVI0X$$qGs9q7k?|5dlJ7$@3(;y56{b~ARu zdpU{Zc!cw>n9A`Dx6+^iV5+IG;T0J|0ExTSmLaJDURr1|8H1x!&xNTNgmI@P3p}zI z`pNnO))L%Y-xeM5xJof_JfLl8M|?;m90)j6B3QOPLWFH2RE{1MB$*B!V0&9k)1^2I47^C6rVtZH9PP-QI`)#Yh#LAq|XVAZ)l_C&sdTj6-Q7Cab z7al?nOBau{D{0!c=UwWJEa0H4s&#_qGSp1{;ve|(wN8jSKqp{x_l~K}W$<>67&$fR)dfjF;WSAgRvU#7gqraT&Pgsu^L?98R|BIS^%Wle;P9cl*y7=mgR&B zJG+fIEtZXMxecA=b@4iNyZIIOon}%V#BY>*C~N3Bh^=bq=gS3lI80lpr&8DXI)%9H z@e=3H&tRYy;T9{Q>{>+U*aQ6>08KT^7tvL!1~6UT8|7OZpPB0X^hkt_+0Lnb6+YI7 zxRaMcVHBKGeZLe6Of5&aB%~PRoC%(4-3=P!7r1kz#J5)zM6YqJG;BI44~wa0*8W%n z$s;kvK;bcRNEeg}EfVF%Eu3<{TYZ_mx=2gieWwv50&St2READUa!uL^yU`UieJlV0w}92&3!p1i^zxCRp#*UuJ2ukT0n>*w+EL z_)4wZ@Tu)^A3)X0O&?50=GQ<`OBvcR>eL_kx!W8&FHT+D`*4rw#c(=%u`peQUsZu*daEKVx#%7hcv}OpN($0h03=u8>5y7U3$QVI-{ldJl zfx%k{5IB>8=yr|cA}1S(5ouT9|5)D@do!Z33IX0e%P9^;O~bhCHU#*qNQQ*KO$ef7 z$d8N^`CDtWM;T(pOpffrZj2K*3qgyT!S7iLZ`Nx+{mw5jPXdT|5)*YRWloHXYU?l{ z_T)kd0v<9Y3ks4+22dvOUKJPqu~}R%y=1n@;4tX>4?HSI6nJS-Odjn-2Da4o$@j*L z;5jauXxqfjdkLj6W*n&h=F{@(YX=EDu{Mi<1n{ldz?a>I#F#P+bwx&1W)UQpDMR!e zIIZ#!w_wLBiAnItbTW-9`}}=&z7Ad#4XgFRQ6lIA?m7m9K+>ed7!X6>^gca}-C{bL z;jH3s$sRsqaQ4OL^|x{WCSJRCo_{qAf)F7fj=T3KbcG>6fF+SGXKzy9JD#OMcf0ZU ztNDmB<I}(!2jdUcu`Lw!PTqYy`v|7v_J?srvdK31gd<=Isu`9t3RystC zq_U_pfDtFiK=SrLy67dNFdjcXb~HF;;zAzi-sk2fAL%d!2^o%|9wG|GWMI~NG_Jbnzymn255P$3GV zVerE|#7%5FeI+h(s*qBP4Q7$2C4s_5guaeWDUg`Kh15qOq{dYW3a-kaSky2QQ;*4G zITV?nRKf=7uQ5+CVXsc)3(bi7)%qsE@oI6kzV<5Nm-ldJ08J%KL<794eFc6X;e#aQ zlYK==4((zCJps9k9S*v~5st&y6f~#lH1oKOr=OO~a_ds(Hz|}t4F^UjC_8h(^~ww! zP7bB;w%%BJYn|}sMh)_-N-vH>RRTY1LUF#l#6oiKK5bCda^3?iQy~E;DkQj%c@JMe zbvA-SMS>=NpHS+4NmjSB37m3bkvdvFV9r7}u4cQd+#(cC9T^-1l3LM!q=)WNLoOjO z&%qlT5e`lt`6c9_13A|WNjKh8$PhFW{Y|ef&Mwz$M+CpA8Jd+{3JxX33Ox&L7)&g2 zlRaSQr53!MgCXlkU3&=KZe|k%#A52S?r53|o%GVD7ET!)3N#HtA*2?#s8&g7JIH`% zPDjB!qomus%kJ@H5J*FSM}~mx(G+(x23zWAnx-KT1h= zGIoU#_i>W&^lJGfjn!(*V``#+U#mGJB9vjZk7BFdg!hY^Ppj*XsfU*vulFWjzCVA0 z_6(f~r?CDU;_lj)z_?bw2ty22X%3?ihb0SB?iS;UvRUMMd{_*yePHC|7!LPoQamHY zRSj}+Wh=<#H(_+gpiP5Zwq1$H>TZ@K@29EF`a!EG7aUX^@MwiWqPQXCXoEmjb%x6v zmHogb)x5LQsN5GO&Bt{r)iH#@DUs?xPtFv1d&fnTaE%Tn!h`jxWw6)_(3gG zW}|{hGnm^J?U!RD*v--G6*q&fBs(sXOqn7dcw~b0O0~Ffd~-8aAH_Ig z;0f72(xI5g2pnU?I6m~lMO$nNfxLzFUP_RRuGB|Ap%n3HAsv zo?}!#-9)-`ly{HvLr{lL+>B<@`eAA1y0lkHnwDFPpYd4WE31__Rvd=N4p)tNGMu08 z%*}xIDUZc!Dc(VF07;!Ed$qW}ON%K#S^It^Ts`W`104WPt(ZcBdnjf(rp^Qtl=J?g zAJm1|*4PjPGxeh+F5F#T{+c@2iY^3PSZgNbq8Z=f`dTuD0)T=x-L?$-;_b;gWkEoz z0K+{^C;HglTs#JmfLxcXAuOThrmRV2_(1*0GB0)Z)DjJb{-`w{{W zXk!K3_jPaa6km9z?m`o!1?K7DH!cjL=Wx9qncvQ&)-^#eU5<<=DqpgrccI5Nv^i$ga~43*UW-u#JF|g=7h3#$!p% zpV^miTuw?jelXgXaK5T?uqE>LbX9JS_pYQ|1?G7M_^HXHMVjrCIz{ zEd_XpI0y+Ro7u&fCU%#05iAC%VmOV{Ih+oq5L3lq5^tAjLp2mgm&G!HzHE+P)G=g# z;SPLIrGy1g@|7n;aa62kB0)3)2Hwt5p&LNOWko59OtFGIzFeHA<*S|@^L z0HuAay1A*b&27h||rT z7Do`Z94I|mD~$QEPXRBrQx+bF=Nm7;EoUKI^9WO#ijgWXBQ+4m3)+epNXRhoUNtWa z2i#;2rRa@T!izW<46mwQ8G;IfA(#jDDtd9XELYUTV`>x#$bdo_W?M*Gbt}C2v|e7X zeovDWuG^9Bo1cCG|A!8RLwc?p>6Y_v@ea*EmsNTF5>Tb_K$mp#lM{xXO)*jVfPR4_4AQ1UhpzACY&&ow}@3X{6tAx?mTvop~M3?Iwp{$_E-ehcSuK4JFujf90V-#j;BYWd z-WxbGr+4M+JA z%7txuh!2OE2vH`R$^6Jq)S%Td9j4qE68#EUoP3G*5Jw(lJ6FiwtZr}1Wq|S2ni}}j zN{#*)31=xrjgi1+H0tl}oE5=%q!XA$Gq^NSj&_3+BN-?fA=;HWxz8`Zr7e=Yo%jj+ zXk2Y4Aq8^0z+n~d?#YWX1tOxYkrIQYRq}Zh28@smE%`jV`YrV?oLw%ycpO}G=U{N0 z=#M4ClN1JiILgJIoG4fM>3R{;N+7`7DjE7KIHDcSgFIPot`iSh)d3D3t#yK!s2?fk zXrRfpZsPi_-3nvI(JPhk#-r?mmDS|IRS6^P(UCt!d}Q?xro zLniqhUwS_vLO(Q{PovOjCy`*HpkS6^78Mf-^N0@K>RNkYcjSC?kE! zZc7^4ROrSLco}CB*;~Z^6kIk+g@6HGZwv!LV=4xYCHwR!bURh>HV%a6XDq`lg|$q?eIFX8N6SVWaV*v_zkS~J7) zD;&8Di_-$c1pFGRN`>8}U|uZW@tD7tz63aXm-HSyK9Xciy(HeHm15!VS_B~za$kb` z;tbyNCr6jFn*IrSujIKwKfN~^!3`H?$ zT(f8_Jp2sVkLx`5zxj;4dRz^7}H z_@a_NQBOUIDV!QzYsNc_bSb=z$og+;Kt)JRLZEYnaO?9=Sc1jyB) zgCj-=r-9Cx+r%<_hCCfc)@w+oH&rQQ$|amN>HD>Dl@sZ5Z%d8AIagDS-p7R|J|Y zpv3Q?n8l)SmQV;5U8BJ1Y!o4SFe*?x4Tcmt&0eh2j#Pe;M4wOKrmB}{ZT2K(K7otI zm@ra?8I#NIBC^yOqe7HRoX$XmVA^u+0ECQXe2Jk-A8*Rlz}%oM+&MeeE*f;dH5~kU zRLnmb4ty9FNp#d9Akn47`PaL&O5pOt2i!+1xZt1~25?gdc(_+#POz97Q6H3qpmi~p zVvN$~15Rf?gq}t~*VG4eCZ%N|VoAd&i;L#HhFr5`ty>TZ^XSFg>JC~2}2O635c)@%Gg>~l{n$}uuB z+n=s0G9lO0tHdw%q2Py2&(+x~t)1b(W|boW2sE??0@vRQh}=GLTxJr))g*fuY9=_~ zb7v18mi#gta0?gVclSwi@f@2kE*y_gB*I__oEbwx$k9{?hGLz$Z1a<1WjrCmH1Bm3 zMBA`H!%8f+Y?Fo`)P_QQnh_ozRfA%F4500i{LSyRnaZvqZf|2S7v`cRSx9l%ERAdf zfvH0sDEEZbjl;aGN`>`6*!~r<1lo- zcgcX>wV%=!8fA?Pm)Az&APBz0L4cR2AmBT<6=KYHWTO`{2nD9Oj{Y9qM8w!2D|b9i z8DMwQb~Hle0qr#}T1NYB&pe*ET->HzF8=S?f6adQmwf!g;p5YX|MuYM-;R#nJ%D#h z|3E7LHT{d(C$sk zR9#F@!@^gGM~EJBno$noL)+V3O!z=52?)cK1;>nosR<_xgxWmV<|$4dlyL;DN@7bs zz-hyY&iNCo;3gho7RQ{iyA~(MGQ5`18B=K&6PupNPyN&42gE3A;pjenXra-Z`ri?HYwhfHR4(r;>ic@#z7npO#-NKm;D&hMZqdU@7^6gsVDu1} zalX1teel<z>0D2L=eHHWT_D0bY#-UwB)^aGO;0>ZkNVrW0+?IS(d+A2k!u7ipeK zS_bXH#}44SG6IKnWF10>HX+1Y;``wyd+_1-fgjWX^oQ^Km&# z>nx{e3Xp0o-KC%SZS6Qi#jqVu;;$kT;zcH>pXK;g2Sp&mz^N+1h;vm8iC`J5Nc^~h+H-V1#*h+l4q!ncs1uOL$4B;n8h)L>$rnPlDLCtBabX>kTINJ zHcQ{_>;S;!wJ{_B%8Wwe_p1M3?+rv)_|MV(uAK<{k$wpnU=HL&nCY!X;@Ux9wM;0& zK75aZ`C}j6bNN}JB*St4RZev_k4Eu&9Oa6#JC0Tvxm#>Zi&DUz{Dw#5^c!AYzSOTn|Nfb>}?=&h*z)0 zi6j+qiW`VHFz^foLs$Udq@LF{&PWK+G)IVHj|ew0MWWl`!l>3bt%%G+4V~86ifKDr zi1>*q_zX%GG*!70)EX_XwTiML`ENSE4>t}UIlXd4Qu;y1jqc_$^KJ)l;KtZ?8F-q~ z15Hvi2u#z9gHB^;$QN&V@onTN`<|Qb!5dQ>NNS1XtC5!~qeeqXVrP2-@edelk$$-~#W^td5M5^F_(q0;WOm9MxoRPgW zY)!673k{d+0)(Uk05X>^R}}CYMM0X2 zYbBbP>?*oR2Q1$y_)OeIaS5)%0fA3c zXB^@_CnHg=AF6w)09o;$MwvVdR*=B!XCy+8D_*Zz6lkxjd+Y>~%@-sbLqZINk5CJw zQNjViHh8nhjZm|YA6XkqFpx=4s1C)|Y|sM10k7d0(OYWS0pTd&fCym}&L|L|tx`go zk1vSxdn8@G5*fiVWr)FpXGC`pc0i)Jux>l1?!$n4)_kBL|0#8eppflUEAGf=Mu8V| zWazK4j0@|)!Ql|nEk_5Z5Jm@Q7i@yFi;)oOpl1^ywFpC8VLPf15efp(3MKms4C7g& zPz13A7i&t0J-{*?6!J%#`+YEE*pg~RiRg~%0}Y+$h~|OQ8IW$`8O)=#!m#gE!Dp;X zGhu`ZE>bMe7F7u|59I=2nL>+lgo-qN5FJyAArKL_fyXKead~09oX8~!VhLyNk&m;$ z@#Sh#5EBWk!+=)@1P&DlX|rp+I~*Ltn0=H$kWS1xd1C{jEfb+~iQi}xg(Jh@+1=$; z>U(~&S>V68mqu9l#V80VyC`ew7R6R&^|WKhZ%b^5FnhZAZE=-0)a?wKdHEc#U@Kp7 zql#QsF9*%w1)BjbN2e$;A^Dk@IF@3_Tx_oy(pWyzG1GEdw&N#nYTxDH^hoyH&=eR_ zBMdDAT>CVPk#c~`mg@(=LvnzN%QcWY9xbbL?sBbF(sXdqX%%jo9QsbQ?CKQn=i7gL z?6?W^)j;D;8lZMNu0z-7lf}o9nVmfX&@ct2AI_km$_Kg``f=tGd&D!xr@n*5)zir| zheQqzg$c-sG7_Ql6A%g#JTfzVyG{#V#my4<#M(y577?nrSptw&NzYs?1RAU`;Ch7thpG=eYQhlZ zL4GtC&cX4+Vxt|N-d(lh$ZOLEyt0$+x7d>?E#X-#>k48i9e|v|z(b`&IVJ#S6b9VB zV#9s{VeyF+2}KqL=VxZ&!~yM8ljzS(0G1Ad7t7V{r`gM;pIzvy0=Q?*gfKD$qETz) zUQ7@xa!k1Yh@qqIGH}5%ddrx)5kqY_!pHzLyoN(shA${bO~V+|hlj-qffEUF7(yA+ zpw;F0a(#WB7IQsMJ5QJ6g&@bZOcD^Wi$DsTsZlUe;QVxVApnK(5xmtlljxVs(K2Te zT>AR`^2To#i6_*JM`P4k1`4zr558^H#t{lcQIJ?^XHn>nkHPCY1fDF@SgCbds^d0| z5XUJQf$WqKKd5CPWV~$<@K*sr;FxX)#m*@-riuMXhrw5}`7{Qag#`@HFF!6e)wY>i zEg^In;HH1PC72K0r^_IOOkD(?tu~)$uU626xUUd?Q`-doYBo{kHbVlFQky?*3c^X4 zf-oQLG0N$q3P!?Hp_4s!!b+2sw6kiCEr)z zq6C1U2$&}B88#oE_*vd=k7@CcuKBuqqcq8?k)z4(q0A@Uz-sXnjt@#4;R7(w!E@+4!rL{=K2H~%srS0v zSelQlg%OWeVGNZoSX@m9z$41Q7oSdtGM_X$P;$0KOR*}99|;C{8)p-LTimAfA!(>H z2s}#xa7^H#go+_haN3Bwt1{f12_*nhpP)hDd9D`RFM`H7o5ln*#-Z?j@jXq%dAeSI zEr93@t9CXKsDLwLHnFEhX62V$(jl=M7ecULD8OhO3Nq~G{NhvEUdvBN@}&i~y&8cO zV8ExZEka}w(g2Iu!&P>dLL}A9B+~A^zUb&&#m*y;o}^vY(t0)5|GZiiW2}MQsA&Ww zU=KACrVV?FWNx4kf~=xt><&aXgbtR$q2|xi^3eq$@SGY3G*%@tCor3SN`ZsPeBRy3QXiraOe4tV|APtZUnWYjca)*a<&G6?tTA?Zp3(XdSF z%`)xK%9TOOHNqbINMmkwolO=g70Pch}-3&3j9(Xm!z9vG&WhBIEfY?THyc_lok4U;kEDz zdw8wxs4WE?`_53kk?JgV12bS^V>141ndXe8xF&6H zha(4d*OQ=TXtxRu{nxwnK7uj`|M(~3raQfc7F`Qpp$&2sa( zT%&{*KWZ*J#dvs@Sjz}CX!D)^#CfrU4!wZRT*IArLn0Iej2DnZXDQfH=pShr{IvV5 z%LDmLMFRM$k@Ukn5TK?7<1*^T1X>7u=27yxJYpnb8nC;aMD(b;vK4ORaKH$VYSGem z-Q1xN%0qai8U^7wURm&H#(B{=A`18GLkhtj-bmw$5``3tEoaZu`qn#8fwtyg zqwH60qhMCS57(B6F{>z}U4lB%ee`<|@JgNx$N}G{UDwl^rDb0l9y^M~PTfg@02m>t zG#N35MHyw0R>XogHi6(Rs=y+|6>NK}59Qf1tzG-;EL8^kgB`7c#=SdDI z9ZdX&N|;33WWCEcPL7Lx2&uAS6k;ECqlc%mfQPVQmkqb^E_pPT51R10t&D@dQLX3( z4f9-Gt2nulv~kV>n@*DmUgGaJtHmeoB(?Ag z$uy^Smw0|#BJlu40}$klLzqBPq`&Twag^)SxN@OLgh5db$pUY3K-qN_xJA^+my6Yx z56d#5MB8eK#9tMQ!!W2AFxAu$ssjsWA0nKVl8`U;ayV%@wE$sch<1^6Lh$4x zTJU}L^lq~(V@cn~S*)&5|N3Wdvca2Q-r_S@c)MRo9bDJXfy3;!&~EWtqw(#Q zn&Di>(5qNIpa-BA*D!C8{ewq3^H$8XTT(~Vr~IUq>L3|kQpY7Zz(rW9-{6FU!+HTO zRAY>lEbg^cYFp|*-*uc!UX4s30I5`|=AjB=MoJ8fp69n|KtSpZ$F9oGM*Tt*Q2g3| z_hT8DW97d^y_;$8HZ+EeNCwZLDN-CGGaFEXXsABroN|Xt)w14-6IP^Wz8aixfB!akH>3|0-1o*33MT#lPpf?A%e`Oi@;bZFmP>zdyWvAY={-qSgWH;cQg*_-r@gCe-N!8r-1 zjv;P9n`nN{5HXM~)8sIuc_CIAU0d(>bi{7DsTA!$Vhpf0g!9-K421 zc&^gLFP7;b!S78W{J3;9ULnDulH*St$Ahmk5=-H9jv)dZAZIg{k{IIY-IuiIa6KE=}d?2I#;;JK_N>bzDdhxTomaizMoo#gM$7=1IrQ$TvS6LEC7@DG>jn$p%Z0r z+CpL^ybb{snZSSwZ{&bD!wlcq%hY#W*abdejRTmaln5JUv~l2vqd?HMr`yHJBcErO zUEpyeYKhazh{Ih4B6YQ-JmYVxWg7YDJcl<9(m;%(&j_@+;W_Za_dz0$aTSFyM(m^z zhhYepDMT;}*|+j4LE#{1Yoy~sKi9%V>MZ!8L_m%v%R`x9(6Dyb$N&JFO>+nU=O1P)zMf*2rrWfx|5Yk9R!}sL81mbG?`&Yr;Oa4Ud5Z3 z6X!juP$rI^!H<*>I5H$CRIgUI7io!#q6^T4wxCd53g~x)0&F!9v4s1;P}GlInUI;a z!EkVT=m%5L0@QOIhYFdo0&R3yoNaDbH?tpCS6|Y$;7+&wb`rFd%5_C70C#OB^7#RV zu!CkoV|QjUgd#mFnfn!KMJf{j~g6xN9)!ZG)iq`r!^bI{_nw!GN^I&bOuQV#ZYMxB=DX&%nd<}MR#~PM-AzOYmA)0H_eKQ z_6s%05{YvIHC(X-KdsC}K$+~*kB)qGKx_lZ1|%XWa5_WLB}LHAw`p{GWA0fyxN*7} zrf@hD0qd&ZhHu^ubO#5ZCL$&@sH$0zRL;`f#hX%$94&HkP}3p={WObK6_u;}zDxrs zoLbN%w}m*{OC+Y8(!9x($gPt=m%M6u)3*PJ3s&N5iT~h{Nd&jR`f;;dUVmG!FC7ec zV2uLWq8icXgEI^<2QV3bymvKS7%<$ zXKJtU2SXr=urmbREdc@qXIH;&mY0|;I{WD^bwzuBu?r4m%Cq`A?9g8kzp@@6PadOEVro``T#+u-n?Bz>0VF56svl&k#OLmV^0YKN0p6 zFQ+*M-p17c4jvYf)Uo-9qC0_El!lbJB=;w<*~)tOA@zuyZmYCEA@9|_-hhgJSUTF5 z^L{W#A)rqq0($~*bn3_DTM0yvsJ(}H1(q$;Mb3j;V6$7bAkKn)z9@$lq5+BvT99$U zp;p9#B03-(4zyq5Aj7TNE|PlI$t=pTg>DsmMx~~SVS`~fcm?7(r~)H|77%Ai5mJ(# zma<6GR{T=F$9R^a0shr!gpmk%daaFoV}c+7z zmD0wRx-^U$2|QnUu@e%K2oMSZWik}q(J2v%99UFgQ-s23K}#`z`2vG)UoEcI*FMc6 zpKA-Dh^U2x9(dfgCWYt&WJPRcG7|Tj-Po<179nK7MT!7! zuUbh-BNyaoG6LNNl_5mVSG2>X{<D!zk=kE|(1=#6ius#Q>*e+8 z_u2VfXMW|IpMJqphbWc{rsN=Y*Ck#2#6o;HwMU6GNW|k2cRiSshA(tKZhqzQsqe~i zE4pw*v=gU;(-SF*s9oZjVwhzhi&xY@7QRMpk2hAxLHR5~xQk*w=NjIprLw)*<-m?#tItX%L9d=Q>j>@u8-KETTpb5(<7tm?9lS%Pm|l zmoVWZTFqo_(fI3hykrLHN~@1b+Gyew(R+L(%CFx4@c}8hC=* zheRubu(kvH2#0{^;OH&!O*1ljL^yCd!x7RPysf|iN;|QZjju8Kyqsl;HmT*{w^QIi za}*A<6j6YwVtsJ@$XCi?QM89eyZK@W`M@9gOIlzAV>gWA{~!0{~Sz#PMR=_^>As zWzUIt4hRn(=QNY?j(~tOY85e{sH5yz>mx2?|@&eM)4iTDi81frkB>1+qVPvS+Iy7*+d zxlWFuH8sgm0l27t;Gvd0%3*RiQyYUoFtE2BImq{Jjvw8h3f;|e@uDtN$gUyoYlh2e zGL-Ev^BPTI{>pBF)6s5$CyC%`X~Au(Br|=5Pu*RvzsgzW`$rt;D`RCUOGv0jcMK0>onG+9#G&z1hlGFgys?Gi1X9% z(G?9?Lk^&!C<=$t!l~uJ?GcGQIC&(KY@`_2pMP=CosJUoZcw!e1>%KGS_f;fDYr%- z{^M#Hb`^7I00Bh<$O{-uirUl|!{X6VF~ymP(oo1BORYi=6273);y9$-xJA=h-0&iAZiP1us-;5%nk+8W=I*;=b#Ec}IZ1C=e1`*vF zP2)#RR>*qrXK4zG?=%6)Ya9qkGn(3l1HO?~Vx*l@^z+_>%wx|1UO~X%k*P(R#F^CM ze0A~ZHUR=;2M1$-0Lq49lo6Hls(6N)h!_SmLX4$Jj1d!YzJ{#eT_(ntN8R9x%3)w| zd3@BS*1jvL%?xKDVe;BGW+)w|>gKSTNQkp{GLDdD869XPh5*)%tyfG%TgTmx z9+jnt0Lf+4;@E{L>kk&Jz$>DtPNo;m&X75%vmuYcx#|W5VZ5AX)vm+jz41o}hT2svl{5po8KN@2*%tRO$>j6oQ;i;`;10 z(xFHMu`MNI0Dlw9W{@Bh$>Z@WwK2gE2e5P2(g#+!bTvaOo%xJ5AG+*1N{L!(GA%v7CL9n$VNn z#eg(O!@^RX!bw!TVfGP#)cle15dmg8c+?jG^NV?X=SEmai~s~9ifbWW^-9JUHb;SK!zezywK4kNoliCefZ zGI@yq_bzEkqes!9flsQ$;9)bU#^Y-+81Bj7-E_Xgi*04lK}YB@Nj2&Ob9m!Mv=f|G zKw`dScETeg$LsaxOR|&Si%iSU&2?Bf0SVC)E>bLTkt&8^!55z{7`+{1ML7q#q<_KtfN)p`3HX3}yGS z_)!-JyrRS5?K<@pz5i4ehMinGCIHLqO3I`gT0LeJ!ii=-(Kf5d)-Q@l)gcN&C8AZ} zbf(0vGVu)gL|Q!iA1Uv?P9ddFzFlTP7^+n+Lc-bR1pX=!g>$CzM=Q)(Q#wO29{2iG zsl$z`;|ZNE7eXQpFK&*5Lz;9Q2n1#_)3Om>*43GU70YyJDB1&m2wAuYfDk;oy60_`JmY z3}xM@hxy#c{Ezu_s+O+9VmHcjY{iisnTeJI#&8D>`XPb2GX-y}*t?jYQ;gIEk4FcL zfw;9rQbrifj17+(VdAi+jE)DANS~ml2fDhLNu9JGZBv_^2M30)AqeouVwsogyEItr zmty5^c84i2fUcIN(1n0|DiAoVTsT!_4;Oyo2M)URi0}}H!!Y2sh9Qij#fu0GkkWH4f5x;2?-JW12D3*mXeEfMrFAew2@wb8`LTa+CH0 zK}Ta)dj$qwOSXfeALu*lFiMPA$Iy&UEYsZ(Bw9$00|gGeYYy*%P!NPx#4q!Z>Iv0& zQjG>>Lx>T^4T7y>#1Q$MOpILyG_)gpws0K3M5-;NH%F+=Ue+L6DgGDBdq)5pCf&UaFaqcAQAPj9-9cd3eg zJA0Q_!!MSm^+T~85@?ws5oY3`rHTYDQV+s+-BuVFlZyc)#&yJK_ayKzfPl9V5D14k zkCgdrkolx>awQB-DJT!s3T~lRgeTN|Fcjoys%;2B`$V>(T4n(7$N+enrtzlb{4QQE z7gv*t>8btY_@ErX{*~4Agv@^@;BS|hQI}j3%evQ#FH8K-4=&uEyEQ z*COpyPcaFmvqLrA<@;<#E)3#{SpZlvR)=f2FK&kVgjxdbr7%c)@mcl_t;@||wAr{J zM7jV#s|4aE(B@8L-F290C7R{(%k1pK<=xf4{hTK5EH(gNG1?wu2fs-iYjJRpC>sb5 zT2o|Pc>&yZFU!MX^crW?$b@EqU%;Xhp9L0t&J0U;^cJvlVb!a}$JIrnmdyU5I>L0$ zGXUaT2INR7!tY3zL3U41I&yrV$rnDaKq1~^@USuYG83t$>KH|MWV-p}ds>Slt<93= zA$V%wsu~IBJj24DXudK7KUE}vk5tR>$LR%;VK7dt9pZ706I2x81I{QP`Xe&}AK2;i zcJbxI%9lm(8Al-qWB~US0vA6>!(kA6LeMLY?F7N$u7x3)pLG6pv-()<0pdWw1G9+Y zQ1my1lg=U>2&NEDAQX>*%~o@03q-G)&ih#I8I1y`l@P}vn!>4ONI6sq*Xz`C?d-z$ zEKnAza4L`jAk>OUNt60aI=_cq93B?sDcnS=Q#=|VjacAqvT<~$C=nGCh*yidG_Cq%S(^v`s>}!zFhHLg34XkoXpq{@b8cKU>ZE1CN|3tUfPOit*#p z_bcI=y76eDlAua3TsU7<&)?hG zbC#i+q>FG_!q!nUPOjzixC;6}JCSR$t*D^;K;JK9#i+mp`yh803onmxT8LQ$`ixdJ z&wJ=G`VU;4*&5U%B~^>f505V$z* zBy@eEu>t~K(A%cU^wUqvrS!UFfDnW#K5ds3>L7T8;(=#qC}%Ef1dlPH?yl^m1CULp z9zB$KECL4WI4V>pp*%*`3mhS&%PTTGS!_O+%P;yV8fdEW1+7wpJq(@;C@3KKK^5go zV%#LiC?H3Ex{gbDct2+!59eY@iiZ>K*u{cR-Db!7hMOIPHA(%HYqV0a;9*X$OX-G{ zj~NRcaNC7#HI`yQtHX(|JjQn914W!-VIuzX-BlV|HhaGLt(>mGbYAVm@xo!zrvb;a3wxnCs+>47EK--#hwmv7zFzvo#|OD+nX&>8 zXva0U{}?GK_-#^*8A$dE0t97e_sZ!}kP0-UiNX02vouoA+h&>e0>S%a;lxjr5Do?& zigAQa7M!Vdf}f2Mt{hDz#L%-SFtDVsI}H50VWU>>ULGTG;Lg1Y5w%Hzo3En!uGRA@vBuHV9K`fa?VWfnzES%-jV9-eqqT@HT=% zpuroiJn*U15Z8yXe`X#1G>l6BFj_}HUtDZbO+9t6r+L)6*J-ty(Rmz^ zPAAz8tpQ%Mh|Xn;R13gYbq=z`jHJ8bD5lcj;N-EN*m)|k0?SLn2n(Z5b*)Tw4;EAw z15={R%ocg>%itw}paS%iM&0quo zoL2uBm#Q&qpgNc+zFmI&xPot3mV?pJh&oh&P~;GDm#9hHBEH?8Tqq~<@peQ4t#H-R zhy+e&Btm*&NZ^%Rq`*BNez{63_gt1?lglnB2vQJp+OShW%%EV{H$7G;q0p3qW>I1> z6d8pSXk{m8V%W$4l6GOFc)2)VTrpx;fB9NYb>-l&QDTGzQE}_BkU<(U9gcDn3=Sug z0%4%W2prv6Z1`5587PXpPrDI7EAe+xG zFO&bMV@fXOJ{nT>a|_1`aiM>L2cfYU6rpqs)lwwlFmyvAaN-wiK8fLtUw!cu(TBo- z>uZq+dWp7=83%5%2PI15kj4X5JkB{%CadB&a3(_#d?^5gk?w8jd^EQ{qRH;ffQW1C?v zX{_G^ubjhNbLVsC&xxz+))`pU89#P%?8n}o@=g-p#0uG?Q`!AXow|j3MW{<>s<|JU zG{m~@8{p14)W!SeP?u4RYTON|;U`|=i`OU^2o3`>%3(lCb-K1nR!W;2tQ_iU(t3P{ zEK?!(MT4aMXmKUj=U-O0#i9hb)hIVZMGSYXtrLIM%vW2FMo?@W5Zfl<&L8=_$5l&p zKI#znA3WB&s=&dJEaMS6deA=Q1CA`jdvlkngEzDDBv-y76vr-K`#WiW=wavPihuVpD zz1fs$5GY*p17AVl5Q`xk6+MPwl%3wD-Z*_i>^xby_!FL}kQ@ba7$mrbgXH{bx%~P) zE%>wk?iX|Q`+MQHsv>Az7LgJSFrr37OavD4M+bTVi5TACbcP}fq{2Hj?L-G%-d&~D ze_Ywn4?<@9tE4atIt5-e6d=;QV&3kS>!h5KB0_=F8HzAu#Gt@MjujgGFBcz+C4YR> z>sZ0VW)T_oS3_Z}kfW)RA#P@$zVUt?9c^#`{J31Fxn)1yVYzU}1-!5N5Z8YERjpzt zDv-OZ8p%;pBkVJ(L&XFAde5ap6|rnMRDxB9dBK_T`?ornxK87fR3b<#VL2cmezh}o zKm^}`Gc^$`sF21@C4$9VB!d`21A~505qlI!gI4a77O`D_Nnt2vCVyHkJQjFtjRpB3 zMv<^6gWg{jpRdwZJbz^^`C_z|e0H1O=XR0yIQ-U`B05 z2H2<$Gwu;!jKIfocsyIDMH^OWRgcE>Hm;nbd;w>bE#$#XrPIaMizzUmPTZ_e0q8^idM@}cS2a-G^ z7$l!#$HODZ+{KCdydw;_#Ag#M0z1yX-lchu%MTx%2lYiWnummyfM;ujjq6oAC9Z~O zhB=i}m$IG3pdo*C^oO%7#7) zxJcQ+*QP@J!6*<(V}tXaIHPJAgDGP|(1x^#T8ge;LLoke2pcUJm4`6C54hEA;77X; z4rNgS4{7!|S&&|v#jd#)>gm_ttBoq#ItfU#PEJd}`32iC#h6(P<6;?9i7_-H>^32~ z`bcJ&G5~^9n7K_G&*Mfa47jK^35P&Pws2;Q3OGN#oG#$(lWcZlDWK@|fYX&y=zW9j zF}$yA1At!}1i)Dg!I|swFU^HI^lE1-h00_huJ%!WZYZ!%zu4p=O0NJ>)!7 z@hI2%J;Wz!P9Pd^6|u&Hab1KMC%2dE>5s@CvP z$HaPm;*Enzs{j#*F(giPNE-%9fW__beta@68mQ)jLj?q|X6Xuu;8aZyI$qI?bbh*c z4-FN$FlG={i)V(cS^&YzC^O>14QNdp5UllCO}lhn9FMGVh|Arii)t9~K2keYHVhuv zsm2&FN-vH!2rmK*yoEr3GYUcIO6Vfcn{|m>)qdP@A&d?HS@*P^LX^De{bvxm3 z+zn?4iG?%W372gooM!G+2QVKZ$=~FZuUrC{;w5yT06D8T?M+UP-Ai~Su?l=VrjUQ`DGEE$TqXzoF!MDBaWvsBtUS7M03(57+9MIXrd?tCyFz* zX^_1{L5`*pV*d~~cndtfDURHI{*Rt($k6vP4Gw`CVv+BB{KMhn(}(}|;OO6u=6kIU z{y2=oMxh>`q`jU=8fyRB66O5Vsr=jGYSo+N`1Iv-0Noj+vTFt1i6eYVuArMH%yET! zO8Kjs2_1?ejNl;hTtY9V ztUT6-LD>sToF4h4<@CS`zKa!R&9TI0is-Q4H6qGT`+!aB%t<;wPL@JaAZ52PL+g zstgC+9YuC_6*06nD1xhayZ%1=DU}9`4p$D18O0&i#Q>BQnxu$_c!GZ5sguJfQZPq6(nIejwOa)*giP zlnGIFSh2!D2xjqOnZ~iDTB%nDeAPN^AQsJG7>)&aj*5dc8vv+4;t=N(Axz_jkh$H* zR_5LeNGX-V3A;^R*+Qh!Swvjd#(9kNDvk}$FF!6e_4H1(La_jHH5r0Hw6Tz8Z^2^B zBJRJQOWbS-2sVSMv_|~w%{uip-WI!O0{c-?gjf>5)mC9bDMw9@5rq<%`k^Uhf+4N~ z*zW8!;y4|=YZHs!kaRzO4c}I2L02j!641a!H5#T75+gLx7^B4>I_uX^AU7=Ge7R0* zXQr)}J}tw(!VK_6NfEkJ+N8i=|G@eKZ%37&*;&F#B&+&ENV^RM{MrcwXLY?_ruFXd zcd6szlDhr)t45h2jKZ0k2KeiJ+Aj?GGy4#pt{TqcXV*bF{GmU`(ohwh z##hO?P3ZA9p(ZY7sY#>LMo_o6pO)Wzcc&j{jbGGI1hIy9uEko60pyU;Z|y$s7{bV} zEMA?T7F{W3BD6qrSz-TWT$kYpsa{vOq&SAv$jY6u{EDXdvEl*5)pkO;7-wo@!C%FB z@zvXKh=&q8!Lz)b|`<$ne{>nLxQ(6Bt~>I4xlqq;2ti({Tch+?7Dq|10qnuS%(9H z)_k9?lpYYlIwBA_t=4hat%(Fqyj@`IaT-X71;&Aq1pBP{)|5bd+k%NU*A9eVZW|KY z{KwHz5n`nWO{fjU&AIWJnlUdK@p6&y!xe%8C|HBw6e4)hHV8%sJiJpPj35Yd=0tlc z!CRVb(D_tpU$<|GXqZ4?IOX}>_2qAAy0C)*S1AlQRG+~^k%ubsckz9S{r|GwrPO^j z(!2C*dAq^JfN#>?7;70fTP^bQ?D;!1+@&07W==VF?OB42sWBiFR()i*jf4+6O@j)F zi;FVhwl0-Z=`IqLJjMHoJyaRTX))o|ub(-`uNn*G$ikTgw-Kx50dJ7;DaVzz^9|ET?L%z(&8QC|D%Q?CvR+tmXn;(Z1|19W|#ic&D@cq z`hZ$=pg67ch<$A&1+fV9;6~*y7ppHHmPNV5r(pCL)m+!Z5NZSDF?+%Jj`yJKcRH9# z&w;m};67u%VUz&PeL$7wgFn0bvXD}aY6z~b4FrE}^T8V= zcWXRj+Ifka&Xz?9T&@tX-4zRV>Qk{0LXMgeF-C&ky70R}5aI{CgGeQhOc(yas%dLk z8$$s<$*u&45<%=F45kZmG#Q0b7w~D+bMk1Ag<+_pI1OBWJwwk%*$~F>;q%mz05iEy4c&Z6n2A7G%^>)+MQ=3z&$D(L1dL@w{|7N%8rXN(zQ#-^GAN? zic@l&C1f3YZPuAP0>wI6+7t8ajhB)Y;x(~l)0KQ^P}U-FQjwpt+|jF}J0}HYP!yP& zGt=F(YCM1BBU+tjjXIFXlr=w(vy|rJL*$4ytlfea6-we3PMpqHm)Fbh)wUIWWj>-I zzRCe}7-Sl3RYKrUptl%`C*NUIYjp}z1# zq_%f?_p9a(0*0J~fK;9iLFu+hFHwfn7~jK|udCg57-wdSk~ye+$aHU zsofP>A<;{4S2$Cf1?Va6N(^MOyLxo=NJ2dE+IWhDFOoMJ6%NW$G4mhgLmXp`rU@VL zdQKzKsx6z3sam|ma`@hLw?h~w62UBhbsfSOC*-IJ#x|FcN0LjS=kTwfWfnsWZ6-58^%fI9A_bUx^r9d?R&{U&@V}2&_kuGRb zL9p0wFm+J^(M~&ElHf6^L_4Tv^gb%rfX62k4^hU@co!i8$CX(9i&w4h;qjadXH{zp?`(xJxvk#sWl?SlVI1kLkwSkJ-lkO2T2RnnA!L zGmE!tCfctqo^BRCu|jj}m;p3bvj_+O+O&HXycGR=!@;GLa7lqCne zXHF+}9j7Ma^=5CEE8nr~N9^I5wLSzkwDm!Z;2uD9m2xM2*m0F(MEqfP#Wp#Lg&$v1tYZ-pgs^PpQ<9cGr5g z{#Js}76=DIRUFm`wki%k!+N6_Bl_Di?bh2_k@DGDT#*t2JGDOS2U-KKMr$Ua}oJ&UeKk zaa*%#Q1^*$j}QI2@2*}R=n&z+=?q8c!@)ZVBY0$5#py6-AMaM1Vk~W^MvNb4qLh&x zcvdsRpx86K&0}0ds&QWS$Bbvol>Da^w$gST#s258Y_%G;zv?>(o*Wk` z6gX6^5TnPd%h66s!B%$HZ6&4@3ea_HJuC+Mc)2DgQsS3Vp3UddkK<`|WQR9wM|KVd zyq?0qH>Gim2G_%0X-o?T-hTpw(^4MlXN(LnCi@7<4f!CI#jPd1CoYAHt0sJ0LMHN#LU zChj?g0{Off3UcX?;tqowL^Y{5yUWC3i1664SL0!d0;NIh0{57L z0#!WOo$06bLAAgy8#SnoxTEu^bMB3u|j|C?tI>E9Vl< z|AmF~`Ge8I`LpG9N-Sot?>>I))ygEviPI7@y&|dG}g^_S8xkzfxVeWh- zzoaV#nXq?OGb`t)J+`2M%Y--=ad3L#vy1NN6Wl@KhGVcaXBUi9v*}a))hD7&YW#B+ zMO8{~z-~W{6L+gEf%3(O!LQb%RxxPam?_L3`C;X<#3*fHLhtf%NdDP!sv_Rc`G&Kr zx9acT<(vG3=zr-Kfpze~Oh+TwH#K36GF7V4e++|rccZnv2#!P}bBOu_6hR}y0Yye+{3p_Fq z-Y>qVVK`6M>oUjOnp*`B(8yXT%^vmzq1Xz8{)ak=Mih?X_}Jk97Ag*LnI2jKRRI?{ zm1N+MN`(KsTKWEVUv>a}lnr=(_8{FL+Svu)mo8&ZAabvO2m{TGKus}uyzQpb&T^ zF%I0#k%B`op8vKex(@(;jw;e&-H#H&Y23Zmhd&Jl-q>JpW`Q#qfzZv$cGE0ykwf75 z73S|@Ewk&wVd4i00uJk(B2*i2+ZqJ?RQx5_IGqdjjkO$yyX>A+gbFR2-in}7xn;FU zizMHCxw|Pl-5m`0brXh=R4|CIx(?o?O)j|@=ex~fb%g@YKYI&uzWL=XKB|lHA+tHg zFQ-l7rJ5l=2f^qEfo0?npTW2Ym5BFFRYB^Js|wB^i5$yUdCZS1a>cyC*m>~iPy#=3 zU^}eG$%&UkgpYSr8sZ%ke~x0$^2~`}O6e-VX8>Pol=&@}=K#w@(IJZ2R{5D9`yl_& zG<$cUiVL++sarAa)2r{7X-?Q?`FpwVx!?KF*{O41!H=WmH6CJ}5Lrn9o1~!9bmqmK z=58>7K9Gra>nDhfxjvmhryEpsG1ynX-bA#$kdR( zEDYiK&4O_lX$^bIL0$-U@g71A+{}5i6!Bl8_qSS(5p7T%al~*?gz2NWzD7dY@0#xp z2iX`ilM)HhiQyi`i;hq4QUc%2p&*O2JWV643zv?!uQ4DTlK1pSMc@JIL2xK8eZ)AT zbh4$+f0QBKAH1ql;Ka`uPFD@Jc3?Q$;GuvK;v3vl34uceL%avRp!n6vy4me*#6x@n z)E{;qB0WIeOa#-ET27wZ^y(sHLRA+z46MLawPhG9aDIBAXM|x19p6ExWAJD&u6|YE zFbunKEjZ0Jch2fvkkPrnQbueQdNKw=4``4 z2S1yYU(yHKTSnrSv-bSnT2x*0o1q7uW2hvA=#Ex`NolshhU-Vii|522Hj~50(M2Jl zS_%T1m_bN~ngfePG$moo4sw(z!L@dxz^(|dZ+>q_(Eeg1cxPt~T)64g;%a?e#MM&; z0kK={B#aIF_N$!qHj*Nx91=OesEWrC33HdYMMUE59ddSj8|8>E9^-2&M;t}MbOabv zjRb|9AvLqcwlqdBL8s_~sO~zUcpD!(+1R4v5NkS0zxko>j9K&1JD-*K$S;kN*Rm&pl6=xZYv2t;7TtHt%*ZJO1( zzH+3%0~G~ao^=C<5=(}z6|}PE1Yet+5IdQSLMfAO1Um6`Fmz+dSVG1tweu`)l$yq& z$9*w;Io;U@$st&TdISJdEO4ldFf8!LJ;KS75v0}Ba(alX0is|*E3p_VmDAHyx3th( zT;ST#!a+fX2o#~q5f|085R(9#_k*HeLgoD&Eiz`6X2u0d@JdZ6@&W*9>ARw50oc_B zLQt9pf;SsOLNK9$C@4dM;F{Zxt;=pV@t{CgIMTRJ1xE<3Nr#qJ$WG(0E|7TnrOuEv zDjIEG?38HWrxI4k(PX*EVyGoM+-~>iI_RZgz#}t`m&??-@V;Ci6_<>*KUAe~ea!~^ z^?$DYp(mcLCI@I6*8XRUG|Krpjokb0<0*##_^S>Be^rMu5dk%V!R5KwdMvKY2Cs1? z0NtGd-7Yp_VFmr9;P$;{XEUBxym|icx%~EkARW)X>d&%DGpUd%Mr#{)+b?qWQHw6Nk!#MNW zT+OlLwqsZ;(as>e2lJHH;xh;#Sb zhXMTfB?YseR~`zyx^e`q%pAd?#4P~|v|pjX^$LZXi7)h+?5fN4NTuijj`4N|#X-JK zkid1B69Eb11dj}fCuv#MW~PTnjY4r40()Rr+XoO5uPp>=LJ<`r+DGZP4w*tva9kmN zGgjc`3Ra|@4^r0}R`pIx?|oWq;`A+-{^Bmm58S4m_yO!16HsMqB(n{ZGMY1r>?9dS zVGg#@1E0Nw;dS;)$#6TEhECw zU&n`JF>^`b-M6$8U>aTL$CR}Ld+AA#?%W5%u^sqZ%DXxggKr444gs2kV zzukx6G=asd99H5NqK{L;$|wu-hklBy>qL;A2{VU-Q=JSeCNYO2Yjq+(V>OzfO6roB zehdnJnrZL7!f0UGcAVxUMx=2}4N9kJK9jez)<;#u=^R9K(;aGqDMH>{{pqLWvRM2N zO;bgn3{;)XK@os!9Z%sd!iTYLgl!Jjv{DM2M>U*c;E+FG}58Kv8VGa%|$|Z#+Z>Fr>Bpd0|hrT z6rq5OmNnV&^WD`d^{+g~;*j2fdL?mK@K9Vx7_q)*l#J|Ma*tH*evQo?ys2g6R?D|}mauZWaR4ySrM=kRuY0mW%$MBF{L z%ZP+Ss+pZ#uGgTJRxDhy(S{-PwK+E6FbhRG6vG&SVVidLW-;v#1>R67!6Wk}=gUh> z`hAxsy_VB>92}%V(gG2i@3Z7M{G=6JUHc3CHT#PgX8WMF zs-f<){5w2C4t94PosXu~UYrwP#X9K-JA91hDY`!R=Y#p9BcUv_9#%S~f?ChNEp`NC zFd|x2mQcw?dhufZ?E|!2!N*|^bs5MA{pPen)%Y7EpMLr+?yD%^FtgF5T0*_1R7Vix zM-GJUP!Ymqf_-rM&`(0<+ML;ihq?#}NgNpJ88vf&SFL>Rm#V<86(YPs=4anR(ImwL zzZGI2qU!KsOsC2*L~a)!TD(Ie3z~+z>1o+}1v@n`LbTgi5#LWQL(6Kd+R2X_5};WJ ziEZS_BVwd9f{&>N!lQviG1A4eGaB7w2(RU0nyss#J1lr8h7&R&Jh|3we!D+zWIh4i zUANE;%jKQ%dCw9zt zA`*E0j6~>ep*^%}r|os%UBW{}kkHoIMi5Fz3tX=*Ls}17?CCP{)-WQHmiO^?&SP|Y zonp6w2?So#2}E-(csk;m55Q6-BJjb%fG1U+Q|zP}OpHmyyfZ}|FXGZ)-lD3a$LZ;K zhrUh3#o%3h#jh_!z=u@?Qm;x`L)Q3pw-WNny9ekiPXEAP{%iUd>(k8s`+xn{?1%qM zYs)6;q@#aX%i#|vN538|9$dsh=n2q~#o+f^(*})gN7NurvwH03yMmzk>szsKzONkU z8Z|l|fh?kv)CJLpXdwiW-Duj(fCDKsy+8D;)^dq?4Unjd`n|`)X|#Jx4{1@Ih-u6M z9-jJqf@LWQmvoG&A<$hdbYwmwO~QO-XEhtYKd7?>c+j>Q;A_`l;HUSi3zn09$tkiS zs$#iq4Bj(4hY&ST3+Wt43`3&5e2HJlhFVyOLP#iJ0W}a}3^4IMW${R}Chc96=Hq^7 zkJH5wHyQWCG;s|6p|zq9nx)>Y-wE6lZx4kVg6 z$%q0@=S(8xRd|tRDQvG*eL@%w#wZ!^3W@>~Afh0&n2y3DzxcU@(E)`}BF6X5AcR3n zc#o!^+;=&f{zx4_qBxjQ?8%0Et@a(w0Wa3XptF<>z*dVuL)0RiLIz>LMal&eu~As7W9sufZw@F&F2_^wVM+ks<$ zA0_I8d5PWvF239XL#xXz0R`N*rh`cXUwb+oay?1>1TAbri6PA5G?SpOFcf-g8sP_{ zIYbzjgzLws@P~jPst9KBcIGA8ROpUF3C4guV_IYPwqh``r$h{6vX3ZxR3~B(+yk1_ z^pR6n@S(o*3UZt=(ROC z45~3nlD<{lByT>gm)EP`XXkgaa#pp!*PEYy0h!yAio=K?w7i+qaVWA{BVvl|rD6LL z#2>RN(f$gt22AO31B~ZIUUy1XOI{Zo)xo3FVhtmpD2}S^CBeH0Dg7R~epWFxIj41K zQBGY(dr9y-MFEG3f+eB3zV8oDb9;^XQQ4hwwGDrS+7JXxELdt*jPH4|tvU%>*WGZeu# zX_3-rh6WoHKd0fuKiz%#`f0OT`ku^=!*T63-9!ckUal|uf_!Xy%Wp<4s z6I30EA%Jb)Z{R`&BPM!yQ4vghS!W(;3d-j+(fmWfiMDOv_U)(^&8+hqvkkUjIt-=H zFtsGA21BU-coaf_Pa@EJ(t#KynDL)u@xP7N2wbIfz)#g{@K6V$lp28+*EmS)p%EiE zY>O*lQG6mXBWQ2?JB1&JH>&vC-Om?RgC z0*`Y>F~WvW|Dh$24Y-A~iR?e)kWu`grh@~bA4K3Hl}a3Hp24H3VWml+$U_$iyHG>y z^f;A=u3g~e91>?&X%*wy+2!JkH;cB*a5$vIqwGM`%O&8dLL7XP&Ut%6cZ3IwTgaog*cN2+ErqG|CUhgM8GXw2T#F z3@OFH$pDnu6{lEh{~=!AD?+&+2uIzk9n6Cy7zPvva}Yl*Hy>J6Fphn!BL@6ByUGwh zK?iGfWSB!V0M{PzQ*S&0Z)g4?Y64Cx7ICFqoNDG+|48xE>~Cr8a5=G{=xI*BRX^dO z2&v{aDE1{r5mFzTWl(ty3uhEKlVJ#{CVfMzBURQzJ-=OjU0kn!OEY|#R3HFVlNL{}R@b+kDb3*0xRsQF6dqD*h83sC z>OBI=hjXWyI?iAS%#0Z$aKxcE1_$lM6eD#hrrdk>ZgqpH2A&)EiGl%~>!>nt1FWgJ zfxk)^AuO4$pGBdLw_`ko*K_LGwvssMqzl87&G&@D`&6h)iH1X=Z6Iih>SicJv_asz z-p4@lqGjqu+4LbsHA0L8m7$;ARFjYxAKs>!7H3Q_)_wz>&eCQMh~Pi)fSL`a6nnCv zmvZjJhL~UA9hw@!KIR`6X;+9_zxARcL)#*PW+cppDgBjs0; zc%MzrB*Z1zJ;A1whI5)@FWyHRYgS5gA+6G|>F%oNSaVZnd1oipc@~B?KfO(kWJ3Mi zxQSPG+-xsb>*HHTOZ--G2`YsbtAUW_1BiXY+Z}h+?)$9c5YpjAn{u5(x?g`qb#QD! zPW-wy(UEa-08eehz`!5((5kX*O!L}3h>0_ZjS#dA^WDw$S#fnX6oUYNRXT)$nv|G~Fwp@>KBx?hCX60%8JIfZZG70a^$z)^Q#>2PEHygJ}$adV9%nFWts*5 ztHCwiHW!H5^8D4&)+_+d0FK$8*vqpV4EVjmKq+H9DMyo)u@nS1P)oGIfSf<{X(sDf z>LcQg(s zyg^4NS?lfW#n_v_jApoU@j))4RY7IUw$OwoE8Z;bu4ZqN?T4MT(3)&9=`b4%4uumT z!@%_l1fEp^@keta2t=oF9>(3{y9f$ohVel*R{Ps(c~uNs^SVJ2Y>Omu+nNsesl?SZ z>$YWVbDLQKq$EWJkieREH>r%722SU2vKv|BA|FPj4Q@Zqep+9CToytB`Y9>!3nc|U zg_2^Yz?v`o7xW`UB=Ge!5}|TRXTiWa2*ueIwpM<*3Zry79SOLNA`wb5X!95`_Vgbo zCw@gtcG>M7OU12XNP<=s0>jq6r{RptO)>P_=UL={Scjv-#NHIDTiZIu#Il!h2C5uz3jr*)}5K}B81K!JPLs~@p zDh={|w)k!3#Q~w0YD1e0J5AHVPem%_XmUi=%PuH7yQySaOaXLTYZ(d6r&ZvM91v-r zh7?b|U4Fk_mtF&p8g-pjkIE3vSA8lk*Egvn^WzeTrvmZUue||OFVCLhrH5$0*aC3p zUKMz0l_`97WhR#|&`uTS!3e18Xcd&?!P>8`@y zqJ4UH=~VHvbtuR>h6-2b*kyF}Uh3MISW{<3r|?`~9W#;X6pzCo5$ZlAn~`Flpg5hQ z+-|{;C|FeUdcFBFd%gO-=mYmlk~%}h=T>w=jN6WBga`O~f3WKkM22AB0Ig%(Fb17M zMN7p!oTWZpUZw#Nvsa7Xv9UDFBqxTruof+cMciH+h>cO>|4{dCO>!egm*D&SiZt22 z&8m!bW08-%iB$8_G!~^EJ*B6K_Au_>epB9WoMEgTdNS67?$^+Nvjzm`%NaE$OlE`DC;fFa1mMPrmu zROr%isn^Y+Atsw@M}zI;)2y88cvkX4y~uE3v=E$5h=gPiU#^)9{&aJ--fX2hu$KHM zOzCoh(S{_1db=Q@fzP*+P&ZN#V0F_4<0zC;agS!4^JXJqPMn79V_y8S=0?9h08CP3 zA%_}R7$Ig0XR$=6wjTH3?Yeg2SaBG-Au|lPg|m~F*gj@;xp=hv;0GAOnnr`YkOSea zwT-B+7+!onA$r@5qd6lE!~+o=NH&iEg!DCCi5p$~Q$|`|$(SmS20lQoo_LDI7BdG` zDK5<# zsL~*p?EH~kEf-<(%H7efmZy^4$@Y&jbPh{%U})_|r>#{TE>F*Y0o+3hc6bPfmpWWR z4hD3ZJuXa!j0i9%Q%a63Q>pZ;;3@0fz_v6X%ls(g1!ol}#gbq5vsvi6)!8l%&W`<( z%Et%~)i}x?7lzszVj+1gBvK(QT3Y@~yZ%O*UM|KAk32fwo+gs+qU!l70k z!pL;Aw-yM#bsW-U&C-}bPgqlk;;?Df;R4TgNaipAsBUzNXkK8r; zm*Z|of%kT?)%oRW_2s)vbJ~2rz!x$V;WV?F8EBrFNT?|S?-~weJT!xEK0mlh{f*Z3 zl1(Haks+;W6>(v>%nTl78+jyaihNzXyphF!id+njGAQs1GZNrvP=ve`nps1E>(z-k zC~)blT&&aS!24ugL^N>P>?BTT$!G*(Jl(E7h=N{T$`S&O7~W2BTSEgMrcDf7RHLCf z0b=Z_7)NKvDk!R*3IOhh295X1s$^6bHP4ap5pbkVRuU@%DD}+13V={Y|}TKpbl<2#aDdNhdNN_Oy&1 z7SjzQ1QmEA#e%40!NihzTUlOY{cg3q`Q5R?Sq7#SqpX!@z`56zc6rcdgO-=a7pTB9@HO| z8~08`BNPn6l1)^5&8xk+eFo%cf(Cc6K&uT4TxGn_PzG*S?PNAD(kLqBg&G5HN07Z) zSg}#f0wgryJ`Tn9Obgu11-ep1Y;V5oM;hTb=EJ}i!y?q$ol*chgMyTfkw4z1i0@|A z2;87V3W~?$OowkO9zO67c~E9Xz@xqS@)3l>jKqO~b{H6;i_(GNc)`~t7`REtL3HZ4;wRJ@jFD(Jj>W_GYTC(z%Xcg3oO>RYdcc!LB_mJ+XxCPPr&|4pN3*S@JMD=f zLPjDK3SdMjzUi7t5RC4A)NLpnF=j?L3d(FKQY5Q_q3wrY@OBvqM#X9zIFnEakqu5Y zeaQ3GrEDiEd)Hr;V>9uCnh*GElFe?oi;GMuR2A^nWb*v1_y=@3bcbExm7Hw0x90}W zYaCpV5y*hX*Pc{;25?YBnq=6e5+xQ4Pg;0 zac&z1M+XthGR~d1OZk3V%)}!5*=Ujdp)7+Y<18PoF0a?`*|@5@{_y8>G-7 zzMfoMcM$||Gg~-O2LTf3R^n<6Mu;3i4}~BNu$4OQ^ogI&aF%reuF;hP4pMfc+Q4c? zE561zRAu01GFBC1!;SY;K8vpLHpxBGKbi%7MYDWmo%Q(I5cmxB5PV<7z{J5dG7ZpB z3>2e$`tC_l5+Ou11wx#)G5SlKHi3-mL}h!x!<;N=J^A6fs15l=qVP+Lq?{}qhUpAW z7x05Bs&r`JQXfjupe=IHa~X-#BR||t(Ipautd9gv8xnDCX0TTY0bCstSW{9Whu2Ff z9{Sq4ub|@n3=9ZV@)e;Q5>Ib%L;hOT4d;N7rZ}p{)Ibuvk&?{gxYO-^(fJ3GeSQ;UY#;GKYS>Muw*1AF`_?o9dGLlR`g-xp z>Y^OS14A+lfV`On9&L;Wp#v^5M&JP{+tQ{31kE*?vBQ`~-0RF94|IrPiPI(#aj|Zk zQriefa1|V^*jSTwg-~?Mr+p)dAO!(ZM*-(#jDC98<#s$}5-zKWfWKx8LR!HfzF9s0 z*Jk@^@%&02)ei1o{p&f{Fa-B!a7QV)pIAI+gk@BONSQV2TBVdG?tI?)4g--)n$+88 z$9-JH_HN*e)?dBrw(e02?sJ*2ES@uhqhV5mO@i3)Nv>!Q zUw{Ev)e6n;79F%kk79hEG@9<>dud1^!f) zKm6i1&R`rKlg6CpVzClrekhmnVp<60W4hCh3!P@;AUCpFKis^-RtnpzGK_;&kBfBx z0q<9n0uNK$&@bHtVHYV5k31=;Hw0obCyqir46JO1v>cg!{slwpzBvm4c%xv1;2st{ zhK0Ix0ql`o8LMgb@wem1@F0%FXoBerQ_O2;IBK87!`=ZV~{KJgmS7z$m$ki7AObFP#K4V6G|MKWe^gLB&rem7zu+| zUV?na{ueSR9;qGnm`}%lIlO=G?qBa5{q<Yvd^?YJ6q1#iQmVNo6oB} z6v+i{U=qnlBJD2Wh>r#&Dde$!@$ud1{cetEUqV0*6#zV~lVFXtzuaBIGAi(DvXh8I z^0O%LD2u|ImGqyjB!*fQvtK(=B%W{T7|LL1zNsTnP2N(K?hmL4l1{r^#Bo;|nQdki zUM$aLP%U8CeE#yatO(-L+Fw#2lp}{BaKNeBQBcpAV35x9Q_j<3$$U2qg(S;R~vJz#;S*z^GH|9U`b~ z903NI7X4}QGe)6Yl({$Tz>okDYfsVdQUjfmb;zOW1YT|rohXrDziBh|j+6iw6Bc2N zZN?&)h>f@Yep$FoKTfNSMBoJOS{sM9A2PA0ag<{@UFRa6=jt&L37k$ygw#7D0l1uT zJh`|K#jHD$I-QGnglT3NnB+7QhsKI56nnBFEUAH8&oq#@9y86NRW#?oB;?;;iVj9+ z9zZeq)eJ-!uZD|iQt-v8m^goH^B}G>!s;Lrc$;RL#nGG(x6A8)yHFAj8x@FUyFM_zmfHR z&Nosw@5HrF9^e}cW%IFJ)6ksupmQftc;94}s!aG8eoTy#bXr(@a<`}i(Y#5>Dy&T- zaMgozU~nU>WngHW1m3CJ&hMIrY(Lqbi{8dp_r!&@J<@>G9<9{?V-5hR0&D}$@tCH+ zI!G5*5zI*tX*|7Yl~x~pwyJl%@hkqIO*J=z&jJf-IOtgj$DTS}_V~M*x4d%>=mHve zT5?&T4*@?^G^p4)zWpDvIGM}}dHG&Ogvg?fPN)uXyE>>NE*jqULm3#hS;y_cD0UQd z4qUBd$oQfI_h^JJKAHr*7IDdFUHpJ*I zZK(#+#R8`jGlHFDEO3zvW`0^q*V6g=)90lp1!D9X2^d>a@8H1!ok8JPq1tc@|5cg@ zNUeIo8)pl_tN&UR2;;2EtQB}ghk*{0`q$>B82tdqYQvy28>&H_s+fZShqtN)q_L5z znY1(Y8Eo^wg&-1u%BT<*w#hJ%6wWNxpG6@a$s%de-RRih%mTkn-UNr108beLfrlv& zdNe!jOFm)0LFOTtVbfb(jLL<7ZkJapuMNPxCIkLz7=&^N?q5p-X-j3;6N570%VZ$S zvOR><7Y{Qs9Cj-JL?n|TE${RCj= z06c9M143x8@%x`yCBHvfCI83u^5f#C<-eJ-RYU9l@pgI|f$!H&Bj7_#0$?*nd=|}( z`I=G8{`Mz#{Xpt_YWr}q9O~_g9nmM`SgELvvxZ|qG`Wz<c!Gwl70Jp6WN3W3|! zNKn9ugawVWxp22w@6C-v(*AW99@sntjgt7uQ>YkR0ugA1%@1VNP}xpZJf!5}e#^vT z=M%t2L&JH;5IDEd$c14Bdh;vfN5_6J`HvWW!(h3E#d8@TC<~}wZi?mJT-fYzKxTRke*>mVxbRmjVUJ@X9htqG)M}oLE_~M z0W`Zxs`nJia$j~=VIZ$E5n@t}(}@WI2Q;S{D!u)9BPLOeHsM1J3TG1zia-WDyCws_ z-fbf1(q~X$nrT#N5->^wA`VGIW16)V89OW<J=kDG@Xh7u7fbH;o?j zXtu*h<54w>QXULtyv&Gp+}!n=1S2O8=o^t00ypnJmBN5`t|Ldhgz00bisSkpj{)1J z?I(!S1Lvt@=%wHA{o?P--R22B}s0n&B*B18YLC{1tno&Y%{`vLlx0QH@ z??o*E7uBsVt)R@GkgimnBF{El_etQD5LMx=GMQ&T{{yWYIz;Xx7U+!g!E3cM87D>1 z;grcJ`yd{^Lqz|YJ46nK@vk}qP+eILOivE|A~g3Qlj^1q^)O#~uh?3eqY+e_i)@BT zkYzInf~~mAmK*9mnL&e$bf=@UgdGStG!S%b4oS#}dw$S*Tugbh6s{GlrCMlZj3>bw z*=o`eFY2uE&GPcg$Hn=w8sdUq7&iDxoy{Ef*WSeUuEEd{cCJMpoZdTjR_QW3kke(h zyhwq-nG~x8eFlM)2@o6y#7|P`Z0zNbyF z0r0>2m1)cA(rAI&g&qxNk=0!B!0CiXAO>Ec8M4q{zqpa5n*nU<4#799S%I6F!Gs_d zXABG+8YwaudNiLCMHruPm_~^J0e-F!!0;Ujt@IW&SM~T1) z+_q+feq2~J&d+z*2i;|N6ptxvh)Zr}HlQ^QiWjSIGRx*gSbWqQ2b*8bj147lM+YER z(P0FMT~jJbpW$R;1{z%-PKxAM)|L`@vs(Kyutx(8t$7hLeK4Trg_;C-p$wUA9HsZ5 zZ)GIM+vRXRtgwM5nnuJ@IDTl}6Gs_`m+R~I(ib2F@oL2t=RTZ`5EjjLLuX-|8MJ4r zCeC+kBr<~^+^ObavD`+keDLNOkGB8luQwSiw%iqfVb8uNL7Q|#m-^e5& z)Jahh{mg&xIog#kj;H4>9m(vDc9X`%be={ zvnU>%-P2Ue0!ci+kzlc|Zjyj^N#P=!ZeJXZ$Xp^XPZ<`C-z+EtE<1*xk+7ONe$+4l z=K<|Rx1g$#Av(VAdj{BhV@al3#&Bg84h4bvT?(heYy!M*QyL#{WNpv2Y^c?kp38kD z3?(SDp#(o>AccA;E~*0uZ6Ht?VaA9+9-N-|v51`RF{3(u92A1O(5isw#?rs6udmAm zXUOwf9*Bs#$`C(szC+CpIyVLldzQ%W-SeHPq!`IaSiNm@*E;QXmEhf+vpD~9BlERa z@803Fn9+2GB04sN91C=4jrIaV2vCA|z&OYkD9mw^+q zk*h3Y|6(PpdwvK@+57IJHXMgRFY%?!Lhvqz2Va^x!EZV{mOAJy*cQu5w}D5gedy6_R;1=>SWWe%P-x~kxY2_o ztLjCePXMbC^ym1x^x?lwX zXA%g(eX{9vpA{FRBkdt4KRB@j=W4OUO$>sWITkzi;Ya*|Mvz1 zwz1E)gTeU>$AtZ1;A_u@fnQh=&~{H|!Bhlv&Aw+6B|{$_0C?Um9+WATWi(Y^(xXf1 zsKQFmW8O*GzagP20o?gip>JUr~+H7NDg( z>0e!kQVK$>aIQ+0rKcPo=R1_5NTs3GYM<Ku>9Qcru#r z;BIv+=RCI^&KBv?SJ8E7Y_Bqvp-O6AsdU`RfU-n^g7!F-ezW;5OE6!RQ>KrcW#X50 zjuWR$Q`KvCrM~(WSxz|+O80Faa!uEtl(o#bAa8A2-Je4qq8N6fJj8ZHzW=~GZZyZR zAS~*yLI(owS|g#_v;&D)997H7pSv70?RcSnJYQHE20nQLqZ`D~G;$F0MuM1!GJU+9 zEDQiluyB2iMSnd>0AM;n7T+f4J1sVT8NnfC14;_KeZnDxws^Z{Dd459S^xHC`=#8! z45~7!-GLT(LM;}V7OH}?r6MW7UM{>pnE+5Zm3Y3AEe<8X_mhO@1~E=8n_R#nJJlg| z(d}CK-OlPp5aUN`7HFd*L~jaxXW0>l#iYrN8S-n;eAY0SiWsIkRb2D^m?`DC^%_Q*5I<*MS5-l)NKt!t$QhPqy zXpBk2-QMQN^uF0qj%*hfVrjZa#tlIwyPH|yla8unESlpH$wD$1%5>U?{Be&Ms=Ms% zBySP714FBtgwQ%jwRZutmR>K+GyMznC3LcYD-`Z)QY}!Kz#{hLXU~ zBmzg-adtPe@ad+r4+lo*P(owtU^0V&U+zJPUWd|jEDnSJ%vj(a9t&B?@%tifa)QQ> z+D5)Z zt$<7@J7yb5%BD5fRGq}_CaFYR1R3wpREhS!9-w&*fisJ+%87#juV)abG2ora zV4F(*VNGlVeC3}i=x4%H0R+p4GouZcz#o_gu zZkwwprZMJ@mxs=CQ=3$#OJe$Em~ee9)4^o}{) z;xymNWvYBp1UIg`T0>C;pQsiJzU`lo|BftJ)^#9wH^&I>iTy(6wtBljtBegW5krB1 zs9l82w~YdR`X3xD77$>&n{~qFcMLecTw^i=z^Qta&^*I{hfNI7+L{TfjZjHNW41*M z)4&-`Wp_b}Wp~jwh{LI~lE{L3Q!E(JC=VvhUF56M8i3v&L4FxfQDkP0Mu}N(mkbz1 zdVX_t@!O{8Q3CF@JmN0qGAH$~W+3!vHW|unhm86GIUnxh6yd?;ud@6E_s)BE^Wo#l z8wK9`|L-`2{sT0r89vzp|eaN6J}|>AMIa!vH2fr9_3H47)A#UHBLk477B@fQ-9yz7tz&4E?ilK|HD}}aU2`5`_?8lAGCGgKF z-`t~zQ9~uVh&^Q?)WUcd>H>ju%}*CrMUay)FpJHmv!r?C0P^MX!}@(|69@dlu)(2W z2ArfctVnDT~>yR-YFS-d)^W{`DuZ zoTc9ti`5iE1b(y*AVSWGyVr;)C2qlvT>7#~;U4tpL&6X{)8pZHS-MFU@si!rT_{C6 ztI0v0Xe<@nC(hJrk;$=V9Fa1~*pq_Cx#XfBRmXu^-2~(L&GvfnMwUu1W@|xL3LrT1tvko1HaQmK+J0?(^KARf)Q z8rRn#XwzX9eE(^s82wT98|8r399K@3irC4BR%k4Nw{l>lWuYH!HnJ6mGYtyVsA+^^ z4BAk`p@oMXaHx-L7h`%*(9*JMW-(D0g43Pkn{=RT0#1*`@m2hzc&LkAVG)BCGGJ;20DIXj>I;hsi#b>M$z%Sbfw8@YK zLKTx~hW;@RX&pqaGaaOy43TYP5(<+?GHUty{nKKIvoBEMwNspxVW>92alvfWv+`KB zs9T~E`r8UgO*_neT!p##7AedvS0n%g?I_m;HTX0s%FQ3-gg~lVsT#R2GR(SA6V*$d zS{-TZsozU5y?9|A zAHAPu-z|sI(Z*ZeYv`p?5bz1iL}RQ=sisij4GW=?5vC75+^n}n)z~jZfM3*sT?lpC zZ~$ekjyWskM|GLch-7Cej^T_lV<1jjsNznSYE26f^PNv};wa0gREg&BHPkc*CZ0%iMFB@wZt#yp%m) z!txBrXGgtxD4e40b)cJF*nZULFMEcs?0i_}P?ZrLhcZd#2kj+0GW3hTUTr^>)A9X$ zV<1^;crcPSH}KQj^*@%oe`@=oLA-MgY}!T=%KIO|x-QddvH(j|T%8D6gD zLl(#S(3C2z{>jwVDKva_j%`Z3SQ9*RR19H4pGt(}gO) z)gboItRBlOmXS5S92zoG}{! z-BV1h)Q6IvD7Bkn5W@!5je*<;C&j|K_wpbF9Sh|oA>~7s_u}!bm>S`RvVc)}nw?Ul zH2lssY!a=rZ+^l|-$djqTIOe`@D8;WsM+IUEu~LFA!@`F-(ryZ&HF&|#tGIB=B1hbP!F>Cw%0RZjKA^@anQUp+DW^+`}pA78zn5nguW4q^2MU7;r>ripMIgkv79^Eqh zr@2ipCx`wvynLpuZ2)EaS&^mzFbK)5k7|I^$z6sSD_u3v;f1fnO?X-K>z)Jh+Bbyq(9W{@o=Ehn zg}hxxqTkba9O^?|AkgFb>_#HV?Rw=U!B6RP93d)3kx~>PqP2(6_R~XBNd|g$ONkKH`_?7gg>=nbz5gVe^SM;ZcSF%RXj#O`MhKn2x*7)!AOC=;0gv<5 z(-dr~O5!z*5OGd;whn;h2w}^gOU>}Aunr(z$B>Q?p@Rj_u7S8UA$lV-cw;9MB@m7f zN8O1V0f>jI?Uh7L?0WlBmel>ER{)v32@ab|(Y6l!Rjq@cakfxo`?uTz_oGlm(M;m1 zv~b=drWe^f0BD02ud5w)AWtYUOc_r+`RwzNmLOSX(+>YRRvEeiPzcyn2drC)bie=9-*7&~^0GPvt5BLxQW70CoR^yOyyNeo@Q& z{J!L<^_u4^&yKXwaAS! z9QeI)C5AZo_+xNrOWN)%b~PxGhNd2rGtTxZp@uwwK(r+6(dG9G8A7pL{eD>t8rVG# zF~q)YJn-Bq_BFd1+M7SEb-hG9j_(#-FFZ}n(4)qUlp{EuLch@W0^X|0fJn}<=~^d` zHpmD(K|m_A7yP!4b^;!4$Ck}S+c?{?mF7ZmB@)vdTS$|$p-@3`wdj$I+Q%-dRRE0l zA4MUWiCkY>NoIwr>1-6zusW5{oEvJyGw@;9chTtJ_z>5@?eam!ld!LNTd)BSs@2oU zMPP&TC3uNcJCehleGI$AA_e&|<|Dy!s1rsHpuH*P3#`Bo)im(d&NSYxw$d+&c%(25 z7yII_wI>PvaJZ=E1Yehqgrsf@Ck#@0Ao93dw;6S>zeR0i;6xT1B?V3=AcAOSP6Qx+ zTFc~xq}PDiasgguq`F2|b#NgmNWQWx7m;_Gfz_A+kz}G&DvP;caUk;3gJHNL&>ne~a+{ z%!<0i0wWc5+Z z$KTEN{SqH1>~uCY{fd;q7uFpLc(YUv(55rj*i#Kk=b$MZ4S~0I0G!&8cJq{YoT*|Q zwZeuol*C2OJ-k_7e)+hNiCv*k#Ig9bvxHuz*g8ZFI8_URLI5=A=Gz`(m$xjvgia}K z0TSH=@G?N+@B|pVl7dHBAGk+ZHDBHN35vq5Vk$(}z`&t#17AVaQIF=T<3Xy1Y1p7$ zEx?v2dt%sNZY2gREWr@E9&t)x;8BL*&E`Vt$%~ioA8nUKSs9P2d4azgFD9*;muayl z5B7bW3rI4(*?vaPN4ZoHRVt_VM2-TY0h&H z#jy~oms}9y%!z0lIBlee!?d$)P&P6t7(%Tiko>yxqq@*!0|GzY8ARc(1_at{K;SM0 zghRp(2P6)L=C&(UcMI>~2K*7y3gw@%>GPG7{7=$J>qHPuZ5!$$s3nre)s2y<`k2feoIJ-cjYNf=FO({dN*#-fB z{ed7T2PT8iwFtbH!yqltE$fVa_)t!icQD|v*2!w*j)nA9_kwJ0BP*ONUfq27(2MjR zKY4{O;39oAI7RwXyA}MDOc%&-`&#`LCyYWgt|PBpK|L1v`cMXnG=`gP!goQIN$S`;t|xkKncT-FSw%EIr&qp!~3G zg_Tmx`(?vCNX0V+zz4RW5Co;{NcHOX#gnTKm+P+|oozCiGi+#)8rq`4?SQTw=Vyh& z`F%(0pA{uz-ic`7n>jkPWvUD9P4TSS`Ep%wwWQdA!X&SWe|i%bS?j3q(Lzc1IHyh*^i zYKe4U^n*A&!L)&2kYT-2W3ID^Cc(Rq5qJ|Pj|ZuotVrOxqz%C=vNqr%7sWj0j&bMG z5&eDTN7wnJ7eB6f5&BTj{(X6|qfpq}RD%4NzykZ~)T`2RG3XPuRn2mfphEiq(NS0Bj$3RRugP zxr-1$F?wtb!}v)SgNSH>0|Dk!093{|Pw zp;aJg+n(e&IXu#cOUEtoF7%-4OEYe%(5~db7f5IXmyy#g;*!z$A2KdoQgo??EM*}B zUk&kV%aazBWQMtpc)6M&`l*N1V0MIkaCma;z{pte#+e#J#Z|GuH%nNAxT@jMn0P~+R&##Oxaj#lE@mC{7unnB4LC8WuottkPr5EAtNC!O{aEx@o z=?)U0WK%ZZ%D5EiW|LXbvSP6_4=~S=VDl@L2z?3ApBjm*5_=+%vMNo($u1(HM*|Wg zd)S2vIvUdnvL)SET`pdKTwQ+gNe859loOK-1JC_|aW<`sNbttH3?n$v3_`Gr2bYUq z%6-ak>1gXiR|>8l`{YeUbLmbLg<;O$u5N~H_;Cf>Q&{H({CEH?=Np9rUjB@7Fjm6adWGvSy1=t3J=3f@iwXzXq`8X34`(3^okC_ zojVk8z0pBy0?RVMYQNs6)TH+8GV zbcbN>vWrkS#BZ7ybmnB>+s%Oh_aB462r!uSv`Amk2}AHntK9s)IdmbQMr3Pf55so| z;N?@4i9@TM#wb5U^M7V{!r^Fl!auHM)$5;@|Nb|CtOnr!<8ACjK=7Y(iiw>F$H2+H zJj5?WkHA>jf5H#ejvogj?(`v)ns|>lWu{jzly1v?s2cFCR1S)if`w5Xl!LYoT?nq; z#hA;!He>h{D30b7c(a=S>J318Ja~^Dgzhr8lS>|P_2(=E!6(SJl9#eu)upUh0k$M) zT%_d8z-eJ9g5j<;5csKS;S4sYZe|Zz)_+_N0=s&AuX9@Q#C!u9k$hxO1-wo(!+Icl z5ihS~O_wX_kn_XN**|64=pez3j4B~!XmcaG2Q`)1?jhI)5T0HHi3@n3AqoSJvN&SJ zUm1MQUF#h$+JO_{7}Rn1dKF|bhav3gB9Drd;_;bhU=aJ8@eYaszK9E;UoK@wt|!ZK zC2V}}ngzH=5{xoP!1ZH>0qpirh;r>`l?4b60quH8yiAIgx_t%2BQxRQ=ACRcbXAVG z$2E1F;CK*X#?Hc5e;5&HTN#F&mDa&?7;jHIP*)*sbZp?Pobzj$*;W$5)m^9sRi8=d zYjuRcTbkn}hj)$4iEfi0+f8LnU4|Mt(hJRyhLBg11_T0M$pP_5HX{BmyYAk|tX?M# zbfCrww5E=n!nhm<1fFF;sK*4A6_Q)(Bj0L za8a!f83mxUhdz`(GwZ_)!w9vOE+0fD-!J~Y{B0?lF|QpV+P;FIsILg)_h5O31THc@ z;IOJX;nJy{iDf!PU}+S*ah8aFA9M7X_+c}2_}g}MAyZ$aQ&p$FS}X+~k9v#{H{kv? zBQg&BgzdN*=nF16bIRh7B~aBti7+H)83^7(WfCwacM;S9rxXMb)TzVkrEIqQQpWDB zi{TfJ3}{Jh7NG;pfq+9JgMRnnrDubX1yo00$em|sL)TN_4IKn&9!@!K5$|5p0DrZ> zL0o3peS|cw>O1=T)rG{TOIb(YeQ!CZzrT8nk2s7H;{@iE-%af^;t&y|A-G~+K$jNP znPDnYvjzI(t}lw8X(=4v)E2C9fqywb3@X-0?wAFbLGnxJlnb`H;g9gvDO6*Rh<&=R zcm+SX+W9AZr38Q+svhm>pN?(NmztsL9|_VtY^kTVh}Sk-JnnW01W&C*JilIlSzc{^ zTfg^{F8E-B2dLE+8EWBeI1~gxw-9SpM9jO>3US^q~Y zc3!w{=u+*t@Fh$}gX_kf>-dH0M!&&tINx>udh(3MQQjlMs7K^DtBF(}1S9mb$InF~QHDXPgXLLA5DwSA0RB&49it9!j00p-nG@1y_NWX=b`q zNGX2E?l~@OXj6f9G4!UFX`W?te`NRJNtlgvG}?Xm%kt|-36U|^>fw#7uA$Y8svq%} z^M`2nkY*k-VoG`D#C}A-I|lufg^+&LV17Rh7|bvRX&XG96+OT|KicT@*w3~&gZI*e zpU?F{_1T%9-p?uNY7|9BB41J2zyvvUm-%3ken10f{@EnpnE8}xYL#rZhg>QB=_07< z^P{G_cl@%+9CE1u&;>h9=q~LY_gteCv?2y>6;t&jvS3haqce!0jeyT20gp;4Mb-q? zwdW&oYD=|Q-mUF#22ZxFimD?hW9oPux=iq<#swT@d6Z!K*I!Wyl*KoSP}+~gBLY=^ z6=!Av0z(6DoUB6*)l(=#=7tX0Y?xHgz-AUjr>2@x3cR_NE$KIY{t=KfPM`r6G4N1D zvfpO{9wbf-C^+LVK(ay%_22etP2DwuNw{nvoxCD=OgvpFL2_hDJiYnaO1RtMbl*5| z#^`_t7!WwT6&(hQNC)0-W+2&Lz$*ljGVN%4x%yhI5WF*T2$aA})-sV33r7i+j%lON z(g*@oi7XP@nKrO0hSWH1DsdDjflt;{2^nG_8gX-V@vBVE^J{n=wy{8q%$I~D3#>CN zn&zQiakfaL;)-ICedjdG2x6HngGbpiFcVf2fY%E8cMz{#otw$)z3Hs7%x^5bcNYBZH>SWjSV5D#+jN8 zO(67-(=4N8L)Hj9&IMA@GH}`~<1kEI#VNH6yod9jvN@$>n=%IHyD!MW80$a+H!&P0 z)uH&hB!o2{A;26QQu1VRggpL7bRh)1;Or^!%4H)V~80N}>MLE+r1$Yj`h3AK$@`8PpGzxtSNEQAZv_ z{gV?P>fg`O%~f_CLl5rWyW_k$->MPnGgdZV&aY!Dd9msC4Pcrnc!3MBk8ny~)eH5V z566AYhih{f>YF^xC1zPP=bSHPCU2c1nS*=XWd|k?B;QCxCq|0R(6`%7vC^L3{fx$` zF)m)h;F&ZIgzhE^w=yMjf}gS7-`y2$^2(u+#*(RMgElGL4_ydwFwNLbHwQfbyuS7w zY;C!MZYEO=IH1L~ZYvqU1v0vw_vC) zxmtbsF5PTlsaibAtj4jz!4YbvxX94JVHNVz0kzQ>V?`P1l4R2>L$RkZgMe@2KzJnE zBYs`Hl;TNI8v>q&0Z^+$n$YD6y4Q?QBcNQ$`F*NmR_ag>HnT@5R*dm+ln4QX*I1=j zwh&ssTP<&X$2YNJ352pnfuzvDF8FaUr$#}oA=m|OB7o$GY$yb(U8phT)7`#!V(jc9 zIeoPYJSN#i5D9|7>Ilh^!(16j`$07G@>2TO92kC&L_$zk1p46}eoz^P0@Dokqf97V z8IEobbbx~frcmIFK@qpZqcF6f(77jXSIZA&hb!)7#K57M2Q8&p6=)Mf8wFzMQ4@(V zi5NH&Y9m1_W1x%E35hU&6|GSl!7DkPkh$hkD0+5-b*&sJoK83(@Z01%ri?M>`_hAs zP+|;-R1#F3z)Q`DbfZpm*#Mnb$j}VgjQO`R19W^K3Dh)*i1HygP@JiSBJ%;~_mInE z`w9czL}AdQ!FnPeaN79L4XbdCqk~OM`{m>Mdi8O&^g4knYuj*Gh-j99|If=$*#7U& z>{bv{8`&J>;fBk%Jz8B}_trOi`13gknY$F&4UqDTiCqeQ!C=^cDWqH9?9?fP9S)x{ zmqyzzSOA5a zcC2BSpC}dbFe?LvCcDjO5;$!p5w{}5XHXP^-;|~Lt|XSi==**x9lthrfu9>bp+Lln zQbQk97T_>6=b*!gw8%}dbVDz2w7Kh-vT~U4!agQAZI~Pfoyg&3z(f;{hwCDe=&btY z($d(s04miC!LzKGj0-vx8S@({iBJ=9^R;-t48>4((@+8rPDq44SeS8>9kvO=>!rlo zKKbG@)@D6GxF$y^q2l2+HxvL+Ls6L>OO)HjWU(OUG7?0y3aI*(WW1O28@jmJ1L?E% z^)eSl;QAVZkZk~`8UoVyHhFj{77R+5aRoAD15+Gu+SqUy>;m95*>L`TbA7$|WpyDV z`T(gV91aM_h0=HKp0uVx6yr)&jM-$kU-ZSg72Z@pvzC-~WLR*ri?G}sdC=;MP4&ZP z<(S5fS>U6XW==ATED`*O^Rw{VjpYj|SEd7xa>^y)lvJqBWlW>4lM?+b98asV@ck$evEW^%*QY|4 zTNegQ?nUKR_F*V8@SPxp$_ySbMhKjrA3tbl!Q`7anBM-W!oXV0b4jus0r03%O%MW< z36|zFRjGu%NN4q!_r~MZX_e!rXpl5b?O#cqDrkDr zBHY@dm0|!DP0-BwD|C1FY#ryc@#GE}cjY^GPy99r)}J&8e-7ia`BpJZjO`r`@?K7& zWI*BnzTxOV z7Wj@SpbMTWdvIcsJ@})QtV{7;CRY9)HnwuXEuLCO`N4^Ucz*yX?jGw}?&ia3Y!q%i z8U!br2u>Roal;gHp%vUdTwQI}*9-aA|60miE+^tFri`k1s0p-rfhTK7vnMZZ72o#v zIwK;P6R%{!Ua^kfi`uah37SI{+s+JRG}fPO8Ar!{3thj7P*jQgM>;}PZG&=>2odJ0 zWI}*7PA1-LB+h5m)N=tY$hd%KCXa$c)rb(y;G)_xfagvw>~bJIJ_O?!YLAush{Nj$ zK+85>Vg{G2VEkxV)B>O;wOvp!nxTFh3x2xW)B=X>R2bdC=g*Dw`xDv%Cu9uKm23gH z_@`vRSCaqvSkGxDer3#XXoSiLI8ZYqs|1CAz9UU(zLk;(GXJW^g}P6TztdaK5rsuveANJ#0;?ja29K3AoOUqSPVgE=yBuib^$T9IP_w9 z^?vykebP@i+lxXo@f?G}@d1A|r3jjdGh=+wBDtAdu^d}8YZorjP@=)`nZ0-{IKDSm3F@M=-J-^an836 zVYeyYn%cPpJF#yMIZT~P__Ml+6Q>5iUviEi`WY(Toq?vOC*Et`<4s8e8_kt(G;!m3 zD3?aoEW;1eRh$CCO(x&yzWOn@LoxP@Wm`@Nw4_M~;Xp8J7~WlZuKA{(8%_-l(85UK zM*!%0vJ~`li`cP*A2y4{X&^fKNy{aC0DCP5xKRomf`33ZQtJU9`!2E0*Jj!wP`I;Ng$F=X6_Zu37K)t2sMTjU7;MBZO&W8j%Dn-tdOe*w(+{|vm z#oK2~p|w@fLw|WlJjc0==ig;pvX{&E#Y$gJJsc2DJc7T(C#rG4^(vVR1ZVrpgK}$a zSBS7xL^4<rBmO$9H5VFIRjLZh8rlr111=yLC5*PKKGgHnz#p1NL z)I4F%d>ACQ&M^WMXhKstxfS8FP3TSN{8#~(|4(Fe@E5BzB9#~5z8rY5{(aPF3vhU#M+ru`B1riA5^msD% zD`h6|C@Uq`M0~ycJ&erA5KtVetRi%;wISg5x=pfK4nrWKgkZ8k#6k>ys0I;Wc)Pj! zzIeFZeDlK4jv^cpsu5cj!d5*Bk1l0o9L91bZ&7-WiXakZJ@F+-PLe2dRyR*Bmh2-zxsw8v5d9dOFtzfz~T#W)Z)0C1FT z^W}*2huVl*3 z;zbDqZd6D4XjW2=a_}|SU$Y{^phvT!9Y&b6=4WXjs#I(ThLCXnY^#JO71; z*G1A`9c>)cSA^WpAjzyAT(w#UNuQT4%~ccD2idbtD0rVfC2%@XBIJH}EyW^O$K$oE z`TcS&V@_9N)3Wia8j+xzc*vL&(PqwNMkc;egLj!8+r>eJnH2#Eu73W@>g%;jc6KIy zp)dkPtZhTv4Nltdh?SXJwTbDyE> z8ErHSaA@9BTMi5qjV23*9HmnOI4**G%X$Y~`-Xvsn_={)Y2YHKl}|1%)~#I;VfHl~ zR1D4ff=oE0z+nZ40@H+Jj1pzf5=53)%Jb<9fL`yW2}KUdI|bNAYxW41l*?eZ9HVK|cRS_M#Cr0p)SV zYyzJuMU+_>PWc5pvDDxi)mvzsk)6ru*@-emy+!7NRfzJH<*s`iK;OzS@LHx2JWMk* z@E=Bf2xCezz=sqna72!o(N{FRGh_*X7z%W644@8i*N!|!X}}Hq!dLf-+3;Mesy#@j zyO?Y&qyxvM%S?rInGhZ->0Cvt76;rZr5ZsUSl6vR1|Zd3V+4{hc}T6?6-{O5s)-k1 zHKmMrz`zv`T;!DU`>a1jg?bWI*r#r%sBpPAk=LW~5QlXj#WnRDfY_S}Y5$$g! zx-?9poBc+}Hjwlh7;*bO+@eX2mz%FLn5|kBjFYiyW;iIqST;O+j2CJ(M8ZHB%shWn zbEA5}aQ@(Q;?$jf=`<$g84g-aPDFpYxs;J?izg@|`w{7W@=lu?A+o}kGCXj-X$J_V zcCsfkdXW`x=ZKLQRFx;W>^NdIfd7m>c;@*-;#D#tbBe*;y@!NM^+O3hPeGnDp&xS*)~6Z)GL|_>+6V;jYLE z*Bd4Hwsa@jz*~xiDg?x55)5%q#tZ{ZB*TDRhF-pZaIx6{&wAnxerO2ru-T)4Gc_CV z*FVH4&;&<>tTUBW9EL&_8&9ji%Q@d6k>O{FG3(im(2-Fx1lO?(3I%008E!RFC$9g* zwiAUwhS!|9Bd)`DM&s(*W8kl54%IjnAJbMv^~YF>0iNl%6;^j{SJWRrcmX=*AtTi3 z6z-NoM(`#(qg|L!igq<_g(>+VBPQHghYnAyOxmuf_lJy7`9KBrS08ER>J7t4;r@)cRF2uc?1^X2D-*y<;# ztUDyU3#m2K_WQM!6(fHjGFLoK9hG7l{zf{IvWmVeaG2 zmF&4)c80PlQ|p%FMHrxu&t5w*g#fg0kuK2ny$oZtIJIHcmI0|a;C?ogX0VZi`-eK) zMRmdtmWT`BLaUl7(3=gqD`ha=CPzQ!??-sFmuw}EW$FA+vW4PSW`a30Y2)B{5sU<9YF?w6gZv2{Se@FE#u*Kc_9m1{;UjGlFhJL;j!+yP&Z4!$3%A zHyC%!DpG!{YNXtI5&A>(B32g@^R3<>UDY8XT$^9QOS~6AMN??O0(aAc`PGq7c^*) z-Ngc@6Fb6ECfQ8{C}as_&h-poH9n;&C7Z8dq(g%97jmByT3REaJ^^NeU*0-=(&kTg z6tP%}cTfun82lvb?LNNwywrhz<|{ye+E<`OW*wm9kpQ+5h4O?cdK5^^=w`)KW?^u05&eK^^s^aQwdyO zLy#qb?>t{0%aS<_YJUha3Gg;f5uV6uK8r^;+f}*56RxT;z+bZ-lw(sYey{4~53Z%8 zbG?*0#$6EqK#3Ue!U?cIXJoJ_ny87AT)}r z!uoQ`5CgAPSnoKAPiwCEkWDw>;Sp1y=8adahKN#nU-BVtpvHk$H6V_}oL|BM3|<>;W>Nxs0AP#?lh0eEaJ61bC* z;xN?9{~(p6Wn%#R&y|S_c+EC~#3t3;b0R9zB}N3N4h7 z5xl|lNWVKl1@{TfmxUUxuZxvCaZ$|!{58vD8RuI(|7Ug_oQ-xIxK(8qFP5KrBhX*O zAvt^X9YDA2>w`Ec$-uFG{u4dq_cx^+eBHsYPm1=TX?WxUsna{fELX;;Dw4q-4$cn! z9swK|uPRw~9^hwII>(!*EW?M7oXwb64=-pR{&;i^3@J?ZoCjxTekE0W?+zkV20G3lkuR4YWUa`05TT#( z>af787#3kH3Z76ykzJ=I<+GvaF5ID@4D1MlFTj8lLxfO@0JLI=hu^mnMVw<{cwZ;Q zi`T&v?qWcMV5-fD41`k)vzVa~>Jn5b^xSYNOl<6il+j8&5Fy{noS?nre9Jbe`?6hs z_~64zA5XFURmnK?vEibc6PkxJ1C{f8pBBplI4DSiN-ZVdDg$j7*H%UK*Rtu^)%y3v`Auh}%ytRE0 z2vQ33h#BL&6WUb!lZ-jA(_&@xGpe03lQ+z|eQhH>`&zCwllR7-r?nvrz4Z2!J7-KiffXJt}6o3zcX&larBcb3_oRv750 z4*Zp@V(N#T`@zpTo446*%T{7@#$Ab_im4^J*+u;JDFPglkAB|fgjU2DS#l`&jYO9Ys7n}X2bhb-;G?OGTN z*O@-bb%sMMjAa}^ZHGgehb?8(!YI z%8>KnH4`C1XIlmW@8*DbaQWNvYVqJ=`Ps_@E~@1Le>I!v^8v7#&&)ch%$RKx<@6k) zN zQzBnj8%GccG^gn_9lN!cG_KJ2NTKC}Vw#=l{r}`dJu_e{Zy?{&Ic! zZF%!aI+!2Ih)K#}H5&ir-{ZI)e6}=hr<~*p@Yd}FC;HvnoeN0^R4+jNX3=mAh)_z- zK;V@eHFUV&!|m#(n81T))`69?k5HQc;cCIq<^u=_&>oq`y+c3f(^YAra`tNz0SGp~ z6?0=G@^?u`SKG2m3P`?5^+mygXwNhjx_%-KK;@+v>$dF3hav_ltc$<`jh3o^T zngO>=+mTTke_O9)LcG&VO7&=n*{>y|`6d-~`*f&kCQYdrGmg@k?qeL27fFABh4L3K zWIkkJ96v@SG9`X65R6W8RMGYwarH?2sHv1pIG)N*Tz-IlTOR01n^h{DspWw$N-F5l zTq-C__8GDhL;;W2#`7qr7!0RgZa%HPep)+Iz*V(6;;*I&GGeJrFNSEo>PYxy`SqiC zWf1M*&E@4v;#s**73%+T{t$m3x)Znndy4d@j?ibx*}elL#7t60(5p?N5hm28R5>CW z793Eo#W5^J56|6V4mOYb94v#Wj2l%poYpX(Hb%n;c#JWlD_Qj8S+btWB88l-P}Wd8y-ki9 zp<@BJH8_AwMGc(aBckh#y;V#Gt)a9lD{v--Frj9Mw{M0pFPB$0*HSsz_$ijS+KdDD zG(wx%p^$~ouB6ZLyYGi~(JZdt=`+yyniXVl4C@V&f!D+5wAv8c=ykGK z=_+GEt;r5_xQ&Gm@9BSV_F~%Vu){bF`wd%k3!Z;PL#t4x~ss+U^5`so$g zJ)~BLtaPN@YHBYEf>)D8*sBph7qp}moZP})mRkh+$yra>+lUj{06$+l!QKP02x2OZAnOGV(=}cK_fvQ4o{*if8^$Y;VJi|#? znxb1PM{ipzfcE(Wtw0NbaAC$e7 z1yU1QMN!#`{hRDR{%Er)Ch`G1V+rnBd&(e=_zrc*z;8#>&}E2bhtdb9#f(OmXOeJz zC~(@KI1WK|7D{{`CzL;5ufHxHu4Nnca=i|}9zI%RU^rd`TC`0>BLJ9)8cQ>(R(H_u zGYGUGx#s4WA-%%4ZKqf3PpidK=}aoiV%|g?6wYUalpBpUUH}!dkPL;!hqIwLzEkuX zs9j7>v*PTf%m}=WW5k2YcdPC7;(56r06t$GI1mmEo3PtExxE)$rMTDkhII5HdC6lLQ;}3@#X?@d7{DshPm2MgrePd7(#hk;va# zT`BJ}mlWODG8hPpl(LKERndFmfFKWSpaw8e0||QB1_FQm;gPkXG0-7{RVA7M0=!K# z&(ys#9?7KN!V!0l`2*@ z$|W^Zl#8Yt`gmQlMc;ui9_8ZqH43Ut@H4t1O-Qhah3NbUbSG4fnjyHuMKu_i5v*R!$KvjNU7XcI{;b&~SdQ(6AW&@-PfMX)7|S*b?wde~N@QVX&>U8t}0Wbi?47&WJkPMwOedzXvlqtGP@!lKyIqcqJF&y^#qk ztMUi9mwM!*%+|m9)-9U0 zkqAAGZ42QGYbuCWZ*wBL4}Nb~J?^dYGaZc}82D;UG-Pj8*?#8w`s3;wd+V)$49se| zpsh4nffuV;L46|@+Q8XrQD%aq79%_wUKUOYj*2GEB%uhA4c@Vd0fW1G0WdtS#sGIQ z$()q_Xv?9PipG2lips#u0{Y|*l?RuaFqzAwIs=|bBkND9@35K*-Oz+ZP}JW(T+G&#f`M(gWH1l)4EHiRw}yihag z!RlW!FZ$xQ&86>O!}T=}@K=+DYSffVZT`qEg?nLQhG6qwc&inLP>2R`I`Wn56NLZx z%lY#CR*og(V(EuAUk%qffA9zd$bAYNah}qxiG2!OOwlX>Mr5_2<7^5i`13?}8L4o3 zR%Bbesa67bQ!fo?I<8Bd1>Z`&MUidcS4xL+_c1)zK06E{=^&n$5`GT0Hz~=8+m^#j z%W6HKU!$AcGr=gUW3G;_-Xhyf%0#sJ>>M&@c^nob$QFUtIca>e`tU*4yMFs|b>n>m zXTcjbfXT3d<{LCY8qsKj0#7hPZc@e(Aw?F71m2(n1e@RLPF`J4z9&8 zgfi7S0)C@X8F+!~$9SO(nIA=+6mxa(^0Ox*bZ7ICBiV849F@1IIc&I|r zkF3#p;{-n)9f(3Qn-jgN3|_=>g2B1+0cR2fQ*%xBJBnP&9&`=^e7jm892Y`Ji8HlU zW(f8)i*lfEPP2Hs>^P!S;&j3xVTlGjHEQ6 zg-r)xYKHX)dqzB4U2Xk{Ej+8%1T@tcFcSp)vXfic$um9ae@7W=8actWS1$C ztNPUbes!^4UM&}|m+yP4+5Y|2W1x^TygR@vWq6Z2)L^#8L>wmHq&W>D3_onlXgRpp zgQ4W90|3;c=hG)eig%wsS65A!mj(LZ^uAB=7=Nlj7bzYq`lil!L#uBbvgt-Wv?Aqr zc%LM~B%i6+*CDG;Ii6f-DBvXx1@+6gR%d>;i(kzVCd}52Tnn7yIanp!F+8RxL zHBu?_QE~P?JyR>YdA8cVYxNiSm27E`)g;nTCC5cI9-8d$!ehiX4^Geg0#}FGVXIC# z=G^hC8#^rG=$MQJKD={MzbwDY1|o~M>#rBGTd33OBV1VP6rN#R33Knkh#Cyd?f}M~ zN$;=}w3fo0ame#r{MKKUA*TOCpv4QVjGkYwzbvmdzlnS~I7n@S17Mo%@X&eUcA8=KpjUR@Ox39IrVG!7W zyVh*T7{Ej=rZG5bUR!{#KWdz&UQm8of!*Maln{7IB1GtH#VN%iNaw@tcTvt?mRs~J zIYxkmH5ha#O+w%SV_+Bp&`{h&aLKJ5Itnzhh+qtX2fyS0zddZ`JnWYN^c`L&EIeRor2J3j^kzBv7a9?ez9 z{V|xD?>_#v{C*`PwcbCvToz`~1^@`=bgvvjXj#pl*_HNgv@30ooe2-2H7U!Q*pKE% zEkd_bdg0u{o-xyDF=6o@??N-Mv3prhLtz+Fu4-5&Sh*98FP#!Bw%RP7<Ju^msk=?R0X~CygE%vXfk7*Z9^H2ik28R9gM<$bEs74+ zk6oI$@0^tMp{)# znG@|;A$A>QIshpbF7>vm#8Zq7Z4eGa6pb%civ@mrt4ty-(xs+BPN`Vp#u_NlhE zWju6B$zVZiQ6z?HLm-0_O0?LtA)AEKVs7AcFwB5w0GVZyx65RRGKte27;sA2fV(-n zXzoVnU?2q)2DC6ajG007o^&Yvg?dd$E@1aY(XG0~Ia(P83PsK(Mm8hF*EG|hlc;*x zLyxNC1i-8O1^zg{>tINo&36)kAw$cYXv<``PZ=*$u%WpNA+9nAa99TrA%4I`wL0*t z-6Eg39R%IHDlp+3n`+5^;9n)NF4hBWgFrkiii(r|^Jawq_u~I7{^CZ-?KLau4pcCO z_mewOKzaKX_y8%s$9t>88rvi#L*6cRoD>o6eVTbY#=m!{BkEMKIZRKbs)+G9nMrku zSwf2O*>qzdgU>`kKw_G7I@*ikU_jFi1`X^fDay|IQeDi;a?_Fs@GiTIGpuv?LZ!<3 z#Gc*8jHJ$JC<>LbG7JoJI5;}ETt|s98;4S=b@@9oLA$3i5C`{8G%GUSjaN0N73XI` z@Qn;O%4qZ^C2^<8TBy*Zy)t?U+MlX)j z36QA^IrV2I`FUD&;8pip7$k;f)QDt)?CHvri&pog@f-lg=l8C>pY8+!2Tbkwwl^Ke zCp@G?<;i2L-)4t?XuLd(X%AQ43-2aLMnAyCmvKhYnV*HLl9I}y2`Q;DxG}mb>weya z!Jc|iR*3NsEsYRdrxs?vQ~I4M~c=IjNOAL0~Ba~EfE(9GxxTtKn;W-CO?wEmWi6NT-x& z@jvXlGqmumqj?tERoepnJ5&Edhk+vFI@SXQls`9G?e6`eKP5|pR?o91DYwg^ ze=|pU61opS=w{R)J?ozq4?f(iWm!;Iq5*W$whQMc!e}+L(eS{Bs`5?;2X5`h8I1YL z6eh=TS_7#8i71jdopOs1Pj`_xUw&S^UTr_gidGJY*_*Bdq8bS6NrJ8G*PiV$%jG}` zr`iS3%w`dhz|#^EA#}hgGYuR$zbR82zb+ncu9mX6Rfu%84*2&*$K2ulXz&E`^eTH7ohUM}B%lF^Kg8=azrgCn2; z%P=%xFJ>ev4}9?NJ9ndrWhnJ8O_gb%GDS9P`(LJ7!njYQ^! z3Mq{ws%K_+XLqUIOh)QXwiL>aIuD#NUL1uHdw9AU2^o@kfz?v0ty_KSjbEnn9S)lz z6icKRon1F|fchQ!wI6%0!^W3he3oPuav(CPX_@ zxbSi=7P!8$4u^#u=Em;us42USy^h3m7bW60DcLyi8csJQ2wN?lTwbod1HrefB@@4F zGXk$vGeSQ;XrMMS*L^snVWiEwOwYK2QFIab$2VDU|8n`R%>p!{wuqpS?e=S{cUS~1 zx;-^(m~o{HnA!Ypw-SMHnjkAkFMg4gw53Z$7GrZL;KG^(_^YvC5(=3GdNh{>xj?fI z38v1?5$_<2KFI8xl??IrqnYv2H3;~tfgllgnQLiU!_3EbEIpO`%OdtNBv3YZ;27CW zXUV&#=fB`9Wo&lY2VPLaC_} zX{<;{oy+=kaQCDb50K-TZtoIAcu0EO2k=P`UdX<{2|K(^)V{*rxbTJ5Nb&pHYpbuu zwRAG`5-Ek$#o6oMbB*A5nnX_F6SQ1$nPOrH~)7JGvL zZR02Pxhj>0J|+hT9#c825je6Xww-skQzb&uov;)qrl3U%2`Q09>-Ej{H(C1bT&5R$ z@9nq8q)sUs_;FjZaHe)#_^VX~cr;tGdgI6NP80Kl zfQa2GoQ_;W!8g|8Kp%=o4E<3yfjfn*Sf?Xw4{z!4pf9YV+c=gklLF6iq0IT^YV}1n z0KD9M_sjR-Dzgv}ac7v1i)tYFssut?H=7f^To}!9804Twfxu}KjM$No1%r@e>v;5W zE5`9sc4aFDUPJRnU0KL?aMv0L8Z&{=en$eqMz}aXx1y8plp6zZXmE2D!ayxW1K20| zoSOd0>VF?ZKc&{V^|W9&_$VCxY7xjVrku0kxO<`_FwC7&fTl;fad;tKPsIXW(piYD zRkB)sC}uYL!XjS1j(6jbGCZNChFS&;TNQLaYTPU4ZP8H^3WA#BicGP}P~vX7_@&}O zWU`^iPVB$_DofyfEdyU)8Ne@UJc6aP;ZPO;4z-!Nl95}ewBTK)v*IvRi!uuMPL36_ z&&cMp#H1hB*Q<}Kr8g4rXVgX}r2v^10IJgwJC+CyRRg8~N(A7lN7>P&jJgtwZtq(l z#ETWSOqG2juFAE)@t|Eelv%*f%TK7mq0{95ksn`3)Q>ktr%5{g%i;ZdcmI0lhxNAL zVe+WX@^7y{t}mAttIIDR*UQD(-QMy5ub=%rBp(PpI>`rMkv-dQE_H!wX`BV!9BX9) z0os~a9%tuq;sQ;6M&~_-fz;+aR89P6_RBln*B%D)K`Tc2%6oT<^|rJ7D02_ImI?$* z=R@v+rrT$yCYTU#B%B45ryQcwOFm#f1_Pd*d`311UA|4zj|zrFXM;h1l^b_<$9K87 zK}@``uar1#PV^*nG|*qPs%2?wKFLEqmFrjdwczk`!w4V6AUX^w2vDt^he`v!qN}5t zUJPv>6N$S=o)vhAMilBnhFfG+Rh7W$ghuFWzz1pijhD+C>9Jfb9(`P`wBIsO26s!0 zk;6n795zSr;1M+z8d(7gXC-s6I6m@qR5!axeaHaDK|d7>pmCFiW zZi;n%d@hDpHz@Gq1Vvac0jFv#lnuawpR}E+4`E_2+$=`%xV68)SQmYd^u2_cuP`Eo zgGU*TpKdPKvX$u*Nl=SIlJ9uIPYe%2qtrMnr*YsSg99u{5-Nw9n<_AKgNk9VN|HnQ?@`3fI@R!Ebh(r1Vy!9s6A^z|ui*zQR%} zvUcWnC5w+c7}_Y{dm9Qf<81rc?fT(q5;C~$=&W!$ z#dM)elAG!)g&HMrVxN5OAr6kw>130&0Z+4Ab&mutVc zldq#Yb3l7dAOjL@Dm2Xj?;@5la5+!D)*eU&t#MlGglY*xG?>xo*hFv@83`U`Rp5lb zC%?(sKDg1~Mdi3Q3H-F}D4Zee@7` zFGq&Q%d4yJi*s2B@B7N5fXfU7w%mp&%U|1Mz-O;@fgkQam)8V0iFZ^G@LmptCzt3r zSX4VjIuP)S6i(r=Sp>pGV<0FT%}jrnpS)FS6Z)Xp`c_2GoyM#K!C3^ZuMGl!H5jUx zUc>f3vt}+u`H`CWUc0d00r68F5k~8PO3g%qyB8V>Z(!h|zlCBBE7 zYh9EDDPQ}#drzk^XcEzz2Hs5~&}{qM?BX}t^D#*QN0)lyuWCEs8}Eu2M-}l_v0_|A zm$3n@AVf-?J;$9D;0J~ZblE@&Wj~y$LBL&ZRqZ+{##_5N;B+(DFC3)F;>H4bZpt2l zZ@??5EQWBYx8wLBnpQi9QV{&~R=k=)fk&84(4~${4aWc@vf(lU$Yz(}>MYW0GuZjh~W4rfgOe~THL@WX=A{IEEu;>~HzPAe}_uO`z zQ~q4ndlLun=~g)0uBV_20ltreKz2vqHolTt6`Lf0T1EzE8DLeij5rFryX`n)x_@nC z0^#$k?xfRK|9TEixY{t@GsSchYr}p3HWa|D)9-?(?4$$J59CtaA&=8kS50}ay@QjY zN7Jp~X^Z3ME6;30Y85Q%tunJ$8)hszWVLw51U*>Y2^ycWkj_YoOBANscQ2mE7j}eHD)O0LXry$g`kpRa(}{_}_s$R8i(bvH`rX21A)&Q_nUwldcejx0<+ z^&>(6Vqy#qo5ev~R0D#?-74h0TP%R(h8^R1F3yYEjnl@C!YVVsj%yZhzhu zh2nORlq69cVn=tI8T@p#6oq7Vz?(uAB}Fz8-Y#<@;(*hJ!%=rO5nj%b;>||-_Rhax zo`T={%tbbAzS;;66^TiKO*WA|ffzKE^+CK;bI>v40hLRLmthcN8oWa@zWa$&ksmJC zzgVSz*QrTY0Xul{~0~13yTyh8)&uafo$seN7E=`ZjubyI42$B6LA`vb~ax0iSMf zu6|t=`%ODi;Kzmn51V4?Pyo&qDR90MPD~dHWdS(G+hsnq#jTzJV?vI-DAJ&7UMsagfhhdbJSC?D4bFZ zzYHR`;7&4#>+N%o&+$ZCYgtss0Ln7tneQUfQZ<%cDDiPWg(9~<6-;Vh8AcS7{ZvG} zvU3qnE*7t4F@jH5McPZCO%U-@0|uYja0zw7|Ihl$;{ZgaLI48GGCaHa{6(g)6&;Iy zLuLHLpum^Nir|aK2$X70zT9k-+JcRs=VZkpRV=c}V1hc{z`l zGV9kv;anw|pm~L}k1z}cXKGSpC~$rcSD8MwLP3tri>doF`0#EH2w6;4w%B@bvHa|D z@WoPxgQG+r2VibKBMT-dvj+~nDHwQ$OSs!1LmXC)Z=v1-@8vK^b5UNeudmC+jXVx) zyz+s1p{8*dBFMIFkjwO)dlI5IHVbd!fCxzzUO8Dt2&G8_Iu5*>1A{$Gvb^7gP3SU_ zfKAttG`XQbgz7n7tkwvAH;p0W{0}5Vz#&QmPA4FOSY{w_kptqP>_#Xve35Z}{!*?0 z#?nviM$tO+9l<;t8aPZLl^m*KDnI5bW^SeG1n)wfm}DQ5cb}74t=kXl#na2p&HIm^ z!~_78&d`G;)wDnx4Tw;Kz>AI1LJ2Y#2-?45pe=8t6+@x3a-WCY1S>Ei=|r4>f@jts z;I9TkH7>;>c}DwwO_vv&ud?~$ht;J_fO@f%WVRjWzldvC-obZBaef#gg7;_!`V0o` zV}Lrw%FbAG6RyDv%NDveB~1GhG$hWaPmYeJ-16<~@o}E(Z;+ z$ErbkX>R37u|u4jF{c(gT-f&I{e@hDjlRTQx~t22fU)mZL4)deKS&jBY*^s>Dx}#n z$~`&t3mv%*6^2OJDzdn%S>SZyMaX>swFWk{i(D2<(@J05ykGk8u1JvO$2AslH5YuO z+D7yn!$$Uu@5(s{*+hW73u_`4IGx;i7(_$_T5-+u8`(be&GPfRBH;I)9CtM&&>XXn zP&{_504Bx?zAxh%l|ec%{bRO}+=@=Yv|Apa0rshZ(+Q3c=E0zq8a&F>c=&xQBTdh- z|BZ9!zCM8F8632iW|-IJMTWyvV(`bVeyrX>od<67|1$S(%W)h@w&?qOMOoXJorgIM zBTxV@`;7!CQoKksg5BKxG*$vi1U5kw2L!}sADe$azLtArcz8r)WfmmuF_T)^sH)72 z70Z`j;`XcC{P$qL7>32WHwRXNQ+Gft0}zbl6$x~%6VJzc$9qyWU9bI^0+?++#o@q9 zi~HjaCh~*YKr#^Q!ub`Wa?i$`2=c~#M&RQPBeH=6M%czrU#>4c7Dpigt{MtQiQq%< z>^}mDViy-JwoH$84?>KG*H<>+bsZb@?48r+YhNJ&QZ)qlrHMpv9c`;nlEV~sBZJcq zUA`vIZ(2p$g@$DrcpF84S6Lb40CJ2Y=*}fh8@7T_MY%4T{S%Am_pz}&x@L`)b zg~NGVZ)Jx!Y^1Vye=P&5*r@Ygy)gUj&+pLwTu!GvrZBslwF5FVqwFPcGkbvTrQA=I z5$|{Ce1>u)`;0;0%w;HZhG1#$$o4|CLJ=LZR*HE4tPpz-Y-*-P6AY$RA&QbZf*NH7 zi|#4L_I?4+Fu%Z1Jw=jCdCinE?rHy)3<8>HAZWvZfkqN@^FC>Qv1$*UpcVnpT>ud# zA>(N#k8w7376uAsHj7uXqp5C~=xkyicdC^EH))3tjtqEN!$CtyazXyMLs?|V#V8Cp zj@lW9tPTN&H{T_`zHAmn&}zei(x|nvkx&Y^Hp_5eWK!r1o3_*3W*CPL{1FNac$FEn zAZHh;)LNAV;zTZn4U8;-s&g5~m*3CUi>KG?e_j@&&weH{QK$fkQ8rK*bDiq-+JI2> zO(j%CoJt(|B^w+PQ!q537`~W2jH2Ol(GX|jRhtg<)y1ca#otjs{I>ph?!BmA?u$39 zgUf!^%!!6J4#vW+oR3C1;G#N-+v!yo418>&(XX75Bv~}{(3#^61{<7gfa0}k5EG3q zLs1#uuX&-($D8k%igq^%j5d-GGvASch|}571Z4%#ayIhvLe7;~{j)5iGWo4>pm-<_ zfe^Uq7!K^g9SBi`mgvG6Vby*&t^$e<0^KVS;YY!Rwg*8=%!33BoJQc%JP3uJ38RzLJ=;7@0>0ZZm~)L%6&8}2{3c(;-1P*SFsJ6AJBUi|zw z{N07NKrMx~Q!B{yaMl%ItdWs>foNa&MTTabgTM}Zg_+kP1*LO++@8I=)`{IaAv=)(R5?&{Cs=(=*ad-RI(Jrl7wLyI=cM5GdABTnJtO@SiBx=yad?l_#VD zr4+<<=ZcUVnG)DxYY|K6By}y=q=r=p%WZsc(=izE`3{+1SsIqyXFi1%=~z!tx)$q`mDq2aZGH1$%OJ3@|#M^7}> zUNUZJt8hgFZ#wv(gz98~2FS6|Az%n*za=4Kf1Vh#q`*5{K16i+vZ_#G<%c&(bwMuHQ?Cp8NA ztHzg#M>C+D?bWqVh|*EaCqC=!nYS zZdT`um#csM3lLVB=HK2w#@}70iN>W&bLt=pev^D9u8sX1T(L}Zg#S?rz&JnLe6;Tu zCOpvek|G-lqA^-wYt8Pl0{#XBnqxrp z({6miz6&)4Aj+j8qsDKA0 zb&$)gCBdi-YD_sKJSgTdX3M~He4d%mQZ}HGDUc2`Q8F9wD2K#5*$qI-Fe6h@@XXph z2D8BZYqP*#O(=Oan++*6)^JI6ff9+^Wvj>mW@Q%fxT!8Bu+CQk$THXLB;3HztRfW3 zomS%VkG2Y`O& z#+eoUc?faAP9_E3$f<}(b)o*gX=+y-xt5kkK|JRz?X9b>6Bf-8SEAV*-iLIJh z%yCr=7@pvk4vkVX(KRXE4km(DG;5S^S8{5~8`(SbqSz7um>CvrRs{di#zGOmi|@vY zO#Gd4%y{H-1_6jVO2{dm;x|rCZ)G2R#|U)gCH}-XridWqOmx6w$M$ zOD>w#F+U0uv#t&iGuxDmq?;q-;ej7XdYHpGn-r7dz6W-ii-j?GD;1|A)8kl_l|^B1 zCdO>J5f303?7A4;2DL{MJ^aW(Kocx@p$7rc^YO7s zb6k`{7B-_79?S9mdpRk}Pq?QYs>8+u<^(UmN5fflShJB>+M*j(=6&9UNT}}~kl5>P z^tJ3RBt*NMVZK{S=?|yXtZ%(G`GGWNnjo4X(RWgS%Q&I!hdAwsM0XA)UdnsxGT(KS zh$D*B=2pJ@dUYblrUHt3NhLrti*aZ;UWA1hPA2euLj!lYQ<{^eIw>zQEdm&HcxoH? zB!#nq68K2Pfso`cytYfHXp{Jg3*+T=ra_(tN`V1XKwZZ z@7YHKA17jjIyv69NsMP_XCf8#nNHlouQm@5tSy^pM2!boyv>1Z!((};?XRO)WE%lq zPBwKticG8Z`@Z>nkBzU~M}HU%pjR@1Kki^46L)kPpsd_zJSBqyLCp+O!OgF+>)97S zmljwV9cU8@1OD2k1F)*;kj+Cm-!zZuI>Fn~iQ+}P>qx4qFhEt4OoXM3T^L?&){9q{ z>x<$@NZhD43Id`*R`8&06twLSiQUMMRWKGp@H_`Y+i6T6O8m55Jh@)oaGb>g%{C7l z0+SM)i~!$w_NcN_Bxir*0Kx35BR)0~gjpzfho-BLJoj?(VtrL)xwy|L2Lb|UYi$XC z2m+J3KNvu_@lqW=U&waG<)8_^zOD6TdY3b~KXU}}U~~lW$%f~i9Ir2LE>7FI<&&RJ zz_L7y$b>K@ms5ukUFSn-%o}iHWsR4PoCMfq4g?0#*W6Nbu>A2+u|2FiMcP)MFWVMH$mtx%0dTLF~XCJYd;ZdiH{z&04NU{B_+PL8D= zKf=s3d@_cIA~QR9O4aY3Qq5|8`v3zyNKQ3vvf|s7OwaS@P`cR3k%E_2pqes&l`c-J za`FNlQjA5Ay;<(OaaN0V=oAkO&W3?@IGs4bJ_m~r>$7ruuQmpFvOI6{U}Gc%lwE3(-BR%*oq2(c4jF?QraYBHh9Fhf#-@7L;}Odu(C z!{KfdyMst}$?%dz+7pQ_Z^}Xxx^y(K40$%5P@|B22JSbX4Vmv-Qmekd2z96|5yT|x zq)fZ`C}SHp-X^wEAq=#)LBL-%y4($fVbEAHAf_}4-guivc1O^HEMWG@0vXoD*FqN@ z2KYrifHa8++VVS?L+LDeJ7R!$aw&Euj0z+^PC@1W(q*0-b#Lr`ssgVv5oGPp;_tF` zt_*g;>sZtc)N6adPus2npVaoix23zp=kx7hpt(5;+@uA=Fwj$lPg8{fJELOnLkX=v z$-G^M1lll)L^YzQ>0LFZ_;h`9y}7uNeTtF&wbQ<*&)(zFIqkcLVsuLTCXXq)88GO` zR0Yf_&mm%7>ksXuyt-g-sD^8VucDS?+M^@C(c44bR69j!n!j0=!I%puIg7jxucDs2 z*n@*DH{kJ=l=i_z8V8r7X&>IELCMTXg=22?@_Q^c=_h=MN?agEm;HKm@%fi^v5I=X z9m=6?hD|)`tnIcl7##GNT?xx;T6Kg~<2UHuI2XXFq*L8B7ITs-ywZY)aQ>`2riqLfH=MYiY#GaNu}+z1gJ?4ULjRcv%6-+Vqb(x&dJ-4vYZZanUHoDzzEzu2L0@!$U@JAePDEFl+;%hkVc^ztu9 z2frSy?wy^6HF(0NkzEacygHLrRV&$J>9n_-;g3H(MYrcvsu)2FsT3OD+|H1zw2b=@ zZ0rAJPNj}^O(P!qH0n_f4s4XbMT(YiS#q0W=OT^bdVCcD9+BD_KBN98(PvVnoIhej zXuk9-ddQETxopyz0A51{a7O^99xu6~<$PoyXB#YjxLkiOi+;>V z%@P<4RJG6kS|fbl$YA4#fNg9jQT0x9YE=!_#YL3lcF7KUFcd@-3v@>(YXD5j23}=R ze6ha%rFD9?-@Xq|HJZ>tm|qOzM2;8uK|(_=l@~h(&C7d!D?>f!meBzjm$fn0zVLAZ zBg{X;>oxu3@#TfY&X{W;M_-ijvyYXWmn2V`aRR<%O5w~T3&oCR(mhx`z_FL|Eu{v) zH#Fi}RmO^~`Kmgl{^aK3%j#L73wioe8Ym_S4=qD7xR6eY}%K+Nq{l(X_a`4FSed53Xu-S$B!?f_0H5Qp0zc&_& z%AhZl9>oElBr6GV6k1{q1Xo!yWQ$}urUNJB6eCCbaEPPw85Zt3M&L5}Qn|d7VDxr_ z#WP&3e5h+G5Kb=Q$+T!!Q#(&?ZdbAsj_mH>7al}e83hsr=OfgfX@a5us4Z7bJ|wn_~3vbcYnV@1L8V8 zyPP^XPqw#Q%b>~Pya;I*7KutF{K`(-@UF#&ouiaid97tC@q)pvC-3}{w0nL@+9R@D!#ic@;Iw2ZlHYvfFbj5>_uyTOnxI|EdwdPJ$b0Y@ z>wqPdxtI4ic|s#ir?ou@ICeD4vhYLC&-Pb&-=n1l!s@T~@!;gS!ze%XnGqfBG;{NK zE!&Tr%8KEChK;HGkvRBGBb*B&g>Tg%4F0;+c@J4jXO`SB;HZO0yk{%o?}xbYyri7{ z1i2EW`mb9#ZEU!_>tNMn&f1g z#{>OzA?L(i6sz=reXUt&ikVD*)Bry<8&S)+gN+PRkK@8k-Z}fOm;>VDl=Xz+69PaR z2~oa`oM2d9{_}rUQXp_%tdI~uP>mY|N&teI66`rrLdn5GDM{`&e9W_6I-~AIg}{xY z@J4+^@UbBh!(5t1Yf8w|)%6uNar?VuJy+t`{Tv~#2Q!9{7K#ZTV+;vtAaJiSQR4tZ zoGVeFoGTfsq>73DC_9Kl6G%%1)qf1E>to_r5?8fmOn8-*^at6|?UPgs7SGPUp;q8& z;-VemP(c#rRid2+37%p`lWBs4VuyI|=>C$bP{9!>62_Mt8ga<6i^jXn_4mbx^K$xF zXB?hl+N_vt!vVkS;IP%D^vs3#>{A0DCv%ya3cQvBrsdohIbZqt=K8EikzHyCEKNYr z?i>R8Isv{^8w*A6_sfXz5KRpO7=8a5Pk_k8z%86#8O|eI6?5|Ot=g?P(dfGsfK^$D z+REG!r%W2U6*UX!1#g#0(e|yupWrvmeBkA+?9Os6OV=*WKCa6N*!WEi1DT-)5@IfV zzorA<_6~G#BEeg=lH&m?4L$)?@~dTHn3;g@I3~PY|N86V_4aDDOQR+gxBP96Ag@&s7BAt(d@?Q9G!waZc@WoR$ z=65^9om0Nyy8du983Vg&Q=oupv`uTCA8wc1L1>Pv>6kpw#oIB`M1Pq@#0gtylR<#1 z97E6~s{Ic=G=!r{JVLPqjjo|k5!e_ccD28Z`3jHG(LPa^J=IC>WgcLDE z9b~PJ>=9fzOuu-`fq~9V3ek5Jct|o2xzxPWEMky(7jvr+c1Gmvj13K@a+Z#la zikOB`8X0K^ciB5)+pqhld#E@OP(agbPT;4C!iaSYQs2qW%Uq0bwhSahm3UzC7~Nr7 zyq{ym^V_ejkx4vqPR4;Q8pDEJINv;mWQ4&(#(~gg>xe?(fQUPsvH2}p$PRC}H?sPD z<0qoyMs@H&Bhla?WEoB-@E|h`-OnRtqXx;~7sf23x4FC`GTfrSlTaLwq)iY8G|}-v z)_00V++Lmida=IrOI}$j8>d9b%<*z#oPhmUs=@wrDlrh->_fkr+qt}79NtTtn`t0n zIv0AQUA3{bY&e|J&w%!0)g(lffrg|UUmAmheL3Gm(ss#FGy^rFiQr??iv3X8%q9ZZ z9XH-@K1<2{4fa?sN;bIH94l!Hr*dE@B`6Z$scV1@5TiFP$(v6Tj6jTx1}Tyw zi`Q!jMX{?83^&DGc;DJc$PX2a5LdKGfxoH`A+zU`;!Z33LO{W0cS=?=9w*yufA_^I zAU#Tk|1tSiJC2y#`r9+vCj+~s;J=Ooz-t=-WxkbDn`#tFjP2X%_S0G%lguk@FJOA} z<7+%Vr#$yyU@7GZv+nSA&76n&Zu*2iOhKJmAk1>{LrP8^x%OO%^q?u0u#1s<{iFR z%e1yPh?5y-Zsb9V<`o1=upfpyEHIDc@UpvR2M9Tp)-xbpY@`z6%K)w{Ml33K z6+)Rd4(f=3gR3D|I1IyWii3ieVH!Txkp|wA-kV(Ot8}KqUt+YM*}$u;(kB;s@Z9F= z^!j4`tFu!-6A@NX7YakXkH^={(8dR5;C_YM8Uk4}4}P8zDdT-N(xCPA{Ke${)HskL zrGURX{Vves?dtY&@m3_DJRH)GF*r0x74=+`!Wm@+&|qKQ)pP0G&Y6*2FqBLUG{zZ< zOec|w`tj{&Oi}fwf=1L%9QQE=nK{R5M<~kuLvuQ;_A+oHMt1nP6WNgpt*HN<9HtCk zw!STwJc0?eFLR^_%i8e$+C;SdcQlc1$WL|Yiv~VUHqtF812e}AEaCVri-y+o8sVUHFk^fM}9q1Gf)-E;)}+dSH&V zWjHDXcZ#mw5rJ*Rq8){8;6q(a6ZC2Yh9GG0b!HTdmYh)>9r*<=kC>ThSjmVrOT*qn zJ6J?jk|Q#W^pk(L0VmucrJsH*1sPWo0la!r!qN0|-@)KiLEDQEN?SI282)IvRMF6! zD+8cc8OXF?y7Cf-VMqZz*{qnpS$&lGkM(T71N@?P5)PV>aG}w4_{0|`AvD>T4Mlem zocfCaiWpeqV~ZrKtr9XAyvi<2#AEwc_K18AXZ zw&sUW_lt80*+p8E;H|w~SZzaj!B9AX(bdZ6GvpAN_2)4lV&>{ zT_43Hq+K9R9a5<~1SF0gK+osnxob@?^b14R_cxs3?K3RxNFr7z&|^(gdGcM3SdwFP zWa~hOL}#c4?`Z4@!KEVyE`P6R>TVu1L{oS%6HHuX3(-y3Prh8s@wKx&XedaayK6eA z&j`6E-o?{lZ)mBOXH;!78@jpYBsMI&X(y!2Au&wk+H7RCfg?n_c87->ANo<1<3omm zRiz!YqS!Z))!^yTb=;JnZExnBd&hg#35A_$!A>xPIH27%8?@uW1_gt@12-wykP;=+ z2K{PgLvPX7Br@~^Y&@nQgY3or=Jfg6S0EtDCXwv@s)!qG0{5&@;EU3OIu!JexuQ>H z>!cXH0tAuF;+R#|f+=|$^BH{@sFhhAUdoYBGDBf=eRJ*ySsW1LKvjSUxfWVC281aB zm@x;4JLQ|92!rRUK=LX>zyrP)uWrA{ifO;}4-K!$fFGMG2nJ<};o|vfr0Kb&^fH0K96Gpl@%lCHY;P$d)%gjNzX5 zMo?#@xQ#&*;{A5&bLV(JO`X$nX?9z9C(ndr%!&lamw*Vf((#@O1R=?mYN5>Mi<@E@ zKfXU2*zO0}#uA%H%!9rM9R zjZ`q)a4cc&2RP114uW?FWz^how8mmPv)w#js0-U-Oct2TOaPh@gl|lO;4bDn?Q$@Z>CS@YMQ4F1z?(;cRH*^u z>>zgr9J@#Hoa@*vF|)|is4qJ0qO%#C53yN<4djnFcvn88MRbGjTFc)aCaju$g4 z>|-8vOer4Sd!!#L6bb;~CDAezBi^IG+aNiT^`bY61&KsRNejC1BBY?k}+Xy8qd{QHTzy9CLwL%PmmuUjfna_pq zk3vw@*N33Ks;>XE{>r!Zqk@jjbw z-~)wJSBIZ40xrowBl1_ggWo_IZ&veBo5S~2YEA+{q03E6v6_{K2J` z>(%8MR_A__CEK8ep~Gr^%}giNA)aZ2p$GsBg{6tbm^>|ePD_<)04ypsK2FhXD3jq$ znl}1+b1Op(KVSP{aS+2`VB0mc1&WCx<)l-5skV$9+UnJ87!EtjdNj!GqJWPR3Sr3t zUQ|&4x13b#Xy}vGMY-f`e``z^f_z|9TSrC$FR(*QcTk+nn`?w*TK3%Jlm;;P#6akm z2hlnQ!rRpiHbGrH`&Mo(?xK9$whmza9IHq}1*#6jbY3LaFA>r@a`eI-p-#S_ivG>X z1Hw=<3)(`J`i2srGYjCB9EMz~>mCc_VqB}l891MJSkquGOQqtHznHd|R$AbDTWy0@ zlF@iCi&1YD?`4@}Q7fcBtl@Af5op2Y*2bY43AJRdGCu<@d7NnO`hWd+@7C7x#(5Dvv4j7qmLz*E&c6ph&!ltBbJH9dyt*8zw|HVR2_ zWUvjVRF^Awoa=NTz@m%@lM)>E*Y8&yBc?MzD0jue%P0?cl`;6?dVRJy-oyyvn|K=Z`%dy-tlkBJU>e5f4!sE8Jznngll9)eo z6lEP2C_ES)MR|%rLTTLUw>3h7c3$=Laaf+fh3uWY3LI>%Pw=PGz{92jarO&G$^|Nc z%0r$_w{3>$(Se_p@hHo|GDMMk4~uaWzeDpQJl~F$`eR1af90C=7m z{vj>DmsYi71-fz@@Q*p)UbZXqtGa>Fcij0$%&;&yQSQ`>K zR?xNF^W&y+icWupLd-Hf7Tr=9`H@}4yA4K(H;X5itJ6>AK9W4(sb&V+mn^5uHu3!0 zUjR$>7r!?%NTJM(PVf{mOO9`a0^Z|vDb}k_fsx(^f?9FUkOEE1m1rP3i`hJGI4m{S0>#NR!WWm zuP77vxRYw4InB)CBJ1H6=u zoe)%K7Rl4919fbOI|t$;Wdq>!W+5A*Ek4M4Xx*OHMH6i(@CI#{f%es=!FSi_N-Cy*=NouP*+%IJwpFfNBWo?TerB z+&qND^dY4><%}`BRXfvx4>1}dh)KFc715wpDaoi-4K>?jF8Qgu(ytDEvv>ILzUzej zd)Z+v*`>}j??3X}ANNune+VaTHsm^ZcZ1#MI{34h-w`?2%XR#eAg4^~pTIlD;=6b% z9T}^JkML%${1tXWxU@tNU2NEdsm1uD zmZnSzAKg<{oJ?t5MA+4eInG35apO6;7^{ryJ;id&}p@lM5Pc`1u=f93Rq}x z5SeCC5KS~V95tL$lgsnDadc2vM>YFV^;Rk4ku zA7phvI7);Gm0K-Do$T$|8cnNkT)?GqK^XwSROxIM$Xt-pPIJ=SSyN3a4+()MdDScl0l*B5IDV4B2qM7= zF(*<8unkX=M*)LI;Rv-M2M7AZ;J{-wzGr97*NAd;il%u$Hza%+QKbbwPDq3m>1=;< zcCDPzdnxO0-^yv`WjVxee~fl+Rm?vKjM8_g!vGjW+396U2P(ac!_Hk+Jdp#fWkCvt zlsJ8m&BqAMvWv4w>m7EMxy32yMUVdTjgS|Y=(3PKW_&6Y4@j>o2xRs zYJ!0FWhYvWtypOPXAZ4A935IYS+8y+my=vWDu5>&nYPv*T{(I7xJ)@X6Re$bpf8&# z2lsw4i+~uVC_+1*;syrpSPVHj@-s>wWinWmC?_ZPCvE=3pD7-7ly3V|d3Br7bQBF{@nR>HCav6l7>oESeR0k!#ese142*}=WP8a-u z0u4dygv3cjGQ?933?~c<0bYJbFuEIIsa|F-q-Eo(l0L8uKbl62t{+9e zx_IKH47V*#E@a5fBZ1DWk#KghABq(CXp9phk}(8Dooy4kvVop=XbiQED!7WgAOx+T zJ8c8?_I!Q$#iwH(33WCSrg@^fYdq-x?ody@TS_{4ZHAi1-{lO@389t4AjFsW#4y06 zMHU$Y{Bei0yv#c&JM(0sKZOhL=4|8r1x{T#-dul{87|H|+Sbw6i8dKj5g-$KH8;>! z1<3-^0)gLAd%#0d5;}SMmYf2Mqa9B_ZN8N|?&5B>K;W+?l~rNgqS~sm{$j(0_Hy)* zOdstWE%@T+zo8|$xPA|MlOo;8>oZ(jgG(bMHx5F_130Aji z78LuXDE8a6j5B;K&&fjjYg&*Y&2lO}sol3S09~k8v!NJ{>QN~0lo`tk4}x)^zmyIE zg?FpVFXs!HT^5REK(rQZhe9ZpwNap5-$^Grt9Dc{1_N>miBA$0LST!YXoB#1{Y@s{ zz7Cr{wL|#}*^9?o|> z#wmJ>*K3)&Tx@~Glyel1{=9TN!u+UG0v%}}41MbG&`6<~{^cf`Bb8gLO7PBoEby^e z$z&{EAi|TH(dJu;#a25B)BxDEohSfz5X^x!6ijXZ0B1&`EfxDyCysENPcGY$jgZR) zpH4E-81tcMZM!xCld5sR_pM;WtJ#iYINppnbfuz$Ls;pFhBSk!lbiK#Ys@^AWed1f z3MAxm)Om*b5bj@N0Av&c6+&X51j#|RLEpvWdKS=mP_5) z=Gpn>#-V`lxE2S;gnl%IFPS)KgZ~I|Ajh_uL^pyUCo%%hCHMagr;W-=HrX9dx1+7; zz}8pukcAHA2#ck7f8+?uuVL}ZqtOwT$2T(B{AMLo|JGT#^7wsNxpDwSOc`f6oeS8t z$5xO%m?ePp#sSV}S5r80w!jW_E@JTey}H;#pI>kZid6||YMBaq5B4=P)c}GsZq!ed z>1MWv@-Tbn3(%mHN5J?}A~8C)f-ly3K-<+?n5n=Z><$DfTBl5U_`shm;#aK%TL=@d z>F3Q+#?$O+2hCae8#$});=gXiBC1^h`~n5szz}i(OY=Se8Z7Oj%F|Z!08C$cA5feFGyt|O~fIfKe zvsKUx1A~{_su1XY)6A?E3>%dJ7oJJg)oT; zU2UuY=7xmY1XwZRJH}+QM1sJ!La2dQ<~8IEQgRUtBohMf=5#{lW-MOG@vp_+`RKpV zI4aCWM=NXF$SC0HJ7`32nTE#JIpFLb%CsNokYxgI>`*xQaw}WOtbg(I(%Zo#nqnSx zQYA)ZVfSFNJDjAX$RH%YqEtW+CMpCFNC3J|T1O5jk1sD|PuD?`t5DHm|7jl%IEq*zWGa9Jf6f2=gL zF$gtUyqC%&uQCL$H?m#Gzc!ekff=FT0{LoC>FxQ&bE zPd>O~5Th93_o*TjN@E>R2m~-unlwv7gAY#J;E!^D-`_8&L28j10g?e)MFcOqHU9~S zyjk7KIPhAmcD>e|*^Jgty>nJeY&5{@`!Z)Oc$}5Uj2OVi=rXjJ%F{^)%gNd9+!d=P z4>#F+U<;uX4aQ{&ju6^pg5Xu1Q-8DhDwDI{tS)8xsZ+2enr$Y8X4EOreyAa%&l)Bp ze^oSQutRan&FoG-jO-}gJKiY45!^dD(aMfr-d&SR7M+({Y=$G$u{AM|A#xLCWsw%&22YcHnftz$N*d`cv zsw4LoCkGr()@N9r{9aa~%1mZw9{65zFlc@i>V+@|Ev?x=nbXQqM#4bJ>>q{>+5=8K zBzT|fOoDQv0cIOoJt=QbPy!ENA6H3FFn z>Of{AQ0{TU65I>~f-+;sXc zfTO8~Ljh1F6y(wbaW_lYJ9_8`72K>XBIO)IDx=jQbr1!FEGmn*g%brWqrW+QA1cF^9MM4z4P6xd*RK}7ppAVnZUP67t9pcC3!%m{5ayeK9g|u>%asD}5&M-Qtq)xnkEHShvgbyI` z(_||dgfN}}tE~wEZkRyus;Lj^37Yd#_J#?tbQJwLI{lbw876OKAcAQ;#hxx-8}0(E zHrv2YwnmTuxQ0Tr2=IXd*S3u@C>|UX8%Ypa%A{Jzz=x4qL_Db+R@$sZ*yOFZE18v1 z9BzqsH!Sc1h6UchY$NUy=fD8+Rq#Ngo)1PjA)jh9J%EUd67Y@+1IdxY=o1{(E_L*? zay2v_H!7AhhZ9sPX4^*chPx%0U9$)nykr=8a&dZoQ|8%@3iwHF79p610hn0;aAQK2 z$sJKB2VQAqEhY?J#|kj=1}UYOT10-rou6b&k>ZFx2ZOT+3dM|Q8VIUT>eXyAl!u38 zap3K82+?*H`&}~7G_paw!%pmSkf#j77x4q0QdPy}V>Kry5Z@eo>AnJp%kG#Vee(D5nLKY5H}=k3cd>8s?p%v>p-h~KEM36cer10 z0#BL2iFp1tx~v((K3`wq0Pd@D{0whjgFrmgOhQo{U0@jCNl6{#QvEgTbQcVYkd9yn83l|*=8~{!if+gVHj0Lzf7EsM;33@dffN~I3NA8J$Vv)T}`l8ALOx9!} z=J@zXtpR=JP|&@;KFem;jtATKG>%EFGY0@?Kma&32WBA}y6GSDjDsw0z47$}U-qHs zPNzczf+V1ymN8=vc4BqVEi&VTp|TNnJB#Y>GtEt-lG4f&whe1 z4t&lNB-?|?hljl-|2*!)r8RpG4~rdjA%fTpPok1yB% zTs*t_C)M~C=dfXmw_7=j2I$>_WHD?oxN#sHMupzWrX=Q z5POA!S6L)~SlwVUZE@UMM2n4GGNi_$+VAg=k2m_*k`@MI2i>J3&UKjB!3nHDz#4QYfNt2B-0Cf0mA2ABR13#%kw}T=F$g6&C zg^yEi6~++pPR$_dcy%d(^x~&5&jxKaB-nhNXyamsNmO9p-6gL&DyD2QRh@1O{pGvC z7(T4NOI~%n+4K)gak}8>5ONCOUk6Y*q*gd)ht&H=en{ONk5O$4^T;+vu!0Ndm!hXI zib@1pX2H6+Eb9K(<%JKc{nTEEh0}&Gj*17=STqR%KnsZ+1VANHf8=~>Z!8th^e(eI z!X7umz!TIu(1lJU7)w5r{gwUHE58n>Z5&}Z6G@EW5ao7y+ zs1VS7W}SR?$$2U7Z?CTkZ-KAXRR?mT8BgPr8U$@RK-e*WNLxLszsw3j(qyUZ7s%s2 z+jyg<%ha)TIUZy6eO=JOA>mBJAraUO#VQxEKv*|LhKMoeL zUS5=2jsv;61mke1vI-62t19_UJ9;tkPlfWSD=(;^#!3({)xxwWiq7zraup&M-5;a_ zF;u9O!da*em;PL%N9w(4@Uc%G*cpP_C;qsML*Qp819D2bagz?+f_q={VBDM%Iy}ZA zmqY5aY1>!4gfErjZUzH>V=!oYNJ+fK4mH8VHMLGRrC!x4LzRZV6#&4W99IbLDbRFW zL8p3q79BYABpw>dplvDfpXA1~Qt{nYuGx=1-y0g-c3s2AIs-;U3IG%8Y^1Rcf{zm* zQ-^okdwQWGYuUz&7#foXHkA%|agw?XMs7r>DiqM*O{BX4i! zwC}h<2$UNRjZiwrE7nd7-@jAod}${^s+llrAP&57pB4Bx0TJT;t`~c^Y43Z7hZ--O zl{h3q>Vq#C5)cnl3|uM_iplI)x1>R6X(_sx2YWZK^SWUi0VG5qB+KEZ|{XJG$_FPFRjG!S0SVfP2G?pqncJ2W z51rbP&a~xYR1Bi+07>>#aic%rMrblOx|^NhQ8hit6s1Qfn&FcgjY;$vE0&~jBt=3A zF&LghB2h?@K-ZXb#&!5z>FjNF2Io8q#H4gk1#@`wb~?C?7W>8A1n_@G=_~?~Z3JEC zq;u9bU${$(LCc9k_{0c-OVbNgi7hyc5h8W85`wp*3(>0_a(1f&ArAsWn^Mu-`LKn< zky7$DQ8-%qeZC%M?`g;NWs`VlqcP?!a@J9dF>xi2Sp*4@#+cA@<37KWA_nM?v48y7 zMwf;?5+(C;<4_oWx<+p98Pg6b05#U#~9YYtR5O zL*^i)8br`IfnOLW=-?PV=z^-<{6x8Ek;Pq=2aUJ~MnpvLaYCdUQPF{Bk@)fJ#p;|# zZu2YrO>(@F zJwm@P-d|sQxjp^l&&%*bkf4@G(Vrh`Py_~7oDF3p z{R#t+T_|2|)-w9`dDY$Pi>5InA9sSmxJPY-fXs%WH}#zOW*A5-l?r^4;>oG;!z-C& zym+#{DyQwB9Z5vx(wG1o+h&39*G!-t69RVPe6#4zat#WxmzHcO4&a^P(4XE(Qe=UU z`POpaRAa4{-x-7eC>@+tgbWl}y&$wG2rH2wBqtkG&HVJ%I?Uh+>J46D_)a1F!6m;LN3C*@OOpHVh?)d6vQF5Y7ClWcuf?1h2n z)R_hmnK1yD8KGBmwKDg#SqH#mzC~Cw4sEdv6{?H@ud+sHF?&<&4YO4#LqvsJ)+WK% zq*ChDTmW+P9eBXoZEJrBEAbnR9C(%6&w=RY7r&Ig0$-{rfWMk0NC~FQv7WA((o{ok zKdrwlWO3lv^>-nf#Pf2CYH7+(FT;GgdoZVz>P#JSW71KJv|pT}g5B(hjJ6K2=vRk$ zRVLz>6Y5|}O2=xl#I!SX+=M#4v}A{EPl5~|YmS;@T!#MP=mrE3teTE|VbXCV;~R@- z%h29U9-1!Ehe4vLM^vUJD`j8f@@yw!Fv`ogi50Y`#)5BrmmsD)yHIJ7X_MTV1~D$Q zk5I4%tQLy5b1MA=BHb7jXZC7ffUR|CzaNHH@NzX6%5((d8s0X|V~iN(Ak+TL;^4CD ztsuh=ho`ISZ!)1wW>fk3Lq2|SD7eZ%aN2F_$RLbZN7M*)Y`L?`W2W4oH1D1jJ9uuq zYl>mIAwF*5!1(z_HX*>46r$V3Ns&laM@bRFIe=VqA{)q#oG77i$uBX>P-HJ(RYqc% zPXs6y0gK0%JVWMHx$vCR*=i)vvUa}HCI*B_16v@~iRL0P^sAW|W!guEO^;;ygKYr^ zDFR@|rELB!-WCRH3m(xh1A-o0lO1$~0TIF(+}0?8FIiZdfxzb-AjV{(S1aS~`i#KG zDFF^a9R;FQK9j4DEe`}6C;(W&cXDiv=KthNAZ}FFZPuG9hfAsMFU+*pddhhocOF0`IOL@MzJd;DSPHyB) zU(w5cW}m|WgfSd6 zT%aV-oZFfZ{iWJYZrlB0exnZ4P#Eygknua;<&!GRm3q!gD5N0_GX16^?#11(QnBNeu2_B7qY#!|Q(j z8O%d>N`1dxeJnOL@?!~Y%LoZ5z#WUFMmRAh6n9$DbTD;vjawQ$SzTVP%H0srs9G)Y zSCfZ81t3*}piKuA)G;>YVXI+G+b&|bO_g}Y{gd&oY8POwa~-eOU*!ZksYqOPSFjL) zD&YzP(6AT-!`Vev3H-665@U=gSA!EDT#s<B0L4YSkn0rfPA;lD z4+ddPnyvOp7lwuzJFSCUAeW{Mwu|aV_kAAxNIe&BO4s7MCY>jt#>%A^+BXM2+Ql1M zu#5O&2zJ|!im`Eub?LZ!v92>v^xd4;ZXi`>zU|=yKQY3Ex`efBrD#zKYC-{(wn=gZ z2liOYLp*W09PZ(%ie{cX&?>VI#|ZSRsYi(L@PHZx{8dXuyD=jAF%^jIehkk|ek`Pd zXpJJls~qt^zQ*{%^NY*RQZ;kN(ME!x7$M4;*bWJN$%Hc_;dMg)nioO3_Nw>LFAU6g zpX&fQo-kvEKpbBn1TEcU3H7zi7jZyzmaXFzjT0dgbSi;MLm~&feEIo;dFQzzadaW0 zK)J}2kbOL5cQkAo_-(WCfeqSRTL$=R&HsR>rL@ zG6wj$^BFXX`oLj$7Q+BPtvyBPXqG*lz2qPh#{|H!)fe%J>~3il(&hP>W7$QhYmWD65I z#j33S`tdPv%!x*Ec*xYUI`A=LfGC(?*$=QUmqvk)qm2*vf{NRKV z9h~Ba5I{Hs0Zoh>>MX$xcI*5AqU44y{h(^Pi4Kt;u4VUOIhTC#?ChJ=mJ5EMKh2}kK&o+uU@CaX7$n-xrf_V7q!(D=y z$Lun@>l0XOBm8wbPDEWMw6}k_^xV|IC?kG&;A_ke)jq)l?py0+>yb@N)4pN!t({YZ zzizHFr%4R)uj>N5LyCX#>gLH0X^kfQU-1rpPKiSSyf75ho#VR>1AJ9R9fn5w_UmWE zM|)jA#ye*}7M7g(065lC@p{eLz`M=&#S59YUI=z)-a6n)(OxK|!ei7nstp4Y+QpRY znhoWki-MNXD7P_iGfDy)(W~R*j#tMkG|~95xxPGGJlR|PAcZ3@Ss;0BtUxsZQWg@? zh#C)VI`Ggqc(w~WNKk17FVT&k263yyY#q3fi}GHrWk!wc5A3;suhdk) z-Hi(UnMDqOOoiP5$UUslF7Gk}fe`0rivXt_0uuSlY7Ut=;H;sI0B&IjOrn6c8x&A1 zW*fucMQ99U)N}yiQgw7qGYfp9v(#lu@|U7M0L~Z<*m4c>og#!m7JO1GLIwe!?=Wei zv;)ctLE!DOtH^Yz!kBpI3Vzu{LW(4dw;Ra=Z;BJb92*=Ejt!xn4y4C`*ijlwAk;Su z`qBpi9gtKZ6jIRy1p=?+Y~r;{ppkXjC+BiRap5<;I8qQQQiO7{!vaq;am3};WOyuq zNOLPy8Zs72kHNJ@QsCo+MbL+A9z-R3iFY!#@Z@^^t=QvZzs(6StL-lFiesF><2%N& zEdwsIJ~a#Qg15}P=s1c1LzfayFGMEaaQ*(T7wck}1rHh(iasasQ{#jpFueF|hpBX- zQfkYGTW!N2j56WZY88PAkcT&?Pd8VqUi0x&ix+q~WP4?rGo&(q=HP-hBF+dzg#!{?b|&>w)O%gmZ6XL5-RV65$vWL6+WLdNML#c*3?5Itl# z;QLkZtQ|Z%a!Q*FtAEIaW8N<7LG%y!*!)8b8nQ43EZ(keWJKZ9;+d?4Ef#*F3lm4= z(kS8>5~gtB2{k3~G+iogECL*g+qiFryk6chE;enjV7Hz>_an ztntl(;EaL|SEB&+4TIny(dWhle7_2s@W)QDFv5gPb{jsxUFL?;-34xe3olonWi;i< z>Y_}p+iXC4+hLPK0-zfbdHj_fIiC$_yhwpiQ2@DUY|bWVV0gp1f0OgYd!KM0-xN!o^zm^AjIJ!-?Vm=h9iBTzOaT$tC9>oG5 zCoIAkTec3ooXaX?(BK+_1|L?JpQc39&Oj!mno0)rqoW18)RiT(i)_z;PWwYB9y#Zx z&GqrX#|e*MBVBGhyGD}r{QCCl*D|2?E9Kjw8Fs_MmuhZk(+6J9=0@&w1w*X^_rvpC z4J!B!d~Yij=px4rSrChj1FtcQ-YKWk3&2=gNgxKkR0Dy(sth^`~WNT`>gVjh_DA6j~bVlT~}sGWK@z#$!S&@VF)Wu=)M zD-eg+;5v;e0MC$s`5I9|)f8>6k;r;ABHi2cs;rzk<(|%rI4hedurNT5WIUeVer?S` z^)4O1tvy*F2EJ75g@OP!I5-reY$#*IxbtZO@igrZDfOxX;tJZz} zxDCV1q}bX%*FMUcDa}pGMA8Ny*!1nAyFQnIWIm)5?PZGkx~P=MlScj6%*aM1V|Jk= zYzsv=iG*U3)iwEiRSIZt3h{zckOr-(mvX9_=!DGDk1pNs7={-x+W?Slslb#THL4SO4lwVSD@YJ2ZdDIQH)Ws+4j}9BAQ&LM9DB(9FUu z;P3|?lF%-=%*=16fJgWIoc)JN2N1(HSvFE&&#qP;Gn@z3>seA2vk`R!1rrcVW}j#ZpR8@YN8g2+=2zXiVLQq zM&M%uV>x;2xbHI4^pZzRd?{!2?c?oh!E`DSSF_;x4LtDDj3Nw~rr#Lj#?g`AnAmMP z%3MEd=8VaWUC;6A;?spp!oE6_`3WD-eI!9UH#}he$#QVHtzP6UMXdt#*fHbirsb*` zJyQW&6Fb5j1vE#sf>+s8kd5nJS?HfMB#GzL#Bg@9A7Uy9O-&5EkP?HuZdX&IY_g56 zPE&5#3~`z}0<#RGad<)#4BbQEcylQ`;&?dFp*0+&$PC$-M2oFxbk2Ma%!tB)k5h;h z(y(kO#3fUMXF#t088ic5GAz)&wT*-z3nHB1_BLh>n1)K141F0%L&DZK{szOJ^Si10r&CA*BToOA}THP0pF^d z5A1rub_#5(`udMoXBVrh)#B~yv={0A_|sFgbcl2xL3>l6JGHh>7i(Pr##oTY09X}! zsbzL{0*%T?x~axvp`NOHc1wp3i_*F}aYnkdcIVIUAL=)ilS|x4Nm-WG*$<5Lcu;}4 zDYo2zXXKH@h+(fO^XQZ!XOVa_LqUVOJNWPAk#9c>+^SA^F&Ey26?VQTau`&ame?v0 zsn0sOx%jfW+WaP~ikw~gIaidWY8FLUhw1Qu7#SY)xf|nIrns*0Mk5Gtuk{` ztyTE|;+RIo)yf$Uypprg7whX^TGixYvsn3waRtpZD-C5-aIqFCr8>Bws5C`ts}o;7 zbYitjD%6kugJk`EeSIaHUan;wdok0>h5l$#jYcRK0p%JEcHf=qhs7Ktmk7}pdZ8#5 z+~K)KjAf`E;-hB8=)>y!XDO$?zxaA4D=@v4;3u_y6BY~mLreqkYB1C`D0A}p4uzvZ zRq4aBYJ%%dHO6AQ07ll$U?nmHB_rnVun3XIFE}_H8hCKhR9q?=VIfDG85+xM0VCRp zBAC_S;GwUW3v({MNIClPaIlD?;Cpaltyu)zUdM$H6|a{7t6`;R3#zAoc_ zcrF8j2&g9^L6j9iFYrliBHAplLa&nL z+lTpiHF#J}2Zum^G!joTU#blnUU{}FRd99*a=TN`&;&;Sp3(Xoi`9z*)$rohe73nF zfM{enW#3}iKeL>?KUz*cS)E?X97~xmwZJ(m?HT(gVJDzRFrpM*Ppu8tdq=}e z{SKMk8gssa(SFhjU3o1408{gek??7{0JzW^Zp-2Ag51sIG&JfXB@m^i6ymlu5`16!g&mPt_G+dfbjrR(seq4D>=bIqRDxDPo%@Vpw5@asOva2R zXk{Hm_9tE9S!NfR6m)E}H8G61xhOJ_B0_R1IS}yEI+hMO zCy=TMfxoI~Qsn1@aQI-~c?~=35d;fT5$X}>heU`_BI^S2?Ch+4TCoctX%JN@B1F^p z(ijwYgB^0L{bI9(Y#Wp4updMMKT_w(D=!i#6v_bPH{fpNDs*G54glFKA|zJ0ry+pG znnh$X@RjG2p`53}+s&wyhUL)cr6vMA+YhtTzxY%eubjdPxvcF$Vu~_|%rRS4EB)Ke z>U{BX^{;;c<7!98zrBBqzvnV3TAJdx$z@Xf$oc@bmjXEzW?GQ;M^mF?daDvPNY38! zflqJ8wzMfXHRQriHOw-Y1D!3YgSn+LDPA$5j%$ORF`-fKh!u`*Feg8%ZIR-kWx*Yd zF=eZ^R)SD!G)sV8UYl0rsY3I(lB7(*CbKf(cFt8jmR$@b&zC86H({;}U^9FiJkBsf zZVLxp!;#%KRb{?mmV4g1(3a>rFjAdJ!N7YaEV^zxVDSp)sJ~fV%7B&=sLq62G^g#e zw`sJR$t2rg-@8PAdEamMr1J_$jlhf;#CSA9$pas$bx^gkT1y!WCoOVcXb%V8DcKD$ zs6vz9(Lizsu_F5*}#dWAmt7>x~DoTd_ za5tV+K`|21N6)DY8d|xRCKJbUs8VD2UPJ>QCp1Di3{OyT2zDXI$cZ+boZd=A=fwf{ zs+|UYY^D*M23lJ?4OK{pgEnz4F>(tP3f^UgZFHR{n&&vdeZNj_Hm9FTfk1m}6wuZb zSdWIAO&QzIB|{ej+{lqZT*c|)N7+>8?jWHi4HO6^LLG zZ8B)ncQ=Vp4I)jZTf|h6az6>+24@OJm zPc~n%w)0pvvAQ^I7s#Lde1ZlKiPio+WJW2onph$CTiW=)68OFONWO5DSyd64c+;|~~Bk`m- zq7}|CA`aakkR8h5R#~pdmA3_;I zZ^gpp7t0VnWGsSKq=n!w!ZaLcp&qg6|s=cejwDSk9^rEo3q&4no}l zZ`34(9JDD_;uD#KBQp*B>?~I+$5ZPlB8UaPG-e+(?sh>cM%8SkOFYY>R7Qg^XA|i} z)ghX~&6`M^oNjJzWJlREnW5#PDX^<%1RiaS2vIdYsYR2HkKPoF*+D1D@ibM+AwJC* z3>k=;23T*B**`CV+{lv2xfin6Pnl%F!mYnG{vY!+d%Puwln{$DzG zX`_9Hw`qcKviiLE`^D9%%u{iu0O}e9?D(n{ITHc}W3HRnodRL}u-kf`&HYMF9(HZV zm+S5aB6GT5l?`_LKc?-}!%TlUo$%sJCKgB({Yj>vZAAd>Fa)FuS*Dem$W>!z$LANP ztB)IQ-qTL+j$izU2M_7pBWQlg?k10$K_qJ>KA``g6fa$zqCMl%yO7wZY!CU$-eGYP znqNrEpx7yL7&il8D#=B9$FVZa?(kM=*bFagw8LeJN1(=r`jYueFSkQ*U8|5b1~gPK zXo3*|Cp9WX`UM9;IfI;FQjd94k3jdttFY(?*r*<%?_6s4N+vZ(#p~2pKmDEyc(sHL zE>+%wXGX`=Sg0WeEcB0=4XQa!MczB?!N8k17*b(OT{B)eWiz1;jP6kw(9aHr=j$uk znMKZb^5q_;lC>-Y!L2ypCCoExS_>TT(D|NuNKni%Y3p&nXb>qARvbWZ0%lhDDh1gSV_X!8fKvXxui}MeQ9O6lD)Jh=F5{6Wzn$mcdzN zPT=JnCr&QTt{MY}ozu4oidsjgdpNZOC{?_M&v#%&FLuX^jO6l zuQ%5pWn;$mwQTa`92b~fyKZM6Vff5(!r6!V3iKhF-OgmjbRwC54BTcQ5t6!$MBoIc zN}hbaUi+m4cup-7xQCfWSclan1mBjXu^SAEP##~L9>Di^V#J#`i{LoX+qh^LU#gpr zzqVBpz|_j1+Bm4%d=?C0MOa@modt0MSY`pQvOT<9eUm!i;^)h=^VMf(58M`M6pA1X z0I7xof7Mh*uV#y6X%Gs)U|(4y$r$w&c#x?=98u3G;7g7Tw1pS4oW2+E7^Lrolv}f{2DYG5{i9pvyn22spCa3ezODk6eEE@0zuW0mS9m-4wr`=iZzVX z$UA?0|G=-;<&K@U#{{{R?P2dhu?(?ywjA!S!4m^zODMGA$@mf7q_7#^H|`&f4i|x; zwHjn$)P~JggHjmX_=sA>08~y(suEXRhMBD)_(vTnI{9*a@ljXcBy?P;fS(vP=v9Lz z3<@|b&`ZV$-FNQKsP?7N1BC(}duu3-FFASSS;FsDpMSaVIR$#tNWwU&nkXGN<#DC~ zvK%R?d4xz%2eJ}vHc1f zkjzFdlhxo-!|4z`fl0NMkjo^7LNnV+$}%vs6FLjtIBUd+k;KV(*+_Vm0eQ3fC?{?= zMvL(3H5ATB!W0X9QbU2KY53r{fv<`iZ5X@FNz*S3pYI(#T)K$T4H@x^8IWQ7_{vgZ zdVwD{3G(E-Of`{7STaiBgDEE&_-XA#Llpw;HZ;&_m8CdhSWq#vHuq|0h3>;*&j;d4 zc0>=7LXJv7BRMh{w3|$h$9u0DMtI<|?}$a-Dp}nr z1;LFOl^fFRGOl_M#uAA@E4E^U`R4R_aTudNsnkiR!y@p)DJUqCT!~z&mEf112vaSf zU7acgLuC`t>Q#eUm4U>3gEB!Jc$GOJtG014!fQE`vDli%NvC54;B3d*_@riqwjZyq zB6>&Zq?t{Rv->A(qftYNmc+ z=;~0YQ_lfhiSQK3jC(a@MgUa@#&E#&X7i;-3A|kmhm0hZnTHGXs{w_1i*O#I#VPck zJeYu%S%;u+tV393(;o4I3EEHDjg1-T;nJ51A8IF{JItBoe!IWa8D(mry`#^-H1l8V z7o8@?Mkl9@XC%m(0;o7IIm^TP?<-!x&k{pF)mEtrjqhp#Fxx7RrtKv+kX9Ry7Nkgb zY6{$3T;Qj^I9NzZMI{uBonZprN~Qt-E;7A;79>*;nz#nGCcYB?3N4L}?g+x+QQ;^6wU!B~ zwi$(fMWjEDf}1%%(OZLsCm9lGPEChN>ZsL&4HGl>8~+ayTgyO>1UWhc5xwT*7* zgpZS>2-C?k61>U^*;#Spqe%Rs_7(l%B)oRasQ8k@Lx+nrU@F zYQHOf9X<4Gj~*~d%a1MU=1X-amQCRCY7@oeG^V1;11?WW!zF%iSB}^i@%bg|^6~j; zT<%{BBbAJn;7~B57z}zWt1io4834T+Zn5{M7)#^8jcGz$c9cy4z2VgH?dtY&@m6~I zYcUncueNdO=!_wZvO9Hjnvjvudcb@?Sq`kRYcygYo-SfY>QSjeTz7=#G#!QRKy-Zj z89TjtVc=6u8Hj+o3YSiVG#igFS~xJ20ioJ3+c-KE2O5!sqgs3<~snRer$>?BF=>mS%=nlKQ~Zf`*c# zhj((|2WDZcZ;N6mzF*S;ElCpEO|S4J!$5@+y@#jnfI*s;sbN$eJmT%TWWZxPWa#4X z7?W`puP?$l0R_&0U=XPyf#(|#VfYBGukAu95JW-!V+o;1sP4L=cFWapPk{ zA|{)7rDhn#p`+(=jGCX8sohcx|0r}h%& zD&oNk0l~;dp@+Zxa3M!&dyj$d*G7Ss8U~_~~f&A-r2VssjhCC%~H&)hX(Gs3xi@6AI+Ujtonw`w{iV`Y zd70~hItt-T^x=I!Qovp3m6>H!hj+244sV;HI=0-fFQPgcrB!^G+L^*vV+Hu4!2m=I z2GymzD3k?jG0}?(Rj#7ymD+v0bmu^#k0&ZAA*g_p_kv}+~niU#!o2r(SKX7 zLyfqtQyB~;{OmG$YM<#8o-#wIVlV#@tqbL5%{|rp(AXQU8}8?g4l*6( zLNk+5G&JSLxKt4hlL^7R+KuC{TZd4hRw!CSBeQ3r>&Q|;+sjfB!NAAHjegY_Uvgl) zT3_Ofb%}M$d>EJ|ry`fNm4srU;|3t8xS_IfN3qCA5V1ZI_&6aE2AuJcIuQ~iM>aD1 z0bBajGw5L8H58OkB}Rzv@PwKe0B9>QMg${wb`0=%=ROwrIAPH(oCa3BT+3V#_6y(E7`U0Tc=|9_}X4FV2Rf0 zhm4_2Q>xe!oT-fgk2Yh#T|4P-lu7++mdRytX4n#ap(tpD#iMZF*G;*bwVA|GNPO^S z1_Z9MJUrcq1HJgK+cg5sPvT3R?^rsCK!Z&!@mB*WZnw}5G}(f?)KNoFLBQq(nK8b? zD=8jemcv7OWfBOg7;0HLrnr=;lc|2W44nKz1-`D6pBiw zQ3B0OMBc%;5zA%xVbgW!_B6r^*@eQ07A7p-ZG;PjMmXn*yP8?>QZWegQ_#lR zEV6Lw)$D*O`2)oOd{fDcR7j(=ih~cc!$c*pM2!NsZ~`GmpDtc)u1;4)5{fU?RPZJd zs_8(eHVL4qTwpi0xe)z_%4;_KQXv!pSQ)>U`Ou%tco5>LED*fP&O^>llYr{=T0-rQ z*e%hCgmWE4p{gE+k~;9N@c>F&@fcIfrEU74ywI!*Sg4f@nB^!`iGh#JKn{X|;FXjZ zXqO|#$(P$R*|_PKUmOb1q)}RgbOW6=hQf{tkz9eIK$7jQ5E+2L2Ba9Wz7u=5cz3Z? zAp!<%%g9X7tGNoHBw^yo8%%G1sMg`v&B)>P=6Zegug$`?g>WpuPmBe)tTj>)C=(0| z=c^j>+w+Ud)!F*;%lXA>@!)=Mq3PSObIm>rs43Q;T1Ixk9Qx4Qm0M_fNS&P~Qn2q| z-Q8Bz+`z{>q_~ZuWCf7X8RzJ+ z+9YkTld_6$bVB_xX*w*5+BA|gxSDWy$Y13g4YV?Oh!0FJ$NH&N9oPU zvKihiq&&d6X(uup{TGxCuh!m~<7st3Lo}M_@1ZZ~cK^y|YhBDCe0o@;2O+E@UQ16dclaDXgzpbU9d?s6v z{SVFw*U!-UJY9j4%2J#;wWiE!tQiHIOA0hq8)y~IEbMaNbex5%J|=%0 zYRc#ga~w*I{LT9|5zRaYbln2YL;Nb$l(Q%0ys;tB#NU%NIxS#T*2LgwG}1(SYi+=7 z)m=EPG}9Q~JsWmsO@8;>L*B`WWwM^yeFlNZmI2l~vE)4cC##QPPdvQ6!GkU|r9>Yj zaLVWkcQrKdjz$V}ilV_Nxt)8Asl)w8ep0fFGRZ3kM4E`EKx8ufWf@BAY(66^yr%== z-RAq^g;dLmx=1nfAm6V&Mwo+&n;H&yQi{0AV}_ad>z$(rZ9Qx)5+b=-#Q_g9j}dWz z;i<>K%^VI~^7=v6<2$8v#)6jADC~#&5Pne8LEDbk9x;xg(AAm3a(Gaj399KA?isx2 z-qB$(+ybPM_w1e?6tIvb`Il#lCwmK7!{MDLkDNC1K%)#0Ji^Q)gxWx@#v_Zxj;u(_ zSv3jc?S2pENp584`D%S}b1rp5j{txg^_^j!g0l-?k?cY)RUdw502GQraQmD8#|6-M zxn>~sWPP=GyOEK%n_`P+(61%}&@m#+EF|NX9eIcuIZfZyXp-s2;wiu9rLtJQ0N|5Z z1fPMA6boKuLI8_5r%yLmtIIQ|5)O;DN(7O>UCk&QCvd5pQ18Kr5?>Wqh7PSfsD5Gi zeAw^I$`iaPO(TX8<-{hyZ)i-OyfWqFU@AjZR1eh}yxJHZip=Z?dlB$4nhA&y=y!p5b|!0zu1-J64%m31#1yo^aPT~lzZwo9 zq;nxI$Y40|b?*}Bm#c~xC%4LEkr8uOt zD2k_GL2VfLu@PcYu~3^oIGXt>aOu7?5LBQn5wxw!Z-o&jw!D@6>gJ|Dt>p9;XA^jY zA%M#yom~VR74Rf82~GV6Rhy4M8d8i|j$$!5{~>Wy6ud@;d=DQf6?m1YkVaSD))UGc z7Nmd%b9jX$lR+Qwty(AXK(!0KYPyXvolJ!e1%n|n?ILD8{i!ouyFg={JaB9Li^cQH z&F$&=CuuJ}wgV*5)1$UAX>5fZNUV0S_nEUE4z&3s{8%UrBo^47I2Nuw{ zrVHf~h7&2YmNUy--a|fA)CjpSsPqRYW~v1$GtdaFoZ@jVplS<6oHqmTMolCRHKFCB zj0on@20=Ub4q-&;HZmCk2m`59EAZGvhOT)8t$4D&zFJ-5*rUzoFJ;xxxlr7>*3D29 zz?W)5WGvK9=1WDf^o=G)snDxIH)AM)j}45Vn*g!tLf>u97C*=dyhYxJ*roO!xJOdV znDV?mXJYebj;uTw9a;JD>g-~5wUVPiPkVzZKmPO-7L{`?&WK65*3>~2M&U*Q;Gd#u z(pD9cp`bcXWk#-s4ARyqnI0{d>Qb0&JyKhMk+F{Hv|;aoCCL^|;hRc@GTFkrC=r;h zC9mM88lx2{cBB#LpyG0Lo<5#uD1h^Z0$m$H&@FlAM-caq{4j#GdX|rf6EyI8h5)W| z2=QU{U8bTh-d}vhAhh$DTp2K`kRh-Jf(8ZvtiW*PF{Mc+g!XoObKg=S-P8>A7=tVf zq*5+G(@YhXVef+MFY(F_ix=zbUz#go@#?iS;65e|VMVOVwHy?D;cdCrp!A)gw$f{6 zhe6HF(L)qTe4OA2`4&2(NsTAh--Q_;R@XA4$xlai?gGC}R+G^Pt`q3h?t(8&G@Me* zAx4^tVG#0FJJfK5yvx}M zo@aKVE&l@|iRoy>O|U^-B?j){>_j)lI=Q*{vbx&*CZIUk=xh^4WK>c^Faf0h0CseT zqyrvdtrxgzqKSWS3*Hz^_&fbhN2P7>k8n)kxqjRb@i|m<2R*ydxW> zie_6O?Xql8h*d!Gi6PP73p<#J%-&f`O1?O`k(IH(++O>F6}~!(M&JdWU!%eIRo_Zn zW(S#N`UP9mtb>SVATpp;#2xpK1DMJSyqq%=9x^P$({kvvGZ2RZo>>RmK`!v68V4o7 z5E9G|F>?D%KIJJ*Ac9*(V-qX7X9wWrA`Z`2vM#om-R6J*a9ee=O^6HxNj)2gy*rIb z2UnV@5Fj|YxfLGBVG=8E5@6q`Nr)pY2U#q@uZCDoFmq7m5T4x19`q-htBce2l&X_w zk3q#8{c~f76#Y*fX@SEwN}>rV_(z9Ytw=9voYpNlP8%Ya_6{(Ij}ECq%FBmPU7Jy* z#+q;Rz)q90;2+OaYL>x2t&&AVy6P1C`zdZdC(832#nH zW`~V=Fg2M;hJrWr#ni)npD1%Oq>9B1ID#9?a)Em|Ly3-$08ol&SL9ubp}7pY_niNz#}#Lzcm0=_#;-fuo*McQZC8^({xdHHa7 z@KQMvrqMYu|gK{iN{l^e9WhDCRT;fa;w4j+QtF8R~dQ~oHK;P81 zg1;IsLa>MX*Eo>O^eM^^nu>P&j1(Y^M4MxkCp81UEXQ@^8)lW@6`E-1PM9Z~Knp*I zpBPj#jKVNXn-<#g-;EY36~pZ^Eyyed0WV_mx!)b5#Fw0Hq=SLpp390#ZyoqvO$z+A zO$vNcdlFDZ#X_%|`Z;DCWlEThL$7i$lJrE800R>ep?cD#!~2WR;!=+{*PqKm5ocE1PY0jTH;PP;Ez{Q=|HS1{J zBJG+w=yKrlF4%kuvNe)KzxgH8#=KmiY%w!SXlnKh#-Z zDhPhbbg`0!G|;>YsZcBLVzDsg7Wk5N{2^m$6VtC~S#e4i6sq=;E_$-I4^(zAj49&% z{bH(qhCr_}0%?Yac}TR>H1RO(=YW1vJ-|zQ@6b7U)X#5Y{HQ(>jWcjU=7lfS-hm>n zbuzqNCzKomqf9j1#@Pfn%i_Q*1p~B7iAETAAp))0Je~6dJC${!q6!(H0N6DY^zYj; z@-ZY1@A-YhxWq;+gN$=XqzFrq0BI5u!Ak(bCM(7d==D3y;}&K)Xm&E3a+Q2~+YX`~N_sm_*qs-1-vx^v*nPvmZ?2VP+d@5wHyh2R;`C9rS;M z5;=QPPVg?%IT05Kqa&J}Xr4_B7LP`hld|s2h~2CrLcqJsfFR^onF)9?$AsPi_s%fz z+gc>6s=ABfs>=HF&E=UaACeKR8`;DVW8s_5a^>eIA3zLOTSxEBt*ryC+DiQnm?OlD zqOBp(z}vmU;v{ijTd!g~Tga-)y+mxLzO1aRvr9C_QoUWI#}SO|%ENJc?AQ({ zUEO+nEBl^&U7VcD;n5C{PN?Ui-r&s8AvGLIiaWxQ*5Rsvlgb+Ik$n!1-riBEb>nWoDDXvz(;s?jP^hSl;j1f!;R9hR2(=P~9~V2yo!dYIeAIkBw+s%b0qk z7Ah1f8DQIVBtjxU$)J7)@i6E1SynPTARG?PwK*L2!yptss)4|>?o>J~6(i$-$GPd5 zgi_gnPmB!*Va9T11Fy1iJilIl6a~G!lsQ)p1aPK~avTU@USb=AY#V&(`CRB#4JUJ9 zKj47(P)8DQcrBY7$e`r$=BnIT98WSJa9LYL$bvxnnhe@}u=xKL5W%5l9C+pUu-?di zpf7I2UZ4&J{2+xIxZGAp#rLbE7=PTMUee5nLJ~|37)}wj;-pWQ)GZ6=4P>y zJ6Y8f`(Xb4*ji>DZf@=#8IeV41~`BOg(@N=!dEO?cJW(IJsc}cDuz&6KY6<^y1S`$ zMTkQ!#48wRaKa*F#u*E|hsR=bDU*fK46Fz{Q$AaDV# zt?dGLF}dW?Yz%sf3-ZR92Dz$KInI7S2`hJ8c)9v2x#!D}Z24-kBLkkF6tpA*I9~&i z$v}16&n8Ze&y+7D#38L~dg<(FY6$TbBQs<^!%93gAhNOz;4(I@;3BSM;mGh^bQ zkj!R8nO$UIpeO1x0;f$K;;lh*#^I3mKjvee>)0@5`+RJS`j=juxRL;WPr* zn?~SJ?ZcsAaxwUgOzz$g$RpncMS_tQ(~Tq!TfuuZ9qFSh*{V}k&sxeFjlN`s?@*J& z!4ZZlwBgW}L*w}BZ7Ug(OotRrL;=A4q8sgQ%W)XG8t_U@a_BUd=cxI5=iz|BPwTWx zcu@w~7SmR><@4pmcKK;_eY4@dYVBb6{LvHOk%QfPXlV*|!`K0Q)poRt_{D4$s$%Ta z&P?qBbFN&!Qtt5B)&Fzyuvl)LOxF@oAm~9^R^@;&wb>%tWuT(pgu@GxK(XmYLKs2H z^?-h95cr^RE>v+o&q;Y*rqmQlQnyc*B-~h9SyMvf>bB3YAvY<(-`q zV~;3?5|z`?j`0J72Kd*s?2oO(N2B4AX80S*Cs}Ui%@ir02T&)j`V6ZC#9PCD2 zIUc1z=uy`&a5`}!j6%ezCK|m}5B-=L#|gB`010UjY@l`_iUO#t(992dJ6j3y#+egQ zBXHWFI0{f?V&EdD5!$!#RMtI&otbZizSRPO#cKKvHe_hGs#7@I-p4gK?wM9`&u0`1eCYgu|)pDdx>P6tD6miN7;L# zd-}!IYJKe?*y%Sz5Yc7sk7gS~s*x)Kc*qQa?2;gu0RySW+vUwaof+VTYYyO- zSsu&aJ_qrCW)WQ!^FQ%YD=dbWg4}CKCh$Lhd5TUm?8qZ~3d)fCc4G1LQP{GTD}Hnm z3n0)=ED+9^6@c)mG{%ocB{+3pbFBXk3Lf|OtqijYg9b=ot<13jUTkh8CcReQvS}4PZPk59S!!{gBc96qHCN$Zx zGl)9%373O}jLR01{fe3hPNyI$)D5$d1d1@Qe)Rr!z1@1R!9KG(c%UI^x)t39o>23H zk+5opoHu$z$;j<6&Cm<-r%x*hs`F430J6r}`MPd_D5m0Fiiq>LtQM&Cb3rK|RTVV^ zl(>B@9hz_6K{?8W7u~u^#H-H>oK6vC7`Maxn>7i1T4yWL?It}8+pxj2mknno1jVxO z5K*;hp&$4TPz2Rb0B}FNl`BfL)6Uq>lIakv0}HeMT~_Lqf}V>g4&jRGkOD7ZVi7=a ze88c(622;JLywwT!3LOF=w+GicZ#XvnkTrqswR)JYD(m>T09F|I^&vJ9vlxs&e^7c zHVB_h2{9Xik_PHAa63HCu^`mw@G!H7SUJup1bcY8lEpZFTby6WmR_F(C!nQ$EHI&( z41Fl@h*~H!6nyvDImXaL)ySC!5$fj|1-y(?1*x3N;OWJS)k;>+@>|{Ec{K>Qk6A?E z!!8gMj=6kLV`1rmH<*!U$_N?hBe z4nB(6-Z9;g+3^?XqwhdL!|Grq2X0y+rYS}Ys=!Sz$RKnFUaM5+3%kMnaR^v43Oveg zN_K?VtZx?bpZ~M`R4mVR*ml zG6nXb7D6ht0=OruODPmc!Ql6=Wv{P~;qZD!7&OoL5=KvgN$9%rmj-GqTS1!WtYUW^&Y)E$iXvJmPJc3ey zu49ZKo!g;ptcj@FX6B!k!>Guu>ET`6dXQY8Y6e!kmK9AFuh+j9%YN?0ArwO#A)HKT zlR$_WIIy99*P>z%DP4`4IN(7p<%y)gX)}=-y-L?I$PyD3e%x#?OGsSSL&MOL2;SDH zF*5~LaOMX5xs@^PnGQ!Nrs9Vx7(B`*k{17YvMhEnc0jakq#B^_qTD~Sf8jwGr2p$^ z|H3cJFCR3pUp&5*AsC%;`oEk%hCt+@OCy0d@N0K4c>8dQsT*%;2b;-o8=1k^!M){e)ZAOJ1rAt|e1 zAtQ0CJ$&&KV+w#YrjRu1NJ9qV(7<~d8mf`_keZ_InUj_Am1Z8eDR1u(NsDwUIq*s; z2MIY@<_12Ui@Sfk{nA=|zcV!$AQ?G=KLrLx4!o|xfj7EK*d3h*$<|6if=+ z1A%umd6)zQ?xIGat!o-ZZfU!Acc+1DA9x0`~Uf;)SfK-x)~t`rlvR@h;5)rm25n@(c~R-Tn8T%;BDb0aDFdH1-QVVUY z{gk#m1hb@*OZAj<+I~HkTXnyTW7R$3ej3LO7v?ap_Ss#l1lZ0~z1-Z&G{c{_TSrQc zHZx$*n7TR^N+4|UWt|Am2_JiImBH3s45u?3Qpy~ruHekS>9l9?&8_ka&!b9S37V#I@w3rvx3&v81SWuff~we z3`)h!f=Ng-27{s~4!AC*yFo9hlB5GJa@O&9^DE|XZDk&$k7{A|h68?T#)01&4k6!l z{u5v&|0##&K6x}BhoUH?-f~c#j6<{w;Gcl#4wwl*JQac9WaQ=5$70fsU#|c+uCWMq zfsbUI06h!~(wCeV@gBQ`B6Ys+J-jk*UyQfXP$IzbPV&8*#k*Ts2*$fo??7l3S}jC7 zjTj-q#YF}Na4|5lNazuXVTX-ypffT1PAfR{=Va4Xa-9EcE4$Ii?oqebzm-)0TsKE6 z927V-D6&>KC~)2nRv3$}O1)HtfQ?Mnia2Wp@1dN)Eu2~yG`aW+O{jsO zZfF@G47$YwY6Ic>(m?hDB8z}p2k+9X3?;-=HSt^%i--h1gCYT&bx1tZZ&$3LIr^Sh)A>XDMJa!?m= zX!b4nZH(DLlbiqvzKqRpR5von%)!~A4i8g0^cI0a^lw%{ zsZ2>1{>R9V!}Q7NDXa*_Dwxsd3rw>!s0%Mvsltf27*&N6UmF}*NVD9?%`g8HhvMT) zc6322LeI-hjQv8Iu%rYH4x74wrjGfF5h!*!&N84H8MpJ`fuG8RkDdaF5MXC*2rBs$ z123Dyc5pYN3VvgF5E{i}QtqWFfJO+qdSf1w#&zatbl193)f|i?i>g`&PImyw^dL0Z zf$Egk$LnGt$wM@w2E#dx5Ml00L>(A-{#5UoRzEYJq0I-lyh#o*X={oB&KL$UoXQxW zAz(q@)WL2lfm_e%S_xH&%!uGR&M!7M zH;Z3Zm*telwr$XbG(o`Y)r25}X&QX}`6@9CnBJ>bqgBLbm zfx|kGm?V>#1^*=gbQe$wvVi^juxkC)`b)78w*vt!sX+*)fa_}(WDxXdZb>E!U(B-T zh58EVoJAOvf){DVl}}|u*h{HSN#yWRx?LS1+J2L;G%W&WYAlongR}pB7C~q;6vK?K zD&Y(x5%5Ba1To1W6y|s~W;e>^qh?WcwrOtneLC9iL6%a^H@Tw?G_Ws$!{n^gN8wX& zyNiyy+7;!OQSBK!Y}9-6Q@by6cBWl&mh<89>b}dFJa|Q;Cau(xJ~rKu$3v1W;wk2& zk2&DFm-HdTG8FKo6AE&uZfeh9_IvEe8#af^2n0&@x>fM%bzG0zv_o3lwuT}*D@}Li z%G5!3J`>(#T1-26kEmW}TU(_Z6rnDLM%OCkOarezU!{iLY5MJK6dK%iQ$3tcA#HbD zKp;h{_tg)+#pTIZaHwJ(ytiWn45~5?oSzRv-*FM(tPS}>6^E=1f9Ws(g^PS>-|kg$ zIWMK6DxObr;nXq)a%v^BZKIs7qBMZ?64*WvIBkSD3@#k6q&mr??6|tUjZP*U6wWq6 zKSJ9!vP>{PnQt52p4UK%Ft8LKGTBCe0w^gIcsa+3UiIA1Sz-GtRs{RNMYT+*RSchT zJ`{(YzBbY=vx3QUT?h%`eI`Ueq1(-dR+-;GQ%ohu4ONLy9K#2yQOJbY({FSOg~T_* zFqEGyf{?dKgb0B?UZhEgUME6FFdY<}t04P6$dsQHz1C0wP>m+^s3{XWBdVe@l#2kt zJVrD#E(e{NK}*R}(d}nzr@s!4pqjYv7#zxxCLCjxq2taXfis{8Zj=D9Q=Xt?D0-c* zxU9Aj+$KexQ${CDBE}dJLv5J>g|XzcwCJ^$%eah zu|3#ppOv#Pg$tXy;Ip)shHX(Bi<7c%<|18zh7>K%pZA_parU{8CQr={b*2wVhTM%3-A*4SB{v zcWQiG9xihtNP`YR7c8Vk29-%{!4FIu@D&US0AyS_3PFU!0#_M3m?DuKdy=C!x-n8C zO=Zy-0=OG9jzh&5FQ?XlTezT7=IAV5Y-IB9M~{P}1cgx}NRKNk|R9gvtdnZbC zlW^efL?VU{IGvCP1KHUA*0{#EvS6T0j9=f}l*RK-H04MU!fSknhJ6fJ@ZIMtW-q)3 zwH*{;pHDP5#g*NWY{5F7ZY~yY*RuMkw+(z@GYv-xheBWkjP?&eAx%zGkwELTAB868 zMDxBa*`t=h6e+*tZYJ*Ej&nRXJg)|U=F#j$!1Xm5aQ&S?DC=`G$uJcJtRi#$ty(a% z4T0l9D8saQa90Sj{>M%w-Z|?+n+PF{!f%>Hc)5{5t^eFCY=Y|?CR9Bs7Q$CuDNjDE zFP86RVdJ~5$ihIvqq9y zDK%(qfjxe0GzrlQ{d|oEKjN;+c_fBH=#1me+kqX2m=;e^!K(1M#y0lXS|qS6$CbnI z!#dv!@hrZ(L4oVdMl{I-G4|v|w{lM1$@UR>fzyc>p=iw3Qzh_rF3U+P5@5sJV!ElH zo8gFoCK)mMrBVk*=7qx26pS$=DSK3CyBgQxAzm+2{HrzsDd~i@84FzGusC0He^D7G zQ%pnmX%6hmXu(4bX2J?!IAgQ`auqEUli5g=5gp;AT0|hybqa(hMJS(VAb6Bj^QYxk znTxRa`Rek+@{{xB5S}`!!}E7i0+<*OIQ&CG{K-Is5&&LEMFLdERX#;dv~xetjr`cOPX#K%mVA{Pivf3?srUs>1E2 zC%&Nqf+jV?2P(t)r|rrY2ipLEO=b=PK-qs{DUGB${QKon#%4;~ApMX3l8*m!`0(uh zzui0jw|n2G`$Qfv(tRQ`ETp=%czyf+eQ))`onTk zdXZzN6D<`_l0~(e97moW`n^LQ;sMHmWA&Q($}|$&f(BgKO0wu5sR&HdQc>J*PRV#F zMU3v7ACG2PxUg0MzAzP$nLsCF)!C8bn#7EI7+*UsLOtTw2;$|*X_+|M`QmZGM zjVxU6fH-tLNc4)8gA7qLkTX8ON(>1cntR|EoDW4g423+kgo3W8PYKSLNI69aobX!8 z2_EH8`q9!wL$zE0NmwH(ZaBS0S8=AZV3SK>~jI96BW9Kfs34B zJiFrT-*>m$>%uY|59MJ5!{AOTWJ>5!Qzym*GjTrJ2HN3lgSSvR;Ed5Bm_-)_5<&Rz zp`5mdCXHH!RPD=DQ!~4&Q|+(Um+R&Aa`9$)(c5R}udkn?4Y^m1E7hcgZ}RjccnbRp zF#QzSW)IE*Y#ogn?5eX~%}FtW^za@(sXB5!bc$``ZuOIJsSN!lMZX%CXSOIGV5gMN zuhQfC)fh%v+roAu}A_2wTk%NpOvxYB_GU)|t1jD2g6fm*3DAe5H*&Z{gF zcZ1xabf%_(ub!w8@W^r%sPXsJ_ImNd)k+3c`pM>Kv&j{kuel#NqE0*z11Q%x(30;` z7AY0m^(94OlJxh5@o5ZeGKpbGYed=LB)t!jA2lY zrKB68LXcOvI1>B@9%~8_SAfTV4uWjroavc92y&N_=m7zl0}5t5PqXX;89kEr(an!$SAPw& zw2oi}De0Wr%jg^=_+uns&Znku>LkGqb9ykyUuW(4Vgl#kJtqtrJ>)x`9chQ8GJ+du zsY3R0P7Vf4IVYQL-yDv}q9mrDDgdYS2fdV&6QfKQLHo)*X#8|XTW~A?O)`e#Zb1z1 z=&a$*^7d-+M!J!|lmpc86hj5y*-*icO%X!57~i+{4*Jy*J8D2p6e1OJ*4$b2@AT|g z9jVrf`TOa*a1@3epk=C#K^UHFm+SY7=NlP%;!A^v?RddK5h`N%;06VrV1!T_01EtZ zkC>_KTI1~qg2FMFLq^l=L7tXUiclBBFBJ(MWj~>Fk{)kYw?$uy^Ac=*)yWuoopION zGVoK?2geDLdg8VWWyG*gqIndogG6A+5P%S0Aq%`cx&4HcyC8*&p75L6K0@?_2G;hW zp(98^QJbG;-nUt*8c1_6WqSZ;0<9)R2%m^RD}2KGUf&jP)~oG>U*pUt+-+9$%MZ>x zz%6CPp5}4%pb!bNtQAN``@n@}AIBl(#@A8%fVDZ%e1t)Zi8WzO&j?#(dC^hiTjvDN6{86^r^5J!-5<^UyNd|0S3<%YYCNaiR z&fff9MP%SazbF+K8pjWt9z<8he7pK~y}2rOzV>w(#|a0;B;m|9f?w|8P0Ok+HK(N( zBiZ*dhV-WDY$ll$9%wtWqVQVRN2pcv#)aaF7A(`5S^W=^(qpRzi3Mq^jKstonauln6v0-j-k znJJ}P`@Gq5Lmvi^Vi<%XDahxpvE9@sgMd$FAgDG012xjwDs^aU92z5u9-FT~WEfKl zsWA-zPo67;{CE*8;@E)0#0EK3KRyD)Ef+JmX5&3MV5@OJyOJ*{OUOm+QZ&)9AwfRy+c5Nt}UIu}{nOZOLS2YV4O5oOl%AkiMW^r(G zU)KsYKg#=&5>X+Nk5SXWb;*y0DNAfUYXkT?4hO4~EN`w><;1Pd^iD^Mewd5bG+HPE zgGtX%0n2b7kDn3c#;*8m89Z`{u#C-+!T{#;m0gaG5%@`pv~Z{*5h`c6zIGXz5hKPi z#)#w!lo6~h%+!cbEW&%LRt7?7(o;?LbvWR##-Z;p92@Y{Dw5DYPP2?ec*TK=+cb=X zSM553VX*bBq8WDuIKM&Stp~<4^O71TzE@^LF>ME8N5}l zoZ`0lRV)U7GsNGONt}ev1xU6+fbZpe1qbN7T&_R;x+;1X{01_xC&LE!F_AnBYA})yTBwi;zny>|R;E|(=beBnt>&oC2nhbd%`8799 z)%G8ucnF}!A}a<0ZHCayJ8R1vo%t!S3IL$cs5=`=f23pRX+UF4FL0D2ik~(sncMYg zS*(fY5>Uqr0@BRK0d$Q5u-WNFciBl(>v8|SRUX(rC3@G_KXC={*m@|o-^BfW6j zRk`I9q`~Y1KTTqa!)8?FaKKw6@sz`zIE=BO7fp~{8H1n{c;kdaC?(^EO~d&6TDIt0 zHR4NGutFmZ2{f{ueBn&3oS6}O+C_;3gZ5^8S;kd(YzK)f5p*ZnF3=e%bvtYT?exJB z!ok2pB}6~jaD>325(2;MJ0G}>} z7;;z}MW{!#sgNjS{8~d#`@xw4FVk2)*u6V z%JA9(*{V+gyWC1wmW+*&r%2UY?p6i3uYY=tzq?=o+{Vl!2qzl4Cnd&!IO)t^QeF&7(RLnj?FMq7RV{oaQLTg;b^X}U z0S&ApnqU}st(p$mF!&|geslzd>=aZZG9P&7NfI%+x9z9ZZ@(?7z;x`GS}56XNlsS_t@-cL^iQSiwb3K?sJzyS5+br%)lRC5*4pb_}TfR3mf_ zaVtXxKQ&;`uoO0hS$q(y#0WV|5w#pzuD@pxSC;^GHtoj(y2*U!uFf0g*rWYZe#zkYqP*7%vPHFK&b(Q!O)`7sIA!6uv zs2m1bWR1WZIbVVC7_#8glx19~A(3V3d)j(#8L+L-20=OM-?8uO?s#NkYWZHO-ru)=2 zRGx=*zwpF4MuG2NhYgmTlCWjK`A+R2m#8lFmR4k0(q>=zs0uekX z?q9QlA8!{Hs)gFqOw#)uOp4`H@lrF!YITc~Xp!Rscjg%tghF;FV1f}qhj7X|2d>NXlF`uCUzQds1G55^;Eg?R>#xw3vlQ0{k<~p+#`oRM~ z0ECFrOW|>@w;)OrPMg1uT?QmztLpR9^4sG1?WfNlw(FJWqF)US?J;aVu06 z3S4A#;2V<-_&9qw?6g;slEE$4sk9hmdL-EkF+HLh^;UQ}XO(Ft;hTCpL|6GC0xC}U zz43(Ort+G;c)6Mv_^G)pm7UqdI63y~pK-MTl_yfp$#g%E>-uiIokQc7<<*U>7hg@Q z#FI>;JhCq<6|F{NVgc6~H(+7u(Akwp{V$`Rw)r3|^e zzW8`uQ~{8)@A6%pRj&bw$psGY6x8*?7)4}Ii^vL`PB?_oQFx`MT(C-dE*q%YWItmB zB)H7t-A)fXb5@K}W3q6K4_#p$jRK0Wapr@aRgGH(ortEElx( z*&cqH94ijhd4$1(ZIR$dqgC*w=UYX}V9a^Qcew`=fMxKTd zlU}u>!3!MRKlTelj%6BW_xxOE7TQ%R%c1u)22FbLwkb&P%RC{JOu@AC3gbN~_)%>N zIf&8~n&#U=Ia$q(yyva6F+_if)8;DrnMRPvSKnmC)T@>-FyX2V17FJwBd8>>H8_BH z9Xu#9v%~8$@8Vb?br_^uhNHhzLL8q&S!m^5FV-K|i@zg!__})k!LMoK$C*u}$PM1;9A};XbM8kohrg0blS*%CS+qG!X2`od-g_bmRU4E58*45PS z&cXn6XT!L>!=$RU$y~)B2NaW)L>2^b!Gl&9yohLRY-= z9S#OON)ylT%m_TgF`{i1v9}dJQa0dioKWgiX9Qt|RkX`f&MHFAiTl@9kukt8_o&tN zlJCh@5m)r-Q(?8;d|EADaCE$ju<>z67@GV@K9^e|uusJ90Up`G?UfK>`|$sOjuCxlSrJN-;9$Q>qxX^(~$404cr@~{YU zvvt~Vk$mOD`^S!-N4Qeustj^zmh@YUkW!HAlYYEz3UcvHOq`F&LnHJT2&Nw{1g_Vt zRC}bqaH3y}(<$Z+VBqzd ztU#Zn%r?DPUEerDw1XTVn!HQd@zmx6fKwp&;`2);gQMXH?2JQjBqM901wYaiPMH^Y zwfaql5y{e5p9CntWxY;lTTKeI$gr3+&r%V9YgJU5wy{etdNp?5GV{U(7C2*+=$2Cn zO3s2L7+Wo#UCCCf4h39OgMhyp2thP)rWOeNRSBU-O%V9@?TK7^YMQaLJmF&Fa z=P998qXekx=^E7kk>&JX!*m3p)5v}a(tRkK#E9UpH;ePnx4O_mH7()%(K)_-uB4x! zggJOiK+cAOD1zZMv zkczC<$As5XH7K(TD65(StLI7i*mV2o%>7fE0Z7d5Pe;>?LwMBMLo&KyD5--?=6iu#!!fBr&1uaAR`w!LJ)<=TN%$*?zn{4uN@~u#gjHX zbfZo%@gkxZS?rR5TQouYcZ0C+ICv?D1YXG^2^={XWVuWkBz^f02`@boK+BN8o10wX zHb#jMUE@rx5!&b-*%fKsPAmrhsE(8bYxz;$qSlBm7pN8v1Wa|7y{JFRQW*{ew5eu; z!{ARzt^x*xbUFr86RfvVZ)j}G?jT^h)gT-MlW2hJcjlM5om#I|?qqu@lWbLlfmvA_ z+EnOHV#BLBb9i+1+jb?hJY}tr=eIH@p+ko*7G=kZOKqyYm6w|@lJ>k`T}db_+v$G( z+#5sm^3fBtDQ7;c3Z~3wYX1r&Su;>zmts3rYWgpY{^`I>w=(!?0Twe;tF7kqC#Qaa zqr-btGqRLjD9)Wr?o+Jb387K#E^{BswT3mcR;&8>k|^Fi#c@2g=!KD-ebp~$n(tx3 zg|$$r`r=x@Mx!B;o0g3m3Sh7Hsx6!y)RgO8aoEPpG5YfLXdggP#gmG8VW~?P$YnL8ZB^WHj;x~Ja>;wuw49#+@fG=kds-Ff?`DiFP}o)AQfys zS%HfjD;}?|u}HN1=l?8aH876_KC@v#F6=6pI4tl}!$J|D6OljefkjCRiVm@$?PXR( z-NfkxM)2@>(Pkz2?M5~f{N=;?#`jD6DJ*DL@+vr-krLyls+)NH9ys(yk4(0X{wNx} zo8!a}+vW8~sUfS&=z}h|ti((OCzBVTm0eaMog~2kAJAcp=Sn%56e0PtP z2uLE38VOopyqG+k=H>GGVu``(iyt@J%fhRIW}|>i$^$YWeED{EyekME#xzhr)qxBc z_YDQl-U&!xh%BUdjX`&Rmwu;f*}2@?i$jFGP}#s!%v>fRf}ie83r0Mvp4z9|DfGg! z*BX^}s$XSF-f+^1W|9t~Wxs=JT*=AvjY97D@wrJ)GY|Ey|3D{Q3@wAzsTt zz&gp3ZMnk=UU(D$`Tk`pWfJ^45iZaO(imth=_P8Y}H>R`&1QFWktGvnALj^!{FH)=NuY(_^idTaY-9no6CCAm6`ZNjPz<1x43hUiqnPdH zk`5YC_L>JJFI;pPHzv+#;FX$lqGG}h!BF7EELH42j_swlFfWr^oCjzP&%*K=&p_3M*sf`3b)mTc8 znqZ7cMp6Q;v}YjD9LEgaLP>!$34<_K8t>TbZb$*u8|jn$+(|lcUy}+RDhi={7OfmZ z!3if`Mxv`h^zygtBpoT^Rw(uv{26;EGafw3ZUk%S$tqdsz4vJNMX4Pe__0|?fCFd7 z;Lr@6q`;-K;7GYuWQN8V4OlB@av0)mg3!{8cQU=@)x{GjZ3-@4F#tjxaQ5-Q^)(*a z^dVoG?@3A{Q2>Sj-Z_#KnJW@_Y|6eue@sS#N0}HZ7Jrv4edUP(+8YiytUYQ-yl_{; z0f!cIszeMgJ{t$sk$n=cn2zH+b;63x_I+!o6~_dJ02*hO5fErI0e`(yrKU6r0y;hS z3Wd820*^8YSfp9V;Dw8io3G^}{&-?-5cs{pP>uUL*JP*ahH|vpb%B+rS-KB)&GR|bK@uuehxd3_ zewRb_XoFgXSdV#)+Gvpm@n=fVa@{Q4&hTevSW1MP-#($^wg})0n&$hV(6rhzRi|<7 zNUXan4>~#aOJ`ZjanGRG2Pt7vnHq_A@v@2nE^;x=+l?%pCB5cl+>duRaLCB2svL$< zv{g!7INn>yF*~F=J@uWwp-nfZ~ADDXs~d0Rg}!4$=!HyGyLT_`&)d<4YZ>UXhNXd27|8~6Jw=Jb{^TzJ8^$+3OAwdvje9SJEqQSc`L)N z9&csci(+{Ln7V<1n^$Eypj`)XP1}F{q&rB z#cvG>1Pq2mFcK%7xZXqq*ZHE(&m|`==e+@}QR(daQHMh21O5Me zK9pI{K7KTIOXBmk{iyCjBioLAE;_2ej}uXQ2$nHhCd$yC9Gf2c_t#4%J+l9h-ulIx zjSOwMDSE1%L^#9Xp-MzZe{k2@dEolHREwOHG7BWI(?JMP6!ni73WH~=eQP(X%kBjZ&$&~7}zSTpnC=~^PpSKNQ}Hz}w3!Mbgk zaClU?;E?nEk$rI&VH()!XkXkTtf_Xhl#LX=b{3j=^e!$mfhMK|bZRdg45$vWx!{yq z1ZP)SZ!8{ghg#WL76*0>`H1R)&6H9?9OG+`=by}+Q$L(v`35aYp#pT$jNp{RG|bsn z>8FDM^sl09*9|utgp^jAQQ%Q?t)jo88p!Sf2-&af5+j#yZo<@gymc)S__g6PNhlQt zcnr2;zVGbipbE+$5C>@y2BiZ&qlJ`lH8sjYt9JTAc458NG2Fgl*vU7*%%qk931DOv z0uYTFnsxyRM-5ZXp&6P?==g0tH=GJ-DA&8Q+t;KsbhV>tz0Y(ix+H42FCPYGXNo%XUDBqh|2km@5Im(U(=5boAl2`Fsks^-D zdBkaPG!Bvj{BmaUbaSzIyOw2qJT)8+_*gX#lX5RL3=l){X!_AHVx-ooT%ZZc3lb>P zg11lvaK;!B<0-s@GJ@R5{!><^UA)-H7%$(ifQOBWMCgdbgKN_ObM6vN4TD`30p9Lz zJjfyuc<@e&5i(g+RuL#>y5OoB2O5V)l>rAQ5rD)bf}f7|u%|?H9S7|zQy~;@@%BcC zxIIm#1CKl%K1c@6;Xf|2^s_|S`l@6XOad3x!jOR=hvy4J8CGiRVPU*o#-U>pVT5$X z0Pp2QL8hI^yr-A&8}GlAnI|4pOCRIygm%$g3*Mg$2h;(+}ud? z`9+30zV|BGE=BaAz`NCyz(=@KDWXimvqs=WoUdq8BGhmDAbh^Plrr|Oew_`pr=|j} zL$yJiWjWT*Liwtv>DBt`>+<%aC=fPdCdSoJyH|e=LH<2xNec3(_A;^m+&%)nUlOPs z#GDW}wM?aHE))(+fLooAj@gxwPwx92D9*Cqu7mu0$L@o0`3V)OvXhCfN{gnrSqk#m zbc3FOy{ZudA~VDBJ|_HVjEYXCX;E&FMD|JT)zeOoik>zCt3E3b^5B83nq<)&+G=7Q z2ixHtn!fwZ^7d-+Mwav_wc0>YCZ>rQ2S#tdS;`!$C{mN^UA1?wn8xr^wZ6pAfA%R0f0{AErdNezf zJ3cH>$d;jaEn$HnAO-i1ggKL%DrH|}V06rjikf{Q#998mQ}MloCzy>4K_s22;pkZ40~ zAa{i#w=jnKct7XH57OcoY9OFsif}`8mr22+9N|7fXTsv;I&8oIpHOqcS;yhb={#Z5zP+PGMcC63T+X zzILGy>gag+CJ6MOuQnf7Gsh5x$-=mQEf4ssLZC-;orWsMK7+|ZF(@~m>X0pgOThdn zvzYwUM_gThe`g0((R>JBrxnf5-rd8)Y6uE(IjJEw4Rs<-@UBUz~!&^MXux0SUP{d@&3-&f;- zPiJ^UpNUhoQfl}FHo*xSK}<9U2B;jiOXw#jI(5sE4Bmz0Ad4IzWq6I}COpDqg)TKT zyneUBfE`B-KamA*Zngt%CetaW^t4mZjD+&PNsVF9QD?Z$kde`MkYfZxVHp5DIP0#Q z6~->1LFzxz9%m=#SF6?MZ;KzUHs5@|s~_Ok1|w9-(Cjf<(8T9g3)9zDT4W$1D-vK; z(iNj?{7`{_Wji2V(n8KZ$tXWh3N+sA13#^^&yaE9qA?tl9!*k=nMYYZ#;eciNa#ir zfeb%xSMNnCWM_n}Cj($N3>+7DsJT|)0`6L4z)yCUTx(dNhDJd`srw)r*(g%nsa(Kg zO(710LZAi81$-691=$f)s>jc7L**DAbyO=uZzB}QaKKwsH5nIB;8lD;F~)`D9n~hf zN|_U~8i=3{_`nJTk8*hVW+lPb-&b4Na{3=DA7UOlPB=g6I1#qY#hIED_^Afi%8uD~ zk-oKJkvX9jk~xu9Ev@t+cCuwo(0^noUdjk)ne+EV2DBBGh;~5XfCwF~Z6LDO!1+D= zXW6YnG-({XU3Q=3m4bjX$uL4xg;UB0+{hWmPr5M+*BJS*T6&SdZ+3}9Sxf(U`4Mw7 zs#^Lmf=A+&S+h1cj3mYVQ|syMtv6y`R(y7hFJyM7w(zn%hrHr?RILsH<6R;1{KDs5l$OAP#-R^dw;3K;|+_~U}9 zVpTfukv;d4C2RZnxK3b4v2Kk7XwKO1m2|wgAp>F?FhQwYSO-uNAJ}G~=8;hSa;5at z3XAJh)v^qgZk#i7Is-+{PBddQIMC(}NF@I#db z5+X~(tIk+--@!xzRD(qNP&h8|)&Gc4UojbwXmjBxM7Q`+Gn&CT^u`!$Cy)4Po$^iU zMc_xfi#?>2Rzq8|YeqyDQa*glCKEbi@!KZhoMuRSasM$Fp^mVr3A<_`sk=yVpxDm9 zp_}sIjhrwxXD#6yjtWDF?eUSuP}ox$@5Y4Cos_MCoXDEco3GTS0xhHIB-sag<&EjAP5S0n)S~A;Din+gs zt~Y_5LoRrd5lepjIUlMaWYN8(>j7xD3SM!@GM#Ir86@uGA|wC!+zimmaDWbcx6r9G z9iE`(2znU#rCAm7dJCXWfay@m&;3^R@-oXlEC!< z&2c&~@WqovhEdl*uhvLK007`Foy=-i{BUSJsv1A&a(oORB016=X(flpqgx)s3%VeJ zY39pYS!~ra1IA%s;QC}UIBfchwgctcGM*g40*@7#m`tm-WR*BN(w=3-fi*3{z}q<+ zNok0}0If;12$2N%)T9M>@|Mc+_DfMSb{5jXAy`No2yGWg;p%ewM^}g$>Y)mTGAV;m z1`=*PMQW=26+&w<{SNf@^H=1H64L82GAYBQw!pugLuF#?#|skf$mp&;|O1 zA3gQeT4y9N`OLJyr*c}6cCV6IT<_nPQ&#+tSNzHp!=VvIFF9`DqKX?NbsLRc>Y3C) zRfDinH6kF%pj0&goTDlL(s};IcXs#b3=nLQMi^ zzCQ-)Ub1(Yk%v!gzwU7-TY_$jA&vh%nja`Y?fw1`{3r|K{bMc0Afw)7DgRJ!+n7;`f-K0&_F;F%|mGbhim=TMEK4U#O7daM=W<*%vm~8&xD(*x61b^ zqZg*hCeLwLVGdvLwuRUJxRRwRs*NYP$U-g9#L9yF;H3^-%HtqW0uU0z0mpM83B9g4 zj?p6ri3L2$3cOMW5xhY&yzT8*izgW%K+_n5C$zI3KvjDzvY1)VNU*PZ)XsYDKlE9T z(ySH07|nDikD#obTkrV`!AooJz#NuU#?B zQ7E2fBf(d7M$+Al6TdJ{07T;ipf*l~RReKRjRHA*hu)?9sWqt0f}wy6){!qX(UjLQ zPQ)-jb0T1&0ZqMw7qqF?3w&UMA{0pQYBdyitT_=LHGN2KMz5;gSty*Uvy&B}rzr~s zEZVW6wJRx%x<-NAXqHBtS>PgbBm8V61RlAES(I{#FKB~wcr@@~7~rmSz{7UZ0c4FJ z)Z1~=7mWfD^Z~c6>5zdSr`s9dm_GD2?wVm3U9rR~c_iAqv$pL5-#x_{`g5f&f1vN8AUaH$HlXMWar9dSoP{`v~$I6i?evS{McK2`Q@W9E1s3H6lG8C zUvcsX4K*4ZXIJ5aY&8=XV1^)@0^ow|lVW7bAs2@>fl#f;3UR-z3xS6!5l0VkZ0SGi zjT%iIQ6SO|pm?+7Ao;;C9x`C{6Uyo-T>#~BzWA)@Mn@ze5?O!SlieK#L`rxRzVCB9@Vz%*0K zFkA=EuGyiKAQmc|%`jr{ex0$6r$>H{8*7+qBqU!BBl;`c^f!nlFqs>Vzir_?u&fEb zkktohN`?j+nv&FvMqmbBtVSc-%AP4l8DzU5uf*8_kuFqXfRVb#G>S*)VZfQ% zR5Bd6$sU3CVJGw8?YfrYXwhGYI0W9WmbbFk#A*?HXVHXF&6os)vcY)}cQ|~|=>|ev zXH=F$3f85=qk|_03z_6qbj#x1Yac>ZG;=VVsWn1Lj%Mo-U5LVx1FbOxl9!A*Iq#9F zz+0#~;EV}o+z@sdfHp}nxRyy5$Cq2x))&~bd$iMLH8U$^x`}Xc|ph1)eJftrV z^hVRE&>ij=aNxQDuP%Np*6+vb*C61p7EM%%hl}wWMfQuWge})Mo5hQ?j!iYX>ip3Y zd{H;L3NMEwDX3~1?WHY-og{CZdqc^4IHJ zM*jGo7Vkp&Q~$Sj)rcQc?1IY;{L|3_J1(pV$xxI4=+XT49~JA{WM>7KU59vdRyb|8 zc^Za|K-?4oVv%i=ocv|8xeSAeASmMk6-(>w>PYrhnikFmAQME)D@Pj z5oN%ShZz_#y3M>GEZIhE?SgMtMKp&N8xFXsi3gfxI0RPUrUnBJjTLGYa9Ik-TvB8W zh(4UKW*?p}tit>_ql?WaD*_Ol$n^7-Y%X{y&ILZiq_oWmhsC7AdZvU97~G?jdg5n( zGB<6e!x0Rp7ROx`&@23o4n7@X*5CFzXSoR(n)c z2p9=I)E+5M(g1}*+mGAfO`Jm=sx7sNgbG^L3cQ;GLSx;hvK5ar4|s561b$e<5KwT8 zz@bS64vi5!noEdGdSe9c@*SewfB;s$d2zK`Ul&Y}XVnN03?)Jp<(@B7B>QJp*B_2n z*WXAjkUJZ`lVvln7LTu%|Lj%R-~9ZxjIr;5x+%t@ViSWE9P?H;N*di zG0*6RG?t)=b=85h6x4whV^|oNsZJi#Ygl!i%}#2FTc;o!tT8=284a>snt>aqXi5&% z7#JS!;VK>!Yaitxn}Fu_<17AF<-XBNpk>sq6fd1(Su+3h<>sv6Nb7e z+McK&hY5!4Gc}T!PXwEsQhABUgH$U}EZ_-J92Gn!5oit7cp)q1>3UdB8~yB8a-ss_ zFsv1T4^;aIN;$Kxef?#*wT3GpD>rgP`Ox-%S0oC58A<`P1VX_uA_osI)*si4zso`# zvSr)*4~0@XXMvxaz0h=2Duc6dE))0}F>t8%!S6WVK28gP$T|2l^9Y4w{HPg3=}3(y zn=2V}=}3V#nP{-#6(<4)xTvN?4x#jDwr!MQgNvYuZq|tGS?jXQTD2!2{Qu9$&(ap0i+k0+vIy<@zOO2AXPg9 z$MrP`+VtVu%?F`W36+Kc(<~C5K%zHBAK%7Vg!s-zzdRmh2%s5810D`xb!ROVT$F+c zIaFVvSj@EswoEO7H<+GNbh9d2L$(2}z##lC+ytIhHy^(>0)!eRt{)@7AJ_b$|Dkpe zJpu#>WY_kktemmj%6=lwQ??<%Uk!pPu}Voy)pzi>o8^baPs@M)6SuEA4gU7-5&k}O z8gOV$%4eqb8E_i6eF6BRB*$5ZEVY?S!Gn`xOuk#}*LG#j(OVhE`aT2h;gLoQ(LFPC z+ajOwEZ5#NNr>)PYhd?@^%!WfByj!yj*|=D~zF zJ1ZFDeIg_e~GShVot;b?|7y$0nITcgzR<~5Xyrd+ z0?#xYc-X9Z0jyda)Dni|Yd#L8O{gLax63%-3)V&9xE>K=UcNg*S$kEc;%COTK@^2?FktJVZ$Dfp)D3 z*+uxsk*#o34u+0jql5k`f8_2k`v6DGu5fy1b_G*+c>H)PlMZAlf;CJGr?e-{$B#}N zc&G{)hTY(d;QxTajl<}qTFv!?oDA)%qe(}T@o4hVaJbDA_0{{K|I8* zEQ9}e<2O!$m724}<+YQDL+hPJfJ9YV#h#t{UgD(JcSzo33?dzH+URf`LTQ{*AVM~w zo9apsDa+Lq9@8&ogkKvPz$jUW)P8*R4uyb0V9N#?^R?ks@xD zfm2Pve1UDZWfdcIvM-(Az(Z?Rpmj+lN|C^sniY2yiM&_aK4pi)ZaI!{tsDkzMmP-mt`irT+rXhlmU=YTGSsQE{k*{pqv-V!;6C0aZZD-IyLh}^;=c|6 zTsKOBs+S;#@>M^<;|;dxSv*-?-K;MdqEr$7;egU+Q0e{yeuci;Gi_91Mq zZ5Yz{3a%-L)j$Tq?~U`MED7=zwfnLP@*fmiezDN4_E`k^^vG(QhY-&c;kB~D)Wi!IG)JM=0FS5zN}Uz8b;LS{Za9ncRn~HxABv;dOjWvv3!`A;v z4uq%Ttz_hIy;O@I^x}ZP&y5#BvvE<4gQJB)GFP+@y3_9oth14{Lzvo#i|b;)T4n^^ z*@2N<`1574N1~s64@O#`PgFGXi>n;BLtNFrA^d2kJ<&q&<-BP2HVol)4vkb_sS1TL z@j;84it$uxgx?mAq@LuXTzo~73r7pWqF4mAz0dcMB+Aj2R0%oE68?LPV_puIH;xc)A^YLN;=bp{M! zS{55mpAo2l?q+N#d$s;jNCbXRqkz9AC)y42@v14HxFZTfn4qa29&hm!kGkFyuV6+I z#?P|(1OnGMn|LfCzAV*=E$LmD>%@T!sGa9L7?c)sU4{#(c!TLY2>pt=^*qXH4z~<_ zA?uX7w&RkT0{Cl|gR<{H!y~PY->^!C4jp&V}-o^9K)f%K&u@?6jQjEP$i!z8`QP&Xz4@txb;UgfkaG{BY z+RI!I5jb(kEQM~ucFM@?d06ZnL);P`!B^r{6%M?qgF`k}-o9Tgeq3#(Lg+k(-!R0X zL6&GN9mdkPsezxWWQDU?W`j;r!Ef z_0cmTqrkvHU}==M{wExlBJQ z;>ewB2pwwxYYYeA^c_(ZzS(|UyttNJwjJib z{`VJXN)Gey;iXfUKe_h+KeCSj@k?>O8YAF49`WA0RQlvh_jFT-^?-LGD{aw1oIj1%vLKhAD9xY7{}=_~3oL$PBx5nlF)N|HI3G=~DXGc~5a?j`(CBh` zRy;*>eT`*Y^NYn-S`E%<4u$6a8}a|e5o8D)lq(kMV z3%zN^vTrvi1AdkP5F5Y1hMyLS-_~3RLhZPK>x~Q9Dm0R7#&2VhOBxKQs*uBajbWTo z9O~i&8oz-lW)yKVX}qVY0*-R(ixKu%Z$(ym^Ddh`m!pAf=r(Bs0ZgnG!lT(hl#4Uz zptI>t`)F#|A~}uB6vlA5kL7M&(3u(rQllBV;1iftz(9--ceaX@%qky7vfd;j6(OQH zFpQ`B4l2n(L0S}*Wxqkvl-uA!!vX|1+u)&M5p)t~3=05YSWwPlmx=OZT4AWf!Z}QN z#z<6;&yhSv$no%^&1m+?2kB*&fgrC}%PU_CArB1-a-l8|hmdxcS}H@D=CLb+=#34+ zBd6if&vJ-N7K>-wYgyOs$L;O)Z{=7iuxZpxf@}h`G3Q}?7-G|jHN!H(&#?q=mtAN( zzKzR};z!B~9%U5X$)LHL#k*UXvFGgrSM9=}jBQg4JFRs7@ZCK)c=-p6_*eP&9)9?r z|KtB%{N+DnLq>_g<|Ho>(czyf+eQ(hH(`T>C zpqhI&b%JWpdb|2^d!XJ5220PZb0PJT002NepM1gY#ekn3KZ;(4)r+Zpeq zLA4%c2U;${(p{7Y?NBZ-EKiEarq`0lVeWH_=b%qQK@P2Wu6Q(cVJv>Qf9#iYx2elu zh{|Pd|5-602{C-q$S|%LpP;FckFHj~FP>e$m##!V9*6x9wM6#4MOz^?io}~cIsmH4 z-3SE|Ru;wmESAVnJiMnpF6uh{wg=I7(qp!2g0kEQ22Z|TmP^|^b;PUJjx&zCU@$(W zk0ZhLE_ZRq0K2==0$S$c${&_DvK^?1iH{)gjcbVnoT+SH_8~Y<;4no~>MZdQ@zXsj zK_|Vns>rNej$-oA;$N2Eq()pV-`#*Q^Jfgr>QCIHc{q1@ z@b+Nw^!D1P7i}WoyBZNf_=H>5p(Nj!&O)J>Et7YO7y1C8bVVpghTZ@8YfkOeW;3aaHdD&@H&d&4%C> zpDV$S<7ATV1B}lp#gE2Rd*9WWPm4*lZnGlL%B8$viWEMJF*A+~=`vH6Bl*9#vU^iK zu$YN}!Gq@-I(SG5{ky>ot~XXNAg2%88;v8s0I3^c9qlx&OR07WUL`bwa^szp5YbLo z*PHdt;_>SG|13XE83RBi*W%D&Sgo2PdTMs4RxzkC*~T%O;uFBZUugoU$w6lR=~-bU z*+zKJtX=d3iU-aZ9{r)k0gu<4?We`-_3uUL)DwfIqQxO{M4<@L9nKgKxZa4NO-AEK zg8ea17D}he2HX!6^;VKbT2=ZT7q-WXG;_cwt4rCRM@9_&j>Q(8S3ks0l7`5k8HrPn zD)o`8YB2Qs;^mbevm?FkVl9&h(ciKOSUN|>{i|NDKYjkNF1CKaWd;KtXfW_lDKTmF zB5gZR0A=>bd&+>Ho`q$Rs1&LDp<+ZI2;9N}!TyzBK9oaCsc@rg2oWzDY-9jd$&s@0 zfecXaBH$eYx%Ozfqa)wXl%bf!hr>`n&h7(m>|l7gyuOf%mz30g+-xrknE+sh1%6ah zViFd7UAhk?$85tWRU(VTBpgDGD8s;`oC5x^m2py6QW<%6`E|Ly^l`tl4E)Go;Gy}A zt`~KbxGM}ftj8@T%k zW^&339e#3>A7Sg%!C?^wXW)!sfk&8eXvv#kXxcHG6)9A#`k_fUkAz7x1$ug0mk;-ixcLx@N zK#m*IFaK3Ms8o}Ei3GZ0VBq@NNJ10=@C*yQq7egxywfm^)I8;dN(7WjbK1;{>}M4S zo=;gBlEHRyZW^1VbW}*BvWxXspOU?5CXhWA6y6 zoCC!(6AB^7#;K-KNJNFTbL$cfps$qz_i4j`W{!Cf1*9nyY=Q+7dReypvX?=Xp~Z(} zM3qLwWlQj$O(cH85@=OF1D;sJ-~_X;5ok)q2DV_fAB}9tI1sOF7i~6#v5ojm(D!g8Y4kOWuz>2E%4R@dv_7w5OygHx^A^5*%^cybzZ{IIFf zx?Ia^DGd2p*M9glxlt93L$-s9YFhAhX&WOz?4m`=V=TzWlQi`s)iN!zqE{X()(LTs zrd(s{y;R4ZF8{IiwD7Z%@zi8CIBb@MV6-(HG-bj8+uNgH+MR70pcp#ss#q=##KtQr z5?q`=!GFoHABqyS+2ePxUk0%NRi#RH`{C(ZNqO{r%2BkYL$5qF~~* zA#vC(r3aaKyxLw%3B9?$8v>6S4Op7dB6K{W-L-vC?T>bmes93eo4Aq- z$6lvYV`^Ul495%s3SbQIyW=`xFgoN^V(3-X*pmLhMcF5(zAxeMUN#7pb$zOFc33Rs zf;*{M6sZQAXwej$N)ki@S{ z@%j#cD3H!&#+3lGL7=71WxiV8%2)&$cKczqD%WNJoCc3aNB(Nygj@rUsO8ZW(X}D- zgQ@$)$_#F48!I3W)QWiUyb0xT2&mvpj1)M^LiuX*MFucc3*(dBi7mjO@|}mFQvt*` zJiv%rCjr^M2qiOp6eboPP!Wrq>bz=a(!$O(40@hF4w)9sav%ve0b5++3t3$7a_ zNAR7Xrhx%m3=BsO7hG0~kpdzs7~2fRO68Sh$1pOvMjsXa>79ab9RyJtFB9ggd^KUO$V9x`RH=90k6!% z;_I14u<5FpB!;pf2qrG7H6o*cXYQewsdXw3*&reci8WPCA`V~5DBu>(bv(IReUcTr z>z);eTaAR{LqMX<2V8HX6#7W)s#c{|DjI~8@<57ZPK-E@ZpMLII5ggI?a(L7o2ym1 zs)J(?>I z>O?}{3SPp+5W~#TS{iz5;|Bn^W~S`9^|7d9*yOA; zRpGC%pQ6FJlZA;z%6ukwvOq-bQ{XVAKAP{q#n>YG{1^n&4a3ex)f-1utYZ0qF_Btl z{4V#))CVTjNPX~H7Uc5KdRf?XgCBoSei|)GvHj>MDiGLEz+sB!w7o!qzGlATK6y~& ze!2a00@)_}b<=9KLU}z!059j%NhTXg8D#Np{RK|kd95NXl;1Q0xe(jZ-PiW1?&=O< zOqzh8QjMfrvp|x~fsyKpYNLR@6Xp|wR+#^Hsgf;z5bsq+`GvOV#PkoZr@TOG94} zp^gl^jTaz81qWHZhm{lsL(QS660|=@j1>7);@w9hkZ^+G zh>%bcF32t=bSu^KoAu}A_2wVK495w4LbDLG!6@N?2$6mp2yH&y zhumb37`Gf~n1LX|T}I$KlZvD6fRUh^ZzaHe#Eym1HgK5%;cNqcHAY0ylryzvlEIV; z#|ZsnUhQV58!gdL3?|d!FcgY1E$~WCDIRaXi39mX2GGcWDn|*I_OSVy_7Ie2Uo6hl z&NNfPnm;ta1r&IyuqDTEu8x|FUqzF*qex7qL`-|we0mSOkptrV=5{Fql7HMT{h&Gr zgCm54A(RjCUNs?bz2-c8@7WUJxI&aN83=3FG|*TxjX0bPuha}FB;i@fFonhWjSMjS z^|sh>xSau#AI-`L&KMdvOc9nG{viWT33CR6yi%`$GYNKrCkygy6@eEMZWtZPLU5cQFh?>Wg=( zjY3=Q1i>8+#qber&{J78W(E%vGry-_xt_{?Q7A66)6V<0gsw$U>K}kEA$6va5Uum9P%FhPnjZ7* zMi+s8e6_sz7=~v!%ONEc4m8@>F)7zljlkREe)IiiOv0JF(s9u692zP5Ry2UUSxQ_N zjP}8R5*Im3k>{4o<7`+{GoY@{DKG-ZK@WKH>CSed3T?*~1}(c3!G!h3dWg2I`?G(9R_0K3F^0Sku%S?tM~6^>Em}FR>it5e*QkbdF{bN zxfBmD>%gI*!S|(H>rvA-Mu|~W&uoMIf>H^tp`sU0jE@85U@f z*-Qv?osoP`ERG-ec%PQmDu)bGQw6z4VUP%D+YEAbK)^fMEX}WS3iM619VKv+BpqcR z6V8lLf>z%4;A4~+R>-2)VvO8^fw!o<)=_u(F%>gAZbr60V*!WD(A9=!84QjMeHa`Y zaFJPtsski$L?^~zI6E!&SCKnw@&{UbiRjPO=%ze)V}PfM{C4x0$H4FZ2vCg{-}T_%Y&&t-N^BIp9PSzbI}T^HRa0ID_z z;-Mf&cv+T=a|r(@R*fHGivfI@6=tBNAop5Q3H;Ar+!9oamrL0Wp`BQ~jCJFC@XE96 z#*l0?1;CdgNsXoPJ|}x>dogDgg0bL156=I-5OtEk} zVG-mJr;H9b%E6Ns(0|!%Z$9{r=0m5D_^t88i9;w0wiPnl#hy^~_EM$bPD?hsy^G{W zM&j|etqj9Bzt|L=i$1%;59*BKVHrxhpaH-cC-}ZJ5QSv6eWb1uvkwg&DLC>F7d!1U ziZ7VQi{*B;`m}iT>*ej$zx^a7nx#)I{7^p9VOR2vrq|H}8$dvS(jKMrR1sG|C_vM5 zi~epyffO1*oqyi0-@o@&F}!}Q6tooeogvR`gOHU%;kY9!Xk*O)B28M*BnuBSorpt} z*!*_)flg_Ac`J*FB0x8f;s&j?*$`3-d?;fBo>iF#{Dku+5yD+qwGz8@#D(p6u7#U1md8QP!-Bu=vr6HG5twuT<5n!vh$5>Fx!r6&!0U4 z1@m}_d+^*TuHBV1P+;4kOw4ul(!u5qPR@>*(JLX(*e|BW32?mcI(txr zwzyFvwB-y`3udTSX#~EDSs;ZewsoK2m>GUAwuQmfo+^v97WmsBXbUHe8q`+9cUL_I zBl|xE!^B%Px%qDMN#-3dKS?>v>ok{qHF^Rx1IKx+X)6<|DB2Io7` zOoT))b*foVBb&)2X2k8m@FQgg5OXG?`~Up==C+uL4m+w{84(#OL;njqJ*J%$<@(w> z_@?STAI(CCV`|bjULT{7@LmAOtq3GW=b3O<4Ij z0$0`2(8mDxACrdtFi_RlXDm#&UlojU97tK-n-8-7&ny6{F#lXb`9HE`eps$+^zd-B zWWMJ>>waQ^kK7)K1Iwx%L5oGW)<`Dw@XC8ertpC-?nOIk$f5u~tIz zm1ie92iEEzu$e|;!Kg_`;?Nk&>wQ{c*4fl(PIc79T50|0ihLQFyjspb%}*rt_3gY0*8Y%I6=7q!%5T#gAVY9 z%^0#hp4M-s>CoVz29Y7Xpc&Mzg8q$x1K(T|PTWNZk{LeK|Jvj4%ig7KBpIV9P$;1h zx)HkcXb!*kt7SSc@cqq1LcJKAtFgdC@6_RZSgbg$DhZ-R!ANkLUS=4MLim6dG|Ld} zp~WtOaxgefgb^4xQ^UZwB_;G|b}V^xR&=%kRYp)sjDb~=4mfReh@*-#9q`I7j^LcN zUsl)GGBoL?Og8cIbzqG`DW^RQtLni6AIn&7!ie;W6kjf zMhXcped^tYLcc#O8xQ`oU1@)EFW$=;$5WZy`*HDnDZ|x#M~d^KWktw2upGm!-`C z2)fi}4}`QGfOG25)3Vuyb}?!1f}o0T|L@Y^x4Q+t^m*qxjdi~RB~G!QkFY)L6SQY_HmghiNnoXsO( zA=|uPuUngP`#r$$2~0s87?WasypzWIdNdo0-bz>a@GhpyZm#A}+)Z6hwYlV*_ z#PuEGq~fR#>yHfqaLGajAo%90O?hNfl_K2%i)%9A z>E>2r|1(b2j+7s5S3g4RTVoqtcVqGi8ce7*z!&l68H+X}x)rlv6dXyo_+@n&rk*!( zaCDdy;Aa^0Xs$Y_L;0=+bb#zwfZn79#+|Rf6iWf(eQO?+?JomcjbJC0&kygtyN9_E z@((!huktTOk1zi7e@JKLf2?KS;Qt5RmjANR!@ry!{dTmx_ggHVAB~pJ=kGnnF_WEm z8n88M=f|)ZGXR7JCWqXU)*Qpgx8G#~_wV^J#%I|E_^xCA0S{GiECXtOf7JurEsZkp z<3$lNrGOf~*)0E2LRlARiR_E8Eg6xFMxBt3Y&+ zIBm`$#?lS~xyTtqd&4d69cQ)xRZr01P+dcCkULAHd-{f?`KpHid~Q7~j=so91Xb*9 z!qy2E@R?KERTe(bRHFqRX0+hD$7->e$q@aGMbo`XF_G<)0;f|18A_yhdNZ%+?6!p_ zj0$83A7NCrp9ChV5(>#&2XRn_((H*~Buy|g5N#hBcO1(YkQlif*x7px;$Rry2WA+! zO9~cBGT@>b1GNTfgq+_)BJLC?#Nh@R1IlB@U}U#3v|tp6e(VTH%qS@CG>Shs#cY!y zY6BY8tT=Rb8gmALpBfp^l%2`|%qwM3TFf_y&VVccJ>oBMI^_~wWeALT+}~gef>^5q z9%5hwb-81KZaU;zmWSv(aoW&`T_|{`rcU-YUULN)+_v_fVZk0; zR6_xZs8H%rlNY<9sZ?4~2TFoX7t6^)(cEVXZ(S3DhN3Y<$R}`pjRHx$E3F#yn|G4R z(0POprA)xfx-cs36tl;EYho569OIuSx$TMyjX{AbCy|Y4w^fn!g7O4b09-UAML96+QpQ}}lZE&^AEKeTzT{1Zlwi(zSq&YKM{$ZGH zDiuTg3gwC#-FE%?_Tr--XYEJc6BR`WH!{hNh2pk}>K8^d^S!;e>eaxV zsM$DeXvEd#$b?o62(#{=-hNsZa}W-}=US^ARYEv}>&Lh;!#-5thBh9Q<7)Y4N)0BK zeFuiq35?La2b`L5jV!Sy^A8#mj{QPLfU=Hr`_mB6pd=Y`sK)ZWnSqybu}vqw3pn6= zDh@Q39O%A~#lIGhSJz?CJ+3lVIQt+TDj)#_wA@(XK!6S@2)M}}!G4(plUmb#_;J7h zFV@6>#nG3`wam}y^?BpB#snu4u{?@a*0zD)DheaEG3Lk1V!0bPHeDp*HLH$}C))0IR_lnp^6z>}H{8HYXDP}TwP9aJ7BSww%xJ+a6Lut@r9DGT=aMZNLq z%plnA{l-7=P45!Y^@hVtgCHzX(-p^OSeoSjBktXrtcnd&z}7>(IVSok0`dLNm|yP`2PFBTA8R!B!FU9)Ako(hs&(4Dqvyd%0%M! z^mel>hYRrg+Er{t1C&pd@!lAeq98T^7rYe)A*{tS&9L^v`s#a1m4EsAF~txtPR*UR zF~DmyaM&uezb^(B4oL5YrnS8?hD|xTAh4JBV)e0f4%F(^v8`^39!!FbfBb&>S`3@PO(eq%VgE;toLF!x7IA18NaZi<%Bk{P_AKiItdO zqx-e6Y47;qR9Elt#7zuv zndL~o0=IMQ7)B9sOKu3OPx!%DyuBh*$y23|JfCfDDD6)H1m&U6zao-*=69vUiY z(@Qy`C|n>d(sx|^II^ZUxC^4djs-a8@EBRWiQrf90Gx^k-qcQw;rDd}WpU)L>oW56 zP?bwF5(4aPME63k*%6{iv}rS%Y%c8ed%Zhi(2Rr_agG)gp*9vgE3qW2Fn!|1y|#ah z5w$rEa>HV%_7YY$-jo0W@YHOOnuE%0yfy}RS;zjdEWc4g(0Hg9LJc#c444oC{8YKv z`uN<=>VL1N08(wkQ6i2sHqkIfQ0dxejP)+caEiHS0>KxXWklh`ZAD@p=BkkltqeqO z5od=Oc#q0N=x*ZUH8Fsq7{`uKgpL;9HzBI(PR$C|(OJ|B=(B%C&83ThswxO!#yyHx zgFq(VBRAYd@Dh8paiFc1-z$Tn-6ZacC z2-L%EsT zAdb5nOmZ?sf0YN5oLfRIa+rdzC=U-fSfa{yjlj&$6~lWK1zai$;-T?dMjX>6x||Cm zD>%r@P<^vvsXuOO+!Du;jGX}62{lGeo}}|HkrCQ#)@4Enm8#7Y?^1X|?1GxtE(>oF zxpsKqCCOr4qv!b!?IpEkC^r`*fN>0JySuaWS6l~3-ra6~PgCtrQ=0U~8>>Tu;ae5N z1P6}a{{`&m9`3b05+SHHa)fA3cl%#1J{5CnnS!W!K~S2h02r4dfp2BCAO+BdjKsuF zDqajD{lW`;$7^d|kkJfocZ~3J1;wKa82~O3b()zM-_~h>00c_ZOatgHrC(MzepL=K z88s=0h^7I8eK;IkXd?}bg5%L0oO-Dg1e1i|>kNk|p17@yh(|$?2ki6(`$!v7eoE?@ z&XFuutpT`EB*LVrwrsN9NC=>_0}#DPp9mWedwJ}sB1G)`q8!cMPKtJa`)&5}7E@T9 zV&X%J11eJ+#^2NHD+ytqgRmM7DEM!J0q!Ul`X?xZFwT*_US8j%-T$7hudcQjX(b+F z7C{~G?J*z9QcsU5dSuuNgl*&fv?yu)CB>f?tDEJ;a^Y1H->Gc^e{J`Eai@lXrM-tu zu#H6^`Kq0xeLUSXg?Gyofd8`GT&$PB7n{}Mi5dXMgEYEU#`-@mKI25mzjJ8maCB(t z@l7hv%_6O!|Ms~(Q|1QXS^cbyCF`=beBJiCJ;9-hd)OqvXIzUQvXiXThDNGyy{2pgjv#TU#5RI7^>v?uWGdUeCh01U#b= z6P26vr;1HifigABlNY7_GKS)+UyK!SwS)@dVTck?(q@$KZ2e_*wfaA|Ne@<+;q*%% zK@t;*rP&D+RSc8VAht>ghlj+C403h^<2#yM%CJA;k+4^kuewEH2r<&?$G-{;Mf$wJNz@?Z*n1+ELjj=(nn9PQI1q?B2G6Yashrmy1 zuaH;ktFy&tK&c4e*R`v}U)vl2s)_)>Qw~V70gcAyn=Y2U(E?J+I1tSNx78ft)HE2b z5Fxd zC=L=t;7Qeq#ELSc)(DnSZd#b_R$*Kv6`sAl{q$*>Lb&vX#v+Wj_`BuJ=O9rIZNq#i z6sH~Ax~9PM%1jA7=&<~JDdOmpq{D`K;=!feENVME_A`YZNelzNYhYu)JlXdMY6vDh9XBz;dvJIGIMksgfs!aE!th$V-zSn3=Ph_e*XKSh;-pyMxh7+UfX{d6jHy*=20GdXa1AcwiPGB zzP%_>(>k~?=jGz+-2i8wt{ulylv~dos%Lyrftr=G{Ia~)1 zZs&i$8z@`R$JN6Sk$BChj>BzQ!z142(19M7I@rVBu?8Ay4)|p&u10@WWdUUw;%d}f zIS*Hv!)g~i9x?mV3#;+o8U?8ic;I76uqgS?q3mN}!^2(}8RMs%H*vw^*x=&_hxZcoRW(st0fWdP)Xb>9RzH|IY`weOJX-zU^wR7WPdJB zNSdP{O*Wja&aal=tCJtSn?(Dmq3}|KF|Ikl7ivPNJ)rqULdh}9I5;W7NFq9Qx+o9wb8ix=4HgB$R8 zkR)Cx)|*euSz4i!`dcVeR#D)mV9-;FR^Vy1k`PO_!)lhe>VQuY%|a;pYn%7-bVw0R z6eq_}-DO)rEq>l$qUp*+Uj27%(yj+LvK>4{*3klNMHW2*vPU*DZS)vJfXz;T`l3e;)6CFq#SwUkp zl~cAc`jU3Mcedf&s51=|u3K(LsMKsma`*KKbLp;uuD@X8S2LhmP=^*1k%_)V<_jsjKETtoOO zY5sQkZI)*ITqpnhwwE=3`%}o8Kfw2MaKCNVoOx*FA(Suoa@j$bbQnn_4ogXC1~<<_ zEcPhA&2DG;6?;5E0VVgrEm57<+B=y`eqDr`hZMJ>dkCc1B=hdsFR|(hJ<5=S7JN|2 zcgQJ{z33i4R1{EMMFBy!Sff;IG8gk;mF9k|{(PSc=koBERG%?ej_!lg#*>Nwu8fjT z(r!hV4|2I$eDMK3Te#L|!lVIRNQQw2wKQ$zrJ_I!cZ?7R^8*QTWL!&NXz0`M9t9+o zDB}_0TVUGsRzKgQndUh3AgPq!#oPNU@73 z>Tp}}h#NVg-zO{_!UqHk)W_M%v&H6H8c9fbWADN}Gf<73BfzCtNM1LkV5I_-UDD1ypH5H>~1tAUgzyF!LVp*4k<2f%~0dl`$L!%aJr0 zUP9B@UMJ#tZzz)32Cs|}Y5O~jvZZNcDg1B&uD1@Sn?$7vMVk@^1wh;ZiZZ4!N^GqX z{XLz6N~ATGpHl>eXt#`XVQ8wET_noc5h+w$f4=;F^>=2EAC6{^cREc9($s5S;-<1 z<-8191Jj1oz#L(EKS}4Pf`~X-ox^zk*riSJ2}QxIf#%gQhr7av@_-n(s&Q+Zh{;n^ zNm!}(oV~+x_VFz>3d-YMxgl1#mP+%UG6_sN9;y`sEllt=vXUPR_xB#oi}~ne7!^}S z9_@!mdgzh{ns`r+tipgJZZ#EC7Hx&|wKzUf2Ud8A@+IWeo#()hlpw-)xI7OBnrGVq zh9jMCE`1O^6ap`#shJNQEKQKWhcghN3(_S==X@ibqA-LoyKNOxcQhYqJbjSmUb(}VKSpT;g&+)E+xjXkh7x8$G z@y#m7g8Wf79{II&F2EP#G^8?ZrT)NC-Bes#Xq*h<^nP*q^=I0Q3Bt{Xox{VPv~viA{`ka57qV5fra$?iE}DrhX)@JN}zJiMt@p;nPKH|8jSH{ zJuU@+de*7Jc|ZdX)H-Eg22pnBn;BX?59uV z5>z;v8VkZAQnA&13By9Jn&vSk75D4&r1M$v^0k(@2Sb{U|2Zkdv-P)<4=D40Jq7{~ zdpiV?v87xM4wSzU--$aal3^x3?lfs|dXtuLrQJO?>AZEkNksv_|C_3RmqPw6G)Nv| z^=bC};&MF^gtE7z-I~90Fy$~z#XlY$OnG;)x?G$uQ{=o_%#I)Gt^?IV*LQI~{sRb3 z?rMc8JGj;CYjM`0iX!w>{p5jU0-=frI|h@7MK|M^ff#LJUoHn@*gMhfe#Ia-n@237 z=x1@3s@Ox>(L&vn_e^W&FbO_S9l`i$3L~1@G0>Z{ySZapw7cgW5jD>c<0jlzNjU6= z)2OwJ6W%Ot)6S)7_1eX9Sx(7!vOq+{OS0D`I@`|?;~P0}!fP}sD3_Cc!{OXd$lteU zf$$m}X>o=>B!b=C3`dx+!(fSFB+BIBSYI!%)4cr4^_5SIaiMXoktj-zs6&R-1x^kP z%a{YlP9j1afs8Z?WJdY>uWF3Pr=p-@9ih*eXQQXxL*sOGJfmn3g1Q$T!bnu3HWUDJ zue^D0wJT9GVzj+xJXDy5K~4}73nNdL=ZmyhRY? z)k4T;$xfv|?!t+~A~A)RP%pPvi#R>Spx~83@nrq+BjzGpEqwst7e52~Iu8+s*6`Mv z4q_5;9RkT@q3Esdq#zp|qHN-}>O=$rwGrc}fk=_$+6CJ={L1jf{j@#%j7xU zUZY@a5IiQcp-f7_DyPm?>Yt<=V32)upDu1Lm*v_Frx0X;2qiC@glI!ygdmfr6QWGS z7!>%NCqyQW1XTE}+C^BwggZ^MczU_~l6HKmCo2K4niF`BNo5&c`)IZrQuy)ee6_e* z%-$`|dSUL5Z=bb9M*-s3K_xBg#&A+Q)!2Wv=80v!|EEY9H{4nm?I{hsa;oGXoz3c zU`X4-ca6mSkr2Su=Ld75M^Og^oJcC0G85$jdoYGf0t17uwcw& zSj0dVw=)>s`~d3aY~#u1dlJrHkuy9imMJ*v0DcOGqeZ_9h)*aSViWg>`TvA5l{hJ# z2+)fJ6OsJs>CNiv;%fbS(oXM4Q1ekU2{SG6``S!QHGyv@u#l*sognBQ8f~$d zhtamKiEtX(i$&Vaq}T-uty!B0#j_di;!ceLi%JtgOYDF_c8}sm2_!uSUuPIZ^T2I2 zkFanBB@)pLQjr4xx8R+|L=s-7cshn-2FvXx>AF(!MC+^g9Mf#5WZ38;qG z$+YG%&53@ckA`z zxHCNbWxKzH#%&HN976t75TLy|gp!yVtUVHkK@O-vST`w0f;yv6wA5}N>DmhouQ}|Y z6L$DkQ-*Zhuq*=9AOc?!Whgsb_=4I4;?vxxVE<5?U3ys>Is%eReY!_w&^4;&51yK% zKnjF21@jIL2DJ*S=fHRI5CW6k?(X3sLOJ{}$|G*)=ppC=KGD>LH;YfJvqn0=Z|#bQ zHF`k(GCpNci93o1%Av$yyhBeh((4-YmZdnl*m?pM!Dr%0!Gi3tu=1zHmt;b}rbDML zyqoYlqEi~HWO@i3fLHA_g%IG;u9J19X!|Y!yMU{b(d&E|dLd&OV8563vO>R6oKFUi8&%iXz@GzI-euC{ROd zAe=-@T3wCDdr$Z3U9s@<8VCGU zRWl~5IQCup5mK0x5OOpr5j&ys(BK~S?l<@rv;r83X`nbgq?llBidJw8GDM4TzCGzit68K%F#I_M;9;tsFW_vohL=)925%vqfk81w`cmxP( z=VpXuTGv^$QKodMH!TC{^gcsWVRA`!m?!0S0dLM;EPW;yAFFM{X++3Jv|%tluqmex zwUk!jB8s399SDLxm|38>&MfqZzOS1!p|2Qw!|xOZXBJ>%JFvo!wqyfquyZC;wb?xve`R1-0JjQ zSe}M86z@FM7*)>}iI&UCgug2p#}5&}Lka_4tS~Sd5UJWR)XiF~9YI4tUhP!-(f)xm zRC-gD?Kq7FgcA@5hl$mb<<)w1GfV&Z--|TBfY)h1(H|lPvsHX}sU!^}3=R-nDj;+< zcLjKn6?oo{J#{A*Q4tLia{=PXa63b??RX2Lytk__X`UBR}EHX*in38%Rzx3KS^KolxLMPRCQmD4n2o-RdH@mE5r-tY~SI zgFcw#VBx1bwEYvm>dT^k5~$A$+|ChBm_2}kh^gR54veSE^EAqkvS@#V3EFMn?w|;3 zI1m!mybu7u3p7F}l^X3te(JcfWgo!F2I!Tc*ml_E?RxWN_ICA0F#-jls(rhIA%wj6 za18?+j*Ou2O=m>jCQjv3hyYlFRP5ywi^V+9h}k^4GuBZg$B6gqG`)z&jXK?&I~>qF z4c#o0M=0SK4lH*Yhr8+EcYbjj;sz{6WOM7&h+i+#IchwF_QiU0UNFP=kjaOtH`Esk zuq3SU&S!wYN}xfRd!k1(@i0wAdU*$IPU4DoQa9w)D8oG)%d#_ zesVpXzj6rbC@gGwG&%&8W)-Ftd}#yh+0(`Ei_2B7_x1Gka{z6?ljLTk3-y^)amgJ2S6o4f2UY&q!WQNd!T5AWCCXD`#z&mwH_ zdt#wtwU-DTQ0Fb3IvNmc%;`Y%(uNX13^>Ff3b%7a8FEHwR$)ZI;ni|=bCK2rdmrKF z?Nb~jdLkm?B*KUj>OSTyN1~?~P~ufyANGqavD{V)gw|O{zwa4>YkWbVASjlZNJEjA z=d&k!vmerMoC_VibT~KwqzXrf9q^+X4*dQ;KGTq*-y~2IlF8emkb2;0$LeL8$_kt$ zcZCYo!4t6%KJ>?%fU)X>=p*cTSA8gFe!9Llv2|p4RK(L9UC)LQh8OUXreVC19aq!d zvtNrW6;EDLFq~NOQX{CKmSAd42O)Y0CFPHHMv+-~XMmwMDHv_zGBb>~q}z!*ibd>c z;a1aaJU>5QEgLx*RJx9*Pzg1RFvfs8iUckzzpbECEqaxo4Al&``7_dB^5wqhqocfSr+Hw}Z*IIjr|uwlT};?i%U0nBP1 zpnA%K;5Komwg@)YJyLVdSg4R>)AkA(FhFUVZUWPxpKO-jio;(W3s~(c-%#}|h9JHg zUBNjM|5_&%esz`R7_$>wEn0v3uUBA$h4v31WI43ob~z91x3)(roOmifuA;;oqK`Wx z`(wY^>j`53wuX$`IJG`GkcB^*HpYhoR zof;&a%Uy#AV+PqBK-fNrI}ASCD2a+XvwXe2O?yE7yeXy);XAc3IX@MIsf~lQ`aPU> zxlJ6mZ*HzT+$#e(7t6+@^r%UkBtk$efJ;YXC0)XzjnCz#F8)X;aHo#zLj(t|)dFRl zVAo`NTz{ltiDrt;Bx3xMSMG{M;JZ0|2{|BI0hiMPeRkVM>9wIV5D#<&(v#%m6s8 zz@xQ&ggG<#QEeah2BJ5%#uuBzh(I_>><3C15crV;BCXs{I~c4tS2sll0ES&N0(8p0 zq#R0d#(__%ao|#nL(HU^8M9h+V*6o}eMU1T7JR#*K_^tj_Wgka(8dArA+6ZBNk?p? zy-2-Gz^ZE?AOP*q9)%iXgh0|%3G3LlCurx{gQ66|g^A`79I1Jb0LJspRqE)x*xX+I zwwxB>_Itpnx$*+vt$3lx3~|*+P~j*;PLN&ON4WDaA;Dk>#_{wb?TD2E{kO}-rJtMK zmI+6S;6`z$CWWCOS4~okps;tZZL!1PJJ6S?GEgBUfl_{b{o86ABH&rGi*h1W4(I>Q z@q@&U|3s0NICU&}60W7VN)zt!KmRygoNdx|lD-)Zo@)0nP9KMB!9=5uCPTK|j3EfZ`(@fE z5nKK&iwQ~x5TM#i;IE2J2%u2w8VeHyI=SgklsjTEUn{XBwbh_#yUPA10Vs}hm7Hgr zj+~7f1mG)*0;-aGpt#hGU>P*vM~VV)&dz~_y-%03%!9f4_!2t|9%kgMw%{nylFcN@ z>&59J?Iwy4D(*?`*D|3>3Wp=bJS0d_yqXn|4sn({N<YLj6Z!2mzFH94MmNi0A`VQxKUPJLD{bb~|m1k?-r10=F|M!VrRy0zYzk z(My{%0anW<>ZyX!?}Edk6bwNCVCmKD4cOp;=gj6fj4HBJQSv(lw(g znVm^y}@W7vi3Rjg~}b?3hE`tyd?w=D=mPP7!MG z=nyNY5Zx8~Idbb!K_CRWLrPJINr$gG6%X2AU`IcW;yG%{QYzgZ+(}jE z;5bLmFeSo)fgdR;q;&_0osLB*Rpb@>{{Va|sX7QNsvw@t2=7_iC0r&QK!%%M7uG(!|m+JgJRJhNDE$>ZgV!_>va5z z!@-}%OCCsMM@&Sq0uG>Bje`qm@D`N`wx?TLW;tS!39%}ZiHF7JU~{@e5`#Z<;+^-y z5gUPRXDmW;$grTX7#5G^B-%9&t`5_uoQC7^I`R*W4CYWHA~4*+jJuMC`2z?= z>vy3MqFzITSQ{Fk_isWC+)--mhoNypf>)jzzoo1hcE*~$y!~{sEZ_hq6%N`+WiFuy zfQr}TV9~{$psXSrm9^x6wKM5-et5u|3OU4$0O9S>2!=v7G(`h@7~Ec+|Gq9#$*5P2 z13s;^2)$$6sc~RYiGy72M2j5B#mqWic^51C+2p{A_lvYIE2YVxKraGO+Pf=bT?9Bz}Ib`Q4jtDMHFXBQkY@Tf7IjB4g%J@nl-W&1b9>e ze*vRO1>w6m4|!m49_AobEb+eWF$m(on$sKt1`Ms9Y*sfni(*0@8ee(P1)vMWWCep0D{fDI(Jl3 zV}P#aK8FPphc2q3%k#m+kPH$&NcUvm!`{(>-$4C@en)}{GRK5r??exD7MH;(`{FZ- zIr(^htHy1x;^DAcq@LTuoUpmIbc_qqWkk@YrlEE`7&5l+DG~`OuB(hy_4a+Xp_8a~0h&7d%=a!J;oq4pm?*my1iM>o~$p?G#W$!u9^_|csrPED<)g;&kZKUDqwH4JR^lcCal11WyL`l zZ7~BOC>BmVOXl9kY(RsXV6pna;c^eOWSRyJGeljiga;H*QVOWT%n6zd`BTz6w zmMtWNRkVv%FM|{Pu4c~`-+fAX4)Tm55qeudxK>RO1+=0>77Q&#KKlMcoUTyy`@!lZJ%yX{1ESdg+44W7eY30x^M&QSHTK|qObMD6@-Kb7Rftd*&9pH(f*m9rnD zX^L={wxAB~YrFiN7i?A#aEx)<8(RlMU&%OQD5wp+$=^>nIatViLnU91!_;VjM8l z?8HMzgK{((amNx=JXbrfKnLeB@OuS;jV9S+xoihC1N+4|NPl;r`EhR<2a)YUfiEf) z@hBGDYU*Wo3JJ2Wh=sYQP+VG9s0HLN0k8QY2;<0fNqATvV#I3gRJ7BlLRk z{+bGS%bzHfeBJgm=t6+^I2OEKUtzJrX*$TfT*HFuDhRB3HL4JpfFIQ$;IDro2qYFk zAhDcC=I&wTFG6?6xWFrmr8^``{Dma61tZ#->?*1fo3o~=d!WmSL4^$cPaNB3|O zM~A-0`N;J+$t-6akLEH`t;T_GYLD|FeD_*ZjgyQul)|5LkJBI1%+Nr`+~JtiHy(}m zIPp}i4bnA|I7)P)=`F{i$)l5Eqdq-wCEj z9_3mHEKz_O5qw+hgh_zYdgWm1ce=Y zpV>D`FeTaJAviNq^gEviA(}8wL?E#3Pip~#>wSXdY9f6k@P(QWhJ;*A*NZ$xNe4<& zZ68v_2}~%xSO$Hm(V@L|0bi@hKs3ZA!dNWs)MStz7F%zG4R`f09v*ntp&3fNl(;%d zbaPa3r*5MJuMCV{-?K9p76r(v8ItL3FJhe7Q6q8{qjdm(m$q#u)8iQ|WCDE$%H`wi zv~9rmSzNjRtZGi+uZlqkvO&3;6Zrj~7-aKxGz+=vI#JxtS(z~CW;lSQ4u|HfeY~iK z0q;;4g12bHzy`Z({BbOr$T=rrg2BKSn^ClxFz+G|n9yFmfp63hIDxD}Yl~y6VU=fT zRow#9Nq=8r8fAM9(X;AoIg}xXxZ94YK#!D>us;oD{Gke>Sd?l)>UunT!&VK_;bHvd zQe@#!HNR{@T4$i<-b0N9CA{N$+g6Cn7)D|{%D1l1^~)9*)@q0gM3jQ#+yAt+37-(d zlpSlP2QWvE{9uCYsYKHhFK+cOcLgt2ErjWXtT!Rxl_~Z6HFkqt&z@W^&OWDuy}@(s z0~|W%g{Nx&9V`^hqqG1tN(-j4?#hmhS!gdaMkinv+CNS0Fn|HZi4PA}DqEb$9%beH zbQj7vC>&GfVfqYyRf{%@Oe_SI$+67g(F1{sv5c*Py4^<8|g#1vWS}Dll`>1Bw{{#0X4oLSW&IFx!mQHR?t2P#CWol>H zw!?w!&~!lA55|vb9E=iz$YjMVy@%KX1QVZgGSP|e!h#JvBvhb%IIF;!#lNPRLo$!s zc~MkgG{y@~6fdq}Fgmc@lVb|!G^FC}OqLMr<@KZzPN9|6WrPXIT@?P~Tbg8ex%lX+ zkAm0L#~X5oFPWXqdp%bN|3oqD&rPJ<<#u=@E2)Y+!yQw7># zH4tegjyp5+b&Gs77$dTduZ+F_=pqhJd{Fs_URQKcB+>-*_`vU$&!}FbYVPTo6US5m ziKS3=@vEsmHytob+q(1oZNaz$n5cROC9rd<>4v}>X zpKv>ePu*iA(6BCQ`0;wRxZo})3-2HN4i0!i@p15k?mz0FaNttIhB+0T4uXhxSRr;V z9W5HZ>u4Pv7nAeNQ=$Q!G>#lT6jFpJQ(qomFIHbZE(;&w!JsCHvqU{}5+KMUepSn( zkwaR%=_MjLfWF@^zT6O70H%cmJR&)8Tged@exh6g2(Qdvyh1= zMxx6eCu~WH(1sw)M{q=NGGQvQBLuxL5@qrlkvG;87~owp5rRhGK`{x~jU&SI#pZJM z^mel>rzC)TH4b=I6(UUV#HVY6U~#FIbbOQJr=b_Ab5n!?Ffa;^vxq#cNq_+A8HnIG z3<$i!0g?LLn4N*ijU^>Gep!nHUX;~xn~<7iCl>OgrZ;t+-q0B_9*w%dI=Si?*|_A@P>5^4-FM+b!v zQX3h9IK0P-_%{n|H|sEP;@}`~LW-SLyr=1&nrxfVeq_$p)m>*c*Kg=bkeoUSHZuxjka@hlfF>Vp&+U+tE=1(C;S-nUf>xnp`O`PH(;#4j| zKqM&5u}@xVWY4NA@~B0Q?41^mPYFTA6a}zcQIK#R)S;40X5rq!+|LJbKAWXk5zNg`W*@xBjc}jyh`}7?^w7Am5VG;H_;<&|_Itk$?yQyKE zi2_Y%8P3zb3=685;D9@s5&;L?Qk%e)Nz^aP&DHFO%Vj!{+N%_dLJ0u~v#sD#oK)!7 zI6k1d$_EKn{sKOrcupDqj4sXTO*&d|c6zh=y0}{ZzB;?`oIssM;Ru`n*=kBSy+rwU zkctOhD%=^fq?mCc;(^;aj0^D#kQ8FjRE!vDDRzqMlmEWJd7{Nu@tqYasC1T#5X0ar zH6ASWy`tJ;>R2}e5aB>+G91B`qeub=MbzMUviY9m;@31IB1JTw8hDvfg8)d6*I^Oz zsY&9QUV7LPidc)2^VL51! zkRNs{LrPnN5u_8(iB<$FAunFwDB>GG^z2|@-K*SKGLka#*9iab985_2zt5U0{Xu~oOX26Lj)(LS8kSvy z;(kN{1y>YM$Sjs{B!Ys(6_W$ZGCepq03_D5`gd2b#d+5#l2`=V#YLGZJ%}-=^q>E| zNRg&jOV{-vB4QPIKF1OvWeg@L9C$$CFm)K4haNeB7x)}{cWyqxHt*4&tP^(`#TS~8 zB)^ePRJ?BF$^1-o2MKD}_NMqz?Le$ECfH2o$NXepp0kS(r72N~=P#Q;!70(CV-ZD8mWvB90p;t=Kq3yfopA{96^bOh;FVd(8+62$ zX@p_%smNBKL2D!!UkELNdbW{Z!?7o}qgIp#Vn~o>hD87Tjd&Psmk?>!pR1%1vtLr$ z<|;+r9*wpyLFKY9*=B&jY-C3;vL41jldm%{A}w$`cR@ma-OxZ~oMZiAv$*=4dg`<1 z=ikszcb*lEpv)iw;#eIRp&Np4)HZ^@N|Yg2lch5cu|_axd2%NqgqkT*LU%LHT-b8OglfP8HA^|sTXjX4jedZcLh$LKL#s@F$iY(b5CIFAqbRR z*@+3my8;v)=?V~EKLl}@^UCdQEe`kn7%Xu$LKv(9 z2X1FL!V+DB18;K{`%_xFFgsnP%E98K<>0Lq0%Us|J2A z$|+Y($9`9TdC=8eCK1Gm>IMZG&lyNUAkC%tSK9WnGem_ab6N$L%^@oMsD?o#69z&X zpRN@7kVP>LlTE(Pgd!Zq=xihIoW{!2cfhsK`PMTH4?z* zPC|^?MladS7ds?!L|7^2)_nlXD)~M;#X%fC;)Ml`;?UO!l(EVrjTpul4rQ-U;@m-j z9S)@UlNUy z)hJjHEpSYhf`@ZI)a2%E(5lP~_OfJF0d!pKNk`BU1UYW2;3$(YAgqIRH zh8Y{EY7GUOjww^=5N&4}@|1O^%T%E|Z$r8k!w+LW6Sq|*!6?Bivk`_5tJ#ao z_3hck=Tu$5lvl!~Yo$foOhP6G->+$5+R1vftz;~WD$n;5vj7<6o0HDm6|KP7dJr-l z?}x)v&&$KQ{Hg_@S4UD|1qT|v#^K)P(ZfOLF<3``gk?WWb%WhB({tPzES)rkX}kW*Di8M_r`87PfYh$m@YY#LB$9rKO`u5qAX zi2n#G0ibIfsA_i&xy8T=#r1(zudBHo7C|NOP?Hev*XOez))-T8beM_)-d>|%W+7FZ z%!c4VfzS56tp5H-W8pWd;?f$$-brd6r!ajp(cKlyqvvx5e$}Wm+{c&06L^qiB_u*8|-Rt*q}nj zQY69dZWq(4{0^UV1x~S#%k7WMkC(AN9urz}wpVsfe(f!Ol4E_mCdc|zX0_^TI1N9o z>dtxlEN|Ok-DQ_y6DZm`X(^MF41^fh@qP~Mf?!jj80n%-oL0SC++NP!rMA8m2rE2{9VoPeBhAcByZAl5{emSP-l2Pb;AyoWhsHdF zc(_$-xIh4cs8-+-KTWt8L2|ZJhacsqcDr|ORKO$J3vTD2H*AxIVu^0>%8B`AvHCQ7 zxjz52^l2D(cAjL;G{E6IR~;a`X|iB48G#d_{Km$xg0Crr}*ys z?8#<%`=W3Opi8KQ| zyuUUJBf^Ly83SRk7!Iykpu4?E#66G<2woWw4EbNA301|^T%c9sfF@}B57fBELE7DE z0xqYJA$N0kGl(#6#vnkec!S`W(z`U~T4c`fs~Q8xg|K#^%>|JHS8;baXy6zZ%E1I- z!0?VcMWH*I5bWVG&QqAZPCEq^qJYM#F+gj#dCjvpox~qXC%}a`(7=+ zq!k|j-|QccZ_>t@H;c6I*|$z#nSUn5N1LX)>U8-Pef0kK}Av%wHh{3KrE zqXuio&a9>Gfz7&){9s&rkBY#O+dv8J1Y0cWDC zq8Q+sGlj*DN#mbAO^Nzy_RtO%;--mss2MRyu<&cu`6bA;GN2U|l@KyP18Nvd9eG%# zhSSrzP%MGep(T)=%41*{sU&>DZ54-jnjcCgi15lB=CgH*rdMeJ2bR-^oi%xahN40! zL?G~nwq80D!KIpr^tMobuxy7uMwuKU!&+U82nWiU;Rt;UBM4Q~;E=;dd4_cnQUlr= z3FyBf(OUFKad#l9Anfgh(c^VhYwrfq`p=Rrd(IcNu{%ntsIB}fhTtWV*c=0^n+b3+8U*j zxSDbFm;J@466Fw9@XC}@&-}#9c}EDaS^0pv0^S2Q8`ut_=Yv~hb{wo*8Y^!rhp2*WCPzbXXE zSlPsG7+42{R1iymP6{GJJV~so^h@Y$8U(yD7Mz}~Z*EfTeVrCC6*)Qlt|kKhszeC( zfIGEJ;;(-?5%^LgLK}eYkP@DDz3tcQv|Yfz*O=?Qxh&Ff_~Bg$l)WqM%2Wd?Zx2?Hs3La_nM>!azhOcq!@bquAu~bV>{ZkW^^$s^zI7BLIJnWP?tlAWc_=k=WLTY4~U@ zQuspl*TG?fX*DAR06?*0I9u+WClk?#h4`((TSY8zJ7W=sO>nD;h2)4+xA*$<3YwW? zSv*iYe(Y)>2Lf$e z1K~+g4!1f^I3Vy>0b+y`(P^-@Zn(9}3o_1}cz(9xpn!A{B9CEEGnhf>(ycll8}s*!=e9rrfOnpHfE9 zEUL_TP)!uFR!zYGMHgFXhE*`k1QhEk4;T`?ZZCglNbnU^h#$K-s7O;U(?$i~QZ(^& zz4rMas9S9m_;U6frAXlQV-QH)CPQ$)@uaX7G|JF~fG*?b|MwMweW#T8{isl;PEHx( zKiMP{FTbalU2dE&(jxyv0358Gum3TBbn@_@4-Wo0-|a-^IDRr89Y1+@vASHGr}ZWm ztHtd2p-z>o`WNqB{)mbX{fqen$Y;BQfu?Sbq4<+EIQJy?FeIi15Zsh%M`1WR_C1Va z6hk~G7h@Maj3e0*Nc;Yf=0*<#e-=8F`|qOW+U)FAXj+`UL3$4sDeb1n%J|_Ky#QICLX!2doKLgp3GEs>V@bak|7t#A&|q2K#C_ zwWP(@F2ltTff~3|Q-ejmNAJ_xTe{*5)2Erqes>;1;6)yKf0B-7zAWZ6`o%jm4iOPN zuha;=4F?7;vvQ_O39<#2$^NsqM1z!bl99*YL@`l<-0ccjm}KA;&PF7bN@Gh!mduj^ zlv2IG2NZ`ebr0`XQlQFJU@2ktWE{#vnVp5;d(L&xjH*~F2G<4xDpG=wmcBneU$4Dk zpdxh~;V=l{B<|F)6#gn)7*R}%o+YFN=6)i6=|f1Wsr~S7P!$M~j&e-%Ip3_Wee&Fe zm5vU)RM%RDm5vU$tjt2{H<=E@EMN*IktGa#oimFJmj)841Zoy>9K{%cS2#vIOdY5<`>$6hz!2*4jJ6!sK3v zsArwRpH;ATRP3V%e#Z3)^^V+>t0Au0JDmFd0xJy2TR;t-$*r*YWRQ zbhB6dQ0osSYyIhL?-$k;Bb+)r3WOxTjgVo&Im)1>c^D!Y+-gR3at!g4#j14O`wkky zA+8(nD<{LCHyT=@!0#0byhWHGpiI`NviC1@`AGy+$hj;Fcq-?85S+I`figRldcD3~ zUC(~r_>>R_W@_66i4=#B6T;ia+?MnLC*x2mlw(A|Aus}8Q$`$gQ^y3vWJ)*us;5e^ zgQ!>#fQl0_(1&u=VBlFH#g1{z(Mb{M^L-=*zUN5M*6TR50=kN61R%IY;px|Oiop7t zQ|mcsqcAwraB>|&d{Dnefn}vi2p*Fuaj&J7ah)y7(Cq1?#MlNqc&7E}pNnK^8wLEe z9n%7;F%JgbpCX(6Fp2w>VM;IH=zVDb)T zyUn@}69)|hVCV$$Y1*A3WdP3JE*F=6DLx*UVi0$MAf_?dAht}C4!wgqo(({toMIHb zGKoyxJgha|T$MA$Q104S;IDU@1U{fXQ))kW`{_xcQW#*76IR)V!B!Ne3BmIX&JBFA zxxM-=Ey!{5=*YmDi<595RQX~|{Xo3>SLTZ!h57dTqxs?&>&x@y)hr#4ag*i@;~1HB z=jenNr@t0)^qj7_6G)?x+krGdQ*m^Gw4anhKQCOsk5l&8g5V)IknS#9+>bAw__KFU z7@bQDMXI@QvfL7qQ=?naG~S@3K!B06qJDxoYLUgz1&!l7raDLI>_MtSdysH)vM`iE zBtu)V2+5ZYK*k`_MNe_x7PNst5FEw3+s*GuBu|q%_`Ki&-c@TP9#(A#sW1SbjKGgn z8zf)?7CXAja=oI=;3pRdt)oKTDN#fc-_3Dkn4rV@w`Qi`u-%js#Bx@IQ02LrcwsG- zE_#St963C|TEzo@)t)A=CQ~EeAqFvMui+7?fe&XuLINGPgc>A?c@63Re^@R)71K%E z%)qm4uL0=lK+3GdF+=1!+h<%_Kju5Yx+#vty z`l_6*gNoH26%S_v>1NUKt;&Op00AEM=BJcOfkI7)w&y`ES#>&7yvTu&rbe&7q*xRO zCR{8R-ZC5xth@*X{%Sas6EuEad`|00&#T1of7~oCWqrSUxuZnc z8)iG}gqieBC)wr$tGqy5dTUaJaycu-R@Z5IWpZJ~6e>Ud>r9lF;=IC?bW~j7a8kt( zohGs)H4X|d-cP3Yy;pxyKtSSES}9V`6>*_1j7ft#JgO$s5AN`2){1l~LH~|8l#5at3nr-4V62PO|9?%TNxHnP))qepXb?~L@_ ziS7j0*uWo&G~kXh;UGjYmKq5-e7LyXT&B1-jbErrZEfRiApNSp;!F8OO|GM{(!@u@xQqGa>>8 zTv>X0vp)M=YJ;N#o~X4UME59F?J0!gE~IytLjWMGZ%?`&0)H9>C^sRf?Med*>v(X= zL)>33zAydc82qR<2>exj#8x9J`2D>`R7xKKE>DdbLY|D3Z$(k>7HN*@n{;XlmN};H zs5!JX(%}%8fHog92Uejikz-6KJG`_h>H-12OC3X>zY9TflV`IZ7r!qy>148dV1N(& z`52hy?c^!9U1|NpW_g}gT4S}6w})+bPm>wdU?ru zRUONxaoRmcRofj)u)IBl;@km5G{*?!sq`yF6`OI=U7d*Xyfrr;i9H?Hi%2^c$zY4P z$KIK9zFSGpkhd;^m8$w7;#K|U&sF}87!Rn9UPIW6Y4_=&+8{~N3>P(`F4&WY|CPL!{Jp*S0)WX$hv(Ly!8 zD{6>4g5n`Tf|%II$*b>a4aS?rneRn70mX9)0xyLRp*w{;wO|Sh00P@@Iv=`I?WsP7 zK-5dz&Pj$a6$5SBB*f1fWOLUzS?hAM3R8>mR2@#!RGQ-^U}TMlkwPq`=|sn5qTC*p z{%HF!GM*G-gg`x<{Yn0ichJ=h_1k&Q3E{Ix1T=scI$ce{4J_5EHRMLm>kyMbB%!JqmJ za*Yv|lk)ST)5UNR=LTye2hWj*i#QOa$fFEdgLo|>G`=M%?4;HtS`R^xicOZ~@`T(u z-@^LK)!@*ho5JlJ)`vU?4Y8H>dwjY4WA^;&)8*>AnCJk|6&f;82GUS9waPFv%6TaK zUISSH0J=2dp<>t~Vm8!rm^?52d~wxy3tdLKH(^#aTBM1@(*^f%dRwlc z_3I2ABk-1XAd4T>F$)`w#v#qz1Y^uo^_;c%tO2lS8@QcAxL_Nm*6idMB|hxE-mZ%mJ)DS!7}CJ}pqIPtXcfA>D3`#&D>D%W zG|$taZ$3HdN7hl#y1Um;3E}sZ^B4hROe%T@qTm}YiWo7Hey2CkLXMF>J-Z0wCQ$s^ zNT$+)1*T{UL7GM~hD14XB=z6gg~sVncwET9DU!z9Vq2HJJ8nqL%{;y zgAYO#jv^A(?K}M;Ta2fhy6|p^H5y4PSLIQVcwn^tRbNN?G~11w9DI0yCQAQ6BmaB) z7biH({`0@3O^*K8Dow4E?T*%R`H!Rh-}V;|ep`f9D2JmXCok8W{F%mt(ySsG6RO5e zUjF@&JZUX=vdT$$Xv^l<$pIXqngL`)RneU>cO=e}h9H!1vqXnDNyd`pr;g(X zK3)FEqH5-_ojUFvAIKnsS_4WY=1}&r*s+p6FSSv}PW%~j_D8EY& zez9ZbryNIRh%2cdzOx%RAe70H5W4;ho(Mw-quwvlMpv`b#h0&T9_;S3gXdw21#GBB z1fP>Y%CUmh?G`1C`H1d3w19=_s4xPz6^w&zlcPVazRVyIX=h=tjy`Ze(UmOFS@jfQ zKDjdy7=W6H$>WZB;=F%>cDDEyY9yg`w8#n68i~-^0!Sj1c!i56UoXFVi?0aj2ARB;RHUU))9{qGZat{$BA@U zEKgrNd%9WpQ4~C&q+rdfIpqNanvU$zbI1WYG$IMEohSH3NyP z!0il1$j+FR5EcU?PgG0mfQ!y2-Zd(nVLSmJsHMV?*pU{cYEG4k;6A#HNSTPwY3={B z^|w+mfqV^uqeB{XF2@u!uC2xt-lZk87|EP{NN0Ygxw4mwe|LB4{P{iX)56}dW!ET6 zGw$X3YHbi;ud3yjT|2i!plZIe{H7~_3@mooAZI>bvlw9;*ob%P6IJB1oWD##D zu9|6Fj$$yN&73oPdini)IeWHQ{&8944}He~4=X-SzC+mS6bsx@TF|hlSP05H-2nRI z&Q8X;v#-&ONtRq0X5}(`P*9ho&$OR}Zqx8lM-I{Or)f~U;D(<^g(tG@ z;If%z$G7W1M?e4^PB73qi6%q4j&b9#-0_RH#~>#lVUUWQ;C6;0xO4dQv^`_FZK)$e+cbh~IxaZb z6e|D<`0fr|C}q>QKu|5v>kmivmt3QUP9vBrh_i<}qfbOc6e0{Uv|+&Sr7MK{9T?(x zi##+4$X>Zyg^2_)HYg}Gb*Heq))voJN z#9n!TEMuApvn&rnL}@snnVdMJCC(%eFmF%y{Z(R6XqMw)U)Y9=H%u8=0VUEK|h5T{55PQps zEd7`=?&xDG5t#Ahds@eq7PzKkBc0RmfrPUU)K(c0(na`DEf!LHh{cXtQN~l$9{NvW zc~=k-H-Sg#n+e2Mx9QNW_lqwdi6Y$7}S?SSGB7 zgT1HfarGx;2STJ{w=NdT%X(^)ODwl35n?Jw2|%NM^uCm^D#n;<0u~{rGGg$`I$V$U zp6*pUWVo(|;{`7z#tyu2IN(Q$gGeO(C>b(2r(Et8Vx~cE86`#}BA|f6b)2Z2E%W;J zY~gdt0IN0-{I%^h+Cbp5m4V0~%|J$7Xt7fW`86>c5qh5}rvO1nj1HXE_2%rw(kH3V z$*WPoI~0W~zIM@Ul~+E+87*n0(CKZOS9qFM7@xI6-P7mg21k#;{yddp>-p+njjA@D zS8BTbjX2w4%MKtZ=;x~+O8lykuJDq+fUWB&TJG+{h){Yz-JCKTMh3iCb8P^aW)5^+ z3mh<*>jk=COzpZrW1;1)q21w;pG)F4>yWg-&{H-=6Vb-)oF)rHV3g`sX6Sz|eqW^} z4r$0^k+!-m#)ut12}zYE3$Y!9sfL5C$JmZCyIX#Jn0&)IRt%meX3E0YD9X~5ESY%t zul4PfKW7##txUm_nJKsw#RV(|AXJFQWz`5_@_hh&}&3LsXhip*tmfJ zNPoD5LeC0#xlAJsn{u`hOCl4(kr?$c(Z#C^|8O2z_Fp$e9=+ zv2JKkJIt5;v`7aKpRPWCS#-jFM+=8T5DUD%wh%lj+Brgtm_HP=8dPzwcNjB;*|6yM z1^W>%%I9n(N4)RSB!aJnm7sbxEr?7ror*g(972Y{z}wn68sQWJE&>2vN1P1`zCwHm z?kFSTiUwl@5O*+$X8y8Sl$mujp<=*_i~r!IU@&of0pIUL6k`~aJwcLXn(v4>pa#v} z;Dz-KkC=mQaLLohSj=*9j5%kaAq)r@InLxWeQ z6OYqz8tb*6VRR=U!WaWySQ`b4%78);nd&-(E8~;32N3>O!UVgUC-72Ww-Avq_chb0i_a?BbvsIn<}3p(>*& z1U8_2H5=}W!qCf&Nddn2E{P12l??=5nJr+;%(NLC=G}QW;UHk^R|=URC}$OEFYm7$ zVEGuP?>`(JV0p6U8kMKZ%bQ-0^U2SrusNH?#3L6vT~6T8p2mbMlD0qM8?^%MXTl7Q zT+E=CgP`pVYnNmilO$2>_QU-Xrwb?6!>Fe*sY?}Q;Lb?mFUu~cQGvbEJdKG}SJuNV zpYU zG-C+s#DSK^3~`$qG-tD)CHuiYSryx#b0aB52h~t?&{#Po5>kbzx59x36b`93paW@k zh#4Lp7C`WQltv+jA=aJ5)bD4ZoX$_YTBZ$dQrPe^9kmPa^9*f#Q=xF$;CK=OYTT*i zQOE!!gz;0I<=%~rSBQ-m7i*Y@GVez)@-VMu#MexE&Fr9pgW9=_iV1nV?Jf4B|5&)#FeK+u?M9=VwX z19KbrhNA;jwd!E)hder!fe8qb!l=MOw%M=C^Dhg(ya%5fwTqC7fuz(tU{UEhcEq4u zx*)CsinARC-FO^uYcnBwgKHyHR4^nl2!17P-XNDdYT4usTm2JD3X(8zBm(|r-I>6E) z(53q@fgQJuGX{DueY7abf=F)e~;78bAPW~tqr7_-Mu-CK*@;E zA~yw7+iZ(NvCgJVMv0Q2TG)PgAp6&f+kRyEa`r%+5R8EUNzJYcYh#?yYS#$&h*=wxT$+1LHB~M%L z1|o zCnL;IA9Fh>-2=PWywDG>LA;eMXgFm{i0mCbaH(iWJP$M|w$l~6oPjOF*xQHpF$aLg z5!*n)TuArRGM)WZpTG@ktmVpq5Zo4ku5H9X*pU*wnNxhRF=CjADaL`qtM86IJ=VTO zh@{*&WHueA^Q}yn`^j`pJscGAcsyqwju2)ZI}&2Zo-u81T|K_eu!z=y+Zl}RPA4d* zBgL<2ruLgOmE~gj?b->2Hx1UmnvKwp^HKfmKhmYTasj2OoMTE+j9}GC1cbR(2|U}DN+47_4yn=*i%plxat8yxZhH*o zVHP&Z*kl6TXBtboxmgsuVdH@s1V@J8FB}NKTa7{}01yO@$u?2Ox8gGKIpjtXKGOhXa5&@>yVBl5Ch>#L;VBoR}C3Xa(468&T z0mk-`L|}!1!7Bsf{o;FTPbi-oW9>yM9S~K#e`PG$6`ly$1C(fQ8@LaxU#^iJ;c&k;zvltI)W1c zLSvK=ax^J%msXVB4iQX1%U2pL2&dqHJ2~3kc8z>z+!3!DHI2{}$M1K_Jq0yj71Y!RPK#v^yS@pjlRH3K!o z1}0OZmsdxH95V())l$;k9MA+e&ic0knlwato@UK_{0OG1jI<+Muu|)7CJ!U4fc3F)>9Wq|*k z5-|!yv40?)@%CBd$Oe$PDU`^hzgl0NE&SpX&jp%6#f9K808Y&XgMbR(4kgAQ$U7rS zM~4R1f*{BOKBiQN$#jDO>~#Y9^m3KP9n;xFY03~LuRA`pT?W-s=n$|#F>5SLB6q~1 zR0l#!Ecm*?0F9+Vl-ptTNGL&HshqIb zAmoo3G5XN**e63yOdNCrBzw`Zo~ zp)Vd$aGXK#QlN<^5w~5YG(LQs%pDhEG>Wbj)TUg{BX8|I_17;cfu*Y zqPD^ih;V>DYATT(77le5DI6UZ+XV1=Lc*7t|LE^V924sVf&nIpVvu0k<-6r3^g{i# zH;0C^max+eTB9b1R2;@4xHP#j1Oy6(SeTi>ak0tE)JXdvMA`-fn%3FLvt>GjG$nF2 z;Z!%^l|?mOW=+MV=*Cw44XM-}NQ^~S-Hf`BV}Q%JC1!%#Ilc=0af1S-b5Q)0hNbJN zxPH}&;{@<&TL{{Gj1z)MlM`b|Y#C?r6@me1n=uGuZxqW`*Yn9{ab5*gjtdk9iL{+n zggiR#)Cl0O(%E3!wIl7jtYUC5%%4(vsStq<{dB}UOwl(s@X8b-g<;Ft^OT(N;Uzve z?oGRDiR!Cb8WM6fl?y_XkU=J`Oe3Q~;I@*X%LSCi*@KKGUbedYCD_`F9|ne^TnYoO%tm`>3LK)$N>Ha*j-bJh z<41}Iex!Iv^P}kEe%q2YCfY|Q#n3oWrCyO_#)r0QZ#^pB-!tBCbjs?Bb~_}(a4?V? z({3@8sfslWALu-Fa*9BpBrbb8B3lRD)BxJav7z(`}8cu+6v-Wp-(Z5}^| z5!J2(^;H822?A8FHW1o04TMFX>^V#aH9wHPGIat?-0_G;K`f$RrnC0y<@%^XO$k(I z3J_BLLEI(-A#4e;E-7V3(e|DpJ7Trxl?x(zCv3FU0oW-3!F9Hoz_OAFay8j?md*l& z7b5U=cYzSn0C=XEF6*2sf#260Kp7N*D$vgn{ok2Se>9#?U*2ritDD)o6$VW@>GbCx zKqA}fhs$=Rrqh9zN&)mQN0FulhdV}3M-PjBg#`>$D;GX(J5|L+)YJk^rTfK7s6md8 zcr5|a;pkaq_+c%EWNM5StPL5be+}y^Lf>(w_I-Bff_GEcgj<=!4wZ}vYD-K(k2a8J_1d+*%=+0v!*Gwvg ze5)v?w~lV2ly+h`q?CEufx8hvdT-)5fp1I+pzwQhVl=!e_hx^OdkL4+>-a1HyN+-e+KR`Stn!=CCNc6@K{(K~!rwxy0Pj5HNGI0z& zs;z_M5a$t=$fNBQ2mGi~iT^Sjf^ndn;yPgeuCvXl*l!jW>y00Ebuh5*6&=Ea{5BnE z%J&h=GUs4kBWT3L?iHSP2yg=1>vgfq5$aRBO+2W=5aKD^sm;NJL9V8ngE;~57JLhx z(^de4+4!haGx@|d&@WaWOF!Y#46JuWfnW*XNo@%%C@COUQz01SU&E;MkqMp4fB=fv zjGvxfq_}DJHcebBj$A^KN5vqqpxj0FnRMlG+V&jH$Q(dR27s zI3X$b@WP5)l&m>pigSXr89)~mB)1}A?EH|iF3#Ztzq8MY>*LVtIX88z^QzQMfp_Ak zilHMe3Q#^G#MWuVtaUa$RI}B^V-j3#_!=>KF?N(5s)>lU7bm@AJb!rXR~vI8hA7x)j_r0IYS1C8kkQQKkp6!vGu8UIcVn zG#?LzGbm6tC(f_ew`r@+pNkO!Kew1w7iq#f6onv7xKpD*cS00)?bP%R4G~A>IyKrY z7K6aoI-Ok;C<0=Pc)!MOd|%T@$@&|NG%wx3m(*BT{kufk@d3zH;mVFkbQfXZd(Jl6 zjyi5wf1<3-z{5EftH zp{6=KODw>7%W0>|bnL2Q10=b2S9p(_#a3g7;L*K$Gl%mc@gd$}$gi89*p3Wl5@fU}xj=KPX%lK*(sFs=_+-3%hp@1iMNdM%%TI}cZ#Rfuu34WY)Z+r+qJXwE4 zx?_Xw105S+MWZ-`!2#5B%qH;F9dNjRk1q6Nj1assjr<|)Uz3hV{4E`}<+n3(IN$*d zCs3PqD31oJaloTu6*T7QF7sZ4B=c?{OI8>L&o{_)yx81c{Z^uY-;J^%%$CIOYZQb7 zgDu||1+j(Ub!HW9Y3L6Z1}3D$*X&8!-8zk@6xz@R0i{qX1e0ik@Rw16=o$#t`>XOj z-H}6-*lFa`i?ri=+5-RWa&hTBCcdO>a5lk9(a6MxvSX7wFn{H!*?Cxnl7h?sMB6ME zUo6NUX6Csg8)s?Z(bqIRxIJ|C`tein$%f8$IfC4;^yXYE4V`4GAqg4)zA7Y{?WPp$ z-8C<-7&tlMn;22+8HE;0K0K66x^SYq6a@v)7>b|KR)Fyl}HApK0Mf8Mr3>d&UoU^-69GYC*SXB8%!hTjL-!);1a9a4MVO}u zyBAjAM-Gy-7$q%xZ=Q+ZqDr!)>THGa0*45gr--o3M1=Ja&rw;)zyd);V6?W+i*6JV z!C`Cn(Vu@5`0+NKGd6p>`lAS^Ix8Cl>ikPl~kx+qQ#K?{sx5oiI7l@<;Q7MDUXf(7Hl&X(|H z$IA?auQOWoJ6`+U>8tdlW*|I>F71smdw%|Hu{rm|fW3`kF)<;rqga$!Ow`NR0Y%F+ z(=tp>{d!Rzk${iY2;d#rlWx-u2jchXb_r2-nVc8aWU=XK41ol%7irl)`Wdqq>&TAt_=dezmLbT@F1ol_-1Djxr-{wfYPWkgzg5O6C&`+3?gNw zu8^7fC9O=lO6P|7b**ha-~|Ro+2wEnel?zQx;WdUB|0hPF?)s02GGvc67uzDu=B3+2J-GOED230}9$1JRh~lIAH#RjhHidO0VG zA2|HE0b(9jfiu?@I2c`$(~j#znBc`FSZlhjF}XgNA2=hOn_i2&JoZf+>0=YkApoF{#AE=E~Di0V&l=?0%%ypMq4R28cOy`H5mj@mBDKSpIdD%=PJ23F0sw)T!+#%r4fxNK7#@dLosdvNzw{u|E z&7h*aU6}iNahkUF04>&EzV;0TMy7_sOF<(XUPIJvMN3N>&$!_l2`PLh~r$dvOB-!F?KqTj3%Z_PrA%OQEznMjD5RvkgvkGAbT zMxfa1?#~dcq#x)0M6WYU+cK4Cd(k+f z)OZjC;^&)`PR3Z%S0CElnGXnQbq9en2z>OfiF5g8!yrlnZmTrJaTcQjAu(x4v*s>l zm~~wYM1y#>HK4j}Wx(reYoPhw#~N~Ya7Vn~=0fnBT?DWU;nn4`4Dx|e9Y5f&3P55) z8RT0a|96fod>kEF`0?s|wYXZO^K#F6V+udMeFpe!Orh94HcZw60h*Jwn7yyXfU2rp z3;O%%zTxOtHf&NaX^J?P%fZ*~9Y2)OdW|JfRxyUMTLDsP=bxI8*WnwJxzCBRz~fo&C&=B&VW(UP6b&I|}SDm{vd1;qwmXgyWLxSbVhBS-|- zAS;+pZ&qIySL@%CJOWwmeR2?MR2W0QqSMR5$AuOA!4ARn(Y|byEOy~0@u7tsErw+p zbr&nF$uVlsM2s3~Oft<+fulffut*H~RXL6xycDC^%44FL1P&p}g$6XD z$HeW-kFKQzI9@I{ADd_D;CD4Qz%KO}VXC<^6_|pC(_$4U`;GzB{Z8hKgDPewyfRk2 zPdkM^zB*5SrCla6ShHL4Wb-{867_4E)t9D* zJ1E+Q0jMpQ^8HM-))*&%;SQ2n8U`IRf#BaZqzR<$;e$6nh7%%~L(jtpTb>ZX5JOu4rkh6(#7jY1 z55$eL4b(&j5cl@?buO(;h>&2|ZXZ4@)?}Do%jkr&&f^D?W|tN@z*wy`Ng3v&0BV~k zVQcgNWylujy!80s0bZskppc4!fiQ-_FoU$cn^&3na51jT2r#g^r`H^G`@EDYbKa+` z^Q-0e*_*V}_hz;H%_&sp@K98+ZJ2m-ZI}iK3%^6Zub4;Xzz}K}7&-K--tD`^y%#v^nGYSnJr+&XfP9!g7`#D4zUGTh*||MVe31liusn7QVqj0m0HL7AzitW zlOdjLmbXQ!7>JGPRUBM0An>WmN`xL$)q1ZtfI`s-sl zzg`DVX7#|O<_M$%aYrpq-DHLWLAu|w3f!m5k9DBt(#CPCFPARa&_3)%)8(ysllN?3HJ%M*+?g<2?K0oLu(x6Y03p$VjuKyC_rZ; z0-_=o67ofk8Gv1tLYxMJiuA`_V*Py|Ply8@kTn#BLx@x{0a7-R;6B*tYeDh&GA&n` zy^d4#J4m3Csul4JP28$kVMZcXlSMO6hLJEAADP zc)~KQR5%+QPk48+x?G$uFVpPN#q9WDZ%pCc%O6p58&i0IP`lmV00QkH1xEsigen6M z(h#S?gUmYGaFkH3nQN4zy!t$mD9WEacg@hLxQZU3vU*;DI>I; zJ-t{irHiWYxcJ)X0?MM$gs>7ns&#=aCc}&@{C190eroM}bnHEbOQ;Jp_QP^pQ1tgQ z9-;RI);1;Lr`yXk`+D|#gX4vqMsR6Pt&@0<;?d6_qdH2B?eJg#(3yGsaPAM?Vt6A; z$=vW*u>(^`Z|RXZx)y9Vv;3SBW1fm{l#UVYT0{+=?J)0bHlCYk?^W% zCs8fLq^!yJi}Q8loz2u?HYy1xQ0dH#;65y{Lc_TbGY3{r(xEElm^`o_B}Y7725)X4 zlKv|CDLiQcGDeQ>5_zI#0liCy)>BBA~D3F7=udDz-JYT*fS>`7-o_pma@U(=r?DN&(~`Y1Abnc z2~bos2^k9KRB>5369Hs03}x!d!fh1KIg^nwhR`oJ7y=_SS+lsgT%H$;Hh{e%ftsmc z@Ul4)>5#z3DkNC+wksL24kLXWZo&5)6nuxs1nwvsVpbiuL^gS4YQk?y-bxs7 z)r7#)ZI=p`jfupc4Fk&OU@*J{3*63FY%3D!aG^AtEjbe3LAKX@H_JsHE zOC0T#HhKSjahWmz{8%+p|LN=JXjvEf0{R^KhUql8)r@_agw<9-17&hjUl{2eIz!ba zGO?K-9(amMXWDKb7T(OC#>_XFg6|!l_|uq~gq6lAbM1IcSjvJAu`lYE+W^(bv9Dv} zA$xwk*cU&n$w>-Hyoa}TdH@~)yvHv{Jv#Iod+Za-rfio9H1E$ttcNlQ1bCVAVDH!8 zXD`#K!^I%EpKS{8GBCJo<`L0^3Iu+i`H(Kfd$~~usjJOFjS+u1) zCJ|8xVFXC(KzOxW-CWFGFN+K!K9Nl&T`D0!xE$_=@lb~Wo~;-mM$&ZTkr8MvZym!> zUDy+AD;Va%Fz`?lhTcqUR7{b;vo#W-Pl(dj8iBWnY3wEu#&+@Jd;_A@OXLc9?v_-HzifNG{HIVBmSRj*v@61!_jHxCCQFC`UtxV%SOSVQXJ8@Ah_3 zCFd*B&K{}LCx^{Cg}~=(jdU1<+z9T}7)a9&IlsTVuhVrD|37u_)*DBXB#plBuP8J= z;5;mb5XquM?VD~RjczT7n$el3MM4AO$OSPJUw^Aa5`4h4L<+Gp;B0-CNa6W5D2$WhZ}jxa$^?K3gE zI~f^o=Kuf+-mKHACfCL>AY1tR81Yv%kNsfC8*fG!@KUY>L?nd|IFsoRy5FgZtc!-A z=>?X*ulf%=t^O6qc#sAI=J8@f(`HzF?6xF*T1@(>oJsw{#Z5(zSKM%9J>zK z!4Q=+vhV3;x%9r$FZu*1>lkvUv`Sh&7`B8A)Hw%-HA5v+E-(?;ahC|YPX$K|pzvN| zAb6G&3slXYEiSLRVBnVu1`owTc8Vd58+#TUdIrMVIh{B;@ntv=ajTI87}Cy^i4k37Qy`I&%_7Uh zRkbmok*W-#`MADrKK^=}EYn0oVhG%BLOHU>02+irAX8tTExs>30UQE2Yi1^_pm4z#leXZX~X@^Ol3PWg83Ktt-JUjc1YMCySKoUm+Ai0xsQulSQ6^-D4bdFtu%Vxt{10OgN z|JCaHV=4e$I9*}_J?gVVmkAzTqai#18WP4#2cxrO3vXvoL@$NY8Hy0x8y)a+PNCCi zYpjW$Li=xN0WgP!9}eSe6qqVe!n|}my~e_ru_qQ`^to9E?`&WU7N&;L$HHiVS8`Z9 zV69x;RiW5E_ag#TR2ztai^jlEeHa|B%XP#pN!rKOx|s=wN5xjM=^-N75sU7ivACX( z@q(zBZa-iC@(ZS57?X)EA0{}{om0NIi|?(qD?00%IAVl~ z2wGhe!+a?K>e$B^7Fh}wJruG7z&OC5p)?Q8-P!8{C!=r~fR``OLq`EO~$j?)QOLB>yO%cus= zx#0dsb}s!ArmHMRJC`0WFMmtR8Na^y^l2$6ZPml{_{nRu+tQ6YXmBUpz^P^z6Q_Kr zIpCt4ZdeI@E;2DWxSZcDmcO#T1m*@V^*^|OBGHCuB@J3!Mwx0*A88cjJQcil=01+| zH^=qL{yCjPFV}GD=!^k;6IZeC;*ckIeH10zKhhEei-tuB0ywQc^Eiwobr__JikNcw z`|Qc(r;F8rjAGV{a|x0-`S2RyQRU1 z1m&P5k!b~!%QRtUy-u6aIAQR426a)8AYsI3hHe!!uSUXHv8P!K9WMJ+L*g!dczX#R z2!{oe?+)G`%pTtqQ^4`UH5D8Hp^p(~YGp7-DNsz-g|Y+zz6le=We#jIHzP)Y(>ag~ z9c(zIH3%FT3aO(tO#wektACYcA<$Wo04P~7a41v=DJNQ5Q$YxT>|#$O%8-FE*zS%p z?vOwN&>rw!4g?kSvwz#;wx*SkfKIfYVta0~y$Q z@e=7GhXdETN*!wdx>)%Yo;ow&$qOOFPE}I!_%;gJ>qsKTeI#%?n?>k)HzddnGmA$H z79d`Sc}cj{C?`Vg0oT`7A*lqcVoz65>VswyLjl8(pt})mKubFy;3Y*H2#102*Tr=j z$(({LXB9rz#t&5(oLPjvv^E6ztC$6TxkoNBY{5hyK^!LtL+CXy1bAc!Jh`|^C2fq^ zyev?_7gQeL(F%eyh@=f=(Oja{s%-v#arHS(6U9Rw-lS!EJAv%`vxoQwLm+z}@0sJ+ za-*Tt-#~Gvs*%YRF#iM<5=KU8zlZJY!onV>9E}x5W;8%8ATeWvq*RI+hCO} z{nSHZ93{~lMFtOOyDQvPk-$%t4g7A@=>6={8n{=Ie;f>xO{b?qL!75dJ$X0RX;rkf z?=x@^;DbD?3`bzjFf!tsThLuP(PG>TNJ_CGFu?E_O z2~=9WH5G?boxGZa1O(Jl+I$E)-3G%fLtvR~8Kq87c2xlf;{%Tjho@=AeJYV(tgr_s zz^Y54GzH0M&{nmIe&m9tt5t|C3=VcWC2p0`#1%S?48bV!?DFSnw!KHeQ2~dww+c}U zh*;Z&g>QS>MR(O!G{YA=a(GjlMOPyO5-F=mMv+QiaejdhWgQld5J4jVc8!G*g6}Sb z5L1&Yh{KQ6U*gCthNZ)Bv7b6T)y%761}G9NJ)l-zf{{Hj3xqJ&A=JHSQx8g3}7d&Qr`^E>p;f zji%a^aJGTFTL@7`wHChqnRVe55R7cNJglrVWzAGhZO#F87>9z-*o>%)xd*r^y9@d7 zpumZ80LT1_u6KEBNPYj9wl8crr2`f^@TL}39+XRlazi!Y!_i%sxeg73Y>Py2hVtMd zof7z%+G*l3>waoWS7?hLhr+U9Z4#!D;`KWsIdEP?Qa~E0p7wnx7VhCq34*|37nW5~ zH;i^3a(+Ca8B2{Un+jZ1BnXRy5qJf)jNmnJks^Xa<%J{{RE9mHsj_R8D+*1l5zm8K zcG2jro4S2uPN)(?OKW<_hXxn|OVe`16E6XVaHx0~$%k^N7>oSKP7eWx6wW1(Am7q6T}UycD3$++ zvso>y;E{zDk2Z_dr`fZ0TBgN^n&7n>iUWg(QYj2k1xHt}m21C>$?B>d`h6uu!1bQ!=^s zI#z()8f!WzLT3VQtE@o#RX!!{80akiM5tmxw|LCvkS%MWX6Ar)A}eAS8y+vLKpPz^ zI_ri4^x8fMO0!!9%^wpBp+{3YN02zU+Znt@!Lh)?5ED+|bdI{i08aw~jd4IsS!$Vf zGU!AZY7SvtRYQIBdG&GeY0UyEwWt~g@F*@>d5Eu-!@XVWr{JG{3$#&LDQgK*x#!@m z*0S}mVP5LWI-At(K#eU#wKxbJTx+T%1$4fps-v?9W$D+O`qY>Ye> z?B|ST^dnZ`9&T1sl4?Q}ToHMKaULAs^#x?c<6@lDJ!^jCBu*o4cG&(L7 zq|i{Ds*wO8vT?2)*Vj5Fs7yA`lS9u1#`A(jMTL8(G;=XeAN0mCLKggkc+Ng^T=VlH ztVnna_!JHNMu~zZ<-jHkjA)A$HHFM@Vl~w~?5m4*^Hwwu-o8%{oX+&<7MF36gXBfp zN;Qo${a8$ZL&-N0a5Utgef{7!Y+fkxLDgO&oTUVgh1W&(9mI61Ya;>|(Pjm#D<0 zY8q`Yf@!qad=rNOFzAXr7~cuxH@%A}v$7IbV3w z@Z-qPo*ZJ}aNtE5-DwQBTNUx--5t2M^bch2-_yT$@WcQ4xBol)=|5L#Ns|AVrKA6| zmcyS;j(#~>-1#MpF8zSt;Ek5(GD<&TN$Ya)HH|v`kJ(SJm)~Y-#rvx?vHEqdSN`?$ zIJ^`H<*;(sDk6( z59uXDB4`{MR^CC=mHrUn{LOK>e)Rch717Bf?wg})a}|8`wyW3%LN5go=E&c@c~qg_#t%2;;PzTm_FO+Qh zjHSWdZC1%T71W(TI0`jGypDiCIE)NwOLp|irqW?@FfOP>t7^j_Aex$&oskX& z#|6`fJy9t2l6KZgGTKb&uL()pFc4CI?6(9$8iwu%n>q*ppXwwJhuFcF(*e#H5d@6M zG38KcClQEa3xdJ(h%n$`Y7qUw$MhWr1IKe!BMi~tS{57(K_VR!fJhZu|5zB>Oo(AM zji8-fa9N|#_0Am`@HJEws#WkFZW5g?zF*c=!arn`4x79j zNTo5ITsnBPgL%>Gbt;aVeo#!+uDpBSuPsDhBlIk%4S^h@Q-`=i?is#=52hsaF&!e0 z9Mdt$?Uf2S_Omul7=WO__id|m1VIRFO$+mmp)FDgkt4hC^jP``#X|AK?6QLEHhS{N zS?Kz68lD>$w&o0-DJF^*C>S`*oWP6Iaf=IYGJ^J{Y{1L;6@^N%<_8oYNE-`chQ(d$ z7$*2`*q6{DD0XwX^Ujbeyrz;QlnL<(Y8?34h=b5$G7d+@N)2vrJTk}Okb6l52Rt_u zV%GxU`}OAHeD?5QmKN2SG?u78OaLEC89{rF1ylLzJN0uN^yWzrSOX$PwK$!T2t9r{ zC5*s(x^$*pWd-VGxfLm+FPCca$=2C=QenAqWoTL=u~go$Ktd23%M zrQ?=5sbfQs2i&*z83F*>FQ_!-VQ5CL7}AUG1(?BSM9aWwwT#11F~xfc9wdn2(OUt3 z`?!FeCgbys$qmM)la0fgDBR#(S`qGU)?qO4$YAKKaSb0(8wem&i3r6~oT*7+AP7C0 zq!_c0T<_TrgWxs|gFuDtOFZMJDw8~vOlKzyq~(nM(oOc5Nma%VVhOmN9PMHgA?s!9 z8RioaiUc1HM{CB97Z(=`tWcAt%%zbq z?Xd`tAG`!3hpAkLcM$jGqH&pDp#9B}r+y0|m{N`<@dM2-B#NL#$X_jXhyzbWL544V zaB?KQ%3>96(QBFIZxO`7UG1nAx4{`^qLXEQfp<~giN+531^!3BIu6wW4$-2VU$E@c z%P&CDI&RQvAUNzm5IZpt#$+LvJ{5rU6p5$_q-%aI5>yfOJoGN2MFIf)o{OKJr~Zbt z&+__mk@nOr*SW%#3J%(-;Ltj>5E2|GPAM9A(LXGjyuH&r4shU2n{b>hzRuEyUty)I zPJfbzBKS-fLpUg$RH{jUBU6YmKk=5~ww4OaFz;FiRKaWD+p2Bg$kg)fTKXCvUMxO- z`C4?ab&AH$Zi4H;nc96yBn1pc`VzLqRAt%NrCoTYA9H$oA{#IXBCyPyY=pGS5Wx#N zL>@0U%e2zdVpA?#(HXXb@7hj4oowJDH5B|+v{Mp~$-zW-rhXvDFor`_Gd)+4h^13Q zf&yaPV9xR2)6HtL@%bm~Evgx8el;c`R1KYEI3)Px7+cEGWF-2FUIAkh&@nR!9d3BN zrer)%YhL|hb@_35>33P8r>fNxU!u(koT3hfEHawpz}PL& zHcAi?qeKcmma`{m2BaU3-S#2)rJ~Ruqk^WZWi(OX+wYi$_7LEWjSdIh%1pnKj9WN~ zc(_@drx{+q^-y5b#VYViMIrRC;i6hCrNPc)xWgfK8jC8*WJB6M+`~}l@SwNIFJ8+5 zkkm1)_BDI4TwattY`CtL2K-e)2rd+7{&)}s9~uH4&>XvVI{>(b$@?_fQI=J}%{1QJ z1%Wchkbu7GN|?5-4TtjP_&y8@!7H@`eAv0d4=C%ivq2&??PLbHmAW$Ctx0UoRv z;8xV8mJY95ymc%#YGB%1#Yss z#^mZxO7Z-#AYE<__*ctyoX&k068&n9+d?Uf(HfBmNltf9AHVb((5oxOq1WG8g@eP4 zVsAL^`2p85q+bjoy_NIPA#_8FGZ>*W$^0~7F+bhj(Z zp-9sn0b7*vn0mmVf#SfckG`F*Xw$^?d0BsFk@E}zT3FPTF(jWJz66V&4 zW}3%fuH;b2Jr2SNJXSdo$Ho{6WQQ3@Zxa{%paueeRUpFnIk2_n1g;lO2s@gbkSL`& ziOh^=uV_A$O4;<*h=|UXKDfTN3;eZBhPGZx%c=H6V=4yGgA&sTeFwoF420k}dOO%S zbzsx0c>a$IW$kkd8-zZlmA#l=AhGLm22`c|p`+EXz#UvP(b>NQSJg&ARHR6lpeSQn z-Mxc+I)?#edy=D|^@Q^2!agvj!t1;y|DVL&Or?kfX^4((7p?%6(qobmm1c64Omw zpjsG?o}%ADif-Mamx_=yy-piR#Rz? z&Zkf1hB_V9Bq-YBT8_SNBf&SP1Ht({QmxE;MS{pqh^|9b#*iR0%sBFPaGOn0EbpK| zBWbE8Y_8@5y&X{yDV%H>Wxpb!oTd_oq0`xb;E@3#OW8d6e6fZb(qb{}xz-1VLciw( z7pYhp3L{+DmSgl5>)73Q90rZR8;W)C$e@r7w`OmbY3ih5;XBiSol1lQBXl?8BBcZn zP%!Y<+Z1kX*a}l`i>&Y>TZ4(iZhRT+;^}5_`CD2_bUAy!SV1lw5-y@(>#Oo@UNeoVCuixNa4c$WzZ5s<*^G{dU268x7GTs~fZDFQ%FFdt z+BNdi@*)j_NlV%OnmYE|qo!Uyc!bYjIUu8%oCEIKt8(m!iAH2A6^Viy=PvWA+4^xT zbqg_`goAc^e&4TPa-XO9Rdg<$U3qUVbF?+`ht{-mKt?kX;^C4!Y>G`+{BcQ+U0@Iz z$sMy6*6UY6!y4vEGooaV1T)*j>iD#X?wB_a1UWJcx+^opskZnyO;?_VRjyapv-F?; zTBLcE&P+QSUZLH&P8JLmEv^k!M4P-w=l5{sy<9Lzcj#6Cb2Pf^kDS8Sa?#x*?t_-b z9{wJTb^Dl*(8t0dJT(@^4&TXA*~k#QvI_IkbfI(xnP zy@>gJ9}7N!+6~ZFR)o5tQwy|S)xv<##)BX7N6QC6#>5xv=yp3t9vvOY>EOoD2It0x z($p^Kd)}QKlhUp91Z|fDG_Gu;0d*D2odnw z6#aHiyiGaFJ>g6^11-$?3miC|Q^9UAIq>4IDa84b#)r(FoPS%S>G@7Ny>n?J5@J1Y zphm=)!PjpiGG-*@N~d&46T|dUyumQo`c}8w+w~^xp!qq4{mvxtuu%j;XY@7%1dFLbzsUy? z5LMV&DZ}Ulyh1bNPjm7wQ?Nh#M;c9ddA+*yIN(Q$0{~Q?;Vk0+2Ta)2b8=Gtqs{ut zYvV3NDDxhd-=y5nVQf?w45iR(w}u#6E?FuqF^MU!_?)cn+sVLRn%$+ko!U8ZqY;w{a#AndJd%Q*<@ zLtfYjx68E0`{s0uHXsr>A5FJhZh>dyYKXKQ7Ce$}jk(DAu}9#Lp@;ZLdS45k4o-Dy zv>?C$Uqgm@5C*^~!hqLvq2#mW=0j^;BQ8#<@ZiUdNHPQrc+c8FFy5hq7zr36C-4b; zFH3jiQUoN&T2%-Y*ES*(TAtz`%gyENuNTXdc>1M{e75D_Aa8^#p_-1?DjK+6$su7R zxIr?v!>4U%^rnI1IWBaF0VPi7>@(C2@v_1Wyq05!)RNDxSHCVU*T1E8;hg*Qvv<7x zq&wtfJU9-A34V|bCLM}w2s)D;XjxH~j71a!cQ$hw0VED$qLGju0gzP8y1bsH;ccH0 zUl#!tyY0s>gqhj6`mV-mSWN7Mi39&Sh2uQl^TY{p48}0SmB)wixFzXQGRACcu@z&+qCh@ zgUj>edb_lMT&a)}6;Y0ma5yNOl^7Iz(xRstc!o0)GE3wWXL1G_;w!w32nL$uK;S6- zRGvs7rl$jLRnq}~RjCMF)VOPH8YD1HL(rJ66va{^;yhp(yxmT0IP4De3v76}`JOD} z{UY_kee5iQ#pYLI+;&Pjj19QS^z>8o!>U-jr(B`)4UZQhkP?=8k_b8qv~SNsJ7~2uq}!v?XZ~!vZd$dh~b8gDK>9sirw~FaY=MCp~Z>juA_KFO$Yo{5eVbb+ibvJ#WdKjy$2bJg?{@} zSfXvY$c_pq(y<+%YiR z-F@{#TYOj-$aFRyZC8)c1SLgq-+1{k99%G`kXS>9Qn{#v_!@V*w|>J;wh@=HWbuM@1Q<2kuU3iNx zS8Jt1A|yLFQzL=Dic-kYWUU3$KA@3^^H>1T5Y7p?(99fjvZ<`Ld{x%?( z3Tn?I?nf@ryv$?Rg;lMnzt<)~F}ZASJPP}%{e}Y~umKkE1K2POY9SkdcC$l%FfENJ z({<`KLIu$T0`znO@oxP!4JJz4IHjHiXB22~4Fm4dR?4Ffvo37%L(e|fcvg}l_jONemQ<#tTDLlANi`85&(&8-NI_q9Nco)~bf&p|AEa2as z7w_Z%mZ}Mso5sV4w3F!$B<`&bc;rW?+_RWrPQ5WzIJkd!>|)6|u59I3_`CWDL|-UfLE#<%tHx? zDj+E)5HMqSyiT?ART`gyZiTSo6T6NSAl#siBg3F2bRX1!(2Q?WBi(gLpXep<;2czk zN|50J{yIElAIbNt>+5o{C*P$2vB=3d4y7>IcNBP7u0f>3Tjk#6P#tou*M=vRHol$J1!=&J}=nzo==`#vp zgpP?N8sqqoLde;RbsC}K3#7PF4Fix=7(z!InyCumtU{9QEt0v-ZIl7Eo8^WFIn|6< z;Iv{9JC@8g0v7Mmiqo^SG+gRm@+_NfS zH9peRusSY+h8Jz-SyF{~xlB80e8MDRXCo|tDLbeSRrx&l4{)#cAA&)sL#VwBc0Oh# zB{TLK)7OnHgH&LQ=jHcm?JM8=3MAEg;KynwVI;KU1zM`SkVXRuZN59)35ilJ@Oa^c z91XV!osC{CLgHz(z(s{xaJByH>!PO-&rw341vLzzAcKo)LKqBq{2r=V5~2%3R0>?E zN)f6M1_R#B!Ep9*eSMwc@vGFu_Q^|y6HbQ&?xTcANR$bO2KQCh3ie3*=X&#H_TsX; z-0kaszJP(akwkb>4*ScIM0lO8Q9o=lhcv{G@!fpndqnvbt9F#rSY9OSl;4=HteU%j z@3@$^0nw1JQq0|Fq%Y-Tyxww^zvaiH zw z@<8H0D%(2^2rxY4XmWL^lx>3|kYa#T(_(|-D7f)1FoBjcdBM(Xea6bnh|DmATg%F>5N9G8NsZYviW3lxk`I6Z*DGsSzZ)V zl+!Op;RyYXZDP=zC7vKKw=|J{jU;;+(Fxd(BGF&y7Pz|bj(rMgp#J;C#g~E@KH$f1 za+rZbv5_!3!7&4eRan8#*mkCLg6;Z%eeKx65;T+ds2u5G&`V$LF!j{ zyt>#r5hV)0oAF51W+pFRoW9(m3EwjKsE6rE$dyMohJ z#={Uu;n9i)eozHcIzVr-n;6K#K35k>QDw|!4l7<%e&RUPPVvL0$IQbro|OZyw?|T8 zv@v{HHV=b>GDe5#cp-l@+t^(nUFkF+VeX_N#Ze#y9xqT36w5LmV^Q$dWN6>9;E2J) zrppBH6bwA0;st)WhqIU;`Lz=)dI(qq`+%9{II7#%gg0{bq3gW7StRG_=W4X0N~azn zjKXbexiGIO56Si*~}wVOSH5#L(c4oQ>q%tR_aN81{*g;_L6T2dPe0%uT@c zwfn@!utJ&Ni5QI?14Dhr5E{G96NXQ!Sn!Lx$!@(Rfbfb=IcaHv1I}btgrbDuz#}t| z=S!&OS(>zhnxRw9P8TG8z7+~*AYvA-{W!!;lC2DGVni(*WkfjHdfbF}cAUtbNU-o0 z(#k=?i3pT3x=%Tp97yz52cUi!56o~Xkqq4lY&~%Z_JEza>TPylS!xgXWer2WSEZ>7 z{B)CZO)F3YS>WvqhPF9$5g;dw2~Ri6PqP>0(g1kCX#01ffwbC*p*!3~7eo8y;}7@P z*8Z+{2n@ikI_If=pMvTyX=sLX4{a1^7en||_9#hKS9K`8`MkPVoG;V*zN^LT-re5p z^f%A`hNoM?cL&eP3E!@Bbq-;}bri=bJgQ5Q`!&OtZ614m>{o<#d$iYEtlaS%Y;bmb zBjMu~9U9g$n9Eaj&~&9BtTC5+&^T4&@!9Ff?P?cguG&Q{!qc5xb$1VHysnE_qB7yM zhAnX_Ht9%d!bQ$ay;|I)$#cuuqtDBw3_cvv;dG&5gWo7NxRF8=!dW!1#=DqKhe(!d3Rf3h^b((?g*%I^6lP}vW7sa$w zUkN}Hvzp>?aAiK{!z0FU*pm}o3!$xbEd;Nul-PNQAv)z>eSC1f_N(7Qdg{2w(V-vL z;UbOj`Lf%>TMJ%v(rfy?U(;jQd4aJV@oZVgP=FQV0RJ))p(tXkpqW^Jmo}bF>F=v@ z%^%$8_DGm+iesG|=-(>WiJd8+iP%Lz;89x7B#pJKmkWfWs!^bYh*^X=jz7pOvOmy~ zI_agFb5+`Lew?mjDDcR5@Qyrqv`7QU&WnoB*6LVr7~4?5`8~>nWq+%9iJhbUZoeAM z!a#VDilyIEPwTRntb>bc9^kK2%OV#kSP5tck&8JVjjdvbDc@3y7&*Ff% zoM)9|_W(e%W396!!m)f)Mtv@<7MEcu#?~~eSZ?^fxd%z(j1LE=#f%+x_X!`0el@mG zwUM!trdj;Ees;WW4h(ROQh$8L4w|ch0z_9mq;;(A=@*Xf9Xo4~m3b$cL(H?-Y}x~) z#RZRP6`#wLWv$oc_S4|n=1Xkd|H5RyDT!o~~(^HC*L*J>!x(kxW4o1TP@9b#t;9~jv?8)V)R4@0PYQEnYGLW?chY~&YyPa|UlxTXyYucAq zPNZ8;2%qAJHN2S!WkBKG!P|q`FHd!RTO>H z?M@5;Jh|YIm3KF3Q6oQO4@#pT&=#-vJYW3o;jol3n?SU`!P#}L6B zgkfY9XBd`b_7EIKT0$qqkY{NPyzfN_#+W%eNB-z)Uf~;UAt>@TTS^sIL+@;Ea08Zc;@cEUCiwTdhS@ajRM$ z@ndxmVaO(~ukC@ax~<$Y;vg*6W@<$uQ5ksSZ5VV#Az<+EVzJuH-Y%}b-dvSCk>P=L z_=JZm3ds`6%t~^YsvGHS@o}^GnubKKQ-7UKeyTe=_fU7yV*|iht$cZ$hRH-PP zJp2|@W+sX4ngq}9`>iBT@hq`QZe-)SQ4a2%_)a7~vz6TdOwpzzgg-++3GS@W!}N26 z;w=DuNRt}pGzBx*O`;|iTGq%APv(Db61JPiv2h7p_HXY6EpL6@UP~$_k*RKwcMBksquSg&kqL5)G9gEUbyyN4=HYP&9?WzC7Us<(^(Ce~ML#Sz z=b;x3KN&TU5X+&hH5Pnj@#DDR9u?yGv7fp}A0>W6{p@%}2_*1Z4gjowKYNk(lPQ)##O1X; z5R7dZFD_DhFa+diDgpvd=tSbJ%xUr(5d@r33+Tp^xW=^}BWGvNmX{kpFAdj>HhyBN zhx<+U5c@YixCJ(uZh=P@EHH*lOJH9A3(wL-W0%I48J?wbKeFfSI7~^qH`;UdbbWE2 zc33~VULc#t%I)jUHabtw-lO%_fyQ31oMZ0Vfd&g#1%Urn{xg9S4LpHGb?RRS!w_K^ zULju8+}?>UX`$(r6vd${x}3CKdGAEFn-P29ihE)ZWd|C)fCLJpgm$2@ zC#v$z86K$#UEw7)))B)4`_8^nHcZyB|lS9E{03rHCpIrs7Kwr=b znBLr^(N|ZqvlJ)z74Lm70luf=gGOa|O3#Rkloohj#X~>Dn*Q^ic zh+FvJk?1ZXjaPO83ICV|=A}V5Nh&vKO)DoAezQ(MndySVZQ-M%2M)zZq;GUO9`}lJ zxf{GmqcdL-25;h%h8%U{2zU8dIG{XEvGbim7x zMUX;_CJt;sdl1erH#HXKh?S#5?qE^BkzOX#A;c1FJ#io+7E3%`u7CMu_UtAs@&?#z zvv7O}{b_9+B%7kgOZV%9QrrATulIz|IxI@f#D`QYb{?+DK}io&Q7Y{#<>az$8^E!x zmN-*$LV^Qeu_q^T`(hOakK1Y&anBs%1HPQIi!@Jh{WXPN*r)UJa^Z2n*UV8yI#jQL z-zyGbTC>9ekE?@<^v4b-#%$y8$QNKtA9l8lJ{Cm9Y~$sU+qOPhUzVNCP9*Rx6$l=R z`wX?(HV}B2_zc{y6JlCW)T=ra+bPlDS|$9pT84@4c+Y0h8hvYDjqs&3SLIHfKE&)#6R&F2EI@+OWZ`lsq}{D}A5xR^(*89!F6k<*-80>@w}KScc`+ zp=S*rNP)*gvLL~nRFUSRJ!|+{V?r%8yy;;~2xvZUc&3SS@d$YRy z_~o*!0y$(HCGppGI0yJ@Bt(~j`<_gPvKnS!(ALdB+X{8OYeDGw;(Ll>(kAqq)zS|s z26vPrN_2Y|X&&aaQbf1;w3KC=3nc-(llU$)%mp-Q5id9| z%&_&mppw*3kR#%{Lb1?60ghB90Wp@z#K;l6XoXn^p5cMW*G-7@aSI28Bz~{cZuCVD zilYP$YZo4RP;gOA2~mY06}U9THA8AAep;$Ta&9;^WcY%E&;YmNjZGznaWsMhnwT@5 zU>~GGD*?{a7T;960XVI2P~aJ=n*CY;uFr0it)>^{(PT=T78CU-IO0BWI|oG;)|wLC zq6l8oQQ~3RXgTFNtuA2qCS4yud3t zFj!STOJSK#jnA6tSi$Ct2XU+j2I7#wVU_mqgYJPu!3x*Ch=+JultG2FkT{YM5H=ql;`dG zeD>G0K3*{%1MjXz(H4kg6lEWaM)ZGX2g~Vb2g}R#RoWr>)AAxsWq-LyYZ~$sSBb{U z2VqB{LwwDgXzbX#!hQ*50BDj0k~uq7VJ-sEAiB zr84z(3%pTc8~~@q7Nz)->NMkqLTFP{5?(Dntv)tJqWHO61Vq#kk5Ft5gVV%!QZSr6 zO7!$2un=CjK%>Yxfm8LxRNHqd=BJ zMG2*JO%y<@Q`j69r@q^f&&yzh5W>Jfdz{yNu}ITczs??fIKR30%k!io3qLJ`_N|yW zdh{{DMGaF)e-)o0(y^Cbl(0B!f!#X?+DSx1LEVW(3CP3mn>5Y)3_C>l%nKG#0|8jG zopgI3n`Xk7o-7$oE~M3<#ZK*yGrX<+kq|eV>of%Y>xUxU!sXh09A*u*=7*B0;{gt{ z=SYXb1N?Ch)3|kC!g04+7+`pC@aUkL;@$2^U@#>)XV6NhQ*90WUXp(Ag@P;KiQ~Xs z7(&qxIlpo5p4SJY1UcCX^3Y|&t29b!vAn2fwz~YHO^A?wELEVbc+|u z@HK(0)F}}#UL!lfy#M4wN-qV55A8b7j{DdPG9D-uM zU8J?~W@#kf%8+g_WgPyim6@%d!u-Cg&M_HSU&$xE(- zGb-6}eQ&m2vK=5|cA7HZ)SHmjEqb(B6!9%?rZ{+_7!IKjg0EE*L)w0eNdNA!pJVHm zWvIMY5$W@zWGq6*C*DaoL3S7x&r*h*qT8P@7XNkgtz?C>8&W}}n*`CW&)~su>oHP@ z0yINg3ESF*)1yN_kIjvt;{A*j2YGzDFaz(L(bzer!S>Q=HG;)&Y1lE|NkwF3hwnTX5M@cs`58k2@%~&qPj|2n2=tT4EIt>a)I|8Nw%SFu;m(`l- zC=pBpXKE0jDz^&oa!puE0=V6TnrYAnBLaPhspa$4*BOV3bmt!P>sLW;@EdImj0iZt z2NBApy$I><7<6Z$<7FHVUahV_eopF`W}>GFuf=KvxU!}K5mAQ`<6Mnpe`N7^9!BTi zA1xlgNi{DFT$#Pwr0v6IX}bBpdIjV+Kfi@ZxfmO2mSgN43&?(bD*%=IQt0W$RXB~L zvbpZYp|4O2$YMpNeDk~BbDkO_j(JnlhJ$~?j#Q&wfu)48VuM>XY8s_Qd@zj(3wvYPz^ zosHj?pFS5Q0kltbg5n@@2@WV(0v@f22Thu%bJnJRlKJYkA8d^ zx?<@`TM4PeR*%*g$K&{cZ&3SFJYK;F@g+d6!7z3>6`c%5E|iLtiu)K+Tm!?Rc9Bv% zGAl=rQoKSl+Gdco_&wAp&};>R2uz5v@TBZm)1kzf{EE{xw2U)}?d~yF>|($>2m}Pf z#hEfj{Y8pBieZmKS+VK^9q13@!u!;r>rfnl3$mN7S8VM5*!3U{Ut#KY*2R*XFVhM4<#TA1Hw zU--o+5TQbY`|b$@)5p$ao*x4O;V=;1abaZy=!Hn`0zp|^&oO>gS%18|-mF*GX}mG! z3biZikDt6Nqy9V4!W{MQT3M%`(+>eBnJep}(4gd;(jxjqR{5}h@Jvz}kxR|&=7)ZG z`r#ekRSXnp`N-z(eZQ^?SKVmD{cigZt{s(2>ukI74-d%!44$HbG#?!}(vD|LpXhYk zA{`A=eqT`(CgmfPmvATjs*#{HUklcp5ljX-<>AqKEC<7kU>|<6SS&E z0j*M!gc1=RT&omg4u<<;CDXIPVj_`Ce*z3tI)MOZG6W%fp`&UAb6lnHkLRY#WF)95Mle+e0SicaMZOGH62hFv!j>%;@8QS8{P(+6)eRvDXVf z`DzJXKwXMsMvzR$48T$`V^1R~$I+)f8Ua>m)HKv0Z>M=ru!rv-l>rS9nuW$zo& zuN??}t!fd{o;DQ11VFJbC+5DKYB?~zl+%d;&B~4LjKmWx{`2T&vn)3dML0A{iKG){ ztg9jIpIKl};*lnO{&c;>;Q9nM2MP}pK+mveQd$%7>?Z94aJIheZOeZ4GFFxwf095xF?gK2BlgFE=Wg--7IZu-P(ZTN(oFJ6AsE#GXXM(UUyc8jv5KGK^ zF(%d5A<{1?hrURFC2C1*43T&1uW8f5Gz_(z%|lkyBH`c&Gawxt5Hht9{Pk9avRoCF zf+4v^E(Wn|yNsO&p~@oh|6Jb`8`9zBYkz@{sf-9Q1ipCfI{BuzNjOf*?kB^5;2IfX zV2RV&K0=`k@6`02PnVliB*KDmnB7$j9f_BuRighM%C5<>r`s&wv{Cnp4=D#LxSn{pr+D1_Dec~ zcC!7gj5H5Xev|s`fA#eV&kRDattSqTsF`7gLX~URYsSFH)0admsK3+-u^98C-HbFY zfP){lMKcZHNaOz2o3Ckia=cP@viL>K3$!}q){wsp>oHAHVue|^vL!hz(wjjaVXAHj;7K;&|wzN8|>~lyFHG90mwPi<|^wD zJm6`y72vOG1;JC`u48(@Pc~T(?8^!Tc$eKV2zfW&zUd(Tn)v@^_Au@K{AK;^>dVT} z0HmvNzZrY)EEJO12o-E)c03bq8VT!|v@TS5| zG|mMtGM7e&+Bssf`z3>uev(r`9Ew1NWXE9v`c~0BUCd+)wbYm*CBYk@4Pl&FvOs~; zIkFAW3qDWNWv6vbmNL)mTdCFjjH6hPC29F7O@kd4ju-gr)=X!p>2#)Q4?S|kgE8R* z!3zEgA4;GgDBfRT<3JgWT*mm$b<<2Fki*ZF7NKha*Q-)ES^yP+VozH17Wv$r7IA>O ziA1oF_uQ%e`ug+oTM^RYbJo(07clAGz6ZCFLNJ-?!Hq8@1Tda3F5sx^#sdh?muYFs z_p6I9y9~rdOR|2J!7O-EyK&(Fta{HJ=ibph7s*h|1q3|LIgF?hIISuXcTY6az!!3Y zNhDsT+OZQ$KR_Rs*8y#)1GI7A$Gc4(pf`33MmPckPG=ZGQOIDxD?1q8rbWUMA1+p5 zWfOcswF-v>p3r6lxKcC1kP!Q5T7^-+J#XVyQW@8}F?k>)UMhdtEYA_!rA5=y_*1VD zXm%|TxPgMPQ-8F9!SC1(s*Kw0BC3@RosSP(xinbL9-YVLEv2of*cKjm0c*|1Wv1kglM9RgzhYmR>oSae0Lkfu8t+} z0CO85;n(rX;^q!W*QVpsf^)^=9&*% zt{)u@%Km}J5>3z9v#1Jt1z%XKdt)fzB^3o`Mg(EsL}Pv`qq4LDg4b_4&V#(l;MPO~ zy3$O7AFsczE?573!<{8V7~wmfU>}MP4|RNc2rIWa&^-f*yN7;3WL;zxAA=!^m4rrS zfOhpE#1U`Pj0L#R>+7!zuMd8T3ss>il?JB)F4i7Y{O0iLe_Z0F(bXW82W6k5pTf~! zK^}0rU;?8vQjdIGSkf;8v{Ntvyc7ytU*+TciqoO!B^#m_0}^yRVjwt^BdU;dvFWYi z`HLlTF2+4)#n^@f9zI_5T)b3h8rxDj8?e@w#roy&UJyW7~ z{rvcz59<7Ip{P{%?)vgEpD9vRM78Y7RzlJ~Wil>XuCOy`$x$t0FD2gzyK$yPwGv>^ zEdS^zPTbbuun+Pe0dd+erL5G!Y2$}&rT&}-(4_()wjn4cQ2Eg@_;n+s4I%|-YmE|T zg*cAE`SjrBsMwsFA=QKe;c@8W!s`eSL8;GHT-ze`?+tSW0*;gfSom1)R>|Y?w-B&I%W~nEvVV65FO-N_8M- z!nnM4S2Pe!^vZ&o zr|0uLSW;~mxQ!Y{zX#kQA?^PWX4WRNz*+wt2C$a`Z)Cio;wh@%lY$Ip{!?`zc26h{Voh-xQAYUJrp*+AL z&U;FXY2{%t!WJ(jbcjJj#!Kjc>oN=>h+yMemhtljE5WQWMCf9(I{)NjTL%W(QQJr8 zS;9qXA2?J}2n0ZYZ{Ln0wq-hHf6AdDB1w1`RJ-GMo4tj;tKOn@vr+@F@hyU|$X?(+frgg~@iPF*kS$ zy$5eTuPzqnX^`mW)nazHA^haetgVAH|9dood2V=6pTl;Y?vj@K&kv;>g}yzLM<;m-Qm zIcQ6n?=9U==rZtH1`!ItWC*i)&j1DAVXDVR6h@UvZeuMOwGQL*cX%zfhY&dvf|2{3ouei6N$e@3^OtbhoR92lR1vPEiF4 z(}R6M&hgJ!3XuuLP8X+0St!p&!TMLDgJ>8Jtks{!tBsx}c)Feeud1^NC!0r>49oww^{ z3qnDgXvNx(E|y)?^mKvuYUv`A(2GTQ)wM?naE&uHWr2z=}5j-w2&iRA7e?7F__8!q$nYJKH< zTk*mQ01nj_csS(XWo+$3*}6aBBCTc1Wj-G*ep_6miKU%hg_lq8MTcI6LpsYG=$2z; z@G{Lf*RP_Fz(eIpM$I#-IdP;^D>WyaJ&#sLMB452Nij6yKK&p+N)_hz)(S7fTu@EO zSq{o~6vQ+13oxcpQJ#~88W&zcaXLd~&_o4+PDLWSJwJMdtjXl!kuppZClnHVV+s=y4N+*;Bpn4R499Zj4b zEs*d|&T-{HPz1{((j9J!TR2407Ex)^>BEEBUsDHz^Ihb;A_5R==M?~PiiJm1^dOt3 zD^_ag)r7SWCujYg&jp+#--U zD(0=YwM=$Hp{v}14B89)sqAqBt&|C9h%y0&pHV1NK-^R@z+n~4;eLCBF}Y7sGC)fJ zcY>fl3LW8zvxgVU)%E8zLd4qwnp%qkv1mpxxMfWOV4+nJ9x;LuX6zo^XM-0IB%%ke zb^>ANq1R8&&znp8xPaE#gHy?VSdZ(K3G)571hm^~rg z)_Iz}S*Nj;*Vmp9E~IrhI6{Qgo^WQ25Xyq#L%O~d>RPAMu_OcewLb=ENVA^}7^Foz z)|Y8@0_Py`xY{TPhZx09T#%Nd;r|<|5Ub{Rne#iueC;uUpEuGmus$ec9zy(DEk)ii%|foAyO$(mel3X>@`Te3nNQ9{q6F6 zwYXf&-Yh=$5~;tvehi#;N~FNNt%(%Kl^p^r@RYUP)#^E)lJJJ zWaeyK?JVGg2PFcd0`Um60%be1NB!K0Kr|NO*lZ1k=G6XP66NAU2Y_1$Ulz5T9w(pN z^E36d)zDQ97Kh|xY_Zht5R~8+pDi~ZT9ed>puz(ZC_Iq%94dxl4?wInkzqGZs0D1~zAB9ps*L36}5>Nhk3luJ$!D`J!79CM*WVKbD)z*|y>0@Un1MkHt4hQDxj3YTna0D-_cY(|J+|K}7A``gY7p>a1tAn#+Kj+mL?f6K zPB)EGBV3;%;WZSZZQyjrHt?oR$$Y=~p5|uF-mR|C)9MJ}CnnJ*B&K#?gv<(-Su^5? znMV}NoP}2Px1+9L;ucOOxYl%vtgnlDBpRTca4m=_+BLS{#Qk z0r$i6oa1O)Mt?94ejYJ+y4YN%G~1B@uU~`UxDccgXKE1ms#2iFOHVe8TY?bkuxvTq zMZn?{rjXW(DRy<;3PFgX z0PP=7gdu?zV<4itCz@fnzwsa-ka`f8muV!<+4bV`>ch>(k9BuA;P)C`^H54IRj#Rt zUDbW|diibk&vi=YU!>)GJJZZx|MLZmame^M-zE2)?K&aHZb^l~gKU zK_Y$Ene$Tk+zNvDSW45LK6`%T+b^qxk&?vJeD*7_)oN|^Ex&|1$QJ>g;i(Z~D zzNT?N57U;4Wl7i)9K47_!JJwJ z!%-Mxj)qAP$0LIQLtfrt$@{V(=BlDjq#O`&uf!h|)ZeblETVvKDk$*CP&n~Pnx2KB*Z;jXpI;#Z?p$kAjvlxdy|>hWnDABNtRnhrrA@FLBaUY9Sfm$GU@ z;k;HT34uUkP~d)@Fm7muc9;NFzuxU zaq*?-HSpVG&?+RLL#whcDZ5j*QG-SH9x}mFv399 zBvZ^#(v0&|CVQ8*ktyM5(*w^}ID)0%0b}$~42CSl^`&j2M=2TNLkBH(VDz)T00!Qr zX)I4R*gNIv=H~L3WznnV+z2hCiuOa!1CVPp#ChP=N6=u>*6CR+bz?6#5)xoD6doBb z9(_*Rk*B=p^>T6HgI&i9{6ZZn4<*3KOQJgqph92agPg zH2&s%_GtY}+8SX}H)|F1=h*&7b}!uxOIj^QyO;icowinczWCR_K)Gr;tG~Ys`-E_} zyPRm0L-g4XI%bS`5xa9pt8C;%{RMNX7hjyd8^J3yZ9@NM) z;NnCVm>=c7R5o0LX?Ua@MB*VThU1&@IS^p8InNM}2|1vq?GaoTW%J3ykHQ*v0HqmA zv4i=^qKqf~3U~M}85G0hFcibuN+92qn#hl~2MWC@DB>xM1l^+faS#JT0-wl54-eA1 zhp9lCR(Ve&iM=ZNtx`w}aS(U|Wk_%kI8!?a%QN;(w@OBkVYCQQN5djpmIMx9WlpyO z7Wh8Gih#un?42l$uHQpj;)TeWCiW4#R`i{g%)_E(xP8D z4`?9$e3oit#i|(X-ZjD^K||u@(KAd?cM{3p~cuZAH#}|&+;$5f`ymOZj@L6gehe0yUJV*^AM2aXr&i&;L6WP`&y zz(ZJhD7HFS`Y?_}s{r_3@~K7K5@wq-r{zuN;u4*Hw39@9=f@i?V{Q;1Lz!$ma>_@<-*aOo7%$NWWE4HFjuM0bK< zXV3kQ-uBY4096J@1OhU2g8SV;aM2Gyp_lgUg@S1UDPgwI*+tl4z=n%45Dvj$r4MP1fZ)2wAzO{; zB8*mbv76fjSw!}q${=Bnaa=TtLP-AGD3G+&1UZ@-Ksia|4R&Wi=rF;r#VPQ}4B^q| z)NwR>ofd8`hKS*+y7~C422E9UeA0H>`>og%i{pvaBXciMu793pU8FSr^)ih&N$*~j z$X`EyjlT~=aSj>wcTxy2uU#VtS=0m|+v*c#u7CsvxT50{w$*-4PJE=qHEcxO3)wZB;sSq9(=?QFTTovEb5J-^gj@@C z$wiKiL@szcN+)r4QB;n$s)|FLmV@_bRvgm$m*_;Pmnj10vs*DFC>%{2X)C9+A4rT4 zV+@Hr@<@;ni5SAuosbAgCKaOPL(&MPZ;2C+)@#28rXQXJ^pz3LK!R%GOsy3=8ORt8 zWtaP2S`q8z_?Ar%!kq`xnlP8;$RuYQY`$b!_`>Q-gH)i+wN&7z;!F3mjm&vr1R60R z%L-lZc$sE=O}YH}|9P=aqqLkp;Az|9YoBbRi8Ta91_5JoXjM*ehviLxpxc#(*K)oy zbtr!LK1*wNT`!7-g7MCy7$k`-y9yp|l1*KFzpT0o-Y>2`C)b=hr??SDFWi5B_7LB| z!u>nIHHZ7wo|IpJ2Ev}H5*pd2=^J{i%g-h**~%6iar!Nhc0-CTH!GTsTb@!M&dUagmfrI zDXwcWBL{R?2b9I0=&{6i4LHT1&RYLd4C>fyB7m>uh)H>>2S5O^nwPjs&VtGUh(n_3 zx*Zwt0*MzxE5?L64~Eo8A5RhE3`m5|BRnm~bfE}9yCe{JPX|OA4wLpQN}FI-gXEk} zkr~yfn9zBGcT?NI3sk4@oGD#W`2O?t8oa|ziHj$1CR<$Vs z6qORecjLY_3?c=$h~{!HnNWha+r1*x-?RxI#j)Yl;_~J??S!$u*y^zQMgfd#heZ-o zBGnQAh92%>6k;#?tVgn|zkZsQU2Ww5c*jvq2n0YwYXVrx3xZ6~{!R2$6mXXb3<$l4 zXp{skcrnL-ydZDd+-~XDnnbH=6etW5%7?%W46R8ZZO4~)YM_7&B(SQFxJhA7<21=& z;XKK-O%P&whldLgJTgIed~=?L38oc_7Wl6-hjuv#58o;XxJY%;kPvB@tc%C@{O+HI z0?{%Qj5l?D7qk(-Y(nvF{WT3Hf0Z`SE&^GnjA%`r)r30vHcI@#K+xh0h=cA3(!*T^ z;^i_0nQ7k(KbD;1Cq@k;Wcz@sHVkr?>gWtZh;Jdo!O%e_h9R2b3`5uK7lmMSxw}F{ z6le~uh(WggeDyJPs9r9dU39vW(Fiq+fCSoHBk^Y$Mu?q2K8>CFyCns0$@xeaQT*Mn zx6n2V+}~g*a~n%xQg*{r=Zmy9UQ$WyU6GG}I=p{+_b+#j|8lrr7s|bIe!)jw@UvBkC|oiMPE)_$93F)5>TPhcS}Ar*%|SNxW|(qr&}FB5{M9 zQTd@Yc=4Q3;d%PePj()Kg-0t2V&G`FQw5Ox)n#9NpNeeLmw-O^R(StV9jQ7f7w|Q4!(DUjvRBPag!eE;5G_FgXF4db?X7x4N-WWd^k$JJ=K0l{ywhyM zaM({T@B>u}zAXtM)R-JjmK9M^#{tT1FvK-K@it8|q-sOjI{iVa*LjbLr>RXaTBvLy zEXRV2>cGKlLad$;z-BxXw&fOOnr(JLe7C`{^UfA$AJ^B{Y1z;7v|^JJ2Ug_kSdy4X zlC@JrZ9x2Qa3LmGT`q9&O`2*UP>XhfNT0ea!~>@L=!=2-|uSZ)X&t zNRBsZ8bunZg7ps4GHz+XD&O1gkN}Exg4LG>JWQoQVxqy8r*|ZlJp}}ILRt}qUl<*T zhxrK(TYkTox)f{}d(oB#zVnYzM=FiULJS*lT0KRSNVdLJh&)?-ouz@AAJfc!FAZpz zN&|kXTmZM#Dgqa9QSB}*pcF_ZyNR+>nWi;?3!w;Q7@&2o2$eQw{*(q>6zhXK0NTb7 z;ssn(%L4xTXY?En%A)y1daM#56dMc!T87!f+l6I7%bt1pFy%lwehiu?2l{BDSL`BBz%^8Xp=a+z9x9tHv0kJlzLPU@ zW{KZt5ZG{@cXp|V_U-`#!N^X}=YGBOqFyM>SB@EKLEd8jtJlU|EOfEY0PJ@UMPGSa8Y z`|U2+$DtRIED)*)TQ7>kj^b)jJ|I_q*AO zKnUq^&)RMMFZ1B2>$t&a2SIzY8;1cuy@)3Dp&8LT7T~Z-aq-7JY~glsY}aoGIp}Qd zfp=C*z{jk!ntpK{re8UVKLi7l135Amku9bw2p)E`8oZICLThIWJf)Tcr;5QCT+Ih} zR}gT$#Bk~-7{#&$Bc)*x5o=+Y>B|-YEoBUm93GxS`mo6d0cfdXZ|ff}jalP?mZ&`T zN#XD?c~lIblsI1$J_hzn03{$A9Hpax3Qg!;?k6|I#Dd!9o-Zz*#pQLH+jkNU1!*v2 zC~0~N1&I-Nsg)DTA!Kt?bcYI371_smcz{?EejXVLogI+y9cwlak|D150|Rk=?JDHM zZoy$7b!Ty=VU1XZ4U9cnwm~?nqG-ecEf5@dWH>xtrPXp@aZ+M6`pB@r)0=WxcA^c* z%x-;Xr+aN2AN$Y_NBhtoFD@EbiHUmX=*Q;Q%W3WcVV@k zxf?r8>-JKagJ5L?1Ef!;4JY?}a&>B^p&Luum3Qw;3Zk}w$G5U8XBmv4bQ+SMQ^9Vd z95&;k95AvLtd6EiuK-=0PB;|O`8^_s(pQ)`ieb3s-<8?i(tKQ}u42A(0L3MNWn-|g z?+IX6c>sPH8yu=UgfcmEPGZ%!oSXmT;^L>H#h5n0my z!RykDEmF1lwVYUhPpp){ZENcY`Sv#FiSv6XMHxm(@d=Cw zNDSi^gb{d{0W#T^j1T`yE;R`z{NB)>9+z5E-*4` zCOi2MX%=`bp+XajF&|Rm;Fdk1+4U8}8OrW8iN*I;hYD;{k#MvC(rP3@F!24yq*E}N zj6_bfbwo;fh~!!P)C;;2`6C)KOCR1++?$xhCe zd$qd$n6?A;ym6!6;?Rtb@EWz>+Gy zxbtNXWbQpj)s=a&FCySaH8;X|Q}CfiL>do7fUsr)5s{%5*eRoxR^oagc_TBE;XVQg zzJ>zQ&x-;eoY44kk$#qboSh49G#c%N`ZBJsp>Wcf&hL>F-L9IrYsU)_sJBXS7~o51 z|B6G2Z9@1D1E?{;Pb;O6KicSHs-03n&Kc!PD&XTysjqhjQPDt5B+^desoZ#d{dxJ# z=RkN|9pBM91cMOu4}hx% z0?k!2Bp}K_*G05Hvaoy_CTARt7M7o;RxMKluhTRU+5L3g3ALY|y)Q#;Mi4pFE~mZY zt2XCoFojk=Kn$|?6dF(?9V1|HzLq$Mn_33B?{eakxvwYQ$0yPhh<9=-nL~C_$zxn8 zGFe92_?bpk(C$GDVcGie@%O9*&J5-ZC3}%Jo~J0Fxr%}`m!_b4u#Z_Bl*7;u>4w|+ z#_7DsHw=ZJ=D~a7Ed>bTU^0<5L`)-2-z^uPijlXS^>T5WcBt*N5-=zt@_~thqlF$0 zis*!(-6J_a@w{+zH9|CDHxgdpw0h472(S`B@QyB=cyqH!g_7A>S_#mPx-8a3OTWmJ z!C|w|Oc!5sM2G=I1e<1+U|TZQ-DoSCNp>{?2cD>K9Ct^ukSWHGvy0{O*Y9c1@%48< zd4;W03nD3GAP1gr^67ghogcyV$`46MfyPK`I_67HPyK?iO!8X+_W)=OMCgtIkA)XJ zG9Y9Q&)L`2bunoJSJiF=>{1*KL(~n3lorlVa2rk1)j9x*s>om{V@RA7>x^r69B#5Z zC&Dl{JiVzISVH(Ar!=K8`Sp{2Pje)EN?f<;^f*!uA#{P1Bm1^vtmKQ14ORLuWnmR(6>s2e?gQW@awzAV_MAn-Ui9yniNh+E5j>`v=fTO{qGraBfI1~d zhEyJ-Bh5z>a#R;=6hCZw@HFgfy}F*I|NPe?E#m3P>C6<3Q*Z!TC!hheN(zUDe0@4_ zCBoo+T|pHOP6;B@e~OSx2Iq6xQ(TQyLVet1cR0eROnhL01C4YpEDbLC@0(;pb(P(R z{p3d#=LT1f#@1-yr=nVW#c*Fj4)N}_8yit@oEr0E66LkkVi-Qr?vy(|>=+AaWPp%iRe%@MA7e&Hk6*7h zUlSI;7w){%bq-Rs<2y$Ryq*e%#kdX!zW$CDQVysv10lx@g2)Lk7~SkASjeMg+A{HD zDk%Q`t?ZNQkb=@7vCUzqEaOy73Ow)62=dx|I1EZ*w!tH_jkLb)=f(0O&5|pot>f)$ z%fM%E6QXSxc$Q!wXiWAXr$y33R!QLqH#R~HWw=Ts`a_Na4&0ZWA;0q-@Oz2^E?3uq zwr1B+4mQF?H4~_4+rr#!5#F#o5?zZSLGv;sI&A1B!U2Vsi_4E`HeU+!o~}3Ng+{a) zL0H5r!k7{$P>ltyzjd^m2VaUKorp!~ashrCQsKx9BQ1b*i4EERkp^B}rpfDmkS4xp z%?pP`P>5~T!H+n-qdE`06dVZcH0$Vgz2IIB3wA#}+N`e%?`a_5{kIxN+h<7AT`1)a zhX9eR?nG5R5P0KlhS3E8zjgrpb+fqqa*UMES&Y#)s zlEimpO{`~Y=J;v7)U@7tXIqhHKmQ$H!jdk=4LRxBvBRaaD3(m=m4Ui0q!jJ6F1Uj& zQPjyDzjEMx(*}(&2TC2>y(5KIbs3PWM#{yxQ>qV)sdPBMkC=+#{BRli;pgmmR2p4@ z*M4jh8abwqF6XL17}9+SCB?Z~)4F*VTHxJgnRnqf8d}HB1)4)^`qu-jn>c&9OoLg< zswp0&W`WmlWL>4}bV(N;Rz;M4oGvc#y>Mgl(CvGQSmYI&g%5b0oMwa^6r5_N8E+RC zzkZ&j(d@s5Q5y~m>Vl+P_-hU$LLW6wDK~JaU@%Rjg*XG5gvFsZ4vx-~-bg&e^P-PM z2(LkzruTfd__ADno&9{V_^+F9MI3S14ys5Jjjsb56D(y48iNr@Up+BxxzCtwz&B=U zggNANn=}(3>ioyc<+s!Y_cHc5IVkW0H4+|fi>dIa%1Bfwrn=Bu#g=gt58gp2L2_6? z^>%%JnHHUW_3_bW;n$qSWwir!d0FN4n0|9^Cnrg%#TktaBEJwTTSCrV561r;I z+vUo4F!*jr08>X*p{R@|)NVu?1|A45lM}D(86>pCn@kwe6PM)1+cZ_;`3f_CQo2#@ z$_}Bc8R5(#U;rA7VIXKs#-L{wyq!gsQ6F$x^`Spol#rNh$e7qP^CvB#2XCg8T?Yi( zSUb^>bG3mGQ3M%q{?8ucwrjqXhexM@pr1aKQ^5dM%?JFo7h-EPTlFuz`Mh3Uu71bx zDdJr9E4+F3a~a~^fkx*LcjtZu7tP=`Rj7{ze|(8KO-fQe{VFCygJ+WP4tMy))sat* z{l2p=OdMd=RQrsV7^-k>4!th<=aZ95FU1qFj4YgmM5;Xk|TE$}iQ(;tVu)C)Rk7 zAQI1op(1!djfa463sUr|ro6Es5vryRh@+757!Y8OBgT{S*_*VU-YE@&V7x`iaA4d=E>g`C#@T$~ifZ8T^T4I(UB_h03GB@kG#J16C6S2%O@qp5 zkqoCgrGdrg+H;?si3??L+Zu{6s|6R;Q1I0$75ti$OQzOB32coMax_4Z%0qs&2Q)Gx z5kwO&+RS(IZX<8f7R2QoW|#IkEbt7aMUV+_r^Z5z0y2S{>|q|g_!ghaR|E1a=&CZ( zUoVNE7%3huHx=jVpwJbUN#9e6cq*^TSR|O(;Dgfge2x`PD!h!?2ELlp%v1}Q{WUGzS*&x= z2EZ|)s-$Z;|0ApD_rv6c`Dhj0mG);ZQwgj)dMU1pgGef88M_wJA%;o-9A>X+;q?iy zQ(;hzFo7Ft9g<|ouk3VK7YCB1K7$(UqKGoa<~VyXi89GBE-*IBe!(wcfOk;GaOi@_ zm2uL-qt~B(9Oc(gqZw3%<5&p6 zRW%qCbb3vG#=aI_$!GM}DCorg$om$H8?vN|boKT6{`l zGwQ)heu{7#kl;D-Zi)xmqb5QI6Aw9>98i@bxy&{gsTnnf9Xo^>_zW3|&>@eP6K3Ed z$BY+?4eIC*KAhiN{N;HnS1dLzzVs990cIU&2Rv|5%@4*tx51+v%ta|->V?O-NTTgC z`qjc_{WeE&{2blJ}&Hi6p_Tcbo& z3!GL?^fwnkyBsGTe&1jQAof|<&IL5A9Y~>}H5Zyd$RAxV>@WaboCh1}D^#5wSa#w= zTsI0oY6jTD(53Y{ESQA%9Sx=WbyHydN6EzP^Y9Q)kqE7V#DjxJ2eO!eX9SQ_7)S$g zAGDIDY2b`9q6x#Erje_Vl5&|k1k*Su0|`iTwv29{3IVY^X>tDT~_s!35@kwm-$X-MCIeu9`4JeYM zUg0JbckXB^!sLW&C<%#hNjzk+%ecyS) zD|DjX@zEprUbU&x?a-0SQ8TcK1SRpDc;D@f5|CU@8`?eh z5j8C+_!Lk*DC(EJJJnbPzC_5N!1! zx>#_mvXfz^5yK2R(FwvhXxO~1-}jr9Nuac z$1q2b#xao)!F2#14JL48PE=}l4zBTnh@ZpTQw3Fsk`8dQWy4vV2I`g0b17X6_PFjco~PlJ1%|l zCWYjspV$ULU02tr{?9C|pNtmP(+2I|7B^{3>FnW6Z?*YXe+yOhLpX)0Rdwiw+5irf z09>h2gy2NTKwVUcbP+)ozD{A}z8`^m3j37&q8grFE+faNN1SC*Rfn@xMWD~jqtl%? zgyiJN0ME;oPt#ZST)`Oq;B*!i>rlab1EA@2Id1a}^AnGOCKW^YVQMhgj5r5C-3cP` zL7KYwW^r>ddz03EDrK>g13O&|p0XV*Fw&tYFfo{3Gu(BJ^|mhd>ttw1B@2&kzNU%M zpNg^wE*uS-!q8h>udKkK+J>|qU+|B{0S_68AaOcJkfBnBQ%y>|O$k=oxq7p_DKwJy zSG!6uOARK(k4`3VQI?5xSdBtvD_n)h`9o0&xxhrES5Bw-nSt1S#6oa7!_jpa1jZtV zvy}|WUSEDpW4nHF*~Rw0g5)`rbg6rh(+)3Dw{> zJha9FA&6SYVE_ZCY9As26bM1@H=Phe-vc9RgJObpgnBYldYUEo&?I>mddMjeOn7C&rz?~7GhuPx0j zOEYMSX_tO%I@PHfOB4cJeD5*ybbJ7ADX1CRF(%jDl|D%-(`&P132t#ZgApd7c8!$F z7Q9+qq{<*(Noj(Gc^zv)9ndFZw%1{96}g_7g4LgkxBOBG=t^sI;|KT zcOrC9u>F+_k4xQvk~J25<>zuW67( za>FUGb1H#Hj^eOWbW8HmGURA-bX(#8^OJfZY36XqNs(BE^Bi$hnXv(W%~?fS?`D%K z=#LlQy;#ByYZw4d_Nb*%;P$mZ7z`tsPOKY7fhY|_x^3x2BNn-QB3MAEw?_Iz#RT)^ zGNpa*%WYYFg$S3cKA>q9+xgzswq@X_5>erNM;{^-f}d$%x#o?rf;V@p$dCyVxJw5K zwx8C)BSYfF^0&0=&8v@ycNfk)aGCM~hYALov>zC_Ne7IPXu^U7EsWqUi=%^Cso#VY<)K?_dH(7oyhcsn%GWouA6>>D$ zRh;M$BsB#FsBFJ3^kP8v2P@4(o&hck2Ekj%jD?43%wN&r%#5w}7v3axsscy`35QQZ zo?Z_r6_QliT^HhFns|pM3J=#GFcxKlT>+gq;`+>xbXa?ekZR%eYNu(DBn5st2J^c| zqMeMPlm*j*s89~UiwY1tGB@#ZeUsL=`MDUx=Q|K^xx(OLhcM_C4X$ATcSIaUVh3qw zyLNtjTJ$$EtRNTU$jl-S_7YjZ)6`GI$*;zT0K?PeCJjeHauGK(#C1Mo>)LxTXeQnSh9PPNqh8*e^-9EtKjfoq-kaQj&2!dzS`rl>M)5S~vsQ zDc>>_)6oV@+p-D1)V4te-zEzmB2Ri!W5rIU z5ke~bMtmrb%!e`o$1cW;i_$jmJY@uaQv1t3CysNF6w7ILz#IuS4PW^Vg6k z7wdzExksU#oQ(HrhWZQy^-JLA8syIL0}b`@6*c8kV>RXto&Ms|#q-<6ZizUaarL8ixvljP@cU zrH(vVr|uSADsB)Jq4GkdQ}M)eA+=D?H4oV@P6;mwfbkLo?ppOyzmTKjCD@vcGxU7| z%!VtcJrN8k>g*Be7DLK7uSmigNrDD8L`RO(8IDjjB?#1QytQ-W(hHo1QeAynIXCX7 ztWclCjpN4|jS$$N*)!wcs#iH zuuR)4yhvS@9u8bJDq3L%59n8;0r*rdc+>;~k4-{CZ5GUi4}P#~cT?Y?@4OW5i>2(_ zySOYz8=_@(zzcko7&}>s6V5%6xZ}4R{A07hK#8CKKjPl4H;!Z35`I6wqG1g1Jq$LX z=0&9DO_F76>tYL{_V(!Uk(jWF*urkQ0>$84~Bp3nh}{MXyuHw`T(( zw^TcE$Bg5s+Xq6uoND~;kR5=}7}^OX1N{~v{Dk~+xnDcAtRCk(BX(^KYXv|0$9 zBnB_ZAJoo4T>@!9KqkX5_zg{(Qcoy|WL1`eut6Ln*W?|#SySkmQ^mJS+39vE8RTVo zER|CP%78M&VG)+NI!53jV?>6+`AJpEJS$_25xwsJPNEod=qO`5b-Xy2<3@qP9(%(D zAW+dUPOvqncQ4Z5x_XD~BZ;e{M4FGQ@d5~AjOZtjqjfA~9PVdC)R$#E?LsHOU*`Zq zRtPmUB=Cu(l5(lxG~Ty|Mm~I0lBDvWWwGSQvTyt%U57dTVH;LJi+wUyH{a`;+hOut{={4QHP|8)2MeJ^49 z{j-oTW#>A@wYyHi=H7PJ8mMoQY&A+oG$VdIcJqLN-!uNWI2UQ|5{9-@cJ3OV9r`)g z+@Dd+w3vgzfM@P)Kf(iiCLWRGo7vFa| z+40=(<;d=d=1W6F6HOGi6Br?1N}+D$OCMh^|Cl|yd4Ik7eC2W=o%!evj1b%512q=L z29Rh^?{#!?q(QI-{F*Vw`$)OIT;X=YqAOQuDn|_2tOC>Nep-|}_W{;guux&Mn0e^@ zw0WU6kD@C>Cii`YEnx{oMj}`U;i_|D0R-AaixBS)-X6@pzbjTzq3Cr~hiVuCVIeZ2 zwwerD?V;_aYsBGc!3B^}?Lx1zMkW`hDB!z^3J?C5uHmRAk)eu?3O_7v<=8ZFEzTx@ zxS_zRTV_E1YAEb9Z-`}O95doCl=AJId9U9`er1C==#Cx4a1NDGp+t!s2(mCiHvc-8 zJ$=3|{ZcX?(NPf;2^Y!iWDw*nk)SQN`7r3cjUcEM<)-efOQ76jG@?}Ews9hEla693 z9K6aSZOn~4lk-LjEP!5Z8xD&Q-J$}uZDdy9eS1W8z1`x2Ob&Bu)HG0-tuUZ$julU2 zF8=KK;^yDiE1xxY*5NpT&nJ~Edw!^Ajf2)68li|x7s=wp6`B}(JA7T{!;n(0Bw`+f zlC`H$20A>u{*2|vRl*omuHA(rgcwH|-)a#1Ez`xnhAGEqqv_&*+%B$WFBbp)H)N>F z6aVAgWBlDsKtR)X;%T7QOcXQPvi1jJDV`=@DGD|jVKM_wEd8tLpi^cLw8N_|Gi%4` zBaO3^16)F>#!!UPc!5RIP>mEx`-#S&S&F22ZMhD1@XdCrNV;tR8Ya;IK$(4%;MC8) zVDvwA65&ypC51+5hSIYC^|c(*9+vR=NSeG?|A4oc7KG7dRC-hk_JpF`?rlzNNd?V< zWG0mPFvjgEVhCGQpw)~GxXRw)u^d=03;D!n-jskILoT&j zfj{oSixd>aK{UQDD`WJOxSg;FlMXU1P#K5CYvf6vEy`)vKz~#hL;3@6uSuayfPL&c zkdn55iX#YobH;%om?{JA7zA-5C2lDl=qEBAuq}fOss1SY!~41C$~fquRnK#qmd-My|< zDY8Cv&)A8(Jdp*A76J_KuI@In0driD(cy!W%3u`^30zj<01WpKhjN|)fu&ILb^9Ad z&;{8?SZw0_;%>9?b87JsvkBBGc~V@eBMpgiwDy=yki^s3Pzq#r7QrT>K;m{%hmasf z#hU7X(Sq6etsLfFY9wL}LjwG%$C1Jm9LENH%h-_Zg3UJ_iINTY$i#kj|72CPol^(# z6c@9|o<@t357_ z2E0^*2sFT*y7u_1%A;OQjij5k2Q8h0h{k~1W(*-%KpC14aB{gGx9mICc+)5b)w~;Y zTdDzq^QC0-Wc>R4PO`n{Lb3~fr0Uh2Ka0H@SW)WL>^QBO$$D!DFgU4mHpt@^JCC?LsX}LI(=aJS_G&cg#>D$;>F@ zS#%*1Gs5T*YNS}8fQ}j1kV00Rf0RiegvU`o6UhbHI3e{oE#Qd;wWU`6$GUarK*+S^&CiL_IA#7FY z^Cb^eEmj))#e+cCfN-P;y*-qyCI!p8Em5xCx;u zvDb+eHm-TP-h7lyfb$UfPYlSmmqH3@YSkX^nmYs1JWS{O=1In`nCDx1V(Su+^+szK}3 z9Ki3|9p1Jv;Q9N+_XEil15#@D9!{`>Tzy!kvjg3q^n4xsC7vIJpbGCTlK(4*Ngj<3 zlRS|Va%BMM>GJxv*QD4^ir#spS+A#Y(*jzXHz%1#{HGSbsdaFUz)oX+#Jj27ua6 zze;{j4MC^v&=OP^hVU^)Ot_sQrZB5K+XL~)av+0!H?sb!o-f)NRmaZ_pAavR zWVFzwV~iH|oqu#%jG%Evgi<1iV}t{@6C6PvaI4w1(2oD?%|e#j7ZZ@s+_gYBH0B|o zMq|{D0tTZZsaH)$xs4xXX3%vn_)hOFY>|qF4X8rGBV-0iiq@#IPNBu>JQp+gJ|~D( z$C06{h1b_uDD5E_e|irbA9Pv~nSt90iI5jUp_#YmgHJzrcGNZ_&B zLI{dRk|7b{NP&l{WHAb`2NG#t9z}w{?jA+N@iY`n*?`izFoLsAWbcJCUk;leg&>Tc zz<6rAz+Y7(NaN}G^4{bdzBqG%@u>0ucg!H-Doxx{2na^zfo|=6zPh-&Emxm7BCz$V z=~$s}g)fYuz+z8Fp&T_pq10>ebz4yg8S_kpfWkQrR-XN`y!^QE1FQJRC=>p?l=(Jq zQR0B&PrzZvA zoq<%f$K7Qzz7d+A!mQi69yPW7gC1#sogF3Bp90>(5r;XC49v`c2z~jvps^NR7 zLqq17oBxa=_@ySUf8%(FFPJppuX_-&yVPWNgN*B`5r;H=_VeZD*VgVloyCDbG8ttM zYDWn?Wt6~&6^)V6lM$gl=#HF_HGQGR?G(}P>a+g2+}zB5xL)F*GpA3!cZ1g&PtdxC zMObTsJ7e(++i`mQ@{OW6T1R$QZzW~)xE?A%^VTBe_z>vO#z1QhL&7iju#Hlr$*$dl z`JGe4I_u&5cJ*m-v;JKITE_+yyavLNq2Is5Pt7j0`h%BklOYPFqL+p8@X)8m@;Dk_ zo=t=1RfqMVpNk)vNa89J;_3BDwp^5W?rOm;c^npw6a+v67>7k51v+1~VPsO!rrTJs z61`oT%B?r#BO1#EiRiB_h_mD}7I=lj;>qT#%%S~7HYF_wO@O_T0<|%Pz-vq)0x59E zfWY%c3avg!p|UwOsvS0!u%H%SnH<#yu|)BjokS&UeC5m`eje|3fkU7R%Yn>pS8C@zO)6Jup zTCKH*wUKIBFJqq%w-XTEIG>PYAY}WArSx-T4y8;j{`IbyPU_IWuWGRfs)>4!p~2#} zRWo`JeKdQJGI2~;cXy{@5eACcREqDva6z*SHeW9a`@r{VS`d>7WA0j~p&i>mj0i<; z?wAW8;W97yj>?aA89_WUBhGK{L?2|r{lZTzb`Y@MtrS4n_p?skZo`a^-aSCxO8x;8 z{-^x=06+Y%|M-8i@BYu~5}?V||5)qgcPEFx9WLe%F2ZUo8HWEa5Oulum^(o?vSAl~ z|J`0&93XN?z0Pv-B4B6_GeUFC4q&^CjCo9geGwT zb!#<0hlR~ApmMc?3<%)uh5#;;Fvz9q!k#oZDwgW%kmLmK7$BfKXbqorw);S{)y@(1 zYuiGb25hZ+B!}E{DOJrJTDdrvzQE5Rl`Zo;LufYBu0CZN0>6Yc^yM@XI~)vdq!) zb-TOG5JI9In)dKS`jVU3+r{UPcc06(yC`+-HSi)sAq0_lzD7ao&JKXw-vfnGDj9<8 zjzYI7jgg z+f@cceLscU$xns(lH_5_ZObtR*I#GvHmgr^?uTEF#kGgEU6P-g?Fn{?YS&=UxiFyJ zGc8>DY&TaDZS8t@V@^AOfs(37wVc(f#Yz@q6+3m!x4Nq^cN8sQSb!sD9oaZWY-7xC z_l~RLi!(3y3bhE_Nt6h|J@9LyfL%7fzx%i-*Q3ePH5-77NmLjyX|q8i`g^E!T8Vj| z5tHDu&=!gVz;a^!_s=WYw+I_mFTB@wTmXb78mO-!5#m{ggyRC)dykG@K3|=SZtxKu zIwG|)7lv4`N_xc8x-uVll{6s8HZ`*hz?E&!Y4-C-7UtAaDZ2sX0L|(==2rnw%Ju$?`ZP zxBMdTVa#QO*q-%o5#rriwlsXY`1t8pail0zN*v=`H5NdqEfaXY#v=1UQJIWIZ$HD` zp@`c%W+>1+juf0i`g(x_avcx|tPBTmu$If+kwl^EuvZ z0^mAX{B!aOj2(NOEEL=X0k1L?+)mK@ExeZgJqkYt*xjqe4dmFEx5NFi2;L

zm`pz7+<6`cKBZr1m*^c^Kx$PQE z$vmY0dnbxg2bG=UPf%quoiH`4?Kyy>2?nnw2NB1|#SCw}WcS=yT$h&lfZ)c(4=?Xz zzQs?gkDsnq#Q{lMBZvTkV+8O>vdOB$<105NgHY}kq#A+OMY&9FR@DM^Odaqb7OZ|N zrhK}Ty+1ExAJjj-grE}Ns)H%#L;HPC2Sw(?NP;T*4_4DDMR%e%;CDTZgCw(x2fm%~ z2p#cE3|?iS_(Ar3_#m@dXU{IbU{0%7PgKhg!LLj%(8O&E!9#U;iNC6aXf-F3qwIDO z;0YEIIvXfMGb539LwUL=*Raa-h5>$R81Qn4te7WWd}g6=XAA@FbWFw|Z4j@y8Cn@X zr)XrwEXQ5(Tp~nwP$>}N?RxXIN}@9&sExzHAfBTPFkL*yxKHL*4XJc@Z}w^-M|Afh zy;q>)kWHU64oZ>UuIY5{6!H}%Fg~3g zo96VP9*Ss@4ql{fQ>4?`p)r?^M^cy9&y`|pBqoATW{%@Kz42EyHMPPJTdmq;Y91dI zr{U(b2&tY!y_gZeZ6jwttU>9V8+*NYFXCN0b3eP-NtMGU_%ZlB!vdc$KgJSkH*n9c za2hjFI&cBJx^t>v9}L`1V1%w9j7$lE;yEy6L+|DC{3x5IzX;QJ_D`y&Fsq=?{c4v`l!5A(C^;Ip~C@)JwvF79Rht8PFT zZo%`lrLe$chtmtZPew$YAf$w;>V^I&-!A9V=_}0E5;Er)2Zs?3UHTB{3;!gTP%h{{D zi((WG@2WvSRgDT0)5>hc=^&J8Wf~f(uoDPDBk>!>0M&3B$$aYTiyxPM6*|6ED+3wE!3==|Z=O#OU6k^WNn` zR`!c7Vo^o9{%-#0?BU-Y9Q|$n=d8xK7)>d^xmsN>E@gAetJPw5`moo{c=P=4p_>8M zkh&SWCX^99Cp`qmQffp4NjeA@HW(uY7oct3-VyY~7Fj429-?p>Q|hTZSq=_P&-`+X z6BI)=q3mL0J}_<`x){zQqBbc#=put7=0MDTKBH2_lpQ=-+ku)t9#+3M)ea8IqhWIW zZ5sLjVvL!JY;t!CNID4TAWamXUM-g&yisgV7>4XAYHMiV8%7AX=i9!0j1%AG%y6*( z=GgG?NcSMOz$vrgyW;=`A!zwX**tlJ0huRqNKAQB&fLWd;FFOBRWgm7hyDe=U&8^) zSa!&40z{c&9wj^G1eH=rb8~p4{bUUfRN6dd?0=%=nu7Uy{aMhc4^eVppqd5-USnYN zxAy`7Nh{>?FT&zcci1JKCx!Yb79oCmad$1b0oWogc5#hkVPm+w_vY9eNL)5B) z?|zfSI$!K)k-`k6g=0lbu4mb#y=2KaUt-y}%x2o)00T&Qngl7AwI9W=lZ});EflRr z0?#WFYAloWv)8@gi!%~YK5;vBFG5hANr8u4IE530USB-ET(2Dr&aXJBz&o}Q!dXXF ziamv*462m|5Ssu)nAHUer@*=!RFWE*5Q4(5UnNad?xu|I7#mpkYA=A$x4=t{4S3!} zLM!h?(p?Uoa3q}2TYK#$y=z$cRJM(g#6z8?L)k~|A;i_Fc5M$C0k+|NI~Lmf1B%%m zh*Z_P>TZL8VmV8Avy_9AX5ui47@`RP*`U%WtGLR*+Q+a}9{uUn>SFPJJ$rI@ea+8Q z6Vjhnu`Q~bV%uF)=$sZ|4FRW?{8ctdzueebWgz3hJdO8ie5+wkzI|-FZfbn{D7S}Y z$MTbB#~S&VRHG=Z_!bUGUDgp^HpI91pJaf<-12~U-jwKgbh-q7R9hs9q57;>)AiZ5 z?ot5UG;EFShWbZ=#_bfr1ZkqLZbfwGtIM0#S$9sO(W>UO>A#hkVNo5PuT3)(VkEZR zHu`yTtOF$m2kFFsPD8sc9Jrm}2vUWHF-G7jEA@{{IbQyW41szp^`i-nC!mkfB!qL@ zz`%W5HpALX$QM7H7hn+JRw7^k)uLPZV!|Q>`_!q{(D=)*62!b-TokEubSjd~p#Bq_ z7Cw|ZJ#x7{vK{JRX;Q^%Xz$PuoGTr67Vfx#O~!%j@SIq0-Y;izR)|ElE{yRVKypE` zpt%g{?Z<*)J++Ono5r-zo^sJ!gNq8e6lvRi#Z7#(PykM68L8{{^CI^A@SWO=JE@rW z*CL}yllMSzi37m=t5=o9lf+RlRfxa_CJ4_zZkGOF5LC8?0Pko=w1BK;g7O;#-V=dg zSb^aj=|pW%mB@xu_Iy!z(}s9fOh$oM*(QG8TrX}e*Ek*Hdb7GLrbuj+h>#aSWyio! zSSEYX)Q?lQK}1_Z**1%5laSvm{^-uXMN^wf^0KK6Xdr_C1+7ARG*QPQqDU%ra$6ss z`IF~dFu`}`=r+t9a?Ueu4TvAAX#`TFv7{I4&1JE{v!9iX@);iZd5W{T9bY_D;h~kh zCnXeso&Ci;3@@TJn=!=O_2uljWYAZ-DAdADe64nzjuI35I{0OW5~Orzu4S*&%hzQ# zw5=labut3H%IZLke7+7FzT=@tjvX8U6O#idgVCRVRu6eRVgTC&p$w-_HA_sV6BQwi z$ZH76&#Rv(h7a%pwF6GCPQ9zn4#J!Qlbj;Q4K2v{j3N}H2N?w5VIbT;T1xXV=p${& z0N+-3nT0duk+t>+*;4==#@4BV44^}(@a$3+hu>U$kPa3|k&wYtW(Q6TA-IN1=uMzm zj15i<8SOG$qIaL3^(beaWE$)oEX0L;Xr-ngKED24cEfypS?hGB%+N&TaF20879mLE8~PIqe(F}oGF3wxhNW^V942|*!SM|Fc0zBS{sRr z1`J{F#BstI2R?hR%tPY5#!N(1vA~P7cL*s_u-I&)->D)b*+slqe4RbNllkYH)zTx; znORChX@*XyejPf|nD5h>D8r^~9i&!9qU|Rm3SBzrG#{s_CvM94SbhfSH#o|SYfY+&Ai@LP-nNJ9cGYbSThJwy6XjsyY-bavOy)=W(xS zI;3d30Hn;zo7L^@#DjA}pQ5c108qWgekdqF`-~t>MIer#p}e)%bhgkKK4Ab~jhvh> zE;ce*_~v#kCy?Ceb}*J z5Ao2jfu#+cphS(x9)E5{_V~NO4lPZQ8(PB@`H-Un-N?SpLkcN<_P)NVcapfe@1gop zqH#OL_FYP%bY7K~(!s|V|FT#=$5u3K@C(Dn1#&^20h&20@HTbzOhd4yk$)Dos1Lsl?EVN?V&)L)bA^kJq`j~CVP;}d&RQHC&fq!RgTtxk~oLm zKPLES*NhJ%7aSBEe6=$a8ix)L@UrQvL6dJlhO{bBqx#&#K|vf4lonM4+({9AIP?Iw zOcZdHBYIg{`(gI;wVV}vSxx|Qd|=h9VUjSi6_^X<-z_ zks@^U09JydOAI{Zi1F<5vb~K@n-~s@00rJ#L%{-*7<&fx-4#g$dH=p13g%$Y$<^O) zmS13KDH()1jq)KFhogh@7kG=(VI-U%3mwurQI!aWA`?6QA)H6yR3-u&e!i6xlx3r@ z%_l#@9!+O3IE}zh&1Z!C2Og@afR8E+Y9EuW;$HE5cMk$$k%ba_zP-F&u5LU4C~yq` zu^2iuWpX@+tbb*GT&D1k-Ik$XBarBN!azBC2=7n&xgtPlX=n?>>9^wb}D zfDfC0I1Y1e@CrpBM3IsVmDv@tcJ=0qgiuZ#{dO(11dSY=gP4bC8Xl#Vh1|2+7mYG6 zI?utw?r4Nqk|b!&x{+kL?7pB!tC!)VhXYlr`xaqN7BH*P$o^7UGFc=~4hw211g#O2 zv!f$&sEVYHLL`-)MevyntY5EgFRseXIXWArqFvg343Mvef~;of0I2rJj+Xv{$f`=< zb=gQ_hZEmTQB>D&1}NT2!o0DsHC|8AkbV!|3iz1{5Ngsk2 zMM0b0Z=xUq@u8e{LXTJ51wk2d5HYYEg19PA`uJAnecdi*&lg{Gifol6ef%zN(t^6C z;BMC$=G^(t%oC`k2FbKMrW4jCG11C0lU9SA4TMekp z)Qt|C0bUySQfi_|M$O~U;Xx6b)ck%e+wiSq-!(X}z|SMR(D;ek)DA54{2V4Au@OTV zKuqx0j$Ua*+g??hJd@(#HlZVMo)Wg)vNF}UVTw$K0uMPTUM>;sGOj6{ zSlgRB)!HrcS1tew7QGEc^kT}TT+6|b*cGDe&`|hXCPo%}Wd?3f7IE3qR`xiK(?gs~ zCj{CL!Us%7LZZy^0uR+TB7dB2BDsP_47`O%1YYC^6$#unB<5Xr23#F6-bsH@dO>oI zOK%os8;Sl@b3AWII0Z!=+O!xEi(J8oMBqfoo;o1nW;H0CiYH#-fRMd!5#GQ~-s7MV z7v#XIYS)gB7%lp8;lNxkv>TKXcWNSJ z2*}~d`p`WvVmAo7<`6VerbEnLu5T_DeklsLRwIB9XJ1jq`xeuG%PjIT%(9lKXe5jL z)AGygUuzjne0d{B$ulmfx*0$H>m>*@MJY??SC`VA-HP0M` z$zu)-26Jym*vG|IkY^~Mxl6swFFHH(TS0O%sG393%Rs5jMIZ>ao-QkH4ekPHj_4&f zFq%OIHOvGY1{ngpbh?W?IGPthWEMldFUvsorK$zEZCVi1geYlS3%JkUlf^3RNrHFP zwgB`EnUF9={cAP2w_E5<#-Nw!BjNl)T&0I1DH6matKzHmoeZ!2yz%of{OTL(Ks5~^ z#0hx&7z}$dqPI?w_+}XT-tjO5k7%wY3bIslaV`F_>TPzQKnbR}NS63MAyZ2CX!xi+ zKraQ&Qc#R>SFSp^VGSqWUKj|_BZTLu2AVS=JDIaul? z2l={8ho}#@onnPu`M^Dl&p$0To3$T&cfmp%h!8b)fXGO&`KCt;6k>PE#(G53GQqVAL2qU2aUv%;^Ke{eyB^ zN*+fbvY8ZZ!-z)-XDHA_E_Qsj!NF2LZtiY=TVDGS$4&@McGzfa8A7)yp$NdB>Gs-; z*p_xUet5`gS{E~i54&N}wGBY-u#k?n969z*P9!WQ@oiVike73zlw(Ck!m)xC*l~GI z*}2s5Tjm@P0y(M7%+fFdw@oH?K78$a5eOM)oXH}c0ESZv6y9uv=AyI+i!o5y+JkDn zK?{|P$&y)Sq9!t;zkW3^;)l)ha`trn+p4q;JXHr%grnKD!kroet@-{KNngSCyiTjg|Ovq_IF)S>_ z`)doOtT4Ex#O~?54*duli#9b?d=yrj1IMPF%Kq*W>_0h}iE#N`P-oj7XQ(02MZc!T zBije?>Y2yjr<5MrMH-lMa8#TCHqU`gU`LiWqlbLb*s=3$^zM9H*BvGZ)pV+W2Ocs! zaA~%Z@u1?i`7vfHr57Wpv=o|K77D&XQNW$#%tKs*TgnQmMD7W{UUDMUySvS&V%Hu# zW&+|s;AOM-gAa_6Ld|8eE5F~`xsW8K<7&nHFPE#^D;YfSW&ywr0Q@c~C@!0^i~|7A z8w<4Uhd%3c7W6tjd~s$$-;E!JG%0@AR7);Qkl8YRCr&(E`*8dCM=RGX0Bg2- zjcsv$uggOT$v{M9z-_aMxLFN~)kNa^l_XVP^H|*&-EkXYLj;wrL)_3AcC^4{iYDYz z5!ur&($+-#Wk$MFh6qU(hi8}X7n^EBPG=dYQf(Pw(hy!+lL9|g2$UX^rD50}F^o6! z#hDOoeVhlY$XvkBUBB&yZ_Gz18SYQs*$tM^8rOpl>Z8a(WTK*)}< z+O1g_az5nj&01!q+{(W6z(!||IS5$qDp?vvknm7V0&V&~O&Aw8xB1vY~r#RRWI36GS$<}8amLU;zWGc(S z!I=#=X~@JKg4910gGlV@SQw4?-dI1MJMqKcllV~vEHD~f)QgH43iyqo;C8nk=}l0| zIvZA<*d%i6?yFY>stQF8v6JVpz!~L9k#n#I+)@beHm8ZYp7rV7N13ly%rEfEdWlG- z3UPOPym?F(M$p(6Fbu;mm}WrXgim-Nu28j>BvE$DE8Ah6y`!e9MGP?r_76xilfAr|MIIOrwEnKHz!f!=9$m9YRpe7?dKK25u+Q z2=OFtnft(1M&hkZ)%Ys=oh>&PKAd8HtEL2AW5Gl}s7C$GkL2kHAmTnMQLJ~)blBZA zf<}-Cq(pEckFo9H>{ZMf@365|h%E7iniOn08i&fooVd^$I}2Oj`(7tjUE08=|yP^MZ@{;TB|nJM$?di~XBMLNSt zs93U@GPp&-Y9MISvA@GQ_^iMn%&HR_|7HGnhm%kz2Sz)(jjMAq5GaT9-;ZS%8wq8W zvOxd(8t+O1f=bsx9gRgTB)Bi!siB}%6AA@ossqm|Z{A4q)^Se*W&AIGsbI}ej&!0Um!^^o3FUvVPXxerz*~tSSPV%6p08uy|i6yor z4v!!C9q}HiF5soyl-;=vnWN*63hgaO;4}scFKHR714S+w#ZAqQ52greG`s{?#*|^t z5aPHuDFuc1)lZhA$^h__LWcYpnpgqAL(U-nw`}k&`+Upb%0i}^7QVBs4k$tjsDksv zN5Ne96t7%V-;oX>Oyd5&je< ze58g$DRGa!M>-wXLJ9R2v^e>mBS(}=4@X!4j(3=O;3~uMVsY_dcE0-XapBx&C%(i> zj2L0H3e2en0`O=kLHnK_X!n+BC?Z|Af!oP#gic4sfdt7oh;c|};9u*z;=ov+DP!&H z!IoVDZPJ+jb59_8Te&bq>)QuzCm_O9QxrrAK}a$X?Pbj*3sj_2|u{b!hqYG6zU2GXoNT5Kl^A)7x-TLE9@`fK}A5otzqBk`p94XMq zW*uRO1$S!ez{k`&MpSc~A3ZIVlNP608oWKD5v9U2BS-}egK*#>Cl#7jKVOf)f!~^o z#g7e+kXFY-1_v;xGB&Klbg>w^Bsvx;Z=NtyBOWY8qiBJ^V-l7-A0SIWnwc7B9(c%5 z0BTAK+}~qxp||@LLCt)iH&)BQ9TSOuA~{guv8+r88|2AbYIShc55r9D>T9Lp>R8@y zMuNet6av)FAPD30C`Qw7rUS;_$aZj_ygJ|)wK#z2DW)ntMjHsN{`pf4JE9WsG`=cgN^*F7ASB)wBxW2xW?AQ72;#M{;Lf3ZP+0XRH^IuQ`H?n|I zOdeT)N3s^dW$OC@1yv`1E`Tz|$gn{+~Mrzb2I?~5%==6Kg6XNLM39#Cx) z4n+ap420`MHQVqg_XT&mqR~A#qe;^k)oAeT1V>0Jetx}Nev)zGFzn(7pzvFx z2fkpwJ(!DQ2QCc_w%>iSse>WyGb{TjBvMg&1tD-kH;F(0D9bjSi8w|;43eZ~2%?3w zB?H!_o`kJ8T|)2W+~b@oG)@x(WYlN>TyEs7&EJc>Im! zuVm4VWKo~4Z*GcdhM;c^0)E^M_rDPc4|@9~;(IO+fad#6g%Nj580R4cfR<=7fg?<^ zk;m%S<>KxS#{~xieq1}w(EV$}z`{})^=dK~N_(KlOyELL#*6~5G8bMfH|viQh+nO4 zmsiV$k04O)nhu0y7>zDx65$YgS#|wajxrsNjxv3Zut;|2kdb!uYT83g-#?Da6<~Os zeg>-F9%I6fEL>o4o3bD|qCD2vflM%0kwc`x31~+{ol3r4rjPv;Ccepq3=hxz$pKt^ z-s)#$Mlcje{R}_0hzLG8MHd(xjV@Sl)_{^33K}aRYQlFrQ4uqrr@ZVP|$6hMA8UiNKEUmz(7W*+o@OVA?n&{IDB-kpy)YB=Ec$3bQE; zCpeG8Ck7wG{ssy@Wt^#-S4L?Z9V7}{&YsDh)7!@u@XM{8 z4S->0q4jpvc9#Pgc1Hkjo`67*2K-tb2d^9jR@nYZ=IhJ)$8ys^l)rWxeH8HGng{>A zDC}wo-!Emt*Kf1)i>uA@qu>OzbZXO96sk1~8rWBZ3;(!XT+LoA{{3&fyjrU8k9T1% z*#nqi3iQjlWGGTQ&PP0NO#senSd%DPk!C!8LU*B0i& zb{k>7#`Ze+3-L-}K5K5oLnN2H5vb$4#QYSUoS{m3Qm_Jq&1~+bu$1&)?^Zn{q<>cC9F(c$=NES}2HUX)YPKj3+;zqr!Q14!ig5U>X%~ z2GczD*GvFJH3wp1tD7@N z5iOR4R;OUvCpJ*rB!76uEdPWK(M04kD#mSe#d!fDokCNy}^T82(Y6}ylc%t@q>OC4ZV zVCWra*kfqu0~w7n@1q;kQW*9}1Ar?Uyvh>!J$n3~8}n-qQL0)W93ml(#EWZ!WKS^? zRx|MF#F5AIQ@zdD$$`N)XKL^vWd-hRWd*9|{H0EYr3L}NHCluqvJHZz zLt#>;54cola=&{Fg+WEapgZLxgAnZlw^JAy5wyQ9Kp|K>Jy! zv0*U}lQ7WY#t1xB*$Cg*38}}xNTVbcNaEe5nYa% zk{?8wOiww6GfIqr1sc1y4kw=|`t&%eHyI=FBCL$ZXA=;+_P?JnKe)gSHx389$zX7t z;H5=U!9dzL;0Kj%M*G;-zX%i7`11+zRIm^HTosd7SuxKq*0;BkDZMO5*QiY6P?V81 z*jO_nBSBhEXGEHfr^Z3iod7~$Lk0oma&jqCGGvD9`Rd{d=kYm0fOs_ucux|E5(T{1 zP=F$tMr0K5%RTI(95hgCfadbWSuYQg{Zwg~dAFk#kl@tTmohWOfq+M9ad04n6*_pn zrh---rT%ZBLRjDl44V-I@=%!TEJG)SL^>w0=9Rk$3c1Z5*mTqFq0GI|{qAlL!AqbX zZB3Br2aUnRCJj)Jt+s$KRkrZofk2pm(M16Jd|hIu+UD|ucnW6>XxD9uQ1&ymXIBlJ zJiS_7EZ)o1hP&(Q_AIHV&;O1O=lpKA@%t1jPdVybod0eVyc8n z8VvZkx@y|eeGM`vLy4)A=47}k&G>8zS;98YXk)EEH7a}Rv$S#r%>!^nY|o2UzeeXaV%~pEV>GXhn&NH`BlcZUoS3vN)!z^>ZXE1J3=@V z)E3~T7;H=@L^*9R6C#)cEW4gc+uyJS<#GW0xV*u_oEx2BIT%TX0D#q*z@0pGhCnGX zIVs%RxF$d_M8Ikjo(ElT9e7~7`o>WsXA$^j(h6K^93RpjcxWtQVWvWAEmUw^BFZ?nZ5wJ4 zunh+Y8`S|qVt2A1-O&QRFJlopcXs!{LGFtIiIP?C8U&U>1{U>5eBbB6v|0tl8%!Bc88AGOVqU zsA$s2IHG1Lwur$85W{GL90U*a+V(La~$RvNfDO)OGP1OmQu;y3C?`#U!l#}?QKYdwvE7{61rB7 z2u=sI@^p}j$mEdv12`hEfrnPFr+_B4xL4+HSKoI?)I%_=e+@wDmeE_s!^-Eyq(Qnurkxzsm) zGIeMC2#ucPF$4{c8X$o|p#V@KQ1A!L=)jSj zLtjoQ2fvICta&wAF|02jYGXYu6krb!$}#2)06BefealW$lq<#Hb?cc2pISk)d{*Hhs4>LPJ31)(CbK)2>B=!Ns$1U zjuPje?k;8E=+|FSo|J)bguvs;jo?yo2uT<`RP%uic%KPbsh!m*wC=Dm;+s(z(T8{h zJ1fs{I_SgG>y<2Tf3=hz_j^p?@o1p^YqcaWibgOGJU`|{_f*S!NkrV+EL#S<-dQML z$gu@4<#?rq?^kT)L-3z%KH!@gMyP>IcAq5z6q()Q=#X*90FVVP}7Rb&+OYAOn<8=P{%7qk!Ncp_`?&whjDkR95md78djGSnKd`)QHvzWs9e(I(QT|83aGB3pw(eW>p-x}p_WQH z1wii&w+_m!INdU0HP8HD8PcLSDWnZ~QT$L5pwr5hIkJq{pZv0E zrzC}&N4m3k9)_?cBf+aIp6#XO+ZWk~NZ&ES0m0%%>p%enlR@)xm~LvJ4-EH*rHM)l zLfWK73ImlE__onv-kmN-W65ANmzKAKTolr*BMiHu{xv7EaajFZLJ@UyS0woIY#`Jd zMFDpVh0ycEoo3Ikxw;%c)rNt07zjJXxRiJDfhBG`k1@3@$504QE6!~L5F9Aud%6rl z;aITC>T>5pc{#{K8Rrh`s`=+j*`pQhF6T%8k97Ur{L$INzdbnm+u?pW^Lbc^ax$7T zmnrnJb<^ypyZ7&V2he^0>?h!tCnj+xnRaxIs<(6IAdFc71lpAG2tP1s)IhuGB%&SY zud*T>pA{#BI8VVRvn9yh8-DYq-U%UYTGAX!mZEFc+-Of%pW;V+(TIOSmtc_IM;8s%<9$zp2m_56BF9||F z&z>Kxbpfw2c!HmRsnwAKQ#jOxk)U!+7akS^DQ=;nQX@0x^wjeKUrR^?he;Z=0!m$c zA)78Nz7|;kzdtN>v6@2~qbQ(cSioA$MBGv2dxlYEddy`B@fF95-8ZJe50w`1sxGGd zW${&JDm`7V!yY~OK}`!7r_o{_a$~f#+H2+jVni**h!L<*1Ymf4bGDI@v!-`7kslVf zNEDWv6`|M$1ionc;p`(UOaeqT40v8uQvqp;<`@j+as%>ecfT2TY@)lQX`oz=4et)# z9?ZVKD^8Gw9T@X^4|d+`fQwILZTr8kS3Xxt zyEY_1!{kbEslh{#%579oOeRwyts>YGO^-N@!_dnDgv}o3vumz;eRsDh_eI^R4#6n! ze9Z~a@Lp--;gDM|r*ewV!t@FI&W90D{2wF=Tg zxv*0>oe|iR3#B?zWp-j}j{!)rZI}=Oc$>kkKU^6hMC$gd zj{=4Kgb>2&`nyB=!n(VvXgNeVwh%=P^iggV3v2R|kB*KUIq5<*nkgrRfL0dN6{%xK zLX#vQ`}?#}3t*q)}r$5geyk3^86SaEPcBZIEF^g zfuSHHVBog(dg9F53drfLlDfu%3{gD|nL?mlW1*Y} z7EV9)k2z^NMvHsyLJ)@C@a1N7$J32u-qh^bz+4MnWvmGC4IZiuggj={ zugcLB&@m)ZMp(rYay2zU5;ykB27)3x14)yOpDv2E2%Zx(imLw;dC7oe#_;_X^iTXX+=G>rwk2$O2la55pLQHFXhrjvN@FH4_398I6T zxmsN>F6C6WtJPw5`mh&fzKLr)87iePvz(v+h}%)7lLy3Y=0r8mN2@q;*)leyk1|7m zMICgz|D8PW+aaE2MsN!uM~Q>8Lru177w8xSZAF>12WAC0wmPIx@%Q8_ah>|TD7%Tl z@n{N_o%Jzspj}UQ8D&mHBNEz#Vlf0A(PiMa*+iUyj7DhofnG1}#C=Mp>}t6z7xg*A zU^rFv1VdK9kph=S3QHryX=rXk;fSQjRp|`OrvtSu+NA6d6AEy4qa&%qFv@}-DkQwh zUNkLumGj5KidQaPHp-v`#uyiE&ci%pCz_yG6>je7NV_{^+KB8xk#L?YTf-<4j~&Z+ zplVJwU#~w)uvu*l={IjdbEaMzE;XnKSfH-817Z7ZbEVt*B1aF4X&Yo$lO4(Ls$`(y z5*oWE4PM+`OK>%NhNX^v%MK!+KPm%cZ5RLaDXs248^O-k;RdXD^-TVH)lg( zbW$ac;`CGesHq$0%S+kIN46^1EM?A}^QUf58b96|U^y6YnS2Q=57nThPuI{ic}rm+ z$w&xFaz-I!*HL&Q#9^3th{83Iz=ol(FCJg6*A4~G1_uM(jAby&m9Gv4JZ~`I`6^?9 z`+k}NTe8zF?M+sEGh*8 z-```-$`Eg`2S!Lq1-^J22K{Ns__=WxHWLrL2PgNvUJ8=w(VEhb;A}C1hxV3HJvDl2$J=XDIagr<{ zrG@g%S*IcxxShZVQw6$UyjThrRYcdBdOG5?@PRQX6qKe`Mccru{CUfV#`r3ZQ33CA z1Gf_#VfV2tTPT;~#*Y%vagEvHqZckTqzM->P^-aPj2IznhC7A^Oe1N9T;9ir=k``8 z*kza?-661L)mzg?UN3I$_HCs@2B5M>tn@i6=r3M?}$>s*)j& zNj<+9$)Gij2yA^N0y)tPWN@c8i!7IV)ijH-V6Ftg*%M| z11+eQZ|`{EB~a3}>upjvw-M%#;7&~n@JZES1cq%|S&lJutz&qN@j^arw1_)bpj1i= zG?SyntEGfpvbBw81S(b&0zb_0oj>6U6|I5L>Ti*Vp$45SQ6m0cO4&bEH}K*6yGz*# zZH777<&J2M5|CLFONT_DL>q~nC^4oJiSdd83O{vlAq>ot1oRmZiG>^nh~s3w`fZLJ z3{EZ`4Atz39MgZxjQU}ikbJr&qrP6=tp1pt-{}OI>X4~7&wn22VeFbqcN_q?7Bsl& z$VYp|lP7bZ6FYS@(9m*-pbkzS9+6He3;dqyLNQwdt*V;9h`H6bfbt~Lqtq!(VgPM2 z@);(+eIhcxrn;zClbz+sv7d?U=G9SXvVX|ol~y0Old}wc3*2ft%g5I;i)!}xvJ4;D z8ygK9LV^@Et%Z?t&R&*YO+_MCFshmMI5>Xjk66|T|Lkh+Y#wo10;*tM16SER zbUQdXW!Z+RlRtrB*GQmdQ;<+D49z$_8&k$mnH2!ujX^uVi95+McFn`24(FR{2Pk}T z6o#EtBE!HMOot(jm#OO@zsLu^*cnHHtMZ}q?l21FBJ<(Na&se-b!0?o{qa+op@Wa9 zvB2+*5&;X;wN^|;0F>Ahi&V$`!|HkHeG>wmnIJ`W#(`Zke~^=ziX|Ic^)kSKHX4Hg zU*AJ7djko(Q=&Ux2mEp{{qssrV}^1se9+(7*#nR#Jjy|E&~^+B zjZ)j(*apY(sh?Knb}|h780!V?x?2CG%tZb=d$(DAy1V$WaABUGuHI${ZAVsN+j(&? zt1(1?C?r>PrsJycN_?StOlW) zm~YYiUpa(uHa>)~-rQo;P(lGYLV70aP5<4UnE&%z;FzZ??X$OuU;c^KZ*($Tz@So* zSMCfFZxObdP3~lz*f4gC^=A--NV7z+;3^ZQKQt=3O)iAYIe0= zu0E`0|3sGX%kuq|A7ngo=@5qpFI6N%0Ok8y4v)+VHs|!X;kelBhU8UJ(7qfTDSuQ& zQ2oUkq)3*^r`KOEC7RwW|F|wYo-hrI9bieOl|ab25k^=YEx z#1dDT6z8|HwTtX@gV83Z7Y+;Bgo$XzBAADxgu?=t+QY*AJ%XrS4Dl!65RBu+>f@|B zVaFLp8-)-_p;&b!rA`yBPOl~##zR}FX08)Zw)z&azm3wEV#Yi=9cIgUJY8I0VkLAv z9UpHq>u^}`vf0nVCu%H|2j3v7(#6X`HPYYi~e zM4Q9v+8ln16SS|WRt{o^j8A)a>~is`+a0@}k^ivBqoY)<7?+GxI|c1hjLWJU{)od8 z2T>#QRY!DI)|Q`PaDLW|k7m%82?n`T6c~(6k7iPkVp(I9S*41~msuIjGuQvSI9UXi z+w@#=UZ9B8?D>*|z~!WT;AZ&X_lA#yCuGv`P;H|uG(-N8h-Og*f~@9PF(rxi>I;$j+~0^Vj4e-h}Exxny}joK5neQWfkB7sXsBO;p_S~ zg4+p-ZU+eEbd1nV9seaWvHfCG=fP+aii2ZB2=4GuZ5?blT8G-nWDj;`+dL^FvR0rD zo)Jk@s-91rnU4~wRD|H}f6KwxGMVc2h2S9*(mf~evD%Y4r3fM31nXcuocGA0*O`CZ z?F&+VGBZY2sRv?^4)h-Zk$3BlIPpWqAj^>zc#~QzQ0cZ<-~(gSpvX=4BfV@oIp=`r ziba2GY}BQ)kbiC#qAueoM3)9S90oEM+?LLytLY#OE9qX|j!_ z&Ezen7CTSRe|#M$=Q|k40fm8=8aaksdRr;9Mq6NDpxQ|qm80`r2(p3IQqc|iQDnyl zjIMm0ov(voI3U`52tofn>>~0(WhBR-JK6^ER2)#^#DyRbxYg|Zq*##T@r9$X zMB&ls_=4MsX!dIHp*Obh>Tx)1l#@X^9WTJw-t`B(F@;3Dka(aS4QRt?$7_h9wd1A2 znr-6v%&%5>L{2e9!lNI3F!AWYk+Y2M-~u$pdL0b1Z2}XkZvGJ^PaQ8jZVvL8$x`Fc zXvYhstYS*Qv`0*NWE0M-j+bSWGgOc4pkkGVIlgDwa1BblgXn;+R%!K@qj#&k2*$VB~e81^#n+m zqrn)e6-~Wd$Z?w=XODlqyu1F}3z-AB@VV5^*b6vpu=Iys&<4p=P;SDqCr7%IHSiIK z$bhU-MDBI~Aun38`Q+1(Wf9D%kq2=L3Kq`1`3GK{u3|AFVtDDakw3%Kvo zcG<)`9j~rskZL)`&M&Gd5dzGN24MB#xsvgxrA%e24i`Xk*6N8G8X{pB4sB8QIT#(^ zM@~YD%ydK*)I1^Y zZnd7tS-743#Lpj}!{}YI9Xv=eU%5CH&^JRq23JNq!e4lZ}f=Qc%!B0bozrQ zbMMChBd9h~;?z`d zC{wLh84Pu2Q+t93z0pw;DnrqBVsX|!eq>C*l{3w2*|85B04+DS;mr39@5)28)pg;0ETB^=S+VU;(dKlf6xami&Rz_~b8zUifkqbq zpRK`g{iER49T>pgz)%QK_&=ExyW2-RbFmABEYV%6Xn!epw(#Q(UOl9mMvSmE6yxLC zLb4artH~B}R5C*iV+b3wiA1$P+oWJG^bNacyp<)~Kn`~d36!Fu z1@3qJ`Iv4FX$1Om!b!ieJ1yeG8kDXb`eTzULE?k#7=|L**zMNHCiDgIya9na8xUH3 zG~u39RGzKU-i z@ijxZc6<#Kx5n2HFw+LcWg1={YiE#jFrmnaz#$9LKB?y9$fs1@?vhMr`EcT-ID7CI z5~3ne#MpC)huTH4yQDwL+`9&+1jp1w3qLaIw#W_Ns}(YLGML|)9%6SlRJ17^HIS;2 ztfz@&Ke|NiTsZZ1EgPRyTT7zkW)xI}YVX#c6lC*#0*X|_7+EPMHMvkf6Vh@T{YWUnVMOR0)?LtU&g_D zR6z}bS6LdKU0ya$lQ*68I z*6DK?d@awK;ML@@suY_c67pccTKTAtX7N2b_4!jbQQJ-%&rd!)J9G`gISSe$y9Bqa zxdBhQS=s{zV8|bj=8sXY8U#BE2i&14O%HC%?!VJ#hRvCa2M-_l>W`%g(a6fkpiOBX zo}U-tJe63CC<<@YnZ5Tt#t6{K;*OPRxl8v=jQ7iD-xo0 zAQzCp#}x@;lKs~C>QXj0tOovoSFPHFq#4kniE13Q$^)I-TCsHubhkOEIS#172qOy_ zj6jQ*Uu7}S>&1niF9GmJ84>0j0;3uU)k+|tSCbuh_po*C{AJ^KIQQFsdew~9!OAnN z4KVz?K^kd|3Axvs)n$43BEDBMgWAz#2JX~&?8FQ@zs@l1{d1==o1R&LCUU~beJ-l? znT{9ur74BOVJBW>93aGd=Ty*>)6XkLlLHnXy#TY3yo;12&GP|VSPUWZ%zRtn2>r|3>l3(pV3IgYFO0KPykeh zf_eiyY#px2YIs_#aC4h`s4NV6G7>k%Br(dT7!ZOi3B6qu@mL)@Ic!2SjStk0ArnG} zKlTeF%XyaE(nZA)#c?_FhzD+Wx>~r^^pyQw6v25zgfLXYDxuej#;Vzo`xkmOnH(ud zw4Av@K(@*EM!-M=B`|_gW-tOddYk7tDDc{3Ik?n7GS0JXW5Lc5lQORBsh%_^3CEn~ z$=Q+K*04d%qk+ZHKUJkbeG?R6bL6g_bhlpx#cM+$EQ&>#t>dHY2fXU?abTCRfP-^z4LpF*4Lwiq2=Q@B!tqwsb@zUCL z;HMf-Dl;Yv<#DkUF%fM+FhnDD@2Z=gBQG)=dYcKlekQF!Jq9r`nJnij;Z6;NGGI`( z$y~@@(Od?AZ6QJ9QWQ`UQ^@YhPn0W0)jzI&EoZsnp;{Xp1Un5hNp2IXDH$i{)^m_G4cD`v3rM!Cl6a^UO z2|a?OrXYXEY53gR#@GnsF-N7-cyuJiMKC5)Luh(1ti9HRu?kwgeSE9~1WL&>Mgrud z<&N`{r#1`KTz6(>ZzP3_Ee(5CJb7ob&Z0KwosLPtZjg^BYoo+pD|MO*Lb*g_KaUl< z{-|R!`SFwNYcl(3^+%EW@N>}71SZuG+!V-_3e|Sq*;{G6KV7Qlev**Ql!D^zWS?>C z9|p0l0+$bbHG@v#_6@jYX2G>#S_qyq%CktiF`j>?e9yeLyb?sx@SnnNQ!s3L*V zr1(B`bx@?HeLUq9QyD?{1N+!H`JOv3z8e^L%D_N5lW@wVDh9=FvKv1r;~57As_Qy_ z{j;-QozF_xIf7gNMSBo22cq`jm&VH2f zq#~lg7e~nu90#8N^C1}ebg{=1xDb}=qy4t^dQe~|4_ut_bhG~Kg#llwd4OLU52}dY zMfTrvJmDmaCtQqDaV;8n6EzcX@%~x4RmkdviE}yKEQ>;1QC? z%yFgPCcX4)vT)69xoxN{6zmfnr(i7-E}zBggAg zv(xA`h{S8G-H$mVr`OeJ+k#dX!swhOp!5?`vZ(dORC;Uq@KF~$P&~B$xRb0Q^fD-i z6h@TJnZr{#a^U^!`C29(c!Th(w&)nDOi0Lz0kg3X%IZIK5gZ*<{v4RFzfi6U}YX9vTSExf;$M-3oRdA*wIA^In71|Tx6 zloGg|C=miE+-e5ax*hV+I?ozLsU}T4Cd^ zBtvgXO0{U`(czIZ5kEyI22m)D0)iSj8?k9yuh%yha$fOocwLeZG^f!6c-8@BpC0&J z(oMPCipZFm+$(t;ae(!hTH-2aP2b8yC7fr!yelRhw}U9hik-R`B=WsR6OOf^ErLL) znn}}vS!y95mRZPA7);1OfcOrG$C!~adnKnq7C`uwDXe}anqg!K->$_X+lT9fn|=-k zkPW@(KWzIigM;wy9#|d*C}TwKb(1RN69y`7WBgRw%{3S4?;|IOd$Z#CPg+6)?s=wV#nPx z`qmN8ukPZY=hFNj`|p;WZ~UMJ0u5o_BZL&F@)#r3P9`&=?0jdLn81iIe+@0Bg2}7Q z2+mvj@mkL5aX!Pj43eM{i2wu2Rl|Uv-pgf_yFX2ohn-jI$hiWNfOxz7db2Ls0NiVN z0Dig0zW-7`P7_r z3(}9AToY9n?`N$w*QE~(uI=FX%%7k25GAzPLu9>cmU4p|?lPtAQuP1u0t;x^KDz4@ zYi_`UjVX4+)2a8z+=qGo&)|ihO}0&+_6_xmQE-OB^4Vp2&jLyn7F0W)L zSo%EWeB(L3R$C~bG%!N9hhtL>^N=5?SUKZR1 z#h{xUFc%}ccJ>$zY-j}k1@hHk(5UZ|L+{}bB_d7JuA=?8@HiU@Ns_(zOOQhMI`X|? z#|md6yj0x|lV;jDC>6eeiI7;C51Cgr5Wz<9OSO>z#5>ufK^)k%46b+}e69$UOhTF& z5jw$8+nN#T1@3`F8Q8hCj_gMDp}=jkjXoz(W+$9nrt*Xb8hKZaSD8{!9yGQCj8kMY zBcbfLXNap}IRKl17+@~Mn>nR$_Q6Y4ieMjYI5I2r>Yl71y1OR?;?|~_ z5xmN3`D(pc-u!!wLq|5(MNS!C9Cf1tLmB8=IQuOJSRRG}7FpOavhCCJ^&Nr(5y6WO zO9>;TM%9*2&wu_0{yt3Ql0_l%znu^pP2C=00cp%8V3?LEJO*pjD2{2@?L^X*d2L-Z&~DCkKE9TOh(y}5n`Sv}2hY{c6Mt<7mZ+~;2tYGx zuq7E-vL)O00RxrMI4(lK2mE$VXD5C_m&Oi&m(OKl43JjwRNUbxOC^fwkdVcFSPm{n zm~9q*6*R{%YNd2x#5@ej;}Zr2P%$X9??^1h7W0gOQD%wQE!3_!4A_lAhRv_YpkyXt z+zG#I#uCrvXc9SG;pgkcf8Kp5<|r_mV1S@O%v5-3In>bUaDV_d10>suUNvoH435Lo zVvGUpsc=NuOt>jI@U;|Kg&AF>1QY9^t0%|e`qS0yTy~Kx=Vyc8wQAt|W+fq*K*y|( zt?rFP8CKEM)Pcz79514LqMQkd(CvVIHF=?XF-sCrmRjN&P2X(AAus|!*Eq1!<+6uOB_h3-MP@!1d<|SkhZCpw9Mqi@xgcg zB`_!n22^tx(bk2~{YDX07I>9m_)+G%{=SlcYWB-Q=6LGj5)()~TGIi4H9G8s0}D=p z#4rCuv*7Et;SiL8hCpYDUpr^1vzVS;U9X*oz%#X{#9y;Ll>JTX-c`dYKP|t^{1W)t!NrpZ@g{-yXVsd#&I>L}T&{5lvwWv0@{P9i%Ztq@7I2@!ao)<~x1L&MZ+6 zZ3|e7UT|9-Rxzx?4xQ4HYmAI{y%jtR9#f1$=WH$b8D`-JR$Rz}Pa6vAn(*wNuKKvQ zW<@n>sQyUI63+@C4O`P9IPd!WllbfP7e`4S+2gC`s}QqiPHrFeym$0A?3%|OkjC;m zi&I>CjM5klaFx~R)%s5Qy+3dK4uyymYojC<8p(&rLO|Fo!f}DUG8)84rEss2>aXWSHLZ=_aYHGx@jU3c1JL28l z{3i2-92|auAKK8Ql9$%!FITDH6JzF~iJ|FT8{6W|!g``gQUv=rNHbE^X+i`N79pt3 zSO73*9x`zXdq&>uIIj@zG4tqC0}rKikX)*H?3o!0eO(1$aL7598yQ#X26O}^yN>V0 zb4q$p!rE&&uh!E7(r;#hwlG?tEN!X4othSuIgO~~{vKg%Z(bql;qo8--d@}h59L$~ zLA4w!o?c0xU;2ALEf?3`fjA(@gleoYZs~&e)=03x_d%juF^tsRy4^@_-XiOG~GM?&dM6w{&2&b3x?tEJ!k_|%+O1P9G zCFAh`c$HZND_@Nz2o7amC-rIm%E2``J7r{W?akHddT}YIkYBA9v(tyYtrFgZU8m=J z9jOAInlXSAsgD61ZFp_OUqt`O26wAg9+tbPqD3rCtmZLz(1X)czkq3nV+%Mys*nnVgT5of@!C#XeR{QAmK#tzxx{bk?&nT&34YxRi?l;ywWx4ZOky6EE(rWrKj(Gi=5Ez7tkCX5c+j!YZxVkV%+sC1vtB zBcT)-kRV(t7`S7c=&#fu9@#+7mzN94nZCQ)EM*FpgP~0d{57dX+2;VdW28{WFgct^ zVS?I+8bu?{j^+WoG$>+@232VGzoawz>(}~J6#!Pp6FUKchs-*(`Y1Vbg?ohAXT<@) z^9%(@+K;oL?+J-rd!p!;zrJ{Ux%N3^KS_hN*TBm8kKjJ=kTC)nRx-hIPqvHP(n<-f z{a|NVF*}A4FhC)jY>?AHKFai_db=G|cnTj-z%eIE%f2@Ze9P{gjqb7?l-7yl8!lRT z-Q6O<5rLOOI--oQeLUM7Qjo&X%DC`Y`XC6Pz}u6(kBxTuT%sg+c$HjRfDEB`Rx~F+qxBS{-Ljq0JQvkjYsn zwzp`VKJ249GNbAThZVT^LakF7i+eph=p# zAsb|40gJ5lDz~;MCg(Qs2))6!a3~cJi>ODL9PO5dLXx$ZF$P7*o@Hv#n`A?gZJ<7g z$F1WVxD?Y+)TgFK$gtzRW2T~@G@*#p&_^>h28T49T*85oSDHul@23 z)NKkDczumUHWS#|j%E_ENQY0wju2m-t%M%9?hb}=7yL+J;FV{^hvn?&|L-MME;z^9 z8G!;krk`O34QMw8ghJ9Jg~kx=3CZ{C6Yo0W!hNyiu+^I)zm6i;u?7B`JH%zw`(z_* zy8e3eE%VB!VbO-cW42K4VsS`(CaPo1Dx;`Pb<~L)azyp*l^=KGD)(9+ z@fx#^zRBUAwMY=t2lGD7H&ax<61kZ$@h@y!YuohNQvTov)ilt5uz z(EWPxes$6Coqn-6%4cNZ<LH_EJ;TdP3OG8Q9CqeEAW z0MI%}lI$^FE@Ua}$JygwFYm7Z_Cl6`E&Ld@?}fAh3Ats|vL*<=sez_mO?DiEm@tya zwnCC+JR&!6+qiMqjkzNrchdiOyZHEPF+Cm9QCkXXYnHOpku=Hy7z!J5a@J@#OC&}S z0f)iXs!+Q>N)0=5Ci3L#M$%_o2mu>ImdJ*(jCBe!LzAJz-D zNwLg{kxprdwb43F`5d-fU9V-^dk04+u5x&Uei$lQn}@0!}kEWuqi68v*a0vVAi56I0Ll7Bx`K z5ez}W!I_=%t*qo~!B()FImvi<2zFt%20dD;73?ykGmHKVL+i{}M=Msrt}6Fr_uZYN zj6%AxuWZ;_f7~|z9j6SCh#Kz8Lr$PxEzTEMMk4c45I32rN!ic3mKcN7n0a z9N$GeK%9g}ce}2jQg4^CtO#%{i|zC~ksW?)Iu#;2e7kn#fN^UIam;lcKlG-dgR3-+ z3DgLoT{aGSu^j7i*g;MwMmp8IE*IRPmFtvQkZioRc3o;4uqfQ0F4?)U)s6ElghHK-r=l7jwAUX>ystfep#m4!RXq3q4A9tA)*0bH7yhYV8Ldb9_aSc zWh$LrD4c=xPgx*8GAneG;CFI{cM;m*Rij=k0D2+ryC_$SP_(F{Z{;FEBCn>_=tau&mgO-;g{%9Y#ZT4{(Vms8PnMi#l zv$kc!u+6eq^Mp5z8b=sraK?d4bEjD-^lGv@JuXM8<;{u%DVA}FI~kw|35Ot=REJi2 zfFn>?;_=B(IrD@-tJaDsDYY`0n!~uiFrB)@LB}ZHa`7?8I>R25|F~UT&0Z}2{ckvf zYWJIeynBqlyEzNc#&%2z5SmF;ZZnhg(Q)7i*J=#M?5|Ci$diZu)Xg(;NrTGZIu9N` z(wPnBEtHq7%Ab4|IPRl)NkGIh(nnST@r-n9WK4|K54?djvj5<%E zQ#3RmF!;=abaJht!CArvM?AS13g3i7vWC}kUR9il>H;Y=X(9?P6^nU@qySz64EgK5 zg359e2L|a=9YyE?cZq>xNM#)MN6DfVF(vA6t^!0iD0tb-B;x~y1W>GcUTQ5t*5jI-bT~O&h{|CWiqqGYr%W&~$F>5%+hEr%#GhDFYc*Nxmdg z0VV%U6%u!nK!!2s3__s6yQ{m+wJd>*OQoGG;I*|v27AEsV*<%iPq&9MJLp0O)_QOi zSw0jARM(J*<0&YJx(YOrlLhWCdVVY043^4(ch#{pU`)QEoS@$3gVrB>_*0O;M<&oA zu5}{`7)A#=NO${U_V>l_3mH`LGagasDInnaF*@M0_s=}=Ep(U!b*oYQFFoLCZ9ZPg zU|}S#vNUqRpKgKWM-H|UL1KooFM+td$`il2S}$)_f6UJBbW7hVo`3WF=Q5su;N$t7 zH;jjtTN9w3=Vgl{PwZPI0avh-B^yjo@% z)1!(O!NKdLL@}$L)ELd3ga`#nG5zSY2)wOkB9(+Adl0S;6XUco1u0jpJEKtjc78A= ziBz0i%o3-Y(i&T%SuYk}XV35CtWh})$#as?v*Cm0+#0vwp&AYa;T~zQUdAKX<$!1< z(?_AB4@M~;LiG0Ra`t9*bMZm8adb}G4{$n~pv96(hv|fEUa-96h2OrhvW_84Q|n-R zyBYe&XPy4uI2;v@OeU-KWfQ7u$9V1zl z>rrti*koK5?1MBCs>DF+caXpWegS(*nS?$ZLd4T=7Qh(L=c%gv1(&fFYt#QV%h@GDab zUMeqwk-(D1yaXxx`#SLj3{!4V5rB0u?X)lvRm-9Gkcq)psAl2~35H!q4-yP7WTm!OO?M7(#;1?5T2!p$f$AK+MaKA0<*gkQtc|vM==Phjlny5FZ;wL40T# z@LO#EEk_Q{%8A&^`RK^OFN@DtGH!|u^q<_xY=h3S$6wB$fJtr$0c|~T2!XMvsR9U; zTqo0-jH!-Cj3M$sEhxv(C&gIODPKydv~+rY^7Ke|+E=qU$}Y6%anMibybQ*g7A|n$ zz#5%SB6T?Mkdgn0zQMfAXOPiPO#yO-0;+2$Xa@?<>dK?GI7SdCd@is&u~|2o9^~kU zcDo2{mW42)hPs+G#DRot5%`vKoO(Kw^nzvaV=)WC@9%*Z*P-Q33Yo>hN80+=-6kKG z$@Bi?;}}!H^bL)pu22F{hX$8w)Z++FCjp;9f!`WcLX?f?YrmPHU|hHxHB?eF>r0)S zoG*4q_K9t^O|^M zpVHuySHK|MzXA}uFZAgIo!4sp3B zs?dxksSa=N;wh7`=21ojp~U-ZAk-0p5e$B&GosYV41``~AkuRR1Yk-ygn2n#9Awm4 z&gr?kxr}?Ib>?9>PJ}@faHxg?&)=&TUW#N6Ci!CTK8-sTLS!iB-A*`O<|Klj0WGqZ zAexJdIGnZf-#6VOvLZ4vv%0Dw&DqaPGL=x(Rhgb19uaxHKN^AoBP-%ETPR9W2o(ka zHY6yzLI|{l+D89aUO?&zad(j>WTd0qSNN|(0UxYUK&_M${V`X^30w*%s}FI{oWU&+&Da z>)=~C*O@xJ!f;a+3OrWR^wXszm}xe|F5?XI@SRY}rJ3=gBRM)rl?vY!W){f~#9Z@U zNKoz@^}S#mP?642)=j}b=4u!v@(s*lcdoTNTG~^m_v85VvD{|ghzsrcK4W9r z%u)K6E1$ItLQNvJMpj;}uYYYtw0`Rf8lzwXYt42U3=;^}K1)~t;OKALVm*dMcFU6U zS>8L@efOb2*_=fEJ@pVS{*v~iDrZ^~ezj2iB4@;gM2KvGQ4NH}F?*6CPY2WF*Mu6) z<6Kt1doU)ycttayd!Jm^KnDxnwz1*ZG_{=}n;vP(<&I z+v>RDG#T7#c8#TufV2&8nm(3JmkbLMyp2$>D?GxWTN@AI!wwUPlF6F-u8|_1kv;_m zG!ohjiWwon?HuQZK{uKQH6!?PIU4HhcJp<0wf#LI1N+UQirOegP{z7@B(B}TQ)(`_ z%$_7&iiV1UH2uh!ZuEAPC;Lo7BObV&@d#tGD4kFQUdRc@FRSlgQt1C-bAwGD9T*Jgpg8WYNp zSL51$<-o$x=)l6u?d3%}JK^khl{&)6^=)U9mzQV1pz1E(hW+PwyIk@L^qR{Pj-aY4 zlHgAkNr|ao$9|6>F~&%2d{HQxtV@2?t^A(gTaP4`Y;-r)7#!^rka04N#lF$Fgtm z3p)|X)24sO<&4UqD&WS;6b2Zs!ay~XfPzKeBcLd}r%V!~C=e?176;j@Nd2Mj zIi3hPAgkT#JpFN-dQQ1&DNVq=PDk52!{92ES|d?Eg(q~GIa*iI|&$GffXFtNYHrY;JvsiN_TP*1pP2k3!hp<^>r| z)nFUW7mac!B$^sELri6mR|X>j0`4dsrUnu3?$XBTH;c2hw9juF?l3^@vXJ7k8AP-p zkc!iNq+fPSgXQmZRAhG`u@t%0jHR;HR9uK-PC!Vg0Ml`iL|VA>G3n;zWy->wV#13k z7w}WXftP|qpaSmH5@9$v!Nj8plCm8;ICKyC1Mbtp>#z|Nm@PmAd3cB8XwxjF&9Cd# z-9H=@e(2s|;RGVgV#13R3lviY!sJp=X=WQ^oX83&oZ#!o2^7RZVS#u}ihu=5*CYk7 zSfp;7G^PCaVp*3xBf2x*J=?U$63d`hSDep;`*SmMc1u}oG`=3PIpK66x8k9;iSmT_p8yS=C!C1qAQJ>$sM38kC4NCyTYC{O(lb*u9Q|Cf`0+#z%AfW*yEsWJ1p4 zpa2UL3R9>(-Bzj9HqJoKSOz%aw&H9m{IaRl9}eChES}$8`92Znu~329V}(G5G^$+| z-7W26UqbYbEGEE)!(dvK_dX75^-&~&fz&WU{AG2!c#|A=v7HM(qii4rB)G?4+Zk}1 z4FwQf75C2nsXQji_l2Nhst*o{{x)C^2z*ttf8_z#p_hZR z8w>95)oa|}qJU@^9bQ}_f=g#_PTCvXi9>&C9UiK@=7>fP-4_}@2iubl@oUlJ+)T zFS~sqD6umQE@pa>mbrPYbQ0lE;H64L*-6+&0e}4i4MP-iFfP-|c7hXR5=|l=lUHUJ zPcK)S>$K_d&6m5Ia_0(^e>AjDlQqiFUPAb3BPz!q9-xTnADG2|OaEfSrp15!m;bZ) z;eT!}P|tMrU$=7k!^zQaN2|lb$6={PV%EqePw!F(7jliohwHQ(!y;AnpS>we?|yzC zQVUQwrxp{3RQ!%l_<0rw^A)2*Dg)s=z30VQr2dd9YK{@&;NgjEtf+AmnA6HE948Q) z4kIckx;8xgz50bic>J7Lu&+by=mu@&r!e97b*`(6H1P0BzrJ{}#*3^fU&+?o{; z$-z?uB=BK%6mi-Pekk(5D?{Sh<@bvW zYPyOcG3e#o#Za4A1MuZ5%`VL2)Coq{Uj$~zJe6l#ED?9yfDcxaan#_Yc#IH50OcAC zd`mnAbH(|@Se_KCE5MC8LdSzJ-nWsj>KI_A9s) zBMHH!qXmkg-V}coBf%f{7+@-UWoUELDjiTA zrNjRZCM|`b6x5?FmT6*7y}2%c8l^%NVdsdJVQ-3$hu(ABV|C6}`45104sxgVpZh2m zelJJ4M#&{zu(&U!r|C=9TJ`1fsNEidV2*G3D3|Yd%*=wOLS@lS*?*1#NSknOZ4TyG zbp_u?xu{W=BRcI~l#8~m?b2lWQ2ZASGlF3Lw-eDkeC$2fk%<)Hav)6J2gzWd4>R-Z z?2LX4Z&{ls>ZkC8-XXlU#>1SrVt}|zm@=)yaG*<=WE$C!dyy))6*q+H1JTEuXGS87e}Yn?6oAVGG-KyV zxX2&BS2TFpbmvYsfZiN5c5x%mno~q*aML53IL2ajLR?HRB)qFO%||oWdV%U@IW0TQ z91!?G1%x<;*|a!5m8~ii1TzqP#Pt#|ZxBl0j#8pOxO&E6ZAxok_K_#9}hodm$hURL9HqGtw9SQ)hj_txiuQnO**FUfaRU~q7Kqi)L zF2r@Hh5*{k$wPaid~7o?;^rT4lK9mJISQ!1PGxme%)+;EMr=v z)0xX9xc8_%fKWA{_1>fQW+7p=lwdJLooxaH1g!meWmO@Mx~b=%iJ0IxKBq&R$PqIRn8D`g=X0 zK+Z3w4Fdi8U(Pua=UApyqyc-vIA}PeRW2TKX%$m1CI0c~-~oPA$Id2$l2GjB56jfj zt!L!h%^g^FCh-Ao=a4b<6Vns4mSntKU#AYB#jAB=$t(Pjh6s4IS_dko2!)xXcwQmF zrD~(46MJ?RpBxn{!qIRNK!+SN!r^m?Fy3q`5nSYttMku`v(4u(ezOXuwROlyOeA&= ziIoEaUs5215pe4x+!)cwVP`K58S!cXLLf0CI(_7~K|=#%T6DXKQ5hF1eAs+R$3{Ne zUVkYUGImyCP&@^SkW}HlwY$JyC17IF=MNy{8VVGo7$XM0*eoRU3Zh+d038g(WFj!) zf2@Arr0Je-&(nmZ&DyV6Iz%&$hR|X7piK+ILbPx%$;fN7A|Aq#ff?=iVRAAdbGH6H zZS(UHZ&EFFFrY4V02wj~JYQ2nDi7-zfnm%#?gznCI(*oES)|>szNAy>{7ejo0U%cl z(BSPT3U#b8po^mm-P0_(`y>!kvx_K|xSb7SYCwfkWzqz`Z9u|L&2eiLk)IT^W88_$3@m)AlMj*8_OQUVOm*;g&3zFO z4XF;wsMIS~VBjG|e@R2sCcJ@8`iWBQzy+U^AP(NI1HyEPc6km#HF4_i1-}f0#191l zUYSm(69P8apfEYB+j37^g01M_N9wwrzv@S{PNNutk`jAnImeIvY7{q;7F>4-X#u@@ z?#Xkd*cI}2b9_?)+k{)kBs zYp9R#f>(|gX?u>F#mmj*m+iIh74vh>9TdE5rhIKAM5KWPDLp^WDThi3EWK7lB1b!w zV#ke4!KNk>&sV>%<%qy<rgd)0e-0pM(pg!t2 zaAoH4%lam5VRL)?Y5lDTZXG9R5|vPdF#~*CvB0HbAvOW)5LeRV#Ha%~@v{$1C(xi? zC$bfZbvPi7Lcb81(CQ|W>6G=%7&@&{R)pCHyL4j2I>wMFqgw*o>JN#WKz)@HlP8RE z$KyAv^t1G1jWO^ZMZxibW+EC9Yy)@p)QAxjl738=*qd`5VHJGt_`vm)?}vxhWC z4u`j0{GPTza@EIEH3{(7Zg7k9iX@2rGsh8DVHv{7=s3dDbP7hQ;IsARZEpzS>CYjo z=G5?ZSPdpL!)k7kq|FbiW@(gYf!@*u4lvggTn_2UL0)pFVYR4;-Ch<0>waM~Uusl1 zcyufs%jz^u7s{|2x}xgn!)m{=H5{`#1on{TsCslmCu&FSAuM{LjLmlthlj<2J*hQN zC6ccGWzc}{syW1U%_fTg+u1`}NOP6qr&<^BQMCyCq;{5}#~dH81;H{5Ne%=Hg~eJt z%exXn2xGU)janGw>I58EsPhw19hodCB*)zjLV{y1Q>Ga_-4t839HV}@WimZIO&?yvc=@vy>i?Cuu9$BBXAsB;ZqX3ByTvUNJzEW|bH)kd$k7 z(6B5{lW`ji6J2K>dFF8yH5`ZR0*xyIK}d`XdH3^I<<^+Kixibq>u_uc*5Q!Ar6PgP z3L6;t%omE2hsVxk9`b#y?4q)Z^xv9VOKcM8W;N^@&OKct@ z=dciSG0$Q3==8`uF4pJJ{Acu*_7}AVEY8lix3`O5))#5#R2M~fC{T2zg)@*akkS?j z{8cjyUd=X;%zH^N#AyIo6vU}zShR)bax@j9YTRn37hK(oV=RlUmQjH^n!-y|C;FSc zIqSgXyWTLH={bUnAcOEn?I=D`wC~0m|+9%YHs^Nn*3gksIq{f}v zV;Bl@HJc1&VnK^)b(^WXqF-8!RL))o~4*aG)AaeFv)<3uu17%APPaoUn9<5GnTXOQ$ za{qld?J?6VHtpfWAVr?*VY3!F{w+C*IFp)=w4z-DoUz|req{07vfPlu z+?Lqr@Sw@P2KcE+Il%2Q6Yp^5`6Nvjyh=N@q|ULTtMm|GRejngx4t=)M@Dta%jXA$GD~RaBxUV2QNMcjfanmzA=<2!i0F3cF2$#7LovA!OlIr$#N{Y$-98EfzlH&5 z?Z#t7*oLqY7p3HyVPRTI7+23I>^;vMUf7;t#mjF zYcC86URkXFes%fv)8Z`ki-qwemkK*9@avsv4<1sbz~#NdnZwSRXyjMlGH^RX5z-#i zMl1t(Iad6bCXaEe&T^j)M+%yQkir2mX<~?xf{iFykDxOn(k~t9J}x8@!_Ytp9U7YX zykDmsLU2b;8+D55SV3GwCPJ)_%GQF3p9(A3jPp~#d&$IMAOy-Fgy5CghV-7l!m$W` z7{L*OET{+(d?;#LtHdPBh%tryXb^y5P<5|hzk4D;fFbQ;^cfQgw^(oOxPZrMwIn3M zg-M2CT#&1|E<=QLpWgEzk@&pOfLBI?muXJS@99(ooEngJ=qx7C;n5lc{IwVAYfL|_ z`+T|AE z3@Didk>VzX=VH`=+npd11rY!Ug_*>Ut1oGY_4)QHO~MaxgHr`cfv6*KQdJ1DfFF#? zqhc_0Vz|U}436%JQ-s$-hhbnw*kEM%A$FLO1kH|sEw;hhVh61AGzZ`Jj`>OW_k@G&|?-o2&Enl|K{0r`?VY zc#F~@46r$M!1JmO#s<*|vH_2J*I}&IJ{ftS;TOb#8YhAS34#f*G8*0fslW@IDVt6j zPRC?8nebo`0igwP5vdH*F`ytdEewnisf>ZOaB0Be;ONn@_!jz7*;{XV2l8RiOCT*O z5pYPc*Lv|HolWm?0NkT7Wk0{b>nl#+kBJB~{imagV1T#GfS?;)6AY7xzkN%GD_yRB z^+ebOK;Qvde2fReh`9i`_U8+x*S%{6c#li5WgH1}5w1R#V@Y_hrT~p%u!M5jxkT~R z2*N(63)3;?(0<~Sb3Zo*W~U@%s4S8FiE9OAq?y|=b|;ao7_QwudFcB`PdQe~=HS?) zAKZKNP}va$Bu z!8YL?YTJ~m0gw@m76WYOIulpT4Unqmc2I<+QZ&t0Y@g1mX&o`_L>g64rr<3(jw`og z!Sgi^;;u;y2ShXvC&hwBWX8$EVl;)X68lJBb>?9&s_=-jWKdplSwXKm2MyDX#+eaD z5S*=`KG{}KVg_{#!e+azgB+_#5aT>IYBYicZmXHZgNA`;Gsu??!uF}UooEJCRfdGF z(l$e+slgDcUK@{r=A8*5<*{e%sF>l(<6L|af~G=}m@6MScbNmdwMV1rh(`|2bJ=VD!$^g~``;(iaYttQHPR{(bVlVe%n^ zJFyM0Os)njM`HbOIs(esG?MO7%YG&*>tA&hh2#eh)lf+DqwI3^uZ#1hcA$4o6-IGu zlK_q?5W$tUnQ-6gn|ACI=7$ki6sAc6La^7V!v1_|y#D_s0t6GV+Vje^;OzD;9jKg^ zDHWs5c&KiF{MFjO4Dx54sl4CV!LMQJ{iD&bgE!lov>xE&`ZDzfze)2fI;&dWRHFxw z_jZZ_mNy3vjv=tx`cNqi7>=D3N;uUN0He8tP}Dg$c+j1G&&J5(yO9C7!QW*O;vJ8^x>%c1o>?-Xu<1Ts7_h|>4qOTM+$ZDD{RWHa5v5-Ir z;8t6Rq?)i0+@If9+V3uo7Lw0r6C7ZVIp+-h5%{6H4_sL+@pg5WhUwRfH+SbN-)l^_ zFbYMS%m@!vV`17k!ik|hHQ^N4ENeJC@*NU{cY60}oW_UN6ev(44@H`OzFJ?_htN4z zkP4Md1l96lfMDMcgEoVoBLV*&GsS+S=Xq;^p1Nm$b_B zCXC4ASEIxTvl<;5xU95dL^LBWghIxPtcO*Mfe*PLi<%>F;Eux4KY1JvIXGmei1U~0 zVjfcNobxEXDV|dt!p!HceLOjMb|8Brc-Mgn zjJl4{6NBf+V8G}1kj#5c;p?WID6AubtZF>_dcFDh(Tk-s2{di)=f|bEjo?1pRKQ;) zj*zR_0&%ZZDk0WJ5kw_l>S<9vNPM+dW z0{PhTTHW0#+U5zLm(4SDjiGF{MoG&;sz+SbNO*ftPMVVAi~&h zPkNg@!8U78j!LSTgfN$V7`2(;!vcjg_-r>e%*$mDvMEM@!Xlr_O!~(R2VQhGIRdPT z1gl>eN$}@*NP$4@l@n5Z=!J;rY#{FC&tr6p(ll*@JK3b2p293kl(}XDHA7;WzFjz> zfJq4%L_KF?kljMG3!#OHN!|)sxB&afQGS?WX?8(GEU;Qd==|+NI40u|^v}6fIgbx0 zGzZx@jYpdvKhK`g!lmU%<3}+N#+c@x6h~}7lK6n#t_;x46b|y1CgL6V%|>d)fJS^I z*IDG;iZxflWX0 zB@C|e@IiL)=CmTH0_~s~J0?X)nR(jUD4n$8$Cms7(=>$2M}%+zLZFZUhd(HcwwO@j z1>pgjLn7({412H&7~%#Oy)CDpH5n4a3RqzU3Zp9@!WTzj={j$x1oE#HxLHcA3v`+EQ} zY&pb$5+4z%43R_x0=E^2xbc~pMxX@tiCmoBrcIbi*Ws*#)fYovr1 zLWV(pnPm)OQhAK7=L{I+Z6UrFITk*nX3<5UoXv-Dz8*)}`%pZvF-QB8M^c<|q5`T? zoV%{QKqQe!wq6c|IvB)A=G0M*+zhJaV_9aeT|rK)0y7UD6(cE3LWG&cND3ohabBF2 zm&5%B&Jf{Ya=4GLWJ{zdslThjeLPqjrZ_7Y5;f)CZc8dXNS;yBELb>+mQcft;|I7U z2%t>PeK${l<6iBxR^ug#OPEgQoEAu&>5wjk4SNDH@Pwx6)*tYFeIRf<0}-qfB^4lo zSdSch=4gVS*WE#V7w*sidV+@VogyWFw4Hno2KOYULI31Tr*UTj1jaMdibz_}v|amNPZ1RBbz1-b=V$K&C# z+B{H5g<=vb&;s{~b%)J+qjjJ@J9FW<@fS)jh7myMoP`FRtGy|iA#@08iQ3kd!J@KH zK+u>g5kmkg7*Kqv5M+!w)8V-~(CrEY5ix=29uo+nkFp_z`*^P+flG}mSYRR{n9N3^ z99ZG&rVlC{c5@!Q$eBcaDjbSi69GSK8v~vnBf@^wcL4-nFum$w13cE3x`_96SAovf zh5$qa1!j1AbOx0RGGx~YA^@Rse&L`TQnC8~a2#eGpsET1u1pg->!9i`_FD<#r*&=- z{3BXMkpOy12unG~MwrGz>61r~oi6D97_csk-+sgCglW64 za${u|L*h4z23kMsLvWQC>KG#iheFyJ_|7qv2ObgW+8{Sj9EV6h)rtd5#tmK>H(uV| zw2ol&aU|M3n~j5`>?m~PKp~YJf&to63}&{w9Cle@8ZOnx>re{^1o&0&8l4Acska{|u9eV{1kTUYRZ+zkGZC7JJD0u~v|v zj-qHEl23##hc*UM^TCey6Uj1?CalxL$;pv)K3M2=+Vg4!nbF7s45$smsYBm+;vqE* zTxuA>tJ(TkPF+OnG#4|r^|3qLjP`Nz`26l7jVvy(17118=-5CCR2C6(Dpa?&2m?W` zW`j^Nf#jObgwWxFA~b`jH|w;h^_TL%Ry2Ui` zo7Ljc!`{XO?_T{CtvN*ZkHP#L+)o`u;WBM)3B*cDNA6dk!95L1EK|(YI(`n0a>q`4 z5aqZC==`q6A)-4t)vfZyb#sJ7^tuf05o2njfO2YrthvI^*fU44t^rQSKqd1H6vxk* za^4ERQ4|hYN|ER>6b9Q96li>|zr4G{mAEgL;pi!X0s?EIPAeX|4R<%{zMrNoTuT%6 z=i#A*iV+Z1jvj@n~4Xun8d^1H0DT zh#&7RH!0YEah=X*_s3tk#0Rfcf&g+QNO0-ES@{8BP&n{YfkUpEaL^5Pj@QX6ieon^ zUuV=9x>Hp$(q}Uep>qsB6lS0<&YeBGTz^SgULU0iyB{S+uoOJ6NZ@0N1i5^lh`+Zs zMI4*yM%Eue0wqu+`de4PwjB~_0@bhItC$}Qs$szUl@I|2l&dBL-Xeq$Ih{?2avHvf zGR1=LGe$()!0nxssDVh)?vsn{)`fU}FB6bnEdv!(AVTuvECZKwgksDxayyGK=1gbVn(LJ$WKa7$e&u1qh}9_Z?fP}t=`OC%Vb&?aD=E)kA4 z;lv?yq!RE3R+8qfB<&NVEKGJ@;=JK2RBIv4dv+2b{5AWmZX<#iNnkFRq8HHhb(z# zF=1Ika2KF!je^#lFrd`)Q5a@2vDes0LAsg892r{al7f(!&v>T&ra!Mj-UTD6JtvBy zu|r&<(Po4>4SZOkh$3NXKWQ^vL1BQ`@UZu2Dw;m2RuZRfncE0}y#AhC=-bt~pMLE! z4C;kw2WpYAD8~};P)!RqBcljWjoATZK9^H$!#a^CnZ{A*Cqn6)ag+`~rzvbH&vHQo zSzR;2Nko{L(v>pjspVlYr%^C~WrRMOXc@SzXv8(}MvPz?d1lIw+v|(syrtczk%y5- zd}0g`Cl%!O#3_yUUcVG`4bq|IKDg-h>`OXm#~FxIOq9BgvitoIcwXZwsqi3f^LR8ukzUo!~Y5i^S@7r{+)$6P7Nr6B8 z`)eR!UFvXHIn1BjrH(SIRidEU4szxN-Ii|qOTR^4IlDX<2D+s)nSs3POkNg;`#cgd zqDgh)$k3#*f6SDsi1QiSi)q4-5#oD_{LyHb??7;56hT69@!dTFy3#0J zSj&jntTId>JOqqtm2rzF+G0zb(nudd+pA63e9WJYfC?!#4w?|-1K2T{-qV2xENIhA zdE{zf0*$-s4~j70N6k3q{Wf(6oqbIw$!@Dxi9`IIyhu$#Kg3pK9Cc4vF!s z)5*#ET0KwFkWN=lCk$T(Ebz9`OeahW0K_#GW)_0V?0m0`VEDS3eWfwB$2&wUdF8tS z(s|oy1mUOk>e6Svs99|vB#4M*Kc)piDzU`-I-LM9f)Mxy+S|fM_KBEC(9TRGA~SG1 z$F<%0B6yoqiWk?|z4PVu-PLd96e>rINk)Q)YGR1TfWVFTj$N5wIoybXNq9?y4UP~{ z;DZ^B&>?`b3O9IV-1tk{<0$R-Tump#V>LJMebvq|%+>}&ni*iQA?G{NA@B?{ECUdc z7r32~=sMD1CD^aw!>8@_RkJZ6j_9b~5x zTa#L$J0$IOB+gcXD&{ysh9b#ui=X|ZeAYbs2e3M48Ter4K0CuyRBliPKW_&GS6u*9 ziUP|^=gGjVlV(H^#B7{S78^e4Q7QxhMRi^12?Uv?t(~QKky=EYw}bLF{l@d{m(A7Y zf8VV!2Rm$z=x6TW-HH;LJx7nNs(25oHyeQ7H6mX|!9C3gJ zr4%rN+mNZ235fD++1>qxhat*@Wz}v2-@Vs_guG*}j;#nC04v>)`HU!)&S@NnO|b9| z!Gfrm(|Df_XaAl^aeG^y&3@Q+q4;$sMcJ3md~FO2aUipUs}c;fe%Hr2(Ypa2HEf{Z)*DB%ZGgLsmu%%w?c>PAsEM;I@ioOgI=cuwy3Z19r@Oy}deL z`Cen+A3-aqMicsB6rNY#K>|xJ9TAyr59Qv>CGBb_-@K5 z%Gm@BL2w`>%~S(LtoguZyAKZcA4CYFODIuOBA;*17wpW@g!G!dyRe(oo6}%J_ z0vqu5S{c}AL_w~aY}ge@l<6lLqM2cgSVnh!AGN|%^6cx~#p0Lsubl(Q$$`o&LhOK7 z?g_+L=um<{c1@2Q0uxwkT1BWo#~GidIa`QW?!b;&Z>=t{MB_F4?P@02b!SIZi+eh zGG5o}CyXO&sj3>m#D&;Ip*6IWj!V3}SUf#gq&^`pRoqdx#slwBs`Lj{@Q~UEnn;2< z#b5|PoOO(;b(-KPw;9Yy(vNqDZVwhT;|8|2jpJFd5YEc`86N+FMFo9Y$ z#pFQ=xoToDMu{@Z5ut1t#)w3OY|kiBB{^y1&Zn!7`09wiid_aW~ zmm-w=0Wq|*;6PFl2f6ABC2nVp=n^6T@#5m+>bhE^gQl!;Ky7w93~)nnz~#Lh#^741 z4~7F@Y&~K$EeRuVM0CqoqfXAYfQgmoC_qeCa|h`xCTvZJ0ku?VxYaI2YF zus>e~0m`d}pi3|y=_ll_gw=RW8b%oGdDj^0$*GT@PMx3NOU)pL?ISg_fWC`WbO%si z@hXCces>o9J?9oq2T$1jr&e@>iunN)R7_C-hcXK3QsiJ{5NXs+9{J45EoT)Z49DDi zEY)Wpo{B;oEr6y-a)rt{QP^?pHlD7v0Zp!^5hiN^hT2h>$J9}jx#DneQY=t$*+qn; zn{CB014D#Y<}y1+vXk;P6!@`1aTs*cr{5s1(nCl=8%clMb9k*c2_Nm{l1~dYgd(`D z42gJ{VxkQlL*A}FZq6HSgVP5U5d1`~2cJ+t!p=Usz z<5_&HJRL`%Mdvtn7gC~9Swaki5K`j(wSlnngjt64pwG6Cyt|o1Ql^-bFgOasX{d;Z zMZn;7^Cb4p*g>BNsH0-w>_Tb|yTDg_({qLtqCI^rE27}QS2Bh`l}79Wx799UXl-_Z zw|Tqhok8BFgcFL8c;X8+9JJ*73?Y;Yzsa!a;nZR1G&V}mKuj(3{t)p{dQecs0l!td zKvh*7CV^n$AXl^PqMV;ZVzu}%L?j}UcuwhX80IIj`qT$BkfTHM$Zb%jMgi|o6oNXm zser%!DR~w_b>UP-t3shWghF08Hf%npWozO1bw>rZe>J35h1V{Ytvb-3udmWz)#9hS zj~{zGcRqjd6U2V#J3r*U^Et+y*m=&*)mo{KES6c0>C~Za$)igNsmQ*!bm@-*du)PT zFF4jGwM~qgWr*txl*G&njy(hMTQx+wN#)@9Vbl0gtxouyro3`F+cKBS2NDA_=RGp1 zCcs9@7{cIoCxkKEsVVix*cP(~Ou;_^2iTe(O%!WW~%d_}|8YMW<5Jp5_lF z+-u@_JSPGs)*R^laPa8o8VD2qYGzHoW+Eb(vw6{5lp(HTpv6IV zsZ!j=)qn^FBBubHoqt+|lb-P0C=&gCbBBbtL+C=cY#u2<~LLqTNds6LSJrYLXC9MEIYK4={J`5C4 z^BylQF;@)3@K=Ryw#|c}h-{|8wX*wIV%UFW-|@w0-|@@s^)`-tz3XbWyj${Yv(%DMvVDGlM5q6SM2ukv0uUV zSkwWYxeBcx>3O^wTAO5+p>>c6jjUO5H30lF2iDF4mh_kEd2dhz-+7D&>p;SM#-5$W zWe-UI#I62TK5-s~)gLYD;)ox&*u4h(ouvaKuhZ08Cl6eyFe;V7X*f90;#KdU1cmd1 z>yuNTX}QjH^2+(oJoQxAfOg1nRTx_`JYeGv583PM!y3cR9*VX=0L*qT0PfVxAd#77 zIO9NB{LqT&TyLkVyyqNItYTKT|iv% z2!je-DpC6mhJ&%w6fg9E4AVv*A((EN?DLccfYKSGhwE+aHjLM6eHiL>7UkPzsa~FnUUWCu}?F6?KjM#_T)s1)S zCvZD+B1}c5S+u0$O`4*R zx;dY1ugYK(p+@ai`;Rp%L%;FvfGI6y=^r>DeLf=i77eB7Aw_j3_{%Lc&{mZ$&tH5%L%2);WzLyEm`H*a*_jI9_c;}? zVCTeXIG*snY#6pt=PnIt8a`wS672A_U`-E$$sUyR1{fEK$(FeQz91B3{NI{Mg0Lhf z1D=WW;i`3G$pC(%Fv#{B3~^jG5K%B%DGc&}!LWQ(>=$o+1$@wSt8b}svH;qo*|o?n zq&KV0mtWUKj_fzJ#8)#wxYTaeFdzsRYf}X{BH@zu&6e;$38BFdCm9xW1EN^?W{z*V z8*&j5>nlKN^!)D2O48_oCh1Ko-XPMReI{ZMD6{foJ_yCh67Hz5U=_x!O1qDPU;!jy z1e(`*^w()~lkbZU*PE|tvloB#%kB&@h&Om!GlE5?iHP;j2III0^yZE|9EF}{!vO_z zIQ;cyv-*U68@{Z3H(J}D(?o{Oqs}&U_*B-*bor-5HbEgGA#giKHX%zkrK|{%cK%88 zlFRKZUFeT`scG<1a0uN6czsOzvtxf-5}wt?H( zHo^oee4!cWzDUQ%EuP(7uggWr;9Z^mI3U9E4ZOcL4)K^U2-krmm9@FKV?gBoQb~U- zjF+x#5z6p@@=lv8+sUo*Mj?)+*8;7t|;iKWr{OV|7=kb^zP&Cr>rZl1P%{5~_~N5LBaJO;ONYZr`TOE3VTT zHzx~zz@D&FGkJnSGR#wIpiW8+5k@j2{c#Upar~edZ6K%UEaYl{BRUJ*&ap$#NZe|| z@osf@xpcut@hkc$Mt?F>lhLqxfRAOVnP*Q?FP#j9;PWW%Q%ozRkY zBXESc%JBmaWf^770SDW1z7H)!OJX{=TRE5tN+(G0%EAfR2kPnd`mX4epk9pzR+APh$a9BL8OVpF6GR_jgb?C);JM$0=F|5p=-jxz(WpDloE0al1^m>_&s`gnpo~Z#q zBPamX^!*{guLc&d<7gVeNCC^@r{3)SpMLxawXlJO2e6_X`rmV4fxb@~!cX5PiX%Nl zaIC+1FJ8v}ye+;v+4>yv?<4ODDR}uAI+pf zjZ0;tROcQ-U2bS$uA7mfvGL$h8T6yrYLzkQXXw)E8bXeUZ9OkHIDRg@AY+62)Z&Eq zC^BKv1Z<`b>I@2*8nj0kQ=A>gJ!t04T@UgE-_Z=_o?QOAzP??&E>E51`FFK-qOOWU zm`l?}K>}J3V$WD^+1uNc9?%$JS}q2G+scTz64MyLtEf^b$iMl#@q_^RMhP+PtT1Y8 z9CO!Duk z%87wrZ6H$fduYYIy2|AhDJ2^haTjy0fbS80k!bZwZXF!o$zw#}{k+K!w^+0J=uvCT>CY%4BIZMWT&HPf z^Jns>ISOUx`qS%VW0&8r{*}G!DM}jYT|Zl$U#GKq)8PP&6&NyAlXuRZJVQHJjC}~( z$}#rT?sexd;Ky_JYr%xy1V-9=de3rq6BriNWqk*aH1-gY;AC1+iK2TQr4p0iI)EXH zM8&fVknrLrnJ8rPGwWAxa|507^Y|Gz);fuINunWm?Bz1b4iUHbi*I*Ana3yv16WRu zs={Csnn7W}m0865bvi*0$9AvpiZC0cS9}~iXm3R)_N=EniUyunQrM6S@a}kxF=^;^ z7Lry51YaQ_;EsY2H@JXMH9>f@I!n{lfx`C7*K&e_?+kH3@KSnB=Z2SiA~AF@>!dc| zIlVU|?ks71ASlKMdWv*NGr~%snhgdOuKD0#2z+R}O{N8On^gJvSyvuTlKv3Bjv$~Q zUL3Lo2`5m;%!x21*<5A>VwJX4{2AHTMVWi~2m&x_a*m&?X@uTFl&v-m0RXBZM$#0D zG1EACcr3St5Tw-%LWDd6g%sNeIOyt@bTF}BKJ7;*S@WvvIDmkMYPFQ$2_WG99{P|Y zMd5?=!r*m8*YqLgpSYa~5n?E_3_@a-(c4F_!v|7B6=KSCeKMG)!MJ$7X`CE)#vlV0 z{pm~ngCznKvW(;T^}0CQ(76m}7x-!0W#CRt38_8WP!O4I7bPXkEciO3L|8Zt{$)l4 zi7-Y$Z=COB3XUV0zWPBGd@)BB>9UrG&@}@aP#AEjo>RQUK;`*#n5dM|GJule(9bTw zD2D@Py}rI&Z>~HH-YoD^#lb}oeH?(e;(*Kl#8HGW=4wUStdQRsB`MgawCqL3EDrfn3~SJ!@42A&=Tp&CW8M6Mb} zdAGgBL1`ChTJ>skxp;cH`e*MPfpF9%@&+26K=`k*Ffm(6DY{=1ADJ?YB&+xp`t@56VW ztPsEe0%MjUHZ#|Q4PE0R6=E7g(%?e6ydY1oKqQ$%x)9_W7ATQ35}6HveGMEX@FGY4jpdEb?ATwSfe&zz;<%(7Mh*bRp_%&gO77;-{aY zN_E;3ya*nu(U2hF9@$^+_z_;9@X||0(wS6@IFNYo#+X(#2AII<9Bvp6cuCuvIvh|& z^&f1wU96y?cg9po`=FsoaFxE{w4$E{$LtT~6h@$p93#?RY8T&dlzBQ~aP9k$+n#h1 z68KoG~(&?W_6X8GF_)5#GF;&sd?dlj`9D@ zk)_|tbsN7u7#&%9y5;^?&(@c>oAdU#($k;MQ0XDtkS+Fex-oHF$?v=h>S+6OVwA+$ zOR)+%PFl8~x;6BV%NV0PYB|L6)Nii;*l18~4|O_i=T$^n8fViW^8Qd@X&o$Z%CQQ- za&$>JOtscUmX~@k5>M{(65TD+WkIp2}!Vz9#URdI3=Z*9x}nnh8s=iRVU>E-lUNPYObKf zt+UK~2CbBiQ_R|NBEi%6YL#$+#33epS~=nn!KE0AU^73!EPIgma2m%UrotJP_{`}@QZ#aSd4V+SCjPT+P%BFtX0Xac6|LMm>j z@yiN}6`W4^9(D3SJF*OjFy@9&t8F++h#A}?#O@8SOfrkZFex2n6tf^27Ezs@Z*OnY zxx5$U5$ zyEs%$jC8I902gvJq4ppMRKNk7@MdLb@X#@AqYxeA>_fUG76ey}sZ zjRe7~MyBX97$6|DHVbG>+%(=#?@>fzXxCM)&PrTwKlrMW!{HGIh5>mEhsXnLOCtZK zeD8vzbmmMhi3YXB&=}b^IMBn41<*Qr^wzDTfs`B$3>uACN|;%OhsMAVRcwMmd+&9a zEsHG;2riKz>kJ8}8Mu=v5qADz-Pya2;y4s~OOo3t5D-BjSV$WMmX&%jLWwaH%K4UN z9DJRD5RC)36^XbO7ex}ez(XF1_L^i+aFhv0km3ly4C;Cv$Rko z0fLZNeA8Qp3#(To@Qd1ogz*K31Zt|Wg%ANqX!UW4M|~$*J!_l7PS6goI zyLi7%i*r(cX{uATMB&xXf5YFcE16McC(Z_T%`Jc!xvDBK@XN*^ktJ&5JCoHiERxQh z8J$kU&b(pb7odh5j3?j@=yjrEr zv%aJ`>8t;B_pRs|BCk{)h(Xn*eiS+c@ao!Y;;#~5OX4vbjlpi>oOHg~p^;~&inDNN z9ETA%a~6Qs3FVvhw=}%)Chn>4EXEN7jiJPdyCC7)H4%a~_uGrBw9N7CdCD~Wv_rr>>M}z#34F3#rpv6B=~Cz*aF7mU_^cqRw^vx;ghgXj z1kvAh7VmJRc#`Izru|*ktKY-^fv7<3Kgp0?V@`lv0bz+nF7Vfma>kTP5OY4^(0dPe z76%`6Mv{-mtBfQrkH!z1(S#1Oyk94`jb~(#(jPq0mWx0SyuY>)sn;mxedUf@C+D`)~J*7?Liyf-*%^C#!HH&6k0IQ)~)s_0!tBW)dVzqd;I`2(Q{_9WA z%LwlQ%A6y-aS?E$On=KJ7l)lf<}cajp0cWf24AEQcK5vE~Xq9?9_?)Jl=R-(-P(-FPw7irvqC(VR*M>Gh>#?}^26*?~&YBNxaoPX*ocP}CY8Uo|SuNJi@l6r_cD z^H=NZUt4?Lbhh*Wq}e`0Z!*4K`*W6^STG)GcP2;MkRO@%8s^zZh-Y#o$P5og(Z=lz zNYE_2Ljd8G0rKo}lQs><@{)9vRNx01xz;R?$&^XC64*ev<*F$cqR#ET3zj;HP|ZV~ z4o6&@i<$`>yfQe%hn?MSzOJsezb90jPxtc$@aZfixReMd?2CjnqjqHYqJ(^;cbg(( zcpMk0u@1Tb-yj|RiH9+`F?P^!432bk(p5TCYmI$BJrejqtsIUSVTc&d*GK@Lor4Wy zNc2ad@Mb3*dC|kpu$OYd%n+4vVQ)IwH;s}nK5T9-!rTc!R$B=PBCI$Ji6K<72E%9} zS4}h7r5wH9)WD1=9JsB*(ckYfSjyAu?+FhcX?gDV!f{Rot*%CbAFGiBY5=Gj4Hj6^ zfOLP)EFcH>A{;e_AX7!U8MMTR)#!jm;oOZk&vso5C}SZ#r@^lOshSwOe9Hwm#cJk?d`1-Njz2S z0{*HDsOC;+WdA4jCLcWr^OBE7dy~(Sg+EFA-CS+9i?gqHos(`Vebr$Kag7)O2BF1DA)JivCNfJSn>4x(L z^vpR(a00j_@71~7z;XMD8*M$5XmW7!^Mi)$0YwKGs8a{%gPo^5(Ieu;Q*r@L4k^3S z%0kc}F;qP(*~d?Wqv}Ep@e?qvU4t|&px{VA8;V_yvRoPK4x)j5y2yd*Az0u}h9U@~ zkshLCNvLrKaxZG+BjPbhlfwK&YdGR&6<9=M(H2on?x z57fv-QBPzmLC#DL$p&7ha_Pu{H>q4sN=6M4yrd%*BMx&ILx?d0Uu<>~!N6?=qraD6 zAjgxmlI3Rca&!45?HT7BhvNnbQO!Y}89Lx4^O3L`3KV?H9}qeOM!a8rZ|38v%)6YY z-&_)y)ly+>m|-9Cqk;AZdG)WrVAFR3BSx~VpcW94X6Hm&-L*KoySn&29UAZW;8#I1 zd=NtLQp6$<0wt@#5QBhjj&x0onZ`+P`c6Q?m?J`g+u1g{qb_*J2?ci%e3MQo@;JbL zloV~ln3P%=4u7yj1ikDJ%|?ZwNigX|i5v=P+xT^gs@LnQZ)v+Q4+VZw+Xqn*N(8k8 zbTuVFA&IT`grYZ>i7z&_B&#YfdKCJo3pdV5JZVj?o zRK^iDRBlftEL6w@=PfnK{nF3`BX!X{&YSb3CHh zhQ0Kj3X9EsEl(cM{50^F7GWY0_3F`y>~1E8dJIVr*)Av2P@{yR_?8+YYpyMT7w5bO zfol%nMklO;j*0?ms~~{i_v#|-ZrMgiO``gI zYP5K8;v{k%=F;HJ3I;9}3~5@l-JVjFr-a1dHYS1jI6`3pLLT+saq5WKD&Tas`izE} zRQn%Tx9*_u6SHZa0tJn>nfU;uTD5_S@M4B(0D3=9J?~|r0WLvhgv+;gxq;_09$~32 ze%K7{QvcxpzDsJ-$cjCa7$gG~Ub z$%wZYgj}anT36*VT~Me7!Wl>(2>eum04Dc}bxY&0JP_Y-K+xINv2Hi$bzb0U{btmH~_(pP_Po6RUAjBvT=c^rnn9ef-iQWnQch0 zz{gZHg;a1!x;o0ew&@+nQFaVR*s#1C_vflE<{fCAoG`wuqSuJNTY6iPPG z%BFXY#2H0~0AO+myjfkHuWr&*qQ%SY^+h3-cyiP#s^J9})&47o7*aAZvZ?;F%d|xt zcU*k7`h9hoj?W|It4Zt6-n>9_+;h^ppVQ?01^+DKu#||C8Ibr?znejxv?HN5x$ovQ zooE29p|)uyC)hiU2z^C#w5?E^_8=Vtq=;^S;#aAo7TE?+^hvQcUZyqy=Bh&CRriXu z^O;l{PtnF`6jU0MhUhk(HpF0?_8%;4RR| zwcBLdQ6Hp{XJ^F6#b!9%%UeRJD3e)8H0w&GQJIWTbR&rO+wY54sav3shfbVLx-?S` z)U(FH!Uu+C>Mq!xZi4c_XLT9!t07D}D0>cPkG6C>2*?ieoxg0aFE18P4;Ft(U1rW@ zaD+~^l0>LHXPk_Sr)sAm)rPNgq>w+FNEo?0D_go&5}DC;pm8r8lL-{Y840qD#naWt za@SxUt5F9MjvABtQAH?$h8W9i6DcE03#f?bB(;dUsT&X|k^|!PdUN|JOnh*0{Rfs92GxtT>Z>2q45*$=S$yXm1x9SSclKCZ5-J>;BVvhI~} z1U5M5flGBBQhC&U1csP@;(Izhh&NfC+K20l9FU{iJHs$2EMReH) zgN{U0Gk}5j*PM{%2N)Vc%;iK01nujOh%SkSVbD8n0hV2dmiVz6Mwqw*XvaXnFFT3G zE=FYUD8^xoAi|~*w67}Z>WA5A7H1gGH|Zdfx0|$^+WI$~QtN$3da}j>9B*TRg4I}< zbqFe1B1i(xE8|1zq)G>Zu5V_|VhUrA?fOftofWvtAxsz%NxAi>PU1>> z@b>)q_9|_)>6#w}ubUr#wdOC!6^4YS8dccuh+Y`u+z|yA)52u3M2i>!`eS-qnJ=Q& zIW)uIXgcvrxia^ZFBO#*7j7>)JvnB>X=sZl$FrP6x^2yJ9yl2)rcyZ}I%eRerhJ2% zb*56m05c5fQqmj;f)LcAxDhTMoD@3&%i*>JKy-Sje{@s0on!mZZ;UF5N%G2>h*lpTa}6ELjLgWo^$N8igWn??=-#KgIaiTxTZkhQtd-b0k%Zbo==L>XG`8lAn+023z8$>DvImJ^@POCqjy3tEW1Vm z-_M*VSFWI@H6;uQmV7=Ehf5JaiC)Ag2G3{a;Y1RjRE>yJ18}S9wtENk0;?JXyvBT& zpF@Q^H3*XGqSP+w#iJ>rGfofjiS&#KUUvK8vF|f>OID^!=5bdtVP7toOT!55b7?!H zuudt$WC`}O)rg2$1Oriq3{^JqS2r3V$>bMqm|F1d#tnOqSP5<`LE=ywzR+|c?L&+G z1`nW0-BT8NMcVw3dI60+eMoN(5z6G$A_9Ti*-}E%hk^lo<8Mp=kSz(IVIi?uTD+c(1ashuettjK%dFS)) zwfD`@VOXuO934t{_i1yvx>#SPk<``V(Zk+I!n;?01%_@U0j=H+v4KT+I4DAKGMNBb)lfL7)3c}2QsUX1Ou!*kbs(N zv%_W19!NmJ)f51gq9DT^>^&a|u_Fj5OVJrr#FlO{0)aJyIOv|Zu?)I^!ZdXuZx8k~ zCBS80sXv~_iG%YQhoP5{&aK8oniy?30}^0536kkfBaSMVP~aWjjjry}RJ(L~)2H?N zvmb^g2W!2gY&84GfLjfS#T9;JfmC;a#aQxD&Sr815ohX**wGhyX|)26M~4BMe8QYzhW`s}^JI7*UNeES4votrhuZZ!9_; zu#P4I6sS(qSbn^_Ofyv$FRp(Ni-Pf~niu%1ViD30w6#Kk%PRdq2|C?|V^EYUoy0N* zw`@r86@mi3odXqwcmp0vKC*($Lvwo&aaUu+l=Z6yI zc$*8iLAhx$Rh+eh>Zk|7l?4<}x4)*2vnLnZt+NY1w&LsppU-wtCSZ8z|2Zyn*KL5) z4uZ2a)`X|yysp=2mb!z%se_|~Lm_meRgYzIlx2qD07Awg86fsfS&V@Mfi-5A^Z48sRYm6>Y|`u5@RU;+|kcV=>%L5Q_eE-AErV zCC28l54zLR<4#D%Ow`*k^Z7P)rEk)xFV-A}r8J!_)De&=L}BI--aY2C_SC6z*9x6F z1~~XWgJYya65{tAm!k)oK^&K6gtI+Y zso5^X=d1fz;C2r2L(d2q(F*b9Y@Ec4bO?s`;docA-1w_v5yILwErbkcM%?en#h4Q> zH-(ZMxWS5H^sWl<*0##R(UXCahj%sBpF6!<#hLw}4V9t>lq z!7KCRh-1>BQE3TgK?kU3%?27t(=uGX=Pbk6uxEr{cA84UJCMt8h#CQy)GlIx4?>GZ zfLn!To~Ql6e%J1_>{0NL;((Xd3eg{Q!}HmVrb|%>HsJiAuEYVPn{hf$+(#c}5D0i> zT=v+DDtnkn<^uMP!#rbfXjL+A-YeaG1FhoE3=AiSmw!B611+y&BI?}&$-Fr4xDuvck) z0x02-z^7`BM19m(1U}$SjfB+y595Q>AFrE?M9BZ~yCxfSn(b{$`sSceB|Mtbq+ExI z(8VfEO-$v*p#b^%hr`FG5C84K@xLAYF(WD`J4RHt>#NN_uqg%Gx5}~Jz4{q7wj7IN ztU1RjH<|@}&9M}&G}iV9#nd-2ff4867iFkf4CWlO3%0IHu}&TzqlmxhIIA>1VC5Vd z*BO@53z@(nkrUGaF^OU<#Rd_Wqh){S2bX<@#cS;rA3a9^@=+Us%doWK&L9R1q=J%S z%45T=gNH{&&oEy|Sq#}#lttXmA#_M5r~?e4gCe}RSiIX@oqtXnraME}y>)&VcW=i` z4hm`GW;U^l4i8Udk-O00(BL3$P9L*35jNn99W?O6X4HVwCDY2S7(sT%Ptg7?Q%)*&Rzyfz_^T3zy zm09Grj#y}ij0x=H2eOm73I)on_7SJC87qj3#nw;LHoIxDZyG_^e)$^KaiJAzYT#9B zBBA3Ludd-R6OpT?c8m!LV705Bx5isM} z6M=HK2=ShSGdANOh{6SwR;dst^K}v6{G7Bm4R&jHKGb1=>Zwt{sOGTXPs4yOHVj6R zi;!WVEt*XD3ETYqJ#E76Q2^+*yKuG;n1DOAE#R*r4RSTt7Q{^0W6u|~>|h8(DpwIq z0dEihcx9UKa=pF&xLLfs+}@pk`kXkA2W8$4o~&UYFNQv-IF!Obz8Xb2!+>QPx;(o} zTO6IGeO}KAcNOQKy?BD}Tb%y@O_AgL$zv&y4Q&tnK6e7K`-*;R^l08;U|#}V34lz8 zmyiAY>r*pOhUPxd&mpn24d-x(nHGUQdz+P;Fuxq>v+7xqLHykJg=(lZ9@9r@2y_FL z@aqyldW8YMR~RIW!?OtZI{hC5uR60z%U)vFVNTKOS}5w5gSs%(Nyps^_Wwuf8BO7B zS~Rmt>!$o3Kb^yGoLYrBQw|VtTLF;<1|TS22M{sFcIs2Qa5oVhrs4^aPstHNpdm6C zL9WnTLJ7iR*81Zr$<^8B^Ou#gQ$Gs=^~yxSr5I}HG;c@x6jz!D8*zT5f7Ck-359Yv z5X;JS#n=|$H6{Dy`Z_J1TD)4C^Z1X{S3ZjwVg3CejfZ6MfgBADrYdss#}u151}d9gMj z`s*19IkW3W$cP2jvgIT z02BtkbDnAR2U}?*CYMhx7r*p(xW$(h2x1~iITHwhWUf{8$v|M8&*=7v9Cob&m2rgN z)?bUK*x+>g?dJ2wxeX{uEtte*7Z~`6S_Zfx67ffa!8aQiZNCvRAJ(3MB}(IP5X1O; z+6b-aEX4CQ2KaJ21$LH!wyM-Y(3ndIXAx3q7@1s|uPFPDQE*krFoO=i6M^KF*#ncm z_4r&${6wvSmje;X)K62qKXXv&C@j-?I6A2Gx7*dH#gD6h{u5AEndaXmcmC8DdFRj zW2XK-vQz~#gePgg=hR`IBq4QE_)fuNAfn(<2PLJz&s7dWUl7VxJ4)+%*msKB@{wQK z%G6h)g~<*gf`O8XD&Q4P7+ch&(eDK8|5Ml%ZG=PKjD#KXsi z&BD|wQVCQY;}TyKPJjrk;6^1cR^==dmsF4i6(Qm|Z0)QL0wk@&{9B@0w(P82N+DmWv>nP=r$I4`BJ`UD3kixj0yW@@jQeo?hRE03TL$2oqRQxEca82`bxc2>yVx zL&B;ol(VS~A3oh(U#7j@Hl<9m(`%Fqp(~Cus7QbpmDAW$9D1WC_=dBJ{>VW*Gy`oZ zNbt&B24~}>m09PXOMPfN3`dDDkcNsX5J0_BNtV1#iCq!Ip`I}&hG7AOTv?=Xdg2!@ zq5@eVLQc`8#k19Q5{R?&yX$N3J)Ksf8X8%k#%dmc6+mf>6!`x9O#7hRDa3Ff$4nqh zNh+7o&oEIh=R4DukQZqRXXdt8jIaP7$>uQe zq{e(g{r9!!0A5tr9)E573f!qVfWQ8~U3(lDpTftq%Uv;hcS`lkloA#eU;{j zFMhiF_;D>JR^@ijU;KpjbEAkb(wy5(9Ys6@TNQs8p0ba_c+vpN5jol=&C6ro>BUn` zbl+YE113sox}ZF%;TygwbS#rQlvaaSly(?JMAembcymtf5NPBChSTEaC3m7fj9&49 zJv4dfFQ*~8y?Pk}S^Z731OVJtbBz1bQiWPWh)*up|5&`Z`j}SS_=C3ngh85NHCz;C z$l(LEMVcxz2yeS;5uHw+Tg-WJgQ?6K4T^(D{S5}f6ghe&LJ_3-*@{Oa{uCAvx0M-O zQ<6*?8!%SL)m&vh=ynBD^$dx&Jjc8a#cu|&e_7pPg~9q7?@>!}2(a411=ORB07|4> z0N7R7Ml+tzh0-iIvMaiPLb(7ongwoWFBL`x$N+Kyw&UD%I&1tYZM?DFrb!tN2?}5B zs8CCV;xJ@2xK-PQu>tq*FA;dj^zo6S&67SNxWO%a^>>nrkp>F zAB?i0-vQ{50G<*+iGj>TV(8f0FNp~$Eea+e@XE2_a<#c$ykFgXxx49h`rtQ82vkN1 z5f*UcPHhxY`5i_vG-hWW)^Qm+h&@bC=z=^L7Qr&OS~*c7oRPEBG6EmkK(Nr1Nr9xv zhh0GAsa3)Wi6MalUul7ZSy5#jQ-=*XbM{4=Uhi`|hXkrqcXUkBNkc-enn+OOln(M( z&sm^3XL!soArJ3v8itIUo~1*yrsTJ@5YfpLHIxaS z?=CNU`QhoF2-PY7qERBfn&E)mbp0)WJhPtH|Y-d^`uvqwCSynxLa(DDa+1Rd@Gnh9IU0 z{U!XzA*V8fK$%?xDNB}9r(ZlA$4^kjuT>V%#5r^bku-#>b`{e40AtUrVp((qy9|?W zHZY7!l_tSR4D{gtx~w7NnOOv538;wm4c{wVpLeCP!Z!7@e$ zj6x7aCbYUH zyB$IRg-E)DZ(_e=2dcVLGx5CIh*-uw+(-8)G3t78PW`=6u49r@G;R$#|l{5q#n&`uDr5;mz5_UP{jE~u4Y6#}FH6WB( zpv-;wm3b2D`85{f=b|q493WBb$KturC_1`iPHf_K=H|-F36lL z$B@=Sg<2~-VK@mNsPVvGMX~ptG+C}c>SGZ#8&G8|x|8H+w&uOkzFlcK^>*>}GOf`o zw>k6!ji^*DT0yw+>KYHpgux}Xj6rZFdayFq6;K(|OjhnV#AX%(@;YXmt-dVM{;Xl| zOVmJl;c#%gh-WA{Ae>$}xkxX(_sEiUj^J}H`)RB9aR~V7yIb=86=a?qU*qSa=0JR043Yjfo}ul^$l8bS)A1 zxk}}v^?61KxtcANWzIt=r<+B$A0VK>0q9>+Cx8q>I22H7MFE#J7ZM6#Y=u$%pE>hRe3P_NB8l8xYWcm)xV$U`}O4`mc>V6Zr3{3uzb zKc2~c0rIW}aLiyqt!XUPz`GO&gF%#SeggdP(63H&>ycubar#I+iU0t|s`40*2tx4$ z0+h~)gB*77VT}XkolS5*$Ba$-MTQ2K5mQYa8Tf{nq}axdqk7V6TjD=G5|8m zS6gSmurGu_;ggKeBVwL{R~AS8xJ@0%$#}jm(v17-v{SId;|ShP@o-cDn2Ja6p14!v zVOfUro~oLIf#98*=Gc1pw2ZCQt6=~=i5kLml-D|1YSfWa62Or`c6?Ku@Pco#5lWL zufKj@{N-}{-JdJ%*X2+$s!_Kv%7R8xB=9Yj3;a}UM1-RmQCT%QqS>9SLc23Nu@?z5 za697>2AyzA@CeM{xk*pq1FtCdxB>X>p7u0x=FUfdmJ% zg5nVOc{9NX+KH)L*J;DQ&9Cd#-9MaKcxK?&s-CppTnHM`wFyq^`}_L((>Be0_9H9~1y;Qtgq*iq(4Hui-BZ*YGC?>o#c>>lt{MDN zT!{N>P z2Kw-9dsWW+1#oH?juE^RyQl^jeqMb>|7SJ2koK5dJh@6!sy5q2+RnZc^PfFA1Gz2c zXBTpg`KK<~;T8ef7N}?r`e{(2*W$_c^Is>$>C&en)-Y3s?5o-6p#PB-4>1|>FjnW3Z!nV~9UL4OGcgNtT& zS$|MC9y5-15$#5ion}3`zTMm`UT!YGY|Bw6)PGd8LU0GAug%1uaLf?87!=m3omjWT zW1l1_&NU+nEe*#xO6lZ;i{DcZix2SdgBlK!q#5Ah`5F!ZVF*U%CqZS;8G+o}j~NER zKxD=3=$VktkKr{c7GWR~5B=#FSY#t;3i8BmI!n*h zk*;GyFpf47=0oIacIK0ZPbAW155sge#Es6oC~(SQee|9mUvPB5Ym^RQ+zobH(*Zvf z-?3*8N!g`tZZy!rL`Gdl`mTD8{w@{4Fn-~2(zmyt*5CX*RJ!Dv4$d$_zW`*QM#98F zRC6{RhCvwiBpDs}VoQ7?61c5M#KW~vg{IpeC*G{m&+vo{F1YRhR=rAml01~%>Mop} z*0=uX!voA9NdI67%YRG%KEMzEmeI63<5XmxlL`_>KCR`P9gr#Br2?FLq#LZIxicxzISNt7{^iSv}x8VifFN$L5d zm^ig7;(#LOz$)NiCPG9^INNi1K=xWI2#MejJcyGD&?9@0bSa!rBEU}Y65lUjm6()_ zlQI%i;6-L3_8u`2d@_R(MkEakyv^xFb8^>CEGF3pV5zW>Kbn1vIny%jAhD+BUSTd1 zzS|75`*T=;RLuyyNG&6z9Jo_khE&+a5$d?L!$etWBsS~x3sJK_29exZ6TD$I~y?R0300n4sn`-RGf57@raC=@~8Dkri9u39IoO3btDRmb0z zn!MDN%B;E~kH4!j`-qaJw9%kWQTra$2CB z0ZyHkftCQSX`E?+S(-kQ=9gjA2h7lX=or6HZ17&i1`?4YyD%DzAJk|_;{&-pdq&Fb zI|b5t78=(nqey}UugpZ>Z0}Mx^3T^E2j4A%HdGvV+4SCcXAOfiKVWc%F^3b!5B!WG znW8mas$-XkL4YziXQe0U)E#B*tjEC+99G*lgad$q`Z_;|8Dc%kHL5XVJBx?{${<9D zE<0`Pn@&W$#vrA$34E>=YP>~FBJ>pkoiQqC`vVTW95hmVQhPR4*?m3}gOKImy8;2P z%og5n(~PpSub5Ky%|U>tY6$Sxw#No-Y6w_n=Drm4`81e1CwMinxZCU*jatsGFK zrYLaX#6wl;yJuWq3Z%G*!NQ!c%!k+s6ew|q zB8()UoB~A%>z}1lbknN9^PkqMOTYFI-&9i27^*(gxP}KZP_sfL0Yzsegcb5fi)L-- z-p(ZbLq82yw>H5$sk;V8jO-mAM^mS$e@H8GZ_^HGX)E-?WuR-PHWH^3p@RtKQ^R3e zDdxghD#Bsan3?GCMFz(ZlW8QCN|f`Q5J^xcnEVEIytqz>@uh*LyQ|;U#jqnPQp4di zBjkN}Zw&{Y|5I|E=}3f$3Wfw;;hZRUe15i#n;F2aMscWS(By#rUpcOj9P59ig5IvN zSxd4T2E12k-5UPqhqKlBb-GTDaq&7WNocPOK6~;EF2m&*XwsZxgsD5guAO9n_R0fT zlw_mgD7)0z_uj}c;OIm&IsYP$Dm*~V zwb7w!>LeJysh2qBfO9X$fG#Nr&S|i?#K1|VScpmGSPaqK+#^_n=}YB7mm!p(98efg zW@ip>R%dB6YTZ0z+SvqtpW(r!MAzJjI1P-gZY%?=#R0z+aggkbS~&}kypEi4rOPt- zKBtqpYgOUEH&iJ5ix&bO@7EtcCZ+uFX?^DhV9BgnAMoRLJmIK;V&o)zfCBgTnZ{H= zL4Hk7G$yZv5PpK z(Liup3DJ)u@Q^bQUCr|SvJ4~eRE-2d5!(p8V|ZSXz(b0JNyP{scBy4K8`+r$aW*uD z_f%>T=Pu!gs+YL3#DlZ^u){}peYB$mHABFdG;QbpI#JSsF&eEyt_H6WX@T3B7TsQS zRLmjq;^HF7L_LFvdsoy#;jjpaM_VY&LXyK##D(sO!@as@0uj0hV71%p%k}2U1AwB}0N@=t#H#1)GPG7u)^%atuc7ooU&3IW)PW!fqIe)i@@9M<+1NaJ~gr8lpSZH z2dBClq{#L$xYLMnnN4da30o-lqvLK^;)ia0X(j+&m0ojknfb+*C@a(G=^! z`*w1k!y@OAsW_A>IsoUz^LkC818AjS#CZWImJ$M2CfNUD_4_8RB6)kBwg%p;{cL>% zl(n&<1R0Rflk5O-+%O9fXqteGWk99(l4)lI10Qj=!FLD{xRafD5G>phAW&Qf#K_U~ ze!U%juiS7@1SC-B+Dcg7`#7-PnP8L~$BHcQ0tO1BV8o?|AdG-PSd10Dv*}UgniKef zaw6CWUONVakYg?-Oa#Q0^DXo2qrdnNFLSi$9#HSh!|4U-(G2>5bq$38Fm!viyGX{Y zqnu^u7H`OeJM%G*yIY3!r)CEe>6}haZ=D@RN2VneUUmR)OW@q$q^w&5DVNHLqn=K*GBGn(!$i9_0B=u zX#1KG&Nu=FZAM7rqxo=ukBC3J#~AUWAUi$BVMzb*BN0oK%SnV_@b76`T;DwcZ>JEj z?IqH35QMP>G_%qG&ue5QjlTneUExHj5h4tD@!XetfooAsX+hYkpRm}iD&v};go`bX} zY&LO}Dhnq6!!{M&H45H6JT_C%c^dYnho?&yP%#m@f^d#}Zu0XadUKoTf4;tb_|FLIz@L zmr&j){8gHIjBjSDpbp9uI)`dZKJ=G@vo$kVav}k%*~3+qBI)ADNh%o^00SU33tX8> zrekB$47NsJX(z`*y_6DR7F(MUQhVqGW54={O>6wV zc#?*fyk(#PYaH;BtP$mGeB7xGLn=?lk?wa4V=(VdRfQX(0 zw^cCvXOGZX)M5!8s*kOFtbdcC>zCIZB2QlQP06d}OI^R-f#iKsjbxxqwIn`L29%*BDTE)rJsMqL@u8q@5Br7ioMgMU|h^ks~gg z!l!C7IEzR~l&O|vHr42o9l9{U)-aew)%xc`i?Bml%(rOHHAJg0^QXKQ}?yEecnyW;lWy++-A{v%lBUx5mv6n9P^^g+hR~o-%vI)=!6^T#)UKmI#U}k^#3f z5J6SpmHWq*EJEN1Ds_CC=DA^L_^6f$q`>nv5WsL}P5z-; zf{Mq$7}J_TMDWX|*38MWc&eTg5h}_`PXsjZP^~lBV2X%bP1X~o4HXvx4hms5B>L%q zI1z8G;Fami&bfM^d#x|=TXpzD`VI?@o!}(OL=a}S3@!3ze zA3yev?|%C1ClJZzn0Qu1jZ>G`^xrV7!#CZ7JAD>P9W< zo2(p9&x@UA#Z&@SF`omF(N5sD8qK&u1ClNf08XbC9<7)Em_57xm~uk?6al{`-l+x$ zO{lcR(AWCW2qA+!8H#%kJrpi)EUf54f!iG@K*y&4@TbMi>~-24+%Ir+=F-7{fmep? zl^>MOV+4g9lVK=l5r`_oi@mhtNeDUd^CkfAkKP^4p59*jh2Q|P_MY%v1t9RCEfX9q zk% zry%A53_Mk92%!V|&!!)cia7R1@tCfRyf}JLoZU^#R71qg5-<`?EI8lzVsZV?_3AT* zCtDJHwe}teOS51NAE==)Eg@HT)Dl7kAj{V|vzh%-F`F0x1tKU4{7!J-m2u+PnzbzN zZ@1;f`S@gw1OD1}s@ph-APfy}QzC|10@)xuwqAe<8L<`v%M6QXLeW+Qqg%X&mpKc{ zRb0=0U0k!MM=7z>vkrMWrzz0=Djg%3OlCysRa4gXw;KTfV*@~P07&&-I=wC(*zGYu zn`<`UugZoHT{sNzt=|X(QKMu5L2UJZAX6m~p>z*#Xi5Z67fD;mtXEh|j;Cb7$W9Cr z56ZYgeE4cMA!bi`Ugk^nS6A$ zU8U4u63uk9q~E~A`!E=SIxiTOp+nm#8cr(NWTGKglQmPeL0Tn5icvI0NjXK{t-6oi z9DXT$2palTn(|I3ystOqQaW^Bc|sE<-W*0h>!+Zg92EGiiicPpkVxdD3B{mcI_s74 znIQ@X+B3C`E@TF~>Kau5%OA2$zSC6mcBq5bw2e2~sKfv;iUuyLqA8%2w8k)~_-y)b zQ_5aV6xlEfbqKQ=?tW(QIM^*pMr#&<>++v1%HpxMD+g*R3c;&I*J@>9oY1a=9ub9I zoG6XA^nZAdAwe)jRp1NRr|qW1@Q}0KkJAQV$)){PoQZ{JYSZnw5F`WmC)A6IU?8OGprjivE^kxi7+$4D6`D;kAPGcOqEA^>mX(BU6KWWc z%x%Yh41+QaVoH*)Gl}TyhW?5ajEW-zwjxN!^=>Xdm+ArEtjXYj2!1_UR;ve)N5~*o zQ^{a_7kk0Cw5+E+Sb4T_;sEauxxnB#D7eMbyG`1@B)PP>C7!J_7wo=J0x&8i!h|#4 z|Jx%m47C73*=hkXl<5i%7{-pGG#qF+Of-_YxSaiY@#|unj&o}R(Uy$>1kkF1Fxil+ zsgw}0F^jstZU|fA;q$@+URnR;l%W?>gS;xhK_v5gPMMsAWjS&xUvHPMv{&(Vv$~nR zO}oNwB!sV`tEbQ2173@+<{*ELuJ)WWVJ%o~Bk7N;VW6`!T`Qgy8L)E(Dk1|S4ujiu zbgnZistlmj#{;Y4IBxb@l^>d#LkBPO3?5W_e2ak<9;}%!wgAr_8cGy_C0i-J^WJ9R zG{`_#2fRwh@%@;+->$xa6zsn<51>X3&u-3ki9OGn z$f+WFbjrW+eQWU}MFE4YD2Q@Kdv(5lG9S;*ixn3{m4X>fcCI&;{_}I$e+je%#!dg_ z#p3gF`DOO=dhs8(-^-H#oYkUgl0Y9l-6@?wM&ZDvNT-3(O1kxp4>+Mry3 zVOYr3K1QtX++nmF7VkD!*C|Q*=JLsQ;gh6JP{;=n6rinw65<}bR*?WGmEI6FdXS)W z7i$G-^y`EA>rsHHL*w=Gd)kiR^?LK8IDZ?B&ssHIsy%aH1pf|qYB1Pb%85wKL14_! zWFwMlZjJ9Wtr6t}w^dGJIi<-79&%_%o^h2&%-fI=27H+;OVsA2}i7 z(ak#5S1i)>>G#eNbdTP@Mwcyf%oE-^el}>`F1L4L0D#m|oCy0N2bx805@44baawM` zAjK$rX`n$!4e^_z7|OaFv{oH$N`&Y@IowW#pTn$M#$s0suihtJ3f!wwPrR@8#jz)sH)-9IfdL>77Q)qO;8yMqYY*Ni;gEv~PA zBs6Yr%KcjWcEW@}&;YW^mgrdm>RL%UG}8S8tY&Y6lE99ma_PUX+BylVIlEXq-CWMz ztB0jBt{U*A(5=tc1j}=(2#5C8Y%$(D2%Y!e5mQ z{Q@N@Pz3@X6CEM)F>#u@@QG@?B)G-nJKSmPvF2gV-7vZ7b2o z0Wb0r!9Ctyq$3r;3MUr$OpOHos+359RatMZPJER{|MT@#s-RxX-YzcH9#>iPKmYU; zpB}vXhZu+K+?Qn`XhGYzhajt9LQ$!HmYR4m*CS;SaWq7p%?zEMoj4J7$6{z(}{`S$3c`u0qiif_-3EA04lp4mi)6!%klYhBkK@2%An{wg|JyqBhWiWmru zt!JgBnlpaWY~_3TgUWpeWUPS*YyBlM}B-3h5dXR`3Tl0w~i zsHzb>WWd|S)u%<`gUQ>buIeYyV>)cfH!SWu z5R0>7XA-xDQj}Irpeh})Lm5HP|@wA;$I0Fv-s(ImX z2r_~<*D_)_kkjo%z%KoHT$Be`l8`SqEJmo&ueuJ@NT&lXuP>LW@H#C>_;6d4pgTnH z<$6LPAOe}F5fL1Qp=={ECMl(VBNz;?Gc@>$DrFkCDm6R8D`N)B$(}FL^4eknI>x_d z1^Se&Y*~hn=8a(>dZQ^FyI7Iu++*D}n&=cp2ZvBrhjupOKjM%IualeSw1iUG&m`PM_9oIS}x@Q7%mM`40r4w^=PnyusmcadVq^ke00#bFOU^@KcqDpcQbZmIyX>SL8EBg`QR*sHPQ0X4d|v zXxWXpKXg&6rIAncoYz+HycghXBY`(=v?p|3O)^m0B}3ya;I_Sbgb#a1|b> z0{mup(KZekaRnw$qBt_(8%hQVDqz`1!tXIC?ibT)t`I*kA^J_Yon7kSNE#TtGIxPn zP&`jTXE-&-VL?qGhSFmZ)T6@!KTs_2*Sk$9o%NQ!@2wzXtkuPW2gl;HlvI0N#Ng;f zts!ARlo%mx3=ybJ1dthl10vMs;LaEj0?A}k+6%iWbp`}YP#tK70Lw4{XB-CHX}#LV37o0Lf~rDObY+~0 z*N;KKHcu7{v95ut(5WZRk5NhvDNd87&h{jy z4}FQu1HMWMuY)&pbbc&P3ME;hByw5S2vI9Z+8A%hhRGM><0ILxg?KCWCsfHjzZMTR zU`T&e2j(y;_SX{X@~H-tf&lMl!yWuN0|Cu;Qu*^X6_#CXa2n)#ySge4b@Ek0q@);c z45=a%QlyRKW?$#_`7&)w`8d@X`{~~{3XTyW-oc$Q6c9ii;Mks> z7AudX^B@?hW-xS&cTf}fxi~PqGQoJh+-yHCXX)JaZwpR;J3O@%;0P18wsUh0Rl3Zr| zD_4`T=p~E;6y}l$7Pzfg^tWpzKTJNjiO<``Kf0^1(FNrM>Ctp<@lY)scwSu!UQNcK zw_s^M717O_gTamtsZt1=_iq2WD)+uiPpMou4DjjfWtCiLD+o4~T#&2D7?f#q5^Db< z!r(rj2~90YC;!DJ91@DhYZZyVDgsr$TwVEpUA{b3%#Y^F->lZ(7q@BMW{NH@I|~rr z{5dQ@m_w=Le0f;tivegR%vtKMGQf;gP7In9oHA$A_MMJ~SXQc1pnm@DR{EalHvW?S z@Eh0&B?jj@)mZ!Jf~ESR6w2rVKT|G1W9R6Ceb2N94mrHwwFv+C`D(PR3IsHg;(*_a zGLWuK7KHBcs8x8SUZw^Y;HzrIxHTvh0o`dd)TxBGi`(_=Z5rp06m;G+eo@=Xusa2i z&74S=3WWdwU*|w5PUgO3jYB4L2n5+|Wi$}Dtx%iDw zpWJ>y@tSw4{azhZkX5?4j|HArEZAaNixDh_dE!nYqAWWQ>1a6C!)q!yjsrK)Kv5M0 z#+-+=xOeq&_F|Kw?*a@wro_PI6!!rLC>R{b-@}bUe_Z(8i7>jhlW-UavN-4Qc6nRw zYGfFoPg#xPQnYADn>*ofgm4%@#5B$ln%G{kCg(j$6myQ88EsBF+KXyZgovvvBiO&> zN7^fHxxFk-5k{kHZGpZjAfdh!&#Uf)FeoX+go4h_m~ue(+&g#Q>DPRGqW}R#t;(Vw zaG}Lcb3TGQHG7>l%`1pOb*(_K_f>K#7?CzJgaZHqBhd-K2N_ph$fM>flz|3yj-)K{ z%Dx@JYcd*PG8#Q<23&R*F+_X&RA;hCJFsgQE8vxY|U- z)CCTk`!V~|YPr7hIunp83}{xZF7|=}-x4QFu9`4V-zg#Fu>k_Pb;xu=u%PTT-3eY< z_hlMBZIt2TZag!}fhy1#{Q7D(;q9l@dU3T}fBm#t%pTnDtxI?trW77Ry=I?(-*pN2 zjdBs5o^p|AX2~dwO(^X0_)qWq%+NX2Kk2Bskzvq|9_Zp+)!E>WNN4HsgV1WmIVid= z0WVhY;VWcgjYm{5j~@kqA2kG0gbVtK9!NV)x=SK+u1BICokzesMK}$D(zSz5QT^0k zmOwa+m{~tPUTi}O-S1loF;#@{5d|pb&|xhU62OpokuHT62b!*K1-fu!KqFdl-0r}E z<~3crH;da;Oquc`pO(uaeeBDs$dSs^?K?K;w}Qa|q;q!%dX#%q^y@?-Ao#cf65}9X zC4dBeyu4knZokc*A&=~9W;{C_9{9DgBY6IJs3r&gD&*i`w#8*ca}ToxnjpaI_BUSr z(ujaU${v5jp5LDfhaW?zDun&6Jes|XmuU_jU2A---7`l{oM42swBB{0JoxR)Bc@YcqeWU@##d&o7`GmOpS64&eP=q&^ zkFGWw0Gjhh_@d(Abfv=~EY`uD8V9!aE}>s9bBxb<7)rZ=7OP%}UVaw_O$jX088TrK z+K>P_YL70=L8H$#68Nd;1vY)UUg#mg7w-~^c~>d`m5X&QmRPlwHagxe%ku#7TrC#_ zq&eq`=W7(C?=bu$D46`Wm)*|q`yD$h*x@S;i9wTAA#hxbjvtDS;Fal!N7q-|RH&Wy zu`Fr|F~T(#=&|aEy`~(E5%^^%BQSKL6ZqxmHdfQ^#fp#~G8oVxM~b)1mL-Mh10DEA z3Icvui$$^(yOKIvIc39+ShdSBfD7@FTH;B z1PXG$;w3=RoL=CDo+=nHx7m#n4nQ(ag&0k%xl1%oi)}Q|`BIt4?s7qY+e$0B8sz3p^v6!24U(1h$m1V0M5YGo-H>`<0tX!J~m z#$VkygZDdrHN?K@S3g;%W7sZ}L-mia4<1LM))W9X!x8i)I$p!UA*3%MG&x8>x+@hR zuueW=q=DNRk#44p*qHVBX|w$@`)T!$VvAWHW;ng+&Bu<5KB>=xRYJ&?#u$l z$`b;sz?W&4_(f6e=`e6)zz0-M>{Y;IAqU5Cx}$w^UKF(Pefvk?{em5I#DVaqZK~%> zi{fX`uD)YQyf+|tbW~#oHlU3)8-Ti|FL^bU4Wb9QdoiAy-r3FyD)53Bdr?nXfb7qAeUjHt?IK z8GXG-JAh%PY4$u-wH2lVzp29x{8i;+uT{`&EiH#!-4O^ zc(N_y4&eSVx!$GGT&9!i#*1vVyigJ3_y@{sN! zDX9U#;OHLhx$_g6t5FXcqd775Jga5=T@?{eH&zim9X%8bEvpri^p}3VBcompZLd&} z2ZsAcr}unXL<2s&RmeF!XfG0NjoWI$V_g*;xmNJ=%VG@`73YAD!HtZN0R$JdZNl50 zzVr-DKhT7CPyHd(CY=!q+|FKpw`>Hhb29pJ@niPlHaT>us;V&Jc$s1Wh<92oytgKY zxc9)~K$k9OM&o=!V8xJV5>j9wGZG=*!4I3J>)BPR7P-FsTqetWT{^m`I1m!8K@N5c z->!i$DS>nzkQ?ak2NsZsq`>X$;f5nd42t09KU)8qHZpm1wfIs{0`jkvKzFkKz@R{&)Z@hH_mxb<{WeJNpp^v|eDK=CoA6>$Ig!+UXNp@ zct|0@M=LjBCMV%I=wUvVXXvf*GdBtW1wswCo>2?hk!g#qz6ZJ?MUKb|W2 zwOqI16a{`%3r9#Gw!skfa0lV&&9v-Kicn5O1X|(WPpQ~2#eUZtuP-t9wTeJr+ulSQ z1@R$(f{F>Y&M4$d& zImdE5I>+*4y-a5*C6P}lpI;a2R8q=euBKR?yna^18uMciCI=gPui$Vzz~JXBp%LNK zxe%%+OcM^pC^Gvf4w&7(Uo7BwU>1XI^?Z1s$uxDdF=C=3iYXS0HMrV2&%g6+I_!8v zRlpq1k&gTL>TrBc^`o73HH3fNee(pEf#n)6n#!4X$;YkO>9n zMFbBUysW(l>C_IxY5Bl*nL^LAB!wIjd6|bG0d=Ir=&z9v#NgV`$BU2Ujsak}vVz8< zs_zRvL|b8)oRHCNg|Q2Y`}0mpeQ#DAhj83j0kS$Fc_aHkrEOlmmXoqO{mXuNACSpL zBwbd{Htu^N5se_Mkb8rCIQe0~A!Hbw6Gw~eb*S}7pUAYBFz=SOjaw!)n0~Bw1!&cz zz%Z!x>`%Bk6!wyj5flbjN!4ch{DdOMxWz77({!iO-2CkACe1zG^b)8}NAxiOX0?ux zfyKyi3=b5H&PE!1xsicT3K4Kei7*eE61M~gURj{=?CPp{F!fF{gv1OWs)6`7lObfM zj0{0BJY8+mF?~Fv`|nt^;e-Opq!tQiG=fmFbH zrDaTMecSh9856!$cOHLLjnFp}c&JK3u`EVg|Wb zD*4r5sgBA`kKDQyZs6+1A>nBeuK{SXXWAdwOTUA1uxnY1M<$T)<4l8M(mVZZ^`J%6!g*g+;4GP9EcV?7Yvl3 zz$=r^vE%Z5G>A4TMi^a{(vT{2%)s-?4C&qun>C`Mj!p`DC5L@a71L04O6hE+gnEbN z!4Fl1<)A=^cPcCgh0$ZguN{LT2QW$v3R+8L4`L?LfhMT^?w=DEvd6vS(eTY038%8c z0#pb>Ehu zggSVMQXDbT~O(Vr|kPRaRXzEBYa(W!hRZR@y z(wuAq-NuMgj`Kl zmpQOk!-3be>;^d{LV*vdP=pC++-de-Q$jasH4|Q>z7`&@5x`$nPlkXGcWN5oufGcd zK{br{`aw0ANT6*_BrbYq=iwKlNQ98@_d!DR#vvhJhFOja>fW?D+4;i}~?+Sh|yhYvd4(=bQCa3g*%YN;m0@X`C9f=}as=zxV}R zABx!KT=3Ekx1dRyOLzDi6$kJqTL^I=(W5h{Ds>!GMOr+St$wG7?d<%-(SfyQq8Ie! zI(yNB2lAx44`_;Lha%jf$yIwv`Ou|1?1fSvCao22Q4#jSExb%oKpzza={Z1PXf!XtVsKw@V%F&*?Kt&|bElRK+m$BI?soqQ6EwP~zyU2Gg|dbXc#Y`?0N-&ZjM2RS zyt5&7;DfzK-HG%`m5jJd9=~h`8?VzGV`9XU&GmILwa_X3b$IL*aale>uBP%KRsG*{ zoS=N|A8|z#(0W0kAr`ZAC~_*k`nA}ZyR9L>DAf?5(iL}VRtOhh{Bi$)?9`p&&N$a- zC% zucYkl&+jny=J|6Y-R${?k}|Zg>G~goG&&|2i0t}Pm?S%dQSOXNbn|2=&7m)S8y+w& zuJ4JYi|onsd*URk^2RqsSWDL*KNF5}ZAzYLVb`;MrW9&Y482n>o-k+J^Zn8Cnwo~f zh(i-W!1s%dk?;HXXHGaNr4fT{@&yBYQT0{-xNbDt`!qL6LX*0}8Wv~1ILHbQx>URV zL0wV5{YG#IhBnfty;I}_nBl>wzl{=q*c*>0>mOIk+0*UvAL|kj;8sg4u@Q$i0D?O; z9u^<}#!epPXs^gH%sGMYDj&EqyDpnLK1orbBv4|g!4nAq{MpwH5fJ5qzAj@S!{N=3v_gB`s!ZqJ5XuSsNQH!#!if+M zLZ>Px92WSiU?FStrhEQnzJ!y@qBO<6cLfBuGaSL)!-zH=oL8&QD^#anrP_&)pL|Tn zxp0M}&5aNTIcfldDm+FT_B12-p4Xtq$1)8c=@G@GyZ1@Vt{Wa`suL3lkYR-jeF3J` z`jfCU)rF%5KBZW&xsp0z2PeCGr8y;_6q7)Vqy8*I5EJyEsV>ynd6k@xqGTU>Zj=;Z z9?k*bNFh`>+~adTFWxHBAy`>;Fjlv6hjNv(6WvW`DSDO*fhHtK>ik%lG%qH8IS4jex^dpLKp zaBjInE++8Fk@JVLhFT<|d*%k3s0sn!%Ko)atwY)5)H-^xmwOGpQA>ez2T~xO)Ls~f zz!(PI)vwhq#1Bu3Ql=4y(!l@}Od|ScO`M=zwT(a*6``OSai@j^Vx;bapwq-e2ECm) z3_B7VC-`dTROYZ*D1qi@?;%(TLjz;)&=70cIBbGWi-JMLQ02>lUx~3$FwpJFV2Zfh z5exGx39Ug9Lk--{ScJ2Q0aMdr_6}r$U8%Jtg-Be7KnuWC(?a?WNujNu?o^f&E)sbT z2H243Sj{m11bhSp!eQdkIcdV5#_Q<7O93Gi`8hh^A*BN@MK(BoZ4jc$L<_wyS^7}o zLGBMk@T<|HCJ>!-M;sa0`ARH88X1kR$sk?20~tyT4k~tx`+^xIs4W~N+J|lUXbb(x z76q40ml_{XDDb>eLhJ^hz-K$*#hA9tYm_9kFglQ7CKrcnkcO8zW!X7w13joUC4Q?o zgi=R`gA@79_O-!qFzhw!W`rP<+q!a0Fz&SfZa>Tl2;^Nf;(j}AMcD@dcWOl` zT_3djWDo?OL+0D|wG%1MPTOa^TmHCC6+gSfOb5VT!hq-1o8;A02g*pUg&KIv{&D8M z%ebB<6l8;d@0=A$U(v_|BUo!k2SYNLWvn?wcGcX<)8)+;8{583=N)Z2dxAZE_8x=o zCRHFvQzun?Jr!J;9P}9<{W^USRa=Q{JC=xpFM7-Zp0g8Ke=b9E?sT=7FMIOgeVHEA zpbxI2sHk#M#WXcnW8|RE7oVdS*+io~*+$ciS1b1og65a6z_isuX_`sO=tU|c2#$Gz zf;?b-yI*VwJ*R_AW{Ph+JZLWx6mVPhbX>3Ra7Yh1)BP$Xf_}{2Z&zQ_5uFPVL^1Ml zF$On7gv(tv3tv-Wu>cBXICxX_)=&$gYRlLWL?p5Uw{zqdus|OK3tpKO|0|bXwi0E~ zQ#BYE;w(R9)dZS32F3_E#!PvyIGu{cJen}O;-H9Y)X@%sA|wP*fB5wF%R-7(RY>qm zt-~D(!IS}BMF9_~j7Z!Ix!@2^w&j5m~wmG z5}}_2a1a0(vF4&Mno_Y-OY}~)=i7}9Q8I8lOGekOqp?m)uvY)|raX-Z!&eIhUf))k zc&E|<5B)zuhj<#1=}BIhZs5L0Zx)|6TfdLKqXS);iVh(if_~Q$fu9N;D9F<#;%?PL z-AM$r){)>@s?Sdc(r%Z@>2jtM6t1-b$e}a{VUEK99akEF9D;$M(G-ah-6*$$hmTLW z-1&$k>>KE%b5_!!yo)biKO|4Wfq*a7AfPL45OAk95Ylz$GPujZdzcCWUux=rKNk-Q zcT^MXyI4a4-PMOuE8<0?y|0{mb>V-{d8CJ7p~l(hJdz$KpJtKPYld(2&R^je$N4=l zzY|e_&h0WSM}NqO(qE=|q)ba#&=i23Hle6cdO)F~ZLFq|9_UQGFoBEkg$iXvfwri5 zguk653U*yb9FCv5%Zxgd@F$}S;?eb*|BQeL!iZfQ;s21_6K~N1D!$HSAUH_S;wlHH zA>PMmHQj^f%WJIAxZdZ$LcAoafuIbXO$5}+2I)P7U!0{T7shbt=7oX#h(^Ti>>Gsn z5;};jqVt^jK_z5SV$j*?3O25TMw=C88h&F|h-%63?U^{plq!Atmgi?qjP z_RD6o-mc0++B)a9qfZ%*GKgrSAufV54DNF1${08DY++0Ufu)WbgDk0r=%WD`n?b~@ zAL*R7H;c<3XpX85d_@%pewP&oE+wqotI}9hBzk@zCC+-za}bcDekD>U*z0m472qgotimnRT_VTG4eTc z^n8?yqOYdAcE$Xz+wW;_1$7d(dqPVE0d&quC8`FKi+i1>S<2inK2^&lRh7tP$bvaS zpx2ec7QcTw6h*W%N9%P_3a!quta!b-U45JVT&_QGj6fH6x^;Ah zYasC3-v@|jvvRqJ*ep+pV6)i!)&hid9M~3h#p$4#k`ydgR8oNXJI%Kv1-_Q`<$#5t zGFd!IN8dPsZ#Yi$!=8Sw9C$fO;C$})NR0-^E(6Q5d>0P4k-#Sui9n29NZ|hTm`99km`32M%!u+CK>&BMjwttb z#T}6gUYT6Lsfuw99;p>2{%U^YC_Qy&OK)G+W;)Zqzd z5LK6ai$Ml69kqIt?&9IQ1X{)J3@*?BFP;C;7KhWI8qp+?2VR*xw2z>MU9Al!e%jWH z=%?a6s5)?4@rXHfw5y57*pcN1 z#lOVM9^!~N2T4fHn6zDs;91}|!UbNLvS=Pn-39=E?YY{{v8__-7mLg7;!8>mZf37m z*B{$C^@~SO(2*ghKEDSAl^x!F4^n{GWLt*cXNOnJIgHuqh-Ej##iC)v{bIV)`+oiL z{2uSqq=~qi=UgVv=)j7Q77i~rs*zDCPf|eBm4HqNnGVzeIHT3-dc1gn;(!-q7}y`6 zFaZSGCwG*>sV*y4R?-$s%%Vnr51(i%s&~teA5%);{io%vkAgcc4xwtgI1UcS3UPM< z2Zygc(=z7abhkPcVi+CDX;3sFo3KzxgN6tac*uD;uNN0-OHV+t`4Y|;<&@5-x{BM{ zpm{YrWS{|uGh>kX6cSihhJ0yzokI7-ZgKzYJ>8Olc$xwl+aw3)$x{usb z#N+Fe!#R9_KxIWd8=%3#oKB8%KGej7i0;(hq1@C!2)uZ+NK=(RuRkoeS4p*ktQs|= zS4s->s`m8e!HD4#wIP?z-vNd_lV)ur7IW(c1G?wLLzd>hPo*?|!zYJ!h76|`V25Ns`B5J0AS zwqX-Xw;CO=yFjol5wR4XJ#VE-WS85t7uH_J z#Nbv%*nfHd2!A(Unk>t{bl58sJZKhSb4xT80LV^Ohfq73Jid#gP$ZAtUYS*eN*Vt% zcir%%&2uVq#~4{1Z#AsJQS_b00w+;{CNc#18}q>Xrkt$GL+7gxmj5)Pfk_Zyuuo#H;Z-J>&+Plh(xU!(bF~t=xt31z#)VXYTOY6 za})N0VBW1aCKzTLuu^ULqncwxpA-XUAMsnoV6S2fHuVnn(Qx zC29e;b8OvRu7!sj4qUf+akIJn9CRd}Q#PO%sv{j6g13a{YbYewg0_!j%g3PTZ~2zK z=d{9z=Nu?u8j|ENv3URKcANI$PMdKT`jJQ1)N+CTs$7H+0(_~FFj9!7Z0d%Xgu|ao5GjLm(aU2w-!GVVyGtvgaNl9E>-llC!oTlt7QgK2O0!v2>Jd~XX z_G}>a^b|vBI!#6}sE!xpmWT)LC=&O0Bv`HWZj+KN=?tdS1x>X$HGz()nsCCA)I=Fl zzIAIwc~N|h4>eqSZk!^HjAYCc`bnEg{yl3Y!mX)3Dr>P;XUuY` zO!H@*@^gG!VW1+R`F0>+WPKuZgpx<48)q&IU)R@AxUIq&GvpXAMF3YOoLmpVN~!ND z6Y5Cmq!aJ0{rkSg29y;P2fOzV{G5qPeg!Z_R?>BSI-+7&Fd_VAb#wVCEIW5tv;{O& z;zJB->qY%aTKJuOA}@BCuLnIBAE-N}J>-c*aXagE67T?Xg&Sz04O10Js4Xg1)s0h zU(!j6PELFsx)T&oE4%!`j-hQeJMaTho#O8`HK#LH?dg;5Ipw680YdFlne@0i0Y7R6 zfH-OQ;`aLL*R&1WBrJko<7naZrwIz~ACMBb)6_}yYfL zz&o0P@yjwTOjz99d|H0@9v)t$K%n6@5Frr6Lp2c6XV-goc>oY%Fz}B3fe0(P@I%3Y zS0)?peI%}NUzBVY;Orz7lC>fk*#SPR&EKWFn?eH(E2oAbSlbqg@np#m259q*) zaL^!`bc8kf-5PAjyi$Uk5MP@e&#C}j%_6{@x=yB!7GXP)!0%N}TEHPjW3mHLhQQ*a zPE-zYr6;|pRv`Sp7Qe32@px}8Qv!Ll^cx%bngfiGqJuYOCnC6*z(7gi;BXGrfq0bD z?RqA_G#$}*se-@#a&h^2cCq^W#qSa6p>RZTDqcN-8_^33As~gZ$9ow479EJCL@qXdo1)OK-ibh~YZRPd^t~x`y>cu^B~~kKV=18(`E%aQL{q00Kw*f zw#6gjAyl$nW*s29f~uEBM6|~6T=Xz;JEO7h-gwD&q|NdEwz(~;wU|+;$w6F1ME0uI z5(ogs2sy@dMQKR$ZWsF;zvduFYN4E3(9ouuU2p~ZX(#Kn7ehPZdHv`$IxTEU-xG5ado+4yKnSK?KhgD@B~$jKKxE_oZOeA&J+$t zmDE=R+hjEAYur{mPQnzq>1$q@!g`sOvV2P_dwcV#m=gwfGGBm!(1pl|1rK>caH@vG zM1)4C&5ltEUd*@Xj2oDN0d~R>p}_47MNnE)RV`OH)e$Y1UuKU!T-~n!_%dZr7QXu0 zJ9tij@F68gKbwr_Rci?K2X0SCq_pKOMSzbuMA{1cB%}`Uqozai7%8(m4zNV~*wfF`^5D+d*ewY0Hm3Qp$ z&?pWeL4wzhiH8WvWE@V5T6{4Wz+o>g#Num%0AlW}`J2UUa%h&b*SD7o4+6Av4T6)1 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

V~PVLE+ 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

CkrRPJjK#AzXBbQMA1)$lZc>n+a literal 0 HcmV?d00001 From dfd199b047ec1e100b1a8ba6654535a1d68c3b2f Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Mon, 24 Jan 2022 11:51:11 +0800 Subject: [PATCH 0197/1647] fix integration test failed --- src/Storages/MergeTree/MergeTreeMetadataCache.cpp | 1 - src/Storages/MergeTree/MergeTreeMetadataCache.h | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp index 555838204ff..40e84deb259 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp @@ -27,7 +27,6 @@ std::unique_ptr MergeTreeMetadataCache::create(const Str rocksdb::DB * db; options.create_if_missing = true; - options.statistics = rocksdb::CreateDBStatistics(); auto cache = rocksdb::NewLRUCache(size); table_options.block_cache = cache; options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options)); diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.h b/src/Storages/MergeTree/MergeTreeMetadataCache.h index 8e40eaf7310..65c5eada200 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.h +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.h @@ -16,14 +16,15 @@ class MergeTreeMetadataCache public: using Status = rocksdb::Status; + static std::unique_ptr create(const String & dir, size_t size); + explicit MergeTreeMetadataCache(rocksdb::DB * rocksdb_) : rocksdb{rocksdb_} { assert(rocksdb); } - static std::unique_ptr create(const String & dir, size_t size); - MergeTreeMetadataCache(const MergeTreeMetadataCache &) = delete; + MergeTreeMetadataCache & operator=(const MergeTreeMetadataCache &) = delete; Status put(const String & key, const String & value); From 5398af7c054ef1cfcf170e7c8857a3816040dbf8 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 25 Jan 2022 18:59:29 +0800 Subject: [PATCH 0198/1647] update again --- src/Storages/MergeTree/MergeTreeMetadataCache.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp index 40e84deb259..0f67346810f 100644 --- a/src/Storages/MergeTree/MergeTreeMetadataCache.cpp +++ b/src/Storages/MergeTree/MergeTreeMetadataCache.cpp @@ -19,6 +19,7 @@ namespace ErrorCodes extern const int SYSTEM_ERROR; } + std::unique_ptr MergeTreeMetadataCache::create(const String & dir, size_t size) { assert(size != 0); From 90b74c2aebb11f22211b1b6ab8f91468ef3086fc Mon Sep 17 00:00:00 2001 From: Anton Popov Date: Thu, 27 Jan 2022 03:24:34 +0300 Subject: [PATCH 0199/1647] better unflattenTuple --- src/Columns/ColumnObject.cpp | 43 +- src/DataTypes/ObjectUtils.cpp | 396 ++++++++++-------- src/DataTypes/ObjectUtils.h | 5 +- src/DataTypes/Serializations/DataPath.h | 62 ++- .../Serializations/SerializationObject.cpp | 20 +- .../tests/gtest_json_parser.cpp | 1 - src/Functions/FunctionsConversion.h | 22 +- .../0_stateless/01825_type_json_1.reference | 2 +- .../01825_type_json_insert_select.reference | 2 +- .../01825_type_json_nbagames.reference | 2 +- .../01825_type_json_schema_race_long.sh | 2 +- 11 files changed, 326 insertions(+), 231 deletions(-) diff --git a/src/Columns/ColumnObject.cpp b/src/Columns/ColumnObject.cpp index 24686ee8ac2..94603749554 100644 --- a/src/Columns/ColumnObject.cpp +++ b/src/Columns/ColumnObject.cpp @@ -492,7 +492,7 @@ ColumnObject::ColumnObject(bool is_nullable_) ColumnObject::ColumnObject(SubcolumnsTree && subcolumns_, bool is_nullable_) : is_nullable(is_nullable_) , subcolumns(std::move(subcolumns_)) - , num_rows(subcolumns.empty() ? 0 : (*subcolumns.begin())->column.size()) + , num_rows(subcolumns.empty() ? 0 : (*subcolumns.begin())->data.size()) { checkConsistency(); @@ -505,11 +505,11 @@ void ColumnObject::checkConsistency() const for (const auto & leaf : subcolumns) { - if (num_rows != leaf->column.size()) + if (num_rows != leaf->data.size()) { throw Exception(ErrorCodes::LOGICAL_ERROR, "Sizes of subcolumns are inconsistent in ColumnObject." " Subcolumn '{}' has {} rows, but expected size is {}", - leaf->path.getPath(), leaf->column.size(), num_rows); + leaf->path.getPath(), leaf->data.size(), num_rows); } } } @@ -535,7 +535,7 @@ size_t ColumnObject::byteSize() const { size_t res = 0; for (const auto & entry : subcolumns) - res += entry->column.byteSize(); + res += entry->data.byteSize(); return res; } @@ -543,7 +543,7 @@ size_t ColumnObject::allocatedBytes() const { size_t res = 0; for (const auto & entry : subcolumns) - res += entry->column.allocatedBytes(); + res += entry->data.allocatedBytes(); return res; } @@ -553,7 +553,7 @@ void ColumnObject::forEachSubcolumn(ColumnCallback callback) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot iterate over non-finalized ColumnObject"); for (auto & entry : subcolumns) - callback(entry->column.data.back()); + callback(entry->data.data.back()); } void ColumnObject::insert(const Field & field) @@ -575,7 +575,7 @@ void ColumnObject::insert(const Field & field) for (auto & entry : subcolumns) if (!inserted.has(entry->path.getPath())) - entry->column.insertDefault(); + entry->data.insertDefault(); ++num_rows; } @@ -583,7 +583,7 @@ void ColumnObject::insert(const Field & field) void ColumnObject::insertDefault() { for (auto & entry : subcolumns) - entry->column.insertDefault(); + entry->data.insertDefault(); ++num_rows; } @@ -595,7 +595,7 @@ Field ColumnObject::operator[](size_t n) const Object object; for (const auto & entry : subcolumns) - object[entry->path.getPath()] = (*entry->column.data.back())[n]; + object[entry->path.getPath()] = (*entry->data.data.back())[n]; return object; } @@ -609,7 +609,7 @@ void ColumnObject::get(size_t n, Field & res) const for (const auto & entry : subcolumns) { auto it = object.try_emplace(entry->path.getPath()).first; - entry->column.data.back()->get(n, it->second); + entry->data.data.back()->get(n, it->second); } } @@ -620,9 +620,9 @@ void ColumnObject::insertRangeFrom(const IColumn & src, size_t start, size_t len for (auto & entry : subcolumns) { if (src_object.hasSubcolumn(entry->path)) - entry->column.insertRangeFrom(src_object.getSubcolumn(entry->path), start, length); + entry->data.insertRangeFrom(src_object.getSubcolumn(entry->path), start, length); else - entry->column.insertManyDefaults(length); + entry->data.insertManyDefaults(length); } num_rows += length; @@ -637,7 +637,7 @@ ColumnPtr ColumnObject::replicate(const Offsets & offsets) const auto res_column = ColumnObject::create(is_nullable); for (const auto & entry : subcolumns) { - auto replicated_data = entry->column.data.back()->replicate(offsets)->assumeMutable(); + auto replicated_data = entry->data.data.back()->replicate(offsets)->assumeMutable(); res_column->addSubcolumn(entry->path, std::move(replicated_data)); } @@ -647,7 +647,7 @@ ColumnPtr ColumnObject::replicate(const Offsets & offsets) const void ColumnObject::popBack(size_t length) { for (auto & entry : subcolumns) - entry->column.popBack(length); + entry->data.popBack(length); num_rows -= length; } @@ -655,7 +655,7 @@ void ColumnObject::popBack(size_t length) const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const Path & key) const { if (const auto * node = subcolumns.findLeaf(key)) - return node->column; + return node->data; throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key.getPath()); } @@ -663,7 +663,7 @@ const ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const Path & key) con ColumnObject::Subcolumn & ColumnObject::getSubcolumn(const Path & key) { if (const auto * node = subcolumns.findLeaf(key)) - return const_cast(node)->column; + return const_cast(node)->data; throw Exception(ErrorCodes::ILLEGAL_COLUMN, "There is no subcolumn {} in ColumnObject", key.getPath()); } @@ -677,6 +677,7 @@ void ColumnObject::addSubcolumn(const Path & key, MutableColumnPtr && subcolumn) { size_t new_size = subcolumn->size(); bool inserted = subcolumns.add(key, Subcolumn(std::move(subcolumn), is_nullable)); + if (!inserted) throw Exception(ErrorCodes::DUPLICATE_COLUMN, "Subcolumn '{}' already exists", key.getPath()); @@ -707,7 +708,7 @@ void ColumnObject::addNestedSubcolumn(const Path & key, const FieldInfo & field_ const auto * leaf = subcolumns.findLeaf(nested_node, [&](const auto & ) { return true; }); assert(leaf); - auto new_subcolumn = leaf->column.recreateWithDefaultValues(field_info); + auto new_subcolumn = leaf->data.recreateWithDefaultValues(field_info); if (new_subcolumn.size() > new_size) new_subcolumn.popBack(new_subcolumn.size() - new_size); else if (new_subcolumn.size() < new_size) @@ -737,7 +738,7 @@ Paths ColumnObject::getKeys() const bool ColumnObject::isFinalized() const { return std::all_of(subcolumns.begin(), subcolumns.end(), - [](const auto & entry) { return entry->column.isFinalized(); }); + [](const auto & entry) { return entry->data.isFinalized(); }); } void ColumnObject::finalize() @@ -746,12 +747,12 @@ void ColumnObject::finalize() SubcolumnsTree new_subcolumns; for (auto && entry : subcolumns) { - const auto & least_common_type = entry->column.getLeastCommonType(); + const auto & least_common_type = entry->data.getLeastCommonType(); if (isNothing(getBaseTypeOfArray(least_common_type))) continue; - entry->column.finalize(); - new_subcolumns.add(entry->path, std::move(entry->column)); + entry->data.finalize(); + new_subcolumns.add(entry->path, std::move(entry->data)); } if (new_subcolumns.empty()) diff --git a/src/DataTypes/ObjectUtils.cpp b/src/DataTypes/ObjectUtils.cpp index 516b44d3dc6..4c508cdcc86 100644 --- a/src/DataTypes/ObjectUtils.cpp +++ b/src/DataTypes/ObjectUtils.cpp @@ -76,6 +76,13 @@ ColumnPtr getBaseColumnOfArray(const ColumnPtr & column) return last_array ? last_array->getDataPtr() : column; } +DataTypePtr createArrayOfType(DataTypePtr type, size_t num_dimensions) +{ + for (size_t i = 0; i < num_dimensions; ++i) + type = std::make_shared(std::move(type)); + return type; +} + ColumnPtr createArrayOfColumn(ColumnPtr column, size_t num_dimensions) { for (size_t i = 0; i < num_dimensions; ++i) @@ -83,13 +90,6 @@ ColumnPtr createArrayOfColumn(ColumnPtr column, size_t num_dimensions) return column; } -DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension) -{ - for (size_t i = 0; i < dimension; ++i) - type = std::make_shared(std::move(type)); - return type; -} - Array createEmptyArrayField(size_t num_dimensions) { Array array; @@ -155,19 +155,16 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con "Cannot convert to tuple column '{}' from type {}. Column should be finalized first", name_type.name, name_type.type->getName()); - std::vector> tuple_elements; - tuple_elements.reserve(subcolumns_map.size()); + Paths tuple_paths; + DataTypes tuple_types; + Columns tuple_columns; + for (const auto & entry : subcolumns_map) - tuple_elements.emplace_back(entry->path, - entry->column.getLeastCommonType(), - entry->column.getFinalizedColumnPtr()); - - std::sort(tuple_elements.begin(), tuple_elements.end(), - [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs).getPath() < std::get<0>(rhs).getPath(); }); - - auto tuple_paths = extractVector<0>(tuple_elements); - auto tuple_types = extractVector<1>(tuple_elements); - auto tuple_columns = extractVector<2>(tuple_elements); + { + tuple_paths.emplace_back(entry->path); + tuple_types.emplace_back(entry->data.getLeastCommonType()); + tuple_columns.emplace_back(entry->data.getFinalizedColumnPtr()); + } auto it = storage_columns_map.find(name_type.name); if (it == storage_columns_map.end()) @@ -180,10 +177,10 @@ void convertObjectsToTuples(NamesAndTypesList & columns_list, Block & block, con auto type_tuple = std::make_shared(tuple_types, tuple_names); - getLeastCommonTypeForObject({type_tuple, it->second}, true); - - std::tie(column.type, column.column) = unflattenTuple(tuple_paths, tuple_types, tuple_columns); + std::tie(column.column, column.type) = unflattenTuple(tuple_paths, tuple_types, tuple_columns); name_type.type = column.type; + + getLeastCommonTypeForObject({column.type, it->second}, true); } } } @@ -237,7 +234,9 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types, bool check_ambi subcolumns_types[tuple_paths[i]].push_back(tuple_types[i]); } - std::vector> tuple_elements; + Paths tuple_paths; + DataTypes tuple_types; + for (const auto & [key, subtypes] : subcolumns_types) { assert(!subtypes.empty()); @@ -251,17 +250,15 @@ DataTypePtr getLeastCommonTypeForObject(const DataTypes & types, bool check_ambi "Uncompatible types of subcolumn '{}': {} and {}", key.getPath(), subtypes[0]->getName(), subtypes[i]->getName()); - tuple_elements.emplace_back(key, getLeastSupertype(subtypes, /*allow_conversion_to_string=*/ true)); + tuple_paths.emplace_back(key); + tuple_types.emplace_back(getLeastSupertype(subtypes, /*allow_conversion_to_string=*/ true)); } - if (tuple_elements.empty()) - tuple_elements.emplace_back(Path(ColumnObject::COLUMN_NAME_DUMMY), std::make_shared()); - - std::sort(tuple_elements.begin(), tuple_elements.end(), - [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs).getPath() < std::get<0>(rhs).getPath(); }); - - auto tuple_paths = extractVector<0>(tuple_elements); - auto tuple_types = extractVector<1>(tuple_elements); + if (tuple_paths.empty()) + { + tuple_paths.emplace_back(ColumnObject::COLUMN_NAME_DUMMY); + tuple_types.emplace_back(std::make_shared()); + } if (check_ambiguos_paths) checkObjectHasNoAmbiguosPaths(tuple_paths); @@ -300,76 +297,6 @@ void extendObjectColumns(NamesAndTypesList & columns_list, const ColumnsDescript namespace { -struct SubcolumnsHolder -{ - SubcolumnsHolder( - const std::vector & paths_, - const DataTypes & types_, - const Columns & columns_) - : paths(paths_) - , types(types_) - , columns(columns_) - { - parts.reserve(paths.size()); - levels.reserve(paths.size()); - - for (const auto & path : paths) - { - parts.emplace_back(path.getParts()); - - size_t num_parts = path.getNumParts(); - - levels.emplace_back(); - levels.back().resize(num_parts); - - for (size_t i = 1; i < num_parts; ++i) - levels.back()[i] = levels.back()[i - 1] + path.isNested(i - 1); - } - } - - std::pair reduceNumberOfDimensions(size_t path_idx, size_t key_idx) const - { - auto type = types[path_idx]; - auto column = columns[path_idx]; - - if (!isArray(type)) - return {type, column}; - - size_t type_dimensions = getNumberOfDimensions(*type); - size_t column_dimensions = getNumberOfDimensions(*column); - - if (levels[path_idx][key_idx] > type_dimensions) - throw Exception(ErrorCodes::LOGICAL_ERROR, - "Level of nested ({}) cannot be greater than number of dimensions in array ({})", - levels[path_idx][key_idx], type_dimensions); - - if (type_dimensions != column_dimensions) - throw Exception(ErrorCodes::LOGICAL_ERROR, - "Number of dimensionsin type ({}) is incompatible with number of dimension in column ({})", - type_dimensions, column_dimensions); - - size_t dimensions_to_reduce = levels[path_idx][key_idx]; - while (dimensions_to_reduce--) - { - const auto & type_array = assert_cast(*type); - const auto & column_array = assert_cast(*column); - - type = type_array.getNestedType(); - column = column_array.getDataPtr(); - } - - return {type, column}; - } - - std::vector paths; - DataTypes types; - Columns columns; - - std::vector parts; - std::vector> levels; -}; - - void flattenTupleImpl(Path path, DataTypePtr type, size_t array_level, Paths & new_paths, DataTypes & new_types) { bool is_nested = isNested(type); @@ -399,63 +326,138 @@ void flattenTupleImpl(Path path, DataTypePtr type, size_t array_level, Paths & n } } -void unflattenTupleImpl( - const SubcolumnsHolder & holder, - size_t from, size_t to, size_t depth, - Names & new_names, DataTypes & new_types, Columns & new_columns) +void flattenTupleImpl(const ColumnPtr & column, Columns & new_columns, Columns & offsets_columns) { - size_t start = from; - for (size_t i = from + 1; i <= to; ++i) + if (const auto * column_tuple = checkAndGetColumn(column.get())) { - if (i < to && holder.parts[i].size() <= depth) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Cannot unflatten Tuple. Not enough name parts in path"); - - if (i == to || holder.parts[i][depth] != holder.parts[start][depth]) + const auto & subcolumns = column_tuple->getColumns(); + for (const auto & subcolumn : subcolumns) + flattenTupleImpl(subcolumn, new_columns,offsets_columns ); + } + else if (const auto * column_array = checkAndGetColumn(column.get())) + { + offsets_columns.push_back(column_array->getOffsetsPtr()); + flattenTupleImpl(column_array->getDataPtr(), new_columns, offsets_columns); + offsets_columns.pop_back(); + } + else + { + if (!offsets_columns.empty()) { - if (i - start > 1) - { - Names tuple_names; - DataTypes tuple_types; - Columns tuple_columns; + auto new_column = ColumnArray::create(column, offsets_columns.back()); + for (ssize_t i = static_cast(offsets_columns.size()) - 2; i >= 0; --i) + new_column = ColumnArray::create(new_column, offsets_columns[i]); - unflattenTupleImpl(holder, start, i, depth + 1, tuple_names, tuple_types, tuple_columns); - - assert(!tuple_names.empty()); - assert(tuple_names.size() == tuple_types.size()); - assert(tuple_names.size() == tuple_columns.size()); - - new_names.push_back(holder.parts[start][depth]); - - if (holder.paths[start].isNested(depth)) - { - auto array_column = holder.reduceNumberOfDimensions(start, depth).second; - auto offsets_column = assert_cast(*array_column).getOffsetsPtr(); - - new_types.push_back(createNested(tuple_types, tuple_names)); - new_columns.push_back(ColumnArray::create(ColumnTuple::create(std::move(tuple_columns)), offsets_column)); - } - else - { - new_types.push_back(std::make_shared(tuple_types, tuple_names)); - new_columns.push_back(ColumnTuple::create(std::move(tuple_columns))); - } - } - else - { - WriteBufferFromOwnString wb; - wb << holder.parts[start][depth]; - for (size_t j = depth + 1; j < holder.parts[start].size(); ++j) - wb << "." << holder.parts[start][j]; - - auto [new_type, new_column] = holder.reduceNumberOfDimensions(start, depth); - - new_names.push_back(wb.str()); - new_types.push_back(std::move(new_type)); - new_columns.push_back(std::move(new_column)); - } - - start = i; + new_columns.push_back(std::move(new_column)); } + else + new_columns.push_back(column); + } +} + +DataTypePtr reduceNumberOfDimensions(DataTypePtr type, size_t dimensions_to_reduce) +{ + while (dimensions_to_reduce--) + { + const auto * type_array = typeid_cast(type.get()); + if (!type_array) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Not enough dimensions to reduce"); + + type = type_array->getNestedType(); + } + + return type; +} + +ColumnPtr reduceNumberOfDimensions(ColumnPtr column, size_t dimensions_to_reduce) +{ + while (dimensions_to_reduce--) + { + const auto * column_array = typeid_cast(column.get()); + if (!column_array) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Not enough dimensions to reduce"); + + column = column_array->getDataPtr(); + } + + return column; +} + +struct ColumnWithTypeAndDimensions +{ + ColumnPtr column; + DataTypePtr type; + size_t array_dimensions; +}; + +using SubcolumnsTreeWithTypes = SubcolumnsTree; +using Node = SubcolumnsTreeWithTypes::Node; +using Leaf = SubcolumnsTreeWithTypes::Leaf; + +std::pair createTypeFromNode(const Node * node) +{ + auto collect_tuple_elemets = [](const auto & children) + { + std::vector> tuple_elements; + tuple_elements.reserve(children.size()); + for (const auto & [name, child] : children) + { + auto [column, type] = createTypeFromNode(child.get()); + tuple_elements.emplace_back(name, std::move(column), std::move(type)); + } + + std::sort(tuple_elements.begin(), tuple_elements.end(), + [](const auto & lhs, const auto & rhs) { return std::get<0>(lhs) < std::get<0>(rhs); }); + + auto tuple_names = extractVector<0>(tuple_elements); + auto tuple_columns = extractVector<1>(tuple_elements); + auto tuple_types = extractVector<2>(tuple_elements); + + return std::make_tuple(tuple_names, tuple_columns, tuple_types); + }; + + if (node->kind == Node::SCALAR) + { + const auto * leaf = typeid_cast(node); + return {leaf->data.column, leaf->data.type}; + } + else if (node->kind == Node::NESTED) + { + Columns offsets_columns; + ColumnPtr current_column = node->data.column; + + assert(node->data.array_dimensions > 0); + offsets_columns.reserve(node->data.array_dimensions); + + for (size_t i = 0; i < node->data.array_dimensions; ++i) + { + const auto & column_array = assert_cast(*current_column); + + offsets_columns.push_back(column_array.getOffsetsPtr()); + current_column = column_array.getDataPtr(); + } + + auto [tuple_names, tuple_columns, tuple_types] = collect_tuple_elemets(node->children); + + auto result_column = ColumnArray::create(ColumnTuple::create(tuple_columns), offsets_columns.back()); + auto result_type = createNested(tuple_types, tuple_names); + + for (ssize_t i = static_cast(offsets_columns.size()) - 2; i >= 0; --i) + { + result_column = ColumnArray::create(result_column, offsets_columns[i]); + result_type = std::make_shared(result_type); + } + + return {result_column, result_type}; + } + else + { + auto [tuple_names, tuple_columns, tuple_types] = collect_tuple_elemets(node->children); + + auto result_column = ColumnTuple::create(tuple_columns); + auto result_type = std::make_shared(tuple_types, tuple_names); + + return {result_column, result_type}; } } @@ -470,17 +472,27 @@ std::pair flattenTuple(const DataTypePtr & type) return {new_paths, new_types}; } +ColumnPtr flattenTuple(const ColumnPtr & column) +{ + Columns new_columns; + Columns offsets_columns; + + flattenTupleImpl(column, new_columns, offsets_columns); + return ColumnTuple::create(new_columns); +} + DataTypePtr unflattenTuple(const Paths & paths, const DataTypes & tuple_types) { assert(paths.size() == types.size()); - Columns tuple_columns(tuple_types.size()); - for (size_t i = 0; i < tuple_types.size(); ++i) - tuple_columns[i] = tuple_types[i]->createColumn(); + Columns tuple_columns; + tuple_columns.reserve(tuple_types.size()); + for (const auto & type : tuple_types) + tuple_columns.emplace_back(type->createColumn()); - return unflattenTuple(paths, tuple_types, tuple_columns).first; + return unflattenTuple(paths, tuple_types, tuple_columns).second; } -std::pair unflattenTuple( +std::pair unflattenTuple( const Paths & paths, const DataTypes & tuple_types, const Columns & tuple_columns) @@ -488,18 +500,64 @@ std::pair unflattenTuple( assert(paths.size() == types.size()); assert(paths.size() == columns.size()); - Names new_names; - DataTypes new_types; - Columns new_columns; - SubcolumnsHolder holder(paths, tuple_types, tuple_columns); + SubcolumnsTreeWithTypes tree; - unflattenTupleImpl(holder, 0, paths.size(), 0, new_names, new_types, new_columns); - - return + for (size_t i = 0; i < paths.size(); ++i) { - std::make_shared(new_types, new_names), - ColumnTuple::create(new_columns) - }; + auto column = tuple_columns[i]; + auto type = tuple_types[i]; + + size_t num_parts = paths[i].getNumParts(); + size_t nested_level = paths[i].getIsNestedBitSet().count(); + size_t array_level = getNumberOfDimensions(*type); + + if (array_level < nested_level) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Number of dimensions ({}) is less than number Nested types ({}) for path {}", + array_level, nested_level, paths[i].getPath()); + + size_t pos = 0; + tree.add(paths[i], [&](Node::Kind kind, bool exists) -> std::shared_ptr + { + if (pos >= num_parts) + throw Exception(ErrorCodes::LOGICAL_ERROR, + "Not enough name parts for path {}. Expected at least {}, got {}", + paths[i].getPath(), pos + 1, num_parts); + + ColumnWithTypeAndDimensions current_column; + if (kind == Node::NESTED) + { + size_t dimensions_to_reduce = array_level - nested_level; + assert(is_nested.test(pos)); + + ++dimensions_to_reduce; + --nested_level; + + current_column = ColumnWithTypeAndDimensions{column, type, dimensions_to_reduce}; + + if (dimensions_to_reduce) + { + type = reduceNumberOfDimensions(type, dimensions_to_reduce); + column = reduceNumberOfDimensions(column, dimensions_to_reduce); + } + + array_level -= dimensions_to_reduce; + } + else + current_column = ColumnWithTypeAndDimensions{column, type, 0}; + + ++pos; + + if (exists) + return nullptr; + + return kind == Node::SCALAR + ? std::make_shared(paths[i], current_column) + : std::make_shared(kind, current_column); + }); + } + + return createTypeFromNode(tree.getRoot()); } static void addConstantToWithClause(const ASTPtr & query, const String & column_name, const DataTypePtr & data_type) diff --git a/src/DataTypes/ObjectUtils.h b/src/DataTypes/ObjectUtils.h index 4f13882f4b8..cc1a5070761 100644 --- a/src/DataTypes/ObjectUtils.h +++ b/src/DataTypes/ObjectUtils.h @@ -13,7 +13,7 @@ namespace DB size_t getNumberOfDimensions(const IDataType & type); size_t getNumberOfDimensions(const IColumn & column); DataTypePtr getBaseTypeOfArray(const DataTypePtr & type); -DataTypePtr createArrayOfType(DataTypePtr type, size_t dimension); +DataTypePtr createArrayOfType(DataTypePtr type, size_t num_dimensions); Array createEmptyArrayField(size_t num_dimensions); ColumnPtr getBaseColumnOfArray(const ColumnPtr & column); @@ -29,12 +29,13 @@ void extendObjectColumns(NamesAndTypesList & columns_list, const ColumnsDescript using DataTypeTuplePtr = std::shared_ptr; std::pair flattenTuple(const DataTypePtr & type); +ColumnPtr flattenTuple(const ColumnPtr & column); DataTypePtr unflattenTuple( const Paths & paths, const DataTypes & tuple_types); -std::pair unflattenTuple( +std::pair unflattenTuple( const Paths & paths, const DataTypes & tuple_types, const Columns & tuple_columns); diff --git a/src/DataTypes/Serializations/DataPath.h b/src/DataTypes/Serializations/DataPath.h index 446419760cf..319eed817c9 100644 --- a/src/DataTypes/Serializations/DataPath.h +++ b/src/DataTypes/Serializations/DataPath.h @@ -51,7 +51,9 @@ private: using Paths = std::vector; -template +struct EmptyNodeData {}; + +template class SubcolumnsTree { public: @@ -64,19 +66,21 @@ public: SCALAR, }; + explicit Node(Kind kind_) : kind(kind_) {} + Node(Kind kind_, const NodeData & data_) : kind(kind_), data(data_) {} + Kind kind = TUPLE; const Node * parent = nullptr; + std::unordered_map> children; + NodeData data; bool isNested() const { return kind == NESTED; } - std::shared_ptr addChild(const String & key_, Kind kind_) + void addChild(const String & key_, std::shared_ptr next_node) { - auto next_node = kind_ == SCALAR ? std::make_shared() : std::make_shared(); - next_node->kind = kind_; next_node->parent = this; - children[key_] = next_node; - return next_node; + children[key_] = std::move(next_node); } virtual ~Node() = default; @@ -84,14 +88,36 @@ public: struct Leaf : public Node { + Leaf(const Path & path_, const LeafData & data_) + : Node(Node::SCALAR), path(path_), data(data_) + { + } + Path path; - ColumnHolder column; + LeafData data; }; + using NodeKind = typename Node::Kind; using NodePtr = std::shared_ptr; using LeafPtr = std::shared_ptr; - bool add(const Path & path, const ColumnHolder & column) + bool add(const Path & path, const LeafData & leaf_data) + { + return add(path, [&](NodeKind kind, bool exists) -> NodePtr + { + if (exists) + return nullptr; + + if (kind == Node::SCALAR) + return std::make_shared(path, leaf_data); + + return std::make_shared(kind); + }); + } + + using NodeCreator = std::function; + + bool add(const Path & path, const NodeCreator & node_creator) { auto parts = path.getParts(); auto is_nested = path.getIsNestedBitSet(); @@ -100,10 +126,7 @@ public: return false; if (!root) - { - root = std::make_shared(); - root->kind = Node::TUPLE; - } + root = std::make_shared(Node::TUPLE); Node * current_node = root.get(); for (size_t i = 0; i < parts.size() - 1; ++i) @@ -114,6 +137,7 @@ public: if (it != current_node->children.end()) { current_node = it->second.get(); + node_creator(current_node->kind, true); bool current_node_is_nested = current_node->kind == Node::NESTED; if (current_node_is_nested != is_nested.test(i)) @@ -122,7 +146,9 @@ public: else { auto next_kind = is_nested.test(i) ? Node::NESTED : Node::TUPLE; - current_node = current_node->addChild(parts[i], next_kind).get(); + auto next_node = node_creator(next_kind, false); + current_node->addChild(parts[i], next_node); + current_node = next_node.get(); } } @@ -130,12 +156,11 @@ public: if (it != current_node->children.end()) return false; - auto node = current_node->addChild(parts.back(), Node::SCALAR); - auto leaf = std::dynamic_pointer_cast(node); - assert(leaf); + auto next_node = node_creator(Node::SCALAR, false); + current_node->addChild(parts.back(), next_node); - leaf->path = path; - leaf->column = column; + auto leaf = std::dynamic_pointer_cast(next_node); + assert(leaf); leaves.push_back(std::move(leaf)); return true; @@ -189,6 +214,7 @@ public: using Leaves = std::vector; const Leaves & getLeaves() const { return leaves; } + const Node * getRoot() const { return root.get(); } using iterator = typename Leaves::iterator; using const_iterator = typename Leaves::const_iterator; diff --git a/src/DataTypes/Serializations/SerializationObject.cpp b/src/DataTypes/Serializations/SerializationObject.cpp index aa8fc459aa1..4f35bbd284e 100644 --- a/src/DataTypes/Serializations/SerializationObject.cpp +++ b/src/DataTypes/Serializations/SerializationObject.cpp @@ -82,7 +82,7 @@ bool tryInsertDefaultFromNested( leaf = subcolumns.findLeaf(node_nested, [&](const auto & candidate) { - return candidate.column.size() == entry->column.size() + 1; + return candidate.data.size() == entry->data.size() + 1; }); if (leaf) @@ -95,11 +95,11 @@ bool tryInsertDefaultFromNested( if (!leaf) return false; - auto last_field = leaf->column.getLastField(); + auto last_field = leaf->data.getLastField(); if (last_field.isNull()) return false; - const auto & least_common_type = entry->column.getLeastCommonType(); + const auto & least_common_type = entry->data.getLeastCommonType(); size_t num_dimensions = getNumberOfDimensions(*least_common_type); assert(num_skipped_nested < num_dimensions); @@ -109,7 +109,7 @@ bool tryInsertDefaultFromNested( : getBaseTypeOfArray(least_common_type)->getDefault(); auto default_field = applyVisitor(FieldVisitorReplaceScalars(default_scalar, num_dimensions_to_keep), last_field); - entry->column.insert(std::move(default_field)); + entry->data.insert(std::move(default_field)); return true; } @@ -166,7 +166,7 @@ void SerializationObject::deserializeTextImpl(IColumn & column, Reader & { bool inserted = tryInsertDefaultFromNested(entry, subcolumns); if (!inserted) - entry->column.insertDefault(); + entry->data.insertDefault(); } } @@ -264,7 +264,7 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( settings.path.back() = Substream::ObjectStructure; settings.path.back().object_key_name = entry->path.getPath(); - const auto & type = entry->column.getLeastCommonType(); + const auto & type = entry->data.getLeastCommonType(); if (auto * stream = settings.getter(settings.path)) { entry->path.writeBinary(*stream); @@ -276,7 +276,7 @@ void SerializationObject::serializeBinaryBulkWithMultipleStreams( { auto serialization = type->getDefaultSerialization(); serialization->serializeBinaryBulkWithMultipleStreams( - entry->column.getFinalizedColumn(), offset, limit, settings, state); + entry->data.getFinalizedColumn(), offset, limit, settings, state); } } @@ -387,8 +387,8 @@ void SerializationObject::serializeTextImpl(const IColumn & column, size writeDoubleQuoted((*it)->path.getPath(), ostr); writeChar(':', ostr); - auto serialization = (*it)->column.getLeastCommonType()->getDefaultSerialization(); - serialization->serializeTextJSON((*it)->column.getFinalizedColumn(), row_num, ostr, settings); + auto serialization = (*it)->data.getLeastCommonType()->getDefaultSerialization(); + serialization->serializeTextJSON((*it)->data.getFinalizedColumn(), row_num, ostr, settings); } writeChar('}', ostr); } @@ -439,7 +439,7 @@ SerializationPtr getObjectSerialization(const String & schema_format) return std::make_shared>>(); #else throw Exception(ErrorCodes::NOT_IMPLEMENTED, - "To use data type Object with JSON format, ClickHouse should be built with Simdjson or Rapidjson"); + "To use data type Object with JSON format ClickHouse should be built with Simdjson or Rapidjson"); #endif } diff --git a/src/DataTypes/Serializations/tests/gtest_json_parser.cpp b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp index c14b1fa1f72..8b11684da7a 100644 --- a/src/DataTypes/Serializations/tests/gtest_json_parser.cpp +++ b/src/DataTypes/Serializations/tests/gtest_json_parser.cpp @@ -10,7 +10,6 @@ using namespace DB; const String json1 = R"({"k1" : 1, "k2" : {"k3" : "aa", "k4" : 2}})"; - /// Nested(k2 String, k3 Nested(k4 String)) const String json2 = R"({"k1" : [ diff --git a/src/Functions/FunctionsConversion.h b/src/Functions/FunctionsConversion.h index a1df0bc2643..a8dda5e5eba 100644 --- a/src/Functions/FunctionsConversion.h +++ b/src/Functions/FunctionsConversion.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -3076,13 +3078,15 @@ private: throw Exception(ErrorCodes::TYPE_MISMATCH, "Cast to Object can be performed only from flatten Named Tuple. Got: {}", from_type->getName()); - const auto & names = from_tuple->getElementNames(); - const auto & from_types = from_tuple->getElements(); + Paths paths; + DataTypes from_types; + + std::tie(paths, from_types) = flattenTuple(from_type); auto to_types = from_types; for (auto & type : to_types) { - if (checkAndGetDataType(type.get())) + if (isTuple(type) || isNested(type)) throw Exception(ErrorCodes::TYPE_MISMATCH, "Cast to Object can be performed only from flatten Named Tuple. Got: {}", from_type->getName()); @@ -3090,18 +3094,24 @@ private: } return [element_wrappers = getElementWrappers(from_types, to_types), - has_nullable_subcolumns = to_type->hasNullableSubcolumns(), from_types, to_types, names] + has_nullable_subcolumns = to_type->hasNullableSubcolumns(), from_types, to_types, paths] (ColumnsWithTypeAndName & arguments, const DataTypePtr &, const ColumnNullable * nullable_source, size_t input_rows_count) { size_t tuple_size = to_types.size(); - const ColumnTuple & column_tuple = assert_cast(*arguments.front().column); + auto flattened_column = flattenTuple(arguments.front().column); + const auto & column_tuple = assert_cast(*flattened_column); + + if (tuple_size != column_tuple.getColumns().size()) + throw Exception(ErrorCodes::TYPE_MISMATCH, + "Expected tuple with {} subcolumn, but got {} subcolumns", + tuple_size, column_tuple.getColumns().size()); auto res = ColumnObject::create(has_nullable_subcolumns); for (size_t i = 0; i < tuple_size; ++i) { ColumnsWithTypeAndName element = {{column_tuple.getColumns()[i], from_types[i], "" }}; auto converted_column = element_wrappers[i](element, to_types[i], nullable_source, input_rows_count); - res->addSubcolumn(Path(names[i]), converted_column->assumeMutable()); + res->addSubcolumn(paths[i], converted_column->assumeMutable()); } return res; diff --git a/tests/queries/0_stateless/01825_type_json_1.reference b/tests/queries/0_stateless/01825_type_json_1.reference index 59fad00bfae..857c624fb9b 100644 --- a/tests/queries/0_stateless/01825_type_json_1.reference +++ b/tests/queries/0_stateless/01825_type_json_1.reference @@ -6,7 +6,7 @@ all_2_2_0 data Tuple(k5 String) all_1_2_1 data Tuple(k1 String, k2 Tuple(k3 String, k4 String), k5 String) ============ 1 ['aaa','ddd'] [['bbb','ccc'],['eee','fff']] -all_3_3_0 data Tuple(k1 Nested(k2 String, `k3.k4` Array(String))) +all_3_3_0 data Tuple(k1 Nested(k2 String, k3 Nested(k4 String))) ============ 1 a 42 2 b 4200 diff --git a/tests/queries/0_stateless/01825_type_json_insert_select.reference b/tests/queries/0_stateless/01825_type_json_insert_select.reference index ebf4c1ee736..8283cc5af48 100644 --- a/tests/queries/0_stateless/01825_type_json_insert_select.reference +++ b/tests/queries/0_stateless/01825_type_json_insert_select.reference @@ -9,4 +9,4 @@ Tuple(arr Nested(k11 Int8, k22 String, k33 Int8), k1 Int8, k2 String, k3 String) 2 ([],2,'bar','') 3 ([],3,'','aaa') 4 ([(5,'6',0),(7,'0',8)],0,'','') -5 ([],0,'','') +5 ([(0,'str1',0)],0,'','') diff --git a/tests/queries/0_stateless/01825_type_json_nbagames.reference b/tests/queries/0_stateless/01825_type_json_nbagames.reference index 467d74b5155..b0112897ebf 100644 --- a/tests/queries/0_stateless/01825_type_json_nbagames.reference +++ b/tests/queries/0_stateless/01825_type_json_nbagames.reference @@ -1,5 +1,5 @@ 1000 -data Tuple(`_id.$oid` String, `date.$date` String, teams Nested(abbreviation String, city String, home UInt64, name String, players Nested(ast Int8, blk Int8, drb Int8, fg Int8, fg3 Int8, fg3_pct String, fg3a Int8, fg_pct String, fga Int8, ft Int8, ft_pct String, fta Int8, mp String, orb Int8, pf Int8, player String, pts Int8, stl Int8, tov Int8, trb Int8), results Tuple(ast Int8, blk Int8, drb Int8, fg Int8, fg3 Int8, fg3_pct String, fg3a Int8, fg_pct String, fga Int8, ft Int8, ft_pct String, fta Int8, mp Int16, orb Int8, pf Int8, pts Int16, stl Int8, tov Int8, trb Int8), score Int16, won Int8)) +data Tuple(_id Tuple(`$oid` String), date Tuple(`$date` String), teams Nested(abbreviation String, city String, home UInt64, name String, players Nested(ast Int8, blk Int8, drb Int8, fg Int8, fg3 Int8, fg3_pct String, fg3a Int8, fg_pct String, fga Int8, ft Int8, ft_pct String, fta Int8, mp String, orb Int8, pf Int8, player String, pts Int8, stl Int8, tov Int8, trb Int8), results Tuple(ast Int8, blk Int8, drb Int8, fg Int8, fg3 Int8, fg3_pct String, fg3a Int8, fg_pct String, fga Int8, ft Int8, ft_pct String, fta Int8, mp Int16, orb Int8, pf Int8, pts Int16, stl Int8, tov Int8, trb Int8), score Int16, won Int8)) Boston Celtics 70 Los Angeles Lakers 64 Milwaukee Bucks 61 diff --git a/tests/queries/0_stateless/01825_type_json_schema_race_long.sh b/tests/queries/0_stateless/01825_type_json_schema_race_long.sh index b0e2ec95be2..a119dd17ead 100755 --- a/tests/queries/0_stateless/01825_type_json_schema_race_long.sh +++ b/tests/queries/0_stateless/01825_type_json_schema_race_long.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Tags: no-fasttest +# Tags: no-fasttest, long set -e From f3f414ae75885db8b8e1936cc38be77eeb75a95b Mon Sep 17 00:00:00 2001 From: Kruglov Pavel <48961922+Avogar@users.noreply.github.com> Date: Thu, 27 Jan 2022 13:59:39 +0300 Subject: [PATCH 0200/1647] Fix style --- tests/clickhouse-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/clickhouse-test b/tests/clickhouse-test index e8345c01a42..fe8350f7b16 100755 --- a/tests/clickhouse-test +++ b/tests/clickhouse-test @@ -67,7 +67,7 @@ def stringhash(s): # only during process invocation https://stackoverflow.com/a/42089311 return zlib.crc32(s.encode('utf-8')) - + class HTTPError(Exception): def __init__(self, message=None, code=None): self.message = message From e813f6413f12887d0a7a4fec1b5455379073bbe4 Mon Sep 17 00:00:00 2001 From: Pablo Alegre Date: Wed, 29 Dec 2021 16:56:58 +0100 Subject: [PATCH 0201/1647] Add groupSortedArray() function --- .../reference/groupsortedarray.md | 48 ++++ .../aggregate-functions/reference/index.md | 1 + .../AggregateFunctionGroupSortedArray.cpp | 163 +++++++++++++ .../AggregateFunctionGroupSortedArray.h | 229 ++++++++++++++++++ .../AggregateFunctionGroupSortedArrayData.h | 162 +++++++++++++ .../registerAggregateFunctions.cpp | 2 + tests/performance/group_sorted_array.xml | 25 ++ .../02158_groupsortedarray.reference | 14 ++ .../0_stateless/02158_groupsortedarray.sql | 38 +++ 9 files changed, 682 insertions(+) create mode 100644 docs/en/sql-reference/aggregate-functions/reference/groupsortedarray.md create mode 100644 src/AggregateFunctions/AggregateFunctionGroupSortedArray.cpp create mode 100644 src/AggregateFunctions/AggregateFunctionGroupSortedArray.h create mode 100644 src/AggregateFunctions/AggregateFunctionGroupSortedArrayData.h create mode 100644 tests/performance/group_sorted_array.xml create mode 100644 tests/queries/0_stateless/02158_groupsortedarray.reference create mode 100644 tests/queries/0_stateless/02158_groupsortedarray.sql diff --git a/docs/en/sql-reference/aggregate-functions/reference/groupsortedarray.md b/docs/en/sql-reference/aggregate-functions/reference/groupsortedarray.md new file mode 100644 index 00000000000..06c004173b8 --- /dev/null +++ b/docs/en/sql-reference/aggregate-functions/reference/groupsortedarray.md @@ -0,0 +1,48 @@ +--- +toc_priority: 108 +--- + +# groupSortedArray {#groupSortedArray} + +Returns an array with the first N items in ascending order. + +``` sql +groupSortedArray(N)(column) +``` + +**Arguments** + +- `N` – The number of elements to return. + +If the parameter is omitted, default value 10 is used. + +**Arguments** + +- `x` – The value. +- `expr` — Optional. The field or expresion to sort by. If not set values are sorted by themselves. [Integer](../../../sql-reference/data-types/int-uint.md). + +**Example** + +Gets the first 10 numbers: + +``` sql +SELECT groupSortedArray(10)(number) FROM numbers(100) +``` + +``` text +┌─groupSortedArray(10)(number)─┐ +│ [0,1,2,3,4,5,6,7,8,9] │ +└──────────────────────────────┘ +``` + +Or the last 10: + +``` sql +SELECT groupSortedArray(10)(number, -number) FROM numbers(100) +``` + +``` text +┌─groupSortedArray(10)(number, negate(number))─┐ +│ [99,98,97,96,95,94,93,92,91,90] │ +└──────────────────────────────────────────────┘ +``` \ No newline at end of file diff --git a/docs/en/sql-reference/aggregate-functions/reference/index.md b/docs/en/sql-reference/aggregate-functions/reference/index.md index 59befed8785..14a8ecc9dcf 100644 --- a/docs/en/sql-reference/aggregate-functions/reference/index.md +++ b/docs/en/sql-reference/aggregate-functions/reference/index.md @@ -42,6 +42,7 @@ ClickHouse-specific aggregate functions: - [groupBitmapAnd](../../../sql-reference/aggregate-functions/reference/groupbitmapand.md) - [groupBitmapOr](../../../sql-reference/aggregate-functions/reference/groupbitmapor.md) - [groupBitmapXor](../../../sql-reference/aggregate-functions/reference/groupbitmapxor.md) +- [groupSortedArray](../../../sql-reference/aggregate-functions/reference/groupsortedarray.md) - [sumWithOverflow](../../../sql-reference/aggregate-functions/reference/sumwithoverflow.md) - [sumMap](../../../sql-reference/aggregate-functions/reference/summap.md) - [minMap](../../../sql-reference/aggregate-functions/reference/minmap.md) diff --git a/src/AggregateFunctions/AggregateFunctionGroupSortedArray.cpp b/src/AggregateFunctions/AggregateFunctionGroupSortedArray.cpp new file mode 100644 index 00000000000..e52091fc597 --- /dev/null +++ b/src/AggregateFunctions/AggregateFunctionGroupSortedArray.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include +#include + + +static inline constexpr UInt64 GROUP_SORTED_ARRAY_MAX_SIZE = 0xFFFFFF; +static inline constexpr UInt64 GROUP_SORTED_ARRAY_DEFAULT_THRESHOLD = 10; + + +namespace DB +{ +struct Settings; + +namespace ErrorCodes +{ + extern const int ARGUMENT_OUT_OF_BOUND; + extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int LOGICAL_ERROR; + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; +} + + +namespace +{ + template + class AggregateFunctionGroupSortedArrayNumeric : public AggregateFunctionGroupSortedArray + { + using AggregateFunctionGroupSortedArray::AggregateFunctionGroupSortedArray; + }; + + template + class AggregateFunctionGroupSortedArrayFieldType + : public AggregateFunctionGroupSortedArray + { + using AggregateFunctionGroupSortedArray::AggregateFunctionGroupSortedArray; + DataTypePtr getReturnType() const override { return std::make_shared(std::make_shared()); } + }; + + template + static IAggregateFunction * createWithExtraTypes(const DataTypes & argument_types, UInt64 threshold, const Array & params) + { + if (argument_types.empty()) + throw DB::Exception(ErrorCodes::LOGICAL_ERROR, "Got empty arguments list"); + + WhichDataType which(argument_types[0]); + if (which.idx == TypeIndex::Date) + return new AggregateFunctionGroupSortedArrayFieldType(threshold, argument_types, params); + if (which.idx == TypeIndex::DateTime) + return new AggregateFunctionGroupSortedArrayFieldType(threshold, argument_types, params); + + if (argument_types[0]->isValueUnambiguouslyRepresentedInContiguousMemoryRegion()) + { + return new AggregateFunctionGroupSortedArray(threshold, argument_types, params); + } + else + { + return new AggregateFunctionGroupSortedArray(threshold, argument_types, params); + } + } + + template

y*C@?%Dh0DL3pWx%2ee!u`YEpmnHjf!}O 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ෳ+#v4E0_{x$@9%` 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