diff --git a/dbms/include/DB/Columns/ColumnArray.h b/dbms/include/DB/Columns/ColumnArray.h index 165645d87f2..3b697510e22 100644 --- a/dbms/include/DB/Columns/ColumnArray.h +++ b/dbms/include/DB/Columns/ColumnArray.h @@ -452,41 +452,32 @@ private: for (size_t i = 0; i < col_size; ++i) { - // std::cerr << "i: " << i << std::endl; /// Насколько размножить массив. size_t size_to_replicate = replicate_offsets[i] - prev_replicate_offset; - // std::cerr << "size_to_replicate: " << size_to_replicate << std::endl; /// Количество строк в массиве. size_t value_size = cur_offsets[i] - prev_cur_offset; - // std::cerr << "value_size: " << value_size << std::endl; size_t sum_chars_size = 0; for (size_t j = 0; j < size_to_replicate; ++j) { - // std::cerr << "j: " << j << std::endl; current_res_offset += value_size; res_offsets.push_back(current_res_offset); - // std::cerr << "current_res_offset: " << current_res_offset << std::endl; sum_chars_size = 0; size_t prev_cur_string_offset_local = prev_cur_string_offset; for (size_t k = 0; k < value_size; ++k) { - // std::cerr << "k: " << k << std::endl; /// Размер одной строки. size_t chars_size = cur_string_offsets[k + prev_cur_offset] - prev_cur_string_offset_local; - // std::cerr << "chars_size: " << chars_size << std::endl; current_res_string_offset += chars_size; res_string_offsets.push_back(current_res_string_offset); - // std::cerr << "current_res_string_offset: " << current_res_string_offset << std::endl; /// Копирование символов одной строки. res_chars.resize(res_chars.size() + chars_size); memcpy(&res_chars[res_chars.size() - chars_size], &cur_chars[prev_cur_string_offset_local], chars_size); - // std::cerr << "copied: " << mysqlxx::escape << std::string(reinterpret_cast(&cur_chars[prev_cur_string_offset_local]), chars_size) << std::endl; sum_chars_size += chars_size; prev_cur_string_offset_local += chars_size; diff --git a/dbms/include/DB/Columns/ColumnSet.h b/dbms/include/DB/Columns/ColumnSet.h index a1abbb8af71..32346ac5da9 100644 --- a/dbms/include/DB/Columns/ColumnSet.h +++ b/dbms/include/DB/Columns/ColumnSet.h @@ -16,6 +16,9 @@ class ColumnSet : public IColumnDummy public: ColumnSet(size_t s_, SetPtr data_) : IColumnDummy(s_), data(data_) {} + /// Столбец не константный. Иначе столбец будет использоваться в вычислениях в ExpressionActions::prepare, когда множество из подзапроса ещё не готово. + bool isConst() const { return false; } + std::string getName() const { return "ColumnSet"; } ColumnPtr cloneDummy(size_t s_) const { return new ColumnSet(s_, data); } diff --git a/dbms/include/DB/Common/VirtualColumnUtils.h b/dbms/include/DB/Common/VirtualColumnUtils.h index 2e00b0d2b82..37897dc568f 100644 --- a/dbms/include/DB/Common/VirtualColumnUtils.h +++ b/dbms/include/DB/Common/VirtualColumnUtils.h @@ -9,8 +9,6 @@ #include #include #include -#include -#include #include namespace DB @@ -36,9 +34,9 @@ BlockInputStreamPtr getVirtualColumnsBlocks(ASTPtr query, const Block & input, c /// Извлечь из входного потока множество значений столбца name template -std::set extractSingleValueFromBlocks(BlockInputStreamPtr input, const String & name) +std::multiset extractSingleValueFromBlocks(BlockInputStreamPtr input, const String & name) { - std::set res; + std::multiset res; input->readPrefix(); while(1) { @@ -53,10 +51,10 @@ std::set extractSingleValueFromBlocks(BlockInputStreamPtr input, const Strin /// Извлечь из входного потока множество пар значений в столбцах first_name и second_name template -std::set< std::pair > extractTwoValuesFromBlocks(BlockInputStreamPtr input, +std::multiset< std::pair > extractTwoValuesFromBlocks(BlockInputStreamPtr input, const String & first_name, const String & second_name) { - std::set< std::pair > res; + std::multiset< std::pair > res; input->readPrefix(); while(1) { diff --git a/dbms/include/DB/Core/ErrorCodes.h b/dbms/include/DB/Core/ErrorCodes.h index 8fba41fc125..b875e465ba4 100644 --- a/dbms/include/DB/Core/ErrorCodes.h +++ b/dbms/include/DB/Core/ErrorCodes.h @@ -225,6 +225,8 @@ namespace ErrorCodes NOT_AN_AGGREGATE, QUERY_WITH_SAME_ID_IS_ALREADY_RUNNING, CLIENT_HAS_CONNECTED_TO_WRONG_PORT, + TABLE_IS_DROPPED, + DATABASE_NOT_EMPTY, POCO_EXCEPTION = 1000, STD_EXCEPTION, diff --git a/dbms/include/DB/DataStreams/CollapsingFinalBlockInputStream.h b/dbms/include/DB/DataStreams/CollapsingFinalBlockInputStream.h index 29b916ce63c..33f32d964ce 100644 --- a/dbms/include/DB/DataStreams/CollapsingFinalBlockInputStream.h +++ b/dbms/include/DB/DataStreams/CollapsingFinalBlockInputStream.h @@ -14,7 +14,7 @@ namespace DB class CollapsingFinalBlockInputStream : public IProfilingBlockInputStream { public: - CollapsingFinalBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, + CollapsingFinalBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, const String & sign_column_) : description(description_), sign_column(sign_column_), log(&Logger::get("CollapsingSortedBlockInputStream")), diff --git a/dbms/include/DB/DataStreams/CollapsingSortedBlockInputStream.h b/dbms/include/DB/DataStreams/CollapsingSortedBlockInputStream.h index 34968d00543..91cf5d8908a 100644 --- a/dbms/include/DB/DataStreams/CollapsingSortedBlockInputStream.h +++ b/dbms/include/DB/DataStreams/CollapsingSortedBlockInputStream.h @@ -23,7 +23,7 @@ namespace DB class CollapsingSortedBlockInputStream : public MergingSortedBlockInputStream { public: - CollapsingSortedBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, + CollapsingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, const String & sign_column_, size_t max_block_size_) : MergingSortedBlockInputStream(inputs_, description_, max_block_size_), sign_column(sign_column_), sign_column_number(0), diff --git a/dbms/include/DB/DataStreams/IBlockInputStream.h b/dbms/include/DB/DataStreams/IBlockInputStream.h index db49955c310..c07c93b2640 100644 --- a/dbms/include/DB/DataStreams/IBlockInputStream.h +++ b/dbms/include/DB/DataStreams/IBlockInputStream.h @@ -5,7 +5,7 @@ #include #include -#include +#include namespace DB @@ -30,11 +30,7 @@ public: typedef SharedPtr BlockInputStreamPtr; typedef std::vector BlockInputStreams; - /** Листовой BlockInputStream обычно требует, чтобы был жив какой-то Storage. - * Переданный сюда указатель на Storage будет просто храниться в этом экземпляре, - * не позволяя уничтожить Storage раньше этого BlockInputStream. - */ - IBlockInputStream(StoragePtr owned_storage_ = StoragePtr()) : owned_storage(owned_storage_) {} + IBlockInputStream() {} /** Прочитать следующий блок. * Если блоков больше нет - вернуть пустой блок (для которого operator bool возвращает false). @@ -80,10 +76,12 @@ public: */ size_t checkDepth(size_t max_depth) const; - void setOwnedStorage(StoragePtr owned_storage_) { owned_storage = owned_storage_; } + /** Не давать изменить таблицу, пока жив поток блоков. + */ + void addTableLock(const IStorage::TableStructureReadLockPtr & lock) { table_locks.push_back(lock); } protected: - StoragePtr owned_storage; + IStorage::TableStructureReadLocks table_locks; BlockInputStreams children; @@ -99,8 +97,5 @@ private: }; -typedef IBlockInputStream::BlockInputStreamPtr BlockInputStreamPtr; -typedef IBlockInputStream::BlockInputStreams BlockInputStreams; - } diff --git a/dbms/include/DB/DataStreams/IBlockOutputStream.h b/dbms/include/DB/DataStreams/IBlockOutputStream.h index f9100557d1f..72769711008 100644 --- a/dbms/include/DB/DataStreams/IBlockOutputStream.h +++ b/dbms/include/DB/DataStreams/IBlockOutputStream.h @@ -6,7 +6,7 @@ #include #include -#include +#include namespace DB @@ -21,7 +21,7 @@ class IBlockOutputStream : private boost::noncopyable { public: - IBlockOutputStream(StoragePtr owned_storage_ = StoragePtr()) : owned_storage(owned_storage_) {} + IBlockOutputStream() {} /** Записать блок. */ @@ -39,11 +39,13 @@ public: virtual void setExtremes(const Block & extremes) {} virtual ~IBlockOutputStream() {} + + /** Не давать изменить таблицу, пока жив поток блоков. + */ + void addTableLock(const IStorage::TableStructureReadLockPtr & lock) { table_locks.push_back(lock); } protected: - StoragePtr owned_storage; + IStorage::TableStructureReadLocks table_locks; }; -typedef SharedPtr BlockOutputStreamPtr; - } diff --git a/dbms/include/DB/DataStreams/IProfilingBlockInputStream.h b/dbms/include/DB/DataStreams/IProfilingBlockInputStream.h index 5b923ee6c2e..337bb74d155 100644 --- a/dbms/include/DB/DataStreams/IProfilingBlockInputStream.h +++ b/dbms/include/DB/DataStreams/IProfilingBlockInputStream.h @@ -69,7 +69,7 @@ private: mutable bool calculated_rows_before_limit; /// Вычислялось ли поле rows_before_limit }; - + /** Смотрит за тем, как работает источник блоков. * Позволяет получить информацию для профайлинга: * строк в секунду, блоков в секунду, мегабайт в секунду и т. п. @@ -78,8 +78,8 @@ private: class IProfilingBlockInputStream : public IBlockInputStream { public: - IProfilingBlockInputStream(StoragePtr owned_storage_ = StoragePtr()) - : IBlockInputStream(owned_storage_), is_cancelled(false), process_list_elem(NULL), + IProfilingBlockInputStream() + : is_cancelled(false), process_list_elem(NULL), enabled_extremes(false), quota(NULL), prev_elapsed(0) {} Block read(); diff --git a/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h b/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h index 698bb30e59b..72a2de8ded2 100644 --- a/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h +++ b/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h @@ -19,7 +19,7 @@ class MergingSortedBlockInputStream : public IProfilingBlockInputStream { public: /// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке. - MergingSortedBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, size_t max_block_size_, size_t limit_ = 0) + MergingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_, size_t limit_ = 0) : description(description_), max_block_size(max_block_size_), limit(limit_), total_merged_rows(0), first(true), has_collation(false), num_columns(0), source_blocks(inputs_.size()), cursors(inputs_.size()), log(&Logger::get("MergingSortedBlockInputStream")) { diff --git a/dbms/include/DB/DataStreams/PushingToViewsBlockOutputStream.h b/dbms/include/DB/DataStreams/PushingToViewsBlockOutputStream.h index f6aad23c077..6e3591e203a 100644 --- a/dbms/include/DB/DataStreams/PushingToViewsBlockOutputStream.h +++ b/dbms/include/DB/DataStreams/PushingToViewsBlockOutputStream.h @@ -23,7 +23,10 @@ public: { if (database.empty()) database = context.getCurrentDatabase(); + storage = context.getTable(database, table); + addTableLock(storage->lockStructure(true)); + Dependencies dependencies = context.getDependencies(DatabaseAndTableName(database, table)); for (size_t i = 0; i < dependencies.size(); ++i) { diff --git a/dbms/include/DB/DataStreams/SummingSortedBlockInputStream.h b/dbms/include/DB/DataStreams/SummingSortedBlockInputStream.h index 8e7c9a1ca31..7c90a8b3d36 100644 --- a/dbms/include/DB/DataStreams/SummingSortedBlockInputStream.h +++ b/dbms/include/DB/DataStreams/SummingSortedBlockInputStream.h @@ -18,7 +18,7 @@ namespace DB class SummingSortedBlockInputStream : public MergingSortedBlockInputStream { public: - SummingSortedBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, size_t max_block_size_) + SummingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_) : MergingSortedBlockInputStream(inputs_, description_, max_block_size_), log(&Logger::get("SummingSortedBlockInputStream")), current_row_is_zero(false) { diff --git a/dbms/include/DB/IO/BufferWithOwnMemory.h b/dbms/include/DB/IO/BufferWithOwnMemory.h index 32cc2e05498..0c044ddfb4c 100644 --- a/dbms/include/DB/IO/BufferWithOwnMemory.h +++ b/dbms/include/DB/IO/BufferWithOwnMemory.h @@ -20,12 +20,12 @@ namespace DB */ struct Memory : boost::noncopyable { - size_t m_capacity; - size_t m_size; - char * m_data; - size_t alignment; + size_t m_capacity = 0; + size_t m_size = 0; + char * m_data = nullptr; + size_t alignment = 0; - Memory() : m_capacity(0), m_size(0), m_data(NULL), alignment(0) {} + Memory() {} /// Если alignment != 0, то будет выделяться память, выровненная на alignment. Memory(size_t size_, size_t alignment_ = 0) : m_capacity(size_), m_size(m_capacity), alignment(alignment_) @@ -38,6 +38,21 @@ struct Memory : boost::noncopyable dealloc(); } + Memory(Memory && rhs) + { + *this = std::move(rhs); + } + + Memory & operator=(Memory && rhs) + { + std::swap(m_capacity, rhs.m_capacity); + std::swap(m_size, rhs.m_size); + std::swap(m_data, rhs.m_data); + std::swap(alignment, rhs.alignment); + + return *this; + } + size_t size() const { return m_size; } const char & operator[](size_t i) const { return m_data[i]; } char & operator[](size_t i) { return m_data[i]; } diff --git a/dbms/include/DB/Interpreters/Context.h b/dbms/include/DB/Interpreters/Context.h index 7e7838fe18c..cc71891d2d3 100644 --- a/dbms/include/DB/Interpreters/Context.h +++ b/dbms/include/DB/Interpreters/Context.h @@ -41,9 +41,6 @@ typedef std::map Tables; /// имя БД -> таблицы typedef std::map Databases; -/// имя БД -> dropper -typedef std::map DatabaseDroppers; - /// (имя базы данных, имя таблицы) typedef std::pair DatabaseAndTableName; @@ -76,7 +73,6 @@ struct ContextShared String path; /// Путь к директории с данными, со слешем на конце. Databases databases; /// Список БД и таблиц в них. - DatabaseDroppers database_droppers; /// Reference counter'ы для ленивого удаления БД. TableFunctionFactory table_function_factory; /// Табличные функции. FunctionFactory function_factory; /// Обычные функции. AggregateFunctionFactory aggregate_function_factory; /// Агрегатные функции. @@ -137,7 +133,6 @@ struct ContextShared { Poco::ScopedLock lock(mutex); - database_droppers.clear(); current_databases = databases; } @@ -232,8 +227,6 @@ public: String getDefaultFormat() const; /// Если default_format не задан - возвращается некоторый глобальный формат по-умолчанию. void setDefaultFormat(const String & name); - DatabaseDropperPtr getDatabaseDropper(const String & name); - Settings getSettings() const; void setSettings(const Settings & settings_); diff --git a/dbms/include/DB/Interpreters/ExpressionAnalyzer.h b/dbms/include/DB/Interpreters/ExpressionAnalyzer.h index 307aede49c5..a16810e0a8f 100644 --- a/dbms/include/DB/Interpreters/ExpressionAnalyzer.h +++ b/dbms/include/DB/Interpreters/ExpressionAnalyzer.h @@ -15,6 +15,8 @@ namespace DB { /** Превращает выражение из синтаксического дерева в последовательность действий для его выполнения. + * + * NOTE: если ast - запрос SELECT из таблицы, структура этой таблицы не должна меняться во все время жизни ExpressionAnalyzer-а. */ class ExpressionAnalyzer : private boost::noncopyable { diff --git a/dbms/include/DB/Interpreters/InterpreterDescribeQuery.h b/dbms/include/DB/Interpreters/InterpreterDescribeQuery.h index cf7d55cab03..c665d1ca97f 100644 --- a/dbms/include/DB/Interpreters/InterpreterDescribeQuery.h +++ b/dbms/include/DB/Interpreters/InterpreterDescribeQuery.h @@ -76,12 +76,9 @@ private: NamesAndTypesList columns; { - Poco::ScopedLock lock(context.getMutex()); - - if (!context.isTableExist(ast.database, ast.table)) - throw Exception("Table " + (ast.database.empty() ? "" : ast.database + ".") + ast.table + " doesn't exist", ErrorCodes::UNKNOWN_TABLE); - - columns = context.getTable(ast.database, ast.table)->getColumnsList(); + StoragePtr table = context.getTable(ast.database, ast.table); + auto table_lock = table->lockStructure(false); + columns = table->getColumnsList(); } ColumnString * name_column = new ColumnString; diff --git a/dbms/include/DB/Interpreters/InterpreterDropQuery.h b/dbms/include/DB/Interpreters/InterpreterDropQuery.h index 62bc8edd7e1..2593fa6a02b 100644 --- a/dbms/include/DB/Interpreters/InterpreterDropQuery.h +++ b/dbms/include/DB/Interpreters/InterpreterDropQuery.h @@ -18,6 +18,9 @@ public: /// Удаляет таблицу. void execute(); + /// Удаляет таблицу, уже отцепленную от контекста (Context::detach). + static void dropDetachedTable(String database_name, StoragePtr table, Context & context); + private: ASTPtr query_ptr; Context context; diff --git a/dbms/include/DB/Interpreters/InterpreterInsertQuery.h b/dbms/include/DB/Interpreters/InterpreterInsertQuery.h index ac912c79b1d..c9a66a8da97 100644 --- a/dbms/include/DB/Interpreters/InterpreterInsertQuery.h +++ b/dbms/include/DB/Interpreters/InterpreterInsertQuery.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -24,13 +25,13 @@ public: /** Подготовить запрос к выполнению. Вернуть поток блоков, в который можно писать данные для выполнения запроса. * Или вернуть NULL, если запрос INSERT SELECT (самодостаточный запрос - не принимает входные данные). */ - BlockOutputStreamPtr execute(); - - Block getSampleBlock(); + BlockIO execute(); private: StoragePtr getTable(); + Block getSampleBlock(); + ASTPtr query_ptr; Context context; }; diff --git a/dbms/include/DB/Interpreters/InterpreterOptimizeQuery.h b/dbms/include/DB/Interpreters/InterpreterOptimizeQuery.h index 32111c8aaf6..f22e7d8ae4f 100644 --- a/dbms/include/DB/Interpreters/InterpreterOptimizeQuery.h +++ b/dbms/include/DB/Interpreters/InterpreterOptimizeQuery.h @@ -20,7 +20,9 @@ public: void execute() { const ASTOptimizeQuery & ast = dynamic_cast(*query_ptr); - context.getTable(ast.database, ast.table)->optimize(); + StoragePtr table = context.getTable(ast.database, ast.table); + auto table_lock = table->lockStructure(true); + table->optimize(); } private: diff --git a/dbms/include/DB/Interpreters/InterpreterSelectQuery.h b/dbms/include/DB/Interpreters/InterpreterSelectQuery.h index 3b106749922..55991248a49 100644 --- a/dbms/include/DB/Interpreters/InterpreterSelectQuery.h +++ b/dbms/include/DB/Interpreters/InterpreterSelectQuery.h @@ -34,10 +34,6 @@ public: DataTypes getReturnTypes(); Block getSampleBlock(); - /** Получить CREATE запрос для таблицы, из которой идёт выбор. - */ - ASTPtr getCreateQuery(); - private: typedef Poco::SharedPtr ExpressionAnalyzerPtr; @@ -47,8 +43,6 @@ private: */ void getDatabaseAndTableNames(String & database_name, String & table_name); - StoragePtr getTable(); - /** Выбрать из списка столбцов какой-нибудь, лучше - минимального размера. */ String getAnyColumn(); @@ -65,7 +59,7 @@ private: void executeTotalsAndHaving( BlockInputStreams & streams, bool has_having, ExpressionActionsPtr expression, bool overflow_row); void executeHaving( BlockInputStreams & streams, ExpressionActionsPtr expression); - void executeOuterExpression( BlockInputStreams & streams, ExpressionActionsPtr expression); + void executeExpression( BlockInputStreams & streams, ExpressionActionsPtr expression); void executeOrder( BlockInputStreams & streams); void executePreLimit( BlockInputStreams & streams); void executeUnion( BlockInputStreams & streams); @@ -83,7 +77,10 @@ private: size_t subquery_depth; ExpressionAnalyzerPtr query_analyzer; BlockInputStreams streams; - StoragePtr table_function_storage; + + /// Таблица, откуда читать данные, если не подзапрос. + StoragePtr storage; + IStorage::TableStructureReadLockPtr table_lock; Logger * log; }; diff --git a/dbms/include/DB/Interpreters/InterpreterShowCreateQuery.h b/dbms/include/DB/Interpreters/InterpreterShowCreateQuery.h index 94119b57e7b..e0ff1f71d26 100644 --- a/dbms/include/DB/Interpreters/InterpreterShowCreateQuery.h +++ b/dbms/include/DB/Interpreters/InterpreterShowCreateQuery.h @@ -68,18 +68,9 @@ private: { const ASTShowCreateQuery & ast = dynamic_cast(*query_ptr); - String res; - - { - Poco::ScopedLock lock(context.getMutex()); - - if (!context.isTableExist(ast.database, ast.table)) - throw Exception("Table " + (ast.database.empty() ? "" : ast.database + ".") + ast.table + " doesn't exist", ErrorCodes::UNKNOWN_TABLE); - - std::stringstream stream; - formatAST(*context.getCreateQuery(ast.database, ast.table), stream, 0, false, true); - res = stream.str(); - } + std::stringstream stream; + formatAST(*context.getCreateQuery(ast.database, ast.table), stream, 0, false, true); + String res = stream.str(); ColumnWithNameAndType col; col.name = "statement"; diff --git a/dbms/include/DB/Storages/DatabaseDropper.h b/dbms/include/DB/Storages/DatabaseDropper.h deleted file mode 100644 index ebce3d6774d..00000000000 --- a/dbms/include/DB/Storages/DatabaseDropper.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include - - -/// Удаляет директорию в деструкторе. -class DatabaseDropper -{ -public: - DatabaseDropper(const std::string & data_path_) : drop_on_destroy(false), data_path(data_path_) {} - - ~DatabaseDropper() - { - if (drop_on_destroy) - { - try - { - if (std::uncaught_exception()) - LOG_ERROR(&Logger::get("DatabaseDropper"), "Didn't remove database data directory because of uncaught exception."); - else - Poco::File(data_path).remove(false); - } - catch(...) - { - } - } - } - - bool drop_on_destroy; - -private: - std::string data_path; -}; - -typedef boost::shared_ptr DatabaseDropperPtr; diff --git a/dbms/include/DB/Storages/IStorage.h b/dbms/include/DB/Storages/IStorage.h index 321175335b5..90c068aa008 100644 --- a/dbms/include/DB/Storages/IStorage.h +++ b/dbms/include/DB/Storages/IStorage.h @@ -7,20 +7,24 @@ #include #include #include -#include -#include #include #include #include -#include -#include "DatabaseDropper.h" +#include #include +#include namespace DB { class Context; +class IBlockInputStream; +class IBlockOutputStream; + +typedef SharedPtr BlockOutputStreamPtr; +typedef SharedPtr BlockInputStreamPtr; +typedef std::vector BlockInputStreams; /** Хранилище. Отвечает за: @@ -30,41 +34,12 @@ class Context; * - структура хранения данных (сжатие, etc.) * - конкуррентный доступ к данным (блокировки, etc.) */ -class IStorage : private boost::noncopyable +class IStorage : private boost::noncopyable, public ITableDeclaration { public: - /// Основное имя типа таблицы (например, StorageWithoutKey). + /// Основное имя типа таблицы (например, StorageMergeTree). virtual std::string getName() const = 0; - /// Имя самой таблицы (например, hits) - virtual std::string getTableName() const = 0; - - /** Получить список имён и типов столбцов таблицы, только невиртуальные. - */ - virtual const NamesAndTypesList & getColumnsList() const = 0; - - /** Получить описание реального (невиртуального) столбца по его имени. - */ - virtual NameAndTypePair getRealColumn(const String & column_name) const; - - /** Присутствует ли реальный (невиртуальный) столбец с таким именем. - */ - virtual bool hasRealColumn(const String & column_name) const; - - /** Получить описание любого столбца по его имени. - */ - virtual NameAndTypePair getColumn(const String & column_name) const; - - /** Присутствует ли столбец с таким именем. - */ - virtual bool hasColumn(const String & column_name) const; - - const DataTypePtr getDataTypeByName(const String & column_name) const; - - /** То же самое, но в виде блока-образца. - */ - Block getSampleBlock() const; - /** Возвращает true, если хранилище получает данные с удалённого сервера или серверов. */ virtual bool isRemote() const { return false; } @@ -81,6 +56,76 @@ public: */ virtual bool supportsPrewhere() const { return false; } + /** Не дает изменять описание таблицы (в том числе переименовывать и удалять таблицу). + * Если в течение какой-то операции структура таблицы должна оставаться неизменной, нужно держать такой лок на все ее время. + * Например, нужно держать такой лок на время всего запроса SELECT или INSERT и на все время слияния набора кусков + * (но между выбором кусков для слияния и их слиянием структура таблицы может измениться). + * NOTE: Это лок на "чтение" описания таблицы. Чтобы изменить описание таблицы, нужно взять TableStructureWriteLock. + */ + class TableStructureReadLock + { + private: + friend class IStorage; + + /// Порядок важен. + Poco::SharedPtr data_lock; + Poco::SharedPtr structure_lock; + + TableStructureReadLock(IStorage & storage, bool lock_structure, bool lock_data) + : data_lock(lock_data ? new Poco::ScopedReadRWLock(storage. data_lock) : nullptr), + structure_lock(lock_structure ? new Poco::ScopedReadRWLock(storage.structure_lock) : nullptr) {} + }; + + typedef Poco::SharedPtr TableStructureReadLockPtr; + typedef std::vector TableStructureReadLocks; + + /** Не дает изменять структуру или имя таблицы. + * Если в рамках этого лока будут изменены данные в таблице, нужно указать will_modify_data=true. + * Это возьмет дополнительный лок, не позволяющий начать ALTER MODIFY. + * + * WARNING: Вызывать методы из ITableDeclaration нужно под такой блокировкой. Без нее они не thread safe. + * WARNING: Чтобы не было дедлоков, нельзя вызывать это метод при захваченном мьютексе в Context. + */ + TableStructureReadLockPtr lockStructure(bool will_modify_data) + { + TableStructureReadLockPtr res = new TableStructureReadLock(*this, true, will_modify_data); + if (is_dropped) + throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED); + return res; + } + + typedef Poco::SharedPtr TableStructureWriteLockPtr; + typedef Poco::SharedPtr TableDataWriteLockPtr; + typedef std::pair TableFullWriteLockPtr; + + /** Не дает читать структуру таблицы. Берется для ALTER, RENAME и DROP. + */ + TableFullWriteLockPtr lockForAlter() + { + return std::make_pair(lockDataForAlter(), lockStructureForAlter()); + } + + /** Не дает изменять данные в таблице. (Более того, не дает посмотреть на структуру таблицы с намерением изменить данные). + * Берется на время записи временных данных в ALTER MODIFY. + * Под этим локом можно брать lockStructureForAlter(), чтобы изменить структуру таблицы. + */ + TableDataWriteLockPtr lockDataForAlter() + { + TableDataWriteLockPtr res = new Poco::ScopedWriteRWLock(data_lock); + if (is_dropped) + throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED); + return res; + } + + TableStructureWriteLockPtr lockStructureForAlter() + { + TableStructureWriteLockPtr res = new Poco::ScopedWriteRWLock(structure_lock); + if (is_dropped) + throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED); + return res; + } + + /** Читать набор столбцов из таблицы. * Принимает список столбцов, которых нужно прочитать, а также описание запроса, * из которого может быть извлечена информация о том, каким способом извлекать данные @@ -97,6 +142,8 @@ public: * * threads - рекомендация, сколько потоков возвращать, * если хранилище может возвращать разное количество потоков. + * + * Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP). */ virtual BlockInputStreams read( const Names & column_names, @@ -112,6 +159,8 @@ public: /** Пишет данные в таблицу. * Принимает описание запроса, в котором может содержаться информация о методе записи данных. * Возвращает объект, с помощью которого можно последовательно писать данные. + * + * Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP). */ virtual BlockOutputStreamPtr write( ASTPtr query) @@ -119,21 +168,15 @@ public: throw Exception("Method write is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); } - /** Удалить данные таблицы. После вызова этого метода, использование объекта некорректно (его можно лишь уничтожить). - */ - void drop() - { - drop_on_destroy = true; - } - - /** Вызывается перед удалением директории с данными и вызовом деструктора. + /** Удалить данные таблицы. Вызывается перед удалением директории с данными. * Если не требуется никаких действий, кроме удаления директории с данными, этот метод можно оставить пустым. */ - virtual void dropImpl() {} + virtual void drop() {} /** Переименовать таблицу. * Переименование имени в файле с метаданными, имени в списке таблиц в оперативке, осуществляется отдельно. * В этой функции нужно переименовать директорию с данными, если она есть. + * Вызывается при заблокированной на запись структуре таблицы. */ virtual void rename(const String & new_path_to_db, const String & new_name) { @@ -142,12 +185,29 @@ public: /** ALTER таблицы в виде изменения столбцов, не затрагивающий изменение Storage или его параметров. * (ALTER, затрагивающий изменение движка, делается внешним кодом, путём копирования данных.) + * Вызывается при заблокированной на запись структуре таблицы. + * Для ALTER MODIFY можно использовать другие методы (см. ниже). */ virtual void alter(const ASTAlterQuery::Parameters & params) { throw Exception("Method alter is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); } + /** ALTER MODIFY (изменение типа столбца) выполняется в два вызова: + * Сначала вызывается prepareAlterModify при заблокированной записи данных, но незаблокированной структуре таблицы. + * В нем можно выполнить долгую работу по записи сконвертированных данных, оставляя доступными существующие данные. + * Потом вызывается commitAlterModify при заблокированной структуре таблицы. + * В нем нужно закончить изменение типа столбца. + * Для движков с тривиальным ALTER MODIFY можно оставить реализацию по умолчанию, вызывающую alter. + */ + + virtual void prepareAlterModify(const ASTAlterQuery::Parameters & params) {} + + virtual void commitAlterModify(const ASTAlterQuery::Parameters & params) + { + alter(params); + } + /** Выполнить какую-либо фоновую работу. Например, объединение кусков в таблице типа MergeTree. * Возвращает - была ли выполнена какая-либо работа. */ @@ -169,56 +229,56 @@ public: /** Если при уничтожении объекта надо сделать какую-то сложную работу - сделать её заранее. * Например, если таблица содержит какие-нибудь потоки для фоновой работы - попросить их завершиться и дождаться завершения. * По-умолчанию - ничего не делать. + * Может вызываться одновременно из разных потоков, даже после вызова drop(). */ virtual void shutdown() {} - - virtual ~IStorage() {} - /** Проверить, что все запрошенные имена есть в таблице и заданы корректно. - * (список имён не пустой и имена не повторяются) - */ - void check(const Names & column_names) const; - - /** Проверить, что блок с данными для записи содержит все столбцы таблицы с правильными типами, - * содержит только столбцы таблицы, и все столбцы различны. - * Если need_all, еще проверяет, что все столбцы таблицы есть в блоке. - */ - void check(const Block & block, bool need_all = false) const; - /** Возвращает владеющий указатель на себя. */ - StoragePtr thisPtr() + std::shared_ptr thisPtr() { - if (!this_ptr.lock()) + std::shared_ptr res = this_ptr.lock(); + if (!res) { - boost::shared_ptr p(new StoragePtr::Wrapper(this)); - this_ptr = p; - return StoragePtr(this_ptr); - } - else - { - return StoragePtr(this_ptr); + res.reset(this); + this_ptr = res; } + return res; } - - /** Не дает удалить БД до удаления таблицы. Присваивается перед удалением таблицы или БД. - */ - DatabaseDropperPtr database_to_drop; - - bool drop_on_destroy; - - /** Директория с данными. Будет удалена после удаления таблицы (после вызова dropImpl). - */ - std::string path_to_remove_on_drop; + + bool is_dropped; protected: - IStorage() : drop_on_destroy(false) {} - - /// реализация alter, модифицирующая список столбцов. - void alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) const; + IStorage() : is_dropped(false) {} + private: - boost::weak_ptr this_ptr; + std::weak_ptr this_ptr; + + /// Брать следующие два лока всегда нужно в этом порядке. + + /** Берется на чтение на все время запроса INSERT и на все время слияния кусков (для MergeTree). + * Берется на запись на все время ALTER MODIFY. + * + * Формально: + * Ввзятие на запись гарантирует, что: + * 1) данные в таблице не изменится, пока лок жив, + * 2) все изменения данных после отпускания лока будут основаны на структуре таблицы на момент после отпускания лока. + * Нужно брать на чтение на все время операции, изменяющей данные. + */ + mutable Poco::RWLock data_lock; + + /** Лок для множества столбцов и пути к таблице. Берется на запись в RENAME, ALTER (для ALTER MODIFY ненадолго) и DROP. + * Берется на чтение на все время SELECT, INSERT и слияния кусков (для MergeTree). + * + * Взятие этого лока на запись - строго более "сильная" операция, чем взятие parts_writing_lock на запись. + * То есть, если этот лок взят на запись, о parts_writing_lock можно не заботиться. + * parts_writing_lock нужен только для случаев, когда не хочется брать table_structure_lock надолго (ALTER MODIFY). + */ + mutable Poco::RWLock structure_lock; }; +typedef std::shared_ptr StoragePtr; typedef std::vector StorageVector; +typedef IStorage::TableStructureReadLocks TableLocks; + } diff --git a/dbms/include/DB/Storages/ITableDeclaration.h b/dbms/include/DB/Storages/ITableDeclaration.h new file mode 100644 index 00000000000..1307d1cc5e0 --- /dev/null +++ b/dbms/include/DB/Storages/ITableDeclaration.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace DB +{ + +class Context; + +/** Описание таблицы. + * Не thread safe. См. IStorage::lockStructure(). + */ +class ITableDeclaration +{ +public: + /** Имя таблицы. + */ + virtual std::string getTableName() const { return ""; } + + /** Получить список имён и типов столбцов таблицы, только невиртуальные. + */ + virtual const NamesAndTypesList & getColumnsList() const = 0; + + /** Получить описание реального (невиртуального) столбца по его имени. + */ + virtual NameAndTypePair getRealColumn(const String & column_name) const; + + /** Присутствует ли реальный (невиртуальный) столбец с таким именем. + */ + virtual bool hasRealColumn(const String & column_name) const; + + /** Получить описание любого столбца по его имени. + */ + virtual NameAndTypePair getColumn(const String & column_name) const; + + /** Присутствует ли столбец с таким именем. + */ + virtual bool hasColumn(const String & column_name) const; + + const DataTypePtr getDataTypeByName(const String & column_name) const; + + /** То же самое, но в виде блока-образца. + */ + Block getSampleBlock() const; + + /** Проверить, что все запрошенные имена есть в таблице и заданы корректно. + * (список имён не пустой и имена не повторяются) + */ + void check(const Names & column_names) const; + + /** Проверить, что блок с данными для записи содержит все столбцы таблицы с правильными типами, + * содержит только столбцы таблицы, и все столбцы различны. + * Если need_all, еще проверяет, что все столбцы таблицы есть в блоке. + */ + void check(const Block & block, bool need_all = false) const; + + /// реализация alter, модифицирующая список столбцов. + static void alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context); + + virtual ~ITableDeclaration() {} +}; + +} diff --git a/dbms/include/DB/Storages/MergeTree/DiskSpaceMonitor.h b/dbms/include/DB/Storages/MergeTree/DiskSpaceMonitor.h new file mode 100644 index 00000000000..f9e34822df8 --- /dev/null +++ b/dbms/include/DB/Storages/MergeTree/DiskSpaceMonitor.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +/** Узнает количество свободного места в файловой системе. + * Можно "резервировать" место, чтобы разные операции могли согласованно планировать использование диска. + * Резервирования не разделяются по файловым системам. + * Вместо этого при запросе свободного места считается, что все резервирования сделаны в той же файловой системе. + */ +class DiskSpaceMonitor +{ +public: + class Reservation : private boost::noncopyable + { + friend class DiskSpaceMonitor; + public: + ~Reservation() + { + try + { + Poco::ScopedLock lock(DiskSpaceMonitor::reserved_bytes_mutex); + if (DiskSpaceMonitor::reserved_bytes < size) + { + DiskSpaceMonitor::reserved_bytes = 0; + LOG_ERROR(&Logger::get("DiskSpaceMonitor"), "Unbalanced reservations; it's a bug"); + } + else + { + DiskSpaceMonitor::reserved_bytes -= size; + } + } + catch (...) + { + tryLogCurrentException("~DiskSpaceMonitor"); + } + } + private: + Reservation(size_t size_) : size(size_) + { + Poco::ScopedLock lock(DiskSpaceMonitor::reserved_bytes_mutex); + DiskSpaceMonitor::reserved_bytes += size; + } + size_t size; + }; + + typedef Poco::SharedPtr ReservationPtr; + + static size_t getUnreservedFreeSpace(const std::string & path) + { + struct statvfs fs; + + if (statvfs(path.c_str(), &fs) != 0) + throwFromErrno("Could not calculate available disk space (statvfs)", ErrorCodes::CANNOT_STATVFS); + + size_t res = fs.f_bfree * fs.f_bsize; + + Poco::ScopedLock lock(reserved_bytes_mutex); + + if (reserved_bytes > res) + res = 0; + else + res -= reserved_bytes; + + return res; + } + + /// Если места (приблизительно) недостаточно, возвращает nullptr. + static ReservationPtr reserve(const std::string & path, size_t size) + { + if (getUnreservedFreeSpace(path) < size) + return nullptr; + return new Reservation(size); + } + +private: + static size_t reserved_bytes; + static Poco::FastMutex reserved_bytes_mutex; +}; + +} diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h index 09931dba6e7..eb716cfe50a 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h @@ -1,9 +1,8 @@ #pragma once #include -#include +#include #include - #include @@ -14,19 +13,18 @@ namespace DB class MergeTreeBlockInputStream : public IProfilingBlockInputStream { public: - /// Параметры storage_ и owned_storage разделены, чтобы можно было сделать поток, не владеющий своим storage - /// (например, поток, сливаящий куски). В таком случае сам storage должен следить, чтобы не удалить данные, пока их читают. MergeTreeBlockInputStream(const String & path_, /// Путь к куску size_t block_size_, const Names & column_names_, - StorageMergeTree & storage_, const StorageMergeTree::DataPartPtr & owned_data_part_, - const MarkRanges & mark_ranges_, StoragePtr owned_storage, bool use_uncompressed_cache_, + MergeTreeData & storage_, const MergeTreeData::DataPartPtr & owned_data_part_, + const MarkRanges & mark_ranges_, bool use_uncompressed_cache_, ExpressionActionsPtr prewhere_actions_, String prewhere_column_) - : IProfilingBlockInputStream(owned_storage), + : path(path_), block_size(block_size_), column_names(column_names_), storage(storage_), owned_data_part(owned_data_part_), all_mark_ranges(mark_ranges_), remaining_mark_ranges(mark_ranges_), use_uncompressed_cache(use_uncompressed_cache_), - prewhere_actions(prewhere_actions_), prewhere_column(prewhere_column_) + prewhere_actions(prewhere_actions_), prewhere_column(prewhere_column_), + log(&Logger::get("MergeTreeBlockInputStream")) { std::reverse(remaining_mark_ranges.begin(), remaining_mark_ranges.end()); @@ -48,7 +46,7 @@ public: } column_name_set.insert(column_names.begin(), column_names.end()); - LOG_TRACE(storage.log, "Reading " << all_mark_ranges.size() << " ranges from part " << owned_data_part->name + LOG_TRACE(log, "Reading " << all_mark_ranges.size() << " ranges from part " << owned_data_part->name << ", up to " << (all_mark_ranges.back().end - all_mark_ranges.front().begin) * storage.index_granularity << " rows starting from " << all_mark_ranges.front().begin * storage.index_granularity); } @@ -58,7 +56,7 @@ public: String getID() const { std::stringstream res; - res << "MergeTree(" << owned_storage->getTableName() << ", " << path << ", columns"; + res << "MergeTree(" << path << ", columns"; for (size_t i = 0; i < column_names.size(); ++i) res << ", " << column_names[i]; @@ -72,70 +70,6 @@ public: return res.str(); } - /// Получает набор диапазонов засечек, вне которых не могут находиться ключи из заданного диапазона. - static MarkRanges markRangesFromPkRange( - const StorageMergeTree::DataPart::Index & index, - StorageMergeTree & storage, - PKCondition & key_condition) - { - MarkRanges res; - - size_t key_size = storage.sort_descr.size(); - size_t marks_count = index.size() / key_size; - - /// Если индекс не используется. - if (key_condition.alwaysTrue()) - { - res.push_back(MarkRange(0, marks_count)); - } - else - { - /** В стеке всегда будут находиться непересекающиеся подозрительные отрезки, самый левый наверху (back). - * На каждом шаге берем левый отрезок и проверяем, подходит ли он. - * Если подходит, разбиваем его на более мелкие и кладем их в стек. Если нет - выбрасываем его. - * Если отрезок уже длиной в одну засечку, добавляем его в ответ и выбрасываем. - */ - std::vector ranges_stack; - ranges_stack.push_back(MarkRange(0, marks_count)); - while (!ranges_stack.empty()) - { - MarkRange range = ranges_stack.back(); - ranges_stack.pop_back(); - - bool may_be_true; - if (range.end == marks_count) - may_be_true = key_condition.mayBeTrueAfter(&index[range.begin * key_size]); - else - may_be_true = key_condition.mayBeTrueInRange(&index[range.begin * key_size], &index[range.end * key_size]); - - if (!may_be_true) - continue; - - if (range.end == range.begin + 1) - { - /// Увидели полезный промежуток между соседними засечками. Либо добавим его к последнему диапазону, либо начнем новый диапазон. - if (res.empty() || range.begin - res.back().end > storage.min_marks_for_seek) - res.push_back(range); - else - res.back().end = range.end; - } - else - { - /// Разбиваем отрезок и кладем результат в стек справа налево. - size_t step = (range.end - range.begin - 1) / storage.settings.coarse_index_granularity + 1; - size_t end; - - for (end = range.end; end > range.begin + step; end -= step) - ranges_stack.push_back(MarkRange(end - step, end)); - - ranges_stack.push_back(MarkRange(range.begin, end)); - } - } - } - - return res; - } - protected: /// Будем вызывать progressImpl самостоятельно. void progress(size_t rows, size_t bytes) {} @@ -321,8 +255,8 @@ private: Names column_names; NameSet column_name_set; Names pre_column_names; - StorageMergeTree & storage; - const StorageMergeTree::DataPartPtr owned_data_part; /// Кусок не будет удалён, пока им владеет этот объект. + MergeTreeData & storage; + const MergeTreeData::DataPartPtr owned_data_part; /// Кусок не будет удалён, пока им владеет этот объект. MarkRanges all_mark_ranges; /// В каких диапазонах засечек читать. В порядке возрастания номеров. MarkRanges remaining_mark_ranges; /// В каких диапазонах засечек еще не прочли. /// В порядке убывания номеров, чтобы можно было выбрасывать из конца. @@ -332,6 +266,8 @@ private: ExpressionActionsPtr prewhere_actions; String prewhere_column; bool remove_prewhere_column; + + Logger * log; }; } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h index f74e82860c0..b382b660471 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h @@ -1,298 +1,30 @@ #pragma once -#include -#include - -#include - -#include - #include - namespace DB { - + class MergeTreeBlockOutputStream : public IBlockOutputStream { public: - MergeTreeBlockOutputStream(StoragePtr owned_storage) : IBlockOutputStream(owned_storage), storage(dynamic_cast(*owned_storage)), flags(O_TRUNC | O_CREAT | O_WRONLY) - { - } - + MergeTreeBlockOutputStream(StorageMergeTree & storage_) + : storage(storage_) {} + void write(const Block & block) { - Poco::ScopedReadRWLock write_lock(storage.write_lock); - - storage.check(block, true); - - DateLUTSingleton & date_lut = DateLUTSingleton::instance(); - - size_t rows = block.rows(); - size_t columns = block.columns(); - - /// Достаём столбец с датой. - const ColumnUInt16::Container_t & dates = - dynamic_cast(*block.getByName(storage.date_column_name).column).getData(); - - /// Минимальная и максимальная дата. - UInt16 min_date = std::numeric_limits::max(); - UInt16 max_date = std::numeric_limits::min(); - for (ColumnUInt16::Container_t::const_iterator it = dates.begin(); it != dates.end(); ++it) + auto part_blocks = storage.writer.splitBlockIntoParts(block); + for (auto & current_block : part_blocks) { - if (*it < min_date) - min_date = *it; - if (*it > max_date) - max_date = *it; + UInt64 temp_index = storage.increment.get(); + MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, temp_index); + storage.data.renameTempPartAndAdd(part, &storage.increment); + storage.merge(2); } - - /// Разделяем на блоки по месяцам. Для каждого ещё посчитаем минимальную и максимальную дату. - typedef std::map BlocksByMonth; - BlocksByMonth blocks_by_month; - - UInt16 min_month = date_lut.toFirstDayNumOfMonth(DayNum_t(min_date)); - UInt16 max_month = date_lut.toFirstDayNumOfMonth(DayNum_t(max_date)); - - /// Типичный случай - когда месяц один (ничего разделять не нужно). - if (min_month == max_month) - blocks_by_month[min_month] = BlockWithDateInterval(block, min_date, max_date); - else - { - for (size_t i = 0; i < rows; ++i) - { - UInt16 month = date_lut.toFirstDayNumOfMonth(DayNum_t(dates[i])); - - BlockWithDateInterval & block_for_month = blocks_by_month[month]; - if (!block_for_month.block) - block_for_month.block = block.cloneEmpty(); - - if (dates[i] < block_for_month.min_date) - block_for_month.min_date = dates[i]; - if (dates[i] > block_for_month.max_date) - block_for_month.max_date = dates[i]; - - for (size_t j = 0; j < columns; ++j) - block_for_month.block.getByPosition(j).column->insert((*block.getByPosition(j).column)[i]); - } - } - - /// Для каждого месяца. - for (BlocksByMonth::iterator it = blocks_by_month.begin(); it != blocks_by_month.end(); ++it) - writePart(it->second.block, it->second.min_date, it->second.max_date); } - + private: StorageMergeTree & storage; - - const int flags; - - struct BlockWithDateInterval - { - Block block; - UInt16 min_date; - UInt16 max_date; - - BlockWithDateInterval() : min_date(std::numeric_limits::max()), max_date(0) {} - BlockWithDateInterval(const Block & block_, UInt16 min_date_, UInt16 max_date_) - : block(block_), min_date(min_date_), max_date(max_date_) {} - }; - - typedef std::set OffsetColumns; - - void writePart(Block & block, UInt16 min_date, UInt16 max_date) - { - DateLUTSingleton & date_lut = DateLUTSingleton::instance(); - - size_t rows = block.rows(); - size_t columns = block.columns(); - UInt64 tmp_part_id = storage.increment.get(false); - size_t part_size = (rows + storage.index_granularity - 1) / storage.index_granularity; - - String tmp_part_name = storage.getPartName( - DayNum_t(min_date), DayNum_t(max_date), - tmp_part_id, tmp_part_id, 0); - - String part_tmp_path = storage.full_path + "tmp_" + tmp_part_name + "/"; - - Poco::File(part_tmp_path).createDirectories(); - - LOG_TRACE(storage.log, "Calculating primary expression."); - - /// Если для сортировки надо вычислить некоторые столбцы - делаем это. - storage.primary_expr->execute(block); - - LOG_TRACE(storage.log, "Sorting by primary key."); - - /// Сортируем. - stableSortBlock(block, storage.sort_descr); - - /// Наконец-то можно писать данные на диск. - LOG_TRACE(storage.log, "Writing index."); - - /// Сначала пишем индекс. Индекс содержит значение PK для каждой index_granularity строки. - StorageMergeTree::DataPart::Index index_vec; - index_vec.reserve(part_size * storage.sort_descr.size()); - - { - WriteBufferFromFile index(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, flags); - - typedef std::vector PrimaryColumns; - PrimaryColumns primary_columns; - - for (size_t i = 0, size = storage.sort_descr.size(); i < size; ++i) - primary_columns.push_back( - !storage.sort_descr[i].column_name.empty() - ? &block.getByName(storage.sort_descr[i].column_name) - : &block.getByPosition(storage.sort_descr[i].column_number)); - - for (size_t i = 0; i < rows; i += storage.index_granularity) - { - for (PrimaryColumns::const_iterator it = primary_columns.begin(); it != primary_columns.end(); ++it) - { - index_vec.push_back((*(*it)->column)[i]); - (*it)->type->serializeBinary(index_vec.back(), index); - } - } - - index.next(); - } - - LOG_TRACE(storage.log, "Writing data."); - - /// Множество записанных столбцов со смещениями, чтобы не писать общие для вложенных структур столбцы несколько раз - OffsetColumns offset_columns; - - for (size_t i = 0; i < columns; ++i) - { - const ColumnWithNameAndType & column = block.getByPosition(i); - writeData(part_tmp_path, column.name, *column.type, *column.column, offset_columns); - } - - LOG_TRACE(storage.log, "Renaming."); - - /// Добавляем новый кусок в набор. - { - Poco::ScopedLock lock(storage.data_parts_mutex); - Poco::ScopedLock lock_all(storage.all_data_parts_mutex); - - /** Важно, что получение номера куска происходит атомарно с добавлением этого куска в набор. - * Иначе есть race condition - может произойти слияние пары кусков, диапазоны номеров которых - * содержат ещё не добавленный кусок. - */ - UInt64 part_id = storage.increment.get(false); - String part_name = storage.getPartName(DayNum_t(min_date), DayNum_t(max_date), part_id, part_id, 0); - String part_res_path = storage.full_path + part_name + "/"; - - StorageMergeTree::DataPartPtr new_data_part = new StorageMergeTree::DataPart(storage); - new_data_part->left_date = DayNum_t(min_date); - new_data_part->right_date = DayNum_t(max_date); - new_data_part->left = part_id; - new_data_part->right = part_id; - new_data_part->level = 0; - new_data_part->name = part_name; - new_data_part->size = part_size; - new_data_part->modification_time = time(0); - new_data_part->left_month = date_lut.toFirstDayNumOfMonth(new_data_part->left_date); - new_data_part->right_month = date_lut.toFirstDayNumOfMonth(new_data_part->right_date); - new_data_part->index.swap(index_vec); - - /// Переименовываем кусок. - Poco::File(part_tmp_path).renameTo(part_res_path); - - storage.data_parts.insert(new_data_part); - storage.all_data_parts.insert(new_data_part); - } - - /// Если на каждую запись делать по две итерации слияния, то дерево будет максимально компактно. - storage.merge(2); - } - - /// Записать данные одного столбца. - void writeData(const String & path, const String & name, const IDataType & type, const IColumn & column, - OffsetColumns & offset_columns, size_t level = 0) - { - String escaped_column_name = escapeForFileName(name); - size_t size = column.size(); - - /// Для массивов требуется сначала сериализовать размеры, а потом значения. - if (const DataTypeArray * type_arr = dynamic_cast(&type)) - { - String size_name = escapeForFileName(DataTypeNested::extractNestedTableName(name)) - + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); - if (offset_columns.count(size_name) == 0) - { - offset_columns.insert(size_name); - - WriteBufferFromFile plain(path + size_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags); - WriteBufferFromFile marks(path + size_name + ".mrk", 4096, flags); - CompressedWriteBuffer compressed(plain); - - size_t prev_mark = 0; - while (prev_mark < size) - { - /// Каждая засечка - это: (смещение в файле до начала сжатого блока, смещение внутри блока) - writeIntBinary(plain.count(), marks); - writeIntBinary(compressed.offset(), marks); - - type_arr->serializeOffsets(column, compressed, prev_mark, storage.index_granularity); - prev_mark += storage.index_granularity; - - compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего. - } - - compressed.next(); - plain.next(); - marks.next(); - } - } - if (const DataTypeNested * type_nested = dynamic_cast(&type)) - { - String size_name = escaped_column_name + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); - - WriteBufferFromFile plain(path + size_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags); - WriteBufferFromFile marks(path + size_name + ".mrk", 4096, flags); - CompressedWriteBuffer compressed(plain); - - size_t prev_mark = 0; - while (prev_mark < size) - { - /// Каждая засечка - это: (смещение в файле до начала сжатого блока, смещение внутри блока) - writeIntBinary(plain.count(), marks); - writeIntBinary(compressed.offset(), marks); - - type_nested->serializeOffsets(column, compressed, prev_mark, storage.index_granularity); - prev_mark += storage.index_granularity; - - compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего. - } - - compressed.next(); - plain.next(); - marks.next(); - } - - { - WriteBufferFromFile plain(path + escaped_column_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags); - WriteBufferFromFile marks(path + escaped_column_name + ".mrk", 4096, flags); - CompressedWriteBuffer compressed(plain); - - size_t prev_mark = 0; - while (prev_mark < size) - { - writeIntBinary(plain.count(), marks); - writeIntBinary(compressed.offset(), marks); - - type.serializeBinary(column, compressed, prev_mark, storage.index_granularity); - prev_mark += storage.index_granularity; - - compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего. - } - - compressed.next(); - plain.next(); - marks.next(); - } - } }; - + } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h new file mode 100644 index 00000000000..593eef78765 --- /dev/null +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -0,0 +1,356 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace DB +{ + +/** Структура данных для *MergeTree движков. + * Используется merge tree для инкрементальной сортировки данных. + * Таблица представлена набором сортированных кусков. + * При вставке, данные сортируются по указанному выражению (первичному ключу) и пишутся в новый кусок. + * Куски объединяются в фоне, согласно некоторой эвристике. + * Для каждого куска, создаётся индексный файл, содержащий значение первичного ключа для каждой n-ой строки. + * Таким образом, реализуется эффективная выборка по диапазону первичного ключа. + * + * Дополнительно: + * + * Указывается столбец, содержащий дату. + * Для каждого куска пишется минимальная и максимальная дата. + * (по сути - ещё один индекс) + * + * Данные разделяются по разным месяцам (пишутся в разные куски для разных месяцев). + * Куски для разных месяцев не объединяются - для простоты эксплуатации. + * (дают локальность обновлений, что удобно для синхронизации и бэкапа) + * + * Структура файлов: + * / min-date _ max-date _ min-id _ max-id _ level / - директория с куском. + * Внутри директории с куском: + * primary.idx - индексный файл. + * Column.bin - данные столбца + * Column.mrk - засечки, указывающие, откуда начинать чтение, чтобы пропустить n * k строк. + * + * Имеется несколько режимов работы, определяющих, что делать при мердже: + * - Ordinary - ничего дополнительно не делать; + * - Collapsing - при склейке кусков "схлопывать" + * пары записей с разными значениями sign_column для одного значения первичного ключа. + * (см. CollapsingSortedBlockInputStream.h) + * - Summing - при склейке кусков, при совпадении PK суммировать все числовые столбцы, не входящие в PK. + */ + +/** Этот класс хранит список кусков и параметры структуры данных. + * Для чтения и изменения данных используются отдельные классы: + * - MergeTreeDataSelectExecutor + * - MergeTreeDataWriter + * - MergeTreeDataMerger + */ + +struct MergeTreeSettings +{ + /// Набор кусков разрешено объединить, если среди них максимальный размер не более чем во столько раз больше суммы остальных. + double max_size_ratio_to_merge_parts = 5; + + /// Сколько за раз сливать кусков. + /// Трудоемкость выбора кусков O(N * max_parts_to_merge_at_once), так что не следует делать это число слишком большим. + /// С другой стороны, чтобы слияния точно не могли зайти в тупик, нужно хотя бы + /// log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts). + size_t max_parts_to_merge_at_once = 10; + + /// Куски настолько большого размера в основном потоке объединять нельзя вообще. + size_t max_rows_to_merge_parts = 100 * 1024 * 1024; + + /// Куски настолько большого размера во втором потоке объединять нельзя вообще. + size_t max_rows_to_merge_parts_second = 1024 * 1024; + + /// Во столько раз ночью увеличиваем коэффициент. + size_t merge_parts_at_night_inc = 10; + + /// Сколько потоков использовать для объединения кусков. + size_t merging_threads = 2; + + /// Если из одного файла читается хотя бы столько строк, чтение можно распараллелить. + size_t min_rows_for_concurrent_read = 20 * 8192; + + /// Можно пропускать чтение более чем стольки строк ценой одного seek по файлу. + size_t min_rows_for_seek = 5 * 8192; + + /// Если отрезок индекса может содержать нужные ключи, делим его на столько частей и рекурсивно проверяем их. + size_t coarse_index_granularity = 8; + + /** Максимальное количество строк на запрос, для использования кэша разжатых данных. Если запрос большой - кэш не используется. + * (Чтобы большие запросы не вымывали кэш.) + */ + size_t max_rows_to_use_cache = 1024 * 1024; + + /// Через сколько секунд удалять old_куски. + time_t old_parts_lifetime = 5 * 60; +}; + +class MergeTreeData : public ITableDeclaration +{ +public: + /// Описание куска с данными. + struct DataPart + { + DataPart(MergeTreeData & storage_) : storage(storage_), size_in_bytes(0) {} + + MergeTreeData & storage; + DayNum_t left_date; + DayNum_t right_date; + UInt64 left; + UInt64 right; + /// Уровень игнорируется. Использовался предыдущей эвристикой слияния. + UInt32 level; + + std::string name; + size_t size; /// в количестве засечек. + size_t size_in_bytes; /// размер в байтах, 0 - если не посчитано + time_t modification_time; + + DayNum_t left_month; + DayNum_t right_month; + + /// Первичный ключ. Всегда загружается в оперативку. + typedef std::vector Index; + Index index; + + /// NOTE можно загружать засечки тоже в оперативку + + /// Вычисляем сумарный размер всей директории со всеми файлами + static size_t calcTotalSize(const String &from) + { + Poco::File cur(from); + if (cur.isFile()) + return cur.getSize(); + std::vector files; + cur.list(files); + size_t res = 0; + for (size_t i = 0; i < files.size(); ++i) + res += calcTotalSize(from + files[i]); + return res; + } + + void remove() + { + String from = storage.full_path + name + "/"; + String to = storage.full_path + "tmp2_" + name + "/"; + + Poco::File(from).renameTo(to); + Poco::File(to).remove(true); + } + + void renameToOld() const + { + String from = storage.full_path + name + "/"; + String to = storage.full_path + "old_" + name + "/"; + + Poco::File f(from); + f.setLastModified(Poco::Timestamp::fromEpochTime(time(0))); + f.renameTo(to); + } + + bool operator< (const DataPart & rhs) const + { + if (left_month != rhs.left_month) + return left_month < rhs.left_month; + if (right_month != rhs.right_month) + return right_month < rhs.right_month; + + if (left != rhs.left) + return left < rhs.left; + if (right != rhs.right) + return right < rhs.right; + + if (level != rhs.level) + return level < rhs.level; + + return false; + } + + /// Содержит другой кусок (получен после объединения другого куска с каким-то ещё) + bool contains(const DataPart & rhs) const + { + return left_month == rhs.left_month /// Куски за разные месяцы не объединяются + && right_month == rhs.right_month + && level > rhs.level + && left_date <= rhs.left_date + && right_date >= rhs.right_date + && left <= rhs.left + && right >= rhs.right; + } + + /// Загрузить индекс и вычислить размер. + void loadIndex() + { + size_t key_size = storage.sort_descr.size(); + index.resize(key_size * size); + + String index_path = storage.full_path + name + "/primary.idx"; + ReadBufferFromFile index_file(index_path, std::min(static_cast(DBMS_DEFAULT_BUFFER_SIZE), Poco::File(index_path).getSize())); + + for (size_t i = 0; i < size; ++i) + for (size_t j = 0; j < key_size; ++j) + storage.primary_key_sample.getByPosition(j).type->deserializeBinary(index[i * key_size + j], index_file); + + if (!index_file.eof()) + throw Exception("index file " + index_path + " is unexpectedly long", ErrorCodes::EXPECTED_END_OF_FILE); + + size_in_bytes = calcTotalSize(storage.full_path + name + "/"); + } + }; + + typedef std::shared_ptr MutableDataPartPtr; + /// После добавление в рабочее множество DataPart нельзя изменять. + typedef std::shared_ptr DataPartPtr; + struct DataPartPtrLess { bool operator() (const DataPartPtr & lhs, const DataPartPtr & rhs) const { return *lhs < *rhs; } }; + typedef std::set DataParts; + typedef std::vector DataPartsVector; + + + /// Режим работы. См. выше. + enum Mode + { + Ordinary, + Collapsing, + Summing, + }; + + /** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце), + * (корректность имён и путей не проверяется) + * состоящую из указанных столбцов. + * + * primary_expr_ast - выражение для сортировки; + * date_column_name - имя столбца с датой; + * index_granularity - на сколько строчек пишется одно значение индекса. + */ + MergeTreeData( const String & full_path_, NamesAndTypesListPtr columns_, + const Context & context_, + ASTPtr & primary_expr_ast_, + const String & date_column_name_, + const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается. + size_t index_granularity_, + Mode mode_, + const String & sign_column_, + const MergeTreeSettings & settings_); + + std::string getModePrefix() const; + + std::string getSignColumnName() const { return sign_column; } + bool supportsSampling() const { return !!sampling_expression; } + bool supportsFinal() const { return !sign_column.empty(); } + bool supportsPrewhere() const { return true; } + + UInt64 getMaxDataPartIndex(); + + static String getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level); + + /// Возвращает true если имя директории совпадает с форматом имени директории кусочков + bool isPartDirectory(const String & dir_name, Poco::RegularExpression::MatchVec & matches) const; + + /// Кладет в DataPart данные из имени кусочка. + void parsePartName(const String & file_name, const Poco::RegularExpression::MatchVec & matches, DataPart & part); + + std::string getTableName() { return ""; } + + const NamesAndTypesList & getColumnsList() const { return *columns; } + + String getFullPath() const { return full_path; } + + /** Возвращает копию списка, чтобы снаружи можно было не заботиться о блокировках. + */ + DataParts getDataParts(); + + /** Удаляет куски old_parts и добавляет кусок new_part. Если какого-нибудь из удаляемых кусков нет, бросает исключение. + */ + void replaceParts(DataPartsVector old_parts, DataPartPtr new_part); + + /** Переименовывает временный кусок в постоянный и добавляет его в рабочий набор. + * Если increment!=nullptr, индекс куска берется из инкремента. Иначе индекс куска не меняется. + */ + void renameTempPartAndAdd(MutableDataPartPtr part, Increment * increment); + + /** Удалить неактуальные куски. + */ + void clearOldParts(); + + /** После вызова dropAllData больше ничего вызывать нельзя. + * Удаляет директорию с данными и сбрасывает кеши разжатых блоков и засечек. + */ + void dropAllData(); + + /** Перемещает всю директорию с данными. + * Сбрасывает кеши разжатых блоков и засечек. + * Нужно вызывать под залоченным lockStructure(). + */ + void setPath(const String & full_path); + + void alter(const ASTAlterQuery::Parameters & params); + void prepareAlterModify(const ASTAlterQuery::Parameters & params); + void commitAlterModify(const ASTAlterQuery::Parameters & params); + + ExpressionActionsPtr getPrimaryExpression() const { return primary_expr; } + SortDescription getSortDescription() const { return sort_descr; } + + const Context & context; + const String date_column_name; + const ASTPtr sampling_expression; + const size_t index_granularity; + + /// Режим работы - какие дополнительные действия делать при мердже. + const Mode mode; + /// Для схлопывания записей об изменениях, если используется Collapsing режим работы. + const String sign_column; + + const MergeTreeSettings settings; + +private: + ExpressionActionsPtr primary_expr; + SortDescription sort_descr; + Block primary_key_sample; + + ASTPtr primary_expr_ast; + + String full_path; + + NamesAndTypesListPtr columns; + + Logger * log; + volatile bool shutdown_called; + + /// Регулярное выражение соответсвующее названию директории с кусочками + Poco::RegularExpression file_name_regexp; + + /** Актуальное множество кусков с данными. */ + DataParts data_parts; + Poco::FastMutex data_parts_mutex; + + /** Множество всех кусков с данными, включая уже слитые в более крупные, но ещё не удалённые. Оно обычно небольшое (десятки элементов). + * Ссылки на кусок есть отсюда, из списка актуальных кусков и из каждого потока чтения, который его сейчас использует. + * То есть, если количество ссылок равно 1 - то кусок не актуален и не используется прямо сейчас, и его можно удалить. + */ + DataParts all_data_parts; + Poco::FastMutex all_data_parts_mutex; + + /// Загрузить множество кусков с данными с диска. Вызывается один раз - при создании объекта. + void loadDataParts(); + + void removeColumnFiles(String column_name); + + /// Определить, не битые ли данные в директории. Проверяет индекс и засечеки, но не сами данные. + bool isBrokenPart(const String & path); + + /// Найти самые большие old_куски, из которых получен этот кусок. + /// Переименовать их, убрав префикс old_ и вернуть их имена. + Strings tryRestorePart(const String & path, const String & file_name, Strings & old_parts); + + void createConvertExpression(const String & in_column_name, const String & out_type, ExpressionActionsPtr & out_expression, String & out_column); +}; + +} diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h new file mode 100644 index 00000000000..5f10ca5df0e --- /dev/null +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +namespace DB +{ + +/** Умеет выбирать куски для слияния и сливать их. + */ +class MergeTreeDataMerger +{ +public: + MergeTreeDataMerger(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataMerger")), canceled(false) {} + + typedef boost::function AllowedMergingPredicate; + + /** Выбирает, какие куски слить. Использует кучу эвристик. + * Если merge_anything_for_old_months, для кусков за прошедшие месяцы снимается ограничение на соотношение размеров. + * + * can_merge - функция, определяющая, можно ли объединить пару соседних кусков. + * Эта функция должна координировать слияния со вставками и другими слияниями, обеспечивая, что: + * - Куски, между которыми еще может появиться новый кусок, нельзя сливать. См. METR-7001. + * - Кусок, который уже сливается с кем-то в одном месте, нельзя начать сливать в кем-то другим в другом месте. + */ + bool selectPartsToMerge( + MergeTreeData::DataPartsVector & what, + size_t available_disk_space, + bool merge_anything_for_old_months, + bool aggressive, + bool only_small, + const AllowedMergingPredicate & can_merge); + + /// Сливает куски. Возвращает название нового куска. Если слияние отменили, возвращает пустую строку. + String mergeParts(const MergeTreeData::DataPartsVector & parts); + + /// Примерное количество места на диске, нужное для мерджа. С запасом. + size_t estimateDiskSpaceForMerge(const MergeTreeData::DataPartsVector & parts); + + /** Отменяет все текущие мерджи. Все выполняющиеся сейчас вызовы mergeParts скоро отменят слияние и вернут пустую строку. + * После этого с этим экземпляром ничего делать нельзя. + */ + void cancelAll() { canceled = true; } + +private: + MergeTreeData & data; + + Logger * log; + + volatile bool canceled; +}; + +} diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h new file mode 100644 index 00000000000..7ea10726200 --- /dev/null +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +namespace DB +{ + + +/** Выполняет запросы SELECT на данных из merge-дерева. + */ +class MergeTreeDataSelectExecutor +{ +public: + MergeTreeDataSelectExecutor(MergeTreeData & data_); + + /** При чтении, выбирается набор кусков, покрывающий нужный диапазон индекса. + */ + BlockInputStreams read( + const Names & column_names, + ASTPtr query, + const Settings & settings, + QueryProcessingStage::Enum & processed_stage, + size_t max_block_size = DEFAULT_BLOCK_SIZE, + unsigned threads = 1); + +private: + MergeTreeData & data; + + Logger * log; + + struct RangesInDataPart + { + MergeTreeData::DataPartPtr data_part; + MarkRanges ranges; + + RangesInDataPart() {} + + RangesInDataPart(MergeTreeData::DataPartPtr data_part_) + : data_part(data_part_) + { + } + }; + + typedef std::vector RangesInDataParts; + + size_t min_marks_for_seek; + size_t min_marks_for_concurrent_read; + size_t max_marks_to_use_cache; + + BlockInputStreams spreadMarkRangesAmongThreads( + RangesInDataParts parts, + size_t threads, + const Names & column_names, + size_t max_block_size, + bool use_uncompressed_cache, + ExpressionActionsPtr prewhere_actions, + const String & prewhere_column); + + BlockInputStreams spreadMarkRangesAmongThreadsFinal( + RangesInDataParts parts, + size_t threads, + const Names & column_names, + size_t max_block_size, + bool use_uncompressed_cache, + ExpressionActionsPtr prewhere_actions, + const String & prewhere_column); + + /// Создать выражение "Sign == 1". + void createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column); + + MarkRanges markRangesFromPkRange(const MergeTreeData::DataPart::Index & index, PKCondition & key_condition); +}; + +} diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h new file mode 100644 index 00000000000..6e720bfd791 --- /dev/null +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +#include + +#include + +#include + + +namespace DB +{ + +struct BlockWithDateInterval +{ + Block block; + UInt16 min_date; + UInt16 max_date; + + BlockWithDateInterval() : min_date(std::numeric_limits::max()), max_date(0) {} + BlockWithDateInterval(const Block & block_, UInt16 min_date_, UInt16 max_date_) + : block(block_), min_date(min_date_), max_date(max_date_) {} +}; + +typedef std::list BlocksWithDateIntervals; + +/** Записывает новые куски с данными в merge-дерево. + */ +class MergeTreeDataWriter +{ +public: + MergeTreeDataWriter(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataWriter")), flags(O_TRUNC | O_CREAT | O_WRONLY) {} + + /** Разбивает блок на блоки, каждый из которых нужно записать в отдельный кусок. + * (читай: разбивает строки по месяцам) + * Работает детерминированно: если отдать на вход такой же блок, на выходе получатся такие же блоки в таком же порядке. + */ + BlocksWithDateIntervals splitBlockIntoParts(const Block & block); + + /** Все строки должны относиться к одному месяцу. Возвращает название временного куска. + * temp_index - значение left и right для нового куска. Можно будет изменить при переименовании. + * Возвращает кусок с именем, начинающимся с tmp_, еще не добавленный в MergeTreeData. + */ + MergeTreeData::MutableDataPartPtr writeTempPart(BlockWithDateInterval & block, UInt64 temp_index); + +private: + MergeTreeData & data; + + Logger * log; + + const int flags; + + typedef std::set OffsetColumns; + + /// Записать данные одного столбца. + void writeData(const String & path, const String & name, const IDataType & type, const IColumn & column, + OffsetColumns & offset_columns, size_t level = 0); +}; + +} diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h index 8c3833a6eaa..f6ee8257d0b 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h @@ -1,12 +1,15 @@ #pragma once -#include +#include #include #include +#include #include #include #include #include +#include +#include #define MERGE_TREE_MARK_SIZE (2 * sizeof(size_t)) @@ -15,6 +18,20 @@ namespace DB { +/** Пара засечек, определяющая диапазон строк в куске. Именно, диапазон имеет вид [begin * index_granularity, end * index_granularity). + */ +struct MarkRange +{ + size_t begin; + size_t end; + + MarkRange() {} + MarkRange(size_t begin_, size_t end_) : begin(begin_), end(end_) {} +}; + +typedef std::vector MarkRanges; + + /** Умеет читать данные между парой засечек из одного куска. При чтении последовательных отрезков не делает лишних seek-ов. * При чтении почти последовательных отрезков делает seek-и быстро, не выбрасывая содержимое буфера. */ @@ -22,7 +39,7 @@ class MergeTreeReader { public: MergeTreeReader(const String & path_, /// Путь к куску - const Names & columns_names_, bool use_uncompressed_cache_, StorageMergeTree & storage_) + const Names & columns_names_, bool use_uncompressed_cache_, MergeTreeData & storage_) : path(path_), column_names(columns_names_), use_uncompressed_cache(use_uncompressed_cache_), storage(storage_) { for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it) @@ -220,7 +237,7 @@ private: FileStreams streams; Names column_names; bool use_uncompressed_cache; - StorageMergeTree & storage; + MergeTreeData & storage; void addStream(const String & name, const IDataType & type, size_t level = 0) { diff --git a/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h index a97e6eac4c8..6a745460985 100644 --- a/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h @@ -3,7 +3,7 @@ #include #include -#include +#include namespace DB @@ -11,7 +11,7 @@ namespace DB class IMergedBlockOutputStream : public IBlockOutputStream { public: - IMergedBlockOutputStream(StorageMergeTree & storage_) : storage(storage_), index_offset(0) + IMergedBlockOutputStream(MergeTreeData & storage_) : storage(storage_), index_offset(0) { } @@ -182,7 +182,7 @@ protected: } } - StorageMergeTree & storage; + MergeTreeData & storage; ColumnStreams column_streams; @@ -196,7 +196,7 @@ protected: class MergedBlockOutputStream : public IMergedBlockOutputStream { public: - MergedBlockOutputStream(StorageMergeTree & storage_, + MergedBlockOutputStream(MergeTreeData & storage_, UInt16 min_date, UInt16 max_date, UInt64 min_part_id, UInt64 max_part_id, UInt32 level) : IMergedBlockOutputStream(storage_), marks_count(0) { @@ -204,17 +204,18 @@ public: DayNum_t(min_date), DayNum_t(max_date), min_part_id, max_part_id, level); - part_tmp_path = storage.full_path + "tmp_" + part_name + "/"; - part_res_path = storage.full_path + part_name + "/"; + part_tmp_path = storage.getFullPath() + "tmp_" + part_name + "/"; + part_res_path = storage.getFullPath() + part_name + "/"; Poco::File(part_tmp_path).createDirectories(); index_stream = new WriteBufferFromFile(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, O_TRUNC | O_CREAT | O_WRONLY); - for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it) - addStream(part_tmp_path, it->first, *it->second); + columns_list = storage.getColumnsList(); + for (const auto & it : columns_list) + addStream(part_tmp_path, it.first, *it.second); } - + void write(const Block & block) { size_t rows = block.rows(); @@ -223,11 +224,11 @@ public: typedef std::vector PrimaryColumns; PrimaryColumns primary_columns; - for (size_t i = 0, size = storage.sort_descr.size(); i < size; ++i) + for (const auto & descr : storage.getSortDescription()) primary_columns.push_back( - !storage.sort_descr[i].column_name.empty() - ? &block.getByName(storage.sort_descr[i].column_name) - : &block.getByPosition(storage.sort_descr[i].column_number)); + !descr.column_name.empty() + ? &block.getByName(descr.column_name) + : &block.getByPosition(descr.column_number)); for (size_t i = index_offset; i < rows; i += storage.index_granularity) { @@ -243,9 +244,9 @@ public: OffsetColumns offset_columns; /// Теперь пишем данные. - for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it) + for (const auto & it : columns_list) { - const ColumnWithNameAndType & column = block.getByName(it->first); + const ColumnWithNameAndType & column = block.getByName(it.first); writeData(column.name, *column.type, *column.column, offset_columns); } @@ -285,6 +286,8 @@ public: } private: + NamesAndTypesList columns_list; + String part_name; String part_tmp_path; String part_res_path; @@ -299,7 +302,7 @@ typedef Poco::SharedPtr MergedBlockOutputStreamPtr; class MergedColumnOnlyOutputStream : public IMergedBlockOutputStream { public: - MergedColumnOnlyOutputStream(StorageMergeTree & storage_, String part_path_, bool sync_ = false) : + MergedColumnOnlyOutputStream(MergeTreeData & storage_, String part_path_, bool sync_ = false) : IMergedBlockOutputStream(storage_), part_path(part_path_), initialized(false), sync(sync_) { } diff --git a/dbms/include/DB/Storages/MergeTree/PKCondition.h b/dbms/include/DB/Storages/MergeTree/PKCondition.h index d55dd38b037..b90e4f8ee2e 100644 --- a/dbms/include/DB/Storages/MergeTree/PKCondition.h +++ b/dbms/include/DB/Storages/MergeTree/PKCondition.h @@ -3,7 +3,6 @@ #include #include -#include #include #include #include diff --git a/dbms/include/DB/Storages/StorageChunkRef.h b/dbms/include/DB/Storages/StorageChunkRef.h index 28a2cf6fdf2..d6e63744828 100644 --- a/dbms/include/DB/Storages/StorageChunkRef.h +++ b/dbms/include/DB/Storages/StorageChunkRef.h @@ -32,7 +32,7 @@ public: ASTPtr getCustomCreateQuery(const Context & context) const; - void dropImpl(); + void drop() override; String source_database_name; String source_table_name; diff --git a/dbms/include/DB/Storages/StorageDistributed.h b/dbms/include/DB/Storages/StorageDistributed.h index 2ffa9ce2791..a856c8c0412 100644 --- a/dbms/include/DB/Storages/StorageDistributed.h +++ b/dbms/include/DB/Storages/StorageDistributed.h @@ -58,7 +58,7 @@ public: size_t max_block_size = DEFAULT_BLOCK_SIZE, unsigned threads = 1); - void dropImpl() {} + void drop() override {} void rename(const String & new_path_to_db, const String & new_name) { name = new_name; } /// в подтаблицах добавлять и удалять столбы нужно вручную /// структура подтаблиц не проверяется diff --git a/dbms/include/DB/Storages/StorageLog.h b/dbms/include/DB/Storages/StorageLog.h index 91ca50e5193..0c74c712a92 100644 --- a/dbms/include/DB/Storages/StorageLog.h +++ b/dbms/include/DB/Storages/StorageLog.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace DB @@ -36,20 +37,10 @@ typedef std::vector Marks; class LogBlockInputStream : public IProfilingBlockInputStream { public: - LogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage, size_t mark_number_, size_t rows_limit_); + LogBlockInputStream(size_t block_size_, const Names & column_names_, StorageLog & storage_, size_t mark_number_, size_t rows_limit_); String getName() const { return "LogBlockInputStream"; } - String getID() const - { - std::stringstream res; - res << "Log(" << owned_storage->getTableName() << ", " << &*owned_storage << ", " << mark_number << ", " << rows_limit; - - for (size_t i = 0; i < column_names.size(); ++i) - res << ", " << column_names[i]; - - res << ")"; - return res.str(); - } + String getID() const; protected: Block readImpl(); @@ -88,7 +79,7 @@ private: class LogBlockOutputStream : public IBlockOutputStream { public: - LogBlockOutputStream(StoragePtr owned_storage); + LogBlockOutputStream(StorageLog & storage_); void write(const Block & block); void writeSuffix(); private: diff --git a/dbms/include/DB/Storages/StorageMaterializedView.h b/dbms/include/DB/Storages/StorageMaterializedView.h index c29269376b4..bbe2807270f 100644 --- a/dbms/include/DB/Storages/StorageMaterializedView.h +++ b/dbms/include/DB/Storages/StorageMaterializedView.h @@ -16,7 +16,7 @@ public: std::string getInnerTableName() const { return ".inner." + table_name; } BlockOutputStreamPtr write(ASTPtr query); - void dropImpl(); + void drop() override; bool optimize(); BlockInputStreams read( diff --git a/dbms/include/DB/Storages/StorageMemory.h b/dbms/include/DB/Storages/StorageMemory.h index bf18e22271a..e6ab8d97b4b 100644 --- a/dbms/include/DB/Storages/StorageMemory.h +++ b/dbms/include/DB/Storages/StorageMemory.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace DB @@ -15,13 +16,13 @@ class StorageMemory; class MemoryBlockInputStream : public IProfilingBlockInputStream { public: - MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_, StoragePtr owned_storage); + MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_); String getName() const { return "MemoryBlockInputStream"; } String getID() const { std::stringstream res; - res << "Memory(" << owned_storage->getTableName() << ", " << &*owned_storage << ", " << &*begin << ", " << &*end; + res << "Memory(" << &*begin << ", " << &*end; for (size_t i = 0; i < column_names.size(); ++i) res << ", " << column_names[i]; @@ -43,7 +44,7 @@ private: class MemoryBlockOutputStream : public IBlockOutputStream { public: - MemoryBlockOutputStream(StoragePtr owned_storage); + MemoryBlockOutputStream(StorageMemory & storage_); void write(const Block & block); private: StorageMemory & storage; @@ -79,7 +80,7 @@ public: BlockOutputStreamPtr write( ASTPtr query); - void dropImpl(); + void drop() override; void rename(const String & new_path_to_db, const String & new_name) { name = new_name; } private: diff --git a/dbms/include/DB/Storages/StorageMerge.h b/dbms/include/DB/Storages/StorageMerge.h index 9fe18d20dec..f0a474f1e6a 100644 --- a/dbms/include/DB/Storages/StorageMerge.h +++ b/dbms/include/DB/Storages/StorageMerge.h @@ -17,8 +17,6 @@ typedef Poco::SharedPtr StorageMergePtr; */ class StorageMerge : public IStorage { -typedef std::vector SelectedTables; - public: static StoragePtr create( const std::string & name_, /// Имя таблицы. @@ -43,7 +41,7 @@ public: size_t max_block_size = DEFAULT_BLOCK_SIZE, unsigned threads = 1); - void dropImpl() {} + void drop() override {} void rename(const String & new_path_to_db, const String & new_name) { name = new_name; } void getSelectedTables(StorageVector & selected_tables); diff --git a/dbms/include/DB/Storages/StorageMergeTree.h b/dbms/include/DB/Storages/StorageMergeTree.h index 3716f8a5538..bb8c5b8cf45 100644 --- a/dbms/include/DB/Storages/StorageMergeTree.h +++ b/dbms/include/DB/Storages/StorageMergeTree.h @@ -1,123 +1,21 @@ #pragma once -#include -#include - -#include -#include -#include -#include -#include - +#include +#include "MergeTree/MergeTreeDataSelectExecutor.h" +#include "MergeTree/MergeTreeDataWriter.h" +#include "MergeTree/MergeTreeDataMerger.h" +#include "MergeTree/DiskSpaceMonitor.h" namespace DB { -/** Движок, использующий merge tree для инкрементальной сортировки данных. - * Таблица представлена набором сортированных кусков. - * При вставке, данные сортируются по указанному выражению (первичному ключу) и пишутся в новый кусок. - * Куски объединяются в фоне, согласно некоторой эвристике. - * Для каждого куска, создаётся индексный файл, содержащий значение первичного ключа для каждой n-ой строки. - * Таким образом, реализуется эффективная выборка по диапазону первичного ключа. - * - * Дополнительно: - * - * Указывается столбец, содержащий дату. - * Для каждого куска пишется минимальная и максимальная дата. - * (по сути - ещё один индекс) - * - * Данные разделяются по разным месяцам (пишутся в разные куски для разных месяцев). - * Куски для разных месяцев не объединяются - для простоты эксплуатации. - * (дают локальность обновлений, что удобно для синхронизации и бэкапа) - * - * Структура файлов: - * / increment.txt - файл, содержащий одно число, увеличивающееся на 1 - для генерации идентификаторов кусков. - * / min-date _ max-date _ min-id _ max-id _ level / - директория с куском. - * / min-date _ max-date _ min-id _ max-id _ level / primary.idx - индексный файл. - * Внутри директории с куском: - * Column.bin - данные столбца - * Column.mrk - засечки, указывающие, откуда начинать чтение, чтобы пропустить n * k строк. - * - * Имеется несколько режимов работы, определяющих, что делать при мердже: - * - Ordinary - ничего дополнительно не делать; - * - Collapsing - при склейке кусков "схлопывать" - * пары записей с разными значениями sign_column для одного значения первичного ключа. - * (см. CollapsingSortedBlockInputStream.h) - * - Summing - при склейке кусков, при совпадении PK суммировать все числовые столбцы, не входящие в PK. +/** См. описание структуры данных в MergeTreeData. */ - -struct StorageMergeTreeSettings -{ - /// Набор кусков разрешено объединить, если среди них максимальный размер не более чем во столько раз больше суммы остальных. - double max_size_ratio_to_merge_parts = 5; - - /// Сколько за раз сливать кусков. - /// Трудоемкость выбора кусков O(N * max_parts_to_merge_at_once), так что не следует делать это число слишком большим. - /// С другой стороны, чтобы слияния точно не могли зайти в тупик, нужно хотя бы - /// log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts). - size_t max_parts_to_merge_at_once = 10; - - /// Куски настолько большого размера в основном потоке объединять нельзя вообще. - size_t max_rows_to_merge_parts = 100 * 1024 * 1024; - - /// Куски настолько большого размера во втором потоке объединять нельзя вообще. - size_t max_rows_to_merge_parts_second = 1024 * 1024; - - /// Во столько раз ночью увеличиваем коэффициент. - size_t merge_parts_at_night_inc = 10; - - /// Сколько потоков использовать для объединения кусков. - size_t merging_threads = 2; - - /// Если из одного файла читается хотя бы столько строк, чтение можно распараллелить. - size_t min_rows_for_concurrent_read = 20 * 8192; - - /// Можно пропускать чтение более чем стольки строк ценой одного seek по файлу. - size_t min_rows_for_seek = 5 * 8192; - - /// Если отрезок индекса может содержать нужные ключи, делим его на столько частей и рекурсивно проверяем их. - size_t coarse_index_granularity = 8; - - /** Максимальное количество строк на запрос, для использования кэша разжатых данных. Если запрос большой - кэш не используется. - * (Чтобы большие запросы не вымывали кэш.) - */ - size_t max_rows_to_use_cache = 1024 * 1024; - - /// Через сколько секунд удалять old_куски. - time_t old_parts_lifetime = 5 * 60; -}; - -/// Пара засечек, определяющая диапазон строк в куске. Именно, диапазон имеет вид [begin * index_granularity, end * index_granularity). -struct MarkRange -{ - size_t begin; - size_t end; - - MarkRange() {} - MarkRange(size_t begin_, size_t end_) : begin(begin_), end(end_) {} -}; - -typedef std::vector MarkRanges; - - class StorageMergeTree : public IStorage { -friend class MergeTreeReader; -friend class MergeTreeBlockInputStream; friend class MergeTreeBlockOutputStream; -friend class IMergedBlockOutputStream; -friend class MergedBlockOutputStream; -friend class MergedColumnOnlyOutputStream; public: - /// Режим работы. См. выше. - enum Mode - { - Ordinary, - Collapsing, - Summing, - }; - /** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце), * (корректность имён и путей не проверяется) * состоящую из указанных столбцов. @@ -132,36 +30,26 @@ public: const String & date_column_name_, const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается. size_t index_granularity_, - Mode mode_ = Ordinary, + MergeTreeData::Mode mode_ = MergeTreeData::Ordinary, const String & sign_column_ = "", - const StorageMergeTreeSettings & settings_ = StorageMergeTreeSettings()); + const MergeTreeSettings & settings_ = MergeTreeSettings()); void shutdown(); ~StorageMergeTree(); std::string getName() const { - switch (mode) - { - case Ordinary: return "MergeTree"; - case Collapsing: return "CollapsingMergeTree"; - case Summing: return "SummingMergeTree"; - - default: - throw Exception("Unknown mode of operation for StorageMergeTree: " + toString(mode), ErrorCodes::LOGICAL_ERROR); - } + return data.getModePrefix() + "MergeTree"; } std::string getTableName() const { return name; } - std::string getSignColumnName() const { return sign_column; } - bool supportsSampling() const { return !!sampling_expression; } - bool supportsFinal() const { return !sign_column.empty(); } - bool supportsPrewhere() const { return true; } + std::string getSignColumnName() const { return data.getSignColumnName(); } + bool supportsSampling() const { return data.supportsSampling(); } + bool supportsFinal() const { return data.supportsFinal(); } + bool supportsPrewhere() const { return data.supportsPrewhere(); } - const NamesAndTypesList & getColumnsList() const { return *columns; } + const NamesAndTypesList & getColumnsList() const { return data.getColumnsList(); } - /** При чтении, выбирается набор кусков, покрывающий нужный диапазон индекса. - */ BlockInputStreams read( const Names & column_names, ASTPtr query, @@ -170,8 +58,6 @@ public: size_t max_block_size = DEFAULT_BLOCK_SIZE, unsigned threads = 1); - /** При записи, данные сортируются и пишутся в новые куски. - */ BlockOutputStreamPtr write(ASTPtr query); /** Выполнить очередной шаг объединения кусков. @@ -182,264 +68,75 @@ public: return true; } - void dropImpl(); - + void drop() override; + void rename(const String & new_path_to_db, const String & new_name); - /// Метод ALTER позволяет добавлять и удалять столбцы. - /// Метод ALTER нужно применять, когда обращения к базе приостановлены. - /// Например если параллельно с INSERT выполнить ALTER, то ALTER выполниться, а INSERT бросит исключение void alter(const ASTAlterQuery::Parameters & params); - - class BigLock - { - public: - BigLock(StorageMergeTree & storage) : merge_lock(storage.merge_lock), - write_lock(storage.write_lock), read_lock(storage.read_lock) - { - } - - private: - Poco::ScopedWriteRWLock merge_lock; - Poco::ScopedWriteRWLock write_lock; - Poco::ScopedWriteRWLock read_lock; - }; - - typedef Poco::SharedPtr BigLockPtr; - BigLockPtr lockAllOperations() - { - return new BigLock(*this); - } + void prepareAlterModify(const ASTAlterQuery::Parameters & params); + void commitAlterModify(const ASTAlterQuery::Parameters & params); private: String path; String name; String full_path; - NamesAndTypesListPtr columns; - - const Context & context; - ASTPtr primary_expr_ast; - String date_column_name; - ASTPtr sampling_expression; - size_t index_granularity; - - size_t min_marks_for_seek; - size_t min_marks_for_concurrent_read; - size_t max_marks_to_use_cache; - - /// Режим работы - какие дополнительные действия делать при мердже. - Mode mode; - /// Для схлопывания записей об изменениях, если используется Collapsing режим работы. - String sign_column; - - StorageMergeTreeSettings settings; - - ExpressionActionsPtr primary_expr; - SortDescription sort_descr; - Block primary_key_sample; - Increment increment; + MergeTreeData data; + MergeTreeDataSelectExecutor reader; + MergeTreeDataWriter writer; + MergeTreeDataMerger merger; + + MergeTreeData::DataParts currently_merging; + Poco::FastMutex currently_merging_mutex; + Logger * log; + volatile bool shutdown_called; - /// Регулярное выражение соответсвующее названию директории с кусочками - Poco::RegularExpression file_name_regexp; + Poco::SharedPtr merge_threads; - /// Описание куска с данными. - struct DataPart - { - DataPart(StorageMergeTree & storage_) : storage(storage_), size_in_bytes(0), currently_merging(false) {} - - StorageMergeTree & storage; - DayNum_t left_date; - DayNum_t right_date; - UInt64 left; - UInt64 right; - /// Уровень игнорируется. Использовался предыдущей эвристикой слияния. - UInt32 level; - - std::string name; - size_t size; /// в количестве засечек. - size_t size_in_bytes; /// размер в байтах, 0 - если не посчитано - time_t modification_time; - - DayNum_t left_month; - DayNum_t right_month; - - /// Смотреть и изменять это поле следует под залоченным data_parts_mutex. - bool currently_merging; - - /// Первичный ключ. Всегда загружается в оперативку. - typedef std::vector Index; - Index index; - - /// NOTE можно загружать засечки тоже в оперативку - - /// Вычисляем сумарный размер всей директории со всеми файлами - static size_t calcTotalSize(const String &from) - { - Poco::File cur(from); - if (cur.isFile()) - return cur.getSize(); - std::vector files; - cur.list(files); - size_t res = 0; - for (size_t i = 0; i < files.size(); ++i) - res += calcTotalSize(from + files[i]); - return res; - } - - void remove() const - { - String from = storage.full_path + name + "/"; - String to = storage.full_path + "tmp2_" + name + "/"; - - Poco::File(from).renameTo(to); - Poco::File(to).remove(true); - } - - void renameToOld() const - { - String from = storage.full_path + name + "/"; - String to = storage.full_path + "old_" + name + "/"; - - Poco::File f(from); - f.setLastModified(Poco::Timestamp::fromEpochTime(time(0))); - f.renameTo(to); - } - - bool operator< (const DataPart & rhs) const - { - if (left_month < rhs.left_month) - return true; - if (left_month > rhs.left_month) - return false; - if (right_month < rhs.right_month) - return true; - if (right_month > rhs.right_month) - return false; - - if (left < rhs.left) - return true; - if (left > rhs.left) - return false; - if (right < rhs.right) - return true; - if (right > rhs.right) - return false; - - if (level < rhs.level) - return true; - - return false; - } - - /// Содержит другой кусок (получен после объединения другого куска с каким-то ещё) - bool contains(const DataPart & rhs) const - { - return left_month == rhs.left_month /// Куски за разные месяцы не объединяются - && right_month == rhs.right_month - && level > rhs.level - && left_date <= rhs.left_date - && right_date >= rhs.right_date - && left <= rhs.left - && right >= rhs.right; - } - - /// Загрузить индекс и вычислить размер. - void loadIndex() - { - size_t key_size = storage.sort_descr.size(); - index.resize(key_size * size); - - String index_path = storage.full_path + name + "/primary.idx"; - ReadBufferFromFile index_file(index_path, std::min(static_cast(DBMS_DEFAULT_BUFFER_SIZE), Poco::File(index_path).getSize())); - - for (size_t i = 0; i < size; ++i) - for (size_t j = 0; j < key_size; ++j) - storage.primary_key_sample.getByPosition(j).type->deserializeBinary(index[i * key_size + j], index_file); - - if (!index_file.eof()) - throw Exception("index file " + index_path + " is unexpectedly long", ErrorCodes::EXPECTED_END_OF_FILE); - - size_in_bytes = calcTotalSize(storage.full_path + name + "/"); - } - }; - - typedef SharedPtr DataPartPtr; - struct DataPartPtrLess { bool operator() (const DataPartPtr & lhs, const DataPartPtr & rhs) const { return *lhs < *rhs; } }; - typedef std::set DataParts; - - struct RangesInDataPart - { - DataPartPtr data_part; - MarkRanges ranges; - - RangesInDataPart() {} - - RangesInDataPart(DataPartPtr data_part_) - : data_part(data_part_) - { - } - }; - - /// Пока существует, помечает части как currently_merging и пересчитывает общий объем сливаемых данных. + /// Пока существует, помечает части как currently_merging и держит резерв места. /// Вероятно, что части будут помечены заранее. - class CurrentlyMergingPartsTagger + struct CurrentlyMergingPartsTagger { - public: - std::vector parts; - Poco::FastMutex & data_mutex; + MergeTreeData::DataPartsVector parts; + DiskSpaceMonitor::ReservationPtr reserved_space; + StorageMergeTree & storage; - CurrentlyMergingPartsTagger(const std::vector & parts_, Poco::FastMutex & data_mutex_) : parts(parts_), data_mutex(data_mutex_) + CurrentlyMergingPartsTagger(const MergeTreeData::DataPartsVector & parts_, size_t total_size, StorageMergeTree & storage_) + : parts(parts_), storage(storage_) { - /// Здесь не лочится мьютекс, так как конструктор вызывается внутри selectPartsToMerge, где он уже залочен - /// Poco::ScopedLock lock(data_mutex); - for (size_t i = 0; i < parts.size(); ++i) + /// Здесь не лочится мьютекс, так как конструктор вызывается внутри mergeThread, где он уже залочен. + reserved_space = DiskSpaceMonitor::reserve(storage.full_path, total_size); /// Может бросить исключение. + for (const auto & part : parts) { - parts[i]->currently_merging = true; - StorageMergeTree::total_size_of_currently_merging_parts += parts[i]->size_in_bytes; + if (storage.currently_merging.count(part)) + throw Exception("Tagging alreagy tagged part " + part->name + ". This is a bug.", ErrorCodes::LOGICAL_ERROR); } + storage.currently_merging.insert(parts.begin(), parts.end()); } ~CurrentlyMergingPartsTagger() { - Poco::ScopedLock lock(data_mutex); - for (size_t i = 0; i < parts.size(); ++i) + try { - parts[i]->currently_merging = false; - StorageMergeTree::total_size_of_currently_merging_parts -= parts[i]->size_in_bytes; + Poco::ScopedLock lock(storage.currently_merging_mutex); + for (const auto & part : parts) + { + if (!storage.currently_merging.count(part)) + throw Exception("Untagging already untagged part " + part->name + ". This is a bug.", ErrorCodes::LOGICAL_ERROR); + storage.currently_merging.erase(part); + } + } + catch (...) + { + tryLogCurrentException("~CurrentlyMergingPartsTagger"); } } }; - /// Сумарный размер currently_merging кусочков в байтах. - /// Нужно чтобы оценить количество места на диске, которое может понадобится для завершения этих мерджей. - static size_t total_size_of_currently_merging_parts; - - typedef std::vector RangesInDataParts; - - /** @warning Если берете насколько блокировок, то берите их везде в одинаковом порядке - в том же как они написаны в этом файле */ - /** Взятие этого лока на запись, запрещает мердж */ - Poco::RWLock merge_lock; - - /** Взятие этого лока на запись, запрещает запись */ - Poco::RWLock write_lock; - - /** Взятие этого лока на запись, запрещает чтение */ - Poco::RWLock read_lock; - - /** Актуальное множество кусков с данными. */ - DataParts data_parts; - Poco::FastMutex data_parts_mutex; - - /** Множество всех кусков с данными, включая уже слитые в более крупные, но ещё не удалённые. Оно обычно небольшое (десятки элементов). - * Ссылки на кусок есть отсюда, из списка актуальных кусков, и из каждого потока чтения, который его сейчас использует. - * То есть, если количество ссылок равно 1 - то кусок не актуален и не используется прямо сейчас, и его можно удалить. - */ - DataParts all_data_parts; - Poco::FastMutex all_data_parts_mutex; + typedef Poco::SharedPtr CurrentlyMergingPartsTaggerPtr; StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_, const Context & context_, @@ -447,74 +144,25 @@ private: const String & date_column_name_, const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается. size_t index_granularity_, - Mode mode_ = Ordinary, - const String & sign_column_ = "", - const StorageMergeTreeSettings & settings_ = StorageMergeTreeSettings()); - - static String getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level); + MergeTreeData::Mode mode_, + const String & sign_column_, + const MergeTreeSettings & settings_); - BlockInputStreams spreadMarkRangesAmongThreads( - RangesInDataParts parts, - size_t threads, - const Names & column_names, - size_t max_block_size, - bool use_uncompressed_cache, - ExpressionActionsPtr prewhere_actions, - const String & prewhere_column); - BlockInputStreams spreadMarkRangesAmongThreadsFinal( - RangesInDataParts parts, - size_t threads, - const Names & column_names, - size_t max_block_size, - bool use_uncompressed_cache, - ExpressionActionsPtr prewhere_actions, - const String & prewhere_column); - - /// Создать выражение "Sign == 1". - void createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column); - - /// Загрузить множество кусков с данными с диска. Вызывается один раз - при создании объекта. - void loadDataParts(); - - /// Удалить неактуальные куски. - void clearOldParts(); /** Определяет, какие куски нужно объединять, и запускает их слияние в отдельном потоке. Если iterations = 0, объединяет, пока это возможно. * Если aggressive - выбрать куски не обращая внимание на соотношение размеров и их новизну (для запроса OPTIMIZE). */ void merge(size_t iterations = 1, bool async = true, bool aggressive = false); - + /// Если while_can, объединяет в цикле, пока можно; иначе выбирает и объединяет только одну пару кусков. void mergeThread(bool while_can, bool aggressive); - - /// Сразу помечает их как currently_merging. - /// Если merge_anything_for_old_months, для кусков за прошедшие месяцы снимается ограничение на соотношение размеров. - bool selectPartsToMerge(Poco::SharedPtr & what, bool merge_anything_for_old_months, bool aggressive); - void mergeParts(Poco::SharedPtr & what); - /// Дождаться, пока фоновые потоки закончат слияния. void joinMergeThreads(); - Poco::SharedPtr merge_threads; - - void removeColumnFiles(String column_name); - - /// Возвращает true если имя директории совпадает с форматом имени директории кусочков - bool isPartDirectory(const String & dir_name, Poco::RegularExpression::MatchVec & matches) const; - - /// Кладет в DataPart данные из имени кусочка. - void parsePartName(const String & file_name, const Poco::RegularExpression::MatchVec & matches, DataPart & part); - - /// Определить, не битые ли данные в директории. Проверяет индекс и засечеки, но не сами данные. - bool isBrokenPart(const String & path); - - /// Найти самые большие old_куски, из которых получен этот кусок. - /// Переименовать их, убрав префикс old_ и вернуть их имена. - Strings tryRestorePart(const String & path, const String & file_name, Strings & old_parts); - - void createConvertExpression(const String & in_column_name, const String & out_type, ExpressionActionsPtr & out_expression, String & out_column); + /// Вызывается во время выбора кусков для слияния. + bool canMergeParts(const MergeTreeData::DataPartPtr & left, const MergeTreeData::DataPartPtr & right); }; } diff --git a/dbms/include/DB/Storages/StoragePtr.h b/dbms/include/DB/Storages/StoragePtr.h deleted file mode 100644 index b1ca8dc9f84..00000000000 --- a/dbms/include/DB/Storages/StoragePtr.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - - -namespace DB -{ - -class IStorage; - - -class StoragePtr -{ -private: - /// Содержит IStorage. В деструкторе при необходимости вызывает IStorage::dropImpl() перед уничтожением IStorage. - struct Wrapper - { - Wrapper(); - Wrapper(IStorage * s); - - boost::scoped_ptr storage; - - ~Wrapper(); - }; - - - StoragePtr(boost::weak_ptr p) : ptr(p) {} - - boost::shared_ptr ptr; - - friend class IStorage; - -public: - StoragePtr() {} - StoragePtr(const StoragePtr & p) : ptr(p.ptr) {} - - StoragePtr& operator= (const StoragePtr & p) - { - ptr = p.ptr; - return *this; - } - - IStorage* get() const - { - if (ptr == NULL) - return NULL; - else - return ptr->storage.get(); - } - - bool operator== (const IStorage * p) const - { - return get() == p; - } - - IStorage* operator-> () const - { - return get(); - } - - IStorage& operator* () const - { - return *get(); - } - - operator IStorage*() const - { - return get(); - } - - operator bool() const - { - return bool(ptr); - } - - bool operator! () const - { - return !bool(ptr); - } -}; - -} diff --git a/dbms/include/DB/Storages/StorageTinyLog.h b/dbms/include/DB/Storages/StorageTinyLog.h index f47ef0c915a..682efb70215 100644 --- a/dbms/include/DB/Storages/StorageTinyLog.h +++ b/dbms/include/DB/Storages/StorageTinyLog.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace DB @@ -22,20 +23,10 @@ class StorageTinyLog; class TinyLogBlockInputStream : public IProfilingBlockInputStream { public: - TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage); + TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StorageTinyLog & storage_); String getName() const { return "TinyLogBlockInputStream"; } - String getID() const - { - std::stringstream res; - res << "TinyLog(" << owned_storage->getTableName() << ", " << &*owned_storage; - - for (size_t i = 0; i < column_names.size(); ++i) - res << ", " << column_names[i]; - - res << ")"; - return res.str(); - } + String getID() const; protected: Block readImpl(); @@ -68,7 +59,7 @@ private: class TinyLogBlockOutputStream : public IBlockOutputStream { public: - TinyLogBlockOutputStream(StoragePtr owned_storage); + TinyLogBlockOutputStream(StorageTinyLog & storage_); void write(const Block & block); void writeSuffix(); private: @@ -134,7 +125,7 @@ public: BlockOutputStreamPtr write( ASTPtr query); - void dropImpl(); + void drop() override; void rename(const String & new_path_to_db, const String & new_name); diff --git a/dbms/include/DB/Storages/StorageView.h b/dbms/include/DB/Storages/StorageView.h index af42c3a78e4..547b38049f4 100644 --- a/dbms/include/DB/Storages/StorageView.h +++ b/dbms/include/DB/Storages/StorageView.h @@ -26,7 +26,7 @@ public: size_t max_block_size = DEFAULT_BLOCK_SIZE, unsigned threads = 1); - virtual void dropImpl(); + virtual void drop() override; protected: String select_database_name; diff --git a/dbms/include/DB/TableFunctions/ITableFunction.h b/dbms/include/DB/TableFunctions/ITableFunction.h index 6a0ef4f5df5..86540e8d56e 100644 --- a/dbms/include/DB/TableFunctions/ITableFunction.h +++ b/dbms/include/DB/TableFunctions/ITableFunction.h @@ -2,7 +2,7 @@ #include -#include +#include #include #include diff --git a/dbms/include/DB/TableFunctions/TableFunctionMerge.h b/dbms/include/DB/TableFunctions/TableFunctionMerge.h index c5926e131c4..b56ce106fbc 100644 --- a/dbms/include/DB/TableFunctions/TableFunctionMerge.h +++ b/dbms/include/DB/TableFunctions/TableFunctionMerge.h @@ -55,15 +55,26 @@ private: { OptimizedRegularExpression table_name_regexp(table_name_regexp_); - /// Список таблиц могут менять в другом потоке. - Poco::ScopedLock lock(context.getMutex()); - context.assertDatabaseExists(source_database); - const Tables & tables = context.getDatabases().at(source_database); - for (Tables::const_iterator it = tables.begin(); it != tables.end(); ++it) - if (table_name_regexp.match(it->first)) - return new NamesAndTypesList((it->second)->getColumnsList()); + StoragePtr any_table; - throw Exception("Error while executing table function merge. In database " + source_database + " no one matches regular expression: " + table_name_regexp_, ErrorCodes::UNKNOWN_TABLE); + { + /// Список таблиц могут менять в другом потоке. + Poco::ScopedLock lock(context.getMutex()); + context.assertDatabaseExists(source_database); + const Tables & tables = context.getDatabases().at(source_database); + for (Tables::const_iterator it = tables.begin(); it != tables.end(); ++it) + if (table_name_regexp.match(it->first)) + { + any_table = it->second; + break; + } + } + + if (!any_table) + throw Exception("Error while executing table function merge. In database " + source_database + " no one matches regular expression: " + table_name_regexp_, ErrorCodes::UNKNOWN_TABLE); + + auto table_lock = any_table->lockStructure(false); + return new NamesAndTypesList(any_table->getColumnsList()); } }; diff --git a/dbms/include/DB/TableFunctions/TableFunctionRemote.h b/dbms/include/DB/TableFunctions/TableFunctionRemote.h index a4303eec87b..b5edb9a8a21 100644 --- a/dbms/include/DB/TableFunctions/TableFunctionRemote.h +++ b/dbms/include/DB/TableFunctions/TableFunctionRemote.h @@ -56,9 +56,13 @@ public: std::vector > names; std::vector shards = parseDescription(descripton, 0, descripton.size(), ','); + for (size_t i = 0; i < shards.size(); ++i) names.push_back(parseDescription(shards[i], 0, shards[i].size(), '|')); + if (names.empty()) + throw Exception("Shard list is empty after parsing first argument", ErrorCodes::BAD_ARGUMENTS); + SharedPtr cluster = new Cluster(context.getSettings(), context.getDataTypeFactory(), names, username, password); return StorageDistributed::create(getName(), chooseColumns(*cluster, remote_database, remote_table, context), diff --git a/dbms/src/Common/VirtualColumnUtils.cpp b/dbms/src/Common/VirtualColumnUtils.cpp index cebb5a89a35..a287317a92e 100644 --- a/dbms/src/Common/VirtualColumnUtils.cpp +++ b/dbms/src/Common/VirtualColumnUtils.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include namespace DB diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index b4b1b30888b..1fcc148ff9d 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -199,7 +199,7 @@ StoragePtr Context::tryGetTable(const String & database_name, const String & tab void Context::addTable(const String & database_name, const String & table_name, StoragePtr table) { Poco::ScopedLock lock(shared->mutex); - + String db = database_name.empty() ? current_database : database_name; assertDatabaseExists(db); @@ -240,19 +240,22 @@ void Context::detachDatabase(const String & database_name) String db = database_name.empty() ? current_database : database_name; assertDatabaseExists(db); - shared->databases.erase(shared->databases.find(db)); - shared->database_droppers.erase(db); + shared->databases.erase(db); } ASTPtr Context::getCreateQuery(const String & database_name, const String & table_name) const { - Poco::ScopedLock lock(shared->mutex); + StoragePtr table; + String db; - String db = database_name.empty() ? current_database : database_name; + { + Poco::ScopedLock lock(shared->mutex); + db = database_name.empty() ? current_database : database_name; + table = getTable(db, table_name); + } - assertDatabaseExists(db); - assertTableExists(db, table_name); + auto table_lock = table->lockStructure(false); /// Здесь хранится определение таблицы String metadata_path = shared->path + "metadata/" + escapeForFileName(db) + "/" + escapeForFileName(table_name) + ".sql"; @@ -262,7 +265,7 @@ ASTPtr Context::getCreateQuery(const String & database_name, const String & tabl try { /// Если файл .sql не предусмотрен (например, для таблиц типа ChunkRef), то движок может сам предоставить запрос CREATE. - return getTable(database_name, table_name)->getCustomCreateQuery(*this); + return table->getCustomCreateQuery(*this); } catch (...) { @@ -304,22 +307,6 @@ ASTPtr Context::getCreateQuery(const String & database_name, const String & tabl } -DatabaseDropperPtr Context::getDatabaseDropper(const String & name) -{ - Poco::ScopedLock lock(shared->mutex); - - String db = name.empty() ? current_database : name; - - if (shared->databases.count(db) == 0) - throw Exception("Database " + db + " doesn't exist", ErrorCodes::UNKNOWN_DATABASE); - - if (shared->database_droppers.count(db) == 0) - shared->database_droppers[db] = DatabaseDropperPtr(new DatabaseDropper(shared->path + "data/" + escapeForFileName(db))); - - return shared->database_droppers[db]; -} - - Settings Context::getSettings() const { Poco::ScopedLock lock(shared->mutex); diff --git a/dbms/src/Interpreters/ExpressionAnalyzer.cpp b/dbms/src/Interpreters/ExpressionAnalyzer.cpp index 36567103bae..4d06a422031 100644 --- a/dbms/src/Interpreters/ExpressionAnalyzer.cpp +++ b/dbms/src/Interpreters/ExpressionAnalyzer.cpp @@ -1229,9 +1229,7 @@ Sets ExpressionAnalyzer::getSetsWithSubqueries() { Sets res; for (auto & s : sets_with_subqueries) - { res.push_back(s.second); - } return res; } diff --git a/dbms/src/Interpreters/InterpreterAlterQuery.cpp b/dbms/src/Interpreters/InterpreterAlterQuery.cpp index ff7533eed90..ae4808d338b 100644 --- a/dbms/src/Interpreters/InterpreterAlterQuery.cpp +++ b/dbms/src/Interpreters/InterpreterAlterQuery.cpp @@ -46,14 +46,7 @@ void InterpreterAlterQuery::execute() String database_name = alter.database.empty() ? context.getCurrentDatabase() : alter.database; StoragePtr table = context.getTable(database_name, table_name); - - /// для merge tree запрещаем все операции - StorageMergeTree::BigLockPtr merge_tree_lock; - if (StorageMergeTree * table_merge_tree = dynamic_cast(table.get())) - merge_tree_lock = table_merge_tree->lockAllOperations(); - - /// Poco::Mutex является рекурсивным, т.е. взятие мьютекса дважды из одного потока не приводит к блокировке - Poco::ScopedLock lock(context.getMutex()); + auto table_soft_lock = table->lockDataForAlter(); const DataTypeFactory & data_type_factory = context.getDataTypeFactory(); String path = context.getPath(); @@ -68,7 +61,6 @@ void InterpreterAlterQuery::execute() attach.attach = true; ASTs & columns = dynamic_cast(*attach.columns).children; - /// Различные проверки, на возможность выполнения запроса ASTs columns_copy = columns; IdentifierNameSet identifier_names; @@ -137,13 +129,33 @@ void InterpreterAlterQuery::execute() } } + /// Пока разрешим читать из таблицы. Запретим при первой попытке изменить структуру таблицы. + /// Это позволит сделать большую часть первого MODIFY, не останавливая чтение из таблицы. + IStorage::TableStructureWriteLockPtr table_hard_lock; + /// todo cycle over sub tables and tables /// Применяем изменения for (ASTAlterQuery::ParameterContainer::const_iterator alter_it = alter.parameters.begin(); alter_it != alter.parameters.end(); ++alter_it) { const ASTAlterQuery::Parameters & params = *alter_it; - table->alter(params); + + if (params.type == ASTAlterQuery::MODIFY) + { + table->prepareAlterModify(params); + + if (!table_hard_lock) + table_hard_lock = table->lockStructureForAlter(); + + table->commitAlterModify(params); + } + else + { + if (!table_hard_lock) + table_hard_lock = table->lockStructureForAlter(); + + table->alter(params); + } if (params.type == ASTAlterQuery::ADD) { diff --git a/dbms/src/Interpreters/InterpreterCreateQuery.cpp b/dbms/src/Interpreters/InterpreterCreateQuery.cpp index 60e01f5fd5c..07913765133 100644 --- a/dbms/src/Interpreters/InterpreterCreateQuery.cpp +++ b/dbms/src/Interpreters/InterpreterCreateQuery.cpp @@ -74,11 +74,26 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists) return StoragePtr(); } - StoragePtr res; SharedPtr interpreter_select; + Block select_sample; + if (create.select && !create.attach) + { + interpreter_select = new InterpreterSelectQuery(create.select, context); + select_sample = interpreter_select->getSampleBlock(); + } + + StoragePtr res; String storage_name; NamesAndTypesListPtr columns = new NamesAndTypesList; + StoragePtr as_storage; + IStorage::TableStructureReadLockPtr as_storage_lock; + if (!as_table_name.empty()) + { + as_storage = context.getTable(as_database_name, as_table_name); + as_storage_lock = as_storage->lockStructure(false); + } + { Poco::ScopedLock lock(context.getMutex()); @@ -92,12 +107,6 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists) throw Exception("Table " + database_name + "." + table_name + " already exists.", ErrorCodes::TABLE_ALREADY_EXISTS); } - if (!create.as_table.empty()) - context.assertTableExists(as_database_name, as_table_name); - - if (create.select && !create.attach) - interpreter_select = new InterpreterSelectQuery(create.select, context); - /// Получаем список столбцов if (create.columns) { @@ -112,13 +121,12 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists) } } else if (!create.as_table.empty()) - columns = new NamesAndTypesList(context.getTable(as_database_name, as_table_name)->getColumnsList()); + columns = new NamesAndTypesList(as_storage->getColumnsList()); else if (create.select) { - Block sample = interpreter_select->getSampleBlock(); columns = new NamesAndTypesList; - for (size_t i = 0; i < sample.columns(); ++i) - columns->push_back(NameAndTypePair(sample.getByPosition(i).name, sample.getByPosition(i).type)); + for (size_t i = 0; i < select_sample.columns(); ++i) + columns->push_back(NameAndTypePair(select_sample.getByPosition(i).name, select_sample.getByPosition(i).type)); } else throw Exception("Incorrect CREATE query: required list of column descriptions or AS section or SELECT.", ErrorCodes::INCORRECT_QUERY); @@ -159,7 +167,7 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists) } else if (!create.as_table.empty()) { - storage_name = context.getTable(as_database_name, as_table_name)->getName(); + storage_name = as_storage->getName(); create.storage = dynamic_cast(*context.getCreateQuery(as_database_name, as_table_name)).storage; } else if (create.is_view) diff --git a/dbms/src/Interpreters/InterpreterDropQuery.cpp b/dbms/src/Interpreters/InterpreterDropQuery.cpp index 9649cc8b6b9..53c5dd18999 100644 --- a/dbms/src/Interpreters/InterpreterDropQuery.cpp +++ b/dbms/src/Interpreters/InterpreterDropQuery.cpp @@ -1,10 +1,9 @@ #include #include - #include - #include +#include namespace DB @@ -19,8 +18,6 @@ InterpreterDropQuery::InterpreterDropQuery(ASTPtr query_ptr_, Context & context_ void InterpreterDropQuery::execute() { - Poco::ScopedLock lock(context.getMutex()); - String path = context.getPath(); String current_database = context.getCurrentDatabase(); @@ -28,92 +25,116 @@ void InterpreterDropQuery::execute() String database_name = drop.database.empty() ? current_database : drop.database; String database_name_escaped = escapeForFileName(database_name); - String table_name = drop.table; - String table_name_escaped = escapeForFileName(table_name); - String data_path = path + "data/" + database_name_escaped + "/" + table_name_escaped; - String metadata_path = path + "metadata/" + database_name_escaped + "/" + (!table_name.empty() ? table_name_escaped + ".sql" : ""); + String data_path = path + "data/" + database_name_escaped + "/"; + String metadata_path = path + "metadata/" + database_name_escaped + "/"; - if (!drop.if_exists) - context.assertDatabaseExists(database_name); + StorageVector tables_to_drop; if (!drop.table.empty()) { - /// Удаление таблицы - if (!context.isTableExist(database_name, table_name)) - { - if (!drop.if_exists) - throw Exception("Table " + database_name + "." + table_name + " doesn't exist.", ErrorCodes::UNKNOWN_TABLE); - } + StoragePtr table; + + if (drop.if_exists) + table = context.tryGetTable(database_name, drop.table); else - { - /// Удаляем данные таблицы - if (!drop.detach) - { - StoragePtr table = context.getTable(database_name, table_name); - DatabaseDropperPtr database_dropper = context.getDatabaseDropper(database_name); - table->path_to_remove_on_drop = data_path; - /// Присвоим database_to_drop на случай, если БД попробуют удалить до завершения удаления этой таблицы. - table->database_to_drop = database_dropper; - table->drop(); + table = context.getTable(database_name, drop.table); - /// Для таблиц типа ChunkRef, файла с метаданными не существует. - if (Poco::File(metadata_path).exists()) - Poco::File(metadata_path).remove(); - } - - /// Удаляем информацию о таблице из оперативки - StoragePtr detached_table = context.detachTable(database_name, table_name); - - { - /** Во время уничтожения объекта с таблицей, mutex должен быть разблокирован, - * так как таблица может ожидать завершения потоков, которые прямо сейчас ждут этого mutex-а. - */ - Poco::ScopedUnlock unlock(context.getMutex()); - detached_table->shutdown(); - detached_table = StoragePtr(); - } - } + if (table) + tables_to_drop.push_back(table); + else + return; } else { - if (context.isDatabaseExist(database_name)) + Poco::ScopedLock lock(context.getMutex()); + + if (!drop.if_exists) + context.assertDatabaseExists(database_name); + else if (!context.isDatabaseExist(database_name)) + return; + + Tables tables = context.getDatabases()[database_name]; + + for (auto & it : tables) { - /// Удаление базы данных - if (!drop.detach) - { - /// Тот, кто удалит директорию с БД, когда все ее таблицы будут удалены. - DatabaseDropperPtr database_dropper = context.getDatabaseDropper(database_name); - database_dropper->drop_on_destroy = true; - - Tables detached_tables = context.getDatabases()[database_name]; - - /// Удаление всех таблиц - for (Tables::iterator it = detached_tables.begin(); it != detached_tables.end(); ++it) - context.detachTable(database_name, it->first); - - { - Poco::ScopedUnlock unlock(context.getMutex()); - - for (Tables::iterator it = detached_tables.begin(); it != detached_tables.end(); ++it) - { - StoragePtr & table = it->second; - - table->path_to_remove_on_drop = data_path + escapeForFileName(it->first); - table->database_to_drop = database_dropper; - table->drop(); - table->shutdown(); - table = StoragePtr(); - } - } - - Poco::File(metadata_path).remove(true); - } - - /// Удаляем информацию о БД из оперативки - context.detachDatabase(database_name); + tables_to_drop.push_back(it.second); } } + + for (StoragePtr table : tables_to_drop) + { + table->shutdown(); + + /// Если кто-то успел удалить эту таблицу, выбросит исключение. + auto table_lock = table->lockForAlter(); + + String current_table_name = table->getTableName(); + + /// Удаляем информацию о таблице из оперативки + context.detachTable(database_name, current_table_name); + + /// Удаляем данные таблицы + if (!drop.detach) + { + String current_data_path = data_path + escapeForFileName(current_table_name); + String current_metadata_path = metadata_path + escapeForFileName(current_table_name) + ".sql"; + + /// Для таблиц типа ChunkRef, файла с метаданными не существует. + if (Poco::File(current_metadata_path).exists()) + Poco::File(current_metadata_path).remove(); + + table->drop(); + table->is_dropped = true; + + if (Poco::File(current_data_path).exists()) + Poco::File(current_data_path).remove(true); + } + } + + if (drop.table.empty()) + { + /// Удаление базы данных. Таблицы в ней уже удалены. + + Poco::ScopedLock lock(context.getMutex()); + + /// Кто-то мог успеть удалить БД до нас. + context.assertDatabaseExists(database_name); + + /// Кто-то мог успеть создать таблицу в удаляемой БД, пока мы удаляли таблицы без лока контекста. + if (!context.getDatabases()[database_name].empty()) + throw Exception("New table appeared in database being dropped. Try dropping it again.", ErrorCodes::DATABASE_NOT_EMPTY); + + /// Удаляем информацию о БД из оперативки + context.detachDatabase(database_name); + + Poco::File(data_path).remove(false); + Poco::File(metadata_path).remove(false); + } +} + +void InterpreterDropQuery::dropDetachedTable(String database_name, StoragePtr table, Context & context) +{ + table->shutdown(); + + auto table_lock = table->lockForAlter(); + + String table_name = table->getTableName(); + + String path = context.getPath(); + String database_name_escaped = escapeForFileName(database_name); + + String data_path = path + "data/" + database_name_escaped + "/" + escapeForFileName(table_name); + String metadata_path = path + "metadata/" + database_name_escaped + "/" + escapeForFileName(table_name) + ".sql"; + + if (Poco::File(metadata_path).exists()) + Poco::File(metadata_path).remove(); + + table->drop(); + table->is_dropped = true; + + if (Poco::File(data_path).exists()) + Poco::File(data_path).remove(true); } diff --git a/dbms/src/Interpreters/InterpreterInsertQuery.cpp b/dbms/src/Interpreters/InterpreterInsertQuery.cpp index 967db071f02..f8242d30717 100644 --- a/dbms/src/Interpreters/InterpreterInsertQuery.cpp +++ b/dbms/src/Interpreters/InterpreterInsertQuery.cpp @@ -64,12 +64,15 @@ void InterpreterInsertQuery::execute(ReadBuffer * remaining_data_istr) { ASTInsertQuery & query = dynamic_cast(*query_ptr); StoragePtr table = getTable(); - + + auto table_lock = table->lockStructure(true); + BlockInputStreamPtr in; NamesAndTypesListPtr required_columns = new NamesAndTypesList (table->getSampleBlock().getColumnsList()); /// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку. table->write(query_ptr); /// Создаем кортеж из нескольких стримов, в которые будем писать данные. + BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns); /// Какой тип запроса: INSERT VALUES | INSERT FORMAT | INSERT SELECT? @@ -112,11 +115,13 @@ void InterpreterInsertQuery::execute(ReadBuffer * remaining_data_istr) } -BlockOutputStreamPtr InterpreterInsertQuery::execute() +BlockIO InterpreterInsertQuery::execute() { ASTInsertQuery & query = dynamic_cast(*query_ptr); StoragePtr table = getTable(); + auto table_lock = table->lockStructure(true); + NamesAndTypesListPtr required_columns = new NamesAndTypesList(table->getSampleBlock().getColumnsList()); /// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку. @@ -124,18 +129,23 @@ BlockOutputStreamPtr InterpreterInsertQuery::execute() /// Создаем кортеж из нескольких стримов, в которые будем писать данные. BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns); + BlockIO res; + res.out_sample = getSampleBlock(); + /// Какой тип запроса: INSERT или INSERT SELECT? if (!query.select) - return out; + { + res.out = out; + } else { InterpreterSelectQuery interpreter_select(query.select, context); BlockInputStreamPtr in = interpreter_select.execute(); in = new MaterializingBlockInputStream(in); copyData(*in, *out); - - return NULL; } + + return res; } diff --git a/dbms/src/Interpreters/InterpreterQuery.cpp b/dbms/src/Interpreters/InterpreterQuery.cpp index b6f044d37c8..c742a3f1b5e 100644 --- a/dbms/src/Interpreters/InterpreterQuery.cpp +++ b/dbms/src/Interpreters/InterpreterQuery.cpp @@ -135,8 +135,7 @@ BlockIO InterpreterQuery::execute() { throwIfReadOnly(); InterpreterInsertQuery interpreter(query_ptr, context); - res.out = interpreter.execute(); - res.out_sample = interpreter.getSampleBlock(); + res = interpreter.execute(); } else if (dynamic_cast(&*query_ptr)) { diff --git a/dbms/src/Interpreters/InterpreterRenameQuery.cpp b/dbms/src/Interpreters/InterpreterRenameQuery.cpp index 246d1c3d17c..6c55ec2c99a 100644 --- a/dbms/src/Interpreters/InterpreterRenameQuery.cpp +++ b/dbms/src/Interpreters/InterpreterRenameQuery.cpp @@ -27,9 +27,6 @@ InterpreterRenameQuery::InterpreterRenameQuery(ASTPtr query_ptr_, Context & cont void InterpreterRenameQuery::execute() { - /** Все таблицы переименовываются под глобальной блокировкой. */ - Poco::ScopedLock lock(context.getMutex()); - String path = context.getPath(); String current_database = context.getCurrentDatabase(); @@ -53,11 +50,18 @@ void InterpreterRenameQuery::execute() String to_table_name_escaped = escapeForFileName(to_table_name); String to_metadata_path = path + "metadata/" + to_database_name_escaped + "/" + (!to_table_name.empty() ? to_table_name_escaped + ".sql" : ""); - context.assertTableExists(from_database_name, from_table_name); + /// Заблокировать таблицу нужно при незаблокированном контексте. + StoragePtr table = context.getTable(from_database_name, from_table_name); + auto table_lock = table->lockForAlter(); + + /** Все таблицы переименовываются под глобальной блокировкой. */ + Poco::ScopedLock lock(context.getMutex()); + context.assertTableDoesntExist(to_database_name, to_table_name); /// Уведомляем таблицу о том, что она переименовается. Если таблица не поддерживает переименование - кинется исключение. - context.getTable(from_database_name, from_table_name)->rename(path + "data/" + to_database_name_escaped + "/", to_table_name); + + table->rename(path + "data/" + to_database_name_escaped + "/", to_table_name); /// Пишем новый файл с метаданными. { diff --git a/dbms/src/Interpreters/InterpreterSelectQuery.cpp b/dbms/src/Interpreters/InterpreterSelectQuery.cpp index de920019cbe..518a42c2f39 100644 --- a/dbms/src/Interpreters/InterpreterSelectQuery.cpp +++ b/dbms/src/Interpreters/InterpreterSelectQuery.cpp @@ -41,28 +41,42 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndType throw Exception("Too deep subqueries. Maximum: " + toString(settings.limits.max_subquery_depth), ErrorCodes::TOO_DEEP_SUBQUERIES); - /// Если имееем дело с табличной функцией - if (query.table && dynamic_cast(&*query.table)) + if (query.table && dynamic_cast(&*query.table)) { - /// Получить табличную функцию - TableFunctionPtr table_function_ptr = context.getTableFunctionFactory().get(dynamic_cast(&*query.table)->name, context); - /// Выполнить ее и запомнить результат - table_function_storage = table_function_ptr->execute(query.table, context); + if (table_column_names.empty()) + context.setColumns(InterpreterSelectQuery(query.table, context).getSampleBlock().getColumnsList()); + } + else + { + if (query.table && dynamic_cast(&*query.table)) + { + /// Получить табличную функцию + TableFunctionPtr table_function_ptr = context.getTableFunctionFactory().get(dynamic_cast(&*query.table)->name, context); + /// Выполнить ее и запомнить результат + storage = table_function_ptr->execute(query.table, context); + } + else + { + String database_name; + String table_name; + + getDatabaseAndTableNames(database_name, table_name); + + storage = context.getTable(database_name, table_name); + } + + table_lock = storage->lockStructure(false); + if (table_column_names.empty()) + context.setColumns(storage->getColumnsList()); } - if (table_function_storage) - context.setColumns(table_function_storage->getColumnsList()); - else if (!table_column_names.empty()) + if (!table_column_names.empty()) context.setColumns(table_column_names); - else - context.setColumns(!query.table || !dynamic_cast(&*query.table) - ? getTable()->getColumnsList() - : InterpreterSelectQuery(query.table, context).getSampleBlock().getColumnsList()); if (context.getColumns().empty()) throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN); - query_analyzer = new ExpressionAnalyzer(query_ptr, context, table_function_storage, subquery_depth); + query_analyzer = new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth); if (input_) streams.push_back(input_); @@ -127,26 +141,6 @@ void InterpreterSelectQuery::getDatabaseAndTableNames(String & database_name, St } -StoragePtr InterpreterSelectQuery::getTable() -{ - String database_name; - String table_name; - - getDatabaseAndTableNames(database_name, table_name); - return context.getTable(database_name, table_name); -} - - -ASTPtr InterpreterSelectQuery::getCreateQuery() -{ - String database_name; - String table_name; - - getDatabaseAndTableNames(database_name, table_name); - return context.getCreateQuery(database_name, table_name); -} - - DataTypes InterpreterSelectQuery::getReturnTypes() { DataTypes res; @@ -209,7 +203,8 @@ BlockInputStreamPtr InterpreterSelectQuery::execute() bool need_aggregate = false; bool has_having = false; bool has_order_by = !query.order_expression_list.isNull(); - + + ExpressionActionsPtr array_join; ExpressionActionsPtr before_where; ExpressionActionsPtr before_aggregation; ExpressionActionsPtr before_having; @@ -225,8 +220,9 @@ BlockInputStreamPtr InterpreterSelectQuery::execute() if (from_stage < QueryProcessingStage::WithMergeableState && to_stage >= QueryProcessingStage::WithMergeableState) { - query_analyzer->appendArrayJoin(chain); - + if (query_analyzer->appendArrayJoin(chain)) + array_join = chain.getLastActions(); + if (query_analyzer->appendWhere(chain)) { has_where = true; @@ -312,6 +308,16 @@ BlockInputStreamPtr InterpreterSelectQuery::execute() if (need_aggregate) executeAggregation(streams, before_aggregation, aggregate_overflow_row, aggregate_final); + if (array_join && !has_where && !need_aggregate && to_stage == QueryProcessingStage::WithMergeableState) + { + /** Если есть ARRAY JOIN, его действие сначала старается оказаться в + * before_where, before_aggregation или before_order_and_select. + * Если ни одного из них нет, array_join нужно выполнить отдельно. + */ + + executeExpression(streams, array_join); + } + /** Оптимизация - при распределённой обработке запроса, * если не указаны DISTINCT, GROUP, HAVING, ORDER, но указан LIMIT, * то выполним предварительный LIMIT на удалёном сервере. @@ -339,7 +345,7 @@ BlockInputStreamPtr InterpreterSelectQuery::execute() executeHaving(streams, before_having); } - executeOuterExpression(streams, before_order_and_select); + executeExpression(streams, before_order_and_select); if (has_order_by) executeOrder(streams); @@ -424,20 +430,28 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu if (!streams.empty()) return QueryProcessingStage::FetchColumns; - /// Таблица, откуда читать данные, если не подзапрос. - StoragePtr table; /// Интерпретатор подзапроса, если подзапрос SharedPtr interpreter_subquery; /// Список столбцов, которых нужно прочитать, чтобы выполнить запрос. Names required_columns = query_analyzer->getRequiredColumns(); - if (table_function_storage) - table = table_function_storage; /// Если в запросе была указана табличная функция, данные читаем из нее. - else if (!query.table || !dynamic_cast(&*query.table)) - table = getTable(); - else if (dynamic_cast(&*query.table)) - interpreter_subquery = new InterpreterSelectQuery(query.table, context, required_columns, QueryProcessingStage::Complete, subquery_depth + 1); + if (query.table && dynamic_cast(&*query.table)) + { + /** Для подзапроса не действуют ограничения на максимальный размер результата. + * Так как результат поздапроса - ещё не результат всего запроса. + */ + Context subquery_context = context; + Settings subquery_settings = context.getSettings(); + subquery_settings.limits.max_result_rows = 0; + subquery_settings.limits.max_result_bytes = 0; + /// Вычисление extremes не имеет смысла и не нужно (если его делать, то в результате всего запроса могут взяться extremes подзапроса). + subquery_settings.extremes = 0; + subquery_context.setSettings(subquery_settings); + + interpreter_subquery = new InterpreterSelectQuery( + query.table, subquery_context, required_columns, QueryProcessingStage::Complete, subquery_depth + 1); + } /// если в настройках установлен default_sample != 1, то все запросы выполняем с сэмплингом /// если таблица не поддерживает сэмплинг получим исключение @@ -445,13 +459,13 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu if (!query.sample_size && settings.default_sample != 1) query.sample_size = new ASTLiteral(StringRange(NULL, NULL), Float64(settings.default_sample)); - if (query.sample_size && (!table || !table->supportsSampling())) + if (query.sample_size && (!storage || !storage->supportsSampling())) throw Exception("Illegal SAMPLE: table doesn't support sampling", ErrorCodes::SAMPLING_NOT_SUPPORTED); - if (query.final && (!table || !table->supportsFinal())) + if (query.final && (!storage || !storage->supportsFinal())) throw Exception("Illegal FINAL", ErrorCodes::ILLEGAL_FINAL); - if (query.prewhere_expression && (!table || !table->supportsPrewhere())) + if (query.prewhere_expression && (!storage || !storage->supportsPrewhere())) throw Exception("Illegal PREWHERE", ErrorCodes::ILLEGAL_PREWHERE); /** При распределённой обработке запроса, в потоках почти не делается вычислений, @@ -466,7 +480,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu * и там должно быть оригинальное значение max_threads, а не увеличенное. */ Settings settings_for_storage = settings; - if (table && table->isRemote()) + if (storage && storage->isRemote()) settings.max_threads = settings.max_distributed_connections; /// Ограничение на количество столбцов для чтения. @@ -495,10 +509,18 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns; /// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос? - if (!query.table || !dynamic_cast(&*query.table)) - streams = table->read(required_columns, query_ptr, settings_for_storage, from_stage, settings.max_block_size, settings.max_threads); + if (!interpreter_subquery) + { + streams = storage->read(required_columns, query_ptr, settings_for_storage, from_stage, settings.max_block_size, settings.max_threads); + for (auto stream : streams) + { + stream->addTableLock(table_lock); + } + } else + { streams.push_back(maybeAsynchronous(interpreter_subquery->execute(), settings.asynchronous)); + } /** Если истчоников слишком много, то склеим их в max_threads источников. * (Иначе действия в каждом маленьком источнике, а затем объединение состояний, слишком неэффективно.) @@ -510,7 +532,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu * Такие ограничения проверяются на сервере-инициаторе запроса, а не на удалённых серверах. * Потому что сервер-инициатор имеет суммарные данные о выполнении запроса на всех серверах. */ - if (table && to_stage == QueryProcessingStage::Complete) + if (storage && to_stage == QueryProcessingStage::Complete) { IProfilingBlockInputStream::LocalLimits limits; limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL; @@ -647,7 +669,7 @@ void InterpreterSelectQuery::executeTotalsAndHaving(BlockInputStreams & streams, } -void InterpreterSelectQuery::executeOuterExpression(BlockInputStreams & streams, ExpressionActionsPtr expression) +void InterpreterSelectQuery::executeExpression(BlockInputStreams & streams, ExpressionActionsPtr expression) { bool is_async = settings.asynchronous && streams.size() <= settings.max_threads; for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) diff --git a/dbms/src/Parsers/formatAST.cpp b/dbms/src/Parsers/formatAST.cpp index 3fcfe94ae1e..dff56b9c087 100644 --- a/dbms/src/Parsers/formatAST.cpp +++ b/dbms/src/Parsers/formatAST.cpp @@ -168,9 +168,7 @@ void formatAST(const ASTSelectQuery & ast, std::ostream & s, size_t indent, bo if (ast.prewhere_expression) { s << (hilite ? hilite_keyword : "") << nl_or_ws << indent_str << "PREWHERE " << (hilite ? hilite_none : ""); - one_line - ? formatAST(*ast.prewhere_expression, s, indent, hilite, one_line) - : formatExpressionListMultiline(dynamic_cast(*ast.prewhere_expression), s, indent, hilite); + formatAST(*ast.prewhere_expression, s, indent, hilite, one_line); } if (ast.where_expression) @@ -224,11 +222,12 @@ void formatAST(const ASTSelectQuery & ast, std::ostream & s, size_t indent, bo void formatAST(const ASTSubquery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens) { + std::string indent_str = one_line ? "" : std::string(4 * indent, ' '); std::string nl_or_nothing = one_line ? "" : "\n"; - s << nl_or_nothing << "(" << nl_or_nothing; + s << nl_or_nothing << indent_str << "(" << nl_or_nothing; formatAST(*ast.children[0], s, indent + 1, hilite, one_line); - s << nl_or_nothing << ")"; + s << nl_or_nothing << indent_str << ")"; } void formatAST(const ASTCreateQuery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens) @@ -448,6 +447,10 @@ static void writeAlias(const String & name, std::ostream & s, bool hilite, bool void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens) { + /// Если есть алиас, то требуются скобки вокруг всего выражения, включая алиас. Потому что запись вида 0 AS x + 0 синтаксически некорректна. + if (need_parens && !ast.alias.empty()) + s << '('; + /// Стоит ли записать эту функцию в виде оператора? bool written = false; if (ast.arguments && !ast.parameters) @@ -557,32 +560,32 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool written = true; } } + } - if (!written && 0 == strcmp(ast.name.c_str(), "array")) + if (!written && ast.arguments->children.size() >= 1 && 0 == strcmp(ast.name.c_str(), "array")) + { + s << (hilite ? hilite_operator : "") << '[' << (hilite ? hilite_none : ""); + for (size_t i = 0; i < ast.arguments->children.size(); ++i) { - s << '['; - for (size_t i = 0; i < ast.arguments->children.size(); ++i) - { - if (i != 0) - s << ", "; - formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false); - } - s << ']'; - written = true; + if (i != 0) + s << ", "; + formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false); } + s << (hilite ? hilite_operator : "") << ']' << (hilite ? hilite_none : ""); + written = true; + } - if (!written && 0 == strcmp(ast.name.c_str(), "tuple")) + if (!written && ast.arguments->children.size() >= 2 && 0 == strcmp(ast.name.c_str(), "tuple")) + { + s << (hilite ? hilite_operator : "") << '(' << (hilite ? hilite_none : ""); + for (size_t i = 0; i < ast.arguments->children.size(); ++i) { - s << '('; - for (size_t i = 0; i < ast.arguments->children.size(); ++i) - { - if (i != 0) - s << ", "; - formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false); - } - s << ')'; - written = true; + if (i != 0) + s << ", "; + formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false); } + s << (hilite ? hilite_operator : "") << ')' << (hilite ? hilite_none : ""); + written = true; } } @@ -608,11 +611,18 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool } if (!ast.alias.empty()) + { writeAlias(ast.alias, s, hilite, one_line); + if (need_parens) + s << ')'; + } } void formatAST(const ASTIdentifier & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens) { + if (need_parens && !ast.alias.empty()) + s << '('; + s << (hilite ? hilite_identifier : ""); WriteBufferFromOStream wb(s, 32); @@ -622,15 +632,26 @@ void formatAST(const ASTIdentifier & ast, std::ostream & s, size_t indent, bo s << (hilite ? hilite_none : ""); if (!ast.alias.empty()) + { writeAlias(ast.alias, s, hilite, one_line); + if (need_parens) + s << ')'; + } } void formatAST(const ASTLiteral & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens) { + if (need_parens && !ast.alias.empty()) + s << '('; + s << apply_visitor(FieldVisitorToString(), ast.value); if (!ast.alias.empty()) + { writeAlias(ast.alias, s, hilite, one_line); + if (need_parens) + s << ')'; + } } void formatAST(const ASTNameTypePair & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens) diff --git a/dbms/src/Server/OLAPAttributesMetadata.h b/dbms/src/Server/OLAPAttributesMetadata.h index 586ff7e7aca..fa5d6827812 100644 --- a/dbms/src/Server/OLAPAttributesMetadata.h +++ b/dbms/src/Server/OLAPAttributesMetadata.h @@ -523,7 +523,6 @@ struct UserAgentID : public IAttributeMetadata typedef AttributeIntBase ClickGoodEvent; typedef AttributeIntBase ClickPriorityID; typedef AttributeIntBase ClickBannerID; -typedef AttributeIntBase ClickPhraseID; typedef AttributeIntBase ClickPageID; typedef AttributeIntBase ClickPlaceID; typedef AttributeIntBase ClickTypeID; @@ -532,7 +531,6 @@ typedef AttributeUIntBase ClickDomainID; typedef AttributeUIntBase ClickCost; typedef AttributeHashBase ClickURLHash; typedef AttributeUIntBase ClickOrderID; -typedef AttributeUIntBase ClickTargetPhraseID; typedef AttributeUIntBase GoalReachesAny; typedef AttributeUIntBase GoalReachesDepth; typedef AttributeUIntBase GoalReachesURL; @@ -728,7 +726,6 @@ inline AttributeMetadatas GetOLAPAttributeMetadata() ("ClickGoodEvent", new ClickGoodEvent) ("ClickPriorityID", new ClickPriorityID) ("ClickBannerID", new ClickBannerID) - ("ClickPhraseID", new ClickPhraseID) ("ClickPageID", new ClickPageID) ("ClickPlaceID", new ClickPlaceID) ("ClickTypeID", new ClickTypeID) @@ -737,7 +734,6 @@ inline AttributeMetadatas GetOLAPAttributeMetadata() ("ClickCost", new ClickCost) ("ClickURLHash", new ClickURLHash) ("ClickOrderID", new ClickOrderID) - ("ClickTargetPhraseID", new ClickTargetPhraseID) ("GoalReaches", new GoalReaches) ("GoalReachesAny", new GoalReachesAny) ("GoalReachesDepth", new GoalReachesDepth) diff --git a/dbms/src/Server/OLAPQueryConverter.cpp b/dbms/src/Server/OLAPQueryConverter.cpp index 0309ac1e1d0..9fb4b3b3d4b 100644 --- a/dbms/src/Server/OLAPQueryConverter.cpp +++ b/dbms/src/Server/OLAPQueryConverter.cpp @@ -550,7 +550,6 @@ void QueryConverter::fillNumericAttributeMap() M("ClickGoodEvent", "Clicks.GoodEvent[1]") M("ClickPriorityID", "Clicks.PriorityID[1]") M("ClickBannerID", "Clicks.BannerID[1]") - M("ClickPhraseID", "Clicks.PhraseID[1]") M("ClickPageID", "Clicks.PageID[1]") M("ClickPlaceID", "Clicks.PlaceID[1]") M("ClickTypeID", "Clicks.TypeID[1]") @@ -559,7 +558,6 @@ void QueryConverter::fillNumericAttributeMap() M("ClickCost", "Clicks.Cost[1]") M("ClickURLHash", "Clicks.URLHash[1]") M("ClickOrderID", "Clicks.OrderID[1]") - M("ClickTargetPhraseID", "Clicks.TargetPhraseID[1]") M("GoalReachesAny", "GoalReachesAny") M("GoalReachesDepth", "GoalReachesDepth") M("GoalReachesURL", "GoalReachesURL") diff --git a/dbms/src/Storages/IStorage.cpp b/dbms/src/Storages/ITableDeclaration.cpp similarity index 91% rename from dbms/src/Storages/IStorage.cpp rename to dbms/src/Storages/ITableDeclaration.cpp index 9e98fb5f476..3de3049fa62 100644 --- a/dbms/src/Storages/IStorage.cpp +++ b/dbms/src/Storages/ITableDeclaration.cpp @@ -1,21 +1,16 @@ -#include -#include - -#include #include - -#include +#include +#include #include -#include #include #include #include - +#include namespace DB { -bool IStorage::hasRealColumn(const String &column_name) const +bool ITableDeclaration::hasRealColumn(const String &column_name) const { const NamesAndTypesList & real_columns = getColumnsList(); for (auto & it : real_columns) @@ -25,7 +20,7 @@ bool IStorage::hasRealColumn(const String &column_name) const } -NameAndTypePair IStorage::getRealColumn(const String &column_name) const +NameAndTypePair ITableDeclaration::getRealColumn(const String &column_name) const { const NamesAndTypesList & real_columns = getColumnsList(); for (auto & it : real_columns) @@ -35,30 +30,30 @@ NameAndTypePair IStorage::getRealColumn(const String &column_name) const } -bool IStorage::hasColumn(const String &column_name) const +bool ITableDeclaration::hasColumn(const String &column_name) const { return hasRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет. } -NameAndTypePair IStorage::getColumn(const String &column_name) const +NameAndTypePair ITableDeclaration::getColumn(const String &column_name) const { return getRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет. } -const DataTypePtr IStorage::getDataTypeByName(const String & column_name) const +const DataTypePtr ITableDeclaration::getDataTypeByName(const String & column_name) const { const NamesAndTypesList & names_and_types = getColumnsList(); for (NamesAndTypesList::const_iterator it = names_and_types.begin(); it != names_and_types.end(); ++it) if (it->first == column_name) return it->second; - + throw Exception("There is no column " + column_name + " in table " + getTableName(), ErrorCodes::NO_SUCH_COLUMN_IN_TABLE); } -Block IStorage::getSampleBlock() const +Block ITableDeclaration::getSampleBlock() const { Block res; const NamesAndTypesList & names_and_types = getColumnsList(); @@ -71,7 +66,7 @@ Block IStorage::getSampleBlock() const col.column = col.type->createColumn(); res.insert(col); } - + return res; } @@ -104,10 +99,10 @@ static NamesAndTypesMap getColumnsMap(const NamesAndTypesList & available_column } -void IStorage::check(const Names & column_names) const +void ITableDeclaration::check(const Names & column_names) const { const NamesAndTypesList & available_columns = getColumnsList(); - + if (column_names.empty()) throw Exception("Empty list of columns queried for table " + getTableName() + ". There are columns: " + listOfColumns(available_columns), @@ -134,14 +129,14 @@ void IStorage::check(const Names & column_names) const } -void IStorage::check(const Block & block, bool need_all) const +void ITableDeclaration::check(const Block & block, bool need_all) const { const NamesAndTypesList & available_columns = getColumnsList(); const NamesAndTypesMap & columns_map = getColumnsMap(available_columns); - + typedef std::unordered_set NameSet; NameSet names_in_block; - + for (size_t i = 0; i < block.columns(); ++i) { const ColumnWithNameAndType & column = block.getByPosition(i); @@ -163,7 +158,7 @@ void IStorage::check(const Block & block, bool need_all) const + ". Column has type " + it->second->getName() + ", got type " + column.type->getName(), ErrorCodes::TYPE_MISMATCH); } - + if (need_all && names_in_block.size() < columns_map.size()) { for (NamesAndTypesList::const_iterator it = available_columns.begin(); it != available_columns.end(); ++it) @@ -174,7 +169,6 @@ void IStorage::check(const Block & block, bool need_all) const } } - /// одинаковыми считаются имена, если они совпадают целиком или name_without_dot совпадает с частью имени до точки static bool namesEqual(const String & name_without_dot, const DB::NameAndTypePair & name_type) { @@ -182,7 +176,7 @@ static bool namesEqual(const String & name_without_dot, const DB::NameAndTypePai return (name_with_dot == name_type.first.substr(0, name_without_dot.length() + 1) || name_without_dot == name_type.first); } -void IStorage::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) const +void ITableDeclaration::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) { if (params.type == ASTAlterQuery::ADD) { @@ -219,14 +213,14 @@ void IStorage::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTy else if (params.type == ASTAlterQuery::DROP) { String column_name = dynamic_cast(*(params.column)).name; - + /// Удаляем колонки из листа columns bool is_first = true; NamesAndTypesList::iterator column_it; do { column_it = std::find_if(columns->begin(), columns->end(), boost::bind(namesEqual, column_name, _1)); - + if (column_it == columns->end()) { if (is_first) @@ -252,7 +246,7 @@ void IStorage::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTy column_it->second = data_type; } else - throw Exception("Wrong parameter type in ALTER query", ErrorCodes::LOGICAL_ERROR); + throw Exception("Wrong parameter type in ALTER query", ErrorCodes::LOGICAL_ERROR); } } diff --git a/dbms/src/Storages/MergeTree/DiskSpaceMonitor.cpp b/dbms/src/Storages/MergeTree/DiskSpaceMonitor.cpp new file mode 100644 index 00000000000..2175fa5c8fe --- /dev/null +++ b/dbms/src/Storages/MergeTree/DiskSpaceMonitor.cpp @@ -0,0 +1,9 @@ +#include + +namespace DB +{ + +size_t DiskSpaceMonitor::reserved_bytes; +Poco::FastMutex DiskSpaceMonitor::reserved_bytes_mutex; + +} diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp new file mode 100644 index 00000000000..580504feb0e --- /dev/null +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -0,0 +1,690 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +namespace DB +{ + +static String lastTwoPathComponents(String path) +{ + if (!path.empty() && *path.rbegin() == '/') + path.erase(path.end() - 1); + size_t slash = path.rfind('/'); + if (slash == String::npos || slash == 0) + return path; + slash = path.rfind('/', slash - 1); + if (slash == String::npos) + return path; + return path.substr(slash + 1); +} + +MergeTreeData::MergeTreeData( + const String & full_path_, NamesAndTypesListPtr columns_, + const Context & context_, + ASTPtr & primary_expr_ast_, + const String & date_column_name_, const ASTPtr & sampling_expression_, + size_t index_granularity_, + Mode mode_, + const String & sign_column_, + const MergeTreeSettings & settings_) + : context(context_), + date_column_name(date_column_name_), sampling_expression(sampling_expression_), + index_granularity(index_granularity_), + mode(mode_), sign_column(sign_column_), + settings(settings_), primary_expr_ast(primary_expr_ast_->clone()), + full_path(full_path_), columns(columns_), + log(&Logger::get("MergeTreeData: " + lastTwoPathComponents(full_path))), + file_name_regexp("^(\\d{8})_(\\d{8})_(\\d+)_(\\d+)_(\\d+)") +{ + /// создаём директорию, если её нет + Poco::File(full_path).createDirectories(); + + /// инициализируем описание сортировки + sort_descr.reserve(primary_expr_ast->children.size()); + for (ASTs::iterator it = primary_expr_ast->children.begin(); + it != primary_expr_ast->children.end(); + ++it) + { + String name = (*it)->getColumnName(); + sort_descr.push_back(SortColumnDescription(name, 1)); + } + + primary_expr = ExpressionAnalyzer(primary_expr_ast, context, *columns).getActions(false); + + ExpressionActionsPtr projected_expr = ExpressionAnalyzer(primary_expr_ast, context, *columns).getActions(true); + primary_key_sample = projected_expr->getSampleBlock(); + + loadDataParts(); + clearOldParts(); +} + +UInt64 MergeTreeData::getMaxDataPartIndex() +{ + UInt64 max_part_id = 0; + for (DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it) + { + max_part_id = std::max(max_part_id, (*it)->right); + } + return max_part_id; +} + +std::string MergeTreeData::getModePrefix() const +{ + switch (mode) + { + case Ordinary: return ""; + case Collapsing: return "Collapsing"; + case Summing: return "Summing"; + + default: + throw Exception("Unknown mode of operation for MergeTreeData: " + toString(mode), ErrorCodes::LOGICAL_ERROR); + } +} + + + + +String MergeTreeData::getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level) +{ + DateLUTSingleton & date_lut = DateLUTSingleton::instance(); + + /// Имя директории для куска иммет вид: YYYYMMDD_YYYYMMDD_N_N_L. + String res; + { + unsigned left_date_id = Date2OrderedIdentifier(date_lut.fromDayNum(left_date)); + unsigned right_date_id = Date2OrderedIdentifier(date_lut.fromDayNum(right_date)); + + WriteBufferFromString wb(res); + + writeIntText(left_date_id, wb); + writeChar('_', wb); + writeIntText(right_date_id, wb); + writeChar('_', wb); + writeIntText(left_id, wb); + writeChar('_', wb); + writeIntText(right_id, wb); + writeChar('_', wb); + writeIntText(level, wb); + } + + return res; +} + + +void MergeTreeData::parsePartName(const String & file_name, const Poco::RegularExpression::MatchVec & matches, DataPart & part) +{ + DateLUTSingleton & date_lut = DateLUTSingleton::instance(); + + part.left_date = date_lut.toDayNum(OrderedIdentifier2Date(file_name.substr(matches[1].offset, matches[1].length))); + part.right_date = date_lut.toDayNum(OrderedIdentifier2Date(file_name.substr(matches[2].offset, matches[2].length))); + part.left = parse(file_name.substr(matches[3].offset, matches[3].length)); + part.right = parse(file_name.substr(matches[4].offset, matches[4].length)); + part.level = parse(file_name.substr(matches[5].offset, matches[5].length)); + + part.left_month = date_lut.toFirstDayNumOfMonth(part.left_date); + part.right_month = date_lut.toFirstDayNumOfMonth(part.right_date); +} + + +void MergeTreeData::loadDataParts() +{ + LOG_DEBUG(log, "Loading data parts"); + + Poco::ScopedLock lock(data_parts_mutex); + Poco::ScopedLock lock_all(all_data_parts_mutex); + + data_parts.clear(); + + Strings part_file_names; + Strings old_file_names; + Poco::DirectoryIterator end; + for (Poco::DirectoryIterator it(full_path); it != end; ++it) + { + String file_name = it.name(); + + /// Удаляем временные директории старше суток. + if (0 == file_name.compare(0, strlen("tmp_"), "tmp_")) + { + Poco::File tmp_dir(full_path + file_name); + + if (tmp_dir.isDirectory() && tmp_dir.getLastModified().epochTime() + 86400 < time(0)) + { + LOG_WARNING(log, "Removing temporary directory " << full_path << file_name); + Poco::File(full_path + file_name).remove(true); + } + + continue; + } + + if (0 == file_name.compare(0, strlen("old_"), "old_")) + old_file_names.push_back(file_name); + else + part_file_names.push_back(file_name); + } + + Poco::RegularExpression::MatchVec matches; + while (!part_file_names.empty()) + { + String file_name = part_file_names.back(); + part_file_names.pop_back(); + + if (!isPartDirectory(file_name, matches)) + continue; + + MutableDataPartPtr part = std::make_shared(*this); + parsePartName(file_name, matches, *part); + part->name = file_name; + + /// Для битых кусков, которые могут образовываться после грубого перезапуска сервера, попытаться восстановить куски, из которых они сделаны. + if (isBrokenPart(full_path + file_name)) + { + if (part->level == 0) + { + /// Восстановить куски нулевого уровня невозможно. + LOG_ERROR(log, "Removing broken part " << full_path + file_name << " because is't impossible to repair."); + part->remove(); + } + else + { + Strings new_parts = tryRestorePart(full_path, file_name, old_file_names); + part_file_names.insert(part_file_names.begin(), new_parts.begin(), new_parts.end()); + } + + continue; + } + + /// Размер - в количестве засечек. + part->size = Poco::File(full_path + file_name + "/" + escapeForFileName(columns->front().first) + ".mrk").getSize() + / MERGE_TREE_MARK_SIZE; + + part->modification_time = Poco::File(full_path + file_name).getLastModified().epochTime(); + + try + { + part->loadIndex(); + } + catch (...) + { + /// Не будем вставлять в набор кусок с битым индексом. Пропустим кусок и позволим серверу запуститься. + tryLogCurrentException(__PRETTY_FUNCTION__); + continue; + } + + data_parts.insert(part); + } + + all_data_parts = data_parts; + + /** Удаляем из набора актуальных кусков куски, которые содержатся в другом куске (которые были склеены), + * но по каким-то причинам остались лежать в файловой системе. + * Удаление файлов будет произведено потом в методе clearOldParts. + */ + + if (data_parts.size() >= 2) + { + DataParts::iterator prev_jt = data_parts.begin(); + DataParts::iterator curr_jt = prev_jt; + ++curr_jt; + while (curr_jt != data_parts.end()) + { + /// Куски данных за разные месяцы рассматривать не будем + if ((*curr_jt)->left_month != (*curr_jt)->right_month + || (*curr_jt)->right_month != (*prev_jt)->left_month + || (*prev_jt)->left_month != (*prev_jt)->right_month) + { + ++prev_jt; + ++curr_jt; + continue; + } + + if ((*curr_jt)->contains(**prev_jt)) + { + LOG_WARNING(log, "Part " << (*curr_jt)->name << " contains " << (*prev_jt)->name); + data_parts.erase(prev_jt); + prev_jt = curr_jt; + ++curr_jt; + } + else if ((*prev_jt)->contains(**curr_jt)) + { + LOG_WARNING(log, "Part " << (*prev_jt)->name << " contains " << (*curr_jt)->name); + data_parts.erase(curr_jt++); + } + else + { + ++prev_jt; + ++curr_jt; + } + } + } + + LOG_DEBUG(log, "Loaded data parts (" << data_parts.size() << " items)"); +} + + +void MergeTreeData::clearOldParts() +{ + Poco::ScopedTry lock; + + /// Если метод уже вызван из другого потока (или если all_data_parts прямо сейчас меняют), то можно ничего не делать. + if (!lock.lock(&all_data_parts_mutex)) + { + LOG_TRACE(log, "Already clearing or modifying old parts"); + return; + } + + LOG_TRACE(log, "Clearing old parts"); + for (DataParts::iterator it = all_data_parts.begin(); it != all_data_parts.end();) + { + int ref_count = it->use_count(); + if (ref_count == 1) /// После этого ref_count не может увеличиться. + { + LOG_DEBUG(log, "'Removing' part " << (*it)->name << " (prepending old_ to its name)"); + + (*it)->renameToOld(); + all_data_parts.erase(it++); + } + else + ++it; + } + + /// Удалим старые old_ куски. + Poco::DirectoryIterator end; + for (Poco::DirectoryIterator it(full_path); it != end; ++it) + { + if (0 != it.name().compare(0, strlen("old_"), "old_")) + continue; + if (it->isDirectory() && it->getLastModified().epochTime() + settings.old_parts_lifetime < time(0)) + { + it->remove(true); + } + } +} + +void MergeTreeData::setPath(const String & new_full_path) +{ + Poco::File(full_path).renameTo(new_full_path); + full_path = new_full_path; + + context.getUncompressedCache()->reset(); + context.getMarkCache()->reset(); + + log = &Logger::get(lastTwoPathComponents(full_path)); +} + +void MergeTreeData::dropAllData() +{ + data_parts.clear(); + all_data_parts.clear(); + + context.getUncompressedCache()->reset(); + context.getMarkCache()->reset(); + + Poco::File(full_path).remove(true); +} + +void MergeTreeData::removeColumnFiles(String column_name) +{ + Poco::ScopedLock lock(data_parts_mutex); + Poco::ScopedLock lock_all(all_data_parts_mutex); + + /// Регэксп выбирает файлы столбца для удаления + Poco::RegularExpression re(column_name + "(?:(?:\\.|\\%2E).+){0,1}" +"(?:\\.mrk|\\.bin|\\.size\\d+\\.bin|\\.size\\d+\\.mrk)"); + /// Цикл по всем директориям кусочков + Poco::RegularExpression::MatchVec matches; + Poco::DirectoryIterator end; + for (Poco::DirectoryIterator it_dir = Poco::DirectoryIterator(full_path); it_dir != end; ++it_dir) + { + std::string dir_name = it_dir.name(); + + if (!isPartDirectory(dir_name, matches)) + continue; + + /// Цикл по каждому из файлов в директории кусочков + String full_dir_name = full_path + dir_name + "/"; + for (Poco::DirectoryIterator it_file(full_dir_name); it_file != end; ++it_file) + { + if (re.match(it_file.name())) + { + Poco::File file(full_dir_name + it_file.name()); + if (file.exists()) + file.remove(); + } + } + } +} + +void MergeTreeData::createConvertExpression(const String & in_column_name, const String & out_type, ExpressionActionsPtr & out_expression, String & out_column) +{ + ASTFunction * function = new ASTFunction; + ASTPtr function_ptr = function; + + ASTExpressionList * arguments = new ASTExpressionList; + ASTPtr arguments_ptr = arguments; + + function->name = "to" + out_type; + function->arguments = arguments_ptr; + function->children.push_back(arguments_ptr); + + ASTIdentifier * in_column = new ASTIdentifier; + ASTPtr in_column_ptr = in_column; + + arguments->children.push_back(in_column_ptr); + + in_column->name = in_column_name; + in_column->kind = ASTIdentifier::Column; + + out_expression = ExpressionAnalyzer(function_ptr, context, *columns).getActions(false); + out_column = function->getColumnName(); +} + +static DataTypePtr getDataTypeByName(const String & name, const NamesAndTypesList & columns) +{ + for (const auto & it : columns) + { + if (it.first == name) + return it.second; + } + throw Exception("No column " + name + " in table", ErrorCodes::NO_SUCH_COLUMN_IN_TABLE); +} + +void MergeTreeData::alter(const ASTAlterQuery::Parameters & params) +{ + { + Poco::ScopedLock lock(data_parts_mutex); + Poco::ScopedLock lock_all(all_data_parts_mutex); + alterColumns(params, columns, context); + } + if (params.type == ASTAlterQuery::DROP) + { + String column_name = dynamic_cast(*params.column).name; + removeColumnFiles(column_name); + + context.getUncompressedCache()->reset(); + context.getMarkCache()->reset(); + } +} + +void MergeTreeData::prepareAlterModify(const ASTAlterQuery::Parameters & params) +{ + DataPartsVector parts; + { + Poco::ScopedLock lock(data_parts_mutex); + parts = DataPartsVector(data_parts.begin(), data_parts.end()); + } + + Names column_name; + const ASTNameTypePair & name_type = dynamic_cast(*params.name_type); + StringRange type_range = name_type.type->range; + String type(type_range.first, type_range.second - type_range.first); + DataTypePtr old_type_ptr = DB::getDataTypeByName(name_type.name, *columns); + DataTypePtr new_type_ptr = context.getDataTypeFactory().get(type); + if (dynamic_cast(old_type_ptr.get()) || dynamic_cast(old_type_ptr.get()) || + dynamic_cast(new_type_ptr.get()) || dynamic_cast(new_type_ptr.get())) + throw Exception("ALTER MODIFY not supported for nested and array types"); + + column_name.push_back(name_type.name); + ExpressionActionsPtr expr; + String out_column; + createConvertExpression(name_type.name, type, expr, out_column); + + ColumnNumbers num(1, 0); + for (DataPartPtr & part : parts) + { + MarkRanges ranges(1, MarkRange(0, part->size)); + ExpressionBlockInputStream in(new MergeTreeBlockInputStream(full_path + part->name + '/', + DEFAULT_MERGE_BLOCK_SIZE, column_name, *this, part, ranges, false, NULL, ""), expr); + MergedColumnOnlyOutputStream out(*this, full_path + part->name + '/', true); + out.writePrefix(); + + try + { + while(DB::Block b = in.read()) + { + /// оставляем только столбец с результатом + b.erase(0); + out.write(b); + } + LOG_TRACE(log, "Write Suffix"); + out.writeSuffix(); + } + catch (const Exception & e) + { + if (e.code() != ErrorCodes::ALL_REQUESTED_COLUMNS_ARE_MISSING) + throw; + } + } +} + +void MergeTreeData::commitAlterModify(const ASTAlterQuery::Parameters & params) +{ + DataPartsVector parts; + { + Poco::ScopedLock lock(data_parts_mutex); + parts = DataPartsVector(data_parts.begin(), data_parts.end()); + } + + const ASTNameTypePair & name_type = dynamic_cast(*params.name_type); + StringRange type_range = name_type.type->range; + String type(type_range.first, type_range.second - type_range.first); + + ExpressionActionsPtr expr; + String out_column; + createConvertExpression(name_type.name, type, expr, out_column); + + /// переименовываем файлы + /// переименовываем старые столбцы, добавляя расширение .old + for (DataPartPtr & part : parts) + { + std::string path = full_path + part->name + '/' + escapeForFileName(name_type.name); + if (Poco::File(path + ".bin").exists()) + { + LOG_TRACE(log, "Renaming " << path + ".bin" << " to " << path + ".bin" + ".old"); + Poco::File(path + ".bin").renameTo(path + ".bin" + ".old"); + LOG_TRACE(log, "Renaming " << path + ".mrk" << " to " << path + ".mrk" + ".old"); + Poco::File(path + ".mrk").renameTo(path + ".mrk" + ".old"); + } + } + + /// переименовываем временные столбцы + for (DataPartPtr & part : parts) + { + std::string path = full_path + part->name + '/' + escapeForFileName(out_column); + std::string new_path = full_path + part->name + '/' + escapeForFileName(name_type.name); + if (Poco::File(path + ".bin").exists()) + { + LOG_TRACE(log, "Renaming " << path + ".bin" << " to " << new_path + ".bin"); + Poco::File(path + ".bin").renameTo(new_path + ".bin"); + LOG_TRACE(log, "Renaming " << path + ".mrk" << " to " << new_path + ".mrk"); + Poco::File(path + ".mrk").renameTo(new_path + ".mrk"); + } + } + + // удаляем старые столбцы + for (DataPartPtr & part : parts) + { + std::string path = full_path + part->name + '/' + escapeForFileName(name_type.name); + if (Poco::File(path + ".bin" + ".old").exists()) + { + LOG_TRACE(log, "Removing old column " << path + ".bin" + ".old"); + Poco::File(path + ".bin" + ".old").remove(); + LOG_TRACE(log, "Removing old column " << path + ".mrk" + ".old"); + Poco::File(path + ".mrk" + ".old").remove(); + } + } + + context.getUncompressedCache()->reset(); + context.getMarkCache()->reset(); + + { + Poco::ScopedLock lock(data_parts_mutex); + Poco::ScopedLock lock_all(all_data_parts_mutex); + alterColumns(params, columns, context); + } +} + + +bool MergeTreeData::isPartDirectory(const String & dir_name, Poco::RegularExpression::MatchVec & matches) const +{ + return (file_name_regexp.match(dir_name, 0, matches) && 6 == matches.size()); +} + + +bool MergeTreeData::isBrokenPart(const String & path) +{ + /// Проверяем, что первичный ключ непуст. + + Poco::File index_file(path + "/primary.idx"); + + if (!index_file.exists() || index_file.getSize() == 0) + { + LOG_ERROR(log, "Part " << path << " is broken: primary key is empty."); + + return true; + } + + /// Проверяем, что все засечки непусты и имеют одинаковый размер. + + ssize_t marks_size = -1; + for (NamesAndTypesList::const_iterator it = columns->begin(); it != columns->end(); ++it) + { + Poco::File marks_file(path + "/" + escapeForFileName(it->first) + ".mrk"); + + /// при добавлении нового столбца в таблицу файлы .mrk не создаются. Не будем ничего удалять. + if (!marks_file.exists()) + continue; + + if (marks_size == -1) + { + marks_size = marks_file.getSize(); + + if (0 == marks_size) + { + LOG_ERROR(log, "Part " << path << " is broken: " << marks_file.path() << " is empty."); + + return true; + } + } + else + { + if (static_cast(marks_file.getSize()) != marks_size) + { + LOG_ERROR(log, "Part " << path << " is broken: marks have different sizes."); + + return true; + } + } + } + + return false; +} + +Strings MergeTreeData::tryRestorePart(const String & path, const String & file_name, Strings & old_parts) +{ + LOG_ERROR(log, "Restoring all old_ parts covered by " << file_name); + + Poco::RegularExpression::MatchVec matches; + Strings restored_parts; + + isPartDirectory(file_name, matches); + DataPart broken_part(*this); + parsePartName(file_name, matches, broken_part); + + for (int i = static_cast(old_parts.size()) - 1; i >= 0; --i) + { + DataPart old_part(*this); + String name = old_parts[i].substr(strlen("old_")); + if (!isPartDirectory(name, matches)) + { + LOG_ERROR(log, "Strange file name: " << path + old_parts[i] << "; ignoring"); + old_parts.erase(old_parts.begin() + i); + continue; + } + parsePartName(name, matches, old_part); + if (broken_part.contains(old_part)) + { + /// Восстанавливаем все содержащиеся куски. Если некоторые из них содержатся в других, их удалит loadDataParts. + LOG_ERROR(log, "Restoring part " << path + old_parts[i]); + Poco::File(path + old_parts[i]).renameTo(path + name); + old_parts.erase(old_parts.begin() + i); + restored_parts.push_back(name); + } + } + + if (restored_parts.size() >= 2) + { + LOG_ERROR(log, "Removing broken part " << path + file_name << " because at least 2 old_ parts were restored in its place"); + Poco::File(path + file_name).remove(true); + } + else + { + LOG_ERROR(log, "Not removing broken part " << path + file_name + << " because less than 2 old_ parts were restored in its place. You need to resolve this manually"); + } + + return restored_parts; +} + +void MergeTreeData::replaceParts(DataPartsVector old_parts, DataPartPtr new_part) +{ + Poco::ScopedLock lock(data_parts_mutex); + Poco::ScopedLock all_lock(all_data_parts_mutex); + + for (size_t i = 0; i < old_parts.size(); ++i) + if (data_parts.end() == data_parts.find(old_parts[i])) + throw Exception("Logical error: cannot find data part " + old_parts[i]->name + " in list", ErrorCodes::LOGICAL_ERROR); + + data_parts.insert(new_part); + all_data_parts.insert(new_part); + + for (size_t i = 0; i < old_parts.size(); ++i) + data_parts.erase(data_parts.find(old_parts[i])); +} + +void MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr part, Increment * increment) +{ + LOG_TRACE(log, "Renaming."); + + Poco::ScopedLock lock(data_parts_mutex); + Poco::ScopedLock lock_all(all_data_parts_mutex); + + String old_path = getFullPath() + part->name + "/"; + + UInt64 part_id = part->left; + + /** Важно, что получение номера куска происходит атомарно с добавлением этого куска в набор. + * Иначе есть race condition - может произойти слияние пары кусков, диапазоны номеров которых + * содержат ещё не добавленный кусок. + */ + if (increment) + part_id = increment->get(false); + + part->left = part->right = part_id; + part->name = getPartName(part->left_date, part->right_date, part_id, part_id, 0); + + String new_path = getFullPath() + part->name + "/"; + + /// Переименовываем кусок. + Poco::File(old_path).renameTo(new_path); + + data_parts.insert(part); + all_data_parts.insert(part); +} + +MergeTreeData::DataParts MergeTreeData::getDataParts() +{ + Poco::ScopedLock lock(data_parts_mutex); + + return data_parts; +} + +} diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp new file mode 100644 index 00000000000..160b4dc2b95 --- /dev/null +++ b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp @@ -0,0 +1,348 @@ +#include +#include +#include +#include +#include +#include +#include + + +namespace DB +{ + +/// Не будем соглашаться мерджить куски, если места на диске менее чем во столько раз больше суммарного размера кусков. +static const double DISK_USAGE_COEFFICIENT_TO_SELECT = 1.6; + +/// Объединяя куски, зарезервируем столько места на диске. Лучше сделать немного меньше, чем DISK_USAGE_COEFFICIENT_TO_SELECT, +/// потому что между выбором кусков и резервированием места места может стать немного меньше. +static const double DISK_USAGE_COEFFICIENT_TO_RESERVE = 1.4; + + +/// Выбираем отрезок из не более чем max_parts_to_merge_at_once кусков так, чтобы максимальный размер был меньше чем в max_size_ratio_to_merge_parts раз больше суммы остальных. +/// Это обеспечивает в худшем случае время O(n log n) на все слияния, независимо от выбора сливаемых кусков, порядка слияния и добавления. +/// При max_parts_to_merge_at_once >= log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts), +/// несложно доказать, что всегда будет что сливать, пока количество кусков больше +/// log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts)*(количество кусков размером больше max_rows_to_merge_parts). +/// Дальше эвристики. +/// Будем выбирать максимальный по включению подходящий отрезок. +/// Из всех таких выбираем отрезок с минимальным максимумом размера. +/// Из всех таких выбираем отрезок с минимальным минимумом размера. +/// Из всех таких выбираем отрезок с максимальной длиной. +/// Дополнительно: +/// 1) с 1:00 до 5:00 ограничение сверху на размер куска в основном потоке увеличивается в несколько раз +/// 2) в зависимоти от возраста кусков меняется допустимая неравномерность при слиянии +/// 3) Молодые куски крупного размера (примерно больше 1 Гб) можно сливать не меньше чем по три +/// 4) Если в одном из потоков идет мердж крупных кусков, то во втором сливать только маленькие кусочки +/// 5) С ростом логарифма суммарного размера кусочков в мердже увеличиваем требование сбалансированности + +bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & parts, size_t available_disk_space, + bool merge_anything_for_old_months, bool aggressive, bool only_small, const AllowedMergingPredicate & can_merge) +{ + LOG_DEBUG(log, "Selecting parts to merge"); + + MergeTreeData::DataParts data_parts = data.getDataParts(); + + DateLUTSingleton & date_lut = DateLUTSingleton::instance(); + + size_t min_max = -1U; + size_t min_min = -1U; + int max_len = 0; + MergeTreeData::DataParts::iterator best_begin; + bool found = false; + + DayNum_t now_day = date_lut.toDayNum(time(0)); + DayNum_t now_month = date_lut.toFirstDayNumOfMonth(now_day); + int now_hour = date_lut.toHourInaccurate(time(0)); + + /// Сколько кусков, начиная с текущего, можно включить в валидный отрезок, начинающийся левее текущего куска. + /// Нужно для определения максимальности по включению. + int max_count_from_left = 0; + + size_t cur_max_rows_to_merge_parts = data.settings.max_rows_to_merge_parts; + + /// Если ночь, можем мерджить сильно большие куски + if (now_hour >= 1 && now_hour <= 5) + cur_max_rows_to_merge_parts *= data.settings.merge_parts_at_night_inc; + + if (only_small) + { + cur_max_rows_to_merge_parts = data.settings.max_rows_to_merge_parts_second; + } + + /// Левый конец отрезка. + for (MergeTreeData::DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it) + { + const MergeTreeData::DataPartPtr & first_part = *it; + + max_count_from_left = std::max(0, max_count_from_left - 1); + + /// Кусок достаточно мал или слияние "агрессивное". + if (first_part->size * data.index_granularity > cur_max_rows_to_merge_parts + && !aggressive) + continue; + + /// Кусок в одном месяце. + if (first_part->left_month != first_part->right_month) + { + LOG_WARNING(log, "Part " << first_part->name << " spans more than one month"); + continue; + } + + /// Самый длинный валидный отрезок, начинающийся здесь. + size_t cur_longest_max = -1U; + size_t cur_longest_min = -1U; + int cur_longest_len = 0; + + /// Текущий отрезок, не обязательно валидный. + size_t cur_max = first_part->size; + size_t cur_min = first_part->size; + size_t cur_sum = first_part->size; + size_t cur_total_size = first_part->size_in_bytes; + int cur_len = 1; + + DayNum_t month = first_part->left_month; + UInt64 cur_id = first_part->right; + + /// Этот месяц кончился хотя бы день назад. + bool is_old_month = now_day - now_month >= 1 && now_month > month; + + time_t oldest_modification_time = first_part->modification_time; + + /// Правый конец отрезка. + MergeTreeData::DataParts::iterator jt = it; + for (; cur_len < static_cast(data.settings.max_parts_to_merge_at_once);) + { + const MergeTreeData::DataPartPtr & prev_part = *jt; + ++jt; + + if (jt == data_parts.end()) + break; + + const MergeTreeData::DataPartPtr & last_part = *jt; + + /// Кусок разрешено сливать с предыдущим, и в одном правильном месяце. + if (!can_merge(prev_part, last_part) || + last_part->left_month != last_part->right_month || + last_part->left_month != month) + break; + + /// Кусок достаточно мал или слияние "агрессивное". + if (last_part->size * data.index_granularity > cur_max_rows_to_merge_parts + && !aggressive) + break; + + /// Кусок правее предыдущего. + if (last_part->left < cur_id) + { + LOG_WARNING(log, "Part " << last_part->name << " intersects previous part"); + break; + } + + oldest_modification_time = std::max(oldest_modification_time, last_part->modification_time); + cur_max = std::max(cur_max, last_part->size); + cur_min = std::min(cur_min, last_part->size); + cur_sum += last_part->size; + cur_total_size += last_part->size_in_bytes; + ++cur_len; + cur_id = last_part->right; + + int min_len = 2; + int cur_age_in_sec = time(0) - oldest_modification_time; + + /// Если куски примерно больше 1 Gb и образовались меньше 6 часов назад, то мерджить не меньше чем по 3. + if (cur_max * data.index_granularity * 150 > 1024*1024*1024 && cur_age_in_sec < 6*3600) + min_len = 3; + + /// Равен 0.5 если возраст порядка 0, равен 5 если возраст около месяца. + double time_ratio_modifier = 0.5 + 9 * static_cast(cur_age_in_sec) / (3600*24*30 + cur_age_in_sec); + + /// Двоичный логарифм суммарного размера кусочков + double log_cur_sum = std::log(cur_sum * data.index_granularity) / std::log(2); + /// Равен ~2 если куски маленькие, уменьшается до 0.5 с увеличением суммарного размера до 2^25. + double size_ratio_modifier = std::max(0.25, 2 - 3 * (log_cur_sum) / (25 + log_cur_sum)); + + /// Объединяем все в одну константу + double ratio = std::max(0.5, time_ratio_modifier * size_ratio_modifier * data.settings.max_size_ratio_to_merge_parts); + + /// Если отрезок валидный, то он самый длинный валидный, начинающийся тут. + if (cur_len >= min_len + && (static_cast(cur_max) / (cur_sum - cur_max) < ratio + /// За старый месяц объединяем что угодно, если разрешено и если этому хотя бы 15 дней + || (is_old_month && merge_anything_for_old_months && cur_age_in_sec > 3600*24*15) + /// Если слияние "агрессивное", то сливаем что угодно + || aggressive)) + { + /// Достаточно места на диске, чтобы покрыть новый мердж с запасом. + if (available_disk_space > cur_total_size * DISK_USAGE_COEFFICIENT_TO_SELECT) + { + cur_longest_max = cur_max; + cur_longest_min = cur_min; + cur_longest_len = cur_len; + } + else + LOG_WARNING(log, "Won't merge parts from " << first_part->name << " to " << last_part->name + << " because not enough free space: " << available_disk_space << " free and unreserved, " + << cur_total_size << " required now (+" << static_cast((DISK_USAGE_COEFFICIENT_TO_SELECT - 1.0) * 100) + << "% on overhead)"); + } + } + + /// Это максимальный по включению валидный отрезок. + if (cur_longest_len > max_count_from_left) + { + max_count_from_left = cur_longest_len; + + if (!found + || std::make_pair(std::make_pair(cur_longest_max, cur_longest_min), -cur_longest_len) + < std::make_pair(std::make_pair(min_max, min_min), -max_len)) + { + found = true; + min_max = cur_longest_max; + min_min = cur_longest_min; + max_len = cur_longest_len; + best_begin = it; + } + } + } + + if (found) + { + parts.clear(); + + MergeTreeData::DataParts::iterator it = best_begin; + for (int i = 0; i < max_len; ++i) + { + parts.push_back(*it); + ++it; + } + + LOG_DEBUG(log, "Selected " << parts.size() << " parts from " << parts.front()->name << " to " << parts.back()->name); + } + else + { + LOG_DEBUG(log, "No parts to merge"); + } + + return found; +} + + +/// parts должны быть отсортированы. +String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & parts) +{ + LOG_DEBUG(log, "Merging " << parts.size() << " parts: from " << parts.front()->name << " to " << parts.back()->name); + + Names all_column_names; + NamesAndTypesList columns_list = data.getColumnsList(); + for (const auto & it : columns_list) + all_column_names.push_back(it.first); + + DateLUTSingleton & date_lut = DateLUTSingleton::instance(); + + MergeTreeData::MutableDataPartPtr new_data_part = std::make_shared(data); + new_data_part->left_date = std::numeric_limits::max(); + new_data_part->right_date = std::numeric_limits::min(); + new_data_part->left = parts.front()->left; + new_data_part->right = parts.back()->right; + new_data_part->level = 0; + for (size_t i = 0; i < parts.size(); ++i) + { + new_data_part->level = std::max(new_data_part->level, parts[i]->level); + new_data_part->left_date = std::min(new_data_part->left_date, parts[i]->left_date); + new_data_part->right_date = std::max(new_data_part->right_date, parts[i]->right_date); + } + ++new_data_part->level; + new_data_part->name = MergeTreeData::getPartName( + new_data_part->left_date, new_data_part->right_date, new_data_part->left, new_data_part->right, new_data_part->level); + new_data_part->left_month = date_lut.toFirstDayNumOfMonth(new_data_part->left_date); + new_data_part->right_month = date_lut.toFirstDayNumOfMonth(new_data_part->right_date); + + /** Читаем из всех кусков, сливаем и пишем в новый. + * Попутно вычисляем выражение для сортировки. + */ + BlockInputStreams src_streams; + + for (size_t i = 0; i < parts.size(); ++i) + { + MarkRanges ranges(1, MarkRange(0, parts[i]->size)); + src_streams.push_back(new ExpressionBlockInputStream(new MergeTreeBlockInputStream( + data.getFullPath() + parts[i]->name + '/', DEFAULT_MERGE_BLOCK_SIZE, all_column_names, data, + parts[i], ranges, false, NULL, ""), data.getPrimaryExpression())); + } + + /// Порядок потоков важен: при совпадении ключа элементы идут в порядке номера потока-источника. + /// В слитом куске строки с одинаковым ключом должны идти в порядке возрастания идентификатора исходного куска, то есть (примерного) возрастания времени вставки. + BlockInputStreamPtr merged_stream; + + switch (data.mode) + { + case MergeTreeData::Ordinary: + merged_stream = new MergingSortedBlockInputStream(src_streams, data.getSortDescription(), DEFAULT_MERGE_BLOCK_SIZE); + break; + + case MergeTreeData::Collapsing: + merged_stream = new CollapsingSortedBlockInputStream(src_streams, data.getSortDescription(), data.sign_column, DEFAULT_MERGE_BLOCK_SIZE); + break; + + case MergeTreeData::Summing: + merged_stream = new SummingSortedBlockInputStream(src_streams, data.getSortDescription(), DEFAULT_MERGE_BLOCK_SIZE); + break; + + default: + throw Exception("Unknown mode of operation for MergeTreeData: " + toString(data.mode), ErrorCodes::LOGICAL_ERROR); + } + + MergedBlockOutputStreamPtr to = new MergedBlockOutputStream(data, + new_data_part->left_date, new_data_part->right_date, new_data_part->left, new_data_part->right, new_data_part->level); + + merged_stream->readPrefix(); + to->writePrefix(); + + Block block; + while (!canceled && (block = merged_stream->read())) + to->write(block); + + if (canceled) + { + LOG_INFO(log, "Canceled merging parts."); + return ""; + } + + merged_stream->readSuffix(); + to->writeSuffix(); + + /// В обычном режиме строчки не могут удалиться при мердже. + if (0 == to->marksCount() && data.mode == MergeTreeData::Ordinary) + throw Exception("Empty part after merge", ErrorCodes::LOGICAL_ERROR); + + new_data_part->size = to->marksCount(); + new_data_part->modification_time = time(0); + + if (0 == to->marksCount()) + { + LOG_INFO(log, "All rows have been deleted while merging from " << parts.front()->name << " to " << parts.back()->name); + return ""; + } + + /// NOTE Только что записанный индекс заново считывается с диска. Можно было бы формировать его сразу при записи. + new_data_part->loadIndex(); + + /// Добавляем новый кусок в набор. + data.replaceParts(parts, new_data_part); + + LOG_TRACE(log, "Merged " << parts.size() << " parts: from " << parts.front()->name << " to " << parts.back()->name); + + return new_data_part->name; +} + +size_t MergeTreeDataMerger::estimateDiskSpaceForMerge(const MergeTreeData::DataPartsVector & parts) +{ + size_t res = 0; + for (const MergeTreeData::DataPartPtr & part : parts) + { + res += part->size_in_bytes; + } + return static_cast(res * DISK_USAGE_COEFFICIENT_TO_RESERVE); +} + +} diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp new file mode 100644 index 00000000000..ae60e78e71d --- /dev/null +++ b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -0,0 +1,466 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +MergeTreeDataSelectExecutor::MergeTreeDataSelectExecutor(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataSelectExecutor")) +{ + min_marks_for_seek = (data.settings.min_rows_for_seek + data.index_granularity - 1) / data.index_granularity; + min_marks_for_concurrent_read = (data.settings.min_rows_for_concurrent_read + data.index_granularity - 1) / data.index_granularity; + max_marks_to_use_cache = (data.settings.max_rows_to_use_cache + data.index_granularity - 1) / data.index_granularity; + + +} + +BlockInputStreams MergeTreeDataSelectExecutor::read( + const Names & column_names_to_return, + ASTPtr query, + const Settings & settings, + QueryProcessingStage::Enum & processed_stage, + size_t max_block_size, + unsigned threads) +{ + data.check(column_names_to_return); + processed_stage = QueryProcessingStage::FetchColumns; + + PKCondition key_condition(query, data.context, data.getColumnsList(), data.getSortDescription()); + PKCondition date_condition(query, data.context, data.getColumnsList(), SortDescription(1, SortColumnDescription(data.date_column_name, 1))); + + MergeTreeData::DataPartsVector parts; + + /// Выберем куски, в которых могут быть данные, удовлетворяющие date_condition. + { + MergeTreeData::DataParts data_parts = data.getDataParts(); + + for (MergeTreeData::DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it) + { + Field left = static_cast((*it)->left_date); + Field right = static_cast((*it)->right_date); + + if (date_condition.mayBeTrueInRange(&left, &right)) + parts.push_back(*it); + } + } + + /// Семплирование. + Names column_names_to_read = column_names_to_return; + UInt64 sampling_column_value_limit = 0; + typedef Poco::SharedPtr ASTFunctionPtr; + ASTFunctionPtr filter_function; + ExpressionActionsPtr filter_expression; + + ASTSelectQuery & select = *dynamic_cast(&*query); + if (select.sample_size) + { + double size = apply_visitor(FieldVisitorConvertToNumber(), + dynamic_cast(*select.sample_size).value); + + if (size < 0) + throw Exception("Negative sample size", ErrorCodes::ARGUMENT_OUT_OF_BOUND); + + if (size > 1) + { + size_t requested_count = apply_visitor(FieldVisitorConvertToNumber(), dynamic_cast(*select.sample_size).value); + + /// Узнаем, сколько строк мы бы прочли без семплирования. + LOG_DEBUG(log, "Preliminary index scan with condition: " << key_condition.toString()); + size_t total_count = 0; + for (size_t i = 0; i < parts.size(); ++i) + { + MergeTreeData::DataPartPtr & part = parts[i]; + MarkRanges ranges = markRangesFromPkRange(part->index, key_condition); + + for (size_t j = 0; j < ranges.size(); ++j) + total_count += ranges[j].end - ranges[j].begin; + } + total_count *= data.index_granularity; + + size = std::min(1., static_cast(requested_count) / total_count); + + LOG_DEBUG(log, "Selected relative sample size: " << size); + } + + UInt64 sampling_column_max = 0; + DataTypePtr type = data.getPrimaryExpression()->getSampleBlock().getByName(data.sampling_expression->getColumnName()).type; + + if (type->getName() == "UInt64") + sampling_column_max = std::numeric_limits::max(); + else if (type->getName() == "UInt32") + sampling_column_max = std::numeric_limits::max(); + else if (type->getName() == "UInt16") + sampling_column_max = std::numeric_limits::max(); + else if (type->getName() == "UInt8") + sampling_column_max = std::numeric_limits::max(); + else + throw Exception("Invalid sampling column type in storage parameters: " + type->getName() + ". Must be unsigned integer type.", ErrorCodes::ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER); + + /// Добавим условие, чтобы отсечь еще что-нибудь при повторном просмотре индекса. + sampling_column_value_limit = static_cast(size * sampling_column_max); + if (!key_condition.addCondition(data.sampling_expression->getColumnName(), + Range::createRightBounded(sampling_column_value_limit, true))) + throw Exception("Sampling column not in primary key", ErrorCodes::ILLEGAL_COLUMN); + + /// Выражение для фильтрации: sampling_expression <= sampling_column_value_limit + + ASTPtr filter_function_args = new ASTExpressionList; + filter_function_args->children.push_back(data.sampling_expression); + filter_function_args->children.push_back(new ASTLiteral(StringRange(), sampling_column_value_limit)); + + filter_function = new ASTFunction; + filter_function->name = "lessOrEquals"; + filter_function->arguments = filter_function_args; + filter_function->children.push_back(filter_function->arguments); + + filter_expression = ExpressionAnalyzer(filter_function, data.context, data.getColumnsList()).getActions(false); + + /// Добавим столбцы, нужные для sampling_expression. + std::vector add_columns = filter_expression->getRequiredColumns(); + column_names_to_read.insert(column_names_to_read.end(), add_columns.begin(), add_columns.end()); + std::sort(column_names_to_read.begin(), column_names_to_read.end()); + column_names_to_read.erase(std::unique(column_names_to_read.begin(), column_names_to_read.end()), column_names_to_read.end()); + } + + LOG_DEBUG(log, "Key condition: " << key_condition.toString()); + LOG_DEBUG(log, "Date condition: " << date_condition.toString()); + + /// PREWHERE + ExpressionActionsPtr prewhere_actions; + String prewhere_column; + if (select.prewhere_expression) + { + ExpressionAnalyzer analyzer(select.prewhere_expression, data.context, data.getColumnsList()); + prewhere_actions = analyzer.getActions(false); + prewhere_column = select.prewhere_expression->getColumnName(); + /// TODO: Чтобы работали подзапросы в PREWHERE, можно тут сохранить analyzer.getSetsWithSubqueries(), а потом их выполнить. + } + + RangesInDataParts parts_with_ranges; + + /// Найдем, какой диапазон читать из каждого куска. + size_t sum_marks = 0; + size_t sum_ranges = 0; + for (size_t i = 0; i < parts.size(); ++i) + { + MergeTreeData::DataPartPtr & part = parts[i]; + RangesInDataPart ranges(part); + ranges.ranges = markRangesFromPkRange(part->index, key_condition); + + if (!ranges.ranges.empty()) + { + parts_with_ranges.push_back(ranges); + + sum_ranges += ranges.ranges.size(); + for (size_t j = 0; j < ranges.ranges.size(); ++j) + { + sum_marks += ranges.ranges[j].end - ranges.ranges[j].begin; + } + } + } + + LOG_DEBUG(log, "Selected " << parts.size() << " parts by date, " << parts_with_ranges.size() << " parts by key, " + << sum_marks << " marks to read from " << sum_ranges << " ranges"); + + BlockInputStreams res; + + if (select.final) + { + /// Добавим столбцы, нужные для вычисления первичного ключа и знака. + std::vector add_columns = data.getPrimaryExpression()->getRequiredColumns(); + column_names_to_read.insert(column_names_to_read.end(), add_columns.begin(), add_columns.end()); + column_names_to_read.push_back(data.sign_column); + std::sort(column_names_to_read.begin(), column_names_to_read.end()); + column_names_to_read.erase(std::unique(column_names_to_read.begin(), column_names_to_read.end()), column_names_to_read.end()); + + res = spreadMarkRangesAmongThreadsFinal( + parts_with_ranges, + threads, + column_names_to_read, + max_block_size, + settings.use_uncompressed_cache, + prewhere_actions, + prewhere_column); + } + else + { + res = spreadMarkRangesAmongThreads( + parts_with_ranges, + threads, + column_names_to_read, + max_block_size, + settings.use_uncompressed_cache, + prewhere_actions, + prewhere_column); + } + + if (select.sample_size) + { + for (size_t i = 0; i < res.size(); ++i) + { + BlockInputStreamPtr original_stream = res[i]; + BlockInputStreamPtr expression_stream = new ExpressionBlockInputStream(original_stream, filter_expression); + BlockInputStreamPtr filter_stream = new FilterBlockInputStream(expression_stream, filter_function->getColumnName()); + res[i] = filter_stream; + } + } + + return res; +} + +BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreads( + RangesInDataParts parts, + size_t threads, + const Names & column_names, + size_t max_block_size, + bool use_uncompressed_cache, + ExpressionActionsPtr prewhere_actions, + const String & prewhere_column) +{ + /// На всякий случай перемешаем куски. + std::random_shuffle(parts.begin(), parts.end()); + + /// Посчитаем засечки для каждого куска. + std::vector sum_marks_in_parts(parts.size()); + size_t sum_marks = 0; + for (size_t i = 0; i < parts.size(); ++i) + { + /// Пусть отрезки будут перечислены справа налево, чтобы можно было выбрасывать самый левый отрезок с помощью pop_back(). + std::reverse(parts[i].ranges.begin(), parts[i].ranges.end()); + + sum_marks_in_parts[i] = 0; + for (size_t j = 0; j < parts[i].ranges.size(); ++j) + { + MarkRange & range = parts[i].ranges[j]; + sum_marks_in_parts[i] += range.end - range.begin; + } + sum_marks += sum_marks_in_parts[i]; + } + + if (sum_marks > max_marks_to_use_cache) + use_uncompressed_cache = false; + + BlockInputStreams res; + + if (sum_marks > 0) + { + size_t min_marks_per_thread = (sum_marks - 1) / threads + 1; + + for (size_t i = 0; i < threads && !parts.empty(); ++i) + { + size_t need_marks = min_marks_per_thread; + BlockInputStreams streams; + + /// Цикл по кускам. + while (need_marks > 0 && !parts.empty()) + { + RangesInDataPart & part = parts.back(); + size_t & marks_in_part = sum_marks_in_parts.back(); + + /// Не будем брать из куска слишком мало строк. + if (marks_in_part >= min_marks_for_concurrent_read && + need_marks < min_marks_for_concurrent_read) + need_marks = min_marks_for_concurrent_read; + + /// Не будем оставлять в куске слишком мало строк. + if (marks_in_part > need_marks && + marks_in_part - need_marks < min_marks_for_concurrent_read) + need_marks = marks_in_part; + + /// Возьмем весь кусок, если он достаточно мал. + if (marks_in_part <= need_marks) + { + /// Восстановим порядок отрезков. + std::reverse(part.ranges.begin(), part.ranges.end()); + + streams.push_back(new MergeTreeBlockInputStream( + data.getFullPath() + part.data_part->name + '/', max_block_size, column_names, data, + part.data_part, part.ranges, use_uncompressed_cache, + prewhere_actions, prewhere_column)); + need_marks -= marks_in_part; + parts.pop_back(); + sum_marks_in_parts.pop_back(); + continue; + } + + MarkRanges ranges_to_get_from_part; + + /// Цикл по отрезкам куска. + while (need_marks > 0) + { + if (part.ranges.empty()) + throw Exception("Unexpected end of ranges while spreading marks among threads", ErrorCodes::LOGICAL_ERROR); + + MarkRange & range = part.ranges.back(); + size_t marks_in_range = range.end - range.begin; + + size_t marks_to_get_from_range = std::min(marks_in_range, need_marks); + ranges_to_get_from_part.push_back(MarkRange(range.begin, range.begin + marks_to_get_from_range)); + range.begin += marks_to_get_from_range; + marks_in_part -= marks_to_get_from_range; + need_marks -= marks_to_get_from_range; + if (range.begin == range.end) + part.ranges.pop_back(); + } + + streams.push_back(new MergeTreeBlockInputStream( + data.getFullPath() + part.data_part->name + '/', max_block_size, column_names, data, + part.data_part, ranges_to_get_from_part, use_uncompressed_cache, + prewhere_actions, prewhere_column)); + } + + if (streams.size() == 1) + res.push_back(streams[0]); + else + res.push_back(new ConcatBlockInputStream(streams)); + } + + if (!parts.empty()) + throw Exception("Couldn't spread marks among threads", ErrorCodes::LOGICAL_ERROR); + } + + return res; +} + +BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreadsFinal( + RangesInDataParts parts, + size_t threads, + const Names & column_names, + size_t max_block_size, + bool use_uncompressed_cache, + ExpressionActionsPtr prewhere_actions, + const String & prewhere_column) +{ + size_t sum_marks = 0; + for (size_t i = 0; i < parts.size(); ++i) + for (size_t j = 0; j < parts[i].ranges.size(); ++j) + sum_marks += parts[i].ranges[j].end - parts[i].ranges[j].begin; + + if (sum_marks > max_marks_to_use_cache) + use_uncompressed_cache = false; + + ExpressionActionsPtr sign_filter_expression; + String sign_filter_column; + createPositiveSignCondition(sign_filter_expression, sign_filter_column); + + BlockInputStreams to_collapse; + + for (size_t part_index = 0; part_index < parts.size(); ++part_index) + { + RangesInDataPart & part = parts[part_index]; + + BlockInputStreamPtr source_stream = new MergeTreeBlockInputStream( + data.getFullPath() + part.data_part->name + '/', max_block_size, column_names, data, + part.data_part, part.ranges, use_uncompressed_cache, + prewhere_actions, prewhere_column); + + to_collapse.push_back(new ExpressionBlockInputStream(source_stream, data.getPrimaryExpression())); + } + + BlockInputStreams res; + if (to_collapse.size() == 1) + res.push_back(new FilterBlockInputStream(new ExpressionBlockInputStream(to_collapse[0], sign_filter_expression), sign_filter_column)); + else if (to_collapse.size() > 1) + res.push_back(new CollapsingFinalBlockInputStream(to_collapse, data.getSortDescription(), data.sign_column)); + + return res; +} + +void MergeTreeDataSelectExecutor::createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column) +{ + ASTFunction * function = new ASTFunction; + ASTPtr function_ptr = function; + + ASTExpressionList * arguments = new ASTExpressionList; + ASTPtr arguments_ptr = arguments; + + ASTIdentifier * sign = new ASTIdentifier; + ASTPtr sign_ptr = sign; + + ASTLiteral * one = new ASTLiteral; + ASTPtr one_ptr = one; + + function->name = "equals"; + function->arguments = arguments_ptr; + function->children.push_back(arguments_ptr); + + arguments->children.push_back(sign_ptr); + arguments->children.push_back(one_ptr); + + sign->name = data.sign_column; + sign->kind = ASTIdentifier::Column; + + one->type = new DataTypeInt8; + one->value = Field(static_cast(1)); + + out_expression = ExpressionAnalyzer(function_ptr, data.context, data.getColumnsList()).getActions(false); + out_column = function->getColumnName(); +} + +/// Получает набор диапазонов засечек, вне которых не могут находиться ключи из заданного диапазона. +MarkRanges MergeTreeDataSelectExecutor::markRangesFromPkRange(const MergeTreeData::DataPart::Index & index, PKCondition & key_condition) +{ + MarkRanges res; + + size_t key_size = data.getSortDescription().size(); + size_t marks_count = index.size() / key_size; + + /// Если индекс не используется. + if (key_condition.alwaysTrue()) + { + res.push_back(MarkRange(0, marks_count)); + } + else + { + /** В стеке всегда будут находиться непересекающиеся подозрительные отрезки, самый левый наверху (back). + * На каждом шаге берем левый отрезок и проверяем, подходит ли он. + * Если подходит, разбиваем его на более мелкие и кладем их в стек. Если нет - выбрасываем его. + * Если отрезок уже длиной в одну засечку, добавляем его в ответ и выбрасываем. + */ + std::vector ranges_stack; + ranges_stack.push_back(MarkRange(0, marks_count)); + while (!ranges_stack.empty()) + { + MarkRange range = ranges_stack.back(); + ranges_stack.pop_back(); + + bool may_be_true; + if (range.end == marks_count) + may_be_true = key_condition.mayBeTrueAfter(&index[range.begin * key_size]); + else + may_be_true = key_condition.mayBeTrueInRange(&index[range.begin * key_size], &index[range.end * key_size]); + + if (!may_be_true) + continue; + + if (range.end == range.begin + 1) + { + /// Увидели полезный промежуток между соседними засечками. Либо добавим его к последнему диапазону, либо начнем новый диапазон. + if (res.empty() || range.begin - res.back().end > min_marks_for_seek) + res.push_back(range); + else + res.back().end = range.end; + } + else + { + /// Разбиваем отрезок и кладем результат в стек справа налево. + size_t step = (range.end - range.begin - 1) / data.settings.coarse_index_granularity + 1; + size_t end; + + for (end = range.end; end > range.begin + step; end -= step) + ranges_stack.push_back(MarkRange(end - step, end)); + + ranges_stack.push_back(MarkRange(range.begin, end)); + } + } + } + + return res; +} + +} diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp new file mode 100644 index 00000000000..a7c1550c7ee --- /dev/null +++ b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -0,0 +1,248 @@ +#include +#include +#include +#include + +namespace DB +{ + +BlocksWithDateIntervals MergeTreeDataWriter::splitBlockIntoParts(const Block & block) +{ + data.check(block, true); + + DateLUTSingleton & date_lut = DateLUTSingleton::instance(); + + size_t rows = block.rows(); + size_t columns = block.columns(); + + /// Достаём столбец с датой. + const ColumnUInt16::Container_t & dates = + dynamic_cast(*block.getByName(data.date_column_name).column).getData(); + + /// Минимальная и максимальная дата. + UInt16 min_date = std::numeric_limits::max(); + UInt16 max_date = std::numeric_limits::min(); + for (ColumnUInt16::Container_t::const_iterator it = dates.begin(); it != dates.end(); ++it) + { + if (*it < min_date) + min_date = *it; + if (*it > max_date) + max_date = *it; + } + + BlocksWithDateIntervals res; + + UInt16 min_month = date_lut.toFirstDayNumOfMonth(DayNum_t(min_date)); + UInt16 max_month = date_lut.toFirstDayNumOfMonth(DayNum_t(max_date)); + + /// Типичный случай - когда месяц один (ничего разделять не нужно). + if (min_month == max_month) + { + res.push_back(BlockWithDateInterval(block, min_date, max_date)); + return res; + } + + /// Разделяем на блоки по месяцам. Для каждого ещё посчитаем минимальную и максимальную дату. + typedef std::map BlocksByMonth; + BlocksByMonth blocks_by_month; + + for (size_t i = 0; i < rows; ++i) + { + UInt16 month = date_lut.toFirstDayNumOfMonth(DayNum_t(dates[i])); + + BlockWithDateInterval *& block_for_month = blocks_by_month[month]; + if (!block_for_month) + { + block_for_month = &*res.insert(res.end(), BlockWithDateInterval()); + block_for_month->block = block.cloneEmpty(); + } + + if (dates[i] < block_for_month->min_date) + block_for_month->min_date = dates[i]; + if (dates[i] > block_for_month->max_date) + block_for_month->max_date = dates[i]; + + for (size_t j = 0; j < columns; ++j) + block_for_month->block.getByPosition(j).column->insert((*block.getByPosition(j).column)[i]); + } + + return res; +} + +MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDateInterval & block_with_dates, UInt64 temp_index) +{ + Block & block = block_with_dates.block; + UInt16 min_date = block_with_dates.min_date; + UInt16 max_date = block_with_dates.max_date; + + DateLUTSingleton & date_lut = DateLUTSingleton::instance(); + + size_t rows = block.rows(); + size_t columns = block.columns(); + size_t part_size = (rows + data.index_granularity - 1) / data.index_granularity; + + String tmp_part_name = "tmp_" + data.getPartName( + DayNum_t(min_date), DayNum_t(max_date), + temp_index, temp_index, 0); + + String part_tmp_path = data.getFullPath() + tmp_part_name + "/"; + + Poco::File(part_tmp_path).createDirectories(); + + LOG_TRACE(log, "Calculating primary expression."); + + /// Если для сортировки надо вычислить некоторые столбцы - делаем это. + data.getPrimaryExpression()->execute(block); + + LOG_TRACE(log, "Sorting by primary key."); + + SortDescription sort_descr = data.getSortDescription(); + + /// Сортируем. + stableSortBlock(block, sort_descr); + + /// Наконец-то можно писать данные на диск. + LOG_TRACE(log, "Writing index."); + + /// Сначала пишем индекс. Индекс содержит значение PK для каждой index_granularity строки. + MergeTreeData::DataPart::Index index_vec; + index_vec.reserve(part_size * sort_descr.size()); + + { + WriteBufferFromFile index(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, flags); + + typedef std::vector PrimaryColumns; + PrimaryColumns primary_columns; + + for (size_t i = 0, size = sort_descr.size(); i < size; ++i) + primary_columns.push_back( + !sort_descr[i].column_name.empty() + ? &block.getByName(sort_descr[i].column_name) + : &block.getByPosition(sort_descr[i].column_number)); + + for (size_t i = 0; i < rows; i += data.index_granularity) + { + for (PrimaryColumns::const_iterator it = primary_columns.begin(); it != primary_columns.end(); ++it) + { + index_vec.push_back((*(*it)->column)[i]); + (*it)->type->serializeBinary(index_vec.back(), index); + } + } + + index.next(); + } + + LOG_TRACE(log, "Writing data."); + + /// Множество записанных столбцов со смещениями, чтобы не писать общие для вложенных структур столбцы несколько раз + OffsetColumns offset_columns; + + for (size_t i = 0; i < columns; ++i) + { + const ColumnWithNameAndType & column = block.getByPosition(i); + writeData(part_tmp_path, column.name, *column.type, *column.column, offset_columns); + } + + MergeTreeData::MutableDataPartPtr new_data_part = std::make_shared(data); + new_data_part->left_date = DayNum_t(min_date); + new_data_part->right_date = DayNum_t(max_date); + new_data_part->left = temp_index; + new_data_part->right = temp_index; + new_data_part->level = 0; + new_data_part->name = tmp_part_name; + new_data_part->size = part_size; + new_data_part->modification_time = time(0); + new_data_part->left_month = date_lut.toFirstDayNumOfMonth(new_data_part->left_date); + new_data_part->right_month = date_lut.toFirstDayNumOfMonth(new_data_part->right_date); + new_data_part->index.swap(index_vec); + + return new_data_part; +} + +void MergeTreeDataWriter::writeData(const String & path, const String & name, const IDataType & type, const IColumn & column, + OffsetColumns & offset_columns, size_t level) +{ + String escaped_column_name = escapeForFileName(name); + size_t size = column.size(); + + /// Для массивов требуется сначала сериализовать размеры, а потом значения. + if (const DataTypeArray * type_arr = dynamic_cast(&type)) + { + String size_name = escapeForFileName(DataTypeNested::extractNestedTableName(name)) + + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); + if (offset_columns.count(size_name) == 0) + { + offset_columns.insert(size_name); + + WriteBufferFromFile plain(path + size_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags); + WriteBufferFromFile marks(path + size_name + ".mrk", 4096, flags); + CompressedWriteBuffer compressed(plain); + + size_t prev_mark = 0; + while (prev_mark < size) + { + /// Каждая засечка - это: (смещение в файле до начала сжатого блока, смещение внутри блока) + writeIntBinary(plain.count(), marks); + writeIntBinary(compressed.offset(), marks); + + type_arr->serializeOffsets(column, compressed, prev_mark, data.index_granularity); + prev_mark += data.index_granularity; + + compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего. + } + + compressed.next(); + plain.next(); + marks.next(); + } + } + if (const DataTypeNested * type_nested = dynamic_cast(&type)) + { + String size_name = escaped_column_name + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level); + + WriteBufferFromFile plain(path + size_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags); + WriteBufferFromFile marks(path + size_name + ".mrk", 4096, flags); + CompressedWriteBuffer compressed(plain); + + size_t prev_mark = 0; + while (prev_mark < size) + { + /// Каждая засечка - это: (смещение в файле до начала сжатого блока, смещение внутри блока) + writeIntBinary(plain.count(), marks); + writeIntBinary(compressed.offset(), marks); + + type_nested->serializeOffsets(column, compressed, prev_mark, data.index_granularity); + prev_mark += data.index_granularity; + + compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего. + } + + compressed.next(); + plain.next(); + marks.next(); + } + + { + WriteBufferFromFile plain(path + escaped_column_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags); + WriteBufferFromFile marks(path + escaped_column_name + ".mrk", 4096, flags); + CompressedWriteBuffer compressed(plain); + + size_t prev_mark = 0; + while (prev_mark < size) + { + writeIntBinary(plain.count(), marks); + writeIntBinary(compressed.offset(), marks); + + type.serializeBinary(column, compressed, prev_mark, data.index_granularity); + prev_mark += data.index_granularity; + + compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего. + } + + compressed.next(); + plain.next(); + marks.next(); + } +} + +} diff --git a/dbms/src/Storages/StorageChunkMerger.cpp b/dbms/src/Storages/StorageChunkMerger.cpp index cba0e3bd0a3..8e4e7343cc6 100644 --- a/dbms/src/Storages/StorageChunkMerger.cpp +++ b/dbms/src/Storages/StorageChunkMerger.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -85,7 +86,7 @@ BlockInputStreams StorageChunkMerger::read( { if (chunk_ref->source_database_name != source_database) { - LOG_WARNING(log, "ChunkRef " + chunk_ref->getTableName() + " points to another database, ignoring"); + LOG_WARNING(log, "ChunkRef " + it->first + " points to another database, ignoring"); continue; } if (!chunks_table_names.count(chunk_ref->source_table_name)) @@ -97,7 +98,7 @@ BlockInputStreams StorageChunkMerger::read( } else { - LOG_WARNING(log, "ChunkRef " + chunk_ref->getTableName() + " points to non-existing Chunks table, ignoring"); + LOG_WARNING(log, "ChunkRef " + it->first + " points to non-existing Chunks table, ignoring"); } } } @@ -109,6 +110,14 @@ BlockInputStreams StorageChunkMerger::read( } } + TableLocks table_locks; + + /// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем. + for (auto table : selected_tables) + { + table_locks.push_back(table->lockStructure(false)); + } + BlockInputStreams res; /// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную. @@ -130,33 +139,36 @@ BlockInputStreams StorageChunkMerger::read( else /// Иначе, считаем допустимыми все возможные значения virtual_columns = new OneBlockInputStream(virtual_columns_block); - std::set values = VirtualColumnUtils::extractSingleValueFromBlocks(virtual_columns, _table_column_name); + std::multiset values = VirtualColumnUtils::extractSingleValueFromBlocks(virtual_columns, _table_column_name); bool all_inclusive = (values.size() == virtual_columns_block.rows()); - for (Storages::iterator it = selected_tables.begin(); it != selected_tables.end(); ++it) + for (size_t i = 0; i < selected_tables.size(); ++i) { - if ((*it)->getName() != "Chunks" && !all_inclusive && values.find((*it)->getTableName()) == values.end()) + StoragePtr table = selected_tables[i]; + auto table_lock = table_locks[i]; + + if (table->getName() != "Chunks" && !all_inclusive && values.find(table->getTableName()) == values.end()) continue; /// Список виртуальных столбцов, которые мы заполним сейчас и список столбцов, которые передадим дальше Names virt_column_names, real_column_names; for (const auto & column : column_names) - if (column == _table_column_name && (*it)->getName() != "Chunks") /// таблица Chunks сама заполняет столбец _table + if (column == _table_column_name && table->getName() != "Chunks") /// таблица Chunks сама заполняет столбец _table virt_column_names.push_back(column); else real_column_names.push_back(column); /// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой. if (real_column_names.size() == 0) - real_column_names.push_back(ExpressionActions::getSmallestColumn((*it)->getColumnsList())); + real_column_names.push_back(ExpressionActions::getSmallestColumn(table->getColumnsList())); ASTPtr modified_query_ast = query->clone(); /// Подменяем виртуальный столбец на его значение if (!virt_column_names.empty()) - VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, (*it)->getTableName()); + VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, table->getTableName()); - BlockInputStreams source_streams = (*it)->read( + BlockInputStreams source_streams = table->read( real_column_names, modified_query_ast, settings, @@ -164,6 +176,11 @@ BlockInputStreams StorageChunkMerger::read( max_block_size, selected_tables.size() > threads ? 1 : (threads / selected_tables.size())); + for (auto & stream : source_streams) + { + stream->addTableLock(table_lock); + } + /// Добавляем в ответ вирутальные столбцы for (const auto & virtual_column : virt_column_names) { @@ -171,7 +188,7 @@ BlockInputStreams StorageChunkMerger::read( { for (auto & stream : source_streams) { - stream = new AddingConstColumnBlockInputStream(stream, new DataTypeString, (*it)->getTableName(), _table_column_name); + stream = new AddingConstColumnBlockInputStream(stream, new DataTypeString, table->getTableName(), _table_column_name); } } } @@ -335,6 +352,14 @@ static ASTPtr newIdentifier(const std::string & name, ASTIdentifier::Kind kind) bool StorageChunkMerger::mergeChunks(const Storages & chunks) { typedef std::map ColumnsMap; + + TableLocks table_locks; + + /// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем. + for (auto table : chunks) + { + table_locks.push_back(table->lockStructure(false)); + } /// Объединим множества столбцов сливаемых чанков. ColumnsMap known_columns_types(columns->begin(), columns->end()); @@ -387,16 +412,16 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks) } currently_written_groups.insert(new_table_full_name); - - /// Уроним Chunks таблицу с таким именем, если она есть. Она могла остаться в результате прерванного слияния той же группы чанков. - executeQuery("DROP TABLE IF EXISTS " + new_table_full_name, context, true); - - /// Выполним запрос для создания Chunks таблицы. - executeQuery("CREATE TABLE " + new_table_full_name + " " + formatted_columns + " ENGINE = Chunks", context, true); - - new_storage_ptr = context.getTable(source_database, new_table_name); } - + + /// Уроним Chunks таблицу с таким именем, если она есть. Она могла остаться в результате прерванного слияния той же группы чанков. + executeQuery("DROP TABLE IF EXISTS " + new_table_full_name, context, true); + + /// Выполним запрос для создания Chunks таблицы. + executeQuery("CREATE TABLE " + new_table_full_name + " " + formatted_columns + " ENGINE = Chunks", context, true); + + new_storage_ptr = context.getTable(source_database, new_table_name); + /// Скопируем данные в новую таблицу. StorageChunks & new_storage = dynamic_cast(*new_storage_ptr); @@ -435,7 +460,7 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks) DEFAULT_MERGE_BLOCK_SIZE); BlockInputStreamPtr input = new AddingDefaultBlockInputStream(new ConcatBlockInputStream(input_streams), required_columns); - + input->readPrefix(); output->writePrefix(); @@ -455,6 +480,9 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks) } /// Атомарно подменим исходные таблицы ссылками на новую. + /// При этом удалять таблицы под мьютексом контекста нельзя, пока только отцепим их. + Storages tables_to_drop; + { Poco::ScopedLock lock(context.getMutex()); @@ -467,13 +495,13 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks) std::string src_name = src_storage->getTableName(); /// Если таблицу успели удалить, ничего не делаем. - if (!context.getDatabases()[source_database].count(src_name)) + if (!context.isTableExist(source_database, src_name)) continue; - /// Роняем исходную таблицу. - executeQuery("DROP TABLE " + backQuoteIfNeed(source_database) + "." + backQuoteIfNeed(src_name), context, true); - - /// Создаем на ее месте ChunkRef + /// Отцепляем исходную таблицу. Ее данные и метаданные остаются на диске. + tables_to_drop.push_back(context.detachTable(source_database, src_name)); + + /// Создаем на ее месте ChunkRef. Это возможно только потому что у ChunkRef нет ни, ни метаданных. try { context.addTable(source_database, src_name, StorageChunkRef::create(src_name, context, source_database, new_table_name, false)); @@ -490,6 +518,15 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks) currently_written_groups.erase(new_table_full_name); } + /// Теперь удалим данные отцепленных таблиц. + table_locks.clear(); + for (StoragePtr table : tables_to_drop) + { + InterpreterDropQuery::dropDetachedTable(source_database, table, context); + /// NOTE: Если между подменой таблицы и этой строчкой кто-то успеет попытаться создать новую таблицу на ее месте, + /// что-нибудь может сломаться. + } + /// Сейчас на new_storage ссылаются таблицы типа ChunkRef. Удалим лишнюю ссылку, которая была при создании. new_storage.removeReference(); diff --git a/dbms/src/Storages/StorageChunkRef.cpp b/dbms/src/Storages/StorageChunkRef.cpp index 1f42a1ddb3f..4c0ada7e39f 100644 --- a/dbms/src/Storages/StorageChunkRef.cpp +++ b/dbms/src/Storages/StorageChunkRef.cpp @@ -43,7 +43,7 @@ ASTPtr StorageChunkRef::getCustomCreateQuery(const Context & context) const return res; } -void StorageChunkRef::dropImpl() +void StorageChunkRef::drop() { try { diff --git a/dbms/src/Storages/StorageChunks.cpp b/dbms/src/Storages/StorageChunks.cpp index e85f47e28ee..7ff9741a3f6 100644 --- a/dbms/src/Storages/StorageChunks.cpp +++ b/dbms/src/Storages/StorageChunks.cpp @@ -57,7 +57,7 @@ BlockInputStreams StorageChunks::read( Block virtual_columns_block = getBlockWithVirtualColumns(); BlockInputStreamPtr virtual_columns = VirtualColumnUtils::getVirtualColumnsBlocks(query->clone(), virtual_columns_block, context); - std::set values = VirtualColumnUtils::extractSingleValueFromBlocks(virtual_columns, _table_column_name); + std::multiset values = VirtualColumnUtils::extractSingleValueFromBlocks(virtual_columns, _table_column_name); bool all_inclusive = (values.size() == virtual_columns_block.rows()); if (all_inclusive) diff --git a/dbms/src/Storages/StorageDistributed.cpp b/dbms/src/Storages/StorageDistributed.cpp index ede4fb735ad..51a7646276b 100644 --- a/dbms/src/Storages/StorageDistributed.cpp +++ b/dbms/src/Storages/StorageDistributed.cpp @@ -177,12 +177,12 @@ BlockInputStreams StorageDistributed::read( else /// Иначе, считаем допустимыми все возможные значения virtual_columns = new OneBlockInputStream(virtual_columns_block); - std::set< std::pair > values = + std::multiset< std::pair > values = VirtualColumnUtils::extractTwoValuesFromBlocks(virtual_columns, _host_column_name, _port_column_name); bool all_inclusive = values.size() == virtual_columns_block.rows(); size_t result_size = values.size(); - if (values.find(std::make_pair("localhost", clickhouse_port)) != values.end()) + if (cluster.getLocalNodesNum() > 0 && values.find(std::make_pair("localhost", clickhouse_port)) != values.end()) result_size += cluster.getLocalNodesNum() - 1; processed_stage = result_size == 1 @@ -239,10 +239,6 @@ BlockInputStreams StorageDistributed::read( } } - /// Не дадим уничтожать объект до конца обработки запроса. - for (auto & stream : res) - stream->setOwnedStorage(thisPtr()); - return res; } diff --git a/dbms/src/Storages/StorageFactory.cpp b/dbms/src/Storages/StorageFactory.cpp index 08cb6904183..315390328b5 100644 --- a/dbms/src/Storages/StorageFactory.cpp +++ b/dbms/src/Storages/StorageFactory.cpp @@ -29,6 +29,31 @@ namespace DB { + +/** Для StorageMergeTree: достать первичный ключ в виде ASTExpressionList. + * Он может быть указан в кортеже: (CounterID, Date), + * или как один столбец: CounterID. + */ +static ASTPtr extractPrimaryKey(const ASTPtr & node, const std::string & storage_name) +{ + const ASTFunction * primary_expr_func = dynamic_cast(&*node); + + if (primary_expr_func && primary_expr_func->name == "tuple") + { + /// Первичный ключ указан в кортеже. + return primary_expr_func->children.at(0); + } + else + { + /// Первичный ключ состоит из одного столбца. + ASTExpressionList * res = new ASTExpressionList; + ASTPtr res_ptr = res; + res->children.push_back(node); + return res_ptr; + } +} + + StoragePtr StorageFactory::get( const String & name, const String & data_path, @@ -182,17 +207,12 @@ StoragePtr StorageFactory::get( String date_column_name = dynamic_cast(*args[0]).name; ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1]; UInt64 index_granularity = safeGet(dynamic_cast(*args[arg_offset + 2]).value); - ASTFunction & primary_expr_func = dynamic_cast(*args[arg_offset + 1]); - - if (primary_expr_func.name != "tuple") - throw Exception("Primary expression for storage " + name + " must be in parentheses.", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - ASTPtr primary_expr = primary_expr_func.children.at(0); + ASTPtr primary_expr_list = extractPrimaryKey(args[arg_offset + 1], name); return StorageMergeTree::create( - data_path, table_name, columns, context, primary_expr, date_column_name, sampling_expression, index_granularity, - name == "SummingMergeTree" ? StorageMergeTree::Summing : StorageMergeTree::Ordinary); + data_path, table_name, columns, context, primary_expr_list, date_column_name, sampling_expression, index_granularity, + name == "SummingMergeTree" ? MergeTreeData::Summing : MergeTreeData::Ordinary); } else if (name == "CollapsingMergeTree") { @@ -224,17 +244,12 @@ StoragePtr StorageFactory::get( ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1]; UInt64 index_granularity = safeGet(dynamic_cast(*args[arg_offset + 2]).value); String sign_column_name = dynamic_cast(*args[arg_offset + 3]).name; - ASTFunction & primary_expr_func = dynamic_cast(*args[arg_offset + 1]); - if (primary_expr_func.name != "tuple") - throw Exception("Primary expression for storage CollapsingMergeTree must be in parentheses.", - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - - ASTPtr primary_expr = primary_expr_func.children.at(0); + ASTPtr primary_expr_list = extractPrimaryKey(args[arg_offset + 1], name); return StorageMergeTree::create( - data_path, table_name, columns, context, primary_expr, date_column_name, - sampling_expression, index_granularity, StorageMergeTree::Collapsing, sign_column_name); + data_path, table_name, columns, context, primary_expr_list, date_column_name, + sampling_expression, index_granularity, MergeTreeData::Collapsing, sign_column_name); } else if (name == "SystemNumbers") { diff --git a/dbms/src/Storages/StorageLog.cpp b/dbms/src/Storages/StorageLog.cpp index d9c5a19dbf1..24ea5ce5b2b 100644 --- a/dbms/src/Storages/StorageLog.cpp +++ b/dbms/src/Storages/StorageLog.cpp @@ -31,12 +31,26 @@ namespace DB using Poco::SharedPtr; -LogBlockInputStream::LogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage, size_t mark_number_, size_t rows_limit_) - : IProfilingBlockInputStream(owned_storage), block_size(block_size_), column_names(column_names_), storage(dynamic_cast(*owned_storage)), mark_number(mark_number_), rows_limit(rows_limit_), rows_read(0), current_mark(mark_number_) +LogBlockInputStream::LogBlockInputStream(size_t block_size_, const Names & column_names_, StorageLog & storage_, size_t mark_number_, size_t rows_limit_) + : block_size(block_size_), column_names(column_names_), storage(storage_), + mark_number(mark_number_), rows_limit(rows_limit_), rows_read(0), current_mark(mark_number_) { } +String LogBlockInputStream::getID() const +{ + std::stringstream res; + res << "Log(" << storage.getTableName() << ", " << &storage << ", " << mark_number << ", " << rows_limit; + + for (size_t i = 0; i < column_names.size(); ++i) + res << ", " << column_names[i]; + + res << ")"; + return res.str(); +} + + Block LogBlockInputStream::readImpl() { Block res; @@ -232,8 +246,8 @@ void LogBlockInputStream::readData(const String & name, const IDataType & type, } -LogBlockOutputStream::LogBlockOutputStream(StoragePtr owned_storage) - : IBlockOutputStream(owned_storage), storage(dynamic_cast(*owned_storage)), +LogBlockOutputStream::LogBlockOutputStream(StorageLog & storage_) + : storage(storage_), lock(storage.rwlock), marks_stream(storage.marks_file.path(), 4096, O_APPEND | O_CREAT | O_WRONLY) { for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it) @@ -599,7 +613,7 @@ BlockInputStreams StorageLog::read( res.push_back(new LogBlockInputStream( max_block_size, column_names, - thisPtr(), + *this, 0, std::numeric_limits::max())); } else @@ -621,7 +635,7 @@ BlockInputStreams StorageLog::read( res.push_back(new LogBlockInputStream( max_block_size, column_names, - thisPtr(), + *this, from_mark + thread * (to_mark - from_mark) / threads, marks[from_mark + (thread + 1) * (to_mark - from_mark) / threads - 1].rows - ((thread == 0 && from_mark == 0) @@ -650,7 +664,7 @@ BlockOutputStreamPtr StorageLog::write( ASTPtr query) { loadMarks(); - return new LogBlockOutputStream(thisPtr()); + return new LogBlockOutputStream(*this); } diff --git a/dbms/src/Storages/StorageMaterializedView.cpp b/dbms/src/Storages/StorageMaterializedView.cpp index 560df682415..5d704018b16 100644 --- a/dbms/src/Storages/StorageMaterializedView.cpp +++ b/dbms/src/Storages/StorageMaterializedView.cpp @@ -74,7 +74,8 @@ BlockOutputStreamPtr StorageMaterializedView::write(ASTPtr query) return data->write(query); } -void StorageMaterializedView::dropImpl() { +void StorageMaterializedView::drop() +{ context.getGlobalContext().removeDependency(DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(database_name, table_name)); /// Состваляем и выполняем запрос drop для внутреннего хранилища. diff --git a/dbms/src/Storages/StorageMemory.cpp b/dbms/src/Storages/StorageMemory.cpp index f63ee7b0f7d..e790106ddc7 100644 --- a/dbms/src/Storages/StorageMemory.cpp +++ b/dbms/src/Storages/StorageMemory.cpp @@ -12,8 +12,8 @@ namespace DB using Poco::SharedPtr; -MemoryBlockInputStream::MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_, StoragePtr owned_storage) - : IProfilingBlockInputStream(owned_storage), column_names(column_names_), begin(begin_), end(end_), it(begin) +MemoryBlockInputStream::MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_) + : column_names(column_names_), begin(begin_), end(end_), it(begin) { } @@ -39,8 +39,8 @@ Block MemoryBlockInputStream::readImpl() } -MemoryBlockOutputStream::MemoryBlockOutputStream(StoragePtr owned_storage) - : IBlockOutputStream(owned_storage), storage(dynamic_cast(*owned_storage)) +MemoryBlockOutputStream::MemoryBlockOutputStream(StorageMemory & storage_) + : storage(storage_) { } @@ -92,7 +92,7 @@ BlockInputStreams StorageMemory::read( std::advance(begin, thread * size / threads); std::advance(end, (thread + 1) * size / threads); - res.push_back(new MemoryBlockInputStream(column_names, begin, end, thisPtr())); + res.push_back(new MemoryBlockInputStream(column_names, begin, end)); } return res; @@ -102,11 +102,11 @@ BlockInputStreams StorageMemory::read( BlockOutputStreamPtr StorageMemory::write( ASTPtr query) { - return new MemoryBlockOutputStream(thisPtr()); + return new MemoryBlockOutputStream(*this); } -void StorageMemory::dropImpl() +void StorageMemory::drop() { Poco::ScopedLock lock(mutex); data.clear(); diff --git a/dbms/src/Storages/StorageMerge.cpp b/dbms/src/Storages/StorageMerge.cpp index 204ff60b203..d6c984cd18e 100644 --- a/dbms/src/Storages/StorageMerge.cpp +++ b/dbms/src/Storages/StorageMerge.cpp @@ -55,7 +55,7 @@ BlockInputStreams StorageMerge::read( else virt_column_names.push_back(it); - SelectedTables selected_tables; + StorageVector selected_tables; /// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную. processed_stage = QueryProcessingStage::Complete; @@ -72,6 +72,14 @@ BlockInputStreams StorageMerge::read( getSelectedTables(selected_tables); } + TableLocks table_locks; + + /// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем. + for (auto table : selected_tables) + { + table_locks.push_back(table->lockStructure(false)); + } + Block virtual_columns_block = getBlockWithVirtualColumns(selected_tables); BlockInputStreamPtr virtual_columns; @@ -81,23 +89,26 @@ BlockInputStreams StorageMerge::read( else /// Иначе, считаем допустимыми все возможные значения virtual_columns = new OneBlockInputStream(virtual_columns_block); - std::set values = VirtualColumnUtils::extractSingleValueFromBlocks(virtual_columns, _table_column_name); + std::multiset values = VirtualColumnUtils::extractSingleValueFromBlocks(virtual_columns, _table_column_name); bool all_inclusive = (values.size() == virtual_columns_block.rows()); - for (SelectedTables::iterator it = selected_tables.begin(); it != selected_tables.end(); ++it) + for (size_t i = 0; i < selected_tables.size(); ++i) { - if (!all_inclusive && values.find((*it)->getTableName()) == values.end()) + StoragePtr table = selected_tables[i]; + auto table_lock = table_locks[i]; + + if (!all_inclusive && values.find(table->getTableName()) == values.end()) continue; /// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой. if (real_column_names.size() == 0) - real_column_names.push_back(ExpressionActions::getSmallestColumn((*it)->getColumnsList())); + real_column_names.push_back(ExpressionActions::getSmallestColumn(table->getColumnsList())); /// Подменяем виртуальный столбец на его значение ASTPtr modified_query_ast = query->clone(); - VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, (*it)->getTableName()); + VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, table->getTableName()); - BlockInputStreams source_streams = (*it)->read( + BlockInputStreams source_streams = table->read( real_column_names, modified_query_ast, settings, @@ -105,17 +116,21 @@ BlockInputStreams StorageMerge::read( max_block_size, selected_tables.size() > threads ? 1 : (threads / selected_tables.size())); + for (auto & stream : source_streams) + { + stream->addTableLock(table_lock); + } + for (auto & virtual_column : virt_column_names) { if (virtual_column == _table_column_name) { for (auto & stream : source_streams) - stream = new AddingConstColumnBlockInputStream(stream, new DataTypeString, (*it)->getTableName(), _table_column_name); + stream = new AddingConstColumnBlockInputStream(stream, new DataTypeString, table->getTableName(), _table_column_name); } } - for (BlockInputStreams::iterator jt = source_streams.begin(); jt != source_streams.end(); ++jt) - res.push_back(*jt); + res.insert(res.end(), source_streams.begin(), source_streams.end()); if (tmp_processed_stage < processed_stage) processed_stage = tmp_processed_stage; @@ -135,7 +150,7 @@ Block StorageMerge::getBlockWithVirtualColumns(const std::vector & s Block res; ColumnWithNameAndType _table(new ColumnString, new DataTypeString, _table_column_name); - for (SelectedTables::const_iterator it = selected_tables.begin(); it != selected_tables.end(); ++it) + for (StorageVector::const_iterator it = selected_tables.begin(); it != selected_tables.end(); ++it) _table.column->insert((*it)->getTableName()); res.insert(_table); @@ -146,7 +161,7 @@ void StorageMerge::getSelectedTables(StorageVector & selected_tables) { const Tables & tables = context.getDatabases().at(source_database); for (Tables::const_iterator it = tables.begin(); it != tables.end(); ++it) - if (it->second != this && table_name_regexp.match(it->first)) + if (it->second.get() != this && table_name_regexp.match(it->first)) selected_tables.push_back(it->second); } diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index 537a9cf0f89..5e2b2dfd549 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -1,138 +1,55 @@ -#include -#include -#include - -#include -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - #include -#include #include -#include -#include - -#include - +#include +#include namespace DB { -size_t StorageMergeTree::total_size_of_currently_merging_parts = 0; -StorageMergeTree::StorageMergeTree( - const String & path_, const String & name_, NamesAndTypesListPtr columns_, - const Context & context_, - ASTPtr & primary_expr_ast_, - const String & date_column_name_, const ASTPtr & sampling_expression_, - size_t index_granularity_, - Mode mode_, - const String & sign_column_, - const StorageMergeTreeSettings & settings_) - : path(path_), name(name_), full_path(path + escapeForFileName(name) + '/'), columns(columns_), - context(context_), primary_expr_ast(primary_expr_ast_->clone()), - date_column_name(date_column_name_), sampling_expression(sampling_expression_), - index_granularity(index_granularity_), - mode(mode_), sign_column(sign_column_), - settings(settings_), - increment(full_path + "increment.txt"), log(&Logger::get("StorageMergeTree: " + name)), shutdown_called(false), - file_name_regexp("^(\\d{8})_(\\d{8})_(\\d+)_(\\d+)_(\\d+)") +StorageMergeTree::StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_, + const Context & context_, + ASTPtr & primary_expr_ast_, + const String & date_column_name_, + const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается. + size_t index_granularity_, + MergeTreeData::Mode mode_, + const String & sign_column_, + const MergeTreeSettings & settings_) + : path(path_), name(name_), full_path(path + escapeForFileName(name) + '/'), increment(full_path + "increment.txt"), + data( full_path, columns_, context_, primary_expr_ast_, date_column_name_, sampling_expression_, + index_granularity_,mode_, sign_column_, settings_), + reader(data), writer(data), merger(data), + log(&Logger::get("StorageMergeTree")), + shutdown_called(false) { - min_marks_for_seek = (settings.min_rows_for_seek + index_granularity - 1) / index_granularity; - min_marks_for_concurrent_read = (settings.min_rows_for_concurrent_read + index_granularity - 1) / index_granularity; - max_marks_to_use_cache = (settings.max_rows_to_use_cache + index_granularity - 1) / index_granularity; + merge_threads = new boost::threadpool::pool(data.settings.merging_threads); - /// создаём директорию, если её нет - Poco::File(full_path).createDirectories(); - - /// инициализируем описание сортировки - sort_descr.reserve(primary_expr_ast->children.size()); - for (ASTs::iterator it = primary_expr_ast->children.begin(); - it != primary_expr_ast->children.end(); - ++it) - { - String name = (*it)->getColumnName(); - sort_descr.push_back(SortColumnDescription(name, 1)); - } - - primary_expr = ExpressionAnalyzer(primary_expr_ast, context, *columns).getActions(false); - - ExpressionActionsPtr projected_expr = ExpressionAnalyzer(primary_expr_ast, context, *columns).getActions(true); - primary_key_sample = projected_expr->getSampleBlock(); - - merge_threads = new boost::threadpool::pool(settings.merging_threads); - - loadDataParts(); - clearOldParts(); - - UInt64 max_part_id = 0; - for (DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it) - { - max_part_id = std::max(max_part_id, (*it)->right); - } - increment.fixIfBroken(max_part_id); + increment.fixIfBroken(data.getMaxDataPartIndex()); } StoragePtr StorageMergeTree::create( const String & path_, const String & name_, NamesAndTypesListPtr columns_, const Context & context_, ASTPtr & primary_expr_ast_, - const String & date_column_name_, const ASTPtr & sampling_expression_, + const String & date_column_name_, + const ASTPtr & sampling_expression_, size_t index_granularity_, - Mode mode_, + MergeTreeData::Mode mode_, const String & sign_column_, - const StorageMergeTreeSettings & settings_) + const MergeTreeSettings & settings_) { return (new StorageMergeTree( path_, name_, columns_, context_, primary_expr_ast_, date_column_name_, sampling_expression_, index_granularity_, mode_, sign_column_, settings_))->thisPtr(); } - void StorageMergeTree::shutdown() { if (shutdown_called) return; shutdown_called = true; + merger.cancelAll(); joinMergeThreads(); } @@ -143,633 +60,69 @@ StorageMergeTree::~StorageMergeTree() shutdown(); } - -BlockOutputStreamPtr StorageMergeTree::write(ASTPtr query) -{ - return new MergeTreeBlockOutputStream(thisPtr()); -} - - BlockInputStreams StorageMergeTree::read( - const Names & column_names_to_return, + const Names & column_names, ASTPtr query, const Settings & settings, QueryProcessingStage::Enum & processed_stage, size_t max_block_size, unsigned threads) { - Poco::ScopedReadRWLock lock(read_lock); - - check(column_names_to_return); - processed_stage = QueryProcessingStage::FetchColumns; - - PKCondition key_condition(query, context, *columns, sort_descr); - PKCondition date_condition(query, context, *columns, SortDescription(1, SortColumnDescription(date_column_name, 1))); - - typedef std::vector PartsList; - PartsList parts; - - /// Выберем куски, в которых могут быть данные, удовлетворяющие date_condition. - { - Poco::ScopedLock lock(data_parts_mutex); - - for (DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it) - { - Field left = static_cast((*it)->left_date); - Field right = static_cast((*it)->right_date); - - if (date_condition.mayBeTrueInRange(&left, &right)) - parts.push_back(*it); - } - } - - /// Семплирование. - Names column_names_to_read = column_names_to_return; - UInt64 sampling_column_value_limit = 0; - typedef Poco::SharedPtr ASTFunctionPtr; - ASTFunctionPtr filter_function; - ExpressionActionsPtr filter_expression; - - ASTSelectQuery & select = *dynamic_cast(&*query); - if (select.sample_size) - { - double size = apply_visitor(FieldVisitorConvertToNumber(), - dynamic_cast(*select.sample_size).value); - - if (size < 0) - throw Exception("Negative sample size", ErrorCodes::ARGUMENT_OUT_OF_BOUND); - - if (size > 1) - { - size_t requested_count = apply_visitor(FieldVisitorConvertToNumber(), dynamic_cast(*select.sample_size).value); - - /// Узнаем, сколько строк мы бы прочли без семплирования. - LOG_DEBUG(log, "Preliminary index scan with condition: " << key_condition.toString()); - size_t total_count = 0; - for (size_t i = 0; i < parts.size(); ++i) - { - DataPartPtr & part = parts[i]; - MarkRanges ranges = MergeTreeBlockInputStream::markRangesFromPkRange(part->index, *this, key_condition); - - for (size_t j = 0; j < ranges.size(); ++j) - total_count += ranges[j].end - ranges[j].begin; - } - total_count *= index_granularity; - - size = std::min(1., static_cast(requested_count) / total_count); - - LOG_DEBUG(log, "Selected relative sample size: " << size); - } - - UInt64 sampling_column_max = 0; - DataTypePtr type = primary_expr->getSampleBlock().getByName(sampling_expression->getColumnName()).type; - - if (type->getName() == "UInt64") - sampling_column_max = std::numeric_limits::max(); - else if (type->getName() == "UInt32") - sampling_column_max = std::numeric_limits::max(); - else if (type->getName() == "UInt16") - sampling_column_max = std::numeric_limits::max(); - else if (type->getName() == "UInt8") - sampling_column_max = std::numeric_limits::max(); - else - throw Exception("Invalid sampling column type in storage parameters: " + type->getName() + ". Must be unsigned integer type.", ErrorCodes::ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER); - - /// Добавим условие, чтобы отсечь еще что-нибудь при повторном просмотре индекса. - sampling_column_value_limit = static_cast(size * sampling_column_max); - if (!key_condition.addCondition(sampling_expression->getColumnName(), - Range::createRightBounded(sampling_column_value_limit, true))) - throw Exception("Sampling column not in primary key", ErrorCodes::ILLEGAL_COLUMN); - - /// Выражение для фильтрации: sampling_expression <= sampling_column_value_limit - - ASTPtr filter_function_args = new ASTExpressionList; - filter_function_args->children.push_back(sampling_expression); - filter_function_args->children.push_back(new ASTLiteral(StringRange(), sampling_column_value_limit)); - - filter_function = new ASTFunction; - filter_function->name = "lessOrEquals"; - filter_function->arguments = filter_function_args; - filter_function->children.push_back(filter_function->arguments); - - filter_expression = ExpressionAnalyzer(filter_function, context, *columns).getActions(false); - - /// Добавим столбцы, нужные для sampling_expression. - std::vector add_columns = filter_expression->getRequiredColumns(); - column_names_to_read.insert(column_names_to_read.end(), add_columns.begin(), add_columns.end()); - std::sort(column_names_to_read.begin(), column_names_to_read.end()); - column_names_to_read.erase(std::unique(column_names_to_read.begin(), column_names_to_read.end()), column_names_to_read.end()); - } - - LOG_DEBUG(log, "Key condition: " << key_condition.toString()); - LOG_DEBUG(log, "Date condition: " << date_condition.toString()); - - /// PREWHERE - ExpressionActionsPtr prewhere_actions; - String prewhere_column; - if (select.prewhere_expression) - { - ExpressionAnalyzer analyzer(select.prewhere_expression, context, *columns); - prewhere_actions = analyzer.getActions(false); - prewhere_column = select.prewhere_expression->getColumnName(); - } - - RangesInDataParts parts_with_ranges; - - /// Найдем, какой диапазон читать из каждого куска. - size_t sum_marks = 0; - size_t sum_ranges = 0; - for (size_t i = 0; i < parts.size(); ++i) - { - DataPartPtr & part = parts[i]; - RangesInDataPart ranges(part); - ranges.ranges = MergeTreeBlockInputStream::markRangesFromPkRange(part->index, *this, key_condition); - - if (!ranges.ranges.empty()) - { - parts_with_ranges.push_back(ranges); - - sum_ranges += ranges.ranges.size(); - for (size_t j = 0; j < ranges.ranges.size(); ++j) - { - sum_marks += ranges.ranges[j].end - ranges.ranges[j].begin; - } - } - } - - LOG_DEBUG(log, "Selected " << parts.size() << " parts by date, " << parts_with_ranges.size() << " parts by key, " - << sum_marks << " marks to read from " << sum_ranges << " ranges"); - - BlockInputStreams res; - - if (select.final) - { - /// Добавим столбцы, нужные для вычисления первичного ключа и знака. - std::vector add_columns = primary_expr->getRequiredColumns(); - column_names_to_read.insert(column_names_to_read.end(), add_columns.begin(), add_columns.end()); - column_names_to_read.push_back(sign_column); - std::sort(column_names_to_read.begin(), column_names_to_read.end()); - column_names_to_read.erase(std::unique(column_names_to_read.begin(), column_names_to_read.end()), column_names_to_read.end()); - - res = spreadMarkRangesAmongThreadsFinal( - parts_with_ranges, - threads, - column_names_to_read, - max_block_size, - settings.use_uncompressed_cache, - prewhere_actions, - prewhere_column); - } - else - { - res = spreadMarkRangesAmongThreads( - parts_with_ranges, - threads, - column_names_to_read, - max_block_size, - settings.use_uncompressed_cache, - prewhere_actions, - prewhere_column); - } - - if (select.sample_size) - { - for (size_t i = 0; i < res.size(); ++i) - { - BlockInputStreamPtr original_stream = res[i]; - BlockInputStreamPtr expression_stream = new ExpressionBlockInputStream(original_stream, filter_expression); - BlockInputStreamPtr filter_stream = new FilterBlockInputStream(expression_stream, filter_function->getColumnName()); - res[i] = filter_stream; - } - } - - return res; + return reader.read(column_names, query, settings, processed_stage, max_block_size, threads); } - -/// Примерно поровну распределить засечки между потоками. -BlockInputStreams StorageMergeTree::spreadMarkRangesAmongThreads( - RangesInDataParts parts, - size_t threads, - const Names & column_names, - size_t max_block_size, - bool use_uncompressed_cache, - ExpressionActionsPtr prewhere_actions, - const String & prewhere_column) +BlockOutputStreamPtr StorageMergeTree::write(ASTPtr query) { - /// На всякий случай перемешаем куски. - std::random_shuffle(parts.begin(), parts.end()); - - /// Посчитаем засечки для каждого куска. - std::vector sum_marks_in_parts(parts.size()); - size_t sum_marks = 0; - for (size_t i = 0; i < parts.size(); ++i) - { - /// Пусть отрезки будут перечислены справа налево, чтобы можно было выбрасывать самый левый отрезок с помощью pop_back(). - std::reverse(parts[i].ranges.begin(), parts[i].ranges.end()); - - sum_marks_in_parts[i] = 0; - for (size_t j = 0; j < parts[i].ranges.size(); ++j) - { - MarkRange & range = parts[i].ranges[j]; - sum_marks_in_parts[i] += range.end - range.begin; - } - sum_marks += sum_marks_in_parts[i]; - } - - if (sum_marks > max_marks_to_use_cache) - use_uncompressed_cache = false; - - BlockInputStreams res; - - if (sum_marks > 0) - { - size_t min_marks_per_thread = (sum_marks - 1) / threads + 1; - - for (size_t i = 0; i < threads && !parts.empty(); ++i) - { - size_t need_marks = min_marks_per_thread; - BlockInputStreams streams; - - /// Цикл по кускам. - while (need_marks > 0 && !parts.empty()) - { - RangesInDataPart & part = parts.back(); - size_t & marks_in_part = sum_marks_in_parts.back(); - - /// Не будем брать из куска слишком мало строк. - if (marks_in_part >= min_marks_for_concurrent_read && - need_marks < min_marks_for_concurrent_read) - need_marks = min_marks_for_concurrent_read; - - /// Не будем оставлять в куске слишком мало строк. - if (marks_in_part > need_marks && - marks_in_part - need_marks < min_marks_for_concurrent_read) - need_marks = marks_in_part; - - /// Возьмем весь кусок, если он достаточно мал. - if (marks_in_part <= need_marks) - { - /// Восстановим порядок отрезков. - std::reverse(part.ranges.begin(), part.ranges.end()); - - streams.push_back(new MergeTreeBlockInputStream( - full_path + part.data_part->name + '/', max_block_size, column_names, *this, - part.data_part, part.ranges, thisPtr(), use_uncompressed_cache, - prewhere_actions, prewhere_column)); - need_marks -= marks_in_part; - parts.pop_back(); - sum_marks_in_parts.pop_back(); - continue; - } - - MarkRanges ranges_to_get_from_part; - - /// Цикл по отрезкам куска. - while (need_marks > 0) - { - if (part.ranges.empty()) - throw Exception("Unexpected end of ranges while spreading marks among threads", ErrorCodes::LOGICAL_ERROR); - - MarkRange & range = part.ranges.back(); - size_t marks_in_range = range.end - range.begin; - - size_t marks_to_get_from_range = std::min(marks_in_range, need_marks); - ranges_to_get_from_part.push_back(MarkRange(range.begin, range.begin + marks_to_get_from_range)); - range.begin += marks_to_get_from_range; - marks_in_part -= marks_to_get_from_range; - need_marks -= marks_to_get_from_range; - if (range.begin == range.end) - part.ranges.pop_back(); - } - - streams.push_back(new MergeTreeBlockInputStream( - full_path + part.data_part->name + '/', max_block_size, column_names, *this, - part.data_part, ranges_to_get_from_part, thisPtr(), use_uncompressed_cache, - prewhere_actions, prewhere_column)); - } - - if (streams.size() == 1) - res.push_back(streams[0]); - else - res.push_back(new ConcatBlockInputStream(streams)); - } - - if (!parts.empty()) - throw Exception("Couldn't spread marks among threads", ErrorCodes::LOGICAL_ERROR); - } - - return res; + return new MergeTreeBlockOutputStream(*this); } - -/// Распределить засечки между потоками и сделать, чтобы в ответе (почти) все данные были сколлапсированы (модификатор FINAL). -BlockInputStreams StorageMergeTree::spreadMarkRangesAmongThreadsFinal( - RangesInDataParts parts, - size_t threads, - const Names & column_names, - size_t max_block_size, - bool use_uncompressed_cache, - ExpressionActionsPtr prewhere_actions, - const String & prewhere_column) +void StorageMergeTree::drop() { - size_t sum_marks = 0; - for (size_t i = 0; i < parts.size(); ++i) - for (size_t j = 0; j < parts[i].ranges.size(); ++j) - sum_marks += parts[i].ranges[j].end - parts[i].ranges[j].begin; - - if (sum_marks > max_marks_to_use_cache) - use_uncompressed_cache = false; - - ExpressionActionsPtr sign_filter_expression; - String sign_filter_column; - createPositiveSignCondition(sign_filter_expression, sign_filter_column); - - BlockInputStreams to_collapse; - - for (size_t part_index = 0; part_index < parts.size(); ++part_index) - { - RangesInDataPart & part = parts[part_index]; - - BlockInputStreamPtr source_stream = new MergeTreeBlockInputStream( - full_path + part.data_part->name + '/', max_block_size, column_names, *this, - part.data_part, part.ranges, thisPtr(), use_uncompressed_cache, - prewhere_actions, prewhere_column); - - to_collapse.push_back(new ExpressionBlockInputStream(source_stream, primary_expr)); - } - - BlockInputStreams res; - if (to_collapse.size() == 1) - res.push_back(new FilterBlockInputStream(new ExpressionBlockInputStream(to_collapse[0], sign_filter_expression), sign_filter_column)); - else if (to_collapse.size() > 1) - res.push_back(new CollapsingFinalBlockInputStream(to_collapse, sort_descr, sign_column)); - - return res; + merger.cancelAll(); + joinMergeThreads(); + data.dropAllData(); } - -void StorageMergeTree::createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column) +void StorageMergeTree::rename(const String & new_path_to_db, const String & new_name) { - ASTFunction * function = new ASTFunction; - ASTPtr function_ptr = function; - - ASTExpressionList * arguments = new ASTExpressionList; - ASTPtr arguments_ptr = arguments; - - ASTIdentifier * sign = new ASTIdentifier; - ASTPtr sign_ptr = sign; - - ASTLiteral * one = new ASTLiteral; - ASTPtr one_ptr = one; - - function->name = "equals"; - function->arguments = arguments_ptr; - function->children.push_back(arguments_ptr); - - arguments->children.push_back(sign_ptr); - arguments->children.push_back(one_ptr); - - sign->name = sign_column; - sign->kind = ASTIdentifier::Column; - - one->type = new DataTypeInt8; - one->value = Field(static_cast(1)); - - out_expression = ExpressionAnalyzer(function_ptr, context, *columns).getActions(false); - out_column = function->getColumnName(); + std::string new_full_path = new_path_to_db + escapeForFileName(new_name) + '/'; + + data.setPath(new_full_path); + + path = new_path_to_db; + name = new_name; + full_path = new_full_path; + + increment.setPath(full_path + "increment.txt"); } - -String StorageMergeTree::getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level) +void StorageMergeTree::alter(const ASTAlterQuery::Parameters & params) { - DateLUTSingleton & date_lut = DateLUTSingleton::instance(); - - /// Имя директории для куска иммет вид: YYYYMMDD_YYYYMMDD_N_N_L. - String res; - { - unsigned left_date_id = Date2OrderedIdentifier(date_lut.fromDayNum(left_date)); - unsigned right_date_id = Date2OrderedIdentifier(date_lut.fromDayNum(right_date)); - - WriteBufferFromString wb(res); - - writeIntText(left_date_id, wb); - writeChar('_', wb); - writeIntText(right_date_id, wb); - writeChar('_', wb); - writeIntText(left_id, wb); - writeChar('_', wb); - writeIntText(right_id, wb); - writeChar('_', wb); - writeIntText(level, wb); - } - - return res; + data.alter(params); } - -void StorageMergeTree::parsePartName(const String & file_name, const Poco::RegularExpression::MatchVec & matches, DataPart & part) +void StorageMergeTree::prepareAlterModify(const ASTAlterQuery::Parameters & params) { - DateLUTSingleton & date_lut = DateLUTSingleton::instance(); - - part.left_date = date_lut.toDayNum(OrderedIdentifier2Date(file_name.substr(matches[1].offset, matches[1].length))); - part.right_date = date_lut.toDayNum(OrderedIdentifier2Date(file_name.substr(matches[2].offset, matches[2].length))); - part.left = parse(file_name.substr(matches[3].offset, matches[3].length)); - part.right = parse(file_name.substr(matches[4].offset, matches[4].length)); - part.level = parse(file_name.substr(matches[5].offset, matches[5].length)); - - part.left_month = date_lut.toFirstDayNumOfMonth(part.left_date); - part.right_month = date_lut.toFirstDayNumOfMonth(part.right_date); + data.prepareAlterModify(params); } - -void StorageMergeTree::loadDataParts() +void StorageMergeTree::commitAlterModify(const ASTAlterQuery::Parameters & params) { - LOG_DEBUG(log, "Loading data parts"); - - Poco::ScopedLock lock(data_parts_mutex); - Poco::ScopedLock lock_all(all_data_parts_mutex); - - data_parts.clear(); - - Strings part_file_names; - Strings old_file_names; - Poco::DirectoryIterator end; - for (Poco::DirectoryIterator it(full_path); it != end; ++it) - { - String file_name = it.name(); - - /// Удаляем временные директории старше суток. - if (0 == file_name.compare(0, strlen("tmp_"), "tmp_")) - { - Poco::File tmp_dir(full_path + file_name); - - if (tmp_dir.isDirectory() && tmp_dir.getLastModified().epochTime() + 86400 < time(0)) - { - LOG_WARNING(log, "Removing temporary directory " << full_path << file_name); - Poco::File(full_path + file_name).remove(true); - } - - continue; - } - - if (0 == file_name.compare(0, strlen("old_"), "old_")) - old_file_names.push_back(file_name); - else - part_file_names.push_back(file_name); - } - - Poco::RegularExpression::MatchVec matches; - while (!part_file_names.empty()) - { - String file_name = part_file_names.back(); - part_file_names.pop_back(); - - if (!isPartDirectory(file_name, matches)) - continue; - - DataPartPtr part = new DataPart(*this); - parsePartName(file_name, matches, *part); - part->name = file_name; - - /// Для битых кусков, которые могут образовываться после грубого перезапуска сервера, попытаться восстановить куски, из которых они сделаны. - if (isBrokenPart(full_path + file_name)) - { - if (part->level == 0) - { - /// Восстановить куски нулевого уровня невозможно. - LOG_ERROR(log, "Removing broken part " << path + file_name << " because is't impossible to repair."); - part->remove(); - } - else - { - Strings new_parts = tryRestorePart(full_path, file_name, old_file_names); - part_file_names.insert(part_file_names.begin(), new_parts.begin(), new_parts.end()); - } - - continue; - } - - /// Размер - в количестве засечек. - part->size = Poco::File(full_path + file_name + "/" + escapeForFileName(columns->front().first) + ".mrk").getSize() - / MERGE_TREE_MARK_SIZE; - - part->modification_time = Poco::File(full_path + file_name).getLastModified().epochTime(); - - try - { - part->loadIndex(); - } - catch (...) - { - /// Не будем вставлять в набор кусок с битым индексом. Пропустим кусок и позволим серверу запуститься. - tryLogCurrentException(__PRETTY_FUNCTION__); - continue; - } - - data_parts.insert(part); - } - - all_data_parts = data_parts; - - /** Удаляем из набора актуальных кусков куски, которые содержатся в другом куске (которые были склеены), - * но по каким-то причинам остались лежать в файловой системе. - * Удаление файлов будет произведено потом в методе clearOldParts. - */ - - if (data_parts.size() >= 2) - { - DataParts::iterator prev_jt = data_parts.begin(); - DataParts::iterator curr_jt = prev_jt; - ++curr_jt; - while (curr_jt != data_parts.end()) - { - /// Куски данных за разные месяцы рассматривать не будем - if ((*curr_jt)->left_month != (*curr_jt)->right_month - || (*curr_jt)->right_month != (*prev_jt)->left_month - || (*prev_jt)->left_month != (*prev_jt)->right_month) - { - ++prev_jt; - ++curr_jt; - continue; - } - - if ((*curr_jt)->contains(**prev_jt)) - { - LOG_WARNING(log, "Part " << (*curr_jt)->name << " contains " << (*prev_jt)->name); - data_parts.erase(prev_jt); - prev_jt = curr_jt; - ++curr_jt; - } - else if ((*prev_jt)->contains(**curr_jt)) - { - LOG_WARNING(log, "Part " << (*prev_jt)->name << " contains " << (*curr_jt)->name); - data_parts.erase(curr_jt++); - } - else - { - ++prev_jt; - ++curr_jt; - } - } - } - - LOG_DEBUG(log, "Loaded data parts (" << data_parts.size() << " items)"); + data.commitAlterModify(params); } - -void StorageMergeTree::clearOldParts() -{ - Poco::ScopedTry lock; - - /// Если метод уже вызван из другого потока (или если all_data_parts прямо сейчас меняют), то можно ничего не делать. - if (!lock.lock(&all_data_parts_mutex)) - { - LOG_TRACE(log, "Already clearing or modifying old parts"); - return; - } - - LOG_TRACE(log, "Clearing old parts"); - for (DataParts::iterator it = all_data_parts.begin(); it != all_data_parts.end();) - { - int ref_count = it->referenceCount(); - if (ref_count == 1) /// После этого ref_count не может увеличиться. - { - LOG_DEBUG(log, "'Removing' part " << (*it)->name << " (prepending old_ to its name)"); - - (*it)->renameToOld(); - all_data_parts.erase(it++); - } - else - ++it; - } - - /// Удалим старые old_ куски. - Poco::DirectoryIterator end; - for (Poco::DirectoryIterator it(full_path); it != end; ++it) - { - if (0 != it.name().compare(0, strlen("old_"), "old_")) - continue; - if (it->isDirectory() && it->getLastModified().epochTime() + settings.old_parts_lifetime < time(0)) - { - it->remove(true); - } - } -} - - void StorageMergeTree::merge(size_t iterations, bool async, bool aggressive) { bool while_can = false; if (iterations == 0) { while_can = true; - iterations = settings.merging_threads; + iterations = data.settings.merging_threads; } - + for (size_t i = 0; i < iterations; ++i) merge_threads->schedule(boost::bind(&StorageMergeTree::mergeThread, this, while_can, aggressive)); - + if (!async) joinMergeThreads(); } @@ -782,25 +135,51 @@ void StorageMergeTree::mergeThread(bool while_can, bool aggressive) while (!shutdown_called) { /// Удаляем старые куски. На случай, если в слиянии что-то сломано, и из следующего блока вылетит исключение. - clearOldParts(); + data.clearOldParts(); + + size_t disk_space = DiskSpaceMonitor::getUnreservedFreeSpace(full_path); { - Poco::ScopedReadRWLock lock(merge_lock); - /// К концу этого логического блока должен быть вызван деструктор, чтобы затем корректно определить удаленные куски - Poco::SharedPtr what; + /// Нужно вызывать деструктор под незалоченным currently_merging_mutex. + CurrentlyMergingPartsTaggerPtr merging_tagger; - if (!selectPartsToMerge(what, false, aggressive) && !selectPartsToMerge(what, true, aggressive)) - break; + { + Poco::ScopedLock lock(currently_merging_mutex); - mergeParts(what); + MergeTreeData::DataPartsVector parts; + auto can_merge = boost::bind(&StorageMergeTree::canMergeParts, this, _1, _2); + bool only_small = false; + + /// Если есть активный мердж крупных кусков, то ограничиваемся мерджем только маленьких частей. + for (const auto & part : currently_merging) + { + if (part->size * data.index_granularity > 25 * 1024 * 1024) + { + only_small = true; + break; + } + } + + { + auto structure_lock = lockStructure(false); + if (!merger.selectPartsToMerge(parts, disk_space, false, aggressive, only_small, can_merge) && + !merger.selectPartsToMerge(parts, disk_space, true, aggressive, only_small, can_merge)) + break; + } + + merging_tagger = new CurrentlyMergingPartsTagger(parts, merger.estimateDiskSpaceForMerge(parts), *this); + } + + auto structure_lock = lockStructure(true); + merger.mergeParts(merging_tagger->parts); } if (shutdown_called) break; /// Удаляем куски, которые мы только что сливали. - clearOldParts(); + data.clearOldParts(); if (!while_can) break; @@ -834,651 +213,9 @@ void StorageMergeTree::joinMergeThreads() merge_threads->wait(); } - -/// Выбираем отрезок из не более чем max_parts_to_merge_at_once кусков так, чтобы максимальный размер был меньше чем в max_size_ratio_to_merge_parts раз больше суммы остальных. -/// Это обеспечивает в худшем случае время O(n log n) на все слияния, независимо от выбора сливаемых кусков, порядка слияния и добавления. -/// При max_parts_to_merge_at_once >= log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts), -/// несложно доказать, что всегда будет что сливать, пока количество кусков больше -/// log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts)*(количество кусков размером больше max_rows_to_merge_parts). -/// Дальше эвристики. -/// Будем выбирать максимальный по включению подходящий отрезок. -/// Из всех таких выбираем отрезок с минимальным максимумом размера. -/// Из всех таких выбираем отрезок с минимальным минимумом размера. -/// Из всех таких выбираем отрезок с максимальной длиной. -/// Дополнительно: -/// 1) с 1:00 до 5:00 ограничение сверху на размер куска в основном потоке увеличивается в несколько раз -/// 2) в зависимоти от возраста кусков меняется допустимая неравномерность при слиянии -/// 3) Молодые куски крупного размера (примерно больше 1 Гб) можно сливать не меньше чем по три -/// 4) Если в одном из потоков идет мердж крупных кусков, то во втором сливать только маленькие кусочки -/// 5) С ростом логарифма суммарного размера кусочков в мердже увеличиваем требование сбалансированности - -bool StorageMergeTree::selectPartsToMerge(Poco::SharedPtr & what, bool merge_anything_for_old_months, bool aggressive) +bool StorageMergeTree::canMergeParts(const MergeTreeData::DataPartPtr & left, const MergeTreeData::DataPartPtr & right) { - LOG_DEBUG(log, "Selecting parts to merge"); - - Poco::ScopedLock lock(data_parts_mutex); - - DateLUTSingleton & date_lut = DateLUTSingleton::instance(); - - size_t min_max = -1U; - size_t min_min = -1U; - int max_len = 0; - DataParts::iterator best_begin; - bool found = false; - - DayNum_t now_day = date_lut.toDayNum(time(0)); - DayNum_t now_month = date_lut.toFirstDayNumOfMonth(now_day); - int now_hour = date_lut.toHourInaccurate(time(0)); - - size_t maybe_used_bytes = total_size_of_currently_merging_parts; - size_t total_free_bytes = 0; - struct statvfs fs; - - /// Смотрим количество свободного места в файловой системе - if (statvfs(full_path.c_str(), &fs) != 0) - throwFromErrno("Could not calculate available disk space (statvfs)", ErrorCodes::CANNOT_STATVFS); - - total_free_bytes = fs.f_bfree * fs.f_bsize; - - /// Сколько кусков, начиная с текущего, можно включить в валидный отрезок, начинающийся левее текущего куска. - /// Нужно для определения максимальности по включению. - int max_count_from_left = 0; - - size_t cur_max_rows_to_merge_parts = settings.max_rows_to_merge_parts; - - /// Если ночь, можем мерджить сильно большие куски - if (now_hour >= 1 && now_hour <= 5) - cur_max_rows_to_merge_parts *= settings.merge_parts_at_night_inc; - - /// Если есть активный мердж крупных кусков, то ограничаемся мерджем только маленьких частей. - for (DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it) - { - if ((*it)->currently_merging && (*it)->size * index_granularity > 25 * 1024 * 1024) - { - cur_max_rows_to_merge_parts = settings.max_rows_to_merge_parts_second; - break; - } - } - - /// Левый конец отрезка. - for (DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it) - { - const DataPartPtr & first_part = *it; - - max_count_from_left = std::max(0, max_count_from_left - 1); - - /// Кусок не занят. - if (first_part->currently_merging) - continue; - - /// Кусок достаточно мал или слияние "агрессивное". - if (first_part->size * index_granularity > cur_max_rows_to_merge_parts - && !aggressive) - continue; - - /// Кусок в одном месяце. - if (first_part->left_month != first_part->right_month) - { - LOG_WARNING(log, "Part " << first_part->name << " spans more than one month"); - continue; - } - - /// Самый длинный валидный отрезок, начинающийся здесь. - size_t cur_longest_max = -1U; - size_t cur_longest_min = -1U; - int cur_longest_len = 0; - - /// Текущий отрезок, не обязательно валидный. - size_t cur_max = first_part->size; - size_t cur_min = first_part->size; - size_t cur_sum = first_part->size; - size_t cur_total_size = first_part->size_in_bytes; - int cur_len = 1; - - DayNum_t month = first_part->left_month; - UInt64 cur_id = first_part->right; - - /// Этот месяц кончился хотя бы день назад. - bool is_old_month = now_day - now_month >= 1 && now_month > month; - - time_t oldest_modification_time = first_part->modification_time; - - /// Правый конец отрезка. - DataParts::iterator jt = it; - for (++jt; jt != data_parts.end() && cur_len < static_cast(settings.max_parts_to_merge_at_once); ++jt) - { - const DataPartPtr & last_part = *jt; - - /// Кусок не занят и в одном правильном месяце. - if (last_part->currently_merging || - last_part->left_month != last_part->right_month || - last_part->left_month != month) - break; - - /// Кусок достаточно мал или слияние "агрессивное". - if (last_part->size * index_granularity > cur_max_rows_to_merge_parts - && !aggressive) - break; - - /// Кусок правее предыдущего. - if (last_part->left < cur_id) - { - LOG_WARNING(log, "Part " << last_part->name << " intersects previous part"); - break; - } - - oldest_modification_time = std::max(oldest_modification_time, last_part->modification_time); - cur_max = std::max(cur_max, last_part->size); - cur_min = std::min(cur_min, last_part->size); - cur_sum += last_part->size; - cur_total_size += last_part->size_in_bytes; - ++cur_len; - cur_id = last_part->right; - - int min_len = 2; - int cur_age_in_sec = time(0) - oldest_modification_time; - - /// Если куски примерно больше 1 Gb и образовались меньше 6 часов назад, то мерджить не меньше чем по 3. - if (cur_max * index_granularity * 150 > 1024*1024*1024 && cur_age_in_sec < 6*3600) - min_len = 3; - - /// Равен 0.5 если возраст порядка 0, равен 5 если возраст около месяца. - double time_ratio_modifier = 0.5 + 9 * static_cast(cur_age_in_sec) / (3600*24*30 + cur_age_in_sec); - - /// Двоичный логарифм суммарного размера кусочков - double log_cur_sum = std::log(cur_sum * index_granularity) / std::log(2); - /// Равен ~2 если куски маленькие, уменьшается до 0.5 с увеличением суммарного размера до 2^25. - double size_ratio_modifier = std::max(0.25, 2 - 3 * (log_cur_sum) / (25 + log_cur_sum)); - - /// Объединяем все в одну константу - double ratio = std::max(0.5, time_ratio_modifier * size_ratio_modifier * settings.max_size_ratio_to_merge_parts); - - /// Если отрезок валидный, то он самый длинный валидный, начинающийся тут. - if (cur_len >= min_len - && (static_cast(cur_max) / (cur_sum - cur_max) < ratio - /// За старый месяц объединяем что угодно, если разрешено и если этому хотя бы 15 дней - || (is_old_month && merge_anything_for_old_months && cur_age_in_sec > 3600*24*15) - /// Если слияние "агрессивное", то сливаем что угодно - || aggressive)) - { - /// Достаточно места на диске, чтобы покрыть уже активные и новый мерджи с запасом в 50% - if (total_free_bytes > (maybe_used_bytes + cur_total_size) * 1.5) - { - cur_longest_max = cur_max; - cur_longest_min = cur_min; - cur_longest_len = cur_len; - } - else - LOG_WARNING(log, "Won't merge parts from " << first_part->name << " to " << last_part->name - << " because not enough free space: " << total_free_bytes << " free, " - << maybe_used_bytes << " already involved in merge, " - << cur_total_size << " required now (+50% on overhead)"); - } - } - - /// Это максимальный по включению валидный отрезок. - if (cur_longest_len > max_count_from_left) - { - max_count_from_left = cur_longest_len; - - if (!found - || std::make_pair(std::make_pair(cur_longest_max, cur_longest_min), -cur_longest_len) - < std::make_pair(std::make_pair(min_max, min_min), -max_len)) - { - found = true; - min_max = cur_longest_max; - min_min = cur_longest_min; - max_len = cur_longest_len; - best_begin = it; - } - } - } - - if (found) - { - std::vector parts; - - DataParts::iterator it = best_begin; - for (int i = 0; i < max_len; ++i) - { - parts.push_back(*it); - ++it; - } - what = new CurrentlyMergingPartsTagger(parts, data_parts_mutex); - - LOG_DEBUG(log, "Selected " << parts.size() << " parts from " << parts.front()->name << " to " << parts.back()->name); - } - else - { - LOG_DEBUG(log, "No parts to merge"); - } - - return found; -} - - -/// parts должны быть отсортированы. -void StorageMergeTree::mergeParts(Poco::SharedPtr & what) -{ - const std::vector & parts(what->parts); - - LOG_DEBUG(log, "Merging " << parts.size() << " parts: from " << parts.front()->name << " to " << parts.back()->name); - - Names all_column_names; - for (NamesAndTypesList::const_iterator it = columns->begin(); it != columns->end(); ++it) - all_column_names.push_back(it->first); - - DateLUTSingleton & date_lut = DateLUTSingleton::instance(); - - StorageMergeTree::DataPartPtr new_data_part = new DataPart(*this); - new_data_part->left_date = std::numeric_limits::max(); - new_data_part->right_date = std::numeric_limits::min(); - new_data_part->left = parts.front()->left; - new_data_part->right = parts.back()->right; - new_data_part->level = 0; - for (size_t i = 0; i < parts.size(); ++i) - { - new_data_part->level = std::max(new_data_part->level, parts[i]->level); - new_data_part->left_date = std::min(new_data_part->left_date, parts[i]->left_date); - new_data_part->right_date = std::max(new_data_part->right_date, parts[i]->right_date); - } - ++new_data_part->level; - new_data_part->name = getPartName( - new_data_part->left_date, new_data_part->right_date, new_data_part->left, new_data_part->right, new_data_part->level); - new_data_part->left_month = date_lut.toFirstDayNumOfMonth(new_data_part->left_date); - new_data_part->right_month = date_lut.toFirstDayNumOfMonth(new_data_part->right_date); - - /** Читаем из всех кусков, сливаем и пишем в новый. - * Попутно вычисляем выражение для сортировки. - */ - BlockInputStreams src_streams; - - for (size_t i = 0; i < parts.size(); ++i) - { - MarkRanges ranges(1, MarkRange(0, parts[i]->size)); - src_streams.push_back(new ExpressionBlockInputStream(new MergeTreeBlockInputStream( - full_path + parts[i]->name + '/', DEFAULT_MERGE_BLOCK_SIZE, all_column_names, *this, parts[i], ranges, - StoragePtr(), false, NULL, ""), primary_expr)); - } - - /// Порядок потоков важен: при совпадении ключа элементы идут в порядке номера потока-источника. - /// В слитом куске строки с одинаковым ключом должны идти в порядке возрастания идентификатора исходного куска, то есть (примерного) возрастания времени вставки. - BlockInputStreamPtr merged_stream; - - switch (mode) - { - case Ordinary: - merged_stream = new MergingSortedBlockInputStream(src_streams, sort_descr, DEFAULT_MERGE_BLOCK_SIZE); - break; - - case Collapsing: - merged_stream = new CollapsingSortedBlockInputStream(src_streams, sort_descr, sign_column, DEFAULT_MERGE_BLOCK_SIZE); - break; - - case Summing: - merged_stream = new SummingSortedBlockInputStream(src_streams, sort_descr, DEFAULT_MERGE_BLOCK_SIZE); - break; - - default: - throw Exception("Unknown mode of operation for StorageMergeTree: " + toString(mode), ErrorCodes::LOGICAL_ERROR); - } - - MergedBlockOutputStreamPtr to = new MergedBlockOutputStream(*this, - new_data_part->left_date, new_data_part->right_date, new_data_part->left, new_data_part->right, new_data_part->level); - - merged_stream->readPrefix(); - to->writePrefix(); - - Block block; - while (!shutdown_called && (block = merged_stream->read())) - to->write(block); - - if (shutdown_called) - { - LOG_INFO(log, "Shutdown requested while merging parts."); - return; - } - - merged_stream->readSuffix(); - to->writeSuffix(); - - /// В обычном режиме строчки не могут удалиться при мердже. - if (0 == to->marksCount() && mode == Ordinary) - throw Exception("Empty part after merge", ErrorCodes::LOGICAL_ERROR); - - new_data_part->size = to->marksCount(); - new_data_part->modification_time = time(0); - - if (0 != to->marksCount()) - new_data_part->loadIndex(); /// NOTE Только что записанный индекс заново считывается с диска. Можно было бы формировать его сразу при записи. - - { - Poco::ScopedLock lock(data_parts_mutex); - Poco::ScopedLock lock_all(all_data_parts_mutex); - - /// Добавляем новый кусок в набор, если он не пустой. - - for (size_t i = 0; i < parts.size(); ++i) - if (data_parts.end() == data_parts.find(parts[i])) - throw Exception("Logical error: cannot find data part " + parts[i]->name + " in list", ErrorCodes::LOGICAL_ERROR); - - if (0 == to->marksCount()) - { - LOG_INFO(log, "All rows have been deleted while merging from " << parts.front()->name << " to " << parts.back()->name); - } - else - { - data_parts.insert(new_data_part); - all_data_parts.insert(new_data_part); - } - - for (size_t i = 0; i < parts.size(); ++i) - data_parts.erase(data_parts.find(parts[i])); - } - - LOG_TRACE(log, "Merged " << parts.size() << " parts: from " << parts.front()->name << " to " << parts.back()->name); -} - - -void StorageMergeTree::rename(const String & new_path_to_db, const String & new_name) -{ - joinMergeThreads(); - - /// Кажется тут race condition - в этот момент мердж может запуститься снова. - - std::string new_full_path = new_path_to_db + escapeForFileName(new_name) + '/'; - - Poco::File(full_path).renameTo(new_full_path); - - path = new_path_to_db; - full_path = new_full_path; - name = new_name; - - increment.setPath(full_path + "increment.txt"); -} - - -void StorageMergeTree::dropImpl() -{ - joinMergeThreads(); - - Poco::ScopedLock lock(data_parts_mutex); - Poco::ScopedLock lock_all(all_data_parts_mutex); - - data_parts.clear(); - all_data_parts.clear(); - - Poco::File(full_path).remove(true); -} - -void StorageMergeTree::removeColumnFiles(String column_name) -{ - Poco::ScopedLock lock(data_parts_mutex); - Poco::ScopedLock lock_all(all_data_parts_mutex); - - /// Регэксп выбирает файлы столбца для удаления - Poco::RegularExpression re(column_name + "(?:(?:\\.|\\%2E).+){0,1}" +"(?:\\.mrk|\\.bin|\\.size\\d+\\.bin|\\.size\\d+\\.mrk)"); - /// Цикл по всем директориям кусочков - Poco::RegularExpression::MatchVec matches; - Poco::DirectoryIterator end; - for (Poco::DirectoryIterator it_dir = Poco::DirectoryIterator(full_path); it_dir != end; ++it_dir) - { - std::string dir_name = it_dir.name(); - - if (!isPartDirectory(dir_name, matches)) - continue; - - /// Цикл по каждому из файлов в директории кусочков - String full_dir_name = full_path + dir_name + "/"; - for (Poco::DirectoryIterator it_file(full_dir_name); it_file != end; ++it_file) - { - if (re.match(it_file.name())) - { - Poco::File file(full_dir_name + it_file.name()); - if (file.exists()) - file.remove(); - } - } - } -} - -void StorageMergeTree::createConvertExpression(const String & in_column_name, const String & out_type, ExpressionActionsPtr & out_expression, String & out_column) -{ - ASTFunction * function = new ASTFunction; - ASTPtr function_ptr = function; - - ASTExpressionList * arguments = new ASTExpressionList; - ASTPtr arguments_ptr = arguments; - - function->name = "to" + out_type; - function->arguments = arguments_ptr; - function->children.push_back(arguments_ptr); - - ASTIdentifier * in_column = new ASTIdentifier; - ASTPtr in_column_ptr = in_column; - - arguments->children.push_back(in_column_ptr); - - in_column->name = in_column_name; - in_column->kind = ASTIdentifier::Column; - - out_expression = ExpressionAnalyzer(function_ptr, context, *columns).getActions(false); - out_column = function->getColumnName(); -} - -void StorageMergeTree::alter(const ASTAlterQuery::Parameters & params) -{ - if (params.type == ASTAlterQuery::MODIFY) - { - { - typedef std::vector PartsList; - PartsList parts; - { - Poco::ScopedLock lock(data_parts_mutex); - for (auto & data_part : data_parts) - { - parts.push_back(data_part); - } - } - - Names column_name; - const ASTNameTypePair & name_type = dynamic_cast(*params.name_type); - StringRange type_range = name_type.type->range; - String type(type_range.first, type_range.second - type_range.first); - DB::DataTypePtr old_type_ptr = getDataTypeByName(name_type.name); - DB::DataTypePtr new_type_ptr = context.getDataTypeFactory().get(type); - if (dynamic_cast(old_type_ptr.get()) || dynamic_cast(old_type_ptr.get()) || - dynamic_cast(new_type_ptr.get()) || dynamic_cast(new_type_ptr.get())) - throw DB::Exception("ALTER MODIFY not supported for nested and array types"); - - column_name.push_back(name_type.name); - DB::ExpressionActionsPtr expr; - String out_column; - createConvertExpression(name_type.name, type, expr, out_column); - - ColumnNumbers num(1, 0); - for (DataPartPtr & part : parts) - { - MarkRanges ranges(1, MarkRange(0, part->size)); - ExpressionBlockInputStream in(new MergeTreeBlockInputStream(full_path + part->name + '/', - DEFAULT_MERGE_BLOCK_SIZE, column_name, *this, part, ranges, StoragePtr(), false, NULL, ""), expr); - MergedColumnOnlyOutputStream out(*this, full_path + part->name + '/', true); - out.writePrefix(); - - try - { - while(DB::Block b = in.read()) - { - /// оставляем только столбец с результатом - b.erase(0); - out.write(b); - } - LOG_TRACE(log, "Write Suffix"); - out.writeSuffix(); - } - catch (const Exception & e) - { - if (e.code() != ErrorCodes::ALL_REQUESTED_COLUMNS_ARE_MISSING) - throw; - } - } - - /// переименовываем файлы - /// переименовываем старые столбцы, добавляя расширение .old - for (DataPartPtr & part : parts) - { - std::string path = full_path + part->name + '/' + escapeForFileName(name_type.name); - if (Poco::File(path + ".bin").exists()) - { - LOG_TRACE(log, "Renaming " << path + ".bin" << " to " << path + ".bin" + ".old"); - Poco::File(path + ".bin").renameTo(path + ".bin" + ".old"); - LOG_TRACE(log, "Renaming " << path + ".mrk" << " to " << path + ".mrk" + ".old"); - Poco::File(path + ".mrk").renameTo(path + ".mrk" + ".old"); - } - } - - /// переименовываем временные столбцы - for (DataPartPtr & part : parts) - { - std::string path = full_path + part->name + '/' + escapeForFileName(out_column); - std::string new_path = full_path + part->name + '/' + escapeForFileName(name_type.name); - if (Poco::File(path + ".bin").exists()) - { - LOG_TRACE(log, "Renaming " << path + ".bin" << " to " << new_path + ".bin"); - Poco::File(path + ".bin").renameTo(new_path + ".bin"); - LOG_TRACE(log, "Renaming " << path + ".mrk" << " to " << new_path + ".mrk"); - Poco::File(path + ".mrk").renameTo(new_path + ".mrk"); - } - } - - // удаляем старые столбцы - for (DataPartPtr & part : parts) - { - std::string path = full_path + part->name + '/' + escapeForFileName(name_type.name); - if (Poco::File(path + ".bin" + ".old").exists()) - { - LOG_TRACE(log, "Removing old column " << path + ".bin" + ".old"); - Poco::File(path + ".bin" + ".old").remove(); - LOG_TRACE(log, "Removing old column " << path + ".mrk" + ".old"); - Poco::File(path + ".mrk" + ".old").remove(); - } - } - } - context.getUncompressedCache()->reset(); - context.getMarkCache()->reset(); - } - { - Poco::ScopedLock lock(data_parts_mutex); - Poco::ScopedLock lock_all(all_data_parts_mutex); - alterColumns(params, columns, context); - } - if (params.type == ASTAlterQuery::DROP) - { - String column_name = dynamic_cast(*params.column).name; - removeColumnFiles(column_name); - } -} - - -bool StorageMergeTree::isPartDirectory(const String & dir_name, Poco::RegularExpression::MatchVec & matches) const -{ - return (file_name_regexp.match(dir_name, 0, matches) && 6 == matches.size()); -} - - -bool StorageMergeTree::isBrokenPart(const String & path) -{ - /// Проверяем, что первичный ключ непуст. - - Poco::File index_file(path + "/primary.idx"); - - if (!index_file.exists() || index_file.getSize() == 0) - { - LOG_ERROR(log, "Part " << path << " is broken: primary key is empty."); - - return true; - } - - /// Проверяем, что все засечки непусты и имеют одинаковый размер. - - ssize_t marks_size = -1; - for (NamesAndTypesList::const_iterator it = columns->begin(); it != columns->end(); ++it) - { - Poco::File marks_file(path + "/" + escapeForFileName(it->first) + ".mrk"); - - /// при добавлении нового столбца в таблицу файлы .mrk не создаются. Не будем ничего удалять. - if (!marks_file.exists()) - continue; - - if (marks_size == -1) - { - marks_size = marks_file.getSize(); - - if (0 == marks_size) - { - LOG_ERROR(log, "Part " << path << " is broken: " << marks_file.path() << " is empty."); - - return true; - } - } - else - { - if (static_cast(marks_file.getSize()) != marks_size) - { - LOG_ERROR(log, "Part " << path << " is broken: marks have different sizes."); - - return true; - } - } - } - - return false; -} - -Strings StorageMergeTree::tryRestorePart(const String & path, const String & file_name, Strings & old_parts) -{ - LOG_ERROR(log, "Restoring all old_ parts covered by " << file_name); - - Poco::RegularExpression::MatchVec matches; - Strings restored_parts; - - isPartDirectory(file_name, matches); - DataPart broken_part(*this); - parsePartName(file_name, matches, broken_part); - - for (int i = static_cast(old_parts.size()) - 1; i >= 0; --i) - { - DataPart old_part(*this); - String name = old_parts[i].substr(strlen("old_")); - if (!isPartDirectory(name, matches)) - { - LOG_ERROR(log, "Strange file name: " << path + old_parts[i] << "; ignoring"); - old_parts.erase(old_parts.begin() + i); - continue; - } - parsePartName(name, matches, old_part); - if (broken_part.contains(old_part)) - { - /// Восстанавливаем все содержащиеся куски. Если некоторые из них содержатся в других, их удалит loadDataParts. - LOG_ERROR(log, "Restoring part " << path + old_parts[i]); - Poco::File(path + old_parts[i]).renameTo(path + name); - old_parts.erase(old_parts.begin() + i); - restored_parts.push_back(name); - } - } - - if (restored_parts.size() >= 2) - { - LOG_ERROR(log, "Removing broken part " << path + file_name << " because at least 2 old_ parts were restored in its place"); - Poco::File(path + file_name).remove(true); - } - else - { - LOG_ERROR(log, "Not removing broken part " << path + file_name - << " because less than 2 old_ parts were restored in its place. You need to resolve this manually"); - } - - return restored_parts; + return !currently_merging.count(left) && !currently_merging.count(right); } } diff --git a/dbms/src/Storages/StoragePtr.cpp b/dbms/src/Storages/StoragePtr.cpp deleted file mode 100644 index 5986e754d9c..00000000000 --- a/dbms/src/Storages/StoragePtr.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include -#include -#include - - -namespace DB -{ - -StoragePtr::Wrapper::Wrapper() {} - -StoragePtr::Wrapper::Wrapper(IStorage * s) : storage(s) {} - -StoragePtr::Wrapper::~Wrapper() -{ - try - { - if (std::uncaught_exception()) - LOG_ERROR(&Logger::get("StoragePtr"), "Maybe ignored drop table query because of uncaught exception."); - else - { - if (storage && storage->drop_on_destroy) - { - storage->dropImpl(); - - if (Poco::File(storage->path_to_remove_on_drop).exists()) - Poco::File(storage->path_to_remove_on_drop).remove(true); - } - } - } - catch(...) - { - } -} - -} diff --git a/dbms/src/Storages/StorageTinyLog.cpp b/dbms/src/Storages/StorageTinyLog.cpp index ec365d0321a..ab179fb5492 100644 --- a/dbms/src/Storages/StorageTinyLog.cpp +++ b/dbms/src/Storages/StorageTinyLog.cpp @@ -27,12 +27,25 @@ namespace DB using Poco::SharedPtr; -TinyLogBlockInputStream::TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage) - : IProfilingBlockInputStream(owned_storage), block_size(block_size_), column_names(column_names_), storage(dynamic_cast(*owned_storage)), finished(false) +TinyLogBlockInputStream::TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StorageTinyLog & storage_) + : block_size(block_size_), column_names(column_names_), storage(storage_), finished(false) { } +String TinyLogBlockInputStream::getID() const +{ + std::stringstream res; + res << "TinyLog(" << storage.getTableName() << ", " << &storage; + + for (size_t i = 0; i < column_names.size(); ++i) + res << ", " << column_names[i]; + + res << ")"; + return res.str(); +} + + Block TinyLogBlockInputStream::readImpl() { Block res; @@ -172,8 +185,8 @@ void TinyLogBlockInputStream::readData(const String & name, const IDataType & ty } -TinyLogBlockOutputStream::TinyLogBlockOutputStream(StoragePtr owned_storage) - : IBlockOutputStream(owned_storage), storage(dynamic_cast(*owned_storage)) +TinyLogBlockOutputStream::TinyLogBlockOutputStream(StorageTinyLog & storage_) + : storage(storage_) { for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it) addStream(it->first, *it->second); @@ -363,18 +376,18 @@ BlockInputStreams StorageTinyLog::read( { check(column_names); processed_stage = QueryProcessingStage::FetchColumns; - return BlockInputStreams(1, new TinyLogBlockInputStream(max_block_size, column_names, thisPtr())); + return BlockInputStreams(1, new TinyLogBlockInputStream(max_block_size, column_names, *this)); } BlockOutputStreamPtr StorageTinyLog::write( ASTPtr query) { - return new TinyLogBlockOutputStream(thisPtr()); + return new TinyLogBlockOutputStream(*this); } -void StorageTinyLog::dropImpl() +void StorageTinyLog::drop() { for (Files_t::iterator it = files.begin(); it != files.end(); ++it) if (it->second.data_file.exists()) diff --git a/dbms/src/Storages/StorageView.cpp b/dbms/src/Storages/StorageView.cpp index a2edea772e5..ab494a1b382 100644 --- a/dbms/src/Storages/StorageView.cpp +++ b/dbms/src/Storages/StorageView.cpp @@ -65,7 +65,7 @@ BlockInputStreams StorageView::read( } -void StorageView::dropImpl() { +void StorageView::drop() { context.getGlobalContext().removeDependency(DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(database_name, table_name)); } diff --git a/dbms/src/Storages/tests/MergeLogicTester.cpp b/dbms/src/Storages/tests/MergeLogicTester.cpp index 6719da88dfb..e03b503c07d 100644 --- a/dbms/src/Storages/tests/MergeLogicTester.cpp +++ b/dbms/src/Storages/tests/MergeLogicTester.cpp @@ -37,7 +37,7 @@ DataParts copy(const DataParts &a) const int RowsPerSec = 100000; -StorageMergeTreeSettings settings; +MergeTreeSettings settings; size_t index_granularity = 1; diff --git a/dbms/tests/.gitignore b/dbms/tests/.gitignore new file mode 100644 index 00000000000..3b3e487c63a --- /dev/null +++ b/dbms/tests/.gitignore @@ -0,0 +1,4 @@ +*.result +*.diff +*.error +test_data diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test new file mode 100755 index 00000000000..3300a944d0f --- /dev/null +++ b/dbms/tests/clickhouse-test @@ -0,0 +1,100 @@ +#!/bin/bash + +# Скрипт для тестирования запросов к ClickHouse. +# Из файлов *.sql в заданной директории, в алфавитном порядке, отправляются все запросы. +# Результаты сравниваются с эталонами. + +QUERIES_DIR="./queries" +CLIENT_PROGRAM="curl -sS http://localhost:8123/ --data-binary @-" + + +COLOR_RESET="\033[0m" +COLOR_WHITE="\033[1;37m" +COLOR_FAIL="\033[1;31m" +COLOR_UNKNOWN="\033[1;30m" +COLOR_OK="\033[1;32m" + +MSG_FAIL="${COLOR_WHITE}[ ${COLOR_FAIL}FAIL${COLOR_WHITE} ]${COLOR_RESET}" +MSG_UNKNOWN="${COLOR_WHITE}[ ${COLOR_UNKNOWN}UNKNOWN${COLOR_WHITE} ]${COLOR_RESET}" +MSG_OK="${COLOR_WHITE}[ ${COLOR_OK}OK${COLOR_WHITE} ]${COLOR_RESET}" +MSG_GENERATED="${COLOR_WHITE}[ ${COLOR_UNKNOWN}GENERATED${COLOR_WHITE} ]${COLOR_RESET}" + +ERRORS=0 + +for dir in $(ls $QUERIES_DIR) +do + tests_name=$(echo $dir | sed -E 's/^[0-9_]+//') + + echo + echo "Running $tests_name tests." + echo + + if [[ "$tests_name" =~ "stateful" && 0 -eq $(echo "EXISTS TABLE test.hits" | $CLIENT_PROGRAM) ]]; then + echo "Won't run stateful tests because test data wasn't loaded. See README.txt." + continue + fi + + for query_file in $(ls $QUERIES_DIR/$dir/*.sql) + do + test_name=$(basename $query_file .sql) + + result_file=$QUERIES_DIR/$dir/$test_name.result + error_file=$QUERIES_DIR/$dir/$test_name.error + reference_file=$QUERIES_DIR/$dir/$test_name.reference + diff_file=$QUERIES_DIR/$dir/$test_name.diff + + printf "%-60s" "$test_name: " + + $CLIENT_PROGRAM < $query_file > $result_file 2> $error_file + ret_code=$? + + if [ $ret_code -ne 0 ]; then + ERRORS=$(($ERRORS + 1)) + echo -e "$MSG_FAIL - return code $ret_code" + if [ -s "$error_file" ]; then + cat $error_file + fi + # разорвано соединение с сервером + if grep -q -E "Connection refused|Attempt to read after eof" $error_file; then + exit 1; + fi + elif [ -s "$error_file" ]; then + ERRORS=$(($ERRORS + 1)) + echo -e "$MSG_FAIL - having stderror:" + cat $error_file + elif grep -q "Exception" $result_file; then + ERRORS=$(($ERRORS + 1)) + echo -e "$MSG_FAIL - having exception:" + cat $result_file + elif [ ! -e "$reference_file" ]; then + # надо сгенерировать эталонный результат + if [ "$1" == "--generate" ]; then + cp $result_file $reference_file + echo -e "$MSG_GENERATED - no reference file" + else + echo -e "$MSG_UNKNOWN - no reference file (use --generate to create)" + fi + else + diff $reference_file $result_file > $diff_file + if [ -s "$diff_file" ]; then + ERRORS=$(($ERRORS + 1)) + echo -e "$MSG_FAIL - result differs with reference:" + cat $diff_file + else + echo -e "$MSG_OK" + rm $error_file $result_file $diff_file + fi + fi + done +done + + +echo + +if [ $ERRORS -gt 0 ]; then + echo -e "${COLOR_FAIL}Having $ERRORS errors!${COLOR_RESET}" + exit 1 +else + echo -e "${COLOR_OK}All tests passed.${COLOR_RESET}" + exit 0 +fi diff --git a/dbms/tests/queries/0_stateless/00001_select_1.reference b/dbms/tests/queries/0_stateless/00001_select_1.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00001_select_1.reference @@ -0,0 +1 @@ +1 diff --git a/dbms/tests/queries/0_stateless/00001_select_1.sql b/dbms/tests/queries/0_stateless/00001_select_1.sql new file mode 100644 index 00000000000..2e3761f7a2c --- /dev/null +++ b/dbms/tests/queries/0_stateless/00001_select_1.sql @@ -0,0 +1 @@ +SELECT 1 diff --git a/dbms/tests/queries/0_stateless/00002_system_numbers.reference b/dbms/tests/queries/0_stateless/00002_system_numbers.reference new file mode 100644 index 00000000000..8b1acc12b63 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00002_system_numbers.reference @@ -0,0 +1,10 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 diff --git a/dbms/tests/queries/0_stateless/00002_system_numbers.sql b/dbms/tests/queries/0_stateless/00002_system_numbers.sql new file mode 100644 index 00000000000..bc9269495bc --- /dev/null +++ b/dbms/tests/queries/0_stateless/00002_system_numbers.sql @@ -0,0 +1 @@ +SELECT * FROM system.numbers LIMIT 10 diff --git a/dbms/tests/queries/0_stateless/00003_reinterpret_as_string.reference b/dbms/tests/queries/0_stateless/00003_reinterpret_as_string.reference new file mode 100644 index 00000000000..fc75583cf02 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00003_reinterpret_as_string.reference @@ -0,0 +1 @@ +33232 diff --git a/dbms/tests/queries/0_stateless/00003_reinterpret_as_string.sql b/dbms/tests/queries/0_stateless/00003_reinterpret_as_string.sql new file mode 100644 index 00000000000..1204f6280f2 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00003_reinterpret_as_string.sql @@ -0,0 +1 @@ +SELECT number FROM system.numbers WHERE reinterpretAsString(number) = 'Ё' LIMIT 1 diff --git a/dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.reference b/dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.reference new file mode 100644 index 00000000000..343ee5c2f6c --- /dev/null +++ b/dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.reference @@ -0,0 +1,2 @@ +-1 +-1 diff --git a/dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.sql b/dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.sql new file mode 100644 index 00000000000..dd20fd1d8e4 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.sql @@ -0,0 +1 @@ +SELECT (dummy AS x) - 1 FROM remote('127.0.0.{1,2}', system, one) diff --git a/dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.reference b/dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.reference new file mode 100644 index 00000000000..0cfbf08886f --- /dev/null +++ b/dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.reference @@ -0,0 +1 @@ +2 diff --git a/dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.sql b/dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.sql new file mode 100644 index 00000000000..fb401df5184 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.sql @@ -0,0 +1 @@ +SELECT count() FROM remote('127.0.0.{1,2}', system, one) WHERE arrayExists((x) -> x = 1, [1, 2, 3]) diff --git a/dbms/tests/queries/0_stateless/00006_1_set_extremes.reference b/dbms/tests/queries/0_stateless/00006_1_set_extremes.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/0_stateless/00006_1_set_extremes.sql b/dbms/tests/queries/0_stateless/00006_1_set_extremes.sql new file mode 100644 index 00000000000..53bd0c96ce2 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00006_1_set_extremes.sql @@ -0,0 +1 @@ +SET GLOBAL extremes = 1 diff --git a/dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.reference b/dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.reference new file mode 100644 index 00000000000..841ae17708f --- /dev/null +++ b/dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.reference @@ -0,0 +1,18 @@ +{ + "meta": + [ + { + "name": "'Hello, world'", + "type": "String" + } + ], + + "data": + [ + + ], + + "rows": 0, + + "rows_before_limit_at_least": 10 +} diff --git a/dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.sql b/dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.sql new file mode 100644 index 00000000000..c8e1e1bf66f --- /dev/null +++ b/dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.sql @@ -0,0 +1,2 @@ +SELECT 'Hello, world' FROM (SELECT number FROM system.numbers LIMIT 10) WHERE number < 0 +FORMAT JSONCompact \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00006_3_unset_extremes.reference b/dbms/tests/queries/0_stateless/00006_3_unset_extremes.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/0_stateless/00006_3_unset_extremes.sql b/dbms/tests/queries/0_stateless/00006_3_unset_extremes.sql new file mode 100644 index 00000000000..31e8a97da82 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00006_3_unset_extremes.sql @@ -0,0 +1 @@ +SET GLOBAL extremes = 0 diff --git a/dbms/tests/queries/0_stateless/00007_array.reference b/dbms/tests/queries/0_stateless/00007_array.reference new file mode 100644 index 00000000000..2a64c8ea7b2 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00007_array.reference @@ -0,0 +1 @@ +['Hello','Goodbye'] diff --git a/dbms/tests/queries/0_stateless/00007_array.sql b/dbms/tests/queries/0_stateless/00007_array.sql new file mode 100644 index 00000000000..7c1f27f1978 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00007_array.sql @@ -0,0 +1 @@ +SELECT ['Hello', 'Goodbye'] diff --git a/dbms/tests/queries/0_stateless/00008_array_join.reference b/dbms/tests/queries/0_stateless/00008_array_join.reference new file mode 100644 index 00000000000..c86756d1938 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00008_array_join.reference @@ -0,0 +1,2 @@ +Hello +Goodbye diff --git a/dbms/tests/queries/0_stateless/00008_array_join.sql b/dbms/tests/queries/0_stateless/00008_array_join.sql new file mode 100644 index 00000000000..abb35cbbfc8 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00008_array_join.sql @@ -0,0 +1 @@ +SELECT arrayJoin(['Hello', 'Goodbye']) diff --git a/dbms/tests/queries/0_stateless/00009_array_join_subquery.reference b/dbms/tests/queries/0_stateless/00009_array_join_subquery.reference new file mode 100644 index 00000000000..c86756d1938 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00009_array_join_subquery.reference @@ -0,0 +1,2 @@ +Hello +Goodbye diff --git a/dbms/tests/queries/0_stateless/00009_array_join_subquery.sql b/dbms/tests/queries/0_stateless/00009_array_join_subquery.sql new file mode 100644 index 00000000000..378baadd026 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00009_array_join_subquery.sql @@ -0,0 +1 @@ +SELECT x FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x) diff --git a/dbms/tests/queries/0_stateless/00010_big_array_join.reference b/dbms/tests/queries/0_stateless/00010_big_array_join.reference new file mode 100644 index 00000000000..e9c80bae6d0 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00010_big_array_join.reference @@ -0,0 +1,6 @@ +Hello +Hello +Hello +Goodbye +Goodbye +Goodbye diff --git a/dbms/tests/queries/0_stateless/00010_big_array_join.sql b/dbms/tests/queries/0_stateless/00010_big_array_join.sql new file mode 100644 index 00000000000..f7b9160b578 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00010_big_array_join.sql @@ -0,0 +1 @@ +SELECT x FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x, [1, 2, 3] AS arr) ARRAY JOIN arr diff --git a/dbms/tests/queries/0_stateless/00011_array_join_alias.reference b/dbms/tests/queries/0_stateless/00011_array_join_alias.reference new file mode 100644 index 00000000000..ff5ad16d763 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00011_array_join_alias.reference @@ -0,0 +1,6 @@ +Hello 1 +Hello 2 +Hello 3 +Goodbye 1 +Goodbye 2 +Goodbye 3 diff --git a/dbms/tests/queries/0_stateless/00011_array_join_alias.sql b/dbms/tests/queries/0_stateless/00011_array_join_alias.sql new file mode 100644 index 00000000000..228038c1509 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00011_array_join_alias.sql @@ -0,0 +1 @@ +SELECT x, a FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x, [1, 2, 3] AS arr) ARRAY JOIN arr AS a diff --git a/dbms/tests/queries/0_stateless/00012_array_join_alias_2.reference b/dbms/tests/queries/0_stateless/00012_array_join_alias_2.reference new file mode 100644 index 00000000000..ebad7b98d93 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00012_array_join_alias_2.reference @@ -0,0 +1,6 @@ +Hello 1 [1,2,3] +Hello 2 [1,2,3] +Hello 3 [1,2,3] +Goodbye 1 [1,2,3] +Goodbye 2 [1,2,3] +Goodbye 3 [1,2,3] diff --git a/dbms/tests/queries/0_stateless/00012_array_join_alias_2.sql b/dbms/tests/queries/0_stateless/00012_array_join_alias_2.sql new file mode 100644 index 00000000000..a45cf2d87b8 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00012_array_join_alias_2.sql @@ -0,0 +1 @@ +SELECT x, a, arr FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x, [1, 2, 3] AS arr) ARRAY JOIN arr AS a diff --git a/dbms/tests/queries/0_stateless/00013_1_drop_if_exists_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_1_drop_if_exists_table_with_arrays.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/0_stateless/00013_1_drop_if_exists_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_1_drop_if_exists_table_with_arrays.sql new file mode 100644 index 00000000000..8dbef3a0abb --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_1_drop_if_exists_table_with_arrays.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS arrays_test diff --git a/dbms/tests/queries/0_stateless/00013_2_create_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_2_create_table_with_arrays.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/0_stateless/00013_2_create_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_2_create_table_with_arrays.sql new file mode 100644 index 00000000000..8585ba14140 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_2_create_table_with_arrays.sql @@ -0,0 +1 @@ +CREATE TABLE arrays_test (s String, arr Array(UInt8)) ENGINE = Memory diff --git a/dbms/tests/queries/0_stateless/00013_3_insert_into_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_3_insert_into_table_with_arrays.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/0_stateless/00013_3_insert_into_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_3_insert_into_table_with_arrays.sql new file mode 100644 index 00000000000..5b41e56db66 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_3_insert_into_table_with_arrays.sql @@ -0,0 +1 @@ +INSERT INTO arrays_test VALUES ('Hello', [1,2]), ('World', [3,4,5]), ('Goodbye', []) diff --git a/dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..5a5af40fc00 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.reference @@ -0,0 +1,3 @@ +Hello [1,2] +World [3,4,5] +Goodbye [] diff --git a/dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..af4b5dd8538 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.sql @@ -0,0 +1 @@ +SELECT * FROM arrays_test diff --git a/dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..02ff6407fd6 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.reference @@ -0,0 +1,5 @@ +Hello 1 +Hello 2 +World 3 +World 4 +World 5 diff --git a/dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..238f888202d --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.sql @@ -0,0 +1 @@ +SELECT s, arr FROM arrays_test ARRAY JOIN arr diff --git a/dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..45d362a350e --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.reference @@ -0,0 +1,5 @@ +Hello [1,2] 1 +Hello [1,2] 2 +World [3,4,5] 3 +World [3,4,5] 4 +World [3,4,5] 5 diff --git a/dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..d1c9936af30 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.sql @@ -0,0 +1 @@ +SELECT s, arr, a FROM arrays_test ARRAY JOIN arr AS a \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..577260f94f0 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.reference @@ -0,0 +1,5 @@ +Hello [1,2] 1 1 +Hello [1,2] 2 2 +World [3,4,5] 3 1 +World [3,4,5] 4 2 +World [3,4,5] 5 3 diff --git a/dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..a75b97e6faa --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.sql @@ -0,0 +1 @@ +SELECT s, arr, a, num FROM arrays_test ARRAY JOIN arr AS a, arrayEnumerate(arr) AS num \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..e29fe6d82a1 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.reference @@ -0,0 +1,5 @@ +Hello [1,2] 1 1 [1,2] +Hello [1,2] 2 2 [1,2] +World [3,4,5] 3 1 [1,2,3] +World [3,4,5] 4 2 [1,2,3] +World [3,4,5] 5 3 [1,2,3] diff --git a/dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..d7b3a7b44b9 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.sql @@ -0,0 +1 @@ +SELECT s, arr, a, num, arrayEnumerate(arr) FROM arrays_test ARRAY JOIN arr AS a, arrayEnumerate(arr) AS num \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..58328ca2588 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.reference @@ -0,0 +1,5 @@ +Hello [1,2] 1 2 +Hello [1,2] 2 3 +World [3,4,5] 3 4 +World [3,4,5] 4 5 +World [3,4,5] 5 6 diff --git a/dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..010faea3d76 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.sql @@ -0,0 +1 @@ +SELECT s, arr, a, mapped FROM arrays_test ARRAY JOIN arr AS a, arrayMap(x -> x + 1, arr) AS mapped diff --git a/dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..14686f65be3 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.reference @@ -0,0 +1,5 @@ +Hello [1,2] 1 1 2 +Hello [1,2] 2 2 3 +World [3,4,5] 3 1 4 +World [3,4,5] 4 2 5 +World [3,4,5] 5 3 6 diff --git a/dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..1c4b2b02e19 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.sql @@ -0,0 +1 @@ +SELECT s, arr, a, num, mapped FROM arrays_test ARRAY JOIN arr AS a, arrayEnumerate(arr) AS num, arrayMap(x -> x + 1, arr) AS mapped diff --git a/dbms/tests/queries/0_stateless/00014_1_drop_if_exists_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_1_drop_if_exists_table_with_nested.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/0_stateless/00014_1_drop_if_exists_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_1_drop_if_exists_table_with_nested.sql new file mode 100644 index 00000000000..257afc58919 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_1_drop_if_exists_table_with_nested.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS nested_test diff --git a/dbms/tests/queries/0_stateless/00014_2_create_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_2_create_table_with_nested.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/0_stateless/00014_2_create_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_2_create_table_with_nested.sql new file mode 100644 index 00000000000..49f015ce1aa --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_2_create_table_with_nested.sql @@ -0,0 +1 @@ +CREATE TABLE nested_test (s String, nest Nested(x UInt8, y UInt32)) ENGINE = Memory diff --git a/dbms/tests/queries/0_stateless/00014_3_insert_into_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_3_insert_into_table_with_nested.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/0_stateless/00014_3_insert_into_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_3_insert_into_table_with_nested.sql new file mode 100644 index 00000000000..44fdbc820ff --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_3_insert_into_table_with_nested.sql @@ -0,0 +1 @@ +INSERT INTO nested_test VALUES ('Hello', [1,2], [10,20]), ('World', [3,4,5], [30,40,50]), ('Goodbye', [], []) diff --git a/dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.reference new file mode 100644 index 00000000000..32bd08bad02 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.reference @@ -0,0 +1,3 @@ +Hello [1,2] [10,20] +World [3,4,5] [30,40,50] +Goodbye [] [] diff --git a/dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.sql new file mode 100644 index 00000000000..509911959ac --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.sql @@ -0,0 +1 @@ +SELECT * FROM nested_test diff --git a/dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.reference new file mode 100644 index 00000000000..05200edd33b --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.reference @@ -0,0 +1,5 @@ +Hello 1 10 +Hello 2 20 +World 3 30 +World 4 40 +World 5 50 diff --git a/dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.sql new file mode 100644 index 00000000000..31e1ac1b05e --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.sql @@ -0,0 +1 @@ +SELECT s, nest.x, nest.y FROM nested_test ARRAY JOIN nest diff --git a/dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.reference new file mode 100644 index 00000000000..8ea4568b73c --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.reference @@ -0,0 +1,5 @@ +Hello 1 [10,20] +Hello 2 [10,20] +World 3 [30,40,50] +World 4 [30,40,50] +World 5 [30,40,50] diff --git a/dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.sql new file mode 100644 index 00000000000..3648cb4852c --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.sql @@ -0,0 +1 @@ +SELECT s, nest.x, nest.y FROM nested_test ARRAY JOIN nest.x diff --git a/dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.reference new file mode 100644 index 00000000000..05200edd33b --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.reference @@ -0,0 +1,5 @@ +Hello 1 10 +Hello 2 20 +World 3 30 +World 4 40 +World 5 50 diff --git a/dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.sql new file mode 100644 index 00000000000..9380d7d285f --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.sql @@ -0,0 +1 @@ +SELECT s, nest.x, nest.y FROM nested_test ARRAY JOIN nest.x, nest.y \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.reference new file mode 100644 index 00000000000..05200edd33b --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.reference @@ -0,0 +1,5 @@ +Hello 1 10 +Hello 2 20 +World 3 30 +World 4 40 +World 5 50 diff --git a/dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.sql new file mode 100644 index 00000000000..945f3dc79a1 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.sql @@ -0,0 +1 @@ +SELECT s, n.x, n.y FROM nested_test ARRAY JOIN nest AS n \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.reference new file mode 100644 index 00000000000..d40754269c6 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.reference @@ -0,0 +1,5 @@ +Hello 1 10 [1,2] +Hello 2 20 [1,2] +World 3 30 [3,4,5] +World 4 40 [3,4,5] +World 5 50 [3,4,5] diff --git a/dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.sql new file mode 100644 index 00000000000..4e275aa4e12 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.sql @@ -0,0 +1 @@ +SELECT s, n.x, n.y, nest.x FROM nested_test ARRAY JOIN nest AS n \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.reference new file mode 100644 index 00000000000..7129270657e --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.reference @@ -0,0 +1,5 @@ +Hello 1 10 [1,2] [10,20] +Hello 2 20 [1,2] [10,20] +World 3 30 [3,4,5] [30,40,50] +World 4 40 [3,4,5] [30,40,50] +World 5 50 [3,4,5] [30,40,50] diff --git a/dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.sql new file mode 100644 index 00000000000..4643e428942 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.sql @@ -0,0 +1 @@ +SELECT s, n.x, n.y, nest.x, nest.y FROM nested_test ARRAY JOIN nest AS n \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.reference new file mode 100644 index 00000000000..fa7a8052ce9 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.reference @@ -0,0 +1,5 @@ +Hello 1 10 [1,2] [10,20] 1 +Hello 2 20 [1,2] [10,20] 2 +World 3 30 [3,4,5] [30,40,50] 1 +World 4 40 [3,4,5] [30,40,50] 2 +World 5 50 [3,4,5] [30,40,50] 3 diff --git a/dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.sql new file mode 100644 index 00000000000..6033dd1c011 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.sql @@ -0,0 +1 @@ +SELECT s, n.x, n.y, nest.x, nest.y, num FROM nested_test ARRAY JOIN nest AS n, arrayEnumerate(nest.x) AS num \ No newline at end of file diff --git a/dbms/tests/queries/0_stateless/00015_totals_having_constants.reference b/dbms/tests/queries/0_stateless/00015_totals_having_constants.reference new file mode 100644 index 00000000000..1b62b5f7c43 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00015_totals_having_constants.reference @@ -0,0 +1,12 @@ +0 10 +1 10 +5 10 +7 10 +2 10 +6 10 +4 10 +8 10 +3 10 +9 10 + +0 100 diff --git a/dbms/tests/queries/0_stateless/00015_totals_having_constants.sql b/dbms/tests/queries/0_stateless/00015_totals_having_constants.sql new file mode 100644 index 00000000000..586bef3ac13 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00015_totals_having_constants.sql @@ -0,0 +1 @@ +SELECT number, count() / 0.1 FROM (SELECT number FROM system.numbers LIMIT 10) GROUP BY number WITH TOTALS HAVING count() > 0.1 diff --git a/dbms/tests/queries/0_stateless/00016_totals_having_constants.reference b/dbms/tests/queries/0_stateless/00016_totals_having_constants.reference new file mode 100644 index 00000000000..4804131baba --- /dev/null +++ b/dbms/tests/queries/0_stateless/00016_totals_having_constants.reference @@ -0,0 +1,3 @@ +0 10 + +0 10 diff --git a/dbms/tests/queries/0_stateless/00016_totals_having_constants.sql b/dbms/tests/queries/0_stateless/00016_totals_having_constants.sql new file mode 100644 index 00000000000..c50659b8140 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00016_totals_having_constants.sql @@ -0,0 +1 @@ +SELECT dummy, count() / 0.1 GROUP BY dummy WITH TOTALS HAVING count() > 0.1 diff --git a/dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.reference b/dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.reference new file mode 100644 index 00000000000..e25c5780b65 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.reference @@ -0,0 +1,18 @@ +{ + "meta": + [ + { + "name": "count()", + "type": "UInt64" + } + ], + + "data": + [ + + ], + + "rows": 0, + + "rows_before_limit_at_least": 1000 +} diff --git a/dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.sql b/dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.sql new file mode 100644 index 00000000000..b83d597159d --- /dev/null +++ b/dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.sql @@ -0,0 +1,2 @@ +SELECT count() FROM (SELECT * FROM system.numbers LIMIT 1000) WHERE 1 IN (SELECT 0 WHERE 0) +FORMAT JSON diff --git a/dbms/tests/queries/0_stateless/00018_distinct_in_subquery.reference b/dbms/tests/queries/0_stateless/00018_distinct_in_subquery.reference new file mode 100644 index 00000000000..6ed281c757a --- /dev/null +++ b/dbms/tests/queries/0_stateless/00018_distinct_in_subquery.reference @@ -0,0 +1,2 @@ +1 +1 diff --git a/dbms/tests/queries/0_stateless/00018_distinct_in_subquery.sql b/dbms/tests/queries/0_stateless/00018_distinct_in_subquery.sql new file mode 100644 index 00000000000..b0bce846904 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00018_distinct_in_subquery.sql @@ -0,0 +1 @@ +SELECT x FROM (SELECT DISTINCT 1 AS x, arrayJoin([1, 2]) AS y) diff --git a/libs/libzkutil/include/zkutil/ZooKeeper.h b/libs/libzkutil/include/zkutil/ZooKeeper.h index 06c7900a5cd..09c8d01b552 100644 --- a/libs/libzkutil/include/zkutil/ZooKeeper.h +++ b/libs/libzkutil/include/zkutil/ZooKeeper.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace zkutil @@ -19,6 +20,22 @@ class ZooKeeper public: ZooKeeper(const std::string & hosts, int32_t sessionTimeoutMs = DEFAULT_SESSION_TIMEOUT, WatchFunction * watch = nullptr); + /** конфиг вида + + + example1 + 2181 + + + example2 + 2181 + + 30000 + + */ + ZooKeeper(const Poco::Util::LayeredConfiguration & config, const std::string & config_name, + WatchFunction * watch = nullptr); + /** Возвращает true, если сессия навсегда завершена. * Это возможно только если соединение было установлено, а потом разорвалось. Это достаточно редкая ситуация. * С другой стороны, если, например, указан неправильный сервер или порт, попытки соединения будут продолжаться бесконечно, @@ -95,6 +112,7 @@ public: OpResultsPtr tryMulti(const Ops & ops); private: + void init(const std::string & hosts, int32_t sessionTimeoutMs, WatchFunction * watch_); friend struct StateWatch; zk::ZooKeeper impl; diff --git a/libs/libzkutil/src/ZooKeeper.cpp b/libs/libzkutil/src/ZooKeeper.cpp index 190ba860881..c82c5f44e5b 100644 --- a/libs/libzkutil/src/ZooKeeper.cpp +++ b/libs/libzkutil/src/ZooKeeper.cpp @@ -43,9 +43,10 @@ struct StateWatch : public zk::Watch } }; -ZooKeeper::ZooKeeper(const std::string & hosts, int32_t sessionTimeoutMs, WatchFunction * watch_) - : state_watch(watch_) +void ZooKeeper::init(const std::string & hosts, int32_t sessionTimeoutMs, WatchFunction * watch_) { + state_watch = watch_; + CHECKED(impl.init(hosts, sessionTimeoutMs, boost::make_shared(this))); ACL perm; @@ -55,6 +56,48 @@ ZooKeeper::ZooKeeper(const std::string & hosts, int32_t sessionTimeoutMs, WatchF default_acl.push_back(perm); } +ZooKeeper::ZooKeeper(const std::string & hosts, int32_t sessionTimeoutMs, WatchFunction * watch_) +{ + init(hosts, sessionTimeoutMs, watch_); +} + +struct ZooKeeperArgs +{ + ZooKeeperArgs(const Poco::Util::LayeredConfiguration & config, const std::string & config_name) + { + Poco::Util::AbstractConfiguration::Keys keys; + config.keys(config_name, keys); + std::string node_key = "node"; + std::string node_key_ext = "node["; + + session_timeout_ms = DEFAULT_SESSION_TIMEOUT; + for (const auto & key : keys) + { + if (key == node_key || key.compare(0, node_key.size(), node_key) == 0) + { + if (hosts.size()) + hosts += std::string(" "); + hosts += config.getString(config_name + "." + key + ".host") + ":" + config.getString(config_name + "." + key + ".port"); + } + else if (key == "session_timeout_ms") + { + session_timeout_ms = config.getInt(config_name + "." + key); + } + else throw KeeperException(std::string("Unknown key ") + key + " in config file"); + } + } + + std::string hosts; + size_t session_timeout_ms; +}; + +ZooKeeper::ZooKeeper(const Poco::Util::LayeredConfiguration & config, const std::string & config_name, + WatchFunction * watch) +{ + ZooKeeperArgs args(config, config_name); + init(args.hosts, args.session_timeout_ms, watch); +} + void ZooKeeper::stateChanged(WatchEvent::type event, SessionState::type state, const std::string & path) { session_state = state;