From 96fb6da011d7fbf98b59542339fbf87ee5b7c5bf Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Sun, 9 Mar 2014 18:24:04 +0400 Subject: [PATCH 01/51] Merge --- dbms/include/DB/Storages/MergeTreeData.h | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 dbms/include/DB/Storages/MergeTreeData.h diff --git a/dbms/include/DB/Storages/MergeTreeData.h b/dbms/include/DB/Storages/MergeTreeData.h new file mode 100644 index 00000000000..ec3f68d7354 --- /dev/null +++ b/dbms/include/DB/Storages/MergeTreeData.h @@ -0,0 +1,27 @@ +#pragma once + +namespace DB +{ + +/** Отвечает за хранение локальных данных всех *MergeTree движков. + * - Поддерживает набор кусков на диске. Синхронизирует доступ к ним, поддерживает в памяти их список. + * - Полностью выполняет запросы SELECT. + * - Сам не принимает решений об изменении данных. + * - Умеет двавть рекомендации: + * - Говорить, какие куски нужно удалить, потому что они покрыты другими кусками. + * - Выбирать набор кусков для слияния. + * При этом нужна внешняя информация о том, какие куски с какими разрешено объединять. + * - Умеет изменять данные по запросу: + * - Записать новый кусок с данными из блока. + * - Слить указанные куски. + * - Сделать ALTER. + */ +class MergeTreeData +{ +public: + +private: + +}; + +} From 58ea3b108b1acd812ce1bccad3f06c7b4f0407b4 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Sun, 9 Mar 2014 21:36:01 +0400 Subject: [PATCH 02/51] Merge --- .../include/DB/Storages/IColumnsDeclaration.h | 65 + dbms/include/DB/Storages/IStorage.h | 50 +- .../MergeTree/MergeTreeBlockInputStream.h | 12 +- .../DB/Storages/MergeTree/MergeTreeData.h | 527 ++++++ ...eam.h => MergeTreeDataBlockOutputStream.h} | 16 +- .../DB/Storages/MergeTree/MergeTreeReader.h | 6 +- .../MergeTree/MergedBlockOutputStream.h | 10 +- dbms/include/DB/Storages/MergeTreeData.h | 27 - dbms/include/DB/Storages/StorageMergeTree.h | 465 +----- .../{IStorage.cpp => IColumnsDeclaration.cpp} | 48 +- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 1468 +++++++++++++++++ dbms/src/Storages/StorageFactory.cpp | 4 +- dbms/src/Storages/StorageMergeTree.cpp | 1462 +--------------- dbms/src/Storages/tests/MergeLogicTester.cpp | 2 +- 14 files changed, 2154 insertions(+), 2008 deletions(-) create mode 100644 dbms/include/DB/Storages/IColumnsDeclaration.h create mode 100644 dbms/include/DB/Storages/MergeTree/MergeTreeData.h rename dbms/include/DB/Storages/MergeTree/{MergeTreeBlockOutputStream.h => MergeTreeDataBlockOutputStream.h} (96%) delete mode 100644 dbms/include/DB/Storages/MergeTreeData.h rename dbms/src/Storages/{IStorage.cpp => IColumnsDeclaration.cpp} (91%) create mode 100644 dbms/src/Storages/MergeTree/MergeTreeData.cpp diff --git a/dbms/include/DB/Storages/IColumnsDeclaration.h b/dbms/include/DB/Storages/IColumnsDeclaration.h new file mode 100644 index 00000000000..217e06e8827 --- /dev/null +++ b/dbms/include/DB/Storages/IColumnsDeclaration.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace DB +{ + +class Context; + +/** Описание столбцов таблицы. + */ +class IColumnsDeclaration +{ +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 ~IColumnsDeclaration() {} +}; + +} diff --git a/dbms/include/DB/Storages/IStorage.h b/dbms/include/DB/Storages/IStorage.h index 321175335b5..2ce322f23d4 100644 --- a/dbms/include/DB/Storages/IStorage.h +++ b/dbms/include/DB/Storages/IStorage.h @@ -13,7 +13,8 @@ #include #include #include -#include "DatabaseDropper.h" +#include +#include #include @@ -30,41 +31,15 @@ class Context; * - структура хранения данных (сжатие, etc.) * - конкуррентный доступ к данным (блокировки, etc.) */ -class IStorage : private boost::noncopyable +class IStorage : private boost::noncopyable, public IColumnsDeclaration { 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; } @@ -172,19 +147,6 @@ public: */ 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() @@ -213,9 +175,7 @@ public: protected: IStorage() : drop_on_destroy(false) {} - - /// реализация alter, модифицирующая список столбцов. - void alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) const; + private: boost::weak_ptr this_ptr; }; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h index 09931dba6e7..18cf15862dd 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -18,7 +18,7 @@ public: /// (например, поток, сливаящий куски). В таком случае сам storage должен следить, чтобы не удалить данные, пока их читают. MergeTreeBlockInputStream(const String & path_, /// Путь к куску size_t block_size_, const Names & column_names_, - StorageMergeTree & storage_, const StorageMergeTree::DataPartPtr & owned_data_part_, + MergeTreeData & storage_, const MergeTreeData::DataPartPtr & owned_data_part_, const MarkRanges & mark_ranges_, StoragePtr owned_storage, bool use_uncompressed_cache_, ExpressionActionsPtr prewhere_actions_, String prewhere_column_) : IProfilingBlockInputStream(owned_storage), @@ -74,8 +74,8 @@ public: /// Получает набор диапазонов засечек, вне которых не могут находиться ключи из заданного диапазона. static MarkRanges markRangesFromPkRange( - const StorageMergeTree::DataPart::Index & index, - StorageMergeTree & storage, + const MergeTreeData::DataPart::Index & index, + MergeTreeData & storage, PKCondition & key_condition) { MarkRanges res; @@ -321,8 +321,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; /// В каких диапазонах засечек еще не прочли. /// В порядке убывания номеров, чтобы можно было выбрасывать из конца. diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h new file mode 100644 index 00000000000..9c12ccd0419 --- /dev/null +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -0,0 +1,527 @@ +#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. + */ + +/// NOTE: Следующее пока не правда. Сейчас тут практически весь StorageMergeTree. Лишние части нужно перенести отсюда в StorageMergeTree. + +/** Этот класс отвечает за хранение локальных данных всех *MergeTree движков. + * - Поддерживает набор кусков на диске. Синхронизирует доступ к ним, поддерживает в памяти их список. + * - Полностью выполняет запросы SELECT. + * - Сам не принимает решений об изменении данных. + * - Умеет дававть рекомендации: + * - Говорить, какие куски нужно удалить, потому что они покрыты другими кусками. + * - Выбирать набор кусков для слияния. + * При этом нужна внешняя информация о том, какие куски с какими разрешено объединять. + * - Умеет изменять данные по запросу: + * - Записать новый кусок с данными из блока. + * - Слить указанные куски. + * - Сделать ALTER. + */ + +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; +}; + +/// Пара засечек, определяющая диапазон строк в куске. Именно, диапазон имеет вид [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 MergeTreeData : public IColumnsDeclaration +{ +friend class MergeTreeReader; +friend class MergeTreeBlockInputStream; +friend class MergeTreeDataBlockOutputStream; +friend class IMergedBlockOutputStream; +friend class MergedBlockOutputStream; +friend class MergedColumnOnlyOutputStream; + +public: + /// Режим работы. См. выше. + enum Mode + { + Ordinary, + Collapsing, + Summing, + }; + + /** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце), + * (корректность имён и путей не проверяется) + * состоящую из указанных столбцов. + * + * primary_expr_ast - выражение для сортировки; + * date_column_name - имя столбца с датой; + * index_granularity - на сколько строчек пишется одно значение индекса. + */ + MergeTreeData( StoragePtr owning_storage_, 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_, + Mode mode_, + const String & sign_column_, + const MergeTreeSettings & settings_); + + void shutdown(); + ~MergeTreeData(); + + std::string 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); + } + } + + 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; } + + const NamesAndTypesList & getColumnsList() const { return *columns; } + + /** При чтении, выбирается набор кусков, покрывающий нужный диапазон индекса. + */ + 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); + + /** При записи, данные сортируются и пишутся в новые куски. + */ + BlockOutputStreamPtr write(ASTPtr query); + + /** Выполнить очередной шаг объединения кусков. + */ + bool optimize() + { + merge(1, false, true); + return true; + } + + void dropImpl(); + + 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(MergeTreeData & 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); + } + +private: + StoragePtr owning_storage; + + 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; + + MergeTreeSettings settings; + + ExpressionActionsPtr primary_expr; + SortDescription sort_descr; + Block primary_key_sample; + + Increment increment; + + Logger * log; + volatile bool shutdown_called; + + /// Регулярное выражение соответсвующее названию директории с кусочками + Poco::RegularExpression file_name_regexp; + + /// Описание куска с данными. + struct DataPart + { + DataPart(MergeTreeData & storage_) : storage(storage_), size_in_bytes(0), currently_merging(false) {} + + 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; + + /// Смотреть и изменять это поле следует под залоченным 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 и пересчитывает общий объем сливаемых данных. + /// Вероятно, что части будут помечены заранее. + class CurrentlyMergingPartsTagger + { + public: + std::vector parts; + Poco::FastMutex & data_mutex; + + CurrentlyMergingPartsTagger(const std::vector & parts_, Poco::FastMutex & data_mutex_) : parts(parts_), data_mutex(data_mutex_) + { + /// Здесь не лочится мьютекс, так как конструктор вызывается внутри selectPartsToMerge, где он уже залочен + /// Poco::ScopedLock lock(data_mutex); + for (size_t i = 0; i < parts.size(); ++i) + { + parts[i]->currently_merging = true; + MergeTreeData::total_size_of_currently_merging_parts += parts[i]->size_in_bytes; + } + } + + ~CurrentlyMergingPartsTagger() + { + Poco::ScopedLock lock(data_mutex); + for (size_t i = 0; i < parts.size(); ++i) + { + parts[i]->currently_merging = false; + MergeTreeData::total_size_of_currently_merging_parts -= parts[i]->size_in_bytes; + } + } + }; + + /// Сумарный размер 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; + + static String getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level); + + 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); +}; + +} diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataBlockOutputStream.h similarity index 96% rename from dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h rename to dbms/include/DB/Storages/MergeTree/MergeTreeDataBlockOutputStream.h index f74e82860c0..8cdad62fa9d 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataBlockOutputStream.h @@ -7,16 +7,16 @@ #include -#include +#include namespace DB { - -class MergeTreeBlockOutputStream : public IBlockOutputStream + +class MergeTreeDataBlockOutputStream : public IBlockOutputStream { public: - MergeTreeBlockOutputStream(StoragePtr owned_storage) : IBlockOutputStream(owned_storage), storage(dynamic_cast(*owned_storage)), flags(O_TRUNC | O_CREAT | O_WRONLY) + MergeTreeDataBlockOutputStream(MergeTreeData & data, StoragePtr owned_storage) : IBlockOutputStream(owned_storage), storage(data), flags(O_TRUNC | O_CREAT | O_WRONLY) { } @@ -25,7 +25,7 @@ public: Poco::ScopedReadRWLock write_lock(storage.write_lock); storage.check(block, true); - + DateLUTSingleton & date_lut = DateLUTSingleton::instance(); size_t rows = block.rows(); @@ -82,7 +82,7 @@ public: } private: - StorageMergeTree & storage; + MergeTreeData & storage; const int flags; @@ -130,7 +130,7 @@ private: LOG_TRACE(storage.log, "Writing index."); /// Сначала пишем индекс. Индекс содержит значение PK для каждой index_granularity строки. - StorageMergeTree::DataPart::Index index_vec; + MergeTreeData::DataPart::Index index_vec; index_vec.reserve(part_size * storage.sort_descr.size()); { @@ -183,7 +183,7 @@ private: 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); + MergeTreeData::DataPartPtr new_data_part = new MergeTreeData::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; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h index 8c3833a6eaa..01342700ccc 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -22,7 +22,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 +220,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..e91a0d23883 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) { @@ -299,7 +299,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/MergeTreeData.h b/dbms/include/DB/Storages/MergeTreeData.h deleted file mode 100644 index ec3f68d7354..00000000000 --- a/dbms/include/DB/Storages/MergeTreeData.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -namespace DB -{ - -/** Отвечает за хранение локальных данных всех *MergeTree движков. - * - Поддерживает набор кусков на диске. Синхронизирует доступ к ним, поддерживает в памяти их список. - * - Полностью выполняет запросы SELECT. - * - Сам не принимает решений об изменении данных. - * - Умеет двавть рекомендации: - * - Говорить, какие куски нужно удалить, потому что они покрыты другими кусками. - * - Выбирать набор кусков для слияния. - * При этом нужна внешняя информация о том, какие куски с какими разрешено объединять. - * - Умеет изменять данные по запросу: - * - Записать новый кусок с данными из блока. - * - Слить указанные куски. - * - Сделать ALTER. - */ -class MergeTreeData -{ -public: - -private: - -}; - -} diff --git a/dbms/include/DB/Storages/StorageMergeTree.h b/dbms/include/DB/Storages/StorageMergeTree.h index 3716f8a5538..366716bfd1c 100644 --- a/dbms/include/DB/Storages/StorageMergeTree.h +++ b/dbms/include/DB/Storages/StorageMergeTree.h @@ -1,123 +1,15 @@ #pragma once -#include -#include - -#include -#include -#include -#include -#include - +#include 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 +24,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 getTableName() const { return data.getTableName(); } + 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,20 +52,17 @@ public: size_t max_block_size = DEFAULT_BLOCK_SIZE, unsigned threads = 1); - /** При записи, данные сортируются и пишутся в новые куски. - */ BlockOutputStreamPtr write(ASTPtr query); /** Выполнить очередной шаг объединения кусков. */ bool optimize() { - merge(1, false, true); - return true; + return data.optimize(); } void dropImpl(); - + void rename(const String & new_path_to_db, const String & new_name); /// Метод ALTER позволяет добавлять и удалять столбцы. @@ -191,255 +70,12 @@ public: /// Например если параллельно с 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) - { - } + typedef MergeTreeData::BigLockPtr BigLockPtr; - private: - Poco::ScopedWriteRWLock merge_lock; - Poco::ScopedWriteRWLock write_lock; - Poco::ScopedWriteRWLock read_lock; - }; - - typedef Poco::SharedPtr BigLockPtr; - BigLockPtr lockAllOperations() - { - return new BigLock(*this); - } + BigLockPtr lockAllOperations() { return data.lockAllOperations(); } 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; - - Logger * log; - volatile bool shutdown_called; - - /// Регулярное выражение соответсвующее названию директории с кусочками - Poco::RegularExpression file_name_regexp; - - /// Описание куска с данными. - 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 и пересчитывает общий объем сливаемых данных. - /// Вероятно, что части будут помечены заранее. - class CurrentlyMergingPartsTagger - { - public: - std::vector parts; - Poco::FastMutex & data_mutex; - - CurrentlyMergingPartsTagger(const std::vector & parts_, Poco::FastMutex & data_mutex_) : parts(parts_), data_mutex(data_mutex_) - { - /// Здесь не лочится мьютекс, так как конструктор вызывается внутри selectPartsToMerge, где он уже залочен - /// Poco::ScopedLock lock(data_mutex); - for (size_t i = 0; i < parts.size(); ++i) - { - parts[i]->currently_merging = true; - StorageMergeTree::total_size_of_currently_merging_parts += parts[i]->size_in_bytes; - } - } - - ~CurrentlyMergingPartsTagger() - { - Poco::ScopedLock lock(data_mutex); - for (size_t i = 0; i < parts.size(); ++i) - { - parts[i]->currently_merging = false; - StorageMergeTree::total_size_of_currently_merging_parts -= parts[i]->size_in_bytes; - } - } - }; - - /// Сумарный размер 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; + MergeTreeData data; StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_, const Context & context_, @@ -447,74 +83,9 @@ private: 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()); - - static String getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level); - - 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); + const MergeTreeSettings & settings_ = MergeTreeSettings()); }; } diff --git a/dbms/src/Storages/IStorage.cpp b/dbms/src/Storages/IColumnsDeclaration.cpp similarity index 91% rename from dbms/src/Storages/IStorage.cpp rename to dbms/src/Storages/IColumnsDeclaration.cpp index 9e98fb5f476..b6b21b904ba 100644 --- a/dbms/src/Storages/IStorage.cpp +++ b/dbms/src/Storages/IColumnsDeclaration.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 IColumnsDeclaration::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 IColumnsDeclaration::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 IColumnsDeclaration::hasColumn(const String &column_name) const { return hasRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет. } -NameAndTypePair IStorage::getColumn(const String &column_name) const +NameAndTypePair IColumnsDeclaration::getColumn(const String &column_name) const { return getRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет. } -const DataTypePtr IStorage::getDataTypeByName(const String & column_name) const +const DataTypePtr IColumnsDeclaration::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 IColumnsDeclaration::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 IColumnsDeclaration::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 IColumnsDeclaration::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 IColumnsDeclaration::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/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp new file mode 100644 index 00000000000..7644f8104dc --- /dev/null +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -0,0 +1,1468 @@ +#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 MergeTreeData::total_size_of_currently_merging_parts = 0; + +MergeTreeData::MergeTreeData( + StoragePtr owning_storage_, 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 MergeTreeSettings & settings_) + : owning_storage(owning_storage_), 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("MergeTreeData: " + name)), shutdown_called(false), + file_name_regexp("^(\\d{8})_(\\d{8})_(\\d+)_(\\d+)_(\\d+)") +{ + 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; + + /// создаём директорию, если её нет + 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); +} + +void MergeTreeData::shutdown() +{ + if (shutdown_called) + return; + shutdown_called = true; + + joinMergeThreads(); +} + + +MergeTreeData::~MergeTreeData() +{ + shutdown(); +} + + +BlockOutputStreamPtr MergeTreeData::write(ASTPtr query) +{ + return new MergeTreeDataBlockOutputStream(*this, owning_storage); +} + + +BlockInputStreams MergeTreeData::read( + const Names & column_names_to_return, + 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; +} + + +/// Примерно поровну распределить засечки между потоками. +BlockInputStreams MergeTreeData::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( + full_path + part.data_part->name + '/', max_block_size, column_names, *this, + part.data_part, part.ranges, owning_storage, 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, owning_storage, 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; +} + + +/// Распределить засечки между потоками и сделать, чтобы в ответе (почти) все данные были сколлапсированы (модификатор FINAL). +BlockInputStreams MergeTreeData::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( + full_path + part.data_part->name + '/', max_block_size, column_names, *this, + part.data_part, part.ranges, owning_storage, 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; +} + + +void MergeTreeData::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 = 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(); +} + + +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; + + 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)"); +} + + +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->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 MergeTreeData::merge(size_t iterations, bool async, bool aggressive) +{ + bool while_can = false; + if (iterations == 0) + { + while_can = true; + iterations = settings.merging_threads; + } + + for (size_t i = 0; i < iterations; ++i) + merge_threads->schedule(boost::bind(&MergeTreeData::mergeThread, this, while_can, aggressive)); + + if (!async) + joinMergeThreads(); +} + + +void MergeTreeData::mergeThread(bool while_can, bool aggressive) +{ + try + { + while (!shutdown_called) + { + /// Удаляем старые куски. На случай, если в слиянии что-то сломано, и из следующего блока вылетит исключение. + clearOldParts(); + + { + Poco::ScopedReadRWLock lock(merge_lock); + + /// К концу этого логического блока должен быть вызван деструктор, чтобы затем корректно определить удаленные куски + Poco::SharedPtr what; + + if (!selectPartsToMerge(what, false, aggressive) && !selectPartsToMerge(what, true, aggressive)) + break; + + mergeParts(what); + } + + if (shutdown_called) + break; + + /// Удаляем куски, которые мы только что сливали. + clearOldParts(); + + if (!while_can) + break; + } + } + catch (const Exception & e) + { + LOG_ERROR(log, "Code: " << e.code() << ". " << e.displayText() << std::endl + << std::endl + << "Stack trace:" << std::endl + << e.getStackTrace().toString()); + } + catch (const Poco::Exception & e) + { + LOG_ERROR(log, "Poco::Exception: " << e.code() << ". " << e.displayText()); + } + catch (const std::exception & e) + { + LOG_ERROR(log, "std::exception: " << e.what()); + } + catch (...) + { + LOG_ERROR(log, "Unknown exception"); + } +} + + +void MergeTreeData::joinMergeThreads() +{ + LOG_DEBUG(log, "Waiting for merge threads to finish."); + 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 MergeTreeData::selectPartsToMerge(Poco::SharedPtr & what, bool merge_anything_for_old_months, bool aggressive) +{ + 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 MergeTreeData::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(); + + MergeTreeData::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 MergeTreeData: " + 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 MergeTreeData::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 MergeTreeData::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 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(); +} + +void MergeTreeData::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 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; +} + +} diff --git a/dbms/src/Storages/StorageFactory.cpp b/dbms/src/Storages/StorageFactory.cpp index 42077e75b9c..8ef0d0d3d83 100644 --- a/dbms/src/Storages/StorageFactory.cpp +++ b/dbms/src/Storages/StorageFactory.cpp @@ -192,7 +192,7 @@ StoragePtr StorageFactory::get( return StorageMergeTree::create( data_path, table_name, columns, context, primary_expr, date_column_name, sampling_expression, index_granularity, - name == "SummingMergeTree" ? StorageMergeTree::Summing : StorageMergeTree::Ordinary); + name == "SummingMergeTree" ? MergeTreeData::Summing : MergeTreeData::Ordinary); } else if (name == "CollapsingMergeTree") { @@ -234,7 +234,7 @@ StoragePtr StorageFactory::get( return StorageMergeTree::create( data_path, table_name, columns, context, primary_expr, date_column_name, - sampling_expression, index_granularity, StorageMergeTree::Collapsing, sign_column_name); + sampling_expression, index_granularity, MergeTreeData::Collapsing, sign_column_name); } else if (name == "SystemNumbers") { diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index 537a9cf0f89..2fce5fdd26e 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -1,1484 +1,72 @@ -#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+)") -{ - 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; - - /// создаём директорию, если её нет - 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); -} +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_) + : data( thisPtr(), path_, name_, columns_, context_, primary_expr_ast_, date_column_name_, sampling_expression_, + index_granularity_,mode_, sign_column_, settings_) {} 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; - - joinMergeThreads(); -} - - -StorageMergeTree::~StorageMergeTree() -{ - shutdown(); -} - - -BlockOutputStreamPtr StorageMergeTree::write(ASTPtr query) -{ - return new MergeTreeBlockOutputStream(thisPtr()); + data.shutdown(); } +StorageMergeTree::~StorageMergeTree() {} 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 data.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 data.write(query); } - -/// Распределить засечки между потоками и сделать, чтобы в ответе (почти) все данные были сколлапсированы (модификатор 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) -{ - 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; -} - - -void StorageMergeTree::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 = 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(); -} - - -String StorageMergeTree::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 StorageMergeTree::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 StorageMergeTree::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; - - 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)"); -} - - -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; - } - - for (size_t i = 0; i < iterations; ++i) - merge_threads->schedule(boost::bind(&StorageMergeTree::mergeThread, this, while_can, aggressive)); - - if (!async) - joinMergeThreads(); -} - - -void StorageMergeTree::mergeThread(bool while_can, bool aggressive) -{ - try - { - while (!shutdown_called) - { - /// Удаляем старые куски. На случай, если в слиянии что-то сломано, и из следующего блока вылетит исключение. - clearOldParts(); - - { - Poco::ScopedReadRWLock lock(merge_lock); - - /// К концу этого логического блока должен быть вызван деструктор, чтобы затем корректно определить удаленные куски - Poco::SharedPtr what; - - if (!selectPartsToMerge(what, false, aggressive) && !selectPartsToMerge(what, true, aggressive)) - break; - - mergeParts(what); - } - - if (shutdown_called) - break; - - /// Удаляем куски, которые мы только что сливали. - clearOldParts(); - - if (!while_can) - break; - } - } - catch (const Exception & e) - { - LOG_ERROR(log, "Code: " << e.code() << ". " << e.displayText() << std::endl - << std::endl - << "Stack trace:" << std::endl - << e.getStackTrace().toString()); - } - catch (const Poco::Exception & e) - { - LOG_ERROR(log, "Poco::Exception: " << e.code() << ". " << e.displayText()); - } - catch (const std::exception & e) - { - LOG_ERROR(log, "std::exception: " << e.what()); - } - catch (...) - { - LOG_ERROR(log, "Unknown exception"); - } -} - - -void StorageMergeTree::joinMergeThreads() -{ - LOG_DEBUG(log, "Waiting for merge threads to finish."); - 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) -{ - 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); + data.dropImpl(); } -void StorageMergeTree::removeColumnFiles(String column_name) +void StorageMergeTree::rename(const String & new_path_to_db, const String & new_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(); + data.rename(new_path_to_db, new_name); } 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; + data.alter(params); } } 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; From 989a3162441533b1a59fda8469067e6aa767912d Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Sun, 9 Mar 2014 21:58:27 +0400 Subject: [PATCH 03/51] Merge --- dbms/include/DB/Storages/IStorage.h | 3 ++- .../DB/Storages/MergeTree/MergeTreeData.h | 6 +++-- dbms/include/DB/Storages/StoragePtr.h | 22 ++++++++++++++++++- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 2 +- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/dbms/include/DB/Storages/IStorage.h b/dbms/include/DB/Storages/IStorage.h index 2ce322f23d4..e78294bb44b 100644 --- a/dbms/include/DB/Storages/IStorage.h +++ b/dbms/include/DB/Storages/IStorage.h @@ -16,6 +16,7 @@ #include #include #include +#include namespace DB @@ -153,7 +154,7 @@ public: { if (!this_ptr.lock()) { - boost::shared_ptr p(new StoragePtr::Wrapper(this)); + auto p = boost::make_shared(this); this_ptr = p; return StoragePtr(this_ptr); } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index 9c12ccd0419..ae1d323d7ba 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -140,8 +140,10 @@ public: * primary_expr_ast - выражение для сортировки; * date_column_name - имя столбца с датой; * index_granularity - на сколько строчек пишется одно значение индекса. + * + * owning_storage используется только чтобы отдавать его возвращаемым потокам блоков. */ - MergeTreeData( StoragePtr owning_storage_, const String & path_, const String & name_, NamesAndTypesListPtr columns_, + MergeTreeData( StorageWeakPtr owning_storage_, const String & path_, const String & name_, NamesAndTypesListPtr columns_, const Context & context_, ASTPtr & primary_expr_ast_, const String & date_column_name_, @@ -227,7 +229,7 @@ public: } private: - StoragePtr owning_storage; + StorageWeakPtr owning_storage; String path; String name; diff --git a/dbms/include/DB/Storages/StoragePtr.h b/dbms/include/DB/Storages/StoragePtr.h index b1ca8dc9f84..d19756c0105 100644 --- a/dbms/include/DB/Storages/StoragePtr.h +++ b/dbms/include/DB/Storages/StoragePtr.h @@ -11,7 +11,7 @@ namespace DB { class IStorage; - +class StorageWeakPtr; class StoragePtr { @@ -33,10 +33,12 @@ private: boost::shared_ptr ptr; friend class IStorage; + friend class StorageWeakPtr; public: StoragePtr() {} StoragePtr(const StoragePtr & p) : ptr(p.ptr) {} + StoragePtr(const StorageWeakPtr & p); StoragePtr& operator= (const StoragePtr & p) { @@ -83,4 +85,22 @@ public: } }; +class StorageWeakPtr +{ +public: + StorageWeakPtr(const StoragePtr & p) : ptr(p.ptr) {} + + StoragePtr lock() + { + return StoragePtr(ptr); + } + +private: + friend class StoragePtr; + + boost::weak_ptr ptr; +}; + +StoragePtr::StoragePtr(const StorageWeakPtr & p) : ptr(p.ptr) {} + } diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index 7644f8104dc..230a1512e7e 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -60,7 +60,7 @@ namespace DB size_t MergeTreeData::total_size_of_currently_merging_parts = 0; MergeTreeData::MergeTreeData( - StoragePtr owning_storage_, const String & path_, const String & name_, NamesAndTypesListPtr columns_, + StorageWeakPtr owning_storage_, const String & path_, const String & name_, NamesAndTypesListPtr columns_, const Context & context_, ASTPtr & primary_expr_ast_, const String & date_column_name_, const ASTPtr & sampling_expression_, From be8855dcd28d1867d014108227d37ae584e7d6a3 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Sun, 9 Mar 2014 22:16:52 +0400 Subject: [PATCH 04/51] fixed build; not it segfaults :) [#METR-10202] --- dbms/include/DB/Storages/StoragePtr.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dbms/include/DB/Storages/StoragePtr.h b/dbms/include/DB/Storages/StoragePtr.h index d19756c0105..89aea28cb2d 100644 --- a/dbms/include/DB/Storages/StoragePtr.h +++ b/dbms/include/DB/Storages/StoragePtr.h @@ -38,7 +38,7 @@ private: public: StoragePtr() {} StoragePtr(const StoragePtr & p) : ptr(p.ptr) {} - StoragePtr(const StorageWeakPtr & p); + inline StoragePtr(const StorageWeakPtr & p); StoragePtr& operator= (const StoragePtr & p) { @@ -101,6 +101,6 @@ private: boost::weak_ptr ptr; }; -StoragePtr::StoragePtr(const StorageWeakPtr & p) : ptr(p.ptr) {} +inline StoragePtr::StoragePtr(const StorageWeakPtr & p) : ptr(p.ptr) {} } From 5df33181345bad3ce618ad2944dd86a9f244a1bd Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Wed, 12 Mar 2014 14:28:25 +0400 Subject: [PATCH 05/51] dbms: fixed possible race condition in alter [#METR-10242] --- .../Storages/MergeTree/MergeTreeBlockInputStream.h | 7 +++++-- .../Storages/MergeTree/MergeTreeBlockOutputStream.h | 7 ++++--- dbms/src/Storages/StorageMergeTree.cpp | 12 ++++++------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h index 09931dba6e7..a298e6f2482 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h @@ -20,13 +20,14 @@ public: 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_, - ExpressionActionsPtr prewhere_actions_, String prewhere_column_) + ExpressionActionsPtr prewhere_actions_, String prewhere_column_, bool take_read_lock) : 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_), + lock(take_read_lock ? new Poco::ScopedReadRWLock(storage.read_lock) : NULL) { std::reverse(remaining_mark_ranges.begin(), remaining_mark_ranges.end()); @@ -332,6 +333,8 @@ private: ExpressionActionsPtr prewhere_actions; String prewhere_column; bool remove_prewhere_column; + + std::unique_ptr lock; }; } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h index f74e82860c0..ab1e24f502d 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h @@ -16,14 +16,13 @@ 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(StoragePtr owned_storage, bool take_lock) : IBlockOutputStream(owned_storage), storage(dynamic_cast(*owned_storage)), flags(O_TRUNC | O_CREAT | O_WRONLY), + lock(take_lock ? new Poco::ScopedReadRWLock(storage.write_lock) : NULL) { } void write(const Block & block) { - Poco::ScopedReadRWLock write_lock(storage.write_lock); - storage.check(block, true); DateLUTSingleton & date_lut = DateLUTSingleton::instance(); @@ -86,6 +85,8 @@ private: const int flags; + std::unique_ptr lock; + struct BlockWithDateInterval { Block block; diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index 537a9cf0f89..c90d111c3fc 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -146,7 +146,7 @@ StorageMergeTree::~StorageMergeTree() BlockOutputStreamPtr StorageMergeTree::write(ASTPtr query) { - return new MergeTreeBlockOutputStream(thisPtr()); + return new MergeTreeBlockOutputStream(thisPtr(), true); } @@ -416,7 +416,7 @@ BlockInputStreams StorageMergeTree::spreadMarkRangesAmongThreads( 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)); + prewhere_actions, prewhere_column, true)); need_marks -= marks_in_part; parts.pop_back(); sum_marks_in_parts.pop_back(); @@ -446,7 +446,7 @@ BlockInputStreams StorageMergeTree::spreadMarkRangesAmongThreads( 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)); + prewhere_actions, prewhere_column, true)); } if (streams.size() == 1) @@ -494,7 +494,7 @@ BlockInputStreams StorageMergeTree::spreadMarkRangesAmongThreadsFinal( 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); + prewhere_actions, prewhere_column, true); to_collapse.push_back(new ExpressionBlockInputStream(source_stream, primary_expr)); } @@ -1098,7 +1098,7 @@ void StorageMergeTree::mergeParts(Poco::SharedPtr & 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)); + StoragePtr(), false, NULL, "", true), primary_expr)); } /// Порядок потоков важен: при совпадении ключа элементы идут в порядке номера потока-источника. @@ -1301,7 +1301,7 @@ void StorageMergeTree::alter(const ASTAlterQuery::Parameters & params) { 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); + DEFAULT_MERGE_BLOCK_SIZE, column_name, *this, part, ranges, StoragePtr(), false, NULL, "", false), expr); MergedColumnOnlyOutputStream out(*this, full_path + part->name + '/', true); out.writePrefix(); From 3c4c400e685032a2a1c4f93ae2e9ae76335aef10 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 12 Mar 2014 21:12:09 +0400 Subject: [PATCH 06/51] dbms: formatAST: better [#METR-2944]. --- dbms/src/Parsers/formatAST.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dbms/src/Parsers/formatAST.cpp b/dbms/src/Parsers/formatAST.cpp index 3fcfe94ae1e..cded21c44c3 100644 --- a/dbms/src/Parsers/formatAST.cpp +++ b/dbms/src/Parsers/formatAST.cpp @@ -224,11 +224,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) From 34ef806287031068b3065490137378ba4cd4d482 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 12 Mar 2014 21:15:14 +0400 Subject: [PATCH 07/51] dbms: fixed error [#METR-10377]. --- .../Interpreters/InterpreterSelectQuery.cpp | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/dbms/src/Interpreters/InterpreterSelectQuery.cpp b/dbms/src/Interpreters/InterpreterSelectQuery.cpp index de920019cbe..2250337710d 100644 --- a/dbms/src/Interpreters/InterpreterSelectQuery.cpp +++ b/dbms/src/Interpreters/InterpreterSelectQuery.cpp @@ -433,11 +433,31 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu Names required_columns = query_analyzer->getRequiredColumns(); if (table_function_storage) - table = table_function_storage; /// Если в запросе была указана табличная функция, данные читаем из нее. + { + /// Если в запросе была указана табличная функция, данные читаем из нее. + table = table_function_storage; + } else if (!query.table || !dynamic_cast(&*query.table)) + { + /// Запрос из обычной таблицы или без секции FROM. table = getTable(); + } else if (dynamic_cast(&*query.table)) - interpreter_subquery = new InterpreterSelectQuery(query.table, context, required_columns, QueryProcessingStage::Complete, subquery_depth + 1); + { + /** Для подзапроса не действуют ограничения на максимальный размер результата. + * Так как результат поздапроса - ещё не результат всего запроса. + */ + 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, то все запросы выполняем с сэмплингом /// если таблица не поддерживает сэмплинг получим исключение From b8218d64b8088275f049ed75e9adb9c92be166ed Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 12 Mar 2014 22:20:03 +0400 Subject: [PATCH 08/51] dbms: Fixed accidentially introduced error [#METR-10377]. --- dbms/include/DB/Columns/ColumnSet.h | 3 +++ dbms/include/DB/Common/VirtualColumnUtils.h | 1 - dbms/src/Interpreters/ExpressionAnalyzer.cpp | 2 -- 3 files changed, 3 insertions(+), 3 deletions(-) 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..c6a2328eb61 100644 --- a/dbms/include/DB/Common/VirtualColumnUtils.h +++ b/dbms/include/DB/Common/VirtualColumnUtils.h @@ -10,7 +10,6 @@ #include #include #include -#include #include namespace DB 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; } From 36a86a4fc94ae2ae11f7c3552955cbab25f849d9 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 13 Mar 2014 16:48:07 +0400 Subject: [PATCH 09/51] Merge --- .../DB/Storages/MergeTree/DiskSpaceMonitor.h | 87 ++ .../MergeTree/MergeTreeBlockInputStream.h | 80 +- .../MergeTree/MergeTreeBlockOutputStream.h | 31 + .../DB/Storages/MergeTree/MergeTreeData.h | 421 +++----- .../Storages/MergeTree/MergeTreeDataMerger.h | 44 + .../MergeTree/MergeTreeDataSelectExecutor.h | 78 ++ ...ckOutputStream.h => MergeTreeDataWriter.h} | 30 +- .../DB/Storages/MergeTree/MergeTreeReader.h | 30 +- .../MergeTree/MergedBlockOutputStream.h | 22 +- dbms/include/DB/Storages/StorageMergeTree.h | 52 +- dbms/include/DB/Storages/StoragePtr.h | 1 + .../Storages/MergeTree/DiskSpaceMonitor.cpp | 9 + dbms/src/Storages/MergeTree/MergeTreeData.cpp | 923 ++---------------- .../MergeTree/MergeTreeDataMerger.cpp | 339 +++++++ .../MergeTree/MergeTreeDataSelectExecutor.cpp | 472 +++++++++ .../MergeTree/MergeTreeDataWriter.cpp | 0 dbms/src/Storages/StorageMergeTree.cpp | 138 ++- 17 files changed, 1524 insertions(+), 1233 deletions(-) create mode 100644 dbms/include/DB/Storages/MergeTree/DiskSpaceMonitor.h create mode 100644 dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h create mode 100644 dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h create mode 100644 dbms/include/DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h rename dbms/include/DB/Storages/MergeTree/{MergeTreeDataBlockOutputStream.h => MergeTreeDataWriter.h} (86%) create mode 100644 dbms/src/Storages/MergeTree/DiskSpaceMonitor.cpp create mode 100644 dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp create mode 100644 dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp create mode 100644 dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp 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 18cf15862dd..c7ae24d26cf 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h @@ -3,7 +3,6 @@ #include #include #include - #include @@ -17,6 +16,7 @@ public: /// Параметры storage_ и owned_storage разделены, чтобы можно было сделать поток, не владеющий своим storage /// (например, поток, сливаящий куски). В таком случае сам storage должен следить, чтобы не удалить данные, пока их читают. MergeTreeBlockInputStream(const String & path_, /// Путь к куску + MergeTreeData::LockedTableStructurePtr structure_lock_, size_t block_size_, const Names & column_names_, MergeTreeData & storage_, const MergeTreeData::DataPartPtr & owned_data_part_, const MarkRanges & mark_ranges_, StoragePtr owned_storage, bool use_uncompressed_cache_, @@ -26,7 +26,9 @@ public: 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")), + structure_lock(structure_lock_) { std::reverse(remaining_mark_ranges.begin(), remaining_mark_ranges.end()); @@ -48,7 +50,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); } @@ -72,70 +74,6 @@ public: return res.str(); } - /// Получает набор диапазонов засечек, вне которых не могут находиться ключи из заданного диапазона. - static MarkRanges markRangesFromPkRange( - const MergeTreeData::DataPart::Index & index, - MergeTreeData & 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) {} @@ -150,9 +88,9 @@ protected: if (!reader) { UncompressedCache * uncompressed_cache = use_uncompressed_cache ? storage.context.getUncompressedCache() : NULL; - reader = new MergeTreeReader(path, column_names, uncompressed_cache, storage); + reader = new MergeTreeReader(path, column_names, uncompressed_cache, storage, structure_lock); if (prewhere_actions) - pre_reader = new MergeTreeReader(path, pre_column_names, uncompressed_cache, storage); + pre_reader = new MergeTreeReader(path, pre_column_names, uncompressed_cache, storage, structure_lock); } if (prewhere_actions) @@ -332,6 +270,10 @@ private: ExpressionActionsPtr prewhere_actions; String prewhere_column; bool remove_prewhere_column; + + Logger * log; + + MergeTreeData::LockedTableStructurePtr structure_lock; }; } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h new file mode 100644 index 00000000000..0b7f2a7782e --- /dev/null +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace DB +{ + +class MergeTreeBlockOutputStream : public IBlockOutputStream +{ +public: + MergeTreeBlockOutputStream(StoragePtr storage_) + : IBlockOutputStream(storage_), storage(dynamic_cast(*storage_)), + structure(storage.data.getLockedStructure(true)) {} + + void write(const Block & block) + { + BlocksList part_blocks = storage.writer.splitBlockIntoParts(block, structure); + for (const Block & current_block : part_blocks) + { + UInt64 temp_index = storage.increment.get(); + String temp_name = storage.writer.writeTempPart(current_block, temp_index, structure); + storage.writer.renameTempPart(temp_name, &storage.increment); + } + } + +private: + StorageMergeTree & storage; + MergeTreeData::LockedTableStructurePtr structure; +}; + +} diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index ae1d323d7ba..d8ad6cd0893 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -47,18 +47,11 @@ namespace DB /// NOTE: Следующее пока не правда. Сейчас тут практически весь StorageMergeTree. Лишние части нужно перенести отсюда в StorageMergeTree. -/** Этот класс отвечает за хранение локальных данных всех *MergeTree движков. - * - Поддерживает набор кусков на диске. Синхронизирует доступ к ним, поддерживает в памяти их список. - * - Полностью выполняет запросы SELECT. - * - Сам не принимает решений об изменении данных. - * - Умеет дававть рекомендации: - * - Говорить, какие куски нужно удалить, потому что они покрыты другими кусками. - * - Выбирать набор кусков для слияния. - * При этом нужна внешняя информация о том, какие куски с какими разрешено объединять. - * - Умеет изменять данные по запросу: - * - Записать новый кусок с данными из блока. - * - Слить указанные куски. - * - Сделать ALTER. +/** Этот класс хранит список кусков и параметры структуры данных. + * Для чтения и изменения данных используются отдельные классы: + * - MergeTreeDataReader + * - MergeTreeDataWriter + * - MergeTreeDataMerger */ struct MergeTreeSettings @@ -102,169 +95,9 @@ struct MergeTreeSettings time_t old_parts_lifetime = 5 * 60; }; -/// Пара засечек, определяющая диапазон строк в куске. Именно, диапазон имеет вид [begin * index_granularity, end * index_granularity). -struct MarkRange +class MergeTreeData { - size_t begin; - size_t end; - - MarkRange() {} - MarkRange(size_t begin_, size_t end_) : begin(begin_), end(end_) {} -}; - -typedef std::vector MarkRanges; - - -class MergeTreeData : public IColumnsDeclaration -{ -friend class MergeTreeReader; -friend class MergeTreeBlockInputStream; -friend class MergeTreeDataBlockOutputStream; -friend class IMergedBlockOutputStream; -friend class MergedBlockOutputStream; -friend class MergedColumnOnlyOutputStream; - public: - /// Режим работы. См. выше. - enum Mode - { - Ordinary, - Collapsing, - Summing, - }; - - /** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце), - * (корректность имён и путей не проверяется) - * состоящую из указанных столбцов. - * - * primary_expr_ast - выражение для сортировки; - * date_column_name - имя столбца с датой; - * index_granularity - на сколько строчек пишется одно значение индекса. - * - * owning_storage используется только чтобы отдавать его возвращаемым потокам блоков. - */ - MergeTreeData( StorageWeakPtr owning_storage_, 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_, - Mode mode_, - const String & sign_column_, - const MergeTreeSettings & settings_); - - void shutdown(); - ~MergeTreeData(); - - std::string 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); - } - } - - 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; } - - const NamesAndTypesList & getColumnsList() const { return *columns; } - - /** При чтении, выбирается набор кусков, покрывающий нужный диапазон индекса. - */ - 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); - - /** При записи, данные сортируются и пишутся в новые куски. - */ - BlockOutputStreamPtr write(ASTPtr query); - - /** Выполнить очередной шаг объединения кусков. - */ - bool optimize() - { - merge(1, false, true); - return true; - } - - void dropImpl(); - - 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(MergeTreeData & 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); - } - -private: - StorageWeakPtr owning_storage; - - 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; - - MergeTreeSettings settings; - - ExpressionActionsPtr primary_expr; - SortDescription sort_descr; - Block primary_key_sample; - - Increment increment; - - Logger * log; - volatile bool shutdown_called; - - /// Регулярное выражение соответсвующее названию директории с кусочками - Poco::RegularExpression file_name_regexp; - /// Описание куска с данными. struct DataPart { @@ -309,7 +142,7 @@ private: return res; } - void remove() const + void remove() { String from = storage.full_path + name + "/"; String to = storage.full_path + "tmp2_" + name + "/"; @@ -389,125 +222,183 @@ private: typedef SharedPtr DataPartPtr; struct DataPartPtrLess { bool operator() (const DataPartPtr & lhs, const DataPartPtr & rhs) const { return *lhs < *rhs; } }; typedef std::set DataParts; + typedef std::vector DataPartsVector; - struct RangesInDataPart + + /// Режим работы. См. выше. + enum Mode { - DataPartPtr data_part; - MarkRanges ranges; - - RangesInDataPart() {} - - RangesInDataPart(DataPartPtr data_part_) - : data_part(data_part_) - { - } + Ordinary, + Collapsing, + Summing, }; - /// Пока существует, помечает части как currently_merging и пересчитывает общий объем сливаемых данных. - /// Вероятно, что части будут помечены заранее. - class CurrentlyMergingPartsTagger + /** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце), + * (корректность имён и путей не проверяется) + * состоящую из указанных столбцов. + * + * 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_); + + /** + * owning_storage используется только чтобы отдавать его потокам блоков. + */ + void setOwningStorage(StoragePtr storage) { owning_storage = storage; } + + 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(); + + /** Возвращает копию списка, чтобы снаружи можно было не заботиться о блокировках. + */ + DataParts getDataParts(); + + /** Удаляет куски old_parts и добавляет кусок new_part. Если какого-нибудь из удаляемых кусков нет, бросает исключение. + */ + void replaceParts(DataPartsVector old_parts, DataPartPtr new_part); + + /** Удалить неактуальные куски. + */ + void clearOldParts(); + + /** После вызова dropAllData больше ничего вызывать нельзя. + */ + void dropAllData(); + + /** Поменять путь к директории с данными. Предполагается, что все данные из старой директории туда перенесли. + * Нужно вызывать под залоченным lockStructure(). + */ + void setPath(const String & full_path); + + /** Метод ALTER позволяет добавлять и удалять столбцы и менять их тип. + * Нужно вызывать под залоченным lockStructure(). + * TODO: сделать, чтобы ALTER MODIFY не лочил чтения надолго. Для этого есть parts_writing_lock. + */ + void alter(const ASTAlterQuery::Parameters & params); + + static String getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level); + + /** Изменяемая часть описания таблицы. Содержит лок, запрещающий изменение описания таблицы. + * Если в течение какой-то операции структура таблицы должна оставаться неизменной, нужно держать один лок на все ее время. + * Например, нужно держать такой лок на время всего запроса SELECT или INSERT и на все время слияния набора кусков + * (но между выбором кусков для слияния и их слиянием структура таблицы может измениться). + * NOTE: можно перенести сюда другие поля, чтобы сделать их динамически изменяемыми. + * Например, index_granularity, sign_column, primary_expr_ast. + */ + class LockedTableStructure : public IColumnsDeclaration { public: - std::vector parts; - Poco::FastMutex & data_mutex; + const NamesAndTypesList & getColumnsList() const { return *data.columns; } - CurrentlyMergingPartsTagger(const std::vector & parts_, Poco::FastMutex & data_mutex_) : parts(parts_), data_mutex(data_mutex_) - { - /// Здесь не лочится мьютекс, так как конструктор вызывается внутри selectPartsToMerge, где он уже залочен - /// Poco::ScopedLock lock(data_mutex); - for (size_t i = 0; i < parts.size(); ++i) - { - parts[i]->currently_merging = true; - MergeTreeData::total_size_of_currently_merging_parts += parts[i]->size_in_bytes; - } - } + String getFullPath() const { return data.full_path; } - ~CurrentlyMergingPartsTagger() - { - Poco::ScopedLock lock(data_mutex); - for (size_t i = 0; i < parts.size(); ++i) - { - parts[i]->currently_merging = false; - MergeTreeData::total_size_of_currently_merging_parts -= parts[i]->size_in_bytes; - } - } + private: + friend class MergeTreeData; + + const MergeTreeData & data; + Poco::SharedPtr parts_lock; + Poco::ScopedReadRWLock structure_lock; + + LockedTableStructure(const MergeTreeData & data_, bool lock_writing) + : data(data_), parts_lock(lock_writing ? new Poco::ScopedReadRWLock(data.parts_writing_lock) : nullptr), structure_lock(data.table_structure_lock) {} }; - /// Сумарный размер currently_merging кусочков в байтах. - /// Нужно чтобы оценить количество места на диске, которое может понадобится для завершения этих мерджей. - static size_t total_size_of_currently_merging_parts; + typedef Poco::SharedPtr LockedTableStructurePtr; - typedef std::vector RangesInDataParts; + /** Если в рамках этого лока будут добавлены или удалены куски данных, обязательно указать will_modify_parts=true. + * Это возьмет дополнительный лок, не позволяющий начать ALTER MODIFY. + */ + LockedTableStructurePtr getLockedStructure(bool will_modify_parts) const + { + return new LockedTableStructure(*this, will_modify_parts); + } - /** @warning Если берете насколько блокировок, то берите их везде в одинаковом порядке - в том же как они написаны в этом файле */ - /** Взятие этого лока на запись, запрещает мердж */ - Poco::RWLock merge_lock; + typedef Poco::SharedPtr TableStructureWriteLockPtr; - /** Взятие этого лока на запись, запрещает запись */ - Poco::RWLock write_lock; + TableStructureWriteLockPtr lockStructure() { return new Poco::ScopedWriteRWLock(table_structure_lock); } - /** Взятие этого лока на запись, запрещает чтение */ - Poco::RWLock read_lock; + /// Эти поля не нужно изменять снаружи. NOTE нужно спрятать их и сделать методы get*. + const Context & context; + ASTPtr primary_expr_ast; + String date_column_name; + ASTPtr sampling_expression; + size_t index_granularity; + + /// Режим работы - какие дополнительные действия делать при мердже. + Mode mode; + /// Для схлопывания записей об изменениях, если используется Collapsing режим работы. + String sign_column; + + MergeTreeSettings settings; + + ExpressionActionsPtr primary_expr; + SortDescription sort_descr; + Block primary_key_sample; + + StorageWeakPtr owning_storage; + +private: + String full_path; + + NamesAndTypesListPtr columns; + + Logger * log; + volatile bool shutdown_called; + + /// Регулярное выражение соответсвующее названию директории с кусочками + Poco::RegularExpression file_name_regexp; + + /// Брать следующие два лока всегда нужно в этом порядке. + + /** Берется на чтение на все время запроса INSERT и на все время слияния кусков. Берется на запись на все время ALTER MODIFY. + * + * Формально: + * Ввзятие на запись гарантирует, что: + * 1) множество кусков не изменится, пока лок жив, + * 2) все операции над множеством кусков после отпускания лока будут основаны на структуре таблицы на момент после отпускания лока. + * Взятие на чтение обязательно, если будет добавляться или удалять кусок. + * Брать на чтение нужно до получения структуры таблицы, которой будет соответствовать добавляемый кусок. + */ + mutable Poco::RWLock parts_writing_lock; + + /** Лок для множества столбцов и пути к таблице. Берется на запись в RENAME и ALTER (для ALTER MODIFY ненадолго). + * + * Взятие этого лока на запись - строго более "сильная" операция, чем взятие parts_writing_lock на запись. + * То есть, если этот лок взят на запись, о parts_writing_lock можно не заботиться. + * parts_writing_lock нужен только для случаев, когда не хочется брать table_structure_lock надолго. + */ + mutable Poco::RWLock table_structure_lock; /** Актуальное множество кусков с данными. */ DataParts data_parts; Poco::FastMutex data_parts_mutex; /** Множество всех кусков с данными, включая уже слитые в более крупные, но ещё не удалённые. Оно обычно небольшое (десятки элементов). - * Ссылки на кусок есть отсюда, из списка актуальных кусков, и из каждого потока чтения, который его сейчас использует. + * Ссылки на кусок есть отсюда, из списка актуальных кусков и из каждого потока чтения, который его сейчас использует. * То есть, если количество ссылок равно 1 - то кусок не актуален и не используется прямо сейчас, и его можно удалить. */ DataParts all_data_parts; Poco::FastMutex all_data_parts_mutex; - static String getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level); - - 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 если имя директории совпадает с форматом имени директории кусочков diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h new file mode 100644 index 00000000000..028bee5acd2 --- /dev/null +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +namespace DB +{ + +/** Умеет выбирать куски для слияния и сливать их. + * Требуется внешний механизм координации слияний со вставками и другими слияниями, обеспечивающий: + * - Куски, между которыми еще может появиться новый кусок, нельзя сливать. См. METR-7001. + * - Кусок, который уже сливаются с кем-то в одном месте, нельзя начать сливать в кем-то другим в другом месте. + */ +class MergeTreeDataMerger +{ +public: + MergeTreeDataMerger(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataMerger")), canceled(false) {} + + /// Если merge_anything_for_old_months, для кусков за прошедшие месяцы снимается ограничение на соотношение размеров. + bool selectPartsToMerge( + MergeTreeData::DataPartsVector & what, + size_t available_disk_space, + bool merge_anything_for_old_months, + bool aggressive); + + /// Возвращает название нового куска. Если слияние отменили, возвращает пустую строку. + 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..72b03c24952 --- /dev/null +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h @@ -0,0 +1,78 @@ +#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, + const MergeTreeData::LockedTableStructurePtr & structure); + + 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, + const MergeTreeData::LockedTableStructurePtr & structure); + + /// Создать выражение "Sign == 1". + void createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column, + const MergeTreeData::LockedTableStructurePtr & structure); + + MarkRanges markRangesFromPkRange(const MergeTreeData::DataPart::Index & index, PKCondition & key_condition); +}; + +} diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h similarity index 86% rename from dbms/include/DB/Storages/MergeTree/MergeTreeDataBlockOutputStream.h rename to dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h index 8cdad62fa9d..c0766d89f23 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeDataBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h @@ -13,6 +13,34 @@ namespace DB { +/** Записывает новые куски с данными в merge-дерево. + */ +class MergeTreeDataWriter +{ +public: + MergeTreeDataWriter(MergeTreeData & data_) : data(data_) {} + + /** Разбивает блок на блоки, каждый из которых нужно записать в отдельный кусок. + * (читай: разбивает строки по месяцам) + * Работает детерминированно: если отдать на вход такой же блок, на выходе получатся такие же блоки в таком же порядке. + */ + BlocksList splitBlockIntoParts(const Block & block, const MergeTreeData::LockedTableStructurePtr & structure); + + /** Все строки должны относиться к одному месяцу. Возвращает название временного куска. + * temp_index - значение left и right для нового куска. Можно будет изменить при переименовании. + * NOTE потом понадобится возвращать отсюда структуру с контрольными суммами и размерами. + */ + String writeTempPart(const Block & block, UInt64 temp_index, const MergeTreeData::LockedTableStructurePtr & structure); + + /** Переименовывает временный кусок в постоянный и добавляет его в рабочий набор. + * Если increment!=nullptr, индекс куска бурется из инкремента. Иначе индекс куска не меняется. + */ + String renameTempPart(const String & temp_name, Increment * increment); + +private: + MergeTreeData & data; +}; +#if 0 class MergeTreeDataBlockOutputStream : public IBlockOutputStream { public: @@ -294,5 +322,5 @@ private: } } }; - +#endif } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h index 01342700ccc..3c33a262244 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h @@ -3,10 +3,13 @@ #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,11 +39,13 @@ class MergeTreeReader { public: MergeTreeReader(const String & path_, /// Путь к куску - const Names & columns_names_, bool use_uncompressed_cache_, MergeTreeData & storage_) - : path(path_), column_names(columns_names_), use_uncompressed_cache(use_uncompressed_cache_), storage(storage_) + const Names & columns_names_, bool use_uncompressed_cache_, MergeTreeData & storage_, + MergeTreeData::LockedTableStructurePtr structure_) + : path(path_), column_names(columns_names_), use_uncompressed_cache(use_uncompressed_cache_), storage(storage_), + structure(structure_) { for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it) - addStream(*it, *storage.getDataTypeByName(*it)); + addStream(*it, *structure->getDataTypeByName(*it)); } /** Если столбцов нет в блоке, добавляет их, если есть - добавляет прочитанные значения к ним в конец. @@ -59,7 +78,7 @@ public: ColumnWithNameAndType column; column.name = *it; - column.type = storage.getDataTypeByName(*it); + column.type = structure->getDataTypeByName(*it); if (append) column.column = res.getByName(column.name).column; @@ -114,7 +133,7 @@ public: { ColumnWithNameAndType column; column.name = *it; - column.type = storage.getDataTypeByName(*it); + column.type = structure->getDataTypeByName(*it); /** Нужно превратить константный столбец в полноценный, так как в части блоков (из других кусков), * он может быть полноценным (а то интерпретатор может посчитать, что он константный везде). @@ -221,6 +240,7 @@ private: Names column_names; bool use_uncompressed_cache; MergeTreeData & storage; + MergeTreeData::LockedTableStructurePtr structure; 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 e91a0d23883..a5af398ceef 100644 --- a/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h @@ -196,25 +196,26 @@ protected: class MergedBlockOutputStream : public IMergedBlockOutputStream { public: - MergedBlockOutputStream(MergeTreeData & storage_, + MergedBlockOutputStream(MergeTreeData & storage_, MergeTreeData::LockedTableStructurePtr structure_, UInt16 min_date, UInt16 max_date, UInt64 min_part_id, UInt64 max_part_id, UInt32 level) - : IMergedBlockOutputStream(storage_), marks_count(0) + : IMergedBlockOutputStream(storage_), structure(structure_), marks_count(0) { part_name = storage.getPartName( 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 = structure->getFullPath() + "tmp_" + part_name + "/"; + part_res_path = structure->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 = structure->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(); @@ -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,9 @@ public: } private: + MergeTreeData::LockedTableStructurePtr structure; + NamesAndTypesList columns_list; + String part_name; String part_tmp_path; String part_res_path; diff --git a/dbms/include/DB/Storages/StorageMergeTree.h b/dbms/include/DB/Storages/StorageMergeTree.h index 366716bfd1c..f9c692d3211 100644 --- a/dbms/include/DB/Storages/StorageMergeTree.h +++ b/dbms/include/DB/Storages/StorageMergeTree.h @@ -1,6 +1,9 @@ #pragma once #include +#include "MergeTree/MergeTreeDataSelectExecutor.h" +#include "MergeTree/MergeTreeDataWriter.h" +#include "MergeTree/MergeTreeDataMerger.h" namespace DB { @@ -9,6 +12,8 @@ namespace DB */ class StorageMergeTree : public IStorage { +friend class MergeTreeBlockOutputStream; + public: /** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце), * (корректность имён и путей не проверяется) @@ -36,13 +41,13 @@ public: return data.getModePrefix() + "MergeTree"; } - std::string getTableName() const { return data.getTableName(); } + std::string getTableName() const { return name; } 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 data.getColumnsList(); } + const NamesAndTypesList & getColumnsList() const { return data.getLockedStructure(false)->getColumnsList(); } BlockInputStreams read( const Names & column_names, @@ -58,7 +63,8 @@ public: */ bool optimize() { - return data.optimize(); + merge(1, false, true); + return true; } void dropImpl(); @@ -70,12 +76,27 @@ public: /// Например если параллельно с INSERT выполнить ALTER, то ALTER выполниться, а INSERT бросит исключение void alter(const ASTAlterQuery::Parameters & params); - typedef MergeTreeData::BigLockPtr BigLockPtr; + typedef MergeTreeData::TableStructureWriteLockPtr BigLockPtr; - BigLockPtr lockAllOperations() { return data.lockAllOperations(); } + BigLockPtr lockAllOperations() + { + return data.lockStructure(); + } private: + String path; + String name; + String full_path; + Increment increment; + MergeTreeData data; + MergeTreeDataSelectExecutor reader; + MergeTreeDataWriter writer; + MergeTreeDataMerger merger; + + Logger * log; + + volatile bool shutdown_called; StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_, const Context & context_, @@ -83,9 +104,24 @@ private: const String & date_column_name_, const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается. size_t index_granularity_, - MergeTreeData::Mode mode_ = MergeTreeData::Ordinary, - const String & sign_column_ = "", - const MergeTreeSettings & settings_ = MergeTreeSettings()); + MergeTreeData::Mode mode_, + const String & sign_column_, + const MergeTreeSettings & settings_); + + + + /** Определяет, какие куски нужно объединять, и запускает их слияние в отдельном потоке. Если 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); + + /// Дождаться, пока фоновые потоки закончат слияния. + void joinMergeThreads(); + + Poco::SharedPtr merge_threads; }; } diff --git a/dbms/include/DB/Storages/StoragePtr.h b/dbms/include/DB/Storages/StoragePtr.h index 89aea28cb2d..96bd2a9a489 100644 --- a/dbms/include/DB/Storages/StoragePtr.h +++ b/dbms/include/DB/Storages/StoragePtr.h @@ -88,6 +88,7 @@ public: class StorageWeakPtr { public: + StorageWeakPtr() {} StorageWeakPtr(const StoragePtr & p) : ptr(p.ptr) {} StoragePtr lock() 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 index 230a1512e7e..ee67b5bd3f4 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -47,7 +46,6 @@ #include #include -#include #include #include @@ -57,10 +55,21 @@ namespace DB { -size_t MergeTreeData::total_size_of_currently_merging_parts = 0; +static String lastTwoPathComponents(const 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( - StorageWeakPtr owning_storage_, const String & path_, const String & name_, NamesAndTypesListPtr columns_, + const String & full_path_, NamesAndTypesListPtr columns_, const Context & context_, ASTPtr & primary_expr_ast_, const String & date_column_name_, const ASTPtr & sampling_expression_, @@ -68,19 +77,15 @@ MergeTreeData::MergeTreeData( Mode mode_, const String & sign_column_, const MergeTreeSettings & settings_) - : owning_storage(owning_storage_), path(path_), name(name_), full_path(path + escapeForFileName(name) + '/'), columns(columns_), - context(context_), primary_expr_ast(primary_expr_ast_->clone()), + : 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("MergeTreeData: " + name)), shutdown_called(false), + full_path(full_path_), columns(columns_), + log(&Logger::get("MergeTreeData: " + lastTwoPathComponents(full_path))), file_name_regexp("^(\\d{8})_(\\d{8})_(\\d+)_(\\d+)_(\\d+)") { - 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; - /// создаём директорию, если её нет Poco::File(full_path).createDirectories(); @@ -99,430 +104,34 @@ MergeTreeData::MergeTreeData( 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 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); } - increment.fixIfBroken(max_part_id); + return max_part_id; } -void MergeTreeData::shutdown() +std::string MergeTreeData::getModePrefix() const { - if (shutdown_called) - return; - shutdown_called = true; + switch (mode) + { + case Ordinary: return ""; + case Collapsing: return "Collapsing"; + case Summing: return "Summing"; - joinMergeThreads(); + default: + throw Exception("Unknown mode of operation for MergeTreeData: " + toString(mode), ErrorCodes::LOGICAL_ERROR); + } } -MergeTreeData::~MergeTreeData() -{ - shutdown(); -} - - -BlockOutputStreamPtr MergeTreeData::write(ASTPtr query) -{ - return new MergeTreeDataBlockOutputStream(*this, owning_storage); -} - - -BlockInputStreams MergeTreeData::read( - const Names & column_names_to_return, - 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; -} - - -/// Примерно поровну распределить засечки между потоками. -BlockInputStreams MergeTreeData::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( - full_path + part.data_part->name + '/', max_block_size, column_names, *this, - part.data_part, part.ranges, owning_storage, 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, owning_storage, 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; -} - - -/// Распределить засечки между потоками и сделать, чтобы в ответе (почти) все данные были сколлапсированы (модификатор FINAL). -BlockInputStreams MergeTreeData::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( - full_path + part.data_part->name + '/', max_block_size, column_names, *this, - part.data_part, part.ranges, owning_storage, 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; -} - - -void MergeTreeData::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 = 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(); -} String MergeTreeData::getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level) @@ -622,7 +231,7 @@ void MergeTreeData::loadDataParts() if (part->level == 0) { /// Восстановить куски нулевого уровня невозможно. - LOG_ERROR(log, "Removing broken part " << path + file_name << " because is't impossible to repair."); + LOG_ERROR(log, "Removing broken part " << full_path + file_name << " because is't impossible to repair."); part->remove(); } else @@ -741,454 +350,13 @@ void MergeTreeData::clearOldParts() } } - -void MergeTreeData::merge(size_t iterations, bool async, bool aggressive) +void MergeTreeData::setPath(const String & new_full_path) { - bool while_can = false; - if (iterations == 0) - { - while_can = true; - iterations = settings.merging_threads; - } - - for (size_t i = 0; i < iterations; ++i) - merge_threads->schedule(boost::bind(&MergeTreeData::mergeThread, this, while_can, aggressive)); - - if (!async) - joinMergeThreads(); -} - - -void MergeTreeData::mergeThread(bool while_can, bool aggressive) -{ - try - { - while (!shutdown_called) - { - /// Удаляем старые куски. На случай, если в слиянии что-то сломано, и из следующего блока вылетит исключение. - clearOldParts(); - - { - Poco::ScopedReadRWLock lock(merge_lock); - - /// К концу этого логического блока должен быть вызван деструктор, чтобы затем корректно определить удаленные куски - Poco::SharedPtr what; - - if (!selectPartsToMerge(what, false, aggressive) && !selectPartsToMerge(what, true, aggressive)) - break; - - mergeParts(what); - } - - if (shutdown_called) - break; - - /// Удаляем куски, которые мы только что сливали. - clearOldParts(); - - if (!while_can) - break; - } - } - catch (const Exception & e) - { - LOG_ERROR(log, "Code: " << e.code() << ". " << e.displayText() << std::endl - << std::endl - << "Stack trace:" << std::endl - << e.getStackTrace().toString()); - } - catch (const Poco::Exception & e) - { - LOG_ERROR(log, "Poco::Exception: " << e.code() << ". " << e.displayText()); - } - catch (const std::exception & e) - { - LOG_ERROR(log, "std::exception: " << e.what()); - } - catch (...) - { - LOG_ERROR(log, "Unknown exception"); - } -} - - -void MergeTreeData::joinMergeThreads() -{ - LOG_DEBUG(log, "Waiting for merge threads to finish."); - 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 MergeTreeData::selectPartsToMerge(Poco::SharedPtr & what, bool merge_anything_for_old_months, bool aggressive) -{ - 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 MergeTreeData::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(); - - MergeTreeData::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 MergeTreeData: " + 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 MergeTreeData::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 MergeTreeData::dropImpl() +void MergeTreeData::dropAllData() { - joinMergeThreads(); - - Poco::ScopedLock lock(data_parts_mutex); - Poco::ScopedLock lock_all(all_data_parts_mutex); - data_parts.clear(); all_data_parts.clear(); @@ -1252,7 +420,7 @@ void MergeTreeData::createConvertExpression(const String & in_column_name, const void MergeTreeData::alter(const ASTAlterQuery::Parameters & params) { - if (params.type == ASTAlterQuery::MODIFY) + /*if (params.type == ASTAlterQuery::MODIFY) { { typedef std::vector PartsList; @@ -1360,7 +528,7 @@ void MergeTreeData::alter(const ASTAlterQuery::Parameters & params) { String column_name = dynamic_cast(*params.column).name; removeColumnFiles(column_name); - } + }*/ } @@ -1465,4 +633,27 @@ Strings MergeTreeData::tryRestorePart(const String & path, const String & file_n 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])); +} + +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..66ff9e0542f --- /dev/null +++ b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp @@ -0,0 +1,339 @@ +#include +#include +#include +#include +#include +#include +#include + + +namespace DB +{ + +/// Не будем соглашаться мерджить куски, если места на диске менее чем во столько раз больше суммарного размера кусков. +static const double DISK_USAGE_COEFFICIENT = 1.5; + + +/// Выбираем отрезок из не более чем 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) +{ + 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; + + /// Если есть активный мердж крупных кусков, то ограничиваемся мерджем только маленьких частей. + for (MergeTreeData::DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it) + { + if ((*it)->currently_merging && (*it)->size * data.index_granularity > 25 * 1024 * 1024) + { + cur_max_rows_to_merge_parts = data.settings.max_rows_to_merge_parts_second; + break; + } + } + + /// Левый конец отрезка. + 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->currently_merging) + continue; + + /// Кусок достаточно мал или слияние "агрессивное". + 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 (++jt; jt != data_parts.end() && cur_len < static_cast(data.settings.max_parts_to_merge_at_once); ++jt) + { + const MergeTreeData::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 * 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) + { + 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 - 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); + + auto structure = data.getLockedStructure(true); + + Names all_column_names; + NamesAndTypesList columns_list = structure->getColumnsList(); + for (const auto & it : columns_list) + all_column_names.push_back(it.first); + + DateLUTSingleton & date_lut = DateLUTSingleton::instance(); + + MergeTreeData::DataPartPtr new_data_part = new MergeTreeData::DataPart(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( + structure->getFullPath() + parts[i]->name + '/', structure, DEFAULT_MERGE_BLOCK_SIZE, all_column_names, data, parts[i], ranges, + StoragePtr(), false, NULL, ""), data.primary_expr)); + } + + /// Порядок потоков важен: при совпадении ключа элементы идут в порядке номера потока-источника. + /// В слитом куске строки с одинаковым ключом должны идти в порядке возрастания идентификатора исходного куска, то есть (примерного) возрастания времени вставки. + BlockInputStreamPtr merged_stream; + + switch (data.mode) + { + case MergeTreeData::Ordinary: + merged_stream = new MergingSortedBlockInputStream(src_streams, data.sort_descr, DEFAULT_MERGE_BLOCK_SIZE); + break; + + case MergeTreeData::Collapsing: + merged_stream = new CollapsingSortedBlockInputStream(src_streams, data.sort_descr, data.sign_column, DEFAULT_MERGE_BLOCK_SIZE); + break; + + case MergeTreeData::Summing: + merged_stream = new SummingSortedBlockInputStream(src_streams, data.sort_descr, 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, structure, + 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; +} + +} diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp new file mode 100644 index 00000000000..0508f653128 --- /dev/null +++ b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -0,0 +1,472 @@ +#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) +{ + MergeTreeData::LockedTableStructurePtr structure = data.getLockedStructure(false); + + structure->check(column_names_to_return); + processed_stage = QueryProcessingStage::FetchColumns; + + PKCondition key_condition(query, data.context, structure->getColumnsList(), data.sort_descr); + PKCondition date_condition(query, data.context, structure->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.primary_expr->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, structure->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, structure->getColumnsList()); + 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) + { + 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.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(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, + structure); + } + else + { + res = spreadMarkRangesAmongThreads( + parts_with_ranges, + threads, + column_names_to_read, + max_block_size, + settings.use_uncompressed_cache, + prewhere_actions, + prewhere_column, + structure); + } + + 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, + const MergeTreeData::LockedTableStructurePtr & structure) +{ + /// На всякий случай перемешаем куски. + 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( + structure->getFullPath() + part.data_part->name + '/', structure, max_block_size, column_names, data, + part.data_part, part.ranges, data.owning_storage, 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( + structure->getFullPath() + part.data_part->name + '/', structure, max_block_size, column_names, data, + part.data_part, ranges_to_get_from_part, data.owning_storage, 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, + const MergeTreeData::LockedTableStructurePtr & structure) +{ + 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, structure); + + 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( + structure->getFullPath() + part.data_part->name + '/', structure, max_block_size, column_names, data, + part.data_part, part.ranges, data.owning_storage, use_uncompressed_cache, + prewhere_actions, prewhere_column); + + to_collapse.push_back(new ExpressionBlockInputStream(source_stream, data.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, data.sort_descr, data.sign_column)); + + return res; +} + +void MergeTreeDataSelectExecutor::createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column, + const MergeTreeData::LockedTableStructurePtr & structure) +{ + 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, structure->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.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 > 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..e69de29bb2d diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index 2fce5fdd26e..0d16416009e 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -1,8 +1,12 @@ #include +#include +#include +#include namespace DB { + StorageMergeTree::StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_, const Context & context_, ASTPtr & primary_expr_ast_, @@ -12,8 +16,17 @@ StorageMergeTree::StorageMergeTree(const String & path_, const String & name_, N MergeTreeData::Mode mode_, const String & sign_column_, const MergeTreeSettings & settings_) - : data( thisPtr(), path_, name_, columns_, context_, primary_expr_ast_, date_column_name_, sampling_expression_, - index_granularity_,mode_, sign_column_, 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) +{ + merge_threads = new boost::threadpool::pool(data.settings.merging_threads); + + increment.fixIfBroken(data.getMaxDataPartIndex()); +} StoragePtr StorageMergeTree::create( const String & path_, const String & name_, NamesAndTypesListPtr columns_, @@ -26,17 +39,29 @@ StoragePtr StorageMergeTree::create( const String & sign_column_, const MergeTreeSettings & settings_) { - return (new StorageMergeTree( + StorageMergeTree * storage = new StorageMergeTree( path_, name_, columns_, context_, primary_expr_ast_, date_column_name_, - sampling_expression_, index_granularity_, mode_, sign_column_, settings_))->thisPtr(); + sampling_expression_, index_granularity_, mode_, sign_column_, settings_); + StoragePtr ptr = storage->thisPtr(); + storage->data.setOwningStorage(ptr); + return ptr; } void StorageMergeTree::shutdown() { - data.shutdown(); + if (shutdown_called) + return; + shutdown_called = true; + merger.cancelAll(); + + joinMergeThreads(); } -StorageMergeTree::~StorageMergeTree() {} + +StorageMergeTree::~StorageMergeTree() +{ + shutdown(); +} BlockInputStreams StorageMergeTree::read( const Names & column_names, @@ -46,27 +71,120 @@ BlockInputStreams StorageMergeTree::read( size_t max_block_size, unsigned threads) { - return data.read(column_names, query, settings, processed_stage, max_block_size, threads); + return reader.read(column_names, query, settings, processed_stage, max_block_size, threads); } BlockOutputStreamPtr StorageMergeTree::write(ASTPtr query) { - return data.write(query); + return nullptr; + //return new MergeTreeBlockOutputStream(thisPtr()); } void StorageMergeTree::dropImpl() { - data.dropImpl(); + merger.cancelAll(); + joinMergeThreads(); + data.dropAllData(); } void StorageMergeTree::rename(const String & new_path_to_db, const String & new_name) { - data.rename(new_path_to_db, new_name); + BigLockPtr lock = lockAllOperations(); + + 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; + name = new_name; + full_path = new_full_path; + + data.setPath(full_path); + + increment.setPath(full_path + "increment.txt"); } void StorageMergeTree::alter(const ASTAlterQuery::Parameters & params) { + /// InterpreterAlterQuery уже взял BigLock. + data.alter(params); } +void StorageMergeTree::merge(size_t iterations, bool async, bool aggressive) +{ + bool while_can = false; + if (iterations == 0) + { + while_can = true; + 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(); +} + + +void StorageMergeTree::mergeThread(bool while_can, bool aggressive) +{ + try + { + while (!shutdown_called) + { + /// Удаляем старые куски. На случай, если в слиянии что-то сломано, и из следующего блока вылетит исключение. + data.clearOldParts(); + + size_t disk_space = DiskSpaceMonitor::getUnreservedFreeSpace(full_path); + + { + /// К концу этого логического блока должен быть вызван деструктор, чтобы затем корректно определить удаленные куски + MergeTreeData::DataPartsVector parts; + + if (!merger.selectPartsToMerge(parts, disk_space, false, aggressive) && + !merger.selectPartsToMerge(parts, disk_space, true, aggressive)) + break; + + merger.mergeParts(parts); + } + + if (shutdown_called) + break; + + /// Удаляем куски, которые мы только что сливали. + data.clearOldParts(); + + if (!while_can) + break; + } + } + catch (const Exception & e) + { + LOG_ERROR(log, "Code: " << e.code() << ". " << e.displayText() << std::endl + << std::endl + << "Stack trace:" << std::endl + << e.getStackTrace().toString()); + } + catch (const Poco::Exception & e) + { + LOG_ERROR(log, "Poco::Exception: " << e.code() << ". " << e.displayText()); + } + catch (const std::exception & e) + { + LOG_ERROR(log, "std::exception: " << e.what()); + } + catch (...) + { + LOG_ERROR(log, "Unknown exception"); + } +} + + +void StorageMergeTree::joinMergeThreads() +{ + LOG_DEBUG(log, "Waiting for merge threads to finish."); + merge_threads->wait(); +} + } From 42dc0516d23e868fd28b2d52d218edffdeaafebe Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Thu, 13 Mar 2014 18:49:17 +0400 Subject: [PATCH 10/51] added config for zookeeper [#METR-10172] --- libs/libzkutil/include/zkutil/ZooKeeper.h | 5 +++ libs/libzkutil/src/ZooKeeper.cpp | 47 ++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/libs/libzkutil/include/zkutil/ZooKeeper.h b/libs/libzkutil/include/zkutil/ZooKeeper.h index 06c7900a5cd..ab875279aca 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,9 @@ class ZooKeeper public: ZooKeeper(const std::string & hosts, int32_t sessionTimeoutMs = DEFAULT_SESSION_TIMEOUT, WatchFunction * watch = nullptr); + ZooKeeper(const Poco::Util::LayeredConfiguration & config, const std::string & config_name, + WatchFunction * watch = nullptr); + /** Возвращает true, если сессия навсегда завершена. * Это возможно только если соединение было установлено, а потом разорвалось. Это достаточно редкая ситуация. * С другой стороны, если, например, указан неправильный сервер или порт, попытки соединения будут продолжаться бесконечно, @@ -95,6 +99,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; From 1e44f91f002621bde1e930386a7104f5eda4dd15 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Thu, 13 Mar 2014 21:30:58 +0400 Subject: [PATCH 11/51] dbms: fixed error in formatAST [#METR-10443]. --- dbms/src/Parsers/formatAST.cpp | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/dbms/src/Parsers/formatAST.cpp b/dbms/src/Parsers/formatAST.cpp index cded21c44c3..77d01059ec3 100644 --- a/dbms/src/Parsers/formatAST.cpp +++ b/dbms/src/Parsers/formatAST.cpp @@ -449,6 +449,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) @@ -558,30 +562,33 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool written = true; } } + } + if (!written && ast.arguments->children.size() >= 1) + { if (!written && 0 == strcmp(ast.name.c_str(), "array")) { - s << '['; + s << (hilite ? hilite_operator : "") << '[' << (hilite ? hilite_none : ""); 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 << ']'; + s << (hilite ? hilite_operator : "") << ']' << (hilite ? hilite_none : ""); written = true; } if (!written && 0 == strcmp(ast.name.c_str(), "tuple")) { - s << '('; + s << (hilite ? hilite_operator : "") << '(' << (hilite ? hilite_none : ""); 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 << ')'; + s << (hilite ? hilite_operator : "") << ')' << (hilite ? hilite_none : ""); written = true; } } @@ -609,11 +616,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); @@ -623,15 +637,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) From 00d9c285719981a7fcd1dc5ee7d820cb59674771 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 13 Mar 2014 21:44:00 +0400 Subject: [PATCH 12/51] Merge --- .../MergeTree/MergeTreeBlockOutputStream.h | 9 +- .../DB/Storages/MergeTree/MergeTreeData.h | 115 +++---- .../Storages/MergeTree/MergeTreeDataMerger.h | 20 +- .../Storages/MergeTree/MergeTreeDataWriter.h | 308 ++---------------- dbms/include/DB/Storages/StorageMergeTree.h | 44 ++- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 37 ++- .../MergeTree/MergeTreeDataMerger.cpp | 47 ++- .../MergeTree/MergeTreeDataWriter.cpp | 247 ++++++++++++++ dbms/src/Storages/StorageMergeTree.cpp | 31 +- 9 files changed, 479 insertions(+), 379 deletions(-) diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h index 0b7f2a7782e..0b2993fbecf 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h @@ -14,12 +14,13 @@ public: void write(const Block & block) { - BlocksList part_blocks = storage.writer.splitBlockIntoParts(block, structure); - for (const Block & current_block : part_blocks) + auto part_blocks = storage.writer.splitBlockIntoParts(block, structure); + for (auto & current_block : part_blocks) { UInt64 temp_index = storage.increment.get(); - String temp_name = storage.writer.writeTempPart(current_block, temp_index, structure); - storage.writer.renameTempPart(temp_name, &storage.increment); + MergeTreeData::DataPartPtr part = storage.writer.writeTempPart(current_block, temp_index, structure); + storage.data.renameTempPartAndAdd(part, &storage.increment, structure); + storage.merge(2); } } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index d8ad6cd0893..0e63ad93b72 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -101,7 +101,9 @@ public: /// Описание куска с данными. struct DataPart { - DataPart(MergeTreeData & storage_) : storage(storage_), size_in_bytes(0), currently_merging(false) {} + DataPart(MergeTreeData & storage_) : storage(storage_), size_in_bytes(0) {} + + /// Не изменяйте никакие поля для кусков, уже вставленных в таблицу. TODO заменить почти везде const DataPart. MergeTreeData & storage; DayNum_t left_date; @@ -119,9 +121,6 @@ public: DayNum_t left_month; DayNum_t right_month; - /// Смотреть и изменять это поле следует под залоченным data_parts_mutex. - bool currently_merging; - /// Первичный ключ. Всегда загружается в оперативку. typedef std::vector Index; Index index; @@ -163,26 +162,18 @@ public: 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_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 true; - if (left > rhs.left) - return false; - if (right < rhs.right) - return true; - if (right > rhs.right) - return false; + if (left != rhs.left) + return left < rhs.left; + if (right != rhs.right) + return right < rhs.right; - if (level < rhs.level) - return true; + if (level != rhs.level) + return level < rhs.level; return false; } @@ -265,41 +256,23 @@ public: UInt64 getMaxDataPartIndex(); - /** Возвращает копию списка, чтобы снаружи можно было не заботиться о блокировках. - */ - DataParts getDataParts(); - - /** Удаляет куски old_parts и добавляет кусок new_part. Если какого-нибудь из удаляемых кусков нет, бросает исключение. - */ - void replaceParts(DataPartsVector old_parts, DataPartPtr new_part); - - /** Удалить неактуальные куски. - */ - void clearOldParts(); - - /** После вызова dropAllData больше ничего вызывать нельзя. - */ - void dropAllData(); - - /** Поменять путь к директории с данными. Предполагается, что все данные из старой директории туда перенесли. - * Нужно вызывать под залоченным lockStructure(). - */ - void setPath(const String & full_path); - - /** Метод ALTER позволяет добавлять и удалять столбцы и менять их тип. - * Нужно вызывать под залоченным lockStructure(). - * TODO: сделать, чтобы ALTER MODIFY не лочил чтения надолго. Для этого есть parts_writing_lock. - */ - void alter(const ASTAlterQuery::Parameters & params); - 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); + /** Изменяемая часть описания таблицы. Содержит лок, запрещающий изменение описания таблицы. * Если в течение какой-то операции структура таблицы должна оставаться неизменной, нужно держать один лок на все ее время. * Например, нужно держать такой лок на время всего запроса SELECT или INSERT и на все время слияния набора кусков * (но между выбором кусков для слияния и их слиянием структура таблицы может измениться). - * NOTE: можно перенести сюда другие поля, чтобы сделать их динамически изменяемыми. + * NOTE: Можно перенести сюда другие поля, чтобы сделать их динамически изменяемыми. * Например, index_granularity, sign_column, primary_expr_ast. + * NOTE: Можно вынести эту штуку в IStorage и брать ее в Interpreter-ах, + * чтобы избавиться от оставшихся небольших race conditions. + * Скорее всего, даже можно заменить этой штукой весь механизм отложенного дропа таблиц и убрать owned_storage из потоков блоков. */ class LockedTableStructure : public IColumnsDeclaration { @@ -333,9 +306,41 @@ public: TableStructureWriteLockPtr lockStructure() { return new Poco::ScopedWriteRWLock(table_structure_lock); } + /** Возвращает копию списка, чтобы снаружи можно было не заботиться о блокировках. + */ + DataParts getDataParts(); + + /** Удаляет куски old_parts и добавляет кусок new_part. Если какого-нибудь из удаляемых кусков нет, бросает исключение. + */ + void replaceParts(DataPartsVector old_parts, DataPartPtr new_part); + + /** Переименовывает временный кусок в постоянный и добавляет его в рабочий набор. + * Если increment!=nullptr, индекс куска бурется из инкремента. Иначе индекс куска не меняется. + */ + void renameTempPartAndAdd(DataPartPtr part, Increment * increment, + const LockedTableStructurePtr & structure); + + /** Удалить неактуальные куски. + */ + void clearOldParts(); + + /** После вызова dropAllData больше ничего вызывать нельзя. + */ + void dropAllData(); + + /** Поменять путь к директории с данными. Предполагается, что все данные из старой директории туда перенесли. + * Нужно вызывать под залоченным lockStructure(). + */ + void setPath(const String & full_path); + + /** Метод ALTER позволяет добавлять и удалять столбцы и менять их тип. + * Нужно вызывать под залоченным lockStructure(). + * TODO: сделать, чтобы ALTER MODIFY не лочил чтения надолго. Для этого есть parts_writing_lock. + */ + void alter(const ASTAlterQuery::Parameters & params); + /// Эти поля не нужно изменять снаружи. NOTE нужно спрятать их и сделать методы get*. const Context & context; - ASTPtr primary_expr_ast; String date_column_name; ASTPtr sampling_expression; size_t index_granularity; @@ -354,6 +359,8 @@ public: StorageWeakPtr owning_storage; private: + ASTPtr primary_expr_ast; + String full_path; NamesAndTypesListPtr columns; @@ -401,12 +408,6 @@ private: 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); diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h index 028bee5acd2..5f10ca5df0e 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h @@ -6,23 +6,31 @@ namespace DB { /** Умеет выбирать куски для слияния и сливать их. - * Требуется внешний механизм координации слияний со вставками и другими слияниями, обеспечивающий: - * - Куски, между которыми еще может появиться новый кусок, нельзя сливать. См. METR-7001. - * - Кусок, который уже сливаются с кем-то в одном месте, нельзя начать сливать в кем-то другим в другом месте. */ class MergeTreeDataMerger { public: MergeTreeDataMerger(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataMerger")), canceled(false) {} - /// Если merge_anything_for_old_months, для кусков за прошедшие месяцы снимается ограничение на соотношение размеров. + 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 aggressive, + bool only_small, + const AllowedMergingPredicate & can_merge); - /// Возвращает название нового куска. Если слияние отменили, возвращает пустую строку. + /// Сливает куски. Возвращает название нового куска. Если слияние отменили, возвращает пустую строку. String mergeParts(const MergeTreeData::DataPartsVector & parts); /// Примерное количество места на диске, нужное для мерджа. С запасом. diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h index c0766d89f23..eb7c1e45456 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h @@ -13,314 +13,50 @@ 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_) {} + MergeTreeDataWriter(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataWriter")), flags(O_TRUNC | O_CREAT | O_WRONLY) {} /** Разбивает блок на блоки, каждый из которых нужно записать в отдельный кусок. * (читай: разбивает строки по месяцам) * Работает детерминированно: если отдать на вход такой же блок, на выходе получатся такие же блоки в таком же порядке. */ - BlocksList splitBlockIntoParts(const Block & block, const MergeTreeData::LockedTableStructurePtr & structure); + BlocksWithDateIntervals splitBlockIntoParts(const Block & block, const MergeTreeData::LockedTableStructurePtr & structure); /** Все строки должны относиться к одному месяцу. Возвращает название временного куска. * temp_index - значение left и right для нового куска. Можно будет изменить при переименовании. - * NOTE потом понадобится возвращать отсюда структуру с контрольными суммами и размерами. + * Возвращает кусок с именем, начинающимся с tmp_, еще не добавленный в MergeTreeData. */ - String writeTempPart(const Block & block, UInt64 temp_index, const MergeTreeData::LockedTableStructurePtr & structure); - - /** Переименовывает временный кусок в постоянный и добавляет его в рабочий набор. - * Если increment!=nullptr, индекс куска бурется из инкремента. Иначе индекс куска не меняется. - */ - String renameTempPart(const String & temp_name, Increment * increment); + MergeTreeData::DataPartPtr writeTempPart(BlockWithDateInterval & block, UInt64 temp_index, const MergeTreeData::LockedTableStructurePtr & structure); private: MergeTreeData & data; -}; -#if 0 -class MergeTreeDataBlockOutputStream : public IBlockOutputStream -{ -public: - MergeTreeDataBlockOutputStream(MergeTreeData & data, StoragePtr owned_storage) : IBlockOutputStream(owned_storage), storage(data), flags(O_TRUNC | O_CREAT | O_WRONLY) - { - } - - 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) - { - if (*it < min_date) - min_date = *it; - if (*it > max_date) - max_date = *it; - } - - /// Разделяем на блоки по месяцам. Для каждого ещё посчитаем минимальную и максимальную дату. - 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: - MergeTreeData & storage; - + Logger * log; + 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 строки. - MergeTreeData::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 + "/"; - - MergeTreeData::DataPartPtr new_data_part = new MergeTreeData::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(); - } - } + OffsetColumns & offset_columns, size_t level = 0); }; -#endif + } diff --git a/dbms/include/DB/Storages/StorageMergeTree.h b/dbms/include/DB/Storages/StorageMergeTree.h index f9c692d3211..308f7aaf365 100644 --- a/dbms/include/DB/Storages/StorageMergeTree.h +++ b/dbms/include/DB/Storages/StorageMergeTree.h @@ -4,6 +4,7 @@ #include "MergeTree/MergeTreeDataSelectExecutor.h" #include "MergeTree/MergeTreeDataWriter.h" #include "MergeTree/MergeTreeDataMerger.h" +#include "MergeTree/DiskSpaceMonitor.h" namespace DB { @@ -94,10 +95,50 @@ private: MergeTreeDataWriter writer; MergeTreeDataMerger merger; + MergeTreeData::DataParts currently_merging; + Poco::FastMutex currently_merging_mutex; + Logger * log; volatile bool shutdown_called; + Poco::SharedPtr merge_threads; + + /// Пока существует, помечает части как currently_merging и держит резерв места. + /// Вероятно, что части будут помечены заранее. + struct CurrentlyMergingPartsTagger + { + MergeTreeData::DataPartsVector parts; + DiskSpaceMonitor::ReservationPtr reserved_space; + StorageMergeTree & storage; + + CurrentlyMergingPartsTagger(const MergeTreeData::DataPartsVector & parts_, size_t total_size, StorageMergeTree & storage_) + : parts(parts_), storage(storage_) + { + /// Здесь не лочится мьютекс, так как конструктор вызывается внутри mergeThread, где он уже залочен. + reserved_space = DiskSpaceMonitor::reserve(storage.full_path, total_size); /// Может бросить исключение. + storage.currently_merging.insert(parts.begin(), parts.end()); + } + + ~CurrentlyMergingPartsTagger() + { + try + { + Poco::ScopedLock lock(storage.currently_merging_mutex); + for (size_t i = 0; i < parts.size(); ++i) + { + storage.currently_merging.erase(parts[i]); + } + } + catch (...) + { + tryLogCurrentException("~CurrentlyMergingPartsTagger"); + } + } + }; + + typedef Poco::SharedPtr CurrentlyMergingPartsTaggerPtr; + StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_, const Context & context_, ASTPtr & primary_expr_ast_, @@ -121,7 +162,8 @@ private: /// Дождаться, пока фоновые потоки закончат слияния. void joinMergeThreads(); - Poco::SharedPtr merge_threads; + /// Вызывается во время выбора кусков для слияния. + bool canMergeParts(const MergeTreeData::DataPartPtr & left, const MergeTreeData::DataPartPtr & right); }; } diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index ee67b5bd3f4..114dfbf1090 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -55,7 +55,7 @@ namespace DB { -static String lastTwoPathComponents(const String & path) +static String lastTwoPathComponents(String path) { if (!path.empty() && *path.rbegin() == '/') path.erase(path.end() - 1); @@ -77,11 +77,11 @@ MergeTreeData::MergeTreeData( Mode mode_, const String & sign_column_, const MergeTreeSettings & settings_) - : context(context_), primary_expr_ast(primary_expr_ast_->clone()), + : context(context_), date_column_name(date_column_name_), sampling_expression(sampling_expression_), index_granularity(index_granularity_), mode(mode_), sign_column(sign_column_), - settings(settings_), + 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+)") @@ -649,6 +649,37 @@ void MergeTreeData::replaceParts(DataPartsVector old_parts, DataPartPtr new_part data_parts.erase(data_parts.find(old_parts[i])); } +void MergeTreeData::renameTempPartAndAdd(DataPartPtr part, Increment * increment, + const MergeTreeData::LockedTableStructurePtr & structure) +{ + LOG_TRACE(log, "Renaming."); + + Poco::ScopedLock lock(data_parts_mutex); + Poco::ScopedLock lock_all(all_data_parts_mutex); + + String old_path = structure->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 = structure->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); diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp index 66ff9e0542f..9a8a5e079f5 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp @@ -11,7 +11,11 @@ namespace DB { /// Не будем соглашаться мерджить куски, если места на диске менее чем во столько раз больше суммарного размера кусков. -static const double DISK_USAGE_COEFFICIENT = 1.5; +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 раз больше суммы остальных. @@ -32,7 +36,7 @@ static const double DISK_USAGE_COEFFICIENT = 1.5; /// 5) С ростом логарифма суммарного размера кусочков в мердже увеличиваем требование сбалансированности bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & parts, size_t available_disk_space, - bool merge_anything_for_old_months, bool aggressive) + bool merge_anything_for_old_months, bool aggressive, bool only_small, const AllowedMergingPredicate & can_merge) { LOG_DEBUG(log, "Selecting parts to merge"); @@ -60,14 +64,9 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa if (now_hour >= 1 && now_hour <= 5) cur_max_rows_to_merge_parts *= data.settings.merge_parts_at_night_inc; - /// Если есть активный мердж крупных кусков, то ограничиваемся мерджем только маленьких частей. - for (MergeTreeData::DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it) + if (only_small) { - if ((*it)->currently_merging && (*it)->size * data.index_granularity > 25 * 1024 * 1024) - { - cur_max_rows_to_merge_parts = data.settings.max_rows_to_merge_parts_second; - break; - } + cur_max_rows_to_merge_parts = data.settings.max_rows_to_merge_parts_second; } /// Левый конец отрезка. @@ -77,10 +76,6 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa max_count_from_left = std::max(0, max_count_from_left - 1); - /// Кусок не занят. - if (first_part->currently_merging) - continue; - /// Кусок достаточно мал или слияние "агрессивное". if (first_part->size * data.index_granularity > cur_max_rows_to_merge_parts && !aggressive) @@ -115,12 +110,18 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa /// Правый конец отрезка. MergeTreeData::DataParts::iterator jt = it; - for (++jt; jt != data_parts.end() && cur_len < static_cast(data.settings.max_parts_to_merge_at_once); ++jt) + 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 (last_part->currently_merging || + /// Кусок разрешено сливать с предыдущим, и в одном правильном месяце. + if (!can_merge(prev_part, last_part) || last_part->left_month != last_part->right_month || last_part->left_month != month) break; @@ -172,7 +173,7 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa || aggressive)) { /// Достаточно места на диске, чтобы покрыть новый мердж с запасом. - if (available_disk_space > cur_total_size * DISK_USAGE_COEFFICIENT) + if (available_disk_space > cur_total_size * DISK_USAGE_COEFFICIENT_TO_SELECT) { cur_longest_max = cur_max; cur_longest_min = cur_min; @@ -181,7 +182,7 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa 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 - 1.0) * 100) + << cur_total_size << " required now (+" << static_cast((DISK_USAGE_COEFFICIENT_TO_SELECT - 1.0) * 100) << "% on overhead)"); } } @@ -336,4 +337,14 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa 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/MergeTreeDataWriter.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp index e69de29bb2d..473b387aa51 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -0,0 +1,247 @@ +#include +#include +#include +#include + +namespace DB +{ + +BlocksWithDateIntervals MergeTreeDataWriter::splitBlockIntoParts(const Block & block, const MergeTreeData::LockedTableStructurePtr & structure) +{ + structure->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::DataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDateInterval & block_with_dates, UInt64 temp_index, + const MergeTreeData::LockedTableStructurePtr & structure) +{ + 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 = structure->getFullPath() + tmp_part_name + "/"; + + Poco::File(part_tmp_path).createDirectories(); + + LOG_TRACE(log, "Calculating primary expression."); + + /// Если для сортировки надо вычислить некоторые столбцы - делаем это. + data.primary_expr->execute(block); + + LOG_TRACE(log, "Sorting by primary key."); + + /// Сортируем. + stableSortBlock(block, data.sort_descr); + + /// Наконец-то можно писать данные на диск. + LOG_TRACE(log, "Writing index."); + + /// Сначала пишем индекс. Индекс содержит значение PK для каждой index_granularity строки. + MergeTreeData::DataPart::Index index_vec; + index_vec.reserve(part_size * data.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 = data.sort_descr.size(); i < size; ++i) + primary_columns.push_back( + !data.sort_descr[i].column_name.empty() + ? &block.getByName(data.sort_descr[i].column_name) + : &block.getByPosition(data.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::DataPartPtr new_data_part = new MergeTreeData::DataPart(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/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index 0d16416009e..e8ba4624172 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -76,8 +76,7 @@ BlockInputStreams StorageMergeTree::read( BlockOutputStreamPtr StorageMergeTree::write(ASTPtr query) { - return nullptr; - //return new MergeTreeBlockOutputStream(thisPtr()); + return new MergeTreeBlockOutputStream(thisPtr()); } void StorageMergeTree::dropImpl() @@ -139,13 +138,32 @@ void StorageMergeTree::mergeThread(bool while_can, bool aggressive) size_t disk_space = DiskSpaceMonitor::getUnreservedFreeSpace(full_path); { + /// Нужно вызывать деструктор под незалоченным currently_merging_mutex. + CurrentlyMergingPartsTaggerPtr merging_tagger; + + Poco::ScopedLock lock(currently_merging_mutex); + /// К концу этого логического блока должен быть вызван деструктор, чтобы затем корректно определить удаленные куски MergeTreeData::DataPartsVector parts; + auto can_merge = boost::bind(&StorageMergeTree::canMergeParts, this, _1, _2); + bool only_small = false; - if (!merger.selectPartsToMerge(parts, disk_space, false, aggressive) && - !merger.selectPartsToMerge(parts, disk_space, true, aggressive)) + /// Если есть активный мердж крупных кусков, то ограничиваемся мерджем только маленьких частей. + for (const auto & part : currently_merging) + { + if (part->size * data.index_granularity > 25 * 1024 * 1024) + { + only_small = true; + break; + } + } + + 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); + merger.mergeParts(parts); } @@ -187,4 +205,9 @@ void StorageMergeTree::joinMergeThreads() merge_threads->wait(); } +bool StorageMergeTree::canMergeParts(const MergeTreeData::DataPartPtr & left, const MergeTreeData::DataPartPtr & right) +{ + return !currently_merging.count(left) && !currently_merging.count(right); +} + } From 2a766770fc76593e7352f8a07803883797f6f4cb Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 13 Mar 2014 23:07:17 +0400 Subject: [PATCH 13/51] Merge --- .../DB/Storages/MergeTree/MergeTreeData.h | 1 + dbms/include/DB/Storages/StorageMergeTree.h | 11 ++++- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 3 ++ dbms/src/Storages/StorageMergeTree.cpp | 40 ++++++++++--------- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index 0e63ad93b72..e9de97a851b 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -329,6 +329,7 @@ public: void dropAllData(); /** Поменять путь к директории с данными. Предполагается, что все данные из старой директории туда перенесли. + * Сбрасывает кеши разжатых блоков и засечек. * Нужно вызывать под залоченным lockStructure(). */ void setPath(const String & full_path); diff --git a/dbms/include/DB/Storages/StorageMergeTree.h b/dbms/include/DB/Storages/StorageMergeTree.h index 308f7aaf365..970bd24093a 100644 --- a/dbms/include/DB/Storages/StorageMergeTree.h +++ b/dbms/include/DB/Storages/StorageMergeTree.h @@ -117,6 +117,11 @@ private: { /// Здесь не лочится мьютекс, так как конструктор вызывается внутри mergeThread, где он уже залочен. reserved_space = DiskSpaceMonitor::reserve(storage.full_path, total_size); /// Может бросить исключение. + for (const auto & part : parts) + { + 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()); } @@ -125,9 +130,11 @@ private: try { Poco::ScopedLock lock(storage.currently_merging_mutex); - for (size_t i = 0; i < parts.size(); ++i) + for (const auto & part : parts) { - storage.currently_merging.erase(parts[i]); + 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 (...) diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index 114dfbf1090..9f872d999b0 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -353,6 +353,9 @@ void MergeTreeData::clearOldParts() void MergeTreeData::setPath(const String & new_full_path) { full_path = new_full_path; + context.getUncompressedCache()->reset(); + context.getMarkCache()->reset(); + log = &Logger::get(lastTwoPathComponents(full_path)); } void MergeTreeData::dropAllData() diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index e8ba4624172..244f799a851 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -138,33 +138,35 @@ void StorageMergeTree::mergeThread(bool while_can, bool aggressive) size_t disk_space = DiskSpaceMonitor::getUnreservedFreeSpace(full_path); { + /// К концу этого логического блока должен быть вызван деструктор, чтобы затем корректно определить удаленные куски /// Нужно вызывать деструктор под незалоченным currently_merging_mutex. CurrentlyMergingPartsTaggerPtr merging_tagger; - Poco::ScopedLock lock(currently_merging_mutex); - - /// К концу этого логического блока должен быть вызван деструктор, чтобы затем корректно определить удаленные куски - 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) + Poco::ScopedLock lock(currently_merging_mutex); + + MergeTreeData::DataPartsVector parts; + auto can_merge = boost::bind(&StorageMergeTree::canMergeParts, this, _1, _2); + bool only_small = false; + + /// Если есть активный мердж крупных кусков, то ограничиваемся мерджем только маленьких частей. + for (const auto & part : currently_merging) { - only_small = true; - break; + if (part->size * data.index_granularity > 25 * 1024 * 1024) + { + only_small = true; + break; + } } + + 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); } - 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); - - merger.mergeParts(parts); + merger.mergeParts(merging_tagger->parts); } if (shutdown_called) From 5b67cc94ce0a3eb430a555f8c4c03e23d409bef8 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 13 Mar 2014 23:14:25 +0400 Subject: [PATCH 14/51] Small style change. [#METR-10202] --- dbms/include/DB/Storages/MergeTree/MergeTreeData.h | 3 ++- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 6 ++++++ dbms/src/Storages/StorageMergeTree.cpp | 5 ++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index e9de97a851b..20629798f25 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -325,10 +325,11 @@ public: void clearOldParts(); /** После вызова dropAllData больше ничего вызывать нельзя. + * Удаляет директорию с данными и сбрасывает кеши разжатых блоков и засечек. */ void dropAllData(); - /** Поменять путь к директории с данными. Предполагается, что все данные из старой директории туда перенесли. + /** Перемещает всю директорию с данными. * Сбрасывает кеши разжатых блоков и засечек. * Нужно вызывать под залоченным lockStructure(). */ diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index 9f872d999b0..09e461174cb 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -352,9 +352,12 @@ void MergeTreeData::clearOldParts() 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)); } @@ -363,6 +366,9 @@ void MergeTreeData::dropAllData() data_parts.clear(); all_data_parts.clear(); + context.getUncompressedCache()->reset(); + context.getMarkCache()->reset(); + Poco::File(full_path).remove(true); } diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index 244f799a851..9b38477a71c 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -91,14 +91,13 @@ void StorageMergeTree::rename(const String & new_path_to_db, const String & new_ BigLockPtr lock = lockAllOperations(); std::string new_full_path = new_path_to_db + escapeForFileName(new_name) + '/'; - Poco::File(full_path).renameTo(new_full_path); + + data.setPath(new_full_path); path = new_path_to_db; name = new_name; full_path = new_full_path; - data.setPath(full_path); - increment.setPath(full_path + "increment.txt"); } From 2c81592a9bfce2950ef3ee1438ab983b41bac13a Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 13 Mar 2014 23:36:28 +0400 Subject: [PATCH 15/51] Merge --- .../DB/Storages/MergeTree/MergeTreeData.h | 18 ++++----- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 38 ++++++++++++------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index 20629798f25..7bcfc436b24 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -45,11 +45,9 @@ namespace DB * - Summing - при склейке кусков, при совпадении PK суммировать все числовые столбцы, не входящие в PK. */ -/// NOTE: Следующее пока не правда. Сейчас тут практически весь StorageMergeTree. Лишние части нужно перенести отсюда в StorageMergeTree. - /** Этот класс хранит список кусков и параметры структуры данных. * Для чтения и изменения данных используются отдельные классы: - * - MergeTreeDataReader + * - MergeTreeDataSelectExecutor * - MergeTreeDataWriter * - MergeTreeDataMerger */ @@ -103,7 +101,7 @@ public: { DataPart(MergeTreeData & storage_) : storage(storage_), size_in_bytes(0) {} - /// Не изменяйте никакие поля для кусков, уже вставленных в таблицу. TODO заменить почти везде const DataPart. + /// Не изменяйте никакие поля для кусков, уже вставленных в таблицу. TODO заменить почти везде на const DataPart. MergeTreeData & storage; DayNum_t left_date; @@ -286,10 +284,12 @@ public: const MergeTreeData & data; Poco::SharedPtr parts_lock; - Poco::ScopedReadRWLock structure_lock; + Poco::SharedPtr structure_lock; - LockedTableStructure(const MergeTreeData & data_, bool lock_writing) - : data(data_), parts_lock(lock_writing ? new Poco::ScopedReadRWLock(data.parts_writing_lock) : nullptr), structure_lock(data.table_structure_lock) {} + LockedTableStructure(const MergeTreeData & data_, bool lock_structure, bool lock_writing) + : data(data_), + parts_lock(lock_writing ? new Poco::ScopedReadRWLock(data.parts_writing_lock) : nullptr), + structure_lock(lock_structure ? new Poco::ScopedReadRWLock(data.table_structure_lock) : nullptr) {} }; typedef Poco::SharedPtr LockedTableStructurePtr; @@ -299,7 +299,7 @@ public: */ LockedTableStructurePtr getLockedStructure(bool will_modify_parts) const { - return new LockedTableStructure(*this, will_modify_parts); + return new LockedTableStructure(*this, true, will_modify_parts); } typedef Poco::SharedPtr TableStructureWriteLockPtr; @@ -337,7 +337,7 @@ public: /** Метод ALTER позволяет добавлять и удалять столбцы и менять их тип. * Нужно вызывать под залоченным lockStructure(). - * TODO: сделать, чтобы ALTER MODIFY не лочил чтения надолго. Для этого есть parts_writing_lock. + * TODO: сделать, чтобы ALTER MODIFY не лочил чтения надолго. На долгую часть достаточно лочить parts_writing_lock. */ void alter(const ASTAlterQuery::Parameters & params); diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index 09e461174cb..01d5ab7a4e1 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -427,33 +427,39 @@ void MergeTreeData::createConvertExpression(const String & in_column_name, const 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) { - /*if (params.type == ASTAlterQuery::MODIFY) + if (params.type == ASTAlterQuery::MODIFY) { { - typedef std::vector PartsList; - PartsList parts; + DataPartsVector parts; { Poco::ScopedLock lock(data_parts_mutex); - for (auto & data_part : data_parts) - { - parts.push_back(data_part); - } + 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); - DB::DataTypePtr old_type_ptr = getDataTypeByName(name_type.name); - DB::DataTypePtr new_type_ptr = context.getDataTypeFactory().get(type); + DataTypePtr old_type_ptr = 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 DB::Exception("ALTER MODIFY not supported for nested and array types"); + throw Exception("ALTER MODIFY not supported for nested and array types"); column_name.push_back(name_type.name); - DB::ExpressionActionsPtr expr; + ExpressionActionsPtr expr; String out_column; createConvertExpression(name_type.name, type, expr, out_column); @@ -462,7 +468,8 @@ void MergeTreeData::alter(const ASTAlterQuery::Parameters & params) { 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); + new LockedTableStructure(*this, false, false), /// Не блокируем структуру таблицы, она уже заблокирована снаружи. + DEFAULT_MERGE_BLOCK_SIZE, column_name, *this, part, ranges, StoragePtr(), false, NULL, ""), expr); MergedColumnOnlyOutputStream out(*this, full_path + part->name + '/', true); out.writePrefix(); @@ -531,13 +538,16 @@ 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); + IColumnsDeclaration::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(); + } } From ccc3da3148b6c8c666f5691e6bba90ec77feb968 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Fri, 14 Mar 2014 00:12:40 +0400 Subject: [PATCH 16/51] =?UTF-8?q?=E2=96=88=E2=96=88=E2=96=88=E2=96=88?= =?UTF-8?q?=E2=96=88=E2=96=88=E2=96=88=E2=96=88=E2=96=88=E2=96=88=E2=96=88?= =?UTF-8?q?:=20development=20[#METR-8766].?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbms/include/DB/IO/BufferWithOwnMemory.h | 25 +++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) 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]; } From d5e957818bd4929996e0132c993ddf3866c18f5e Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Fri, 14 Mar 2014 11:05:43 +0400 Subject: [PATCH 17/51] Merge --- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 56 +++---------------- 1 file changed, 8 insertions(+), 48 deletions(-) diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index 01d5ab7a4e1..763f90ee449 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -1,55 +1,15 @@ -#include -#include - -#include -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - #include -#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include -#include namespace DB From 420d5696eaa7baede15fc6c554ba6295ce894017 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Fri, 14 Mar 2014 12:30:50 +0400 Subject: [PATCH 18/51] clickhouse: fixed ARRAY JOIN in distributed queries without WHERE and aggregation. [#METR-10349] --- .../DB/Interpreters/InterpreterSelectQuery.h | 2 +- .../Interpreters/InterpreterSelectQuery.cpp | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/dbms/include/DB/Interpreters/InterpreterSelectQuery.h b/dbms/include/DB/Interpreters/InterpreterSelectQuery.h index 3b106749922..f932066e15c 100644 --- a/dbms/include/DB/Interpreters/InterpreterSelectQuery.h +++ b/dbms/include/DB/Interpreters/InterpreterSelectQuery.h @@ -65,7 +65,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); diff --git a/dbms/src/Interpreters/InterpreterSelectQuery.cpp b/dbms/src/Interpreters/InterpreterSelectQuery.cpp index 2250337710d..6dc8a892e85 100644 --- a/dbms/src/Interpreters/InterpreterSelectQuery.cpp +++ b/dbms/src/Interpreters/InterpreterSelectQuery.cpp @@ -209,7 +209,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 +226,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 +314,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 +351,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); @@ -667,7 +679,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) From 4384ba00097806bdefe60896941444116fa6b661 Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Thu, 13 Mar 2014 18:56:31 +0400 Subject: [PATCH 19/51] zookeeper: added config description [#METR-10172] --- libs/libzkutil/include/zkutil/ZooKeeper.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libs/libzkutil/include/zkutil/ZooKeeper.h b/libs/libzkutil/include/zkutil/ZooKeeper.h index ab875279aca..09c8d01b552 100644 --- a/libs/libzkutil/include/zkutil/ZooKeeper.h +++ b/libs/libzkutil/include/zkutil/ZooKeeper.h @@ -20,6 +20,19 @@ 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); From ba9315d26469318c9cbf5c6b33dc972b15fa2f4d Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Fri, 14 Mar 2014 21:03:52 +0400 Subject: [PATCH 20/51] Merge --- .../CollapsingFinalBlockInputStream.h | 2 +- .../CollapsingSortedBlockInputStream.h | 2 +- .../MergingSortedBlockInputStream.h | 2 +- .../SummingSortedBlockInputStream.h | 2 +- .../DB/Storages/MergeTree/MergeTreeData.h | 21 ++++++++++++------- .../MergeTree/MergedBlockOutputStream.h | 8 +++---- .../MergeTree/MergeTreeDataMerger.cpp | 8 +++---- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 18 ++++++++-------- .../MergeTree/MergeTreeDataWriter.cpp | 16 +++++++------- 9 files changed, 43 insertions(+), 36 deletions(-) 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/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/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/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index 7bcfc436b24..5955feb87f0 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -245,6 +245,8 @@ public: */ void setOwningStorage(StoragePtr storage) { owning_storage = storage; } + StoragePtr getOwningStorage() const { return owning_storage; } + std::string getModePrefix() const; std::string getSignColumnName() const { return sign_column; } @@ -341,26 +343,29 @@ public: */ void alter(const ASTAlterQuery::Parameters & params); - /// Эти поля не нужно изменять снаружи. NOTE нужно спрятать их и сделать методы get*. + + ExpressionActionsPtr getPrimaryExpression() const { return primary_expr; } + SortDescription getSortDescription() const { return sort_descr; } + const Context & context; - String date_column_name; - ASTPtr sampling_expression; - size_t index_granularity; + const String date_column_name; + const ASTPtr sampling_expression; + const size_t index_granularity; /// Режим работы - какие дополнительные действия делать при мердже. - Mode mode; + const Mode mode; /// Для схлопывания записей об изменениях, если используется Collapsing режим работы. - String sign_column; + const String sign_column; - MergeTreeSettings settings; + const MergeTreeSettings settings; +private: ExpressionActionsPtr primary_expr; SortDescription sort_descr; Block primary_key_sample; StorageWeakPtr owning_storage; -private: ASTPtr primary_expr_ast; String full_path; diff --git a/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h index a5af398ceef..b90d527d74c 100644 --- a/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h @@ -224,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) { diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp index 9a8a5e079f5..e10029449e3 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp @@ -269,7 +269,7 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa MarkRanges ranges(1, MarkRange(0, parts[i]->size)); src_streams.push_back(new ExpressionBlockInputStream(new MergeTreeBlockInputStream( structure->getFullPath() + parts[i]->name + '/', structure, DEFAULT_MERGE_BLOCK_SIZE, all_column_names, data, parts[i], ranges, - StoragePtr(), false, NULL, ""), data.primary_expr)); + StoragePtr(), false, NULL, ""), data.getPrimaryExpression())); } /// Порядок потоков важен: при совпадении ключа элементы идут в порядке номера потока-источника. @@ -279,15 +279,15 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa switch (data.mode) { case MergeTreeData::Ordinary: - merged_stream = new MergingSortedBlockInputStream(src_streams, data.sort_descr, DEFAULT_MERGE_BLOCK_SIZE); + merged_stream = new MergingSortedBlockInputStream(src_streams, data.getSortDescription(), DEFAULT_MERGE_BLOCK_SIZE); break; case MergeTreeData::Collapsing: - merged_stream = new CollapsingSortedBlockInputStream(src_streams, data.sort_descr, data.sign_column, DEFAULT_MERGE_BLOCK_SIZE); + 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.sort_descr, DEFAULT_MERGE_BLOCK_SIZE); + merged_stream = new SummingSortedBlockInputStream(src_streams, data.getSortDescription(), DEFAULT_MERGE_BLOCK_SIZE); break; default: diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 0508f653128..ddcc811901d 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -32,7 +32,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read( structure->check(column_names_to_return); processed_stage = QueryProcessingStage::FetchColumns; - PKCondition key_condition(query, data.context, structure->getColumnsList(), data.sort_descr); + PKCondition key_condition(query, data.context, structure->getColumnsList(), data.getSortDescription()); PKCondition date_condition(query, data.context, structure->getColumnsList(), SortDescription(1, SortColumnDescription(data.date_column_name, 1))); MergeTreeData::DataPartsVector parts; @@ -90,7 +90,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read( } UInt64 sampling_column_max = 0; - DataTypePtr type = data.primary_expr->getSampleBlock().getByName(data.sampling_expression->getColumnName()).type; + DataTypePtr type = data.getPrimaryExpression()->getSampleBlock().getByName(data.sampling_expression->getColumnName()).type; if (type->getName() == "UInt64") sampling_column_max = std::numeric_limits::max(); @@ -173,7 +173,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read( if (select.final) { /// Добавим столбцы, нужные для вычисления первичного ключа и знака. - std::vector add_columns = data.primary_expr->getRequiredColumns(); + 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()); @@ -284,7 +284,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreads( streams.push_back(new MergeTreeBlockInputStream( structure->getFullPath() + part.data_part->name + '/', structure, max_block_size, column_names, data, - part.data_part, part.ranges, data.owning_storage, use_uncompressed_cache, + part.data_part, part.ranges, data.getOwningStorage(), use_uncompressed_cache, prewhere_actions, prewhere_column)); need_marks -= marks_in_part; parts.pop_back(); @@ -314,7 +314,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreads( streams.push_back(new MergeTreeBlockInputStream( structure->getFullPath() + part.data_part->name + '/', structure, max_block_size, column_names, data, - part.data_part, ranges_to_get_from_part, data.owning_storage, use_uncompressed_cache, + part.data_part, ranges_to_get_from_part, data.getOwningStorage(), use_uncompressed_cache, prewhere_actions, prewhere_column)); } @@ -361,17 +361,17 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreadsFinal BlockInputStreamPtr source_stream = new MergeTreeBlockInputStream( structure->getFullPath() + part.data_part->name + '/', structure, max_block_size, column_names, data, - part.data_part, part.ranges, data.owning_storage, use_uncompressed_cache, + part.data_part, part.ranges, data.getOwningStorage(), use_uncompressed_cache, prewhere_actions, prewhere_column); - to_collapse.push_back(new ExpressionBlockInputStream(source_stream, data.primary_expr)); + 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.sort_descr, data.sign_column)); + res.push_back(new CollapsingFinalBlockInputStream(to_collapse, data.getSortDescription(), data.sign_column)); return res; } @@ -413,7 +413,7 @@ MarkRanges MergeTreeDataSelectExecutor::markRangesFromPkRange(const MergeTreeDat { MarkRanges res; - size_t key_size = data.sort_descr.size(); + size_t key_size = data.getSortDescription().size(); size_t marks_count = index.size() / key_size; /// Если индекс не используется. diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp index 473b387aa51..daf42ae50de 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -93,19 +93,21 @@ MergeTreeData::DataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDateInter LOG_TRACE(log, "Calculating primary expression."); /// Если для сортировки надо вычислить некоторые столбцы - делаем это. - data.primary_expr->execute(block); + data.getPrimaryExpression()->execute(block); LOG_TRACE(log, "Sorting by primary key."); + SortDescription sort_descr = data.getSortDescription(); + /// Сортируем. - stableSortBlock(block, data.sort_descr); + stableSortBlock(block, sort_descr); /// Наконец-то можно писать данные на диск. LOG_TRACE(log, "Writing index."); /// Сначала пишем индекс. Индекс содержит значение PK для каждой index_granularity строки. MergeTreeData::DataPart::Index index_vec; - index_vec.reserve(part_size * data.sort_descr.size()); + index_vec.reserve(part_size * sort_descr.size()); { WriteBufferFromFile index(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, flags); @@ -113,11 +115,11 @@ MergeTreeData::DataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDateInter typedef std::vector PrimaryColumns; PrimaryColumns primary_columns; - for (size_t i = 0, size = data.sort_descr.size(); i < size; ++i) + for (size_t i = 0, size = sort_descr.size(); i < size; ++i) primary_columns.push_back( - !data.sort_descr[i].column_name.empty() - ? &block.getByName(data.sort_descr[i].column_name) - : &block.getByPosition(data.sort_descr[i].column_number)); + !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) { From 758b1414a77a68b58a91488fcd37aff1d91d4cf5 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Fri, 14 Mar 2014 21:19:38 +0400 Subject: [PATCH 21/51] Merge --- .../Storages/MergeTree/MergeTreeBlockOutputStream.h | 2 +- dbms/include/DB/Storages/MergeTree/MergeTreeData.h | 12 ++++++------ .../DB/Storages/MergeTree/MergeTreeDataWriter.h | 2 +- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 6 +++--- dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp | 2 +- dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h index 0b2993fbecf..097ea2b6cd1 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h @@ -18,7 +18,7 @@ public: for (auto & current_block : part_blocks) { UInt64 temp_index = storage.increment.get(); - MergeTreeData::DataPartPtr part = storage.writer.writeTempPart(current_block, temp_index, structure); + MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, temp_index, structure); storage.data.renameTempPartAndAdd(part, &storage.increment, structure); storage.merge(2); } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index 5955feb87f0..4dab22b75c8 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -101,9 +101,7 @@ public: { DataPart(MergeTreeData & storage_) : storage(storage_), size_in_bytes(0) {} - /// Не изменяйте никакие поля для кусков, уже вставленных в таблицу. TODO заменить почти везде на const DataPart. - - MergeTreeData & storage; + MergeTreeData & storage; DayNum_t left_date; DayNum_t right_date; UInt64 left; @@ -208,7 +206,9 @@ public: } }; - typedef SharedPtr DataPartPtr; + 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; @@ -317,9 +317,9 @@ public: void replaceParts(DataPartsVector old_parts, DataPartPtr new_part); /** Переименовывает временный кусок в постоянный и добавляет его в рабочий набор. - * Если increment!=nullptr, индекс куска бурется из инкремента. Иначе индекс куска не меняется. + * Если increment!=nullptr, индекс куска берется из инкремента. Иначе индекс куска не меняется. */ - void renameTempPartAndAdd(DataPartPtr part, Increment * increment, + void renameTempPartAndAdd(MutableDataPartPtr part, Increment * increment, const LockedTableStructurePtr & structure); /** Удалить неактуальные куски. diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h index eb7c1e45456..87c4f149ff2 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h @@ -43,7 +43,7 @@ public: * temp_index - значение left и right для нового куска. Можно будет изменить при переименовании. * Возвращает кусок с именем, начинающимся с tmp_, еще не добавленный в MergeTreeData. */ - MergeTreeData::DataPartPtr writeTempPart(BlockWithDateInterval & block, UInt64 temp_index, const MergeTreeData::LockedTableStructurePtr & structure); + MergeTreeData::MutableDataPartPtr writeTempPart(BlockWithDateInterval & block, UInt64 temp_index, const MergeTreeData::LockedTableStructurePtr & structure); private: MergeTreeData & data; diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index 763f90ee449..d321ea37734 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -181,7 +181,7 @@ void MergeTreeData::loadDataParts() if (!isPartDirectory(file_name, matches)) continue; - DataPartPtr part = new DataPart(*this); + MutableDataPartPtr part = std::make_shared(*this); parsePartName(file_name, matches, *part); part->name = file_name; @@ -285,7 +285,7 @@ void MergeTreeData::clearOldParts() LOG_TRACE(log, "Clearing old parts"); for (DataParts::iterator it = all_data_parts.begin(); it != all_data_parts.end();) { - int ref_count = it->referenceCount(); + int ref_count = it->use_count(); if (ref_count == 1) /// После этого ref_count не может увеличиться. { LOG_DEBUG(log, "'Removing' part " << (*it)->name << " (prepending old_ to its name)"); @@ -628,7 +628,7 @@ void MergeTreeData::replaceParts(DataPartsVector old_parts, DataPartPtr new_part data_parts.erase(data_parts.find(old_parts[i])); } -void MergeTreeData::renameTempPartAndAdd(DataPartPtr part, Increment * increment, +void MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr part, Increment * increment, const MergeTreeData::LockedTableStructurePtr & structure) { LOG_TRACE(log, "Renaming."); diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp index e10029449e3..20384fb9103 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp @@ -241,7 +241,7 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa DateLUTSingleton & date_lut = DateLUTSingleton::instance(); - MergeTreeData::DataPartPtr new_data_part = new MergeTreeData::DataPart(data); + 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; diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp index daf42ae50de..bb9d41568b4 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -69,7 +69,7 @@ BlocksWithDateIntervals MergeTreeDataWriter::splitBlockIntoParts(const Block & b return res; } -MergeTreeData::DataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDateInterval & block_with_dates, UInt64 temp_index, +MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDateInterval & block_with_dates, UInt64 temp_index, const MergeTreeData::LockedTableStructurePtr & structure) { Block & block = block_with_dates.block; @@ -144,7 +144,7 @@ MergeTreeData::DataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDateInter writeData(part_tmp_path, column.name, *column.type, *column.column, offset_columns); } - MergeTreeData::DataPartPtr new_data_part = new MergeTreeData::DataPart(data); + 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; From 806003d19ef7d8ddb0053d82fe1f1fc885bef4a4 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 16 Mar 2014 19:56:42 +0400 Subject: [PATCH 22/51] dbms: removed old commented code [#METR-2944]. --- dbms/include/DB/Columns/ColumnArray.h | 9 --------- 1 file changed, 9 deletions(-) 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; From f7e2ba67c01174a3489a13d76ead0ad4ebd9b152 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 17 Mar 2014 23:56:37 +0400 Subject: [PATCH 23/51] dbms: fixed error with query formatting [#METR-10476]. --- dbms/src/Parsers/formatAST.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/dbms/src/Parsers/formatAST.cpp b/dbms/src/Parsers/formatAST.cpp index 77d01059ec3..42aae30c55f 100644 --- a/dbms/src/Parsers/formatAST.cpp +++ b/dbms/src/Parsers/formatAST.cpp @@ -496,6 +496,7 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool "less", " < ", "greater", " > ", "equals", " = ", + "lambda", " -> ", "like", " LIKE ", "notLike", " NOT LIKE ", "in", " IN ", From 5d04a9ba347aacd4ed147c7cc60e9d8cee35102c Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Tue, 18 Mar 2014 16:01:13 +0400 Subject: [PATCH 24/51] dbms: bug-fix, exception on empty shard list in table function remote [METR-10477] --- dbms/include/DB/TableFunctions/TableFunctionRemote.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dbms/include/DB/TableFunctions/TableFunctionRemote.h b/dbms/include/DB/TableFunctions/TableFunctionRemote.h index a4303eec87b..a048ee51cc7 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), @@ -69,6 +73,7 @@ private: /// Узнать имена и типы столбцов для создания таблицы NamesAndTypesListPtr chooseColumns(Cluster & cluster, const String & database, const String & table, const Context & context) const { + std::cerr << "Here" << std::endl; /// Запрос на описание таблицы String query = "DESC TABLE " + database + "." + table; Settings settings = context.getSettings(); From a92d7255ef60a99bbc37c418ba8135c467fd48c4 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Tue, 18 Mar 2014 17:19:22 +0400 Subject: [PATCH 25/51] dbms: removed cerr in table function remote [METR-10477] --- dbms/include/DB/TableFunctions/TableFunctionRemote.h | 1 - 1 file changed, 1 deletion(-) diff --git a/dbms/include/DB/TableFunctions/TableFunctionRemote.h b/dbms/include/DB/TableFunctions/TableFunctionRemote.h index a048ee51cc7..b5edb9a8a21 100644 --- a/dbms/include/DB/TableFunctions/TableFunctionRemote.h +++ b/dbms/include/DB/TableFunctions/TableFunctionRemote.h @@ -73,7 +73,6 @@ private: /// Узнать имена и типы столбцов для создания таблицы NamesAndTypesListPtr chooseColumns(Cluster & cluster, const String & database, const String & table, const Context & context) const { - std::cerr << "Here" << std::endl; /// Запрос на описание таблицы String query = "DESC TABLE " + database + "." + table; Settings settings = context.getSettings(); From ce98defebe4da6026bfa4025dfb9ab1fe6bd54ca Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 18 Mar 2014 21:20:44 +0400 Subject: [PATCH 26/51] dbms: fixed error [#METR-10489]. --- dbms/src/Parsers/formatAST.cpp | 2 +- dbms/src/Storages/StorageFactory.cpp | 43 +++++++++++++++++++--------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/dbms/src/Parsers/formatAST.cpp b/dbms/src/Parsers/formatAST.cpp index 42aae30c55f..04d3e459aeb 100644 --- a/dbms/src/Parsers/formatAST.cpp +++ b/dbms/src/Parsers/formatAST.cpp @@ -580,7 +580,7 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool written = true; } - if (!written && 0 == strcmp(ast.name.c_str(), "tuple")) + if (!written && 0 == strcmp(ast.name.c_str(), "tuple") && ast.arguments->children.size() >= 2) { s << (hilite ? hilite_operator : "") << '(' << (hilite ? hilite_none : ""); for (size_t i = 0; i < ast.arguments->children.size(); ++i) diff --git a/dbms/src/Storages/StorageFactory.cpp b/dbms/src/Storages/StorageFactory.cpp index 08cb6904183..c70fd96d9b3 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,16 +207,11 @@ 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, + data_path, table_name, columns, context, primary_expr_list, date_column_name, sampling_expression, index_granularity, name == "SummingMergeTree" ? StorageMergeTree::Summing : StorageMergeTree::Ordinary); } else if (name == "CollapsingMergeTree") @@ -224,16 +244,11 @@ 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, + data_path, table_name, columns, context, primary_expr_list, date_column_name, sampling_expression, index_granularity, StorageMergeTree::Collapsing, sign_column_name); } else if (name == "SystemNumbers") From 68dd884b1fbf4ffc8fa05d46e1c0681cdb238d7c Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 18 Mar 2014 21:23:30 +0400 Subject: [PATCH 27/51] dbms: tiny modification [#METR-10489]. --- dbms/src/Parsers/formatAST.cpp | 39 ++++++++++++++++------------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/dbms/src/Parsers/formatAST.cpp b/dbms/src/Parsers/formatAST.cpp index 04d3e459aeb..556a2e6a53e 100644 --- a/dbms/src/Parsers/formatAST.cpp +++ b/dbms/src/Parsers/formatAST.cpp @@ -565,33 +565,30 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool } } - if (!written && ast.arguments->children.size() >= 1) + if (!written && ast.arguments->children.size() >= 1 && 0 == strcmp(ast.name.c_str(), "array")) { - if (!written && 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 << (hilite ? hilite_operator : "") << '[' << (hilite ? hilite_none : ""); - 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 << (hilite ? hilite_operator : "") << ']' << (hilite ? hilite_none : ""); - 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") && ast.arguments->children.size() >= 2) + 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 << (hilite ? hilite_operator : "") << '(' << (hilite ? hilite_none : ""); - 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 << (hilite ? hilite_operator : "") << ')' << (hilite ? hilite_none : ""); - 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; } } From 9ffad47bdc602f7c509a4c9b8424565d8c2f6e6a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Tue, 18 Mar 2014 21:45:54 +0400 Subject: [PATCH 28/51] dbms: fixed error [#METR-10489]. --- dbms/src/Parsers/formatAST.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/dbms/src/Parsers/formatAST.cpp b/dbms/src/Parsers/formatAST.cpp index 556a2e6a53e..505d31228eb 100644 --- a/dbms/src/Parsers/formatAST.cpp +++ b/dbms/src/Parsers/formatAST.cpp @@ -496,7 +496,6 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool "less", " < ", "greater", " > ", "equals", " = ", - "lambda", " -> ", "like", " LIKE ", "notLike", " NOT LIKE ", "in", " IN ", From 67c563a4d34a98eb0f2969cf59c92b9be79465cf Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Wed, 19 Mar 2014 14:45:13 +0400 Subject: [PATCH 29/51] Added locks to IStorage. Some race conditions are fixed, some are introduced, along with some possible deadlocks. [#METR-10202] --- dbms/include/DB/Common/VirtualColumnUtils.h | 1 - .../DB/DataStreams/IBlockInputStream.h | 17 +-- .../DB/DataStreams/IBlockOutputStream.h | 12 +- .../DataStreams/IProfilingBlockInputStream.h | 6 +- .../Interpreters/InterpreterDescribeQuery.h | 9 +- .../Interpreters/InterpreterOptimizeQuery.h | 4 +- .../DB/Interpreters/InterpreterSelectQuery.h | 7 +- dbms/include/DB/Storages/IStorage.h | 116 ++++++++++++++++-- ...lumnsDeclaration.h => ITableDeclaration.h} | 10 +- .../MergeTree/MergeTreeBlockInputStream.h | 20 ++- .../MergeTree/MergeTreeBlockOutputStream.h | 12 +- .../DB/Storages/MergeTree/MergeTreeData.h | 81 +----------- .../MergeTree/MergeTreeDataSelectExecutor.h | 9 +- .../Storages/MergeTree/MergeTreeDataWriter.h | 4 +- .../DB/Storages/MergeTree/MergeTreeReader.h | 13 +- .../MergeTree/MergedBlockOutputStream.h | 11 +- .../DB/Storages/MergeTree/PKCondition.h | 1 - dbms/include/DB/Storages/StorageLog.h | 17 +-- dbms/include/DB/Storages/StorageMemory.h | 7 +- dbms/include/DB/Storages/StorageMergeTree.h | 9 +- dbms/include/DB/Storages/StorageTinyLog.h | 17 +-- .../Interpreters/InterpreterAlterQuery.cpp | 6 +- .../Interpreters/InterpreterInsertQuery.cpp | 13 +- .../Interpreters/InterpreterRenameQuery.cpp | 4 +- .../Interpreters/InterpreterSelectQuery.cpp | 86 ++++++------- ...sDeclaration.cpp => ITableDeclaration.cpp} | 20 +-- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 14 +-- .../MergeTree/MergeTreeDataMerger.cpp | 10 +- .../MergeTree/MergeTreeDataSelectExecutor.cpp | 43 +++---- .../MergeTree/MergeTreeDataWriter.cpp | 9 +- dbms/src/Storages/StorageDistributed.cpp | 4 - dbms/src/Storages/StorageLog.cpp | 28 +++-- dbms/src/Storages/StorageMemory.cpp | 12 +- dbms/src/Storages/StorageMergeTree.cpp | 23 ++-- dbms/src/Storages/StorageTinyLog.cpp | 25 +++- 35 files changed, 342 insertions(+), 338 deletions(-) rename dbms/include/DB/Storages/{IColumnsDeclaration.h => ITableDeclaration.h} (89%) rename dbms/src/Storages/{IColumnsDeclaration.cpp => ITableDeclaration.cpp} (91%) diff --git a/dbms/include/DB/Common/VirtualColumnUtils.h b/dbms/include/DB/Common/VirtualColumnUtils.h index c6a2328eb61..7ededa3be5c 100644 --- a/dbms/include/DB/Common/VirtualColumnUtils.h +++ b/dbms/include/DB/Common/VirtualColumnUtils.h @@ -9,7 +9,6 @@ #include #include #include -#include #include namespace DB 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/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/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 f932066e15c..73a3baf271f 100644 --- a/dbms/include/DB/Interpreters/InterpreterSelectQuery.h +++ b/dbms/include/DB/Interpreters/InterpreterSelectQuery.h @@ -47,8 +47,6 @@ private: */ void getDatabaseAndTableNames(String & database_name, String & table_name); - StoragePtr getTable(); - /** Выбрать из списка столбцов какой-нибудь, лучше - минимального размера. */ String getAnyColumn(); @@ -83,7 +81,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/Storages/IStorage.h b/dbms/include/DB/Storages/IStorage.h index e78294bb44b..24bcb53d2d4 100644 --- a/dbms/include/DB/Storages/IStorage.h +++ b/dbms/include/DB/Storages/IStorage.h @@ -7,15 +7,14 @@ #include #include #include -#include -#include #include #include #include #include -#include +#include #include #include +#include #include @@ -23,6 +22,12 @@ namespace DB { class Context; +class IBlockInputStream; +class IBlockOutputStream; + +typedef SharedPtr BlockOutputStreamPtr; +typedef SharedPtr BlockInputStreamPtr; +typedef std::vector BlockInputStreams; /** Хранилище. Отвечает за: @@ -32,15 +37,12 @@ class Context; * - структура хранения данных (сжатие, etc.) * - конкуррентный доступ к данным (блокировки, etc.) */ -class IStorage : private boost::noncopyable, public IColumnsDeclaration +class IStorage : private boost::noncopyable, public ITableDeclaration { public: /// Основное имя типа таблицы (например, StorageMergeTree). virtual std::string getName() const = 0; - /// Имя самой таблицы (например, hits) - virtual std::string getTableName() const = 0; - /** Возвращает true, если хранилище получает данные с удалённого сервера или серверов. */ virtual bool isRemote() const { return false; } @@ -57,6 +59,55 @@ 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) + { + return new TableStructureReadLock(*this, true, will_modify_data); + } + + typedef Poco::SharedPtr TableStructureWriteLockPtr; + typedef Poco::SharedPtr TableDataWriteLockPtr; + + /** Не дает читать структуру таблицы. Берется для ALTER, RENAME и DROP. + */ + TableStructureWriteLockPtr lockStructureForAlter() { return new Poco::ScopedWriteRWLock(structure_lock); } + + /** Не дает изменять данные в таблице. (Более того, не дает посмотреть на структуру таблицы с намерением изменить данные). + * Берется на время записи временных данных в ALTER MODIFY. + * Под этим локом можно брать lockStructureForAlter(), чтобы изменить структуру таблицы. + */ + TableDataWriteLockPtr lockDataForAlter() { return new Poco::ScopedWriteRWLock(data_lock); } + + /** Читать набор столбцов из таблицы. * Принимает список столбцов, которых нужно прочитать, а также описание запроса, * из которого может быть извлечена информация о том, каким способом извлекать данные @@ -73,6 +124,8 @@ public: * * threads - рекомендация, сколько потоков возвращать, * если хранилище может возвращать разное количество потоков. + * + * Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP). */ virtual BlockInputStreams read( const Names & column_names, @@ -88,6 +141,8 @@ public: /** Пишет данные в таблицу. * Принимает описание запроса, в котором может содержаться информация о методе записи данных. * Возвращает объект, с помощью которого можно последовательно писать данные. + * + * Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP). */ virtual BlockOutputStreamPtr write( ASTPtr query) @@ -101,7 +156,7 @@ public: { drop_on_destroy = true; } - + /** Вызывается перед удалением директории с данными и вызовом деструктора. * Если не требуется никаких действий, кроме удаления директории с данными, этот метод можно оставить пустым. */ @@ -110,6 +165,7 @@ public: /** Переименовать таблицу. * Переименование имени в файле с метаданными, имени в списке таблиц в оперативке, осуществляется отдельно. * В этой функции нужно переименовать директорию с данными, если она есть. + * Вызывается при заблокированной на запись структуре таблицы. */ virtual void rename(const String & new_path_to_db, const String & new_name) { @@ -118,12 +174,31 @@ public: /** ALTER таблицы в виде изменения столбцов, не затрагивающий изменение Storage или его параметров. * (ALTER, затрагивающий изменение движка, делается внешним кодом, путём копирования данных.) + * Вызывается при заблокированной на запись структуре таблицы. + * Для ALTER MODIFY используются другие методы (см. ниже). TODO: Пока эта строчка не верна, и для ALTER MODIFY используется метод alter. */ virtual void alter(const ASTAlterQuery::Parameters & params) { throw Exception("Method alter is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); } + /** ALTER ... MODIFY (изменение типа столбца) выполняется в два вызова: + * Сначала вызывается prepareAlterModify при заблокированной записи данных, но незаблокированной структуре таблицы. + * В нем можно выполнить долгую работу по записи сконвертированных данных, оставляя доступными существующие данные. + * Потом вызывается commitAlterModify при заблокированной структуре таблицы. + * В нем нужно закончить изменение типа столбца. + */ + + virtual void prepareAlterModify(const ASTAlterQuery::Parameters & params) + { + throw Exception("Method prepareAlterModify is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); + } + + virtual void commitAlterModify(const ASTAlterQuery::Parameters & params) + { + throw Exception("Method commitAlterModify is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); + } + /** Выполнить какую-либо фоновую работу. Например, объединение кусков в таблице типа MergeTree. * Возвращает - была ли выполнена какая-либо работа. */ @@ -167,7 +242,7 @@ public: /** Не дает удалить БД до удаления таблицы. Присваивается перед удалением таблицы или БД. */ DatabaseDropperPtr database_to_drop; - + bool drop_on_destroy; /** Директория с данными. Будет удалена после удаления таблицы (после вызова dropImpl). @@ -179,7 +254,30 @@ protected: private: boost::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::vector StorageVector; + } diff --git a/dbms/include/DB/Storages/IColumnsDeclaration.h b/dbms/include/DB/Storages/ITableDeclaration.h similarity index 89% rename from dbms/include/DB/Storages/IColumnsDeclaration.h rename to dbms/include/DB/Storages/ITableDeclaration.h index 217e06e8827..1307d1cc5e0 100644 --- a/dbms/include/DB/Storages/IColumnsDeclaration.h +++ b/dbms/include/DB/Storages/ITableDeclaration.h @@ -11,12 +11,14 @@ namespace DB class Context; -/** Описание столбцов таблицы. +/** Описание таблицы. + * Не thread safe. См. IStorage::lockStructure(). */ -class IColumnsDeclaration +class ITableDeclaration { public: - /// Имя таблицы. Ни на что не влияет, используется только для сообщений об ошибках. + /** Имя таблицы. + */ virtual std::string getTableName() const { return ""; } /** Получить список имён и типов столбцов таблицы, только невиртуальные. @@ -59,7 +61,7 @@ public: /// реализация alter, модифицирующая список столбцов. static void alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context); - virtual ~IColumnsDeclaration() {} + virtual ~ITableDeclaration() {} }; } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h index 44422fce13f..eb716cfe50a 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockInputStream.h @@ -13,22 +13,18 @@ namespace DB class MergeTreeBlockInputStream : public IProfilingBlockInputStream { public: - /// Параметры storage_ и owned_storage разделены, чтобы можно было сделать поток, не владеющий своим storage - /// (например, поток, сливаящий куски). В таком случае сам storage должен следить, чтобы не удалить данные, пока их читают. MergeTreeBlockInputStream(const String & path_, /// Путь к куску - MergeTreeData::LockedTableStructurePtr structure_lock_, size_t block_size_, const Names & column_names_, MergeTreeData & storage_, const MergeTreeData::DataPartPtr & owned_data_part_, - const MarkRanges & mark_ranges_, StoragePtr owned_storage, bool use_uncompressed_cache_, - ExpressionActionsPtr prewhere_actions_, String prewhere_column_, bool take_read_lock) - : IProfilingBlockInputStream(owned_storage), + const MarkRanges & mark_ranges_, bool use_uncompressed_cache_, + ExpressionActionsPtr prewhere_actions_, String prewhere_column_) + : 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_), - log(&Logger::get("MergeTreeBlockInputStream")), - structure_lock(structure_lock_) + log(&Logger::get("MergeTreeBlockInputStream")) { std::reverse(remaining_mark_ranges.begin(), remaining_mark_ranges.end()); @@ -60,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]; @@ -88,9 +84,9 @@ protected: if (!reader) { UncompressedCache * uncompressed_cache = use_uncompressed_cache ? storage.context.getUncompressedCache() : NULL; - reader = new MergeTreeReader(path, column_names, uncompressed_cache, storage, structure_lock); + reader = new MergeTreeReader(path, column_names, uncompressed_cache, storage); if (prewhere_actions) - pre_reader = new MergeTreeReader(path, pre_column_names, uncompressed_cache, storage, structure_lock); + pre_reader = new MergeTreeReader(path, pre_column_names, uncompressed_cache, storage); } if (prewhere_actions) @@ -272,8 +268,6 @@ private: bool remove_prewhere_column; Logger * log; - - MergeTreeData::LockedTableStructurePtr structure_lock; }; } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h index 097ea2b6cd1..b382b660471 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeBlockOutputStream.h @@ -8,25 +8,23 @@ namespace DB class MergeTreeBlockOutputStream : public IBlockOutputStream { public: - MergeTreeBlockOutputStream(StoragePtr storage_) - : IBlockOutputStream(storage_), storage(dynamic_cast(*storage_)), - structure(storage.data.getLockedStructure(true)) {} + MergeTreeBlockOutputStream(StorageMergeTree & storage_) + : storage(storage_) {} void write(const Block & block) { - auto part_blocks = storage.writer.splitBlockIntoParts(block, structure); + auto part_blocks = storage.writer.splitBlockIntoParts(block); for (auto & current_block : part_blocks) { UInt64 temp_index = storage.increment.get(); - MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, temp_index, structure); - storage.data.renameTempPartAndAdd(part, &storage.increment, structure); + MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, temp_index); + storage.data.renameTempPartAndAdd(part, &storage.increment); storage.merge(2); } } private: StorageMergeTree & storage; - MergeTreeData::LockedTableStructurePtr structure; }; } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index 4dab22b75c8..5d03c3f80eb 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -93,7 +93,7 @@ struct MergeTreeSettings time_t old_parts_lifetime = 5 * 60; }; -class MergeTreeData +class MergeTreeData : public ITableDeclaration { public: /// Описание куска с данными. @@ -240,13 +240,6 @@ public: const String & sign_column_, const MergeTreeSettings & settings_); - /** - * owning_storage используется только чтобы отдавать его потокам блоков. - */ - void setOwningStorage(StoragePtr storage) { owning_storage = storage; } - - StoragePtr getOwningStorage() const { return owning_storage; } - std::string getModePrefix() const; std::string getSignColumnName() const { return sign_column; } @@ -264,49 +257,11 @@ public: /// Кладет в DataPart данные из имени кусочка. void parsePartName(const String & file_name, const Poco::RegularExpression::MatchVec & matches, DataPart & part); - /** Изменяемая часть описания таблицы. Содержит лок, запрещающий изменение описания таблицы. - * Если в течение какой-то операции структура таблицы должна оставаться неизменной, нужно держать один лок на все ее время. - * Например, нужно держать такой лок на время всего запроса SELECT или INSERT и на все время слияния набора кусков - * (но между выбором кусков для слияния и их слиянием структура таблицы может измениться). - * NOTE: Можно перенести сюда другие поля, чтобы сделать их динамически изменяемыми. - * Например, index_granularity, sign_column, primary_expr_ast. - * NOTE: Можно вынести эту штуку в IStorage и брать ее в Interpreter-ах, - * чтобы избавиться от оставшихся небольших race conditions. - * Скорее всего, даже можно заменить этой штукой весь механизм отложенного дропа таблиц и убрать owned_storage из потоков блоков. - */ - class LockedTableStructure : public IColumnsDeclaration - { - public: - const NamesAndTypesList & getColumnsList() const { return *data.columns; } + std::string getTableName() { return ""; } - String getFullPath() const { return data.full_path; } + const NamesAndTypesList & getColumnsList() const { return *columns; } - private: - friend class MergeTreeData; - - const MergeTreeData & data; - Poco::SharedPtr parts_lock; - Poco::SharedPtr structure_lock; - - LockedTableStructure(const MergeTreeData & data_, bool lock_structure, bool lock_writing) - : data(data_), - parts_lock(lock_writing ? new Poco::ScopedReadRWLock(data.parts_writing_lock) : nullptr), - structure_lock(lock_structure ? new Poco::ScopedReadRWLock(data.table_structure_lock) : nullptr) {} - }; - - typedef Poco::SharedPtr LockedTableStructurePtr; - - /** Если в рамках этого лока будут добавлены или удалены куски данных, обязательно указать will_modify_parts=true. - * Это возьмет дополнительный лок, не позволяющий начать ALTER MODIFY. - */ - LockedTableStructurePtr getLockedStructure(bool will_modify_parts) const - { - return new LockedTableStructure(*this, true, will_modify_parts); - } - - typedef Poco::SharedPtr TableStructureWriteLockPtr; - - TableStructureWriteLockPtr lockStructure() { return new Poco::ScopedWriteRWLock(table_structure_lock); } + String getFullPath() const { return full_path; } /** Возвращает копию списка, чтобы снаружи можно было не заботиться о блокировках. */ @@ -319,8 +274,7 @@ public: /** Переименовывает временный кусок в постоянный и добавляет его в рабочий набор. * Если increment!=nullptr, индекс куска берется из инкремента. Иначе индекс куска не меняется. */ - void renameTempPartAndAdd(MutableDataPartPtr part, Increment * increment, - const LockedTableStructurePtr & structure); + void renameTempPartAndAdd(MutableDataPartPtr part, Increment * increment); /** Удалить неактуальные куски. */ @@ -339,7 +293,7 @@ public: /** Метод ALTER позволяет добавлять и удалять столбцы и менять их тип. * Нужно вызывать под залоченным lockStructure(). - * TODO: сделать, чтобы ALTER MODIFY не лочил чтения надолго. На долгую часть достаточно лочить parts_writing_lock. + * TODO: сделать, чтобы ALTER MODIFY не лочил чтения надолго. */ void alter(const ASTAlterQuery::Parameters & params); @@ -364,8 +318,6 @@ private: SortDescription sort_descr; Block primary_key_sample; - StorageWeakPtr owning_storage; - ASTPtr primary_expr_ast; String full_path; @@ -378,27 +330,6 @@ private: /// Регулярное выражение соответсвующее названию директории с кусочками Poco::RegularExpression file_name_regexp; - /// Брать следующие два лока всегда нужно в этом порядке. - - /** Берется на чтение на все время запроса INSERT и на все время слияния кусков. Берется на запись на все время ALTER MODIFY. - * - * Формально: - * Ввзятие на запись гарантирует, что: - * 1) множество кусков не изменится, пока лок жив, - * 2) все операции над множеством кусков после отпускания лока будут основаны на структуре таблицы на момент после отпускания лока. - * Взятие на чтение обязательно, если будет добавляться или удалять кусок. - * Брать на чтение нужно до получения структуры таблицы, которой будет соответствовать добавляемый кусок. - */ - mutable Poco::RWLock parts_writing_lock; - - /** Лок для множества столбцов и пути к таблице. Берется на запись в RENAME и ALTER (для ALTER MODIFY ненадолго). - * - * Взятие этого лока на запись - строго более "сильная" операция, чем взятие parts_writing_lock на запись. - * То есть, если этот лок взят на запись, о parts_writing_lock можно не заботиться. - * parts_writing_lock нужен только для случаев, когда не хочется брать table_structure_lock надолго. - */ - mutable Poco::RWLock table_structure_lock; - /** Актуальное множество кусков с данными. */ DataParts data_parts; Poco::FastMutex data_parts_mutex; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h index 72b03c24952..7ea10726200 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h @@ -55,8 +55,7 @@ private: size_t max_block_size, bool use_uncompressed_cache, ExpressionActionsPtr prewhere_actions, - const String & prewhere_column, - const MergeTreeData::LockedTableStructurePtr & structure); + const String & prewhere_column); BlockInputStreams spreadMarkRangesAmongThreadsFinal( RangesInDataParts parts, @@ -65,12 +64,10 @@ private: size_t max_block_size, bool use_uncompressed_cache, ExpressionActionsPtr prewhere_actions, - const String & prewhere_column, - const MergeTreeData::LockedTableStructurePtr & structure); + const String & prewhere_column); /// Создать выражение "Sign == 1". - void createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column, - const MergeTreeData::LockedTableStructurePtr & structure); + 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 index 87c4f149ff2..6e720bfd791 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h @@ -37,13 +37,13 @@ public: * (читай: разбивает строки по месяцам) * Работает детерминированно: если отдать на вход такой же блок, на выходе получатся такие же блоки в таком же порядке. */ - BlocksWithDateIntervals splitBlockIntoParts(const Block & block, const MergeTreeData::LockedTableStructurePtr & structure); + BlocksWithDateIntervals splitBlockIntoParts(const Block & block); /** Все строки должны относиться к одному месяцу. Возвращает название временного куска. * temp_index - значение left и right для нового куска. Можно будет изменить при переименовании. * Возвращает кусок с именем, начинающимся с tmp_, еще не добавленный в MergeTreeData. */ - MergeTreeData::MutableDataPartPtr writeTempPart(BlockWithDateInterval & block, UInt64 temp_index, const MergeTreeData::LockedTableStructurePtr & structure); + MergeTreeData::MutableDataPartPtr writeTempPart(BlockWithDateInterval & block, UInt64 temp_index); private: MergeTreeData & data; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h index 3c33a262244..f6ee8257d0b 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h @@ -39,13 +39,11 @@ class MergeTreeReader { public: MergeTreeReader(const String & path_, /// Путь к куску - const Names & columns_names_, bool use_uncompressed_cache_, MergeTreeData & storage_, - MergeTreeData::LockedTableStructurePtr structure_) - : path(path_), column_names(columns_names_), use_uncompressed_cache(use_uncompressed_cache_), storage(storage_), - structure(structure_) + 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) - addStream(*it, *structure->getDataTypeByName(*it)); + addStream(*it, *storage.getDataTypeByName(*it)); } /** Если столбцов нет в блоке, добавляет их, если есть - добавляет прочитанные значения к ним в конец. @@ -78,7 +76,7 @@ public: ColumnWithNameAndType column; column.name = *it; - column.type = structure->getDataTypeByName(*it); + column.type = storage.getDataTypeByName(*it); if (append) column.column = res.getByName(column.name).column; @@ -133,7 +131,7 @@ public: { ColumnWithNameAndType column; column.name = *it; - column.type = structure->getDataTypeByName(*it); + column.type = storage.getDataTypeByName(*it); /** Нужно превратить константный столбец в полноценный, так как в части блоков (из других кусков), * он может быть полноценным (а то интерпретатор может посчитать, что он константный везде). @@ -240,7 +238,6 @@ private: Names column_names; bool use_uncompressed_cache; MergeTreeData & storage; - MergeTreeData::LockedTableStructurePtr structure; 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 b90d527d74c..6a745460985 100644 --- a/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h +++ b/dbms/include/DB/Storages/MergeTree/MergedBlockOutputStream.h @@ -196,22 +196,22 @@ protected: class MergedBlockOutputStream : public IMergedBlockOutputStream { public: - MergedBlockOutputStream(MergeTreeData & storage_, MergeTreeData::LockedTableStructurePtr structure_, + MergedBlockOutputStream(MergeTreeData & storage_, UInt16 min_date, UInt16 max_date, UInt64 min_part_id, UInt64 max_part_id, UInt32 level) - : IMergedBlockOutputStream(storage_), structure(structure_), marks_count(0) + : IMergedBlockOutputStream(storage_), marks_count(0) { part_name = storage.getPartName( DayNum_t(min_date), DayNum_t(max_date), min_part_id, max_part_id, level); - part_tmp_path = structure->getFullPath() + "tmp_" + part_name + "/"; - part_res_path = structure->getFullPath() + 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); - columns_list = structure->getColumnsList(); + columns_list = storage.getColumnsList(); for (const auto & it : columns_list) addStream(part_tmp_path, it.first, *it.second); } @@ -286,7 +286,6 @@ public: } private: - MergeTreeData::LockedTableStructurePtr structure; NamesAndTypesList columns_list; String part_name; 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/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/StorageMemory.h b/dbms/include/DB/Storages/StorageMemory.h index bf18e22271a..67c543b57ba 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; diff --git a/dbms/include/DB/Storages/StorageMergeTree.h b/dbms/include/DB/Storages/StorageMergeTree.h index 970bd24093a..4a70a07d1ad 100644 --- a/dbms/include/DB/Storages/StorageMergeTree.h +++ b/dbms/include/DB/Storages/StorageMergeTree.h @@ -48,7 +48,7 @@ public: bool supportsFinal() const { return data.supportsFinal(); } bool supportsPrewhere() const { return data.supportsPrewhere(); } - const NamesAndTypesList & getColumnsList() const { return data.getLockedStructure(false)->getColumnsList(); } + const NamesAndTypesList & getColumnsList() const { return data.getColumnsList(); } BlockInputStreams read( const Names & column_names, @@ -77,13 +77,6 @@ public: /// Например если параллельно с INSERT выполнить ALTER, то ALTER выполниться, а INSERT бросит исключение void alter(const ASTAlterQuery::Parameters & params); - typedef MergeTreeData::TableStructureWriteLockPtr BigLockPtr; - - BigLockPtr lockAllOperations() - { - return data.lockStructure(); - } - private: String path; String name; diff --git a/dbms/include/DB/Storages/StorageTinyLog.h b/dbms/include/DB/Storages/StorageTinyLog.h index f47ef0c915a..2b43fbd24d6 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: diff --git a/dbms/src/Interpreters/InterpreterAlterQuery.cpp b/dbms/src/Interpreters/InterpreterAlterQuery.cpp index ff7533eed90..22a1a96fc75 100644 --- a/dbms/src/Interpreters/InterpreterAlterQuery.cpp +++ b/dbms/src/Interpreters/InterpreterAlterQuery.cpp @@ -46,11 +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(); + auto table_lock = table->lockStructureForAlter(); /// Poco::Mutex является рекурсивным, т.е. взятие мьютекса дважды из одного потока не приводит к блокировке Poco::ScopedLock lock(context.getMutex()); diff --git a/dbms/src/Interpreters/InterpreterInsertQuery.cpp b/dbms/src/Interpreters/InterpreterInsertQuery.cpp index 967db071f02..46a45215ee9 100644 --- a/dbms/src/Interpreters/InterpreterInsertQuery.cpp +++ b/dbms/src/Interpreters/InterpreterInsertQuery.cpp @@ -64,14 +64,20 @@ 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); + /// TODO: Взять также IStorage::TableStructureReadLock-и для всех затронутых materialized views. + out->addTableLock(table_lock); + /// Какой тип запроса: INSERT VALUES | INSERT FORMAT | INSERT SELECT? if (!query.select) { @@ -117,6 +123,8 @@ BlockOutputStreamPtr 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,6 +132,9 @@ BlockOutputStreamPtr InterpreterInsertQuery::execute() /// Создаем кортеж из нескольких стримов, в которые будем писать данные. BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns); + /// TODO: Взять также IStorage::TableStructureReadLock-и для всех затронутых materialized views. + out->addTableLock(table_lock); + /// Какой тип запроса: INSERT или INSERT SELECT? if (!query.select) return out; diff --git a/dbms/src/Interpreters/InterpreterRenameQuery.cpp b/dbms/src/Interpreters/InterpreterRenameQuery.cpp index 246d1c3d17c..efb8c7b113b 100644 --- a/dbms/src/Interpreters/InterpreterRenameQuery.cpp +++ b/dbms/src/Interpreters/InterpreterRenameQuery.cpp @@ -57,7 +57,9 @@ void InterpreterRenameQuery::execute() 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); + StoragePtr table = context.getTable(from_database_name, from_table_name); + auto table_lock = table->lockStructureForAlter(); // TODO: Тут возможен дедлок. + 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 6dc8a892e85..d0f48e276aa 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,16 +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; @@ -436,25 +440,13 @@ 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)) - { - /// Запрос из обычной таблицы или без секции FROM. - table = getTable(); - } - else if (dynamic_cast(&*query.table)) + if (dynamic_cast(&*query.table)) { /** Для подзапроса не действуют ограничения на максимальный размер результата. * Так как результат поздапроса - ещё не результат всего запроса. @@ -477,13 +469,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); /** При распределённой обработке запроса, в потоках почти не делается вычислений, @@ -498,7 +490,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; /// Ограничение на количество столбцов для чтения. @@ -528,9 +520,17 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu /// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос? 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); + { + 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 источников. * (Иначе действия в каждом маленьком источнике, а затем объединение состояний, слишком неэффективно.) @@ -542,7 +542,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; diff --git a/dbms/src/Storages/IColumnsDeclaration.cpp b/dbms/src/Storages/ITableDeclaration.cpp similarity index 91% rename from dbms/src/Storages/IColumnsDeclaration.cpp rename to dbms/src/Storages/ITableDeclaration.cpp index b6b21b904ba..3de3049fa62 100644 --- a/dbms/src/Storages/IColumnsDeclaration.cpp +++ b/dbms/src/Storages/ITableDeclaration.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -10,7 +10,7 @@ namespace DB { -bool IColumnsDeclaration::hasRealColumn(const String &column_name) const +bool ITableDeclaration::hasRealColumn(const String &column_name) const { const NamesAndTypesList & real_columns = getColumnsList(); for (auto & it : real_columns) @@ -20,7 +20,7 @@ bool IColumnsDeclaration::hasRealColumn(const String &column_name) const } -NameAndTypePair IColumnsDeclaration::getRealColumn(const String &column_name) const +NameAndTypePair ITableDeclaration::getRealColumn(const String &column_name) const { const NamesAndTypesList & real_columns = getColumnsList(); for (auto & it : real_columns) @@ -30,19 +30,19 @@ NameAndTypePair IColumnsDeclaration::getRealColumn(const String &column_name) co } -bool IColumnsDeclaration::hasColumn(const String &column_name) const +bool ITableDeclaration::hasColumn(const String &column_name) const { return hasRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет. } -NameAndTypePair IColumnsDeclaration::getColumn(const String &column_name) const +NameAndTypePair ITableDeclaration::getColumn(const String &column_name) const { return getRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет. } -const DataTypePtr IColumnsDeclaration::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) @@ -53,7 +53,7 @@ const DataTypePtr IColumnsDeclaration::getDataTypeByName(const String & column_n } -Block IColumnsDeclaration::getSampleBlock() const +Block ITableDeclaration::getSampleBlock() const { Block res; const NamesAndTypesList & names_and_types = getColumnsList(); @@ -99,7 +99,7 @@ static NamesAndTypesMap getColumnsMap(const NamesAndTypesList & available_column } -void IColumnsDeclaration::check(const Names & column_names) const +void ITableDeclaration::check(const Names & column_names) const { const NamesAndTypesList & available_columns = getColumnsList(); @@ -129,7 +129,7 @@ void IColumnsDeclaration::check(const Names & column_names) const } -void IColumnsDeclaration::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); @@ -176,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 IColumnsDeclaration::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) +void ITableDeclaration::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) { if (params.type == ASTAlterQuery::ADD) { diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index d321ea37734..0d6af769b3f 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -412,7 +412,7 @@ void MergeTreeData::alter(const ASTAlterQuery::Parameters & params) 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 = getDataTypeByName(name_type.name, *columns); + 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())) @@ -428,8 +428,7 @@ void MergeTreeData::alter(const ASTAlterQuery::Parameters & params) { MarkRanges ranges(1, MarkRange(0, part->size)); ExpressionBlockInputStream in(new MergeTreeBlockInputStream(full_path + part->name + '/', - new LockedTableStructure(*this, false, false), /// Не блокируем структуру таблицы, она уже заблокирована снаружи. - DEFAULT_MERGE_BLOCK_SIZE, column_name, *this, part, ranges, StoragePtr(), false, NULL, ""), expr); + DEFAULT_MERGE_BLOCK_SIZE, column_name, *this, part, ranges, false, NULL, ""), expr); MergedColumnOnlyOutputStream out(*this, full_path + part->name + '/', true); out.writePrefix(); @@ -498,7 +497,7 @@ void MergeTreeData::alter(const ASTAlterQuery::Parameters & params) { Poco::ScopedLock lock(data_parts_mutex); Poco::ScopedLock lock_all(all_data_parts_mutex); - IColumnsDeclaration::alterColumns(params, columns, context); + alterColumns(params, columns, context); } if (params.type == ASTAlterQuery::DROP) { @@ -628,15 +627,14 @@ void MergeTreeData::replaceParts(DataPartsVector old_parts, DataPartPtr new_part data_parts.erase(data_parts.find(old_parts[i])); } -void MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr part, Increment * increment, - const MergeTreeData::LockedTableStructurePtr & structure) +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 = structure->getFullPath() + part->name + "/"; + String old_path = getFullPath() + part->name + "/"; UInt64 part_id = part->left; @@ -650,7 +648,7 @@ void MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr part, Increment * in part->left = part->right = part_id; part->name = getPartName(part->left_date, part->right_date, part_id, part_id, 0); - String new_path = structure->getFullPath() + part->name + "/"; + String new_path = getFullPath() + part->name + "/"; /// Переименовываем кусок. Poco::File(old_path).renameTo(new_path); diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp index 20384fb9103..160b4dc2b95 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp @@ -232,10 +232,8 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa { LOG_DEBUG(log, "Merging " << parts.size() << " parts: from " << parts.front()->name << " to " << parts.back()->name); - auto structure = data.getLockedStructure(true); - Names all_column_names; - NamesAndTypesList columns_list = structure->getColumnsList(); + NamesAndTypesList columns_list = data.getColumnsList(); for (const auto & it : columns_list) all_column_names.push_back(it.first); @@ -268,8 +266,8 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa { MarkRanges ranges(1, MarkRange(0, parts[i]->size)); src_streams.push_back(new ExpressionBlockInputStream(new MergeTreeBlockInputStream( - structure->getFullPath() + parts[i]->name + '/', structure, DEFAULT_MERGE_BLOCK_SIZE, all_column_names, data, parts[i], ranges, - StoragePtr(), false, NULL, ""), data.getPrimaryExpression())); + data.getFullPath() + parts[i]->name + '/', DEFAULT_MERGE_BLOCK_SIZE, all_column_names, data, + parts[i], ranges, false, NULL, ""), data.getPrimaryExpression())); } /// Порядок потоков важен: при совпадении ключа элементы идут в порядке номера потока-источника. @@ -294,7 +292,7 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa throw Exception("Unknown mode of operation for MergeTreeData: " + toString(data.mode), ErrorCodes::LOGICAL_ERROR); } - MergedBlockOutputStreamPtr to = new MergedBlockOutputStream(data, structure, + 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(); diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index ddcc811901d..07931d21c34 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -27,13 +27,11 @@ BlockInputStreams MergeTreeDataSelectExecutor::read( size_t max_block_size, unsigned threads) { - MergeTreeData::LockedTableStructurePtr structure = data.getLockedStructure(false); - - structure->check(column_names_to_return); + data.check(column_names_to_return); processed_stage = QueryProcessingStage::FetchColumns; - PKCondition key_condition(query, data.context, structure->getColumnsList(), data.getSortDescription()); - PKCondition date_condition(query, data.context, structure->getColumnsList(), SortDescription(1, SortColumnDescription(data.date_column_name, 1))); + 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; @@ -120,7 +118,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read( filter_function->arguments = filter_function_args; filter_function->children.push_back(filter_function->arguments); - filter_expression = ExpressionAnalyzer(filter_function, data.context, structure->getColumnsList()).getActions(false); + filter_expression = ExpressionAnalyzer(filter_function, data.context, data.getColumnsList()).getActions(false); /// Добавим столбцы, нужные для sampling_expression. std::vector add_columns = filter_expression->getRequiredColumns(); @@ -137,7 +135,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read( String prewhere_column; if (select.prewhere_expression) { - ExpressionAnalyzer analyzer(select.prewhere_expression, data.context, structure->getColumnsList()); + ExpressionAnalyzer analyzer(select.prewhere_expression, data.context, data.getColumnsList()); prewhere_actions = analyzer.getActions(false); prewhere_column = select.prewhere_expression->getColumnName(); } @@ -186,8 +184,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read( max_block_size, settings.use_uncompressed_cache, prewhere_actions, - prewhere_column, - structure); + prewhere_column); } else { @@ -198,8 +195,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read( max_block_size, settings.use_uncompressed_cache, prewhere_actions, - prewhere_column, - structure); + prewhere_column); } if (select.sample_size) @@ -223,8 +219,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreads( size_t max_block_size, bool use_uncompressed_cache, ExpressionActionsPtr prewhere_actions, - const String & prewhere_column, - const MergeTreeData::LockedTableStructurePtr & structure) + const String & prewhere_column) { /// На всякий случай перемешаем куски. std::random_shuffle(parts.begin(), parts.end()); @@ -283,8 +278,8 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreads( std::reverse(part.ranges.begin(), part.ranges.end()); streams.push_back(new MergeTreeBlockInputStream( - structure->getFullPath() + part.data_part->name + '/', structure, max_block_size, column_names, data, - part.data_part, part.ranges, data.getOwningStorage(), use_uncompressed_cache, + 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(); @@ -313,8 +308,8 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreads( } streams.push_back(new MergeTreeBlockInputStream( - structure->getFullPath() + part.data_part->name + '/', structure, max_block_size, column_names, data, - part.data_part, ranges_to_get_from_part, data.getOwningStorage(), use_uncompressed_cache, + 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)); } @@ -338,8 +333,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreadsFinal size_t max_block_size, bool use_uncompressed_cache, ExpressionActionsPtr prewhere_actions, - const String & prewhere_column, - const MergeTreeData::LockedTableStructurePtr & structure) + const String & prewhere_column) { size_t sum_marks = 0; for (size_t i = 0; i < parts.size(); ++i) @@ -351,7 +345,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreadsFinal ExpressionActionsPtr sign_filter_expression; String sign_filter_column; - createPositiveSignCondition(sign_filter_expression, sign_filter_column, structure); + createPositiveSignCondition(sign_filter_expression, sign_filter_column); BlockInputStreams to_collapse; @@ -360,8 +354,8 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreadsFinal RangesInDataPart & part = parts[part_index]; BlockInputStreamPtr source_stream = new MergeTreeBlockInputStream( - structure->getFullPath() + part.data_part->name + '/', structure, max_block_size, column_names, data, - part.data_part, part.ranges, data.getOwningStorage(), use_uncompressed_cache, + 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())); @@ -376,8 +370,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreadsFinal return res; } -void MergeTreeDataSelectExecutor::createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column, - const MergeTreeData::LockedTableStructurePtr & structure) +void MergeTreeDataSelectExecutor::createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column) { ASTFunction * function = new ASTFunction; ASTPtr function_ptr = function; @@ -404,7 +397,7 @@ void MergeTreeDataSelectExecutor::createPositiveSignCondition(ExpressionActionsP one->type = new DataTypeInt8; one->value = Field(static_cast(1)); - out_expression = ExpressionAnalyzer(function_ptr, data.context, structure->getColumnsList()).getActions(false); + out_expression = ExpressionAnalyzer(function_ptr, data.context, data.getColumnsList()).getActions(false); out_column = function->getColumnName(); } diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp index bb9d41568b4..a7c1550c7ee 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp @@ -6,9 +6,9 @@ namespace DB { -BlocksWithDateIntervals MergeTreeDataWriter::splitBlockIntoParts(const Block & block, const MergeTreeData::LockedTableStructurePtr & structure) +BlocksWithDateIntervals MergeTreeDataWriter::splitBlockIntoParts(const Block & block) { - structure->check(block, true); + data.check(block, true); DateLUTSingleton & date_lut = DateLUTSingleton::instance(); @@ -69,8 +69,7 @@ BlocksWithDateIntervals MergeTreeDataWriter::splitBlockIntoParts(const Block & b return res; } -MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDateInterval & block_with_dates, UInt64 temp_index, - const MergeTreeData::LockedTableStructurePtr & structure) +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; @@ -86,7 +85,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDa DayNum_t(min_date), DayNum_t(max_date), temp_index, temp_index, 0); - String part_tmp_path = structure->getFullPath() + tmp_part_name + "/"; + String part_tmp_path = data.getFullPath() + tmp_part_name + "/"; Poco::File(part_tmp_path).createDirectories(); diff --git a/dbms/src/Storages/StorageDistributed.cpp b/dbms/src/Storages/StorageDistributed.cpp index ede4fb735ad..908868bac43 100644 --- a/dbms/src/Storages/StorageDistributed.cpp +++ b/dbms/src/Storages/StorageDistributed.cpp @@ -239,10 +239,6 @@ BlockInputStreams StorageDistributed::read( } } - /// Не дадим уничтожать объект до конца обработки запроса. - for (auto & stream : res) - stream->setOwnedStorage(thisPtr()); - return res; } 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/StorageMemory.cpp b/dbms/src/Storages/StorageMemory.cpp index f63ee7b0f7d..0aa3cb5d917 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,7 +102,7 @@ BlockInputStreams StorageMemory::read( BlockOutputStreamPtr StorageMemory::write( ASTPtr query) { - return new MemoryBlockOutputStream(thisPtr()); + return new MemoryBlockOutputStream(*this); } diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index 9b38477a71c..ac603df1a0b 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -39,12 +39,9 @@ StoragePtr StorageMergeTree::create( const String & sign_column_, const MergeTreeSettings & settings_) { - StorageMergeTree * storage = new StorageMergeTree( + return (new StorageMergeTree( path_, name_, columns_, context_, primary_expr_ast_, date_column_name_, - sampling_expression_, index_granularity_, mode_, sign_column_, settings_); - StoragePtr ptr = storage->thisPtr(); - storage->data.setOwningStorage(ptr); - return ptr; + sampling_expression_, index_granularity_, mode_, sign_column_, settings_))->thisPtr(); } void StorageMergeTree::shutdown() @@ -76,7 +73,7 @@ BlockInputStreams StorageMergeTree::read( BlockOutputStreamPtr StorageMergeTree::write(ASTPtr query) { - return new MergeTreeBlockOutputStream(thisPtr()); + return new MergeTreeBlockOutputStream(*this); } void StorageMergeTree::dropImpl() @@ -88,8 +85,6 @@ void StorageMergeTree::dropImpl() void StorageMergeTree::rename(const String & new_path_to_db, const String & new_name) { - BigLockPtr lock = lockAllOperations(); - std::string new_full_path = new_path_to_db + escapeForFileName(new_name) + '/'; data.setPath(new_full_path); @@ -103,8 +98,6 @@ void StorageMergeTree::rename(const String & new_path_to_db, const String & new_ void StorageMergeTree::alter(const ASTAlterQuery::Parameters & params) { - /// InterpreterAlterQuery уже взял BigLock. - data.alter(params); } @@ -158,13 +151,17 @@ void StorageMergeTree::mergeThread(bool while_can, bool aggressive) } } - if (!merger.selectPartsToMerge(parts, disk_space, false, aggressive, only_small, can_merge) && - !merger.selectPartsToMerge(parts, disk_space, true, aggressive, only_small, can_merge)) - 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); } diff --git a/dbms/src/Storages/StorageTinyLog.cpp b/dbms/src/Storages/StorageTinyLog.cpp index ec365d0321a..63e527e700f 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,14 +376,14 @@ 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); } From 4f46874933992b2f30d7bda78f35c1fb3fef1557 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Wed, 19 Mar 2014 15:44:41 +0400 Subject: [PATCH 30/51] added a couple of comments. [#METR-10202] --- dbms/include/DB/Interpreters/ExpressionAnalyzer.h | 2 ++ dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp | 1 + 2 files changed, 3 insertions(+) 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/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index 07931d21c34..ae60e78e71d 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -138,6 +138,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read( 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; From d123bbc271aa726a5471a9b5f5216004e79a487d Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Wed, 19 Mar 2014 16:01:39 +0400 Subject: [PATCH 31/51] clickhouse: fixed bad_cast when formatting AST with PREWHERE. [#METR-10504] --- dbms/src/Parsers/formatAST.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dbms/src/Parsers/formatAST.cpp b/dbms/src/Parsers/formatAST.cpp index 505d31228eb..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) From 730079e99dee88f22c0ede3b53e808fdc14e47e5 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Wed, 19 Mar 2014 16:35:27 +0400 Subject: [PATCH 32/51] dbms: fixed bug with incorrect to_stage calculation in storage distributed [METR-10499] --- dbms/include/DB/Common/VirtualColumnUtils.h | 8 ++++---- dbms/src/Storages/StorageChunkMerger.cpp | 2 +- dbms/src/Storages/StorageChunks.cpp | 2 +- dbms/src/Storages/StorageDistributed.cpp | 4 ++-- dbms/src/Storages/StorageMerge.cpp | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dbms/include/DB/Common/VirtualColumnUtils.h b/dbms/include/DB/Common/VirtualColumnUtils.h index c6a2328eb61..ea44e41c119 100644 --- a/dbms/include/DB/Common/VirtualColumnUtils.h +++ b/dbms/include/DB/Common/VirtualColumnUtils.h @@ -35,9 +35,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) { @@ -52,10 +52,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/src/Storages/StorageChunkMerger.cpp b/dbms/src/Storages/StorageChunkMerger.cpp index cba0e3bd0a3..c69c7d43a11 100644 --- a/dbms/src/Storages/StorageChunkMerger.cpp +++ b/dbms/src/Storages/StorageChunkMerger.cpp @@ -130,7 +130,7 @@ 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) 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..7ca9d698be5 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 diff --git a/dbms/src/Storages/StorageMerge.cpp b/dbms/src/Storages/StorageMerge.cpp index 204ff60b203..d13fad3f01b 100644 --- a/dbms/src/Storages/StorageMerge.cpp +++ b/dbms/src/Storages/StorageMerge.cpp @@ -81,7 +81,7 @@ 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) From e10125a052d4fc1277a15cc16e6ff8a842995856 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 20 Mar 2014 14:59:45 +0400 Subject: [PATCH 33/51] Using IStorage locks properly in most places. Got rid of StoragePtr and DatabaseDropper. Drops are now synchronous. [#METR-10202] --- dbms/include/DB/Core/ErrorCodes.h | 2 + .../PushingToViewsBlockOutputStream.h | 3 + dbms/include/DB/Interpreters/Context.h | 7 - .../DB/Interpreters/InterpreterDropQuery.h | 3 + .../DB/Interpreters/InterpreterInsertQuery.h | 7 +- .../DB/Interpreters/InterpreterSelectQuery.h | 4 - .../Interpreters/InterpreterShowCreateQuery.h | 15 +- dbms/include/DB/Storages/DatabaseDropper.h | 37 ---- dbms/include/DB/Storages/IStorage.h | 68 ++++--- dbms/include/DB/Storages/StorageMerge.h | 2 - dbms/include/DB/Storages/StoragePtr.h | 107 ----------- .../DB/TableFunctions/ITableFunction.h | 2 +- dbms/src/Common/VirtualColumnUtils.cpp | 2 +- dbms/src/Interpreters/Context.cpp | 35 ++-- .../Interpreters/InterpreterCreateQuery.cpp | 32 ++-- .../src/Interpreters/InterpreterDropQuery.cpp | 175 ++++++++++-------- .../Interpreters/InterpreterInsertQuery.cpp | 17 +- dbms/src/Interpreters/InterpreterQuery.cpp | 3 +- .../Interpreters/InterpreterRenameQuery.cpp | 14 +- .../Interpreters/InterpreterSelectQuery.cpp | 10 - dbms/src/Storages/StorageChunkMerger.cpp | 40 ++-- dbms/src/Storages/StorageMerge.cpp | 38 ++-- dbms/src/Storages/StoragePtr.cpp | 35 ---- 23 files changed, 248 insertions(+), 410 deletions(-) delete mode 100644 dbms/include/DB/Storages/DatabaseDropper.h delete mode 100644 dbms/include/DB/Storages/StoragePtr.h delete mode 100644 dbms/src/Storages/StoragePtr.cpp 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/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/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/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/InterpreterSelectQuery.h b/dbms/include/DB/Interpreters/InterpreterSelectQuery.h index 73a3baf271f..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; 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 24bcb53d2d4..9d6f1ec7fd8 100644 --- a/dbms/include/DB/Storages/IStorage.h +++ b/dbms/include/DB/Storages/IStorage.h @@ -10,12 +10,9 @@ #include #include #include -#include #include -#include #include #include -#include namespace DB @@ -91,7 +88,10 @@ public: */ TableStructureReadLockPtr lockStructure(bool will_modify_data) { - return new TableStructureReadLock(*this, true, 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; @@ -99,13 +99,25 @@ public: /** Не дает читать структуру таблицы. Берется для ALTER, RENAME и DROP. */ - TableStructureWriteLockPtr lockStructureForAlter() { return new Poco::ScopedWriteRWLock(structure_lock); } + TableStructureWriteLockPtr lockStructureForAlter() + { + TableStructureWriteLockPtr res = new Poco::ScopedWriteRWLock(structure_lock); + if (is_dropped) + throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED); + return res; + } /** Не дает изменять данные в таблице. (Более того, не дает посмотреть на структуру таблицы с намерением изменить данные). * Берется на время записи временных данных в ALTER MODIFY. * Под этим локом можно брать lockStructureForAlter(), чтобы изменить структуру таблицы. */ - TableDataWriteLockPtr lockDataForAlter() { return new Poco::ScopedWriteRWLock(data_lock); } + TableDataWriteLockPtr lockDataForAlter() + { + TableDataWriteLockPtr res = new Poco::ScopedWriteRWLock(data_lock); + if (is_dropped) + throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED); + return res; + } /** Читать набор столбцов из таблицы. @@ -150,17 +162,10 @@ 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(); /** Переименовать таблицу. * Переименование имени в файле с метаданными, имени в списке таблиц в оперативке, осуществляется отдельно. @@ -220,40 +225,30 @@ public: /** Если при уничтожении объекта надо сделать какую-то сложную работу - сделать её заранее. * Например, если таблица содержит какие-нибудь потоки для фоновой работы - попросить их завершиться и дождаться завершения. * По-умолчанию - ничего не делать. + * Может вызываться одновременно из разных потоков, даже после вызова drop(). */ virtual void shutdown() {} - + /** Возвращает владеющий указатель на себя. */ - StoragePtr thisPtr() + std::shared_ptr thisPtr() { - if (!this_ptr.lock()) + std::shared_ptr res = this_ptr.lock(); + if (!res) { - auto p = boost::make_shared(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) {} + IStorage() : is_dropped(false) {} private: - boost::weak_ptr this_ptr; + std::weak_ptr this_ptr; /// Брать следующие два лока всегда нужно в этом порядке. @@ -278,6 +273,7 @@ private: mutable Poco::RWLock structure_lock; }; +typedef std::shared_ptr StoragePtr; typedef std::vector StorageVector; } diff --git a/dbms/include/DB/Storages/StorageMerge.h b/dbms/include/DB/Storages/StorageMerge.h index 9fe18d20dec..54163a682ae 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_, /// Имя таблицы. diff --git a/dbms/include/DB/Storages/StoragePtr.h b/dbms/include/DB/Storages/StoragePtr.h deleted file mode 100644 index 96bd2a9a489..00000000000 --- a/dbms/include/DB/Storages/StoragePtr.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - - -namespace DB -{ - -class IStorage; -class StorageWeakPtr; - -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; - friend class StorageWeakPtr; - -public: - StoragePtr() {} - StoragePtr(const StoragePtr & p) : ptr(p.ptr) {} - inline StoragePtr(const StorageWeakPtr & p); - - 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); - } -}; - -class StorageWeakPtr -{ -public: - StorageWeakPtr() {} - StorageWeakPtr(const StoragePtr & p) : ptr(p.ptr) {} - - StoragePtr lock() - { - return StoragePtr(ptr); - } - -private: - friend class StoragePtr; - - boost::weak_ptr ptr; -}; - -inline StoragePtr::StoragePtr(const StorageWeakPtr & p) : ptr(p.ptr) {} - -} 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/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/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..25c45e735b5 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->lockStructureForAlter(); + + 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->lockStructureForAlter(); + + 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 46a45215ee9..f8242d30717 100644 --- a/dbms/src/Interpreters/InterpreterInsertQuery.cpp +++ b/dbms/src/Interpreters/InterpreterInsertQuery.cpp @@ -75,9 +75,6 @@ void InterpreterInsertQuery::execute(ReadBuffer * remaining_data_istr) BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns); - /// TODO: Взять также IStorage::TableStructureReadLock-и для всех затронутых materialized views. - out->addTableLock(table_lock); - /// Какой тип запроса: INSERT VALUES | INSERT FORMAT | INSERT SELECT? if (!query.select) { @@ -118,7 +115,7 @@ void InterpreterInsertQuery::execute(ReadBuffer * remaining_data_istr) } -BlockOutputStreamPtr InterpreterInsertQuery::execute() +BlockIO InterpreterInsertQuery::execute() { ASTInsertQuery & query = dynamic_cast(*query_ptr); StoragePtr table = getTable(); @@ -132,21 +129,23 @@ BlockOutputStreamPtr InterpreterInsertQuery::execute() /// Создаем кортеж из нескольких стримов, в которые будем писать данные. BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns); - /// TODO: Взять также IStorage::TableStructureReadLock-и для всех затронутых materialized views. - out->addTableLock(table_lock); + 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 efb8c7b113b..37ea5d47eb2 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,12 +50,17 @@ 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->lockStructureForAlter(); + + /** Все таблицы переименовываются под глобальной блокировкой. */ + Poco::ScopedLock lock(context.getMutex()); + context.assertTableDoesntExist(to_database_name, to_table_name); /// Уведомляем таблицу о том, что она переименовается. Если таблица не поддерживает переименование - кинется исключение. - StoragePtr table = context.getTable(from_database_name, from_table_name); - auto table_lock = table->lockStructureForAlter(); // TODO: Тут возможен дедлок. + 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 d0f48e276aa..ebc74a1117f 100644 --- a/dbms/src/Interpreters/InterpreterSelectQuery.cpp +++ b/dbms/src/Interpreters/InterpreterSelectQuery.cpp @@ -141,16 +141,6 @@ void InterpreterSelectQuery::getDatabaseAndTableNames(String & database_name, St } -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; diff --git a/dbms/src/Storages/StorageChunkMerger.cpp b/dbms/src/Storages/StorageChunkMerger.cpp index cba0e3bd0a3..bc0dafc4797 100644 --- a/dbms/src/Storages/StorageChunkMerger.cpp +++ b/dbms/src/Storages/StorageChunkMerger.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -387,16 +388,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); @@ -455,6 +456,9 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks) } /// Атомарно подменим исходные таблицы ссылками на новую. + /// При этом удалять таблицы под мьютексом контекста нельзя, пока только отцепим их. + Storages tables_to_drop; + { Poco::ScopedLock lock(context.getMutex()); @@ -467,13 +471,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 +494,14 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks) currently_written_groups.erase(new_table_full_name); } + /// Теперь удалим данные отцепленных таблиц. + 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/StorageMerge.cpp b/dbms/src/Storages/StorageMerge.cpp index 204ff60b203..13b36c809ff 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,15 @@ BlockInputStreams StorageMerge::read( getSelectedTables(selected_tables); } + typedef std::vector TableLocks; + 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; @@ -84,20 +93,23 @@ BlockInputStreams StorageMerge::read( std::set 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 +117,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 +151,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 +162,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/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(...) - { - } -} - -} From 059e0f7e11e4159de026669e6350855709da8dff Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 20 Mar 2014 15:01:56 +0400 Subject: [PATCH 34/51] Fixed build. [#METR-10202] --- dbms/include/DB/Storages/IStorage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbms/include/DB/Storages/IStorage.h b/dbms/include/DB/Storages/IStorage.h index 9d6f1ec7fd8..8674fec2e84 100644 --- a/dbms/include/DB/Storages/IStorage.h +++ b/dbms/include/DB/Storages/IStorage.h @@ -165,7 +165,7 @@ public: /** Удалить данные таблицы. Вызывается перед удалением директории с данными. * Если не требуется никаких действий, кроме удаления директории с данными, этот метод можно оставить пустым. */ - virtual void drop(); + virtual void drop() {} /** Переименовать таблицу. * Переименование имени в файле с метаданными, имени в списке таблиц в оперативке, осуществляется отдельно. From c5da5fd5f09384005a49251b8ff47fbdc64636c7 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 20 Mar 2014 17:00:42 +0400 Subject: [PATCH 35/51] Merge --- dbms/include/DB/Storages/IStorage.h | 29 ++- .../DB/Storages/MergeTree/MergeTreeData.h | 7 +- dbms/include/DB/Storages/StorageMergeTree.h | 5 +- .../DB/TableFunctions/TableFunctionMerge.h | 27 ++- .../Interpreters/InterpreterAlterQuery.cpp | 30 ++- .../src/Interpreters/InterpreterDropQuery.cpp | 4 +- .../Interpreters/InterpreterRenameQuery.cpp | 2 +- dbms/src/Storages/MergeTree/MergeTreeData.cpp | 213 ++++++++++-------- dbms/src/Storages/StorageChunkMerger.cpp | 45 +++- dbms/src/Storages/StorageMerge.cpp | 1 - dbms/src/Storages/StorageMergeTree.cpp | 10 + 11 files changed, 230 insertions(+), 143 deletions(-) diff --git a/dbms/include/DB/Storages/IStorage.h b/dbms/include/DB/Storages/IStorage.h index 8674fec2e84..90c068aa008 100644 --- a/dbms/include/DB/Storages/IStorage.h +++ b/dbms/include/DB/Storages/IStorage.h @@ -96,15 +96,13 @@ public: typedef Poco::SharedPtr TableStructureWriteLockPtr; typedef Poco::SharedPtr TableDataWriteLockPtr; + typedef std::pair TableFullWriteLockPtr; /** Не дает читать структуру таблицы. Берется для ALTER, RENAME и DROP. */ - TableStructureWriteLockPtr lockStructureForAlter() + TableFullWriteLockPtr lockForAlter() { - TableStructureWriteLockPtr res = new Poco::ScopedWriteRWLock(structure_lock); - if (is_dropped) - throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED); - return res; + return std::make_pair(lockDataForAlter(), lockStructureForAlter()); } /** Не дает изменять данные в таблице. (Более того, не дает посмотреть на структуру таблицы с намерением изменить данные). @@ -119,6 +117,14 @@ public: 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; + } + /** Читать набор столбцов из таблицы. * Принимает список столбцов, которых нужно прочитать, а также описание запроса, @@ -180,28 +186,26 @@ public: /** ALTER таблицы в виде изменения столбцов, не затрагивающий изменение Storage или его параметров. * (ALTER, затрагивающий изменение движка, делается внешним кодом, путём копирования данных.) * Вызывается при заблокированной на запись структуре таблицы. - * Для ALTER MODIFY используются другие методы (см. ниже). TODO: Пока эта строчка не верна, и для ALTER MODIFY используется метод 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 (изменение типа столбца) выполняется в два вызова: + /** ALTER MODIFY (изменение типа столбца) выполняется в два вызова: * Сначала вызывается prepareAlterModify при заблокированной записи данных, но незаблокированной структуре таблицы. * В нем можно выполнить долгую работу по записи сконвертированных данных, оставляя доступными существующие данные. * Потом вызывается commitAlterModify при заблокированной структуре таблицы. * В нем нужно закончить изменение типа столбца. + * Для движков с тривиальным ALTER MODIFY можно оставить реализацию по умолчанию, вызывающую alter. */ - virtual void prepareAlterModify(const ASTAlterQuery::Parameters & params) - { - throw Exception("Method prepareAlterModify is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); - } + virtual void prepareAlterModify(const ASTAlterQuery::Parameters & params) {} virtual void commitAlterModify(const ASTAlterQuery::Parameters & params) { - throw Exception("Method commitAlterModify is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); + alter(params); } /** Выполнить какую-либо фоновую работу. Например, объединение кусков в таблице типа MergeTree. @@ -275,5 +279,6 @@ private: typedef std::shared_ptr StoragePtr; typedef std::vector StorageVector; +typedef IStorage::TableStructureReadLocks TableLocks; } diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index 5d03c3f80eb..593eef78765 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -291,12 +291,9 @@ public: */ void setPath(const String & full_path); - /** Метод ALTER позволяет добавлять и удалять столбцы и менять их тип. - * Нужно вызывать под залоченным lockStructure(). - * TODO: сделать, чтобы ALTER MODIFY не лочил чтения надолго. - */ 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; } diff --git a/dbms/include/DB/Storages/StorageMergeTree.h b/dbms/include/DB/Storages/StorageMergeTree.h index 4a70a07d1ad..f1d38fc10d7 100644 --- a/dbms/include/DB/Storages/StorageMergeTree.h +++ b/dbms/include/DB/Storages/StorageMergeTree.h @@ -72,10 +72,9 @@ public: void rename(const String & new_path_to_db, const String & new_name); - /// Метод ALTER позволяет добавлять и удалять столбцы. - /// Метод ALTER нужно применять, когда обращения к базе приостановлены. - /// Например если параллельно с INSERT выполнить ALTER, то ALTER выполниться, а INSERT бросит исключение void alter(const ASTAlterQuery::Parameters & params); + void prepareAlterModify(const ASTAlterQuery::Parameters & params); + void commitAlterModify(const ASTAlterQuery::Parameters & params); private: String path; 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/src/Interpreters/InterpreterAlterQuery.cpp b/dbms/src/Interpreters/InterpreterAlterQuery.cpp index 22a1a96fc75..5de38fc697c 100644 --- a/dbms/src/Interpreters/InterpreterAlterQuery.cpp +++ b/dbms/src/Interpreters/InterpreterAlterQuery.cpp @@ -46,10 +46,7 @@ void InterpreterAlterQuery::execute() String database_name = alter.database.empty() ? context.getCurrentDatabase() : alter.database; StoragePtr table = context.getTable(database_name, table_name); - auto table_lock = table->lockStructureForAlter(); - - /// Poco::Mutex является рекурсивным, т.е. взятие мьютекса дважды из одного потока не приводит к блокировке - Poco::ScopedLock lock(context.getMutex()); + auto table_soft_lock = table->lockDataForAlter(); const DataTypeFactory & data_type_factory = context.getDataTypeFactory(); String path = context.getPath(); @@ -64,7 +61,6 @@ void InterpreterAlterQuery::execute() attach.attach = true; ASTs & columns = dynamic_cast(*attach.columns).children; - /// Различные проверки, на возможность выполнения запроса ASTs columns_copy = columns; IdentifierNameSet identifier_names; @@ -133,13 +129,35 @@ void InterpreterAlterQuery::execute() } } + /// Пока разрешим читать из таблицы. Запретим при первой попытке изменить структуру таблицы. + /// Это позволит сделать большую часть первого MODIFY, не останавливая чтение из таблицы. + IStorage::TableStructureWriteLockPtr table_hard_lock; + + Poco::ScopedLock lock(context.getMutex()); + /// 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/InterpreterDropQuery.cpp b/dbms/src/Interpreters/InterpreterDropQuery.cpp index 25c45e735b5..53c5dd18999 100644 --- a/dbms/src/Interpreters/InterpreterDropQuery.cpp +++ b/dbms/src/Interpreters/InterpreterDropQuery.cpp @@ -67,7 +67,7 @@ void InterpreterDropQuery::execute() table->shutdown(); /// Если кто-то успел удалить эту таблицу, выбросит исключение. - auto table_lock = table->lockStructureForAlter(); + auto table_lock = table->lockForAlter(); String current_table_name = table->getTableName(); @@ -117,7 +117,7 @@ void InterpreterDropQuery::dropDetachedTable(String database_name, StoragePtr ta { table->shutdown(); - auto table_lock = table->lockStructureForAlter(); + auto table_lock = table->lockForAlter(); String table_name = table->getTableName(); diff --git a/dbms/src/Interpreters/InterpreterRenameQuery.cpp b/dbms/src/Interpreters/InterpreterRenameQuery.cpp index 37ea5d47eb2..6c55ec2c99a 100644 --- a/dbms/src/Interpreters/InterpreterRenameQuery.cpp +++ b/dbms/src/Interpreters/InterpreterRenameQuery.cpp @@ -52,7 +52,7 @@ void InterpreterRenameQuery::execute() /// Заблокировать таблицу нужно при незаблокированном контексте. StoragePtr table = context.getTable(from_database_name, from_table_name); - auto table_lock = table->lockStructureForAlter(); + auto table_lock = table->lockForAlter(); /** Все таблицы переименовываются под глобальной блокировкой. */ Poco::ScopedLock lock(context.getMutex()); diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index 0d6af769b3f..580504feb0e 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -399,101 +399,6 @@ static DataTypePtr getDataTypeByName(const String & name, const NamesAndTypesLis void MergeTreeData::alter(const ASTAlterQuery::Parameters & params) { - if (params.type == ASTAlterQuery::MODIFY) - { - { - 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; - } - } - - /// переименовываем файлы - /// переименовываем старые столбцы, добавляя расширение .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); @@ -509,6 +414,124 @@ void MergeTreeData::alter(const ASTAlterQuery::Parameters & params) } } +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 { diff --git a/dbms/src/Storages/StorageChunkMerger.cpp b/dbms/src/Storages/StorageChunkMerger.cpp index bc0dafc4797..dd38c981770 100644 --- a/dbms/src/Storages/StorageChunkMerger.cpp +++ b/dbms/src/Storages/StorageChunkMerger.cpp @@ -86,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)) @@ -98,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"); } } } @@ -110,6 +110,14 @@ BlockInputStreams StorageChunkMerger::read( } } + TableLocks table_locks; + + /// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем. + for (auto table : selected_tables) + { + table_locks.push_back(table->lockStructure(false)); + } + BlockInputStreams res; /// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную. @@ -134,30 +142,33 @@ BlockInputStreams StorageChunkMerger::read( std::set 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, @@ -165,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) { @@ -172,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); } } } @@ -336,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()); @@ -436,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(); @@ -495,6 +519,7 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks) } /// Теперь удалим данные отцепленных таблиц. + table_locks.clear(); for (StoragePtr table : tables_to_drop) { InterpreterDropQuery::dropDetachedTable(source_database, table, context); diff --git a/dbms/src/Storages/StorageMerge.cpp b/dbms/src/Storages/StorageMerge.cpp index 13b36c809ff..82d618f6491 100644 --- a/dbms/src/Storages/StorageMerge.cpp +++ b/dbms/src/Storages/StorageMerge.cpp @@ -72,7 +72,6 @@ BlockInputStreams StorageMerge::read( getSelectedTables(selected_tables); } - typedef std::vector TableLocks; TableLocks table_locks; /// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем. diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index ac603df1a0b..80daf41a528 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -101,6 +101,16 @@ void StorageMergeTree::alter(const ASTAlterQuery::Parameters & params) data.alter(params); } +void StorageMergeTree::prepareAlterModify(const ASTAlterQuery::Parameters & params) +{ + data.prepareAlterModify(params); +} + +void StorageMergeTree::commitAlterModify(const ASTAlterQuery::Parameters & params) +{ + data.commitAlterModify(params); +} + void StorageMergeTree::merge(size_t iterations, bool async, bool aggressive) { bool while_can = false; From 6df8500852f7ec8808db20557c7684c08c9b6667 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 20 Mar 2014 17:28:49 +0400 Subject: [PATCH 36/51] Fixed dropping tables. [#METR-10202] --- dbms/include/DB/Storages/StorageChunkRef.h | 2 +- dbms/include/DB/Storages/StorageDistributed.h | 2 +- dbms/include/DB/Storages/StorageMaterializedView.h | 2 +- dbms/include/DB/Storages/StorageMemory.h | 2 +- dbms/include/DB/Storages/StorageMerge.h | 2 +- dbms/include/DB/Storages/StorageMergeTree.h | 2 +- dbms/include/DB/Storages/StorageTinyLog.h | 2 +- dbms/include/DB/Storages/StorageView.h | 2 +- dbms/src/Storages/StorageChunkRef.cpp | 2 +- dbms/src/Storages/StorageMaterializedView.cpp | 3 ++- dbms/src/Storages/StorageMemory.cpp | 2 +- dbms/src/Storages/StorageMergeTree.cpp | 2 +- dbms/src/Storages/StorageTinyLog.cpp | 2 +- dbms/src/Storages/StorageView.cpp | 2 +- 14 files changed, 15 insertions(+), 14 deletions(-) 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/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 67c543b57ba..e6ab8d97b4b 100644 --- a/dbms/include/DB/Storages/StorageMemory.h +++ b/dbms/include/DB/Storages/StorageMemory.h @@ -80,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 54163a682ae..f0a474f1e6a 100644 --- a/dbms/include/DB/Storages/StorageMerge.h +++ b/dbms/include/DB/Storages/StorageMerge.h @@ -41,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 f1d38fc10d7..bb8c5b8cf45 100644 --- a/dbms/include/DB/Storages/StorageMergeTree.h +++ b/dbms/include/DB/Storages/StorageMergeTree.h @@ -68,7 +68,7 @@ public: return true; } - void dropImpl(); + void drop() override; void rename(const String & new_path_to_db, const String & new_name); diff --git a/dbms/include/DB/Storages/StorageTinyLog.h b/dbms/include/DB/Storages/StorageTinyLog.h index 2b43fbd24d6..682efb70215 100644 --- a/dbms/include/DB/Storages/StorageTinyLog.h +++ b/dbms/include/DB/Storages/StorageTinyLog.h @@ -125,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/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/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 0aa3cb5d917..e790106ddc7 100644 --- a/dbms/src/Storages/StorageMemory.cpp +++ b/dbms/src/Storages/StorageMemory.cpp @@ -106,7 +106,7 @@ BlockOutputStreamPtr StorageMemory::write( } -void StorageMemory::dropImpl() +void StorageMemory::drop() { Poco::ScopedLock lock(mutex); data.clear(); diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index 80daf41a528..5e2b2dfd549 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -76,7 +76,7 @@ BlockOutputStreamPtr StorageMergeTree::write(ASTPtr query) return new MergeTreeBlockOutputStream(*this); } -void StorageMergeTree::dropImpl() +void StorageMergeTree::drop() { merger.cancelAll(); joinMergeThreads(); diff --git a/dbms/src/Storages/StorageTinyLog.cpp b/dbms/src/Storages/StorageTinyLog.cpp index 63e527e700f..ab179fb5492 100644 --- a/dbms/src/Storages/StorageTinyLog.cpp +++ b/dbms/src/Storages/StorageTinyLog.cpp @@ -387,7 +387,7 @@ BlockOutputStreamPtr StorageTinyLog::write( } -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)); } From 280dd19e637bfc6bc7dd60868e5501fde03dba16 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Thu, 20 Mar 2014 21:05:22 +0400 Subject: [PATCH 37/51] Fixed SELECT without table. [#METR-10202] --- dbms/src/Interpreters/InterpreterSelectQuery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dbms/src/Interpreters/InterpreterSelectQuery.cpp b/dbms/src/Interpreters/InterpreterSelectQuery.cpp index ebc74a1117f..518a42c2f39 100644 --- a/dbms/src/Interpreters/InterpreterSelectQuery.cpp +++ b/dbms/src/Interpreters/InterpreterSelectQuery.cpp @@ -436,7 +436,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu /// Список столбцов, которых нужно прочитать, чтобы выполнить запрос. Names required_columns = query_analyzer->getRequiredColumns(); - if (dynamic_cast(&*query.table)) + if (query.table && dynamic_cast(&*query.table)) { /** Для подзапроса не действуют ограничения на максимальный размер результата. * Так как результат поздапроса - ещё не результат всего запроса. @@ -509,7 +509,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns; /// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос? - if (!query.table || !dynamic_cast(&*query.table)) + 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) From 7dc31a3ba339e7773262ad55f359a7bf3e9333ad Mon Sep 17 00:00:00 2001 From: Pavel Kartavyy Date: Wed, 19 Mar 2014 20:00:56 +0400 Subject: [PATCH 38/51] olap compability: removed ClickPhraseID, ClickTargetPhraseID [#METR-10322] --- dbms/src/Server/OLAPAttributesMetadata.h | 4 ---- dbms/src/Server/OLAPQueryConverter.cpp | 2 -- 2 files changed, 6 deletions(-) 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") From 3a6d19d6391ee174690cc889700378e3ef8a19a5 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Fri, 21 Mar 2014 13:26:01 +0300 Subject: [PATCH 39/51] Removed unnecessary context lock during ALTER. [METR-10202] --- dbms/src/Interpreters/InterpreterAlterQuery.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/dbms/src/Interpreters/InterpreterAlterQuery.cpp b/dbms/src/Interpreters/InterpreterAlterQuery.cpp index 5de38fc697c..ae4808d338b 100644 --- a/dbms/src/Interpreters/InterpreterAlterQuery.cpp +++ b/dbms/src/Interpreters/InterpreterAlterQuery.cpp @@ -133,8 +133,6 @@ void InterpreterAlterQuery::execute() /// Это позволит сделать большую часть первого MODIFY, не останавливая чтение из таблицы. IStorage::TableStructureWriteLockPtr table_hard_lock; - Poco::ScopedLock lock(context.getMutex()); - /// todo cycle over sub tables and tables /// Применяем изменения for (ASTAlterQuery::ParameterContainer::const_iterator alter_it = alter.parameters.begin(); From 2ff699ce6f81dceb579e416cbdee1970b97bff14 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 22 Mar 2014 22:27:19 +0400 Subject: [PATCH 40/51] Merge From 40b02cae63cfca6fcab8d61329b8c92e9e0e9f9e Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Mar 2014 07:26:07 +0400 Subject: [PATCH 41/51] dbms: added test script [#METR-2944]. --- dbms/tests/clickhouse-test | 66 +++++++++++++++++++ .../queries/format_ast_and_remote_table.sql | 1 + dbms/tests/queries/select_1.reference | 1 + dbms/tests/queries/select_1.sql | 1 + dbms/tests/queries/system_numbers.reference | 10 +++ dbms/tests/queries/system_numbers.sql | 1 + 6 files changed, 80 insertions(+) create mode 100755 dbms/tests/clickhouse-test create mode 100644 dbms/tests/queries/format_ast_and_remote_table.sql create mode 100644 dbms/tests/queries/select_1.reference create mode 100644 dbms/tests/queries/select_1.sql create mode 100644 dbms/tests/queries/system_numbers.reference create mode 100644 dbms/tests/queries/system_numbers.sql diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test new file mode 100755 index 00000000000..40986eb6e52 --- /dev/null +++ b/dbms/tests/clickhouse-test @@ -0,0 +1,66 @@ +#!/bin/bash + +# Скрипт для тестирования запросов к ClickHouse. +# Из файлов *.sql в заданной директории, в алфавитном порядке, отправляются все запросы. +# Результат сравнивается с эталоном. + +QUERIES_DIR="./queries" +CLIENT_PROGRAM="clickhouse-client" + + +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}" + + +for query_file in $(ls $QUERIES_DIR/*.sql) +do + test_name=$(basename -s .sql $query_file) + + result_file=$QUERIES_DIR/$test_name.result + error_file=$QUERIES_DIR/$test_name.error + reference_file=$QUERIES_DIR/$test_name.reference + diff_file=$QUERIES_DIR/$test_name.diff + + printf "%-30s" "$test_name: " + + $CLIENT_PROGRAM < $query_file > $result_file 2> $error_file + ret_code=$? + + if [ $ret_code -ne 0 ]; then + 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 + echo -e "$MSG_FAIL - having stderror:" + cat $error_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 + echo -e "$MSG_FAIL - result differs with reference:" + cat $diff_file + else + echo -e "$MSG_OK" + fi + fi +done diff --git a/dbms/tests/queries/format_ast_and_remote_table.sql b/dbms/tests/queries/format_ast_and_remote_table.sql new file mode 100644 index 00000000000..dd20fd1d8e4 --- /dev/null +++ b/dbms/tests/queries/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/select_1.reference b/dbms/tests/queries/select_1.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/dbms/tests/queries/select_1.reference @@ -0,0 +1 @@ +1 diff --git a/dbms/tests/queries/select_1.sql b/dbms/tests/queries/select_1.sql new file mode 100644 index 00000000000..2e3761f7a2c --- /dev/null +++ b/dbms/tests/queries/select_1.sql @@ -0,0 +1 @@ +SELECT 1 diff --git a/dbms/tests/queries/system_numbers.reference b/dbms/tests/queries/system_numbers.reference new file mode 100644 index 00000000000..8b1acc12b63 --- /dev/null +++ b/dbms/tests/queries/system_numbers.reference @@ -0,0 +1,10 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 diff --git a/dbms/tests/queries/system_numbers.sql b/dbms/tests/queries/system_numbers.sql new file mode 100644 index 00000000000..bc9269495bc --- /dev/null +++ b/dbms/tests/queries/system_numbers.sql @@ -0,0 +1 @@ +SELECT * FROM system.numbers LIMIT 10 From 18a7749a0491cc84a7185812fb14293f443a6ae5 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Mar 2014 07:37:51 +0400 Subject: [PATCH 42/51] dbms: modified test [#METR-2944]. --- dbms/tests/clickhouse-test | 1 + 1 file changed, 1 insertion(+) diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test index 40986eb6e52..cfd6c85f82b 100755 --- a/dbms/tests/clickhouse-test +++ b/dbms/tests/clickhouse-test @@ -61,6 +61,7 @@ do cat $diff_file else echo -e "$MSG_OK" + rm $error_file $result_file $diff_file fi fi done From 84ce7d62258a077b0b738cb0498632236520d89b Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Mar 2014 07:40:09 +0400 Subject: [PATCH 43/51] dbms: modified test [#METR-2944]. --- dbms/tests/clickhouse-test | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test index cfd6c85f82b..2b7bec67bdc 100755 --- a/dbms/tests/clickhouse-test +++ b/dbms/tests/clickhouse-test @@ -19,6 +19,8 @@ MSG_UNKNOWN="${COLOR_WHITE}[ ${COLOR_UNKNOWN}UNKNOWN${COLOR_WHITE} ]${COLOR_RESE MSG_OK="${COLOR_WHITE}[ ${COLOR_OK}OK${COLOR_WHITE} ]${COLOR_RESET}" MSG_GENERATED="${COLOR_WHITE}[ ${COLOR_UNKNOWN}GENERATED${COLOR_WHITE} ]${COLOR_RESET}" +HAS_ERROR=0 + for query_file in $(ls $QUERIES_DIR/*.sql) do @@ -35,6 +37,7 @@ do ret_code=$? if [ $ret_code -ne 0 ]; then + HAS_ERROR=1 echo -e "$MSG_FAIL - return code $ret_code" if [ -s "$error_file" ]; then cat $error_file @@ -44,6 +47,7 @@ do exit 1; fi elif [ -s "$error_file" ]; then + HAS_ERROR=1 echo -e "$MSG_FAIL - having stderror:" cat $error_file elif [ ! -e "$reference_file" ]; then @@ -57,6 +61,7 @@ do else diff $reference_file $result_file > $diff_file if [ -s "$diff_file" ]; then + HAS_ERROR=1 echo -e "$MSG_FAIL - result differs with reference:" cat $diff_file else @@ -65,3 +70,5 @@ do fi fi done + +exit $HAS_ERROR From a646848ded048582bd83c019af0471f50d2aba9d Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Mar 2014 07:46:25 +0400 Subject: [PATCH 44/51] dbms: modified test [#METR-2944]. --- dbms/tests/clickhouse-test | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test index 2b7bec67bdc..5b853748301 100755 --- a/dbms/tests/clickhouse-test +++ b/dbms/tests/clickhouse-test @@ -19,7 +19,7 @@ MSG_UNKNOWN="${COLOR_WHITE}[ ${COLOR_UNKNOWN}UNKNOWN${COLOR_WHITE} ]${COLOR_RESE MSG_OK="${COLOR_WHITE}[ ${COLOR_OK}OK${COLOR_WHITE} ]${COLOR_RESET}" MSG_GENERATED="${COLOR_WHITE}[ ${COLOR_UNKNOWN}GENERATED${COLOR_WHITE} ]${COLOR_RESET}" -HAS_ERROR=0 +ERRORS=0 for query_file in $(ls $QUERIES_DIR/*.sql) @@ -37,7 +37,7 @@ do ret_code=$? if [ $ret_code -ne 0 ]; then - HAS_ERROR=1 + ERRORS=$(($ERRORS + 1)) echo -e "$MSG_FAIL - return code $ret_code" if [ -s "$error_file" ]; then cat $error_file @@ -47,7 +47,7 @@ do exit 1; fi elif [ -s "$error_file" ]; then - HAS_ERROR=1 + ERRORS=$(($ERRORS + 1)) echo -e "$MSG_FAIL - having stderror:" cat $error_file elif [ ! -e "$reference_file" ]; then @@ -61,7 +61,7 @@ do else diff $reference_file $result_file > $diff_file if [ -s "$diff_file" ]; then - HAS_ERROR=1 + ERRORS=$(($ERRORS + 1)) echo -e "$MSG_FAIL - result differs with reference:" cat $diff_file else @@ -71,4 +71,13 @@ do fi done -exit $HAS_ERROR + +echo + +if [ $ERRORS -gt 0 ]; then + echo -e "${COLOR_FAIL}Having $ERRORS errors!${COLOR_RESET}" + exit 1 +else + echo -e "${COLOR_OK}All tests succeeded.${COLOR_RESET}" + exit 0 +fi From 93472c9103e736dc583401e1204fd50aa6e9e529 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Mar 2014 19:08:31 +0400 Subject: [PATCH 45/51] dbms: added test [#METR-2944]. --- dbms/tests/queries/format_ast_and_remote_table.reference | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 dbms/tests/queries/format_ast_and_remote_table.reference diff --git a/dbms/tests/queries/format_ast_and_remote_table.reference b/dbms/tests/queries/format_ast_and_remote_table.reference new file mode 100644 index 00000000000..343ee5c2f6c --- /dev/null +++ b/dbms/tests/queries/format_ast_and_remote_table.reference @@ -0,0 +1,2 @@ +-1 +-1 From fc45d3cc5b6b322f8683055adc2c98ac7fe91d6e Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Mar 2014 19:16:58 +0400 Subject: [PATCH 46/51] dbms: mofified test [#METR-2944]. --- dbms/tests/clickhouse-test | 4 ++-- .../queries/{select_1.reference => 00001_select_1.reference} | 0 dbms/tests/queries/{select_1.sql => 00001_select_1.sql} | 0 ...ystem_numbers.reference => 00002_system_numbers.reference} | 0 .../queries/{system_numbers.sql => 00002_system_numbers.sql} | 0 dbms/tests/queries/00003_reinterpret_as_string.reference | 1 + dbms/tests/queries/00003_reinterpret_as_string.sql | 1 + ....reference => 00004_format_ast_and_remote_table.reference} | 0 ...remote_table.sql => 00004_format_ast_and_remote_table.sql} | 0 9 files changed, 4 insertions(+), 2 deletions(-) rename dbms/tests/queries/{select_1.reference => 00001_select_1.reference} (100%) rename dbms/tests/queries/{select_1.sql => 00001_select_1.sql} (100%) rename dbms/tests/queries/{system_numbers.reference => 00002_system_numbers.reference} (100%) rename dbms/tests/queries/{system_numbers.sql => 00002_system_numbers.sql} (100%) create mode 100644 dbms/tests/queries/00003_reinterpret_as_string.reference create mode 100644 dbms/tests/queries/00003_reinterpret_as_string.sql rename dbms/tests/queries/{format_ast_and_remote_table.reference => 00004_format_ast_and_remote_table.reference} (100%) rename dbms/tests/queries/{format_ast_and_remote_table.sql => 00004_format_ast_and_remote_table.sql} (100%) diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test index 5b853748301..60d66269645 100755 --- a/dbms/tests/clickhouse-test +++ b/dbms/tests/clickhouse-test @@ -2,7 +2,7 @@ # Скрипт для тестирования запросов к ClickHouse. # Из файлов *.sql в заданной директории, в алфавитном порядке, отправляются все запросы. -# Результат сравнивается с эталоном. +# Результаты сравниваются с эталонами. QUERIES_DIR="./queries" CLIENT_PROGRAM="clickhouse-client" @@ -31,7 +31,7 @@ do reference_file=$QUERIES_DIR/$test_name.reference diff_file=$QUERIES_DIR/$test_name.diff - printf "%-30s" "$test_name: " + printf "%-60s" "$test_name: " $CLIENT_PROGRAM < $query_file > $result_file 2> $error_file ret_code=$? diff --git a/dbms/tests/queries/select_1.reference b/dbms/tests/queries/00001_select_1.reference similarity index 100% rename from dbms/tests/queries/select_1.reference rename to dbms/tests/queries/00001_select_1.reference diff --git a/dbms/tests/queries/select_1.sql b/dbms/tests/queries/00001_select_1.sql similarity index 100% rename from dbms/tests/queries/select_1.sql rename to dbms/tests/queries/00001_select_1.sql diff --git a/dbms/tests/queries/system_numbers.reference b/dbms/tests/queries/00002_system_numbers.reference similarity index 100% rename from dbms/tests/queries/system_numbers.reference rename to dbms/tests/queries/00002_system_numbers.reference diff --git a/dbms/tests/queries/system_numbers.sql b/dbms/tests/queries/00002_system_numbers.sql similarity index 100% rename from dbms/tests/queries/system_numbers.sql rename to dbms/tests/queries/00002_system_numbers.sql diff --git a/dbms/tests/queries/00003_reinterpret_as_string.reference b/dbms/tests/queries/00003_reinterpret_as_string.reference new file mode 100644 index 00000000000..fc75583cf02 --- /dev/null +++ b/dbms/tests/queries/00003_reinterpret_as_string.reference @@ -0,0 +1 @@ +33232 diff --git a/dbms/tests/queries/00003_reinterpret_as_string.sql b/dbms/tests/queries/00003_reinterpret_as_string.sql new file mode 100644 index 00000000000..1204f6280f2 --- /dev/null +++ b/dbms/tests/queries/00003_reinterpret_as_string.sql @@ -0,0 +1 @@ +SELECT number FROM system.numbers WHERE reinterpretAsString(number) = 'Ё' LIMIT 1 diff --git a/dbms/tests/queries/format_ast_and_remote_table.reference b/dbms/tests/queries/00004_format_ast_and_remote_table.reference similarity index 100% rename from dbms/tests/queries/format_ast_and_remote_table.reference rename to dbms/tests/queries/00004_format_ast_and_remote_table.reference diff --git a/dbms/tests/queries/format_ast_and_remote_table.sql b/dbms/tests/queries/00004_format_ast_and_remote_table.sql similarity index 100% rename from dbms/tests/queries/format_ast_and_remote_table.sql rename to dbms/tests/queries/00004_format_ast_and_remote_table.sql From dae349ba00069620d212efb44160869eeff75947 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 24 Mar 2014 00:40:55 +0400 Subject: [PATCH 47/51] dbms: added tests [#METR-2944]. --- dbms/tests/clickhouse-test | 6 +++++- ...ormat_ast_and_remote_table_lambda.reference | 1 + ...0005_format_ast_and_remote_table_lambda.sql | 1 + .../queries/00006_1_set_extremes.reference | 0 dbms/tests/queries/00006_1_set_extremes.sql | 1 + ...0006_2_extremes_and_subquery_from.reference | 18 ++++++++++++++++++ .../00006_2_extremes_and_subquery_from.sql | 2 ++ .../queries/00006_3_unset_extremes.reference | 0 dbms/tests/queries/00006_3_unset_extremes.sql | 1 + dbms/tests/queries/00007_array.reference | 1 + dbms/tests/queries/00007_array.sql | 1 + dbms/tests/queries/00008_array_join.reference | 2 ++ dbms/tests/queries/00008_array_join.sql | 1 + .../00009_array_join_subquery.reference | 2 ++ .../queries/00009_array_join_subquery.sql | 1 + .../queries/00010_big_array_join.reference | 6 ++++++ dbms/tests/queries/00010_big_array_join.sql | 1 + .../queries/00011_array_join_alias.reference | 6 ++++++ dbms/tests/queries/00011_array_join_alias.sql | 1 + .../queries/00012_array_join_alias_2.reference | 6 ++++++ .../tests/queries/00012_array_join_alias_2.sql | 1 + ..._drop_if_exists_table_with_arrays.reference | 0 ...0013_1_drop_if_exists_table_with_arrays.sql | 1 + .../00013_2_create_table_with_arrays.reference | 0 .../00013_2_create_table_with_arrays.sql | 1 + ...3_3_insert_into_table_with_arrays.reference | 0 .../00013_3_insert_into_table_with_arrays.sql | 1 + ...3_4_select_from_table_with_arrays.reference | 3 +++ .../00013_4_select_from_table_with_arrays.sql | 1 + ...3_5_select_from_table_with_arrays.reference | 5 +++++ .../00013_5_select_from_table_with_arrays.sql | 1 + ...3_6_select_from_table_with_arrays.reference | 5 +++++ .../00013_6_select_from_table_with_arrays.sql | 1 + ...3_7_select_from_table_with_arrays.reference | 5 +++++ .../00013_7_select_from_table_with_arrays.sql | 1 + ...3_8_select_from_table_with_arrays.reference | 5 +++++ .../00013_8_select_from_table_with_arrays.sql | 1 + ...3_9_select_from_table_with_arrays.reference | 5 +++++ .../00013_9_select_from_table_with_arrays.sql | 1 + ...3_a_select_from_table_with_arrays.reference | 5 +++++ .../00013_a_select_from_table_with_arrays.sql | 1 + ..._drop_if_exists_table_with_nested.reference | 0 ...0014_1_drop_if_exists_table_with_nested.sql | 1 + .../00014_2_create_table_with_nested.reference | 0 .../00014_2_create_table_with_nested.sql | 1 + ...4_3_insert_into_table_with_nested.reference | 0 .../00014_3_insert_into_table_with_nested.sql | 1 + ...4_4_select_from_table_with_nested.reference | 3 +++ .../00014_4_select_from_table_with_nested.sql | 1 + ...4_5_select_from_table_with_nested.reference | 5 +++++ .../00014_5_select_from_table_with_nested.sql | 1 + ...4_6_select_from_table_with_nested.reference | 5 +++++ .../00014_6_select_from_table_with_nested.sql | 1 + ...4_7_select_from_table_with_nested.reference | 5 +++++ .../00014_7_select_from_table_with_nested.sql | 1 + ...4_9_select_from_table_with_nested.reference | 5 +++++ .../00014_9_select_from_table_with_nested.sql | 1 + ...4_a_select_from_table_with_nested.reference | 5 +++++ .../00014_a_select_from_table_with_nested.sql | 1 + ...4_b_select_from_table_with_nested.reference | 5 +++++ .../00014_b_select_from_table_with_nested.sql | 1 + ...4_c_select_from_table_with_nested.reference | 5 +++++ .../00014_c_select_from_table_with_nested.sql | 1 + .../00015_totals_having_constants.reference | 12 ++++++++++++ .../queries/00015_totals_having_constants.sql | 1 + .../00016_totals_having_constants.reference | 3 +++ .../queries/00016_totals_having_constants.sql | 1 + ...017_in_subquery_with_empty_result.reference | 18 ++++++++++++++++++ .../00017_in_subquery_with_empty_result.sql | 2 ++ .../00018_distinct_in_subquery.reference | 2 ++ .../queries/00018_distinct_in_subquery.sql | 1 + 71 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 dbms/tests/queries/00005_format_ast_and_remote_table_lambda.reference create mode 100644 dbms/tests/queries/00005_format_ast_and_remote_table_lambda.sql create mode 100644 dbms/tests/queries/00006_1_set_extremes.reference create mode 100644 dbms/tests/queries/00006_1_set_extremes.sql create mode 100644 dbms/tests/queries/00006_2_extremes_and_subquery_from.reference create mode 100644 dbms/tests/queries/00006_2_extremes_and_subquery_from.sql create mode 100644 dbms/tests/queries/00006_3_unset_extremes.reference create mode 100644 dbms/tests/queries/00006_3_unset_extremes.sql create mode 100644 dbms/tests/queries/00007_array.reference create mode 100644 dbms/tests/queries/00007_array.sql create mode 100644 dbms/tests/queries/00008_array_join.reference create mode 100644 dbms/tests/queries/00008_array_join.sql create mode 100644 dbms/tests/queries/00009_array_join_subquery.reference create mode 100644 dbms/tests/queries/00009_array_join_subquery.sql create mode 100644 dbms/tests/queries/00010_big_array_join.reference create mode 100644 dbms/tests/queries/00010_big_array_join.sql create mode 100644 dbms/tests/queries/00011_array_join_alias.reference create mode 100644 dbms/tests/queries/00011_array_join_alias.sql create mode 100644 dbms/tests/queries/00012_array_join_alias_2.reference create mode 100644 dbms/tests/queries/00012_array_join_alias_2.sql create mode 100644 dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.sql create mode 100644 dbms/tests/queries/00013_2_create_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_2_create_table_with_arrays.sql create mode 100644 dbms/tests/queries/00013_3_insert_into_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_3_insert_into_table_with_arrays.sql create mode 100644 dbms/tests/queries/00013_4_select_from_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_4_select_from_table_with_arrays.sql create mode 100644 dbms/tests/queries/00013_5_select_from_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_5_select_from_table_with_arrays.sql create mode 100644 dbms/tests/queries/00013_6_select_from_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_6_select_from_table_with_arrays.sql create mode 100644 dbms/tests/queries/00013_7_select_from_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_7_select_from_table_with_arrays.sql create mode 100644 dbms/tests/queries/00013_8_select_from_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_8_select_from_table_with_arrays.sql create mode 100644 dbms/tests/queries/00013_9_select_from_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_9_select_from_table_with_arrays.sql create mode 100644 dbms/tests/queries/00013_a_select_from_table_with_arrays.reference create mode 100644 dbms/tests/queries/00013_a_select_from_table_with_arrays.sql create mode 100644 dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_2_create_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_2_create_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_3_insert_into_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_3_insert_into_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_4_select_from_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_4_select_from_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_5_select_from_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_5_select_from_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_6_select_from_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_6_select_from_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_7_select_from_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_7_select_from_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_9_select_from_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_9_select_from_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_a_select_from_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_a_select_from_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_b_select_from_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_b_select_from_table_with_nested.sql create mode 100644 dbms/tests/queries/00014_c_select_from_table_with_nested.reference create mode 100644 dbms/tests/queries/00014_c_select_from_table_with_nested.sql create mode 100644 dbms/tests/queries/00015_totals_having_constants.reference create mode 100644 dbms/tests/queries/00015_totals_having_constants.sql create mode 100644 dbms/tests/queries/00016_totals_having_constants.reference create mode 100644 dbms/tests/queries/00016_totals_having_constants.sql create mode 100644 dbms/tests/queries/00017_in_subquery_with_empty_result.reference create mode 100644 dbms/tests/queries/00017_in_subquery_with_empty_result.sql create mode 100644 dbms/tests/queries/00018_distinct_in_subquery.reference create mode 100644 dbms/tests/queries/00018_distinct_in_subquery.sql diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test index 60d66269645..1bfd91ab468 100755 --- a/dbms/tests/clickhouse-test +++ b/dbms/tests/clickhouse-test @@ -5,7 +5,7 @@ # Результаты сравниваются с эталонами. QUERIES_DIR="./queries" -CLIENT_PROGRAM="clickhouse-client" +CLIENT_PROGRAM="curl -sS http://localhost:8123/ --data-binary @-" COLOR_RESET="\033[0m" @@ -50,6 +50,10 @@ do 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 diff --git a/dbms/tests/queries/00005_format_ast_and_remote_table_lambda.reference b/dbms/tests/queries/00005_format_ast_and_remote_table_lambda.reference new file mode 100644 index 00000000000..0cfbf08886f --- /dev/null +++ b/dbms/tests/queries/00005_format_ast_and_remote_table_lambda.reference @@ -0,0 +1 @@ +2 diff --git a/dbms/tests/queries/00005_format_ast_and_remote_table_lambda.sql b/dbms/tests/queries/00005_format_ast_and_remote_table_lambda.sql new file mode 100644 index 00000000000..fb401df5184 --- /dev/null +++ b/dbms/tests/queries/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/00006_1_set_extremes.reference b/dbms/tests/queries/00006_1_set_extremes.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/00006_1_set_extremes.sql b/dbms/tests/queries/00006_1_set_extremes.sql new file mode 100644 index 00000000000..53bd0c96ce2 --- /dev/null +++ b/dbms/tests/queries/00006_1_set_extremes.sql @@ -0,0 +1 @@ +SET GLOBAL extremes = 1 diff --git a/dbms/tests/queries/00006_2_extremes_and_subquery_from.reference b/dbms/tests/queries/00006_2_extremes_and_subquery_from.reference new file mode 100644 index 00000000000..841ae17708f --- /dev/null +++ b/dbms/tests/queries/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/00006_2_extremes_and_subquery_from.sql b/dbms/tests/queries/00006_2_extremes_and_subquery_from.sql new file mode 100644 index 00000000000..c8e1e1bf66f --- /dev/null +++ b/dbms/tests/queries/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/00006_3_unset_extremes.reference b/dbms/tests/queries/00006_3_unset_extremes.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/00006_3_unset_extremes.sql b/dbms/tests/queries/00006_3_unset_extremes.sql new file mode 100644 index 00000000000..31e8a97da82 --- /dev/null +++ b/dbms/tests/queries/00006_3_unset_extremes.sql @@ -0,0 +1 @@ +SET GLOBAL extremes = 0 diff --git a/dbms/tests/queries/00007_array.reference b/dbms/tests/queries/00007_array.reference new file mode 100644 index 00000000000..2a64c8ea7b2 --- /dev/null +++ b/dbms/tests/queries/00007_array.reference @@ -0,0 +1 @@ +['Hello','Goodbye'] diff --git a/dbms/tests/queries/00007_array.sql b/dbms/tests/queries/00007_array.sql new file mode 100644 index 00000000000..7c1f27f1978 --- /dev/null +++ b/dbms/tests/queries/00007_array.sql @@ -0,0 +1 @@ +SELECT ['Hello', 'Goodbye'] diff --git a/dbms/tests/queries/00008_array_join.reference b/dbms/tests/queries/00008_array_join.reference new file mode 100644 index 00000000000..c86756d1938 --- /dev/null +++ b/dbms/tests/queries/00008_array_join.reference @@ -0,0 +1,2 @@ +Hello +Goodbye diff --git a/dbms/tests/queries/00008_array_join.sql b/dbms/tests/queries/00008_array_join.sql new file mode 100644 index 00000000000..abb35cbbfc8 --- /dev/null +++ b/dbms/tests/queries/00008_array_join.sql @@ -0,0 +1 @@ +SELECT arrayJoin(['Hello', 'Goodbye']) diff --git a/dbms/tests/queries/00009_array_join_subquery.reference b/dbms/tests/queries/00009_array_join_subquery.reference new file mode 100644 index 00000000000..c86756d1938 --- /dev/null +++ b/dbms/tests/queries/00009_array_join_subquery.reference @@ -0,0 +1,2 @@ +Hello +Goodbye diff --git a/dbms/tests/queries/00009_array_join_subquery.sql b/dbms/tests/queries/00009_array_join_subquery.sql new file mode 100644 index 00000000000..378baadd026 --- /dev/null +++ b/dbms/tests/queries/00009_array_join_subquery.sql @@ -0,0 +1 @@ +SELECT x FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x) diff --git a/dbms/tests/queries/00010_big_array_join.reference b/dbms/tests/queries/00010_big_array_join.reference new file mode 100644 index 00000000000..e9c80bae6d0 --- /dev/null +++ b/dbms/tests/queries/00010_big_array_join.reference @@ -0,0 +1,6 @@ +Hello +Hello +Hello +Goodbye +Goodbye +Goodbye diff --git a/dbms/tests/queries/00010_big_array_join.sql b/dbms/tests/queries/00010_big_array_join.sql new file mode 100644 index 00000000000..f7b9160b578 --- /dev/null +++ b/dbms/tests/queries/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/00011_array_join_alias.reference b/dbms/tests/queries/00011_array_join_alias.reference new file mode 100644 index 00000000000..ff5ad16d763 --- /dev/null +++ b/dbms/tests/queries/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/00011_array_join_alias.sql b/dbms/tests/queries/00011_array_join_alias.sql new file mode 100644 index 00000000000..228038c1509 --- /dev/null +++ b/dbms/tests/queries/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/00012_array_join_alias_2.reference b/dbms/tests/queries/00012_array_join_alias_2.reference new file mode 100644 index 00000000000..ebad7b98d93 --- /dev/null +++ b/dbms/tests/queries/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/00012_array_join_alias_2.sql b/dbms/tests/queries/00012_array_join_alias_2.sql new file mode 100644 index 00000000000..a45cf2d87b8 --- /dev/null +++ b/dbms/tests/queries/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/00013_1_drop_if_exists_table_with_arrays.reference b/dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.sql b/dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.sql new file mode 100644 index 00000000000..8dbef3a0abb --- /dev/null +++ b/dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS arrays_test diff --git a/dbms/tests/queries/00013_2_create_table_with_arrays.reference b/dbms/tests/queries/00013_2_create_table_with_arrays.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/00013_2_create_table_with_arrays.sql b/dbms/tests/queries/00013_2_create_table_with_arrays.sql new file mode 100644 index 00000000000..8585ba14140 --- /dev/null +++ b/dbms/tests/queries/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/00013_3_insert_into_table_with_arrays.reference b/dbms/tests/queries/00013_3_insert_into_table_with_arrays.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/00013_3_insert_into_table_with_arrays.sql b/dbms/tests/queries/00013_3_insert_into_table_with_arrays.sql new file mode 100644 index 00000000000..5b41e56db66 --- /dev/null +++ b/dbms/tests/queries/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/00013_4_select_from_table_with_arrays.reference b/dbms/tests/queries/00013_4_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..5a5af40fc00 --- /dev/null +++ b/dbms/tests/queries/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/00013_4_select_from_table_with_arrays.sql b/dbms/tests/queries/00013_4_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..af4b5dd8538 --- /dev/null +++ b/dbms/tests/queries/00013_4_select_from_table_with_arrays.sql @@ -0,0 +1 @@ +SELECT * FROM arrays_test diff --git a/dbms/tests/queries/00013_5_select_from_table_with_arrays.reference b/dbms/tests/queries/00013_5_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..02ff6407fd6 --- /dev/null +++ b/dbms/tests/queries/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/00013_5_select_from_table_with_arrays.sql b/dbms/tests/queries/00013_5_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..238f888202d --- /dev/null +++ b/dbms/tests/queries/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/00013_6_select_from_table_with_arrays.reference b/dbms/tests/queries/00013_6_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..45d362a350e --- /dev/null +++ b/dbms/tests/queries/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/00013_6_select_from_table_with_arrays.sql b/dbms/tests/queries/00013_6_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..d1c9936af30 --- /dev/null +++ b/dbms/tests/queries/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/00013_7_select_from_table_with_arrays.reference b/dbms/tests/queries/00013_7_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..577260f94f0 --- /dev/null +++ b/dbms/tests/queries/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/00013_7_select_from_table_with_arrays.sql b/dbms/tests/queries/00013_7_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..a75b97e6faa --- /dev/null +++ b/dbms/tests/queries/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/00013_8_select_from_table_with_arrays.reference b/dbms/tests/queries/00013_8_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..e29fe6d82a1 --- /dev/null +++ b/dbms/tests/queries/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/00013_8_select_from_table_with_arrays.sql b/dbms/tests/queries/00013_8_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..d7b3a7b44b9 --- /dev/null +++ b/dbms/tests/queries/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/00013_9_select_from_table_with_arrays.reference b/dbms/tests/queries/00013_9_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..58328ca2588 --- /dev/null +++ b/dbms/tests/queries/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/00013_9_select_from_table_with_arrays.sql b/dbms/tests/queries/00013_9_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..010faea3d76 --- /dev/null +++ b/dbms/tests/queries/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/00013_a_select_from_table_with_arrays.reference b/dbms/tests/queries/00013_a_select_from_table_with_arrays.reference new file mode 100644 index 00000000000..14686f65be3 --- /dev/null +++ b/dbms/tests/queries/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/00013_a_select_from_table_with_arrays.sql b/dbms/tests/queries/00013_a_select_from_table_with_arrays.sql new file mode 100644 index 00000000000..1c4b2b02e19 --- /dev/null +++ b/dbms/tests/queries/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/00014_1_drop_if_exists_table_with_nested.reference b/dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.sql b/dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.sql new file mode 100644 index 00000000000..257afc58919 --- /dev/null +++ b/dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS nested_test diff --git a/dbms/tests/queries/00014_2_create_table_with_nested.reference b/dbms/tests/queries/00014_2_create_table_with_nested.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/00014_2_create_table_with_nested.sql b/dbms/tests/queries/00014_2_create_table_with_nested.sql new file mode 100644 index 00000000000..49f015ce1aa --- /dev/null +++ b/dbms/tests/queries/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/00014_3_insert_into_table_with_nested.reference b/dbms/tests/queries/00014_3_insert_into_table_with_nested.reference new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dbms/tests/queries/00014_3_insert_into_table_with_nested.sql b/dbms/tests/queries/00014_3_insert_into_table_with_nested.sql new file mode 100644 index 00000000000..44fdbc820ff --- /dev/null +++ b/dbms/tests/queries/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/00014_4_select_from_table_with_nested.reference b/dbms/tests/queries/00014_4_select_from_table_with_nested.reference new file mode 100644 index 00000000000..32bd08bad02 --- /dev/null +++ b/dbms/tests/queries/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/00014_4_select_from_table_with_nested.sql b/dbms/tests/queries/00014_4_select_from_table_with_nested.sql new file mode 100644 index 00000000000..509911959ac --- /dev/null +++ b/dbms/tests/queries/00014_4_select_from_table_with_nested.sql @@ -0,0 +1 @@ +SELECT * FROM nested_test diff --git a/dbms/tests/queries/00014_5_select_from_table_with_nested.reference b/dbms/tests/queries/00014_5_select_from_table_with_nested.reference new file mode 100644 index 00000000000..05200edd33b --- /dev/null +++ b/dbms/tests/queries/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/00014_5_select_from_table_with_nested.sql b/dbms/tests/queries/00014_5_select_from_table_with_nested.sql new file mode 100644 index 00000000000..31e1ac1b05e --- /dev/null +++ b/dbms/tests/queries/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/00014_6_select_from_table_with_nested.reference b/dbms/tests/queries/00014_6_select_from_table_with_nested.reference new file mode 100644 index 00000000000..8ea4568b73c --- /dev/null +++ b/dbms/tests/queries/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/00014_6_select_from_table_with_nested.sql b/dbms/tests/queries/00014_6_select_from_table_with_nested.sql new file mode 100644 index 00000000000..3648cb4852c --- /dev/null +++ b/dbms/tests/queries/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/00014_7_select_from_table_with_nested.reference b/dbms/tests/queries/00014_7_select_from_table_with_nested.reference new file mode 100644 index 00000000000..05200edd33b --- /dev/null +++ b/dbms/tests/queries/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/00014_7_select_from_table_with_nested.sql b/dbms/tests/queries/00014_7_select_from_table_with_nested.sql new file mode 100644 index 00000000000..9380d7d285f --- /dev/null +++ b/dbms/tests/queries/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/00014_9_select_from_table_with_nested.reference b/dbms/tests/queries/00014_9_select_from_table_with_nested.reference new file mode 100644 index 00000000000..05200edd33b --- /dev/null +++ b/dbms/tests/queries/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/00014_9_select_from_table_with_nested.sql b/dbms/tests/queries/00014_9_select_from_table_with_nested.sql new file mode 100644 index 00000000000..945f3dc79a1 --- /dev/null +++ b/dbms/tests/queries/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/00014_a_select_from_table_with_nested.reference b/dbms/tests/queries/00014_a_select_from_table_with_nested.reference new file mode 100644 index 00000000000..d40754269c6 --- /dev/null +++ b/dbms/tests/queries/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/00014_a_select_from_table_with_nested.sql b/dbms/tests/queries/00014_a_select_from_table_with_nested.sql new file mode 100644 index 00000000000..4e275aa4e12 --- /dev/null +++ b/dbms/tests/queries/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/00014_b_select_from_table_with_nested.reference b/dbms/tests/queries/00014_b_select_from_table_with_nested.reference new file mode 100644 index 00000000000..7129270657e --- /dev/null +++ b/dbms/tests/queries/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/00014_b_select_from_table_with_nested.sql b/dbms/tests/queries/00014_b_select_from_table_with_nested.sql new file mode 100644 index 00000000000..4643e428942 --- /dev/null +++ b/dbms/tests/queries/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/00014_c_select_from_table_with_nested.reference b/dbms/tests/queries/00014_c_select_from_table_with_nested.reference new file mode 100644 index 00000000000..fa7a8052ce9 --- /dev/null +++ b/dbms/tests/queries/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/00014_c_select_from_table_with_nested.sql b/dbms/tests/queries/00014_c_select_from_table_with_nested.sql new file mode 100644 index 00000000000..6033dd1c011 --- /dev/null +++ b/dbms/tests/queries/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/00015_totals_having_constants.reference b/dbms/tests/queries/00015_totals_having_constants.reference new file mode 100644 index 00000000000..1b62b5f7c43 --- /dev/null +++ b/dbms/tests/queries/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/00015_totals_having_constants.sql b/dbms/tests/queries/00015_totals_having_constants.sql new file mode 100644 index 00000000000..586bef3ac13 --- /dev/null +++ b/dbms/tests/queries/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/00016_totals_having_constants.reference b/dbms/tests/queries/00016_totals_having_constants.reference new file mode 100644 index 00000000000..4804131baba --- /dev/null +++ b/dbms/tests/queries/00016_totals_having_constants.reference @@ -0,0 +1,3 @@ +0 10 + +0 10 diff --git a/dbms/tests/queries/00016_totals_having_constants.sql b/dbms/tests/queries/00016_totals_having_constants.sql new file mode 100644 index 00000000000..c50659b8140 --- /dev/null +++ b/dbms/tests/queries/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/00017_in_subquery_with_empty_result.reference b/dbms/tests/queries/00017_in_subquery_with_empty_result.reference new file mode 100644 index 00000000000..e25c5780b65 --- /dev/null +++ b/dbms/tests/queries/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/00017_in_subquery_with_empty_result.sql b/dbms/tests/queries/00017_in_subquery_with_empty_result.sql new file mode 100644 index 00000000000..b83d597159d --- /dev/null +++ b/dbms/tests/queries/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/00018_distinct_in_subquery.reference b/dbms/tests/queries/00018_distinct_in_subquery.reference new file mode 100644 index 00000000000..6ed281c757a --- /dev/null +++ b/dbms/tests/queries/00018_distinct_in_subquery.reference @@ -0,0 +1,2 @@ +1 +1 diff --git a/dbms/tests/queries/00018_distinct_in_subquery.sql b/dbms/tests/queries/00018_distinct_in_subquery.sql new file mode 100644 index 00000000000..b0bce846904 --- /dev/null +++ b/dbms/tests/queries/00018_distinct_in_subquery.sql @@ -0,0 +1 @@ +SELECT x FROM (SELECT DISTINCT 1 AS x, arrayJoin([1, 2]) AS y) From 2813909ed3c92f41ee83d4eeeacd9c27726cb21c Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 24 Mar 2014 00:45:42 +0400 Subject: [PATCH 48/51] dbms: modified test [#METR-2944]. --- dbms/tests/clickhouse-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test index 1bfd91ab468..4c918cf30a7 100755 --- a/dbms/tests/clickhouse-test +++ b/dbms/tests/clickhouse-test @@ -24,7 +24,7 @@ ERRORS=0 for query_file in $(ls $QUERIES_DIR/*.sql) do - test_name=$(basename -s .sql $query_file) + test_name=$(basename $query_file .sql) result_file=$QUERIES_DIR/$test_name.result error_file=$QUERIES_DIR/$test_name.error From e2292225ef4a1bfd6ab948ceea897d4541d7aeb4 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 24 Mar 2014 04:37:39 +0400 Subject: [PATCH 49/51] Added gitignore [#METR-2944]. --- dbms/tests/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 dbms/tests/.gitignore 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 From 8aaecae5fbb7044c44939fe214899c2e9633b9bd Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 24 Mar 2014 06:50:25 +0400 Subject: [PATCH 50/51] dbms: updated tests [#METR-2944]. --- dbms/tests/queries/{ => 0_stateless}/00001_select_1.reference | 0 dbms/tests/queries/{ => 0_stateless}/00001_select_1.sql | 0 .../queries/{ => 0_stateless}/00002_system_numbers.reference | 0 dbms/tests/queries/{ => 0_stateless}/00002_system_numbers.sql | 0 .../{ => 0_stateless}/00003_reinterpret_as_string.reference | 0 .../queries/{ => 0_stateless}/00003_reinterpret_as_string.sql | 0 .../{ => 0_stateless}/00004_format_ast_and_remote_table.reference | 0 .../{ => 0_stateless}/00004_format_ast_and_remote_table.sql | 0 .../00005_format_ast_and_remote_table_lambda.reference | 0 .../00005_format_ast_and_remote_table_lambda.sql | 0 .../queries/{ => 0_stateless}/00006_1_set_extremes.reference | 0 dbms/tests/queries/{ => 0_stateless}/00006_1_set_extremes.sql | 0 .../00006_2_extremes_and_subquery_from.reference | 0 .../{ => 0_stateless}/00006_2_extremes_and_subquery_from.sql | 0 .../queries/{ => 0_stateless}/00006_3_unset_extremes.reference | 0 dbms/tests/queries/{ => 0_stateless}/00006_3_unset_extremes.sql | 0 dbms/tests/queries/{ => 0_stateless}/00007_array.reference | 0 dbms/tests/queries/{ => 0_stateless}/00007_array.sql | 0 dbms/tests/queries/{ => 0_stateless}/00008_array_join.reference | 0 dbms/tests/queries/{ => 0_stateless}/00008_array_join.sql | 0 .../queries/{ => 0_stateless}/00009_array_join_subquery.reference | 0 .../tests/queries/{ => 0_stateless}/00009_array_join_subquery.sql | 0 .../queries/{ => 0_stateless}/00010_big_array_join.reference | 0 dbms/tests/queries/{ => 0_stateless}/00010_big_array_join.sql | 0 .../queries/{ => 0_stateless}/00011_array_join_alias.reference | 0 dbms/tests/queries/{ => 0_stateless}/00011_array_join_alias.sql | 0 .../queries/{ => 0_stateless}/00012_array_join_alias_2.reference | 0 dbms/tests/queries/{ => 0_stateless}/00012_array_join_alias_2.sql | 0 .../00013_1_drop_if_exists_table_with_arrays.reference | 0 .../00013_1_drop_if_exists_table_with_arrays.sql | 0 .../{ => 0_stateless}/00013_2_create_table_with_arrays.reference | 0 .../{ => 0_stateless}/00013_2_create_table_with_arrays.sql | 0 .../00013_3_insert_into_table_with_arrays.reference | 0 .../{ => 0_stateless}/00013_3_insert_into_table_with_arrays.sql | 0 .../00013_4_select_from_table_with_arrays.reference | 0 .../{ => 0_stateless}/00013_4_select_from_table_with_arrays.sql | 0 .../00013_5_select_from_table_with_arrays.reference | 0 .../{ => 0_stateless}/00013_5_select_from_table_with_arrays.sql | 0 .../00013_6_select_from_table_with_arrays.reference | 0 .../{ => 0_stateless}/00013_6_select_from_table_with_arrays.sql | 0 .../00013_7_select_from_table_with_arrays.reference | 0 .../{ => 0_stateless}/00013_7_select_from_table_with_arrays.sql | 0 .../00013_8_select_from_table_with_arrays.reference | 0 .../{ => 0_stateless}/00013_8_select_from_table_with_arrays.sql | 0 .../00013_9_select_from_table_with_arrays.reference | 0 .../{ => 0_stateless}/00013_9_select_from_table_with_arrays.sql | 0 .../00013_a_select_from_table_with_arrays.reference | 0 .../{ => 0_stateless}/00013_a_select_from_table_with_arrays.sql | 0 .../00014_1_drop_if_exists_table_with_nested.reference | 0 .../00014_1_drop_if_exists_table_with_nested.sql | 0 .../{ => 0_stateless}/00014_2_create_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_2_create_table_with_nested.sql | 0 .../00014_3_insert_into_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_3_insert_into_table_with_nested.sql | 0 .../00014_4_select_from_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_4_select_from_table_with_nested.sql | 0 .../00014_5_select_from_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_5_select_from_table_with_nested.sql | 0 .../00014_6_select_from_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_6_select_from_table_with_nested.sql | 0 .../00014_7_select_from_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_7_select_from_table_with_nested.sql | 0 .../00014_9_select_from_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_9_select_from_table_with_nested.sql | 0 .../00014_a_select_from_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_a_select_from_table_with_nested.sql | 0 .../00014_b_select_from_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_b_select_from_table_with_nested.sql | 0 .../00014_c_select_from_table_with_nested.reference | 0 .../{ => 0_stateless}/00014_c_select_from_table_with_nested.sql | 0 .../{ => 0_stateless}/00015_totals_having_constants.reference | 0 .../queries/{ => 0_stateless}/00015_totals_having_constants.sql | 0 .../{ => 0_stateless}/00016_totals_having_constants.reference | 0 .../queries/{ => 0_stateless}/00016_totals_having_constants.sql | 0 .../00017_in_subquery_with_empty_result.reference | 0 .../{ => 0_stateless}/00017_in_subquery_with_empty_result.sql | 0 .../{ => 0_stateless}/00018_distinct_in_subquery.reference | 0 .../queries/{ => 0_stateless}/00018_distinct_in_subquery.sql | 0 78 files changed, 0 insertions(+), 0 deletions(-) rename dbms/tests/queries/{ => 0_stateless}/00001_select_1.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00001_select_1.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00002_system_numbers.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00002_system_numbers.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00003_reinterpret_as_string.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00003_reinterpret_as_string.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00004_format_ast_and_remote_table.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00004_format_ast_and_remote_table.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00005_format_ast_and_remote_table_lambda.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00005_format_ast_and_remote_table_lambda.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00006_1_set_extremes.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00006_1_set_extremes.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00006_2_extremes_and_subquery_from.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00006_2_extremes_and_subquery_from.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00006_3_unset_extremes.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00006_3_unset_extremes.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00007_array.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00007_array.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00008_array_join.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00008_array_join.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00009_array_join_subquery.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00009_array_join_subquery.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00010_big_array_join.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00010_big_array_join.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00011_array_join_alias.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00011_array_join_alias.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00012_array_join_alias_2.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00012_array_join_alias_2.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_1_drop_if_exists_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_1_drop_if_exists_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_2_create_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_2_create_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_3_insert_into_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_3_insert_into_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_4_select_from_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_4_select_from_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_5_select_from_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_5_select_from_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_6_select_from_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_6_select_from_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_7_select_from_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_7_select_from_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_8_select_from_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_8_select_from_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_9_select_from_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_9_select_from_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_a_select_from_table_with_arrays.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00013_a_select_from_table_with_arrays.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_1_drop_if_exists_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_1_drop_if_exists_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_2_create_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_2_create_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_3_insert_into_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_3_insert_into_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_4_select_from_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_4_select_from_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_5_select_from_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_5_select_from_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_6_select_from_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_6_select_from_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_7_select_from_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_7_select_from_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_9_select_from_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_9_select_from_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_a_select_from_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_a_select_from_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_b_select_from_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_b_select_from_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_c_select_from_table_with_nested.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00014_c_select_from_table_with_nested.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00015_totals_having_constants.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00015_totals_having_constants.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00016_totals_having_constants.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00016_totals_having_constants.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00017_in_subquery_with_empty_result.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00017_in_subquery_with_empty_result.sql (100%) rename dbms/tests/queries/{ => 0_stateless}/00018_distinct_in_subquery.reference (100%) rename dbms/tests/queries/{ => 0_stateless}/00018_distinct_in_subquery.sql (100%) diff --git a/dbms/tests/queries/00001_select_1.reference b/dbms/tests/queries/0_stateless/00001_select_1.reference similarity index 100% rename from dbms/tests/queries/00001_select_1.reference rename to dbms/tests/queries/0_stateless/00001_select_1.reference diff --git a/dbms/tests/queries/00001_select_1.sql b/dbms/tests/queries/0_stateless/00001_select_1.sql similarity index 100% rename from dbms/tests/queries/00001_select_1.sql rename to dbms/tests/queries/0_stateless/00001_select_1.sql diff --git a/dbms/tests/queries/00002_system_numbers.reference b/dbms/tests/queries/0_stateless/00002_system_numbers.reference similarity index 100% rename from dbms/tests/queries/00002_system_numbers.reference rename to dbms/tests/queries/0_stateless/00002_system_numbers.reference diff --git a/dbms/tests/queries/00002_system_numbers.sql b/dbms/tests/queries/0_stateless/00002_system_numbers.sql similarity index 100% rename from dbms/tests/queries/00002_system_numbers.sql rename to dbms/tests/queries/0_stateless/00002_system_numbers.sql diff --git a/dbms/tests/queries/00003_reinterpret_as_string.reference b/dbms/tests/queries/0_stateless/00003_reinterpret_as_string.reference similarity index 100% rename from dbms/tests/queries/00003_reinterpret_as_string.reference rename to dbms/tests/queries/0_stateless/00003_reinterpret_as_string.reference diff --git a/dbms/tests/queries/00003_reinterpret_as_string.sql b/dbms/tests/queries/0_stateless/00003_reinterpret_as_string.sql similarity index 100% rename from dbms/tests/queries/00003_reinterpret_as_string.sql rename to dbms/tests/queries/0_stateless/00003_reinterpret_as_string.sql diff --git a/dbms/tests/queries/00004_format_ast_and_remote_table.reference b/dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.reference similarity index 100% rename from dbms/tests/queries/00004_format_ast_and_remote_table.reference rename to dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.reference diff --git a/dbms/tests/queries/00004_format_ast_and_remote_table.sql b/dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.sql similarity index 100% rename from dbms/tests/queries/00004_format_ast_and_remote_table.sql rename to dbms/tests/queries/0_stateless/00004_format_ast_and_remote_table.sql diff --git a/dbms/tests/queries/00005_format_ast_and_remote_table_lambda.reference b/dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.reference similarity index 100% rename from dbms/tests/queries/00005_format_ast_and_remote_table_lambda.reference rename to dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.reference diff --git a/dbms/tests/queries/00005_format_ast_and_remote_table_lambda.sql b/dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.sql similarity index 100% rename from dbms/tests/queries/00005_format_ast_and_remote_table_lambda.sql rename to dbms/tests/queries/0_stateless/00005_format_ast_and_remote_table_lambda.sql diff --git a/dbms/tests/queries/00006_1_set_extremes.reference b/dbms/tests/queries/0_stateless/00006_1_set_extremes.reference similarity index 100% rename from dbms/tests/queries/00006_1_set_extremes.reference rename to dbms/tests/queries/0_stateless/00006_1_set_extremes.reference diff --git a/dbms/tests/queries/00006_1_set_extremes.sql b/dbms/tests/queries/0_stateless/00006_1_set_extremes.sql similarity index 100% rename from dbms/tests/queries/00006_1_set_extremes.sql rename to dbms/tests/queries/0_stateless/00006_1_set_extremes.sql diff --git a/dbms/tests/queries/00006_2_extremes_and_subquery_from.reference b/dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.reference similarity index 100% rename from dbms/tests/queries/00006_2_extremes_and_subquery_from.reference rename to dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.reference diff --git a/dbms/tests/queries/00006_2_extremes_and_subquery_from.sql b/dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.sql similarity index 100% rename from dbms/tests/queries/00006_2_extremes_and_subquery_from.sql rename to dbms/tests/queries/0_stateless/00006_2_extremes_and_subquery_from.sql diff --git a/dbms/tests/queries/00006_3_unset_extremes.reference b/dbms/tests/queries/0_stateless/00006_3_unset_extremes.reference similarity index 100% rename from dbms/tests/queries/00006_3_unset_extremes.reference rename to dbms/tests/queries/0_stateless/00006_3_unset_extremes.reference diff --git a/dbms/tests/queries/00006_3_unset_extremes.sql b/dbms/tests/queries/0_stateless/00006_3_unset_extremes.sql similarity index 100% rename from dbms/tests/queries/00006_3_unset_extremes.sql rename to dbms/tests/queries/0_stateless/00006_3_unset_extremes.sql diff --git a/dbms/tests/queries/00007_array.reference b/dbms/tests/queries/0_stateless/00007_array.reference similarity index 100% rename from dbms/tests/queries/00007_array.reference rename to dbms/tests/queries/0_stateless/00007_array.reference diff --git a/dbms/tests/queries/00007_array.sql b/dbms/tests/queries/0_stateless/00007_array.sql similarity index 100% rename from dbms/tests/queries/00007_array.sql rename to dbms/tests/queries/0_stateless/00007_array.sql diff --git a/dbms/tests/queries/00008_array_join.reference b/dbms/tests/queries/0_stateless/00008_array_join.reference similarity index 100% rename from dbms/tests/queries/00008_array_join.reference rename to dbms/tests/queries/0_stateless/00008_array_join.reference diff --git a/dbms/tests/queries/00008_array_join.sql b/dbms/tests/queries/0_stateless/00008_array_join.sql similarity index 100% rename from dbms/tests/queries/00008_array_join.sql rename to dbms/tests/queries/0_stateless/00008_array_join.sql diff --git a/dbms/tests/queries/00009_array_join_subquery.reference b/dbms/tests/queries/0_stateless/00009_array_join_subquery.reference similarity index 100% rename from dbms/tests/queries/00009_array_join_subquery.reference rename to dbms/tests/queries/0_stateless/00009_array_join_subquery.reference diff --git a/dbms/tests/queries/00009_array_join_subquery.sql b/dbms/tests/queries/0_stateless/00009_array_join_subquery.sql similarity index 100% rename from dbms/tests/queries/00009_array_join_subquery.sql rename to dbms/tests/queries/0_stateless/00009_array_join_subquery.sql diff --git a/dbms/tests/queries/00010_big_array_join.reference b/dbms/tests/queries/0_stateless/00010_big_array_join.reference similarity index 100% rename from dbms/tests/queries/00010_big_array_join.reference rename to dbms/tests/queries/0_stateless/00010_big_array_join.reference diff --git a/dbms/tests/queries/00010_big_array_join.sql b/dbms/tests/queries/0_stateless/00010_big_array_join.sql similarity index 100% rename from dbms/tests/queries/00010_big_array_join.sql rename to dbms/tests/queries/0_stateless/00010_big_array_join.sql diff --git a/dbms/tests/queries/00011_array_join_alias.reference b/dbms/tests/queries/0_stateless/00011_array_join_alias.reference similarity index 100% rename from dbms/tests/queries/00011_array_join_alias.reference rename to dbms/tests/queries/0_stateless/00011_array_join_alias.reference diff --git a/dbms/tests/queries/00011_array_join_alias.sql b/dbms/tests/queries/0_stateless/00011_array_join_alias.sql similarity index 100% rename from dbms/tests/queries/00011_array_join_alias.sql rename to dbms/tests/queries/0_stateless/00011_array_join_alias.sql diff --git a/dbms/tests/queries/00012_array_join_alias_2.reference b/dbms/tests/queries/0_stateless/00012_array_join_alias_2.reference similarity index 100% rename from dbms/tests/queries/00012_array_join_alias_2.reference rename to dbms/tests/queries/0_stateless/00012_array_join_alias_2.reference diff --git a/dbms/tests/queries/00012_array_join_alias_2.sql b/dbms/tests/queries/0_stateless/00012_array_join_alias_2.sql similarity index 100% rename from dbms/tests/queries/00012_array_join_alias_2.sql rename to dbms/tests/queries/0_stateless/00012_array_join_alias_2.sql diff --git a/dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_1_drop_if_exists_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_1_drop_if_exists_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_1_drop_if_exists_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_1_drop_if_exists_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_1_drop_if_exists_table_with_arrays.sql diff --git a/dbms/tests/queries/00013_2_create_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_2_create_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_2_create_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_2_create_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_2_create_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_2_create_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_2_create_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_2_create_table_with_arrays.sql diff --git a/dbms/tests/queries/00013_3_insert_into_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_3_insert_into_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_3_insert_into_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_3_insert_into_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_3_insert_into_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_3_insert_into_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_3_insert_into_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_3_insert_into_table_with_arrays.sql diff --git a/dbms/tests/queries/00013_4_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_4_select_from_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_4_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_4_select_from_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_4_select_from_table_with_arrays.sql diff --git a/dbms/tests/queries/00013_5_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_5_select_from_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_5_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_5_select_from_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_5_select_from_table_with_arrays.sql diff --git a/dbms/tests/queries/00013_6_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_6_select_from_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_6_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_6_select_from_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_6_select_from_table_with_arrays.sql diff --git a/dbms/tests/queries/00013_7_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_7_select_from_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_7_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_7_select_from_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_7_select_from_table_with_arrays.sql diff --git a/dbms/tests/queries/00013_8_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_8_select_from_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_8_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_8_select_from_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_8_select_from_table_with_arrays.sql diff --git a/dbms/tests/queries/00013_9_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_9_select_from_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_9_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_9_select_from_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_9_select_from_table_with_arrays.sql diff --git a/dbms/tests/queries/00013_a_select_from_table_with_arrays.reference b/dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.reference similarity index 100% rename from dbms/tests/queries/00013_a_select_from_table_with_arrays.reference rename to dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.reference diff --git a/dbms/tests/queries/00013_a_select_from_table_with_arrays.sql b/dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.sql similarity index 100% rename from dbms/tests/queries/00013_a_select_from_table_with_arrays.sql rename to dbms/tests/queries/0_stateless/00013_a_select_from_table_with_arrays.sql diff --git a/dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_1_drop_if_exists_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_1_drop_if_exists_table_with_nested.reference diff --git a/dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_1_drop_if_exists_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_1_drop_if_exists_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_1_drop_if_exists_table_with_nested.sql diff --git a/dbms/tests/queries/00014_2_create_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_2_create_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_2_create_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_2_create_table_with_nested.reference diff --git a/dbms/tests/queries/00014_2_create_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_2_create_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_2_create_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_2_create_table_with_nested.sql diff --git a/dbms/tests/queries/00014_3_insert_into_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_3_insert_into_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_3_insert_into_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_3_insert_into_table_with_nested.reference diff --git a/dbms/tests/queries/00014_3_insert_into_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_3_insert_into_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_3_insert_into_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_3_insert_into_table_with_nested.sql diff --git a/dbms/tests/queries/00014_4_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_4_select_from_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.reference diff --git a/dbms/tests/queries/00014_4_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_4_select_from_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_4_select_from_table_with_nested.sql diff --git a/dbms/tests/queries/00014_5_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_5_select_from_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.reference diff --git a/dbms/tests/queries/00014_5_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_5_select_from_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_5_select_from_table_with_nested.sql diff --git a/dbms/tests/queries/00014_6_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_6_select_from_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.reference diff --git a/dbms/tests/queries/00014_6_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_6_select_from_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_6_select_from_table_with_nested.sql diff --git a/dbms/tests/queries/00014_7_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_7_select_from_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.reference diff --git a/dbms/tests/queries/00014_7_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_7_select_from_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_7_select_from_table_with_nested.sql diff --git a/dbms/tests/queries/00014_9_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_9_select_from_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.reference diff --git a/dbms/tests/queries/00014_9_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_9_select_from_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_9_select_from_table_with_nested.sql diff --git a/dbms/tests/queries/00014_a_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_a_select_from_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.reference diff --git a/dbms/tests/queries/00014_a_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_a_select_from_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_a_select_from_table_with_nested.sql diff --git a/dbms/tests/queries/00014_b_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_b_select_from_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.reference diff --git a/dbms/tests/queries/00014_b_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_b_select_from_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_b_select_from_table_with_nested.sql diff --git a/dbms/tests/queries/00014_c_select_from_table_with_nested.reference b/dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.reference similarity index 100% rename from dbms/tests/queries/00014_c_select_from_table_with_nested.reference rename to dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.reference diff --git a/dbms/tests/queries/00014_c_select_from_table_with_nested.sql b/dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.sql similarity index 100% rename from dbms/tests/queries/00014_c_select_from_table_with_nested.sql rename to dbms/tests/queries/0_stateless/00014_c_select_from_table_with_nested.sql diff --git a/dbms/tests/queries/00015_totals_having_constants.reference b/dbms/tests/queries/0_stateless/00015_totals_having_constants.reference similarity index 100% rename from dbms/tests/queries/00015_totals_having_constants.reference rename to dbms/tests/queries/0_stateless/00015_totals_having_constants.reference diff --git a/dbms/tests/queries/00015_totals_having_constants.sql b/dbms/tests/queries/0_stateless/00015_totals_having_constants.sql similarity index 100% rename from dbms/tests/queries/00015_totals_having_constants.sql rename to dbms/tests/queries/0_stateless/00015_totals_having_constants.sql diff --git a/dbms/tests/queries/00016_totals_having_constants.reference b/dbms/tests/queries/0_stateless/00016_totals_having_constants.reference similarity index 100% rename from dbms/tests/queries/00016_totals_having_constants.reference rename to dbms/tests/queries/0_stateless/00016_totals_having_constants.reference diff --git a/dbms/tests/queries/00016_totals_having_constants.sql b/dbms/tests/queries/0_stateless/00016_totals_having_constants.sql similarity index 100% rename from dbms/tests/queries/00016_totals_having_constants.sql rename to dbms/tests/queries/0_stateless/00016_totals_having_constants.sql diff --git a/dbms/tests/queries/00017_in_subquery_with_empty_result.reference b/dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.reference similarity index 100% rename from dbms/tests/queries/00017_in_subquery_with_empty_result.reference rename to dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.reference diff --git a/dbms/tests/queries/00017_in_subquery_with_empty_result.sql b/dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.sql similarity index 100% rename from dbms/tests/queries/00017_in_subquery_with_empty_result.sql rename to dbms/tests/queries/0_stateless/00017_in_subquery_with_empty_result.sql diff --git a/dbms/tests/queries/00018_distinct_in_subquery.reference b/dbms/tests/queries/0_stateless/00018_distinct_in_subquery.reference similarity index 100% rename from dbms/tests/queries/00018_distinct_in_subquery.reference rename to dbms/tests/queries/0_stateless/00018_distinct_in_subquery.reference diff --git a/dbms/tests/queries/00018_distinct_in_subquery.sql b/dbms/tests/queries/0_stateless/00018_distinct_in_subquery.sql similarity index 100% rename from dbms/tests/queries/00018_distinct_in_subquery.sql rename to dbms/tests/queries/0_stateless/00018_distinct_in_subquery.sql From 9abadce603210bc2295668e8bc1dff44a4c6f6e0 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Mon, 24 Mar 2014 06:50:39 +0400 Subject: [PATCH 51/51] dbms: updated tests [#METR-2944]. --- dbms/tests/clickhouse-test | 111 +++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test index 4c918cf30a7..3300a944d0f 100755 --- a/dbms/tests/clickhouse-test +++ b/dbms/tests/clickhouse-test @@ -21,58 +21,71 @@ MSG_GENERATED="${COLOR_WHITE}[ ${COLOR_UNKNOWN}GENERATED${COLOR_WHITE} ]${COLOR_ ERRORS=0 - -for query_file in $(ls $QUERIES_DIR/*.sql) +for dir in $(ls $QUERIES_DIR) do - test_name=$(basename $query_file .sql) + tests_name=$(echo $dir | sed -E 's/^[0-9_]+//') - result_file=$QUERIES_DIR/$test_name.result - error_file=$QUERIES_DIR/$test_name.error - reference_file=$QUERIES_DIR/$test_name.reference - diff_file=$QUERIES_DIR/$test_name.diff + echo + echo "Running $tests_name tests." + echo - 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 + 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 @@ -82,6 +95,6 @@ if [ $ERRORS -gt 0 ]; then echo -e "${COLOR_FAIL}Having $ERRORS errors!${COLOR_RESET}" exit 1 else - echo -e "${COLOR_OK}All tests succeeded.${COLOR_RESET}" + echo -e "${COLOR_OK}All tests passed.${COLOR_RESET}" exit 0 fi