This commit is contained in:
Evgeniy Gatov 2014-03-24 19:16:54 +04:00
commit e9b57e989a
162 changed files with 3795 additions and 2841 deletions

View File

@ -452,41 +452,32 @@ private:
for (size_t i = 0; i < col_size; ++i) 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; 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; size_t value_size = cur_offsets[i] - prev_cur_offset;
// std::cerr << "value_size: " << value_size << std::endl;
size_t sum_chars_size = 0; size_t sum_chars_size = 0;
for (size_t j = 0; j < size_to_replicate; ++j) for (size_t j = 0; j < size_to_replicate; ++j)
{ {
// std::cerr << "j: " << j << std::endl;
current_res_offset += value_size; current_res_offset += value_size;
res_offsets.push_back(current_res_offset); res_offsets.push_back(current_res_offset);
// std::cerr << "current_res_offset: " << current_res_offset << std::endl;
sum_chars_size = 0; sum_chars_size = 0;
size_t prev_cur_string_offset_local = prev_cur_string_offset; size_t prev_cur_string_offset_local = prev_cur_string_offset;
for (size_t k = 0; k < value_size; ++k) 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; 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; current_res_string_offset += chars_size;
res_string_offsets.push_back(current_res_string_offset); 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); 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); 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<const char *>(&cur_chars[prev_cur_string_offset_local]), chars_size) << std::endl;
sum_chars_size += chars_size; sum_chars_size += chars_size;
prev_cur_string_offset_local += chars_size; prev_cur_string_offset_local += chars_size;

View File

@ -16,6 +16,9 @@ class ColumnSet : public IColumnDummy
public: public:
ColumnSet(size_t s_, SetPtr data_) : IColumnDummy(s_), data(data_) {} ColumnSet(size_t s_, SetPtr data_) : IColumnDummy(s_), data(data_) {}
/// Столбец не константный. Иначе столбец будет использоваться в вычислениях в ExpressionActions::prepare, когда множество из подзапроса ещё не готово.
bool isConst() const { return false; }
std::string getName() const { return "ColumnSet"; } std::string getName() const { return "ColumnSet"; }
ColumnPtr cloneDummy(size_t s_) const { return new ColumnSet(s_, data); } ColumnPtr cloneDummy(size_t s_) const { return new ColumnSet(s_, data); }

View File

@ -9,8 +9,6 @@
#include <DB/Parsers/ASTExpressionList.h> #include <DB/Parsers/ASTExpressionList.h>
#include <DB/Parsers/ASTLiteral.h> #include <DB/Parsers/ASTLiteral.h>
#include <DB/Parsers/ASTSelectQuery.h> #include <DB/Parsers/ASTSelectQuery.h>
#include <DB/Storages/StoragePtr.h>
#include <DB/Interpreters/InterpreterSelectQuery.h>
#include <DB/Columns/ColumnString.h> #include <DB/Columns/ColumnString.h>
namespace DB namespace DB
@ -36,9 +34,9 @@ BlockInputStreamPtr getVirtualColumnsBlocks(ASTPtr query, const Block & input, c
/// Извлечь из входного потока множество значений столбца name /// Извлечь из входного потока множество значений столбца name
template<typename T1> template<typename T1>
std::set<T1> extractSingleValueFromBlocks(BlockInputStreamPtr input, const String & name) std::multiset<T1> extractSingleValueFromBlocks(BlockInputStreamPtr input, const String & name)
{ {
std::set<T1> res; std::multiset<T1> res;
input->readPrefix(); input->readPrefix();
while(1) while(1)
{ {
@ -53,10 +51,10 @@ std::set<T1> extractSingleValueFromBlocks(BlockInputStreamPtr input, const Strin
/// Извлечь из входного потока множество пар значений в столбцах first_name и second_name /// Извлечь из входного потока множество пар значений в столбцах first_name и second_name
template<typename T1, typename T2> template<typename T1, typename T2>
std::set< std::pair<T1, T2> > extractTwoValuesFromBlocks(BlockInputStreamPtr input, std::multiset< std::pair<T1, T2> > extractTwoValuesFromBlocks(BlockInputStreamPtr input,
const String & first_name, const String & second_name) const String & first_name, const String & second_name)
{ {
std::set< std::pair<T1, T2> > res; std::multiset< std::pair<T1, T2> > res;
input->readPrefix(); input->readPrefix();
while(1) while(1)
{ {

View File

@ -225,6 +225,8 @@ namespace ErrorCodes
NOT_AN_AGGREGATE, NOT_AN_AGGREGATE,
QUERY_WITH_SAME_ID_IS_ALREADY_RUNNING, QUERY_WITH_SAME_ID_IS_ALREADY_RUNNING,
CLIENT_HAS_CONNECTED_TO_WRONG_PORT, CLIENT_HAS_CONNECTED_TO_WRONG_PORT,
TABLE_IS_DROPPED,
DATABASE_NOT_EMPTY,
POCO_EXCEPTION = 1000, POCO_EXCEPTION = 1000,
STD_EXCEPTION, STD_EXCEPTION,

View File

@ -14,7 +14,7 @@ namespace DB
class CollapsingFinalBlockInputStream : public IProfilingBlockInputStream class CollapsingFinalBlockInputStream : public IProfilingBlockInputStream
{ {
public: public:
CollapsingFinalBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, CollapsingFinalBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_,
const String & sign_column_) const String & sign_column_)
: description(description_), sign_column(sign_column_), : description(description_), sign_column(sign_column_),
log(&Logger::get("CollapsingSortedBlockInputStream")), log(&Logger::get("CollapsingSortedBlockInputStream")),

View File

@ -23,7 +23,7 @@ namespace DB
class CollapsingSortedBlockInputStream : public MergingSortedBlockInputStream class CollapsingSortedBlockInputStream : public MergingSortedBlockInputStream
{ {
public: public:
CollapsingSortedBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, CollapsingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_,
const String & sign_column_, size_t max_block_size_) const String & sign_column_, size_t max_block_size_)
: MergingSortedBlockInputStream(inputs_, description_, max_block_size_), : MergingSortedBlockInputStream(inputs_, description_, max_block_size_),
sign_column(sign_column_), sign_column_number(0), sign_column(sign_column_), sign_column_number(0),

View File

@ -5,7 +5,7 @@
#include <Poco/SharedPtr.h> #include <Poco/SharedPtr.h>
#include <DB/Core/Block.h> #include <DB/Core/Block.h>
#include <DB/Storages/StoragePtr.h> #include <DB/Storages/IStorage.h>
namespace DB namespace DB
@ -30,11 +30,7 @@ public:
typedef SharedPtr<IBlockInputStream> BlockInputStreamPtr; typedef SharedPtr<IBlockInputStream> BlockInputStreamPtr;
typedef std::vector<BlockInputStreamPtr> BlockInputStreams; typedef std::vector<BlockInputStreamPtr> BlockInputStreams;
/** Листовой BlockInputStream обычно требует, чтобы был жив какой-то Storage. IBlockInputStream() {}
* Переданный сюда указатель на Storage будет просто храниться в этом экземпляре,
* не позволяя уничтожить Storage раньше этого BlockInputStream.
*/
IBlockInputStream(StoragePtr owned_storage_ = StoragePtr()) : owned_storage(owned_storage_) {}
/** Прочитать следующий блок. /** Прочитать следующий блок.
* Если блоков больше нет - вернуть пустой блок (для которого operator bool возвращает false). * Если блоков больше нет - вернуть пустой блок (для которого operator bool возвращает false).
@ -80,10 +76,12 @@ public:
*/ */
size_t checkDepth(size_t max_depth) const; 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: protected:
StoragePtr owned_storage; IStorage::TableStructureReadLocks table_locks;
BlockInputStreams children; BlockInputStreams children;
@ -99,8 +97,5 @@ private:
}; };
typedef IBlockInputStream::BlockInputStreamPtr BlockInputStreamPtr;
typedef IBlockInputStream::BlockInputStreams BlockInputStreams;
} }

View File

@ -6,7 +6,7 @@
#include <DB/Core/Block.h> #include <DB/Core/Block.h>
#include <DB/Core/Row.h> #include <DB/Core/Row.h>
#include <DB/Storages/StoragePtr.h> #include <DB/Storages/IStorage.h>
namespace DB namespace DB
@ -21,7 +21,7 @@ class IBlockOutputStream : private boost::noncopyable
{ {
public: public:
IBlockOutputStream(StoragePtr owned_storage_ = StoragePtr()) : owned_storage(owned_storage_) {} IBlockOutputStream() {}
/** Записать блок. /** Записать блок.
*/ */
@ -39,11 +39,13 @@ public:
virtual void setExtremes(const Block & extremes) {} virtual void setExtremes(const Block & extremes) {}
virtual ~IBlockOutputStream() {} virtual ~IBlockOutputStream() {}
/** Не давать изменить таблицу, пока жив поток блоков.
*/
void addTableLock(const IStorage::TableStructureReadLockPtr & lock) { table_locks.push_back(lock); }
protected: protected:
StoragePtr owned_storage; IStorage::TableStructureReadLocks table_locks;
}; };
typedef SharedPtr<IBlockOutputStream> BlockOutputStreamPtr;
} }

View File

@ -69,7 +69,7 @@ private:
mutable bool calculated_rows_before_limit; /// Вычислялось ли поле rows_before_limit mutable bool calculated_rows_before_limit; /// Вычислялось ли поле rows_before_limit
}; };
/** Смотрит за тем, как работает источник блоков. /** Смотрит за тем, как работает источник блоков.
* Позволяет получить информацию для профайлинга: * Позволяет получить информацию для профайлинга:
* строк в секунду, блоков в секунду, мегабайт в секунду и т. п. * строк в секунду, блоков в секунду, мегабайт в секунду и т. п.
@ -78,8 +78,8 @@ private:
class IProfilingBlockInputStream : public IBlockInputStream class IProfilingBlockInputStream : public IBlockInputStream
{ {
public: public:
IProfilingBlockInputStream(StoragePtr owned_storage_ = StoragePtr()) IProfilingBlockInputStream()
: IBlockInputStream(owned_storage_), is_cancelled(false), process_list_elem(NULL), : is_cancelled(false), process_list_elem(NULL),
enabled_extremes(false), quota(NULL), prev_elapsed(0) {} enabled_extremes(false), quota(NULL), prev_elapsed(0) {}
Block read(); Block read();

View File

@ -19,7 +19,7 @@ class MergingSortedBlockInputStream : public IProfilingBlockInputStream
{ {
public: public:
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке. /// 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), : 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")) num_columns(0), source_blocks(inputs_.size()), cursors(inputs_.size()), log(&Logger::get("MergingSortedBlockInputStream"))
{ {

View File

@ -23,7 +23,10 @@ public:
{ {
if (database.empty()) if (database.empty())
database = context.getCurrentDatabase(); database = context.getCurrentDatabase();
storage = context.getTable(database, table); storage = context.getTable(database, table);
addTableLock(storage->lockStructure(true));
Dependencies dependencies = context.getDependencies(DatabaseAndTableName(database, table)); Dependencies dependencies = context.getDependencies(DatabaseAndTableName(database, table));
for (size_t i = 0; i < dependencies.size(); ++i) for (size_t i = 0; i < dependencies.size(); ++i)
{ {

View File

@ -18,7 +18,7 @@ namespace DB
class SummingSortedBlockInputStream : public MergingSortedBlockInputStream class SummingSortedBlockInputStream : public MergingSortedBlockInputStream
{ {
public: 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_), : MergingSortedBlockInputStream(inputs_, description_, max_block_size_),
log(&Logger::get("SummingSortedBlockInputStream")), current_row_is_zero(false) log(&Logger::get("SummingSortedBlockInputStream")), current_row_is_zero(false)
{ {

View File

@ -20,12 +20,12 @@ namespace DB
*/ */
struct Memory : boost::noncopyable struct Memory : boost::noncopyable
{ {
size_t m_capacity; size_t m_capacity = 0;
size_t m_size; size_t m_size = 0;
char * m_data; char * m_data = nullptr;
size_t alignment; size_t alignment = 0;
Memory() : m_capacity(0), m_size(0), m_data(NULL), alignment(0) {} Memory() {}
/// Если alignment != 0, то будет выделяться память, выровненная на alignment. /// Если alignment != 0, то будет выделяться память, выровненная на alignment.
Memory(size_t size_, size_t alignment_ = 0) : m_capacity(size_), m_size(m_capacity), alignment(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(); 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; } size_t size() const { return m_size; }
const char & operator[](size_t i) const { return m_data[i]; } const char & operator[](size_t i) const { return m_data[i]; }
char & operator[](size_t i) { return m_data[i]; } char & operator[](size_t i) { return m_data[i]; }

View File

@ -41,9 +41,6 @@ typedef std::map<String, StoragePtr> Tables;
/// имя БД -> таблицы /// имя БД -> таблицы
typedef std::map<String, Tables> Databases; typedef std::map<String, Tables> Databases;
/// имя БД -> dropper
typedef std::map<String, DatabaseDropperPtr> DatabaseDroppers;
/// (имя базы данных, имя таблицы) /// (имя базы данных, имя таблицы)
typedef std::pair<String, String> DatabaseAndTableName; typedef std::pair<String, String> DatabaseAndTableName;
@ -76,7 +73,6 @@ struct ContextShared
String path; /// Путь к директории с данными, со слешем на конце. String path; /// Путь к директории с данными, со слешем на конце.
Databases databases; /// Список БД и таблиц в них. Databases databases; /// Список БД и таблиц в них.
DatabaseDroppers database_droppers; /// Reference counter'ы для ленивого удаления БД.
TableFunctionFactory table_function_factory; /// Табличные функции. TableFunctionFactory table_function_factory; /// Табличные функции.
FunctionFactory function_factory; /// Обычные функции. FunctionFactory function_factory; /// Обычные функции.
AggregateFunctionFactory aggregate_function_factory; /// Агрегатные функции. AggregateFunctionFactory aggregate_function_factory; /// Агрегатные функции.
@ -137,7 +133,6 @@ struct ContextShared
{ {
Poco::ScopedLock<Poco::Mutex> lock(mutex); Poco::ScopedLock<Poco::Mutex> lock(mutex);
database_droppers.clear();
current_databases = databases; current_databases = databases;
} }
@ -232,8 +227,6 @@ public:
String getDefaultFormat() const; /// Если default_format не задан - возвращается некоторый глобальный формат по-умолчанию. String getDefaultFormat() const; /// Если default_format не задан - возвращается некоторый глобальный формат по-умолчанию.
void setDefaultFormat(const String & name); void setDefaultFormat(const String & name);
DatabaseDropperPtr getDatabaseDropper(const String & name);
Settings getSettings() const; Settings getSettings() const;
void setSettings(const Settings & settings_); void setSettings(const Settings & settings_);

View File

@ -15,6 +15,8 @@ namespace DB
{ {
/** Превращает выражение из синтаксического дерева в последовательность действий для его выполнения. /** Превращает выражение из синтаксического дерева в последовательность действий для его выполнения.
*
* NOTE: если ast - запрос SELECT из таблицы, структура этой таблицы не должна меняться во все время жизни ExpressionAnalyzer-а.
*/ */
class ExpressionAnalyzer : private boost::noncopyable class ExpressionAnalyzer : private boost::noncopyable
{ {

View File

@ -76,12 +76,9 @@ private:
NamesAndTypesList columns; NamesAndTypesList columns;
{ {
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex()); StoragePtr table = context.getTable(ast.database, ast.table);
auto table_lock = table->lockStructure(false);
if (!context.isTableExist(ast.database, ast.table)) columns = table->getColumnsList();
throw Exception("Table " + (ast.database.empty() ? "" : ast.database + ".") + ast.table + " doesn't exist", ErrorCodes::UNKNOWN_TABLE);
columns = context.getTable(ast.database, ast.table)->getColumnsList();
} }
ColumnString * name_column = new ColumnString; ColumnString * name_column = new ColumnString;

View File

@ -18,6 +18,9 @@ public:
/// Удаляет таблицу. /// Удаляет таблицу.
void execute(); void execute();
/// Удаляет таблицу, уже отцепленную от контекста (Context::detach).
static void dropDetachedTable(String database_name, StoragePtr table, Context & context);
private: private:
ASTPtr query_ptr; ASTPtr query_ptr;
Context context; Context context;

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <DB/DataStreams/IBlockOutputStream.h> #include <DB/DataStreams/IBlockOutputStream.h>
#include <DB/DataStreams/BlockIO.h>
#include <DB/Interpreters/Context.h> #include <DB/Interpreters/Context.h>
@ -24,13 +25,13 @@ public:
/** Подготовить запрос к выполнению. Вернуть поток блоков, в который можно писать данные для выполнения запроса. /** Подготовить запрос к выполнению. Вернуть поток блоков, в который можно писать данные для выполнения запроса.
* Или вернуть NULL, если запрос INSERT SELECT (самодостаточный запрос - не принимает входные данные). * Или вернуть NULL, если запрос INSERT SELECT (самодостаточный запрос - не принимает входные данные).
*/ */
BlockOutputStreamPtr execute(); BlockIO execute();
Block getSampleBlock();
private: private:
StoragePtr getTable(); StoragePtr getTable();
Block getSampleBlock();
ASTPtr query_ptr; ASTPtr query_ptr;
Context context; Context context;
}; };

View File

@ -20,7 +20,9 @@ public:
void execute() void execute()
{ {
const ASTOptimizeQuery & ast = dynamic_cast<const ASTOptimizeQuery &>(*query_ptr); const ASTOptimizeQuery & ast = dynamic_cast<const ASTOptimizeQuery &>(*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: private:

View File

@ -34,10 +34,6 @@ public:
DataTypes getReturnTypes(); DataTypes getReturnTypes();
Block getSampleBlock(); Block getSampleBlock();
/** Получить CREATE запрос для таблицы, из которой идёт выбор.
*/
ASTPtr getCreateQuery();
private: private:
typedef Poco::SharedPtr<ExpressionAnalyzer> ExpressionAnalyzerPtr; typedef Poco::SharedPtr<ExpressionAnalyzer> ExpressionAnalyzerPtr;
@ -47,8 +43,6 @@ private:
*/ */
void getDatabaseAndTableNames(String & database_name, String & table_name); void getDatabaseAndTableNames(String & database_name, String & table_name);
StoragePtr getTable();
/** Выбрать из списка столбцов какой-нибудь, лучше - минимального размера. /** Выбрать из списка столбцов какой-нибудь, лучше - минимального размера.
*/ */
String getAnyColumn(); String getAnyColumn();
@ -65,7 +59,7 @@ private:
void executeTotalsAndHaving( BlockInputStreams & streams, bool has_having, ExpressionActionsPtr expression, void executeTotalsAndHaving( BlockInputStreams & streams, bool has_having, ExpressionActionsPtr expression,
bool overflow_row); bool overflow_row);
void executeHaving( BlockInputStreams & streams, ExpressionActionsPtr expression); void executeHaving( BlockInputStreams & streams, ExpressionActionsPtr expression);
void executeOuterExpression( BlockInputStreams & streams, ExpressionActionsPtr expression); void executeExpression( BlockInputStreams & streams, ExpressionActionsPtr expression);
void executeOrder( BlockInputStreams & streams); void executeOrder( BlockInputStreams & streams);
void executePreLimit( BlockInputStreams & streams); void executePreLimit( BlockInputStreams & streams);
void executeUnion( BlockInputStreams & streams); void executeUnion( BlockInputStreams & streams);
@ -83,7 +77,10 @@ private:
size_t subquery_depth; size_t subquery_depth;
ExpressionAnalyzerPtr query_analyzer; ExpressionAnalyzerPtr query_analyzer;
BlockInputStreams streams; BlockInputStreams streams;
StoragePtr table_function_storage;
/// Таблица, откуда читать данные, если не подзапрос.
StoragePtr storage;
IStorage::TableStructureReadLockPtr table_lock;
Logger * log; Logger * log;
}; };

View File

@ -68,18 +68,9 @@ private:
{ {
const ASTShowCreateQuery & ast = dynamic_cast<const ASTShowCreateQuery &>(*query_ptr); const ASTShowCreateQuery & ast = dynamic_cast<const ASTShowCreateQuery &>(*query_ptr);
String res; std::stringstream stream;
formatAST(*context.getCreateQuery(ast.database, ast.table), stream, 0, false, true);
{ String res = stream.str();
Poco::ScopedLock<Poco::Mutex> 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();
}
ColumnWithNameAndType col; ColumnWithNameAndType col;
col.name = "statement"; col.name = "statement";

View File

@ -1,37 +0,0 @@
#pragma once
#include <string>
#include <Poco/File.h>
#include <Yandex/logger_useful.h>
/// Удаляет директорию в деструкторе.
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<DatabaseDropper> DatabaseDropperPtr;

View File

@ -7,20 +7,24 @@
#include <DB/Core/NamesAndTypes.h> #include <DB/Core/NamesAndTypes.h>
#include <DB/Core/Exception.h> #include <DB/Core/Exception.h>
#include <DB/Core/QueryProcessingStage.h> #include <DB/Core/QueryProcessingStage.h>
#include <DB/DataStreams/IBlockInputStream.h>
#include <DB/DataStreams/IBlockOutputStream.h>
#include <DB/Parsers/IAST.h> #include <DB/Parsers/IAST.h>
#include <DB/Parsers/ASTAlterQuery.h> #include <DB/Parsers/ASTAlterQuery.h>
#include <DB/Interpreters/Settings.h> #include <DB/Interpreters/Settings.h>
#include <DB/Storages/StoragePtr.h> #include <DB/Storages/ITableDeclaration.h>
#include "DatabaseDropper.h"
#include <Poco/File.h> #include <Poco/File.h>
#include <Poco/RWLock.h>
namespace DB namespace DB
{ {
class Context; class Context;
class IBlockInputStream;
class IBlockOutputStream;
typedef SharedPtr<IBlockOutputStream> BlockOutputStreamPtr;
typedef SharedPtr<IBlockInputStream> BlockInputStreamPtr;
typedef std::vector<BlockInputStreamPtr> BlockInputStreams;
/** Хранилище. Отвечает за: /** Хранилище. Отвечает за:
@ -30,41 +34,12 @@ class Context;
* - структура хранения данных (сжатие, etc.) * - структура хранения данных (сжатие, etc.)
* - конкуррентный доступ к данным (блокировки, etc.) * - конкуррентный доступ к данным (блокировки, etc.)
*/ */
class IStorage : private boost::noncopyable class IStorage : private boost::noncopyable, public ITableDeclaration
{ {
public: public:
/// Основное имя типа таблицы (например, StorageWithoutKey). /// Основное имя типа таблицы (например, StorageMergeTree).
virtual std::string getName() const = 0; 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, если хранилище получает данные с удалённого сервера или серверов. /** Возвращает true, если хранилище получает данные с удалённого сервера или серверов.
*/ */
virtual bool isRemote() const { return false; } virtual bool isRemote() const { return false; }
@ -81,6 +56,76 @@ public:
*/ */
virtual bool supportsPrewhere() const { return false; } virtual bool supportsPrewhere() const { return false; }
/** Не дает изменять описание таблицы (в том числе переименовывать и удалять таблицу).
* Если в течение какой-то операции структура таблицы должна оставаться неизменной, нужно держать такой лок на все ее время.
* Например, нужно держать такой лок на время всего запроса SELECT или INSERT и на все время слияния набора кусков
* (но между выбором кусков для слияния и их слиянием структура таблицы может измениться).
* NOTE: Это лок на "чтение" описания таблицы. Чтобы изменить описание таблицы, нужно взять TableStructureWriteLock.
*/
class TableStructureReadLock
{
private:
friend class IStorage;
/// Порядок важен.
Poco::SharedPtr<Poco::ScopedReadRWLock> data_lock;
Poco::SharedPtr<Poco::ScopedReadRWLock> 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<TableStructureReadLock> TableStructureReadLockPtr;
typedef std::vector<TableStructureReadLockPtr> TableStructureReadLocks;
/** Не дает изменять структуру или имя таблицы.
* Если в рамках этого лока будут изменены данные в таблице, нужно указать will_modify_data=true.
* Это возьмет дополнительный лок, не позволяющий начать ALTER MODIFY.
*
* WARNING: Вызывать методы из ITableDeclaration нужно под такой блокировкой. Без нее они не thread safe.
* WARNING: Чтобы не было дедлоков, нельзя вызывать это метод при захваченном мьютексе в Context.
*/
TableStructureReadLockPtr lockStructure(bool will_modify_data)
{
TableStructureReadLockPtr res = new TableStructureReadLock(*this, true, will_modify_data);
if (is_dropped)
throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED);
return res;
}
typedef Poco::SharedPtr<Poco::ScopedWriteRWLock> TableStructureWriteLockPtr;
typedef Poco::SharedPtr<Poco::ScopedWriteRWLock> TableDataWriteLockPtr;
typedef std::pair<TableDataWriteLockPtr, TableStructureWriteLockPtr> TableFullWriteLockPtr;
/** Не дает читать структуру таблицы. Берется для ALTER, RENAME и DROP.
*/
TableFullWriteLockPtr lockForAlter()
{
return std::make_pair(lockDataForAlter(), lockStructureForAlter());
}
/** Не дает изменять данные в таблице. (Более того, не дает посмотреть на структуру таблицы с намерением изменить данные).
* Берется на время записи временных данных в ALTER MODIFY.
* Под этим локом можно брать lockStructureForAlter(), чтобы изменить структуру таблицы.
*/
TableDataWriteLockPtr lockDataForAlter()
{
TableDataWriteLockPtr res = new Poco::ScopedWriteRWLock(data_lock);
if (is_dropped)
throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED);
return res;
}
TableStructureWriteLockPtr lockStructureForAlter()
{
TableStructureWriteLockPtr res = new Poco::ScopedWriteRWLock(structure_lock);
if (is_dropped)
throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED);
return res;
}
/** Читать набор столбцов из таблицы. /** Читать набор столбцов из таблицы.
* Принимает список столбцов, которых нужно прочитать, а также описание запроса, * Принимает список столбцов, которых нужно прочитать, а также описание запроса,
* из которого может быть извлечена информация о том, каким способом извлекать данные * из которого может быть извлечена информация о том, каким способом извлекать данные
@ -97,6 +142,8 @@ public:
* *
* threads - рекомендация, сколько потоков возвращать, * threads - рекомендация, сколько потоков возвращать,
* если хранилище может возвращать разное количество потоков. * если хранилище может возвращать разное количество потоков.
*
* Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP).
*/ */
virtual BlockInputStreams read( virtual BlockInputStreams read(
const Names & column_names, const Names & column_names,
@ -112,6 +159,8 @@ public:
/** Пишет данные в таблицу. /** Пишет данные в таблицу.
* Принимает описание запроса, в котором может содержаться информация о методе записи данных. * Принимает описание запроса, в котором может содержаться информация о методе записи данных.
* Возвращает объект, с помощью которого можно последовательно писать данные. * Возвращает объект, с помощью которого можно последовательно писать данные.
*
* Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP).
*/ */
virtual BlockOutputStreamPtr write( virtual BlockOutputStreamPtr write(
ASTPtr query) ASTPtr query)
@ -119,21 +168,15 @@ public:
throw Exception("Method write is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); throw Exception("Method write is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
} }
/** Удалить данные таблицы. После вызова этого метода, использование объекта некорректно (его можно лишь уничтожить). /** Удалить данные таблицы. Вызывается перед удалением директории с данными.
*/
void drop()
{
drop_on_destroy = true;
}
/** Вызывается перед удалением директории с данными и вызовом деструктора.
* Если не требуется никаких действий, кроме удаления директории с данными, этот метод можно оставить пустым. * Если не требуется никаких действий, кроме удаления директории с данными, этот метод можно оставить пустым.
*/ */
virtual void dropImpl() {} virtual void drop() {}
/** Переименовать таблицу. /** Переименовать таблицу.
* Переименование имени в файле с метаданными, имени в списке таблиц в оперативке, осуществляется отдельно. * Переименование имени в файле с метаданными, имени в списке таблиц в оперативке, осуществляется отдельно.
* В этой функции нужно переименовать директорию с данными, если она есть. * В этой функции нужно переименовать директорию с данными, если она есть.
* Вызывается при заблокированной на запись структуре таблицы.
*/ */
virtual void rename(const String & new_path_to_db, const String & new_name) virtual void rename(const String & new_path_to_db, const String & new_name)
{ {
@ -142,12 +185,29 @@ public:
/** ALTER таблицы в виде изменения столбцов, не затрагивающий изменение Storage или его параметров. /** ALTER таблицы в виде изменения столбцов, не затрагивающий изменение Storage или его параметров.
* (ALTER, затрагивающий изменение движка, делается внешним кодом, путём копирования данных.) * (ALTER, затрагивающий изменение движка, делается внешним кодом, путём копирования данных.)
* Вызывается при заблокированной на запись структуре таблицы.
* Для ALTER MODIFY можно использовать другие методы (см. ниже).
*/ */
virtual void alter(const ASTAlterQuery::Parameters & params) virtual void alter(const ASTAlterQuery::Parameters & params)
{ {
throw Exception("Method alter is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED); throw Exception("Method alter is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
} }
/** ALTER MODIFY (изменение типа столбца) выполняется в два вызова:
* Сначала вызывается prepareAlterModify при заблокированной записи данных, но незаблокированной структуре таблицы.
* В нем можно выполнить долгую работу по записи сконвертированных данных, оставляя доступными существующие данные.
* Потом вызывается commitAlterModify при заблокированной структуре таблицы.
* В нем нужно закончить изменение типа столбца.
* Для движков с тривиальным ALTER MODIFY можно оставить реализацию по умолчанию, вызывающую alter.
*/
virtual void prepareAlterModify(const ASTAlterQuery::Parameters & params) {}
virtual void commitAlterModify(const ASTAlterQuery::Parameters & params)
{
alter(params);
}
/** Выполнить какую-либо фоновую работу. Например, объединение кусков в таблице типа MergeTree. /** Выполнить какую-либо фоновую работу. Например, объединение кусков в таблице типа MergeTree.
* Возвращает - была ли выполнена какая-либо работа. * Возвращает - была ли выполнена какая-либо работа.
*/ */
@ -169,56 +229,56 @@ public:
/** Если при уничтожении объекта надо сделать какую-то сложную работу - сделать её заранее. /** Если при уничтожении объекта надо сделать какую-то сложную работу - сделать её заранее.
* Например, если таблица содержит какие-нибудь потоки для фоновой работы - попросить их завершиться и дождаться завершения. * Например, если таблица содержит какие-нибудь потоки для фоновой работы - попросить их завершиться и дождаться завершения.
* По-умолчанию - ничего не делать. * По-умолчанию - ничего не делать.
* Может вызываться одновременно из разных потоков, даже после вызова drop().
*/ */
virtual void shutdown() {} virtual void shutdown() {}
virtual ~IStorage() {}
/** Проверить, что все запрошенные имена есть в таблице и заданы корректно.
* (список имён не пустой и имена не повторяются)
*/
void check(const Names & column_names) const;
/** Проверить, что блок с данными для записи содержит все столбцы таблицы с правильными типами,
* содержит только столбцы таблицы, и все столбцы различны.
* Если need_all, еще проверяет, что все столбцы таблицы есть в блоке.
*/
void check(const Block & block, bool need_all = false) const;
/** Возвращает владеющий указатель на себя. /** Возвращает владеющий указатель на себя.
*/ */
StoragePtr thisPtr() std::shared_ptr<IStorage> thisPtr()
{ {
if (!this_ptr.lock()) std::shared_ptr<IStorage> res = this_ptr.lock();
if (!res)
{ {
boost::shared_ptr<StoragePtr::Wrapper> p(new StoragePtr::Wrapper(this)); res.reset(this);
this_ptr = p; this_ptr = res;
return StoragePtr(this_ptr);
}
else
{
return StoragePtr(this_ptr);
} }
return res;
} }
/** Не дает удалить БД до удаления таблицы. Присваивается перед удалением таблицы или БД. bool is_dropped;
*/
DatabaseDropperPtr database_to_drop;
bool drop_on_destroy;
/** Директория с данными. Будет удалена после удаления таблицы (после вызова dropImpl).
*/
std::string path_to_remove_on_drop;
protected: protected:
IStorage() : drop_on_destroy(false) {} IStorage() : is_dropped(false) {}
/// реализация alter, модифицирующая список столбцов.
void alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) const;
private: private:
boost::weak_ptr<StoragePtr::Wrapper> this_ptr; std::weak_ptr<IStorage> this_ptr;
/// Брать следующие два лока всегда нужно в этом порядке.
/** Берется на чтение на все время запроса INSERT и на все время слияния кусков (для MergeTree).
* Берется на запись на все время ALTER MODIFY.
*
* Формально:
* Ввзятие на запись гарантирует, что:
* 1) данные в таблице не изменится, пока лок жив,
* 2) все изменения данных после отпускания лока будут основаны на структуре таблицы на момент после отпускания лока.
* Нужно брать на чтение на все время операции, изменяющей данные.
*/
mutable Poco::RWLock data_lock;
/** Лок для множества столбцов и пути к таблице. Берется на запись в RENAME, ALTER (для ALTER MODIFY ненадолго) и DROP.
* Берется на чтение на все время SELECT, INSERT и слияния кусков (для MergeTree).
*
* Взятие этого лока на запись - строго более "сильная" операция, чем взятие parts_writing_lock на запись.
* То есть, если этот лок взят на запись, о parts_writing_lock можно не заботиться.
* parts_writing_lock нужен только для случаев, когда не хочется брать table_structure_lock надолго (ALTER MODIFY).
*/
mutable Poco::RWLock structure_lock;
}; };
typedef std::shared_ptr<IStorage> StoragePtr;
typedef std::vector<StoragePtr> StorageVector; typedef std::vector<StoragePtr> StorageVector;
typedef IStorage::TableStructureReadLocks TableLocks;
} }

View File

@ -0,0 +1,67 @@
#pragma once
#include <DB/Core/Names.h>
#include <DB/Core/NamesAndTypes.h>
#include <DB/Core/Exception.h>
#include <DB/Core/Block.h>
#include <DB/Parsers/ASTAlterQuery.h>
namespace DB
{
class Context;
/** Описание таблицы.
* Не thread safe. См. IStorage::lockStructure().
*/
class ITableDeclaration
{
public:
/** Имя таблицы.
*/
virtual std::string getTableName() const { return ""; }
/** Получить список имён и типов столбцов таблицы, только невиртуальные.
*/
virtual const NamesAndTypesList & getColumnsList() const = 0;
/** Получить описание реального (невиртуального) столбца по его имени.
*/
virtual NameAndTypePair getRealColumn(const String & column_name) const;
/** Присутствует ли реальный (невиртуальный) столбец с таким именем.
*/
virtual bool hasRealColumn(const String & column_name) const;
/** Получить описание любого столбца по его имени.
*/
virtual NameAndTypePair getColumn(const String & column_name) const;
/** Присутствует ли столбец с таким именем.
*/
virtual bool hasColumn(const String & column_name) const;
const DataTypePtr getDataTypeByName(const String & column_name) const;
/** То же самое, но в виде блока-образца.
*/
Block getSampleBlock() const;
/** Проверить, что все запрошенные имена есть в таблице и заданы корректно.
* (список имён не пустой и имена не повторяются)
*/
void check(const Names & column_names) const;
/** Проверить, что блок с данными для записи содержит все столбцы таблицы с правильными типами,
* содержит только столбцы таблицы, и все столбцы различны.
* Если need_all, еще проверяет, что все столбцы таблицы есть в блоке.
*/
void check(const Block & block, bool need_all = false) const;
/// реализация alter, модифицирующая список столбцов.
static void alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context);
virtual ~ITableDeclaration() {}
};
}

View File

@ -0,0 +1,87 @@
#pragma once
#include <Poco/Mutex.h>
#include <sys/statvfs.h>
#include <boost/noncopyable.hpp>
#include <Yandex/logger_useful.h>
#include <DB/Core/Exception.h>
#include <DB/Core/ErrorCodes.h>
namespace DB
{
/** Узнает количество свободного места в файловой системе.
* Можно "резервировать" место, чтобы разные операции могли согласованно планировать использование диска.
* Резервирования не разделяются по файловым системам.
* Вместо этого при запросе свободного места считается, что все резервирования сделаны в той же файловой системе.
*/
class DiskSpaceMonitor
{
public:
class Reservation : private boost::noncopyable
{
friend class DiskSpaceMonitor;
public:
~Reservation()
{
try
{
Poco::ScopedLock<Poco::FastMutex> 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<Poco::FastMutex> lock(DiskSpaceMonitor::reserved_bytes_mutex);
DiskSpaceMonitor::reserved_bytes += size;
}
size_t size;
};
typedef Poco::SharedPtr<Reservation> 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<Poco::FastMutex> 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;
};
}

View File

@ -1,9 +1,8 @@
#pragma once #pragma once
#include <DB/DataStreams/IProfilingBlockInputStream.h> #include <DB/DataStreams/IProfilingBlockInputStream.h>
#include <DB/Storages/StorageMergeTree.h> #include <DB/Storages/MergeTree/MergeTreeData.h>
#include <DB/Storages/MergeTree/PKCondition.h> #include <DB/Storages/MergeTree/PKCondition.h>
#include <DB/Storages/MergeTree/MergeTreeReader.h> #include <DB/Storages/MergeTree/MergeTreeReader.h>
@ -14,19 +13,18 @@ namespace DB
class MergeTreeBlockInputStream : public IProfilingBlockInputStream class MergeTreeBlockInputStream : public IProfilingBlockInputStream
{ {
public: public:
/// Параметры storage_ и owned_storage разделены, чтобы можно было сделать поток, не владеющий своим storage
/// (например, поток, сливаящий куски). В таком случае сам storage должен следить, чтобы не удалить данные, пока их читают.
MergeTreeBlockInputStream(const String & path_, /// Путь к куску MergeTreeBlockInputStream(const String & path_, /// Путь к куску
size_t block_size_, const Names & column_names_, 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_, const MarkRanges & mark_ranges_, bool use_uncompressed_cache_,
ExpressionActionsPtr prewhere_actions_, String prewhere_column_) ExpressionActionsPtr prewhere_actions_, String prewhere_column_)
: IProfilingBlockInputStream(owned_storage), :
path(path_), block_size(block_size_), column_names(column_names_), path(path_), block_size(block_size_), column_names(column_names_),
storage(storage_), owned_data_part(owned_data_part_), storage(storage_), owned_data_part(owned_data_part_),
all_mark_ranges(mark_ranges_), remaining_mark_ranges(mark_ranges_), all_mark_ranges(mark_ranges_), remaining_mark_ranges(mark_ranges_),
use_uncompressed_cache(use_uncompressed_cache_), use_uncompressed_cache(use_uncompressed_cache_),
prewhere_actions(prewhere_actions_), prewhere_column(prewhere_column_) prewhere_actions(prewhere_actions_), prewhere_column(prewhere_column_),
log(&Logger::get("MergeTreeBlockInputStream"))
{ {
std::reverse(remaining_mark_ranges.begin(), remaining_mark_ranges.end()); std::reverse(remaining_mark_ranges.begin(), remaining_mark_ranges.end());
@ -48,7 +46,7 @@ public:
} }
column_name_set.insert(column_names.begin(), column_names.end()); 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 << ", 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); << " rows starting from " << all_mark_ranges.front().begin * storage.index_granularity);
} }
@ -58,7 +56,7 @@ public:
String getID() const String getID() const
{ {
std::stringstream res; std::stringstream res;
res << "MergeTree(" << owned_storage->getTableName() << ", " << path << ", columns"; res << "MergeTree(" << path << ", columns";
for (size_t i = 0; i < column_names.size(); ++i) for (size_t i = 0; i < column_names.size(); ++i)
res << ", " << column_names[i]; res << ", " << column_names[i];
@ -72,70 +70,6 @@ public:
return res.str(); return res.str();
} }
/// Получает набор диапазонов засечек, вне которых не могут находиться ключи из заданного диапазона.
static MarkRanges markRangesFromPkRange(
const StorageMergeTree::DataPart::Index & index,
StorageMergeTree & storage,
PKCondition & key_condition)
{
MarkRanges res;
size_t key_size = storage.sort_descr.size();
size_t marks_count = index.size() / key_size;
/// Если индекс не используется.
if (key_condition.alwaysTrue())
{
res.push_back(MarkRange(0, marks_count));
}
else
{
/** В стеке всегда будут находиться непересекающиеся подозрительные отрезки, самый левый наверху (back).
* На каждом шаге берем левый отрезок и проверяем, подходит ли он.
* Если подходит, разбиваем его на более мелкие и кладем их в стек. Если нет - выбрасываем его.
* Если отрезок уже длиной в одну засечку, добавляем его в ответ и выбрасываем.
*/
std::vector<MarkRange> 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: protected:
/// Будем вызывать progressImpl самостоятельно. /// Будем вызывать progressImpl самостоятельно.
void progress(size_t rows, size_t bytes) {} void progress(size_t rows, size_t bytes) {}
@ -321,8 +255,8 @@ private:
Names column_names; Names column_names;
NameSet column_name_set; NameSet column_name_set;
Names pre_column_names; Names pre_column_names;
StorageMergeTree & storage; MergeTreeData & storage;
const StorageMergeTree::DataPartPtr owned_data_part; /// Кусок не будет удалён, пока им владеет этот объект. const MergeTreeData::DataPartPtr owned_data_part; /// Кусок не будет удалён, пока им владеет этот объект.
MarkRanges all_mark_ranges; /// В каких диапазонах засечек читать. В порядке возрастания номеров. MarkRanges all_mark_ranges; /// В каких диапазонах засечек читать. В порядке возрастания номеров.
MarkRanges remaining_mark_ranges; /// В каких диапазонах засечек еще не прочли. MarkRanges remaining_mark_ranges; /// В каких диапазонах засечек еще не прочли.
/// В порядке убывания номеров, чтобы можно было выбрасывать из конца. /// В порядке убывания номеров, чтобы можно было выбрасывать из конца.
@ -332,6 +266,8 @@ private:
ExpressionActionsPtr prewhere_actions; ExpressionActionsPtr prewhere_actions;
String prewhere_column; String prewhere_column;
bool remove_prewhere_column; bool remove_prewhere_column;
Logger * log;
}; };
} }

View File

@ -1,298 +1,30 @@
#pragma once #pragma once
#include <DB/IO/WriteBufferFromFile.h>
#include <DB/IO/CompressedWriteBuffer.h>
#include <DB/Columns/ColumnsNumber.h>
#include <DB/Interpreters/sortBlock.h>
#include <DB/Storages/StorageMergeTree.h> #include <DB/Storages/StorageMergeTree.h>
namespace DB namespace DB
{ {
class MergeTreeBlockOutputStream : public IBlockOutputStream class MergeTreeBlockOutputStream : public IBlockOutputStream
{ {
public: public:
MergeTreeBlockOutputStream(StoragePtr owned_storage) : IBlockOutputStream(owned_storage), storage(dynamic_cast<StorageMergeTree &>(*owned_storage)), flags(O_TRUNC | O_CREAT | O_WRONLY) MergeTreeBlockOutputStream(StorageMergeTree & storage_)
{ : storage(storage_) {}
}
void write(const Block & block) void write(const Block & block)
{ {
Poco::ScopedReadRWLock write_lock(storage.write_lock); auto part_blocks = storage.writer.splitBlockIntoParts(block);
for (auto & current_block : part_blocks)
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<const ColumnUInt16 &>(*block.getByName(storage.date_column_name).column).getData();
/// Минимальная и максимальная дата.
UInt16 min_date = std::numeric_limits<UInt16>::max();
UInt16 max_date = std::numeric_limits<UInt16>::min();
for (ColumnUInt16::Container_t::const_iterator it = dates.begin(); it != dates.end(); ++it)
{ {
if (*it < min_date) UInt64 temp_index = storage.increment.get();
min_date = *it; MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, temp_index);
if (*it > max_date) storage.data.renameTempPartAndAdd(part, &storage.increment);
max_date = *it; storage.merge(2);
} }
/// Разделяем на блоки по месяцам. Для каждого ещё посчитаем минимальную и максимальную дату.
typedef std::map<UInt16, BlockWithDateInterval> 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: private:
StorageMergeTree & storage; StorageMergeTree & storage;
const int flags;
struct BlockWithDateInterval
{
Block block;
UInt16 min_date;
UInt16 max_date;
BlockWithDateInterval() : min_date(std::numeric_limits<UInt16>::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<std::string> OffsetColumns;
void writePart(Block & block, UInt16 min_date, UInt16 max_date)
{
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
size_t rows = block.rows();
size_t columns = block.columns();
UInt64 tmp_part_id = storage.increment.get(false);
size_t part_size = (rows + storage.index_granularity - 1) / storage.index_granularity;
String tmp_part_name = storage.getPartName(
DayNum_t(min_date), DayNum_t(max_date),
tmp_part_id, tmp_part_id, 0);
String part_tmp_path = storage.full_path + "tmp_" + tmp_part_name + "/";
Poco::File(part_tmp_path).createDirectories();
LOG_TRACE(storage.log, "Calculating primary expression.");
/// Если для сортировки надо вычислить некоторые столбцы - делаем это.
storage.primary_expr->execute(block);
LOG_TRACE(storage.log, "Sorting by primary key.");
/// Сортируем.
stableSortBlock(block, storage.sort_descr);
/// Наконец-то можно писать данные на диск.
LOG_TRACE(storage.log, "Writing index.");
/// Сначала пишем индекс. Индекс содержит значение PK для каждой index_granularity строки.
StorageMergeTree::DataPart::Index index_vec;
index_vec.reserve(part_size * storage.sort_descr.size());
{
WriteBufferFromFile index(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, flags);
typedef std::vector<const ColumnWithNameAndType *> 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<Poco::FastMutex> lock(storage.data_parts_mutex);
Poco::ScopedLock<Poco::FastMutex> lock_all(storage.all_data_parts_mutex);
/** Важно, что получение номера куска происходит атомарно с добавлением этого куска в набор.
* Иначе есть race condition - может произойти слияние пары кусков, диапазоны номеров которых
* содержат ещё не добавленный кусок.
*/
UInt64 part_id = storage.increment.get(false);
String part_name = storage.getPartName(DayNum_t(min_date), DayNum_t(max_date), part_id, part_id, 0);
String part_res_path = storage.full_path + part_name + "/";
StorageMergeTree::DataPartPtr new_data_part = new StorageMergeTree::DataPart(storage);
new_data_part->left_date = DayNum_t(min_date);
new_data_part->right_date = DayNum_t(max_date);
new_data_part->left = part_id;
new_data_part->right = part_id;
new_data_part->level = 0;
new_data_part->name = part_name;
new_data_part->size = part_size;
new_data_part->modification_time = time(0);
new_data_part->left_month = date_lut.toFirstDayNumOfMonth(new_data_part->left_date);
new_data_part->right_month = date_lut.toFirstDayNumOfMonth(new_data_part->right_date);
new_data_part->index.swap(index_vec);
/// Переименовываем кусок.
Poco::File(part_tmp_path).renameTo(part_res_path);
storage.data_parts.insert(new_data_part);
storage.all_data_parts.insert(new_data_part);
}
/// Если на каждую запись делать по две итерации слияния, то дерево будет максимально компактно.
storage.merge(2);
}
/// Записать данные одного столбца.
void writeData(const String & path, const String & name, const IDataType & type, const IColumn & column,
OffsetColumns & offset_columns, size_t level = 0)
{
String escaped_column_name = escapeForFileName(name);
size_t size = column.size();
/// Для массивов требуется сначала сериализовать размеры, а потом значения.
if (const DataTypeArray * type_arr = dynamic_cast<const DataTypeArray *>(&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<const DataTypeNested *>(&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();
}
}
}; };
} }

View File

@ -0,0 +1,356 @@
#pragma once
#include <statdaemons/Increment.h>
#include <statdaemons/threadpool.hpp>
#include <DB/Core/SortDescription.h>
#include <DB/Interpreters/Context.h>
#include <DB/Interpreters/ExpressionActions.h>
#include <DB/Storages/IStorage.h>
#include <Poco/RWLock.h>
namespace DB
{
/** Структура данных для *MergeTree движков.
* Используется merge tree для инкрементальной сортировки данных.
* Таблица представлена набором сортированных кусков.
* При вставке, данные сортируются по указанному выражению (первичному ключу) и пишутся в новый кусок.
* Куски объединяются в фоне, согласно некоторой эвристике.
* Для каждого куска, создаётся индексный файл, содержащий значение первичного ключа для каждой n-ой строки.
* Таким образом, реализуется эффективная выборка по диапазону первичного ключа.
*
* Дополнительно:
*
* Указывается столбец, содержащий дату.
* Для каждого куска пишется минимальная и максимальная дата.
* (по сути - ещё один индекс)
*
* Данные разделяются по разным месяцам (пишутся в разные куски для разных месяцев).
* Куски для разных месяцев не объединяются - для простоты эксплуатации.
* (дают локальность обновлений, что удобно для синхронизации и бэкапа)
*
* Структура файлов:
* / min-date _ max-date _ min-id _ max-id _ level / - директория с куском.
* Внутри директории с куском:
* primary.idx - индексный файл.
* Column.bin - данные столбца
* Column.mrk - засечки, указывающие, откуда начинать чтение, чтобы пропустить n * k строк.
*
* Имеется несколько режимов работы, определяющих, что делать при мердже:
* - Ordinary - ничего дополнительно не делать;
* - Collapsing - при склейке кусков "схлопывать"
* пары записей с разными значениями sign_column для одного значения первичного ключа.
* (см. CollapsingSortedBlockInputStream.h)
* - Summing - при склейке кусков, при совпадении PK суммировать все числовые столбцы, не входящие в PK.
*/
/** Этот класс хранит список кусков и параметры структуры данных.
* Для чтения и изменения данных используются отдельные классы:
* - MergeTreeDataSelectExecutor
* - MergeTreeDataWriter
* - MergeTreeDataMerger
*/
struct MergeTreeSettings
{
/// Набор кусков разрешено объединить, если среди них максимальный размер не более чем во столько раз больше суммы остальных.
double max_size_ratio_to_merge_parts = 5;
/// Сколько за раз сливать кусков.
/// Трудоемкость выбора кусков O(N * max_parts_to_merge_at_once), так что не следует делать это число слишком большим.
/// С другой стороны, чтобы слияния точно не могли зайти в тупик, нужно хотя бы
/// log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts).
size_t max_parts_to_merge_at_once = 10;
/// Куски настолько большого размера в основном потоке объединять нельзя вообще.
size_t max_rows_to_merge_parts = 100 * 1024 * 1024;
/// Куски настолько большого размера во втором потоке объединять нельзя вообще.
size_t max_rows_to_merge_parts_second = 1024 * 1024;
/// Во столько раз ночью увеличиваем коэффициент.
size_t merge_parts_at_night_inc = 10;
/// Сколько потоков использовать для объединения кусков.
size_t merging_threads = 2;
/// Если из одного файла читается хотя бы столько строк, чтение можно распараллелить.
size_t min_rows_for_concurrent_read = 20 * 8192;
/// Можно пропускать чтение более чем стольки строк ценой одного seek по файлу.
size_t min_rows_for_seek = 5 * 8192;
/// Если отрезок индекса может содержать нужные ключи, делим его на столько частей и рекурсивно проверяем их.
size_t coarse_index_granularity = 8;
/** Максимальное количество строк на запрос, для использования кэша разжатых данных. Если запрос большой - кэш не используется.
* (Чтобы большие запросы не вымывали кэш.)
*/
size_t max_rows_to_use_cache = 1024 * 1024;
/// Через сколько секунд удалять old_куски.
time_t old_parts_lifetime = 5 * 60;
};
class MergeTreeData : public ITableDeclaration
{
public:
/// Описание куска с данными.
struct DataPart
{
DataPart(MergeTreeData & storage_) : storage(storage_), size_in_bytes(0) {}
MergeTreeData & storage;
DayNum_t left_date;
DayNum_t right_date;
UInt64 left;
UInt64 right;
/// Уровень игнорируется. Использовался предыдущей эвристикой слияния.
UInt32 level;
std::string name;
size_t size; /// в количестве засечек.
size_t size_in_bytes; /// размер в байтах, 0 - если не посчитано
time_t modification_time;
DayNum_t left_month;
DayNum_t right_month;
/// Первичный ключ. Всегда загружается в оперативку.
typedef std::vector<Field> Index;
Index index;
/// NOTE можно загружать засечки тоже в оперативку
/// Вычисляем сумарный размер всей директории со всеми файлами
static size_t calcTotalSize(const String &from)
{
Poco::File cur(from);
if (cur.isFile())
return cur.getSize();
std::vector<std::string> files;
cur.list(files);
size_t res = 0;
for (size_t i = 0; i < files.size(); ++i)
res += calcTotalSize(from + files[i]);
return res;
}
void remove()
{
String from = storage.full_path + name + "/";
String to = storage.full_path + "tmp2_" + name + "/";
Poco::File(from).renameTo(to);
Poco::File(to).remove(true);
}
void renameToOld() const
{
String from = storage.full_path + name + "/";
String to = storage.full_path + "old_" + name + "/";
Poco::File f(from);
f.setLastModified(Poco::Timestamp::fromEpochTime(time(0)));
f.renameTo(to);
}
bool operator< (const DataPart & rhs) const
{
if (left_month != rhs.left_month)
return left_month < rhs.left_month;
if (right_month != rhs.right_month)
return right_month < rhs.right_month;
if (left != rhs.left)
return left < rhs.left;
if (right != rhs.right)
return right < rhs.right;
if (level != rhs.level)
return level < rhs.level;
return false;
}
/// Содержит другой кусок (получен после объединения другого куска с каким-то ещё)
bool contains(const DataPart & rhs) const
{
return left_month == rhs.left_month /// Куски за разные месяцы не объединяются
&& right_month == rhs.right_month
&& level > rhs.level
&& left_date <= rhs.left_date
&& right_date >= rhs.right_date
&& left <= rhs.left
&& right >= rhs.right;
}
/// Загрузить индекс и вычислить размер.
void loadIndex()
{
size_t key_size = storage.sort_descr.size();
index.resize(key_size * size);
String index_path = storage.full_path + name + "/primary.idx";
ReadBufferFromFile index_file(index_path, std::min(static_cast<size_t>(DBMS_DEFAULT_BUFFER_SIZE), Poco::File(index_path).getSize()));
for (size_t i = 0; i < size; ++i)
for (size_t j = 0; j < key_size; ++j)
storage.primary_key_sample.getByPosition(j).type->deserializeBinary(index[i * key_size + j], index_file);
if (!index_file.eof())
throw Exception("index file " + index_path + " is unexpectedly long", ErrorCodes::EXPECTED_END_OF_FILE);
size_in_bytes = calcTotalSize(storage.full_path + name + "/");
}
};
typedef std::shared_ptr<DataPart> MutableDataPartPtr;
/// После добавление в рабочее множество DataPart нельзя изменять.
typedef std::shared_ptr<const DataPart> DataPartPtr;
struct DataPartPtrLess { bool operator() (const DataPartPtr & lhs, const DataPartPtr & rhs) const { return *lhs < *rhs; } };
typedef std::set<DataPartPtr, DataPartPtrLess> DataParts;
typedef std::vector<DataPartPtr> DataPartsVector;
/// Режим работы. См. выше.
enum Mode
{
Ordinary,
Collapsing,
Summing,
};
/** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце),
* (корректность имён и путей не проверяется)
* состоящую из указанных столбцов.
*
* primary_expr_ast - выражение для сортировки;
* date_column_name - имя столбца с датой;
* index_granularity - на сколько строчек пишется одно значение индекса.
*/
MergeTreeData( const String & full_path_, NamesAndTypesListPtr columns_,
const Context & context_,
ASTPtr & primary_expr_ast_,
const String & date_column_name_,
const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается.
size_t index_granularity_,
Mode mode_,
const String & sign_column_,
const MergeTreeSettings & settings_);
std::string getModePrefix() const;
std::string getSignColumnName() const { return sign_column; }
bool supportsSampling() const { return !!sampling_expression; }
bool supportsFinal() const { return !sign_column.empty(); }
bool supportsPrewhere() const { return true; }
UInt64 getMaxDataPartIndex();
static String getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level);
/// Возвращает true если имя директории совпадает с форматом имени директории кусочков
bool isPartDirectory(const String & dir_name, Poco::RegularExpression::MatchVec & matches) const;
/// Кладет в DataPart данные из имени кусочка.
void parsePartName(const String & file_name, const Poco::RegularExpression::MatchVec & matches, DataPart & part);
std::string getTableName() { return ""; }
const NamesAndTypesList & getColumnsList() const { return *columns; }
String getFullPath() const { return full_path; }
/** Возвращает копию списка, чтобы снаружи можно было не заботиться о блокировках.
*/
DataParts getDataParts();
/** Удаляет куски old_parts и добавляет кусок new_part. Если какого-нибудь из удаляемых кусков нет, бросает исключение.
*/
void replaceParts(DataPartsVector old_parts, DataPartPtr new_part);
/** Переименовывает временный кусок в постоянный и добавляет его в рабочий набор.
* Если increment!=nullptr, индекс куска берется из инкремента. Иначе индекс куска не меняется.
*/
void renameTempPartAndAdd(MutableDataPartPtr part, Increment * increment);
/** Удалить неактуальные куски.
*/
void clearOldParts();
/** После вызова dropAllData больше ничего вызывать нельзя.
* Удаляет директорию с данными и сбрасывает кеши разжатых блоков и засечек.
*/
void dropAllData();
/** Перемещает всю директорию с данными.
* Сбрасывает кеши разжатых блоков и засечек.
* Нужно вызывать под залоченным lockStructure().
*/
void setPath(const String & full_path);
void alter(const ASTAlterQuery::Parameters & params);
void prepareAlterModify(const ASTAlterQuery::Parameters & params);
void commitAlterModify(const ASTAlterQuery::Parameters & params);
ExpressionActionsPtr getPrimaryExpression() const { return primary_expr; }
SortDescription getSortDescription() const { return sort_descr; }
const Context & context;
const String date_column_name;
const ASTPtr sampling_expression;
const size_t index_granularity;
/// Режим работы - какие дополнительные действия делать при мердже.
const Mode mode;
/// Для схлопывания записей об изменениях, если используется Collapsing режим работы.
const String sign_column;
const MergeTreeSettings settings;
private:
ExpressionActionsPtr primary_expr;
SortDescription sort_descr;
Block primary_key_sample;
ASTPtr primary_expr_ast;
String full_path;
NamesAndTypesListPtr columns;
Logger * log;
volatile bool shutdown_called;
/// Регулярное выражение соответсвующее названию директории с кусочками
Poco::RegularExpression file_name_regexp;
/** Актуальное множество кусков с данными. */
DataParts data_parts;
Poco::FastMutex data_parts_mutex;
/** Множество всех кусков с данными, включая уже слитые в более крупные, но ещё не удалённые. Оно обычно небольшое (десятки элементов).
* Ссылки на кусок есть отсюда, из списка актуальных кусков и из каждого потока чтения, который его сейчас использует.
* То есть, если количество ссылок равно 1 - то кусок не актуален и не используется прямо сейчас, и его можно удалить.
*/
DataParts all_data_parts;
Poco::FastMutex all_data_parts_mutex;
/// Загрузить множество кусков с данными с диска. Вызывается один раз - при создании объекта.
void loadDataParts();
void removeColumnFiles(String column_name);
/// Определить, не битые ли данные в директории. Проверяет индекс и засечеки, но не сами данные.
bool isBrokenPart(const String & path);
/// Найти самые большие old_куски, из которых получен этот кусок.
/// Переименовать их, убрав префикс old_ и вернуть их имена.
Strings tryRestorePart(const String & path, const String & file_name, Strings & old_parts);
void createConvertExpression(const String & in_column_name, const String & out_type, ExpressionActionsPtr & out_expression, String & out_column);
};
}

View File

@ -0,0 +1,52 @@
#pragma once
#include <DB/Storages/MergeTree/MergeTreeData.h>
namespace DB
{
/** Умеет выбирать куски для слияния и сливать их.
*/
class MergeTreeDataMerger
{
public:
MergeTreeDataMerger(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataMerger")), canceled(false) {}
typedef boost::function<bool (const MergeTreeData::DataPartPtr &, const MergeTreeData::DataPartPtr &)> AllowedMergingPredicate;
/** Выбирает, какие куски слить. Использует кучу эвристик.
* Если merge_anything_for_old_months, для кусков за прошедшие месяцы снимается ограничение на соотношение размеров.
*
* can_merge - функция, определяющая, можно ли объединить пару соседних кусков.
* Эта функция должна координировать слияния со вставками и другими слияниями, обеспечивая, что:
* - Куски, между которыми еще может появиться новый кусок, нельзя сливать. См. METR-7001.
* - Кусок, который уже сливается с кем-то в одном месте, нельзя начать сливать в кем-то другим в другом месте.
*/
bool selectPartsToMerge(
MergeTreeData::DataPartsVector & what,
size_t available_disk_space,
bool merge_anything_for_old_months,
bool aggressive,
bool only_small,
const AllowedMergingPredicate & can_merge);
/// Сливает куски. Возвращает название нового куска. Если слияние отменили, возвращает пустую строку.
String mergeParts(const MergeTreeData::DataPartsVector & parts);
/// Примерное количество места на диске, нужное для мерджа. С запасом.
size_t estimateDiskSpaceForMerge(const MergeTreeData::DataPartsVector & parts);
/** Отменяет все текущие мерджи. Все выполняющиеся сейчас вызовы mergeParts скоро отменят слияние и вернут пустую строку.
* После этого с этим экземпляром ничего делать нельзя.
*/
void cancelAll() { canceled = true; }
private:
MergeTreeData & data;
Logger * log;
volatile bool canceled;
};
}

View File

@ -0,0 +1,75 @@
#pragma once
#include <DB/Storages/MergeTree/MergeTreeData.h>
#include <DB/Storages/MergeTree/MergeTreeBlockInputStream.h>
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<RangesInDataPart> RangesInDataParts;
size_t min_marks_for_seek;
size_t min_marks_for_concurrent_read;
size_t max_marks_to_use_cache;
BlockInputStreams spreadMarkRangesAmongThreads(
RangesInDataParts parts,
size_t threads,
const Names & column_names,
size_t max_block_size,
bool use_uncompressed_cache,
ExpressionActionsPtr prewhere_actions,
const String & prewhere_column);
BlockInputStreams spreadMarkRangesAmongThreadsFinal(
RangesInDataParts parts,
size_t threads,
const Names & column_names,
size_t max_block_size,
bool use_uncompressed_cache,
ExpressionActionsPtr prewhere_actions,
const String & prewhere_column);
/// Создать выражение "Sign == 1".
void createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column);
MarkRanges markRangesFromPkRange(const MergeTreeData::DataPart::Index & index, PKCondition & key_condition);
};
}

View File

@ -0,0 +1,62 @@
#pragma once
#include <DB/IO/WriteBufferFromFile.h>
#include <DB/IO/CompressedWriteBuffer.h>
#include <DB/Columns/ColumnsNumber.h>
#include <DB/Interpreters/sortBlock.h>
#include <DB/Storages/MergeTree/MergeTreeData.h>
namespace DB
{
struct BlockWithDateInterval
{
Block block;
UInt16 min_date;
UInt16 max_date;
BlockWithDateInterval() : min_date(std::numeric_limits<UInt16>::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<BlockWithDateInterval> BlocksWithDateIntervals;
/** Записывает новые куски с данными в merge-дерево.
*/
class MergeTreeDataWriter
{
public:
MergeTreeDataWriter(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataWriter")), flags(O_TRUNC | O_CREAT | O_WRONLY) {}
/** Разбивает блок на блоки, каждый из которых нужно записать в отдельный кусок.
* (читай: разбивает строки по месяцам)
* Работает детерминированно: если отдать на вход такой же блок, на выходе получатся такие же блоки в таком же порядке.
*/
BlocksWithDateIntervals splitBlockIntoParts(const Block & block);
/** Все строки должны относиться к одному месяцу. Возвращает название временного куска.
* temp_index - значение left и right для нового куска. Можно будет изменить при переименовании.
* Возвращает кусок с именем, начинающимся с tmp_, еще не добавленный в MergeTreeData.
*/
MergeTreeData::MutableDataPartPtr writeTempPart(BlockWithDateInterval & block, UInt64 temp_index);
private:
MergeTreeData & data;
Logger * log;
const int flags;
typedef std::set<std::string> OffsetColumns;
/// Записать данные одного столбца.
void writeData(const String & path, const String & name, const IDataType & type, const IColumn & column,
OffsetColumns & offset_columns, size_t level = 0);
};
}

View File

@ -1,12 +1,15 @@
#pragma once #pragma once
#include <DB/Storages/StorageMergeTree.h> #include <DB/Storages/MergeTree/MergeTreeData.h>
#include <DB/DataTypes/IDataType.h> #include <DB/DataTypes/IDataType.h>
#include <DB/DataTypes/DataTypeNested.h> #include <DB/DataTypes/DataTypeNested.h>
#include <DB/DataTypes/DataTypeArray.h>
#include <DB/Core/NamesAndTypes.h> #include <DB/Core/NamesAndTypes.h>
#include <DB/Common/escapeForFileName.h> #include <DB/Common/escapeForFileName.h>
#include <DB/IO/CachedCompressedReadBuffer.h> #include <DB/IO/CachedCompressedReadBuffer.h>
#include <DB/IO/CompressedReadBufferFromFile.h> #include <DB/IO/CompressedReadBufferFromFile.h>
#include <DB/Columns/ColumnArray.h>
#include <DB/Columns/ColumnNested.h>
#define MERGE_TREE_MARK_SIZE (2 * sizeof(size_t)) #define MERGE_TREE_MARK_SIZE (2 * sizeof(size_t))
@ -15,6 +18,20 @@
namespace DB 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<MarkRange> MarkRanges;
/** Умеет читать данные между парой засечек из одного куска. При чтении последовательных отрезков не делает лишних seek-ов. /** Умеет читать данные между парой засечек из одного куска. При чтении последовательных отрезков не делает лишних seek-ов.
* При чтении почти последовательных отрезков делает seek-и быстро, не выбрасывая содержимое буфера. * При чтении почти последовательных отрезков делает seek-и быстро, не выбрасывая содержимое буфера.
*/ */
@ -22,7 +39,7 @@ class MergeTreeReader
{ {
public: public:
MergeTreeReader(const String & path_, /// Путь к куску 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_) : 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) for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
@ -220,7 +237,7 @@ private:
FileStreams streams; FileStreams streams;
Names column_names; Names column_names;
bool use_uncompressed_cache; bool use_uncompressed_cache;
StorageMergeTree & storage; MergeTreeData & storage;
void addStream(const String & name, const IDataType & type, size_t level = 0) void addStream(const String & name, const IDataType & type, size_t level = 0)
{ {

View File

@ -3,7 +3,7 @@
#include <DB/IO/WriteBufferFromFile.h> #include <DB/IO/WriteBufferFromFile.h>
#include <DB/IO/CompressedWriteBuffer.h> #include <DB/IO/CompressedWriteBuffer.h>
#include <DB/Storages/StorageMergeTree.h> #include <DB/Storages/MergeTree/MergeTreeData.h>
namespace DB namespace DB
@ -11,7 +11,7 @@ namespace DB
class IMergedBlockOutputStream : public IBlockOutputStream class IMergedBlockOutputStream : public IBlockOutputStream
{ {
public: 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; ColumnStreams column_streams;
@ -196,7 +196,7 @@ protected:
class MergedBlockOutputStream : public IMergedBlockOutputStream class MergedBlockOutputStream : public IMergedBlockOutputStream
{ {
public: public:
MergedBlockOutputStream(StorageMergeTree & storage_, MergedBlockOutputStream(MergeTreeData & storage_,
UInt16 min_date, UInt16 max_date, UInt64 min_part_id, UInt64 max_part_id, UInt32 level) UInt16 min_date, UInt16 max_date, UInt64 min_part_id, UInt64 max_part_id, UInt32 level)
: IMergedBlockOutputStream(storage_), marks_count(0) : IMergedBlockOutputStream(storage_), marks_count(0)
{ {
@ -204,17 +204,18 @@ public:
DayNum_t(min_date), DayNum_t(max_date), DayNum_t(min_date), DayNum_t(max_date),
min_part_id, max_part_id, level); min_part_id, max_part_id, level);
part_tmp_path = storage.full_path + "tmp_" + part_name + "/"; part_tmp_path = storage.getFullPath() + "tmp_" + part_name + "/";
part_res_path = storage.full_path + part_name + "/"; part_res_path = storage.getFullPath() + part_name + "/";
Poco::File(part_tmp_path).createDirectories(); 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); 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) columns_list = storage.getColumnsList();
addStream(part_tmp_path, it->first, *it->second); for (const auto & it : columns_list)
addStream(part_tmp_path, it.first, *it.second);
} }
void write(const Block & block) void write(const Block & block)
{ {
size_t rows = block.rows(); size_t rows = block.rows();
@ -223,11 +224,11 @@ public:
typedef std::vector<const ColumnWithNameAndType *> PrimaryColumns; typedef std::vector<const ColumnWithNameAndType *> PrimaryColumns;
PrimaryColumns primary_columns; 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( primary_columns.push_back(
!storage.sort_descr[i].column_name.empty() !descr.column_name.empty()
? &block.getByName(storage.sort_descr[i].column_name) ? &block.getByName(descr.column_name)
: &block.getByPosition(storage.sort_descr[i].column_number)); : &block.getByPosition(descr.column_number));
for (size_t i = index_offset; i < rows; i += storage.index_granularity) for (size_t i = index_offset; i < rows; i += storage.index_granularity)
{ {
@ -243,9 +244,9 @@ public:
OffsetColumns offset_columns; 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); writeData(column.name, *column.type, *column.column, offset_columns);
} }
@ -285,6 +286,8 @@ public:
} }
private: private:
NamesAndTypesList columns_list;
String part_name; String part_name;
String part_tmp_path; String part_tmp_path;
String part_res_path; String part_res_path;
@ -299,7 +302,7 @@ typedef Poco::SharedPtr<MergedBlockOutputStream> MergedBlockOutputStreamPtr;
class MergedColumnOnlyOutputStream : public IMergedBlockOutputStream class MergedColumnOnlyOutputStream : public IMergedBlockOutputStream
{ {
public: 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_) IMergedBlockOutputStream(storage_), part_path(part_path_), initialized(false), sync(sync_)
{ {
} }

View File

@ -3,7 +3,6 @@
#include <sstream> #include <sstream>
#include <DB/Interpreters/Context.h> #include <DB/Interpreters/Context.h>
#include <DB/Storages/IStorage.h>
#include <DB/Core/SortDescription.h> #include <DB/Core/SortDescription.h>
#include <DB/Parsers/ASTExpressionList.h> #include <DB/Parsers/ASTExpressionList.h>
#include <DB/Parsers/ASTSelectQuery.h> #include <DB/Parsers/ASTSelectQuery.h>

View File

@ -32,7 +32,7 @@ public:
ASTPtr getCustomCreateQuery(const Context & context) const; ASTPtr getCustomCreateQuery(const Context & context) const;
void dropImpl(); void drop() override;
String source_database_name; String source_database_name;
String source_table_name; String source_table_name;

View File

@ -58,7 +58,7 @@ public:
size_t max_block_size = DEFAULT_BLOCK_SIZE, size_t max_block_size = DEFAULT_BLOCK_SIZE,
unsigned threads = 1); unsigned threads = 1);
void dropImpl() {} void drop() override {}
void rename(const String & new_path_to_db, const String & new_name) { name = new_name; } void rename(const String & new_path_to_db, const String & new_name) { name = new_name; }
/// в подтаблицах добавлять и удалять столбы нужно вручную /// в подтаблицах добавлять и удалять столбы нужно вручную
/// структура подтаблиц не проверяется /// структура подтаблиц не проверяется

View File

@ -12,6 +12,7 @@
#include <DB/IO/CompressedWriteBuffer.h> #include <DB/IO/CompressedWriteBuffer.h>
#include <DB/Storages/IStorage.h> #include <DB/Storages/IStorage.h>
#include <DB/DataStreams/IProfilingBlockInputStream.h> #include <DB/DataStreams/IProfilingBlockInputStream.h>
#include <DB/DataStreams/IBlockOutputStream.h>
namespace DB namespace DB
@ -36,20 +37,10 @@ typedef std::vector<Mark> Marks;
class LogBlockInputStream : public IProfilingBlockInputStream class LogBlockInputStream : public IProfilingBlockInputStream
{ {
public: 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 getName() const { return "LogBlockInputStream"; }
String getID() const 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();
}
protected: protected:
Block readImpl(); Block readImpl();
@ -88,7 +79,7 @@ private:
class LogBlockOutputStream : public IBlockOutputStream class LogBlockOutputStream : public IBlockOutputStream
{ {
public: public:
LogBlockOutputStream(StoragePtr owned_storage); LogBlockOutputStream(StorageLog & storage_);
void write(const Block & block); void write(const Block & block);
void writeSuffix(); void writeSuffix();
private: private:

View File

@ -16,7 +16,7 @@ public:
std::string getInnerTableName() const { return ".inner." + table_name; } std::string getInnerTableName() const { return ".inner." + table_name; }
BlockOutputStreamPtr write(ASTPtr query); BlockOutputStreamPtr write(ASTPtr query);
void dropImpl(); void drop() override;
bool optimize(); bool optimize();
BlockInputStreams read( BlockInputStreams read(

View File

@ -5,6 +5,7 @@
#include <DB/Core/NamesAndTypes.h> #include <DB/Core/NamesAndTypes.h>
#include <DB/Storages/IStorage.h> #include <DB/Storages/IStorage.h>
#include <DB/DataStreams/IProfilingBlockInputStream.h> #include <DB/DataStreams/IProfilingBlockInputStream.h>
#include <DB/DataStreams/IBlockOutputStream.h>
namespace DB namespace DB
@ -15,13 +16,13 @@ class StorageMemory;
class MemoryBlockInputStream : public IProfilingBlockInputStream class MemoryBlockInputStream : public IProfilingBlockInputStream
{ {
public: 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 getName() const { return "MemoryBlockInputStream"; }
String getID() const String getID() const
{ {
std::stringstream res; 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) for (size_t i = 0; i < column_names.size(); ++i)
res << ", " << column_names[i]; res << ", " << column_names[i];
@ -43,7 +44,7 @@ private:
class MemoryBlockOutputStream : public IBlockOutputStream class MemoryBlockOutputStream : public IBlockOutputStream
{ {
public: public:
MemoryBlockOutputStream(StoragePtr owned_storage); MemoryBlockOutputStream(StorageMemory & storage_);
void write(const Block & block); void write(const Block & block);
private: private:
StorageMemory & storage; StorageMemory & storage;
@ -79,7 +80,7 @@ public:
BlockOutputStreamPtr write( BlockOutputStreamPtr write(
ASTPtr query); ASTPtr query);
void dropImpl(); void drop() override;
void rename(const String & new_path_to_db, const String & new_name) { name = new_name; } void rename(const String & new_path_to_db, const String & new_name) { name = new_name; }
private: private:

View File

@ -17,8 +17,6 @@ typedef Poco::SharedPtr<StorageMerge> StorageMergePtr;
*/ */
class StorageMerge : public IStorage class StorageMerge : public IStorage
{ {
typedef std::vector<StoragePtr> SelectedTables;
public: public:
static StoragePtr create( static StoragePtr create(
const std::string & name_, /// Имя таблицы. const std::string & name_, /// Имя таблицы.
@ -43,7 +41,7 @@ public:
size_t max_block_size = DEFAULT_BLOCK_SIZE, size_t max_block_size = DEFAULT_BLOCK_SIZE,
unsigned threads = 1); unsigned threads = 1);
void dropImpl() {} void drop() override {}
void rename(const String & new_path_to_db, const String & new_name) { name = new_name; } void rename(const String & new_path_to_db, const String & new_name) { name = new_name; }
void getSelectedTables(StorageVector & selected_tables); void getSelectedTables(StorageVector & selected_tables);

View File

@ -1,123 +1,21 @@
#pragma once #pragma once
#include <statdaemons/Increment.h> #include <DB/Storages/MergeTree/MergeTreeData.h>
#include <statdaemons/threadpool.hpp> #include "MergeTree/MergeTreeDataSelectExecutor.h"
#include "MergeTree/MergeTreeDataWriter.h"
#include <DB/Core/SortDescription.h> #include "MergeTree/MergeTreeDataMerger.h"
#include <DB/Interpreters/Context.h> #include "MergeTree/DiskSpaceMonitor.h"
#include <DB/Interpreters/ExpressionActions.h>
#include <DB/Storages/IStorage.h>
#include <Poco/RWLock.h>
namespace DB namespace DB
{ {
/** Движок, использующий merge tree для инкрементальной сортировки данных. /** См. описание структуры данных в MergeTreeData.
* Таблица представлена набором сортированных кусков.
* При вставке, данные сортируются по указанному выражению (первичному ключу) и пишутся в новый кусок.
* Куски объединяются в фоне, согласно некоторой эвристике.
* Для каждого куска, создаётся индексный файл, содержащий значение первичного ключа для каждой 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.
*/ */
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<MarkRange> MarkRanges;
class StorageMergeTree : public IStorage class StorageMergeTree : public IStorage
{ {
friend class MergeTreeReader;
friend class MergeTreeBlockInputStream;
friend class MergeTreeBlockOutputStream; friend class MergeTreeBlockOutputStream;
friend class IMergedBlockOutputStream;
friend class MergedBlockOutputStream;
friend class MergedColumnOnlyOutputStream;
public: public:
/// Режим работы. См. выше.
enum Mode
{
Ordinary,
Collapsing,
Summing,
};
/** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце), /** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце),
* (корректность имён и путей не проверяется) * (корректность имён и путей не проверяется)
* состоящую из указанных столбцов. * состоящую из указанных столбцов.
@ -132,36 +30,26 @@ public:
const String & date_column_name_, const String & date_column_name_,
const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается. const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается.
size_t index_granularity_, size_t index_granularity_,
Mode mode_ = Ordinary, MergeTreeData::Mode mode_ = MergeTreeData::Ordinary,
const String & sign_column_ = "", const String & sign_column_ = "",
const StorageMergeTreeSettings & settings_ = StorageMergeTreeSettings()); const MergeTreeSettings & settings_ = MergeTreeSettings());
void shutdown(); void shutdown();
~StorageMergeTree(); ~StorageMergeTree();
std::string getName() const std::string getName() const
{ {
switch (mode) return data.getModePrefix() + "MergeTree";
{
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);
}
} }
std::string getTableName() const { return name; } std::string getTableName() const { return name; }
std::string getSignColumnName() const { return sign_column; } std::string getSignColumnName() const { return data.getSignColumnName(); }
bool supportsSampling() const { return !!sampling_expression; } bool supportsSampling() const { return data.supportsSampling(); }
bool supportsFinal() const { return !sign_column.empty(); } bool supportsFinal() const { return data.supportsFinal(); }
bool supportsPrewhere() const { return true; } bool supportsPrewhere() const { return data.supportsPrewhere(); }
const NamesAndTypesList & getColumnsList() const { return *columns; } const NamesAndTypesList & getColumnsList() const { return data.getColumnsList(); }
/** При чтении, выбирается набор кусков, покрывающий нужный диапазон индекса.
*/
BlockInputStreams read( BlockInputStreams read(
const Names & column_names, const Names & column_names,
ASTPtr query, ASTPtr query,
@ -170,8 +58,6 @@ public:
size_t max_block_size = DEFAULT_BLOCK_SIZE, size_t max_block_size = DEFAULT_BLOCK_SIZE,
unsigned threads = 1); unsigned threads = 1);
/** При записи, данные сортируются и пишутся в новые куски.
*/
BlockOutputStreamPtr write(ASTPtr query); BlockOutputStreamPtr write(ASTPtr query);
/** Выполнить очередной шаг объединения кусков. /** Выполнить очередной шаг объединения кусков.
@ -182,264 +68,75 @@ public:
return true; return true;
} }
void dropImpl(); void drop() override;
void rename(const String & new_path_to_db, const String & new_name); 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 alter(const ASTAlterQuery::Parameters & params);
void prepareAlterModify(const ASTAlterQuery::Parameters & params);
class BigLock void commitAlterModify(const ASTAlterQuery::Parameters & params);
{
public:
BigLock(StorageMergeTree & storage) : merge_lock(storage.merge_lock),
write_lock(storage.write_lock), read_lock(storage.read_lock)
{
}
private:
Poco::ScopedWriteRWLock merge_lock;
Poco::ScopedWriteRWLock write_lock;
Poco::ScopedWriteRWLock read_lock;
};
typedef Poco::SharedPtr<BigLock> BigLockPtr;
BigLockPtr lockAllOperations()
{
return new BigLock(*this);
}
private: private:
String path; String path;
String name; String name;
String full_path; 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; Increment increment;
MergeTreeData data;
MergeTreeDataSelectExecutor reader;
MergeTreeDataWriter writer;
MergeTreeDataMerger merger;
MergeTreeData::DataParts currently_merging;
Poco::FastMutex currently_merging_mutex;
Logger * log; Logger * log;
volatile bool shutdown_called; volatile bool shutdown_called;
/// Регулярное выражение соответсвующее названию директории с кусочками Poco::SharedPtr<boost::threadpool::pool> merge_threads;
Poco::RegularExpression file_name_regexp;
/// Описание куска с данными. /// Пока существует, помечает части как currently_merging и держит резерв места.
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<Field> Index;
Index index;
/// NOTE можно загружать засечки тоже в оперативку
/// Вычисляем сумарный размер всей директории со всеми файлами
static size_t calcTotalSize(const String &from)
{
Poco::File cur(from);
if (cur.isFile())
return cur.getSize();
std::vector<std::string> 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<size_t>(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<DataPart> DataPartPtr;
struct DataPartPtrLess { bool operator() (const DataPartPtr & lhs, const DataPartPtr & rhs) const { return *lhs < *rhs; } };
typedef std::set<DataPartPtr, DataPartPtrLess> DataParts;
struct RangesInDataPart
{
DataPartPtr data_part;
MarkRanges ranges;
RangesInDataPart() {}
RangesInDataPart(DataPartPtr data_part_)
: data_part(data_part_)
{
}
};
/// Пока существует, помечает части как currently_merging и пересчитывает общий объем сливаемых данных.
/// Вероятно, что части будут помечены заранее. /// Вероятно, что части будут помечены заранее.
class CurrentlyMergingPartsTagger struct CurrentlyMergingPartsTagger
{ {
public: MergeTreeData::DataPartsVector parts;
std::vector<DataPartPtr> parts; DiskSpaceMonitor::ReservationPtr reserved_space;
Poco::FastMutex & data_mutex; StorageMergeTree & storage;
CurrentlyMergingPartsTagger(const std::vector<DataPartPtr> & parts_, Poco::FastMutex & data_mutex_) : parts(parts_), data_mutex(data_mutex_) CurrentlyMergingPartsTagger(const MergeTreeData::DataPartsVector & parts_, size_t total_size, StorageMergeTree & storage_)
: parts(parts_), storage(storage_)
{ {
/// Здесь не лочится мьютекс, так как конструктор вызывается внутри selectPartsToMerge, где он уже залочен /// Здесь не лочится мьютекс, так как конструктор вызывается внутри mergeThread, где он уже залочен.
/// Poco::ScopedLock<Poco::FastMutex> lock(data_mutex); reserved_space = DiskSpaceMonitor::reserve(storage.full_path, total_size); /// Может бросить исключение.
for (size_t i = 0; i < parts.size(); ++i) for (const auto & part : parts)
{ {
parts[i]->currently_merging = true; if (storage.currently_merging.count(part))
StorageMergeTree::total_size_of_currently_merging_parts += parts[i]->size_in_bytes; throw Exception("Tagging alreagy tagged part " + part->name + ". This is a bug.", ErrorCodes::LOGICAL_ERROR);
} }
storage.currently_merging.insert(parts.begin(), parts.end());
} }
~CurrentlyMergingPartsTagger() ~CurrentlyMergingPartsTagger()
{ {
Poco::ScopedLock<Poco::FastMutex> lock(data_mutex); try
for (size_t i = 0; i < parts.size(); ++i)
{ {
parts[i]->currently_merging = false; Poco::ScopedLock<Poco::FastMutex> lock(storage.currently_merging_mutex);
StorageMergeTree::total_size_of_currently_merging_parts -= parts[i]->size_in_bytes; for (const auto & part : parts)
{
if (!storage.currently_merging.count(part))
throw Exception("Untagging already untagged part " + part->name + ". This is a bug.", ErrorCodes::LOGICAL_ERROR);
storage.currently_merging.erase(part);
}
}
catch (...)
{
tryLogCurrentException("~CurrentlyMergingPartsTagger");
} }
} }
}; };
/// Сумарный размер currently_merging кусочков в байтах. typedef Poco::SharedPtr<CurrentlyMergingPartsTagger> CurrentlyMergingPartsTaggerPtr;
/// Нужно чтобы оценить количество места на диске, которое может понадобится для завершения этих мерджей.
static size_t total_size_of_currently_merging_parts;
typedef std::vector<RangesInDataPart> 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;
StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_, StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_,
const Context & context_, const Context & context_,
@ -447,74 +144,25 @@ private:
const String & date_column_name_, const String & date_column_name_,
const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается. const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается.
size_t index_granularity_, size_t index_granularity_,
Mode mode_ = Ordinary, MergeTreeData::Mode mode_,
const String & sign_column_ = "", const String & sign_column_,
const StorageMergeTreeSettings & settings_ = StorageMergeTreeSettings()); const MergeTreeSettings & settings_);
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, объединяет, пока это возможно. /** Определяет, какие куски нужно объединять, и запускает их слияние в отдельном потоке. Если iterations = 0, объединяет, пока это возможно.
* Если aggressive - выбрать куски не обращая внимание на соотношение размеров и их новизну (для запроса OPTIMIZE). * Если aggressive - выбрать куски не обращая внимание на соотношение размеров и их новизну (для запроса OPTIMIZE).
*/ */
void merge(size_t iterations = 1, bool async = true, bool aggressive = false); void merge(size_t iterations = 1, bool async = true, bool aggressive = false);
/// Если while_can, объединяет в цикле, пока можно; иначе выбирает и объединяет только одну пару кусков. /// Если while_can, объединяет в цикле, пока можно; иначе выбирает и объединяет только одну пару кусков.
void mergeThread(bool while_can, bool aggressive); void mergeThread(bool while_can, bool aggressive);
/// Сразу помечает их как currently_merging.
/// Если merge_anything_for_old_months, для кусков за прошедшие месяцы снимается ограничение на соотношение размеров.
bool selectPartsToMerge(Poco::SharedPtr<CurrentlyMergingPartsTagger> & what, bool merge_anything_for_old_months, bool aggressive);
void mergeParts(Poco::SharedPtr<CurrentlyMergingPartsTagger> & what);
/// Дождаться, пока фоновые потоки закончат слияния. /// Дождаться, пока фоновые потоки закончат слияния.
void joinMergeThreads(); void joinMergeThreads();
Poco::SharedPtr<boost::threadpool::pool> merge_threads; /// Вызывается во время выбора кусков для слияния.
bool canMergeParts(const MergeTreeData::DataPartPtr & left, const MergeTreeData::DataPartPtr & right);
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);
}; };
} }

View File

@ -1,86 +0,0 @@
#pragma once
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <DB/Core/Exception.h>
namespace DB
{
class IStorage;
class StoragePtr
{
private:
/// Содержит IStorage. В деструкторе при необходимости вызывает IStorage::dropImpl() перед уничтожением IStorage.
struct Wrapper
{
Wrapper();
Wrapper(IStorage * s);
boost::scoped_ptr<IStorage> storage;
~Wrapper();
};
StoragePtr(boost::weak_ptr<Wrapper> p) : ptr(p) {}
boost::shared_ptr<Wrapper> ptr;
friend class IStorage;
public:
StoragePtr() {}
StoragePtr(const StoragePtr & p) : ptr(p.ptr) {}
StoragePtr& operator= (const StoragePtr & p)
{
ptr = p.ptr;
return *this;
}
IStorage* get() const
{
if (ptr == NULL)
return NULL;
else
return ptr->storage.get();
}
bool operator== (const IStorage * p) const
{
return get() == p;
}
IStorage* operator-> () const
{
return get();
}
IStorage& operator* () const
{
return *get();
}
operator IStorage*() const
{
return get();
}
operator bool() const
{
return bool(ptr);
}
bool operator! () const
{
return !bool(ptr);
}
};
}

View File

@ -11,6 +11,7 @@
#include <DB/IO/CompressedWriteBuffer.h> #include <DB/IO/CompressedWriteBuffer.h>
#include <DB/Storages/IStorage.h> #include <DB/Storages/IStorage.h>
#include <DB/DataStreams/IProfilingBlockInputStream.h> #include <DB/DataStreams/IProfilingBlockInputStream.h>
#include <DB/DataStreams/IBlockOutputStream.h>
namespace DB namespace DB
@ -22,20 +23,10 @@ class StorageTinyLog;
class TinyLogBlockInputStream : public IProfilingBlockInputStream class TinyLogBlockInputStream : public IProfilingBlockInputStream
{ {
public: 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 getName() const { return "TinyLogBlockInputStream"; }
String getID() const 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();
}
protected: protected:
Block readImpl(); Block readImpl();
@ -68,7 +59,7 @@ private:
class TinyLogBlockOutputStream : public IBlockOutputStream class TinyLogBlockOutputStream : public IBlockOutputStream
{ {
public: public:
TinyLogBlockOutputStream(StoragePtr owned_storage); TinyLogBlockOutputStream(StorageTinyLog & storage_);
void write(const Block & block); void write(const Block & block);
void writeSuffix(); void writeSuffix();
private: private:
@ -134,7 +125,7 @@ public:
BlockOutputStreamPtr write( BlockOutputStreamPtr write(
ASTPtr query); ASTPtr query);
void dropImpl(); void drop() override;
void rename(const String & new_path_to_db, const String & new_name); void rename(const String & new_path_to_db, const String & new_name);

View File

@ -26,7 +26,7 @@ public:
size_t max_block_size = DEFAULT_BLOCK_SIZE, size_t max_block_size = DEFAULT_BLOCK_SIZE,
unsigned threads = 1); unsigned threads = 1);
virtual void dropImpl(); virtual void drop() override;
protected: protected:
String select_database_name; String select_database_name;

View File

@ -2,7 +2,7 @@
#include <Poco/SharedPtr.h> #include <Poco/SharedPtr.h>
#include <DB/Storages/StoragePtr.h> #include <DB/Storages/IStorage.h>
#include <DB/Parsers/ASTFunction.h> #include <DB/Parsers/ASTFunction.h>
#include <DB/Interpreters/Context.h> #include <DB/Interpreters/Context.h>

View File

@ -55,15 +55,26 @@ private:
{ {
OptimizedRegularExpression table_name_regexp(table_name_regexp_); OptimizedRegularExpression table_name_regexp(table_name_regexp_);
/// Список таблиц могут менять в другом потоке. StoragePtr any_table;
Poco::ScopedLock<Poco::Mutex> 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());
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<Poco::Mutex> 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());
} }
}; };

View File

@ -56,9 +56,13 @@ public:
std::vector <std::vector< String> > names; std::vector <std::vector< String> > names;
std::vector<String> shards = parseDescription(descripton, 0, descripton.size(), ','); std::vector<String> shards = parseDescription(descripton, 0, descripton.size(), ',');
for (size_t i = 0; i < shards.size(); ++i) for (size_t i = 0; i < shards.size(); ++i)
names.push_back(parseDescription(shards[i], 0, shards[i].size(), '|')); 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> cluster = new Cluster(context.getSettings(), context.getDataTypeFactory(), names, username, password); SharedPtr<Cluster> cluster = new Cluster(context.getSettings(), context.getDataTypeFactory(), names, username, password);
return StorageDistributed::create(getName(), chooseColumns(*cluster, remote_database, remote_table, context), return StorageDistributed::create(getName(), chooseColumns(*cluster, remote_database, remote_table, context),

View File

@ -9,7 +9,7 @@
#include <DB/Parsers/ASTExpressionList.h> #include <DB/Parsers/ASTExpressionList.h>
#include <DB/Parsers/ASTLiteral.h> #include <DB/Parsers/ASTLiteral.h>
#include <DB/Parsers/ASTSelectQuery.h> #include <DB/Parsers/ASTSelectQuery.h>
#include <DB/Storages/StoragePtr.h> #include <DB/Storages/IStorage.h>
#include <DB/Interpreters/InterpreterSelectQuery.h> #include <DB/Interpreters/InterpreterSelectQuery.h>
namespace DB namespace DB

View File

@ -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) void Context::addTable(const String & database_name, const String & table_name, StoragePtr table)
{ {
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex); Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
String db = database_name.empty() ? current_database : database_name; String db = database_name.empty() ? current_database : database_name;
assertDatabaseExists(db); assertDatabaseExists(db);
@ -240,19 +240,22 @@ void Context::detachDatabase(const String & database_name)
String db = database_name.empty() ? current_database : database_name; String db = database_name.empty() ? current_database : database_name;
assertDatabaseExists(db); assertDatabaseExists(db);
shared->databases.erase(shared->databases.find(db)); shared->databases.erase(db);
shared->database_droppers.erase(db);
} }
ASTPtr Context::getCreateQuery(const String & database_name, const String & table_name) const ASTPtr Context::getCreateQuery(const String & database_name, const String & table_name) const
{ {
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex); StoragePtr table;
String db;
String db = database_name.empty() ? current_database : database_name; {
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
db = database_name.empty() ? current_database : database_name;
table = getTable(db, table_name);
}
assertDatabaseExists(db); auto table_lock = table->lockStructure(false);
assertTableExists(db, table_name);
/// Здесь хранится определение таблицы /// Здесь хранится определение таблицы
String metadata_path = shared->path + "metadata/" + escapeForFileName(db) + "/" + escapeForFileName(table_name) + ".sql"; 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 try
{ {
/// Если файл .sql не предусмотрен (например, для таблиц типа ChunkRef), то движок может сам предоставить запрос CREATE. /// Если файл .sql не предусмотрен (например, для таблиц типа ChunkRef), то движок может сам предоставить запрос CREATE.
return getTable(database_name, table_name)->getCustomCreateQuery(*this); return table->getCustomCreateQuery(*this);
} }
catch (...) catch (...)
{ {
@ -304,22 +307,6 @@ ASTPtr Context::getCreateQuery(const String & database_name, const String & tabl
} }
DatabaseDropperPtr Context::getDatabaseDropper(const String & name)
{
Poco::ScopedLock<Poco::Mutex> 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 Settings Context::getSettings() const
{ {
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex); Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);

View File

@ -1229,9 +1229,7 @@ Sets ExpressionAnalyzer::getSetsWithSubqueries()
{ {
Sets res; Sets res;
for (auto & s : sets_with_subqueries) for (auto & s : sets_with_subqueries)
{
res.push_back(s.second); res.push_back(s.second);
}
return res; return res;
} }

View File

@ -46,14 +46,7 @@ void InterpreterAlterQuery::execute()
String database_name = alter.database.empty() ? context.getCurrentDatabase() : alter.database; String database_name = alter.database.empty() ? context.getCurrentDatabase() : alter.database;
StoragePtr table = context.getTable(database_name, table_name); StoragePtr table = context.getTable(database_name, table_name);
auto table_soft_lock = table->lockDataForAlter();
/// для merge tree запрещаем все операции
StorageMergeTree::BigLockPtr merge_tree_lock;
if (StorageMergeTree * table_merge_tree = dynamic_cast<StorageMergeTree *>(table.get()))
merge_tree_lock = table_merge_tree->lockAllOperations();
/// Poco::Mutex является рекурсивным, т.е. взятие мьютекса дважды из одного потока не приводит к блокировке
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
const DataTypeFactory & data_type_factory = context.getDataTypeFactory(); const DataTypeFactory & data_type_factory = context.getDataTypeFactory();
String path = context.getPath(); String path = context.getPath();
@ -68,7 +61,6 @@ void InterpreterAlterQuery::execute()
attach.attach = true; attach.attach = true;
ASTs & columns = dynamic_cast<ASTExpressionList &>(*attach.columns).children; ASTs & columns = dynamic_cast<ASTExpressionList &>(*attach.columns).children;
/// Различные проверки, на возможность выполнения запроса /// Различные проверки, на возможность выполнения запроса
ASTs columns_copy = columns; ASTs columns_copy = columns;
IdentifierNameSet identifier_names; IdentifierNameSet identifier_names;
@ -137,13 +129,33 @@ void InterpreterAlterQuery::execute()
} }
} }
/// Пока разрешим читать из таблицы. Запретим при первой попытке изменить структуру таблицы.
/// Это позволит сделать большую часть первого MODIFY, не останавливая чтение из таблицы.
IStorage::TableStructureWriteLockPtr table_hard_lock;
/// todo cycle over sub tables and tables /// todo cycle over sub tables and tables
/// Применяем изменения /// Применяем изменения
for (ASTAlterQuery::ParameterContainer::const_iterator alter_it = alter.parameters.begin(); for (ASTAlterQuery::ParameterContainer::const_iterator alter_it = alter.parameters.begin();
alter_it != alter.parameters.end(); ++alter_it) alter_it != alter.parameters.end(); ++alter_it)
{ {
const ASTAlterQuery::Parameters & params = *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) if (params.type == ASTAlterQuery::ADD)
{ {

View File

@ -74,11 +74,26 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
return StoragePtr(); return StoragePtr();
} }
StoragePtr res;
SharedPtr<InterpreterSelectQuery> interpreter_select; SharedPtr<InterpreterSelectQuery> 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; String storage_name;
NamesAndTypesListPtr columns = new NamesAndTypesList; 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<Poco::Mutex> lock(context.getMutex()); Poco::ScopedLock<Poco::Mutex> 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); 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) if (create.columns)
{ {
@ -112,13 +121,12 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
} }
} }
else if (!create.as_table.empty()) 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) else if (create.select)
{ {
Block sample = interpreter_select->getSampleBlock();
columns = new NamesAndTypesList; columns = new NamesAndTypesList;
for (size_t i = 0; i < sample.columns(); ++i) for (size_t i = 0; i < select_sample.columns(); ++i)
columns->push_back(NameAndTypePair(sample.getByPosition(i).name, sample.getByPosition(i).type)); columns->push_back(NameAndTypePair(select_sample.getByPosition(i).name, select_sample.getByPosition(i).type));
} }
else else
throw Exception("Incorrect CREATE query: required list of column descriptions or AS section or SELECT.", ErrorCodes::INCORRECT_QUERY); 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()) 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<const ASTCreateQuery &>(*context.getCreateQuery(as_database_name, as_table_name)).storage; create.storage = dynamic_cast<const ASTCreateQuery &>(*context.getCreateQuery(as_database_name, as_table_name)).storage;
} }
else if (create.is_view) else if (create.is_view)

View File

@ -1,10 +1,9 @@
#include <Poco/File.h> #include <Poco/File.h>
#include <DB/Common/escapeForFileName.h> #include <DB/Common/escapeForFileName.h>
#include <DB/Parsers/ASTDropQuery.h> #include <DB/Parsers/ASTDropQuery.h>
#include <DB/Interpreters/InterpreterDropQuery.h> #include <DB/Interpreters/InterpreterDropQuery.h>
#include <DB/Storages/IStorage.h>
namespace DB namespace DB
@ -19,8 +18,6 @@ InterpreterDropQuery::InterpreterDropQuery(ASTPtr query_ptr_, Context & context_
void InterpreterDropQuery::execute() void InterpreterDropQuery::execute()
{ {
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
String path = context.getPath(); String path = context.getPath();
String current_database = context.getCurrentDatabase(); 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 = drop.database.empty() ? current_database : drop.database;
String database_name_escaped = escapeForFileName(database_name); 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 data_path = path + "data/" + database_name_escaped + "/";
String metadata_path = path + "metadata/" + database_name_escaped + "/" + (!table_name.empty() ? table_name_escaped + ".sql" : ""); String metadata_path = path + "metadata/" + database_name_escaped + "/";
if (!drop.if_exists) StorageVector tables_to_drop;
context.assertDatabaseExists(database_name);
if (!drop.table.empty()) if (!drop.table.empty())
{ {
/// Удаление таблицы StoragePtr table;
if (!context.isTableExist(database_name, table_name))
{ if (drop.if_exists)
if (!drop.if_exists) table = context.tryGetTable(database_name, drop.table);
throw Exception("Table " + database_name + "." + table_name + " doesn't exist.", ErrorCodes::UNKNOWN_TABLE);
}
else else
{ table = context.getTable(database_name, drop.table);
/// Удаляем данные таблицы
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();
/// Для таблиц типа ChunkRef, файла с метаданными не существует. if (table)
if (Poco::File(metadata_path).exists()) tables_to_drop.push_back(table);
Poco::File(metadata_path).remove(); else
} return;
/// Удаляем информацию о таблице из оперативки
StoragePtr detached_table = context.detachTable(database_name, table_name);
{
/** Во время уничтожения объекта с таблицей, mutex должен быть разблокирован,
* так как таблица может ожидать завершения потоков, которые прямо сейчас ждут этого mutex-а.
*/
Poco::ScopedUnlock<Poco::Mutex> unlock(context.getMutex());
detached_table->shutdown();
detached_table = StoragePtr();
}
}
} }
else else
{ {
if (context.isDatabaseExist(database_name)) Poco::ScopedLock<Poco::Mutex> 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)
{ {
/// Удаление базы данных tables_to_drop.push_back(it.second);
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<Poco::Mutex> 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);
} }
} }
for (StoragePtr table : tables_to_drop)
{
table->shutdown();
/// Если кто-то успел удалить эту таблицу, выбросит исключение.
auto table_lock = table->lockForAlter();
String current_table_name = table->getTableName();
/// Удаляем информацию о таблице из оперативки
context.detachTable(database_name, current_table_name);
/// Удаляем данные таблицы
if (!drop.detach)
{
String current_data_path = data_path + escapeForFileName(current_table_name);
String current_metadata_path = metadata_path + escapeForFileName(current_table_name) + ".sql";
/// Для таблиц типа ChunkRef, файла с метаданными не существует.
if (Poco::File(current_metadata_path).exists())
Poco::File(current_metadata_path).remove();
table->drop();
table->is_dropped = true;
if (Poco::File(current_data_path).exists())
Poco::File(current_data_path).remove(true);
}
}
if (drop.table.empty())
{
/// Удаление базы данных. Таблицы в ней уже удалены.
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
/// Кто-то мог успеть удалить БД до нас.
context.assertDatabaseExists(database_name);
/// Кто-то мог успеть создать таблицу в удаляемой БД, пока мы удаляли таблицы без лока контекста.
if (!context.getDatabases()[database_name].empty())
throw Exception("New table appeared in database being dropped. Try dropping it again.", ErrorCodes::DATABASE_NOT_EMPTY);
/// Удаляем информацию о БД из оперативки
context.detachDatabase(database_name);
Poco::File(data_path).remove(false);
Poco::File(metadata_path).remove(false);
}
}
void InterpreterDropQuery::dropDetachedTable(String database_name, StoragePtr table, Context & context)
{
table->shutdown();
auto table_lock = table->lockForAlter();
String table_name = table->getTableName();
String path = context.getPath();
String database_name_escaped = escapeForFileName(database_name);
String data_path = path + "data/" + database_name_escaped + "/" + escapeForFileName(table_name);
String metadata_path = path + "metadata/" + database_name_escaped + "/" + escapeForFileName(table_name) + ".sql";
if (Poco::File(metadata_path).exists())
Poco::File(metadata_path).remove();
table->drop();
table->is_dropped = true;
if (Poco::File(data_path).exists())
Poco::File(data_path).remove(true);
} }

View File

@ -64,12 +64,15 @@ void InterpreterInsertQuery::execute(ReadBuffer * remaining_data_istr)
{ {
ASTInsertQuery & query = dynamic_cast<ASTInsertQuery &>(*query_ptr); ASTInsertQuery & query = dynamic_cast<ASTInsertQuery &>(*query_ptr);
StoragePtr table = getTable(); StoragePtr table = getTable();
auto table_lock = table->lockStructure(true);
BlockInputStreamPtr in; BlockInputStreamPtr in;
NamesAndTypesListPtr required_columns = new NamesAndTypesList (table->getSampleBlock().getColumnsList()); NamesAndTypesListPtr required_columns = new NamesAndTypesList (table->getSampleBlock().getColumnsList());
/// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку. /// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку.
table->write(query_ptr); table->write(query_ptr);
/// Создаем кортеж из нескольких стримов, в которые будем писать данные. /// Создаем кортеж из нескольких стримов, в которые будем писать данные.
BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns); BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns);
/// Какой тип запроса: INSERT VALUES | INSERT FORMAT | INSERT SELECT? /// Какой тип запроса: INSERT VALUES | INSERT FORMAT | INSERT SELECT?
@ -112,11 +115,13 @@ void InterpreterInsertQuery::execute(ReadBuffer * remaining_data_istr)
} }
BlockOutputStreamPtr InterpreterInsertQuery::execute() BlockIO InterpreterInsertQuery::execute()
{ {
ASTInsertQuery & query = dynamic_cast<ASTInsertQuery &>(*query_ptr); ASTInsertQuery & query = dynamic_cast<ASTInsertQuery &>(*query_ptr);
StoragePtr table = getTable(); StoragePtr table = getTable();
auto table_lock = table->lockStructure(true);
NamesAndTypesListPtr required_columns = new NamesAndTypesList(table->getSampleBlock().getColumnsList()); NamesAndTypesListPtr required_columns = new NamesAndTypesList(table->getSampleBlock().getColumnsList());
/// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку. /// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку.
@ -124,18 +129,23 @@ BlockOutputStreamPtr InterpreterInsertQuery::execute()
/// Создаем кортеж из нескольких стримов, в которые будем писать данные. /// Создаем кортеж из нескольких стримов, в которые будем писать данные.
BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns); BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns);
BlockIO res;
res.out_sample = getSampleBlock();
/// Какой тип запроса: INSERT или INSERT SELECT? /// Какой тип запроса: INSERT или INSERT SELECT?
if (!query.select) if (!query.select)
return out; {
res.out = out;
}
else else
{ {
InterpreterSelectQuery interpreter_select(query.select, context); InterpreterSelectQuery interpreter_select(query.select, context);
BlockInputStreamPtr in = interpreter_select.execute(); BlockInputStreamPtr in = interpreter_select.execute();
in = new MaterializingBlockInputStream(in); in = new MaterializingBlockInputStream(in);
copyData(*in, *out); copyData(*in, *out);
return NULL;
} }
return res;
} }

View File

@ -135,8 +135,7 @@ BlockIO InterpreterQuery::execute()
{ {
throwIfReadOnly(); throwIfReadOnly();
InterpreterInsertQuery interpreter(query_ptr, context); InterpreterInsertQuery interpreter(query_ptr, context);
res.out = interpreter.execute(); res = interpreter.execute();
res.out_sample = interpreter.getSampleBlock();
} }
else if (dynamic_cast<ASTCreateQuery *>(&*query_ptr)) else if (dynamic_cast<ASTCreateQuery *>(&*query_ptr))
{ {

View File

@ -27,9 +27,6 @@ InterpreterRenameQuery::InterpreterRenameQuery(ASTPtr query_ptr_, Context & cont
void InterpreterRenameQuery::execute() void InterpreterRenameQuery::execute()
{ {
/** Все таблицы переименовываются под глобальной блокировкой. */
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
String path = context.getPath(); String path = context.getPath();
String current_database = context.getCurrentDatabase(); String current_database = context.getCurrentDatabase();
@ -53,11 +50,18 @@ void InterpreterRenameQuery::execute()
String to_table_name_escaped = escapeForFileName(to_table_name); 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" : ""); String to_metadata_path = path + "metadata/" + to_database_name_escaped + "/" + (!to_table_name.empty() ? to_table_name_escaped + ".sql" : "");
context.assertTableExists(from_database_name, from_table_name); /// Заблокировать таблицу нужно при незаблокированном контексте.
StoragePtr table = context.getTable(from_database_name, from_table_name);
auto table_lock = table->lockForAlter();
/** Все таблицы переименовываются под глобальной блокировкой. */
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
context.assertTableDoesntExist(to_database_name, to_table_name); context.assertTableDoesntExist(to_database_name, to_table_name);
/// Уведомляем таблицу о том, что она переименовается. Если таблица не поддерживает переименование - кинется исключение. /// Уведомляем таблицу о том, что она переименовается. Если таблица не поддерживает переименование - кинется исключение.
context.getTable(from_database_name, from_table_name)->rename(path + "data/" + to_database_name_escaped + "/", to_table_name);
table->rename(path + "data/" + to_database_name_escaped + "/", to_table_name);
/// Пишем новый файл с метаданными. /// Пишем новый файл с метаданными.
{ {

View File

@ -41,28 +41,42 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndType
throw Exception("Too deep subqueries. Maximum: " + toString(settings.limits.max_subquery_depth), throw Exception("Too deep subqueries. Maximum: " + toString(settings.limits.max_subquery_depth),
ErrorCodes::TOO_DEEP_SUBQUERIES); ErrorCodes::TOO_DEEP_SUBQUERIES);
/// Если имееем дело с табличной функцией if (query.table && dynamic_cast<ASTSelectQuery *>(&*query.table))
if (query.table && dynamic_cast<const ASTFunction *>(&*query.table))
{ {
/// Получить табличную функцию if (table_column_names.empty())
TableFunctionPtr table_function_ptr = context.getTableFunctionFactory().get(dynamic_cast<const ASTFunction *>(&*query.table)->name, context); context.setColumns(InterpreterSelectQuery(query.table, context).getSampleBlock().getColumnsList());
/// Выполнить ее и запомнить результат }
table_function_storage = table_function_ptr->execute(query.table, context); else
{
if (query.table && dynamic_cast<const ASTFunction *>(&*query.table))
{
/// Получить табличную функцию
TableFunctionPtr table_function_ptr = context.getTableFunctionFactory().get(dynamic_cast<const ASTFunction *>(&*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) if (!table_column_names.empty())
context.setColumns(table_function_storage->getColumnsList());
else if (!table_column_names.empty())
context.setColumns(table_column_names); context.setColumns(table_column_names);
else
context.setColumns(!query.table || !dynamic_cast<ASTSelectQuery *>(&*query.table)
? getTable()->getColumnsList()
: InterpreterSelectQuery(query.table, context).getSampleBlock().getColumnsList());
if (context.getColumns().empty()) if (context.getColumns().empty())
throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN); 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_) if (input_)
streams.push_back(input_); streams.push_back(input_);
@ -127,26 +141,6 @@ void InterpreterSelectQuery::getDatabaseAndTableNames(String & database_name, St
} }
StoragePtr InterpreterSelectQuery::getTable()
{
String database_name;
String table_name;
getDatabaseAndTableNames(database_name, table_name);
return context.getTable(database_name, table_name);
}
ASTPtr InterpreterSelectQuery::getCreateQuery()
{
String database_name;
String table_name;
getDatabaseAndTableNames(database_name, table_name);
return context.getCreateQuery(database_name, table_name);
}
DataTypes InterpreterSelectQuery::getReturnTypes() DataTypes InterpreterSelectQuery::getReturnTypes()
{ {
DataTypes res; DataTypes res;
@ -209,7 +203,8 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
bool need_aggregate = false; bool need_aggregate = false;
bool has_having = false; bool has_having = false;
bool has_order_by = !query.order_expression_list.isNull(); bool has_order_by = !query.order_expression_list.isNull();
ExpressionActionsPtr array_join;
ExpressionActionsPtr before_where; ExpressionActionsPtr before_where;
ExpressionActionsPtr before_aggregation; ExpressionActionsPtr before_aggregation;
ExpressionActionsPtr before_having; ExpressionActionsPtr before_having;
@ -225,8 +220,9 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
if (from_stage < QueryProcessingStage::WithMergeableState if (from_stage < QueryProcessingStage::WithMergeableState
&& to_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)) if (query_analyzer->appendWhere(chain))
{ {
has_where = true; has_where = true;
@ -312,6 +308,16 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
if (need_aggregate) if (need_aggregate)
executeAggregation(streams, before_aggregation, aggregate_overflow_row, aggregate_final); 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, * если не указаны DISTINCT, GROUP, HAVING, ORDER, но указан LIMIT,
* то выполним предварительный LIMIT на удалёном сервере. * то выполним предварительный LIMIT на удалёном сервере.
@ -339,7 +345,7 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
executeHaving(streams, before_having); executeHaving(streams, before_having);
} }
executeOuterExpression(streams, before_order_and_select); executeExpression(streams, before_order_and_select);
if (has_order_by) if (has_order_by)
executeOrder(streams); executeOrder(streams);
@ -424,20 +430,28 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
if (!streams.empty()) if (!streams.empty())
return QueryProcessingStage::FetchColumns; return QueryProcessingStage::FetchColumns;
/// Таблица, откуда читать данные, если не подзапрос.
StoragePtr table;
/// Интерпретатор подзапроса, если подзапрос /// Интерпретатор подзапроса, если подзапрос
SharedPtr<InterpreterSelectQuery> interpreter_subquery; SharedPtr<InterpreterSelectQuery> interpreter_subquery;
/// Список столбцов, которых нужно прочитать, чтобы выполнить запрос. /// Список столбцов, которых нужно прочитать, чтобы выполнить запрос.
Names required_columns = query_analyzer->getRequiredColumns(); Names required_columns = query_analyzer->getRequiredColumns();
if (table_function_storage) if (query.table && dynamic_cast<ASTSelectQuery *>(&*query.table))
table = table_function_storage; /// Если в запросе была указана табличная функция, данные читаем из нее. {
else if (!query.table || !dynamic_cast<ASTSelectQuery *>(&*query.table)) /** Для подзапроса не действуют ограничения на максимальный размер результата.
table = getTable(); * Так как результат поздапроса - ещё не результат всего запроса.
else if (dynamic_cast<ASTSelectQuery *>(&*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, то все запросы выполняем с сэмплингом /// если в настройках установлен default_sample != 1, то все запросы выполняем с сэмплингом
/// если таблица не поддерживает сэмплинг получим исключение /// если таблица не поддерживает сэмплинг получим исключение
@ -445,13 +459,13 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
if (!query.sample_size && settings.default_sample != 1) if (!query.sample_size && settings.default_sample != 1)
query.sample_size = new ASTLiteral(StringRange(NULL, NULL), Float64(settings.default_sample)); 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); 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); 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); throw Exception("Illegal PREWHERE", ErrorCodes::ILLEGAL_PREWHERE);
/** При распределённой обработке запроса, в потоках почти не делается вычислений, /** При распределённой обработке запроса, в потоках почти не делается вычислений,
@ -466,7 +480,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
* и там должно быть оригинальное значение max_threads, а не увеличенное. * и там должно быть оригинальное значение max_threads, а не увеличенное.
*/ */
Settings settings_for_storage = settings; Settings settings_for_storage = settings;
if (table && table->isRemote()) if (storage && storage->isRemote())
settings.max_threads = settings.max_distributed_connections; settings.max_threads = settings.max_distributed_connections;
/// Ограничение на количество столбцов для чтения. /// Ограничение на количество столбцов для чтения.
@ -495,10 +509,18 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns; QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns;
/// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос? /// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос?
if (!query.table || !dynamic_cast<ASTSelectQuery *>(&*query.table)) if (!interpreter_subquery)
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 else
{
streams.push_back(maybeAsynchronous(interpreter_subquery->execute(), settings.asynchronous)); streams.push_back(maybeAsynchronous(interpreter_subquery->execute(), settings.asynchronous));
}
/** Если истчоников слишком много, то склеим их в max_threads источников. /** Если истчоников слишком много, то склеим их в max_threads источников.
* (Иначе действия в каждом маленьком источнике, а затем объединение состояний, слишком неэффективно.) * (Иначе действия в каждом маленьком источнике, а затем объединение состояний, слишком неэффективно.)
@ -510,7 +532,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
* Такие ограничения проверяются на сервере-инициаторе запроса, а не на удалённых серверах. * Такие ограничения проверяются на сервере-инициаторе запроса, а не на удалённых серверах.
* Потому что сервер-инициатор имеет суммарные данные о выполнении запроса на всех серверах. * Потому что сервер-инициатор имеет суммарные данные о выполнении запроса на всех серверах.
*/ */
if (table && to_stage == QueryProcessingStage::Complete) if (storage && to_stage == QueryProcessingStage::Complete)
{ {
IProfilingBlockInputStream::LocalLimits limits; IProfilingBlockInputStream::LocalLimits limits;
limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL; limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL;
@ -647,7 +669,7 @@ void InterpreterSelectQuery::executeTotalsAndHaving(BlockInputStreams & streams,
} }
void InterpreterSelectQuery::executeOuterExpression(BlockInputStreams & streams, ExpressionActionsPtr expression) void InterpreterSelectQuery::executeExpression(BlockInputStreams & streams, ExpressionActionsPtr expression)
{ {
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads; bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)

View File

@ -168,9 +168,7 @@ void formatAST(const ASTSelectQuery & ast, std::ostream & s, size_t indent, bo
if (ast.prewhere_expression) if (ast.prewhere_expression)
{ {
s << (hilite ? hilite_keyword : "") << nl_or_ws << indent_str << "PREWHERE " << (hilite ? hilite_none : ""); s << (hilite ? hilite_keyword : "") << nl_or_ws << indent_str << "PREWHERE " << (hilite ? hilite_none : "");
one_line formatAST(*ast.prewhere_expression, s, indent, hilite, one_line);
? formatAST(*ast.prewhere_expression, s, indent, hilite, one_line)
: formatExpressionListMultiline(dynamic_cast<const ASTExpressionList &>(*ast.prewhere_expression), s, indent, hilite);
} }
if (ast.where_expression) if (ast.where_expression)
@ -224,11 +222,12 @@ void formatAST(const ASTSelectQuery & ast, std::ostream & s, size_t indent, bo
void formatAST(const ASTSubquery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens) 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"; 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); 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) void formatAST(const ASTCreateQuery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
@ -448,6 +447,10 @@ static void writeAlias(const String & name, std::ostream & s, bool hilite, bool
void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens) 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; bool written = false;
if (ast.arguments && !ast.parameters) if (ast.arguments && !ast.parameters)
@ -557,32 +560,32 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool
written = true; written = true;
} }
} }
}
if (!written && 0 == strcmp(ast.name.c_str(), "array")) if (!written && ast.arguments->children.size() >= 1 && 0 == strcmp(ast.name.c_str(), "array"))
{
s << (hilite ? hilite_operator : "") << '[' << (hilite ? hilite_none : "");
for (size_t i = 0; i < ast.arguments->children.size(); ++i)
{ {
s << '['; if (i != 0)
for (size_t i = 0; i < ast.arguments->children.size(); ++i) s << ", ";
{ formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
if (i != 0)
s << ", ";
formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
}
s << ']';
written = true;
} }
s << (hilite ? hilite_operator : "") << ']' << (hilite ? hilite_none : "");
written = true;
}
if (!written && 0 == strcmp(ast.name.c_str(), "tuple")) if (!written && ast.arguments->children.size() >= 2 && 0 == strcmp(ast.name.c_str(), "tuple"))
{
s << (hilite ? hilite_operator : "") << '(' << (hilite ? hilite_none : "");
for (size_t i = 0; i < ast.arguments->children.size(); ++i)
{ {
s << '('; if (i != 0)
for (size_t i = 0; i < ast.arguments->children.size(); ++i) s << ", ";
{ formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
if (i != 0)
s << ", ";
formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
}
s << ')';
written = true;
} }
s << (hilite ? hilite_operator : "") << ')' << (hilite ? hilite_none : "");
written = true;
} }
} }
@ -608,11 +611,18 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool
} }
if (!ast.alias.empty()) if (!ast.alias.empty())
{
writeAlias(ast.alias, s, hilite, one_line); 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) 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 : ""); s << (hilite ? hilite_identifier : "");
WriteBufferFromOStream wb(s, 32); WriteBufferFromOStream wb(s, 32);
@ -622,15 +632,26 @@ void formatAST(const ASTIdentifier & ast, std::ostream & s, size_t indent, bo
s << (hilite ? hilite_none : ""); s << (hilite ? hilite_none : "");
if (!ast.alias.empty()) if (!ast.alias.empty())
{
writeAlias(ast.alias, s, hilite, one_line); 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) 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); s << apply_visitor(FieldVisitorToString(), ast.value);
if (!ast.alias.empty()) if (!ast.alias.empty())
{
writeAlias(ast.alias, s, hilite, one_line); 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) void formatAST(const ASTNameTypePair & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)

View File

@ -523,7 +523,6 @@ struct UserAgentID : public IAttributeMetadata
typedef AttributeIntBase ClickGoodEvent; typedef AttributeIntBase ClickGoodEvent;
typedef AttributeIntBase ClickPriorityID; typedef AttributeIntBase ClickPriorityID;
typedef AttributeIntBase ClickBannerID; typedef AttributeIntBase ClickBannerID;
typedef AttributeIntBase ClickPhraseID;
typedef AttributeIntBase ClickPageID; typedef AttributeIntBase ClickPageID;
typedef AttributeIntBase ClickPlaceID; typedef AttributeIntBase ClickPlaceID;
typedef AttributeIntBase ClickTypeID; typedef AttributeIntBase ClickTypeID;
@ -532,7 +531,6 @@ typedef AttributeUIntBase ClickDomainID;
typedef AttributeUIntBase ClickCost; typedef AttributeUIntBase ClickCost;
typedef AttributeHashBase ClickURLHash; typedef AttributeHashBase ClickURLHash;
typedef AttributeUIntBase ClickOrderID; typedef AttributeUIntBase ClickOrderID;
typedef AttributeUIntBase ClickTargetPhraseID;
typedef AttributeUIntBase GoalReachesAny; typedef AttributeUIntBase GoalReachesAny;
typedef AttributeUIntBase GoalReachesDepth; typedef AttributeUIntBase GoalReachesDepth;
typedef AttributeUIntBase GoalReachesURL; typedef AttributeUIntBase GoalReachesURL;
@ -728,7 +726,6 @@ inline AttributeMetadatas GetOLAPAttributeMetadata()
("ClickGoodEvent", new ClickGoodEvent) ("ClickGoodEvent", new ClickGoodEvent)
("ClickPriorityID", new ClickPriorityID) ("ClickPriorityID", new ClickPriorityID)
("ClickBannerID", new ClickBannerID) ("ClickBannerID", new ClickBannerID)
("ClickPhraseID", new ClickPhraseID)
("ClickPageID", new ClickPageID) ("ClickPageID", new ClickPageID)
("ClickPlaceID", new ClickPlaceID) ("ClickPlaceID", new ClickPlaceID)
("ClickTypeID", new ClickTypeID) ("ClickTypeID", new ClickTypeID)
@ -737,7 +734,6 @@ inline AttributeMetadatas GetOLAPAttributeMetadata()
("ClickCost", new ClickCost) ("ClickCost", new ClickCost)
("ClickURLHash", new ClickURLHash) ("ClickURLHash", new ClickURLHash)
("ClickOrderID", new ClickOrderID) ("ClickOrderID", new ClickOrderID)
("ClickTargetPhraseID", new ClickTargetPhraseID)
("GoalReaches", new GoalReaches) ("GoalReaches", new GoalReaches)
("GoalReachesAny", new GoalReachesAny) ("GoalReachesAny", new GoalReachesAny)
("GoalReachesDepth", new GoalReachesDepth) ("GoalReachesDepth", new GoalReachesDepth)

View File

@ -550,7 +550,6 @@ void QueryConverter::fillNumericAttributeMap()
M("ClickGoodEvent", "Clicks.GoodEvent[1]") M("ClickGoodEvent", "Clicks.GoodEvent[1]")
M("ClickPriorityID", "Clicks.PriorityID[1]") M("ClickPriorityID", "Clicks.PriorityID[1]")
M("ClickBannerID", "Clicks.BannerID[1]") M("ClickBannerID", "Clicks.BannerID[1]")
M("ClickPhraseID", "Clicks.PhraseID[1]")
M("ClickPageID", "Clicks.PageID[1]") M("ClickPageID", "Clicks.PageID[1]")
M("ClickPlaceID", "Clicks.PlaceID[1]") M("ClickPlaceID", "Clicks.PlaceID[1]")
M("ClickTypeID", "Clicks.TypeID[1]") M("ClickTypeID", "Clicks.TypeID[1]")
@ -559,7 +558,6 @@ void QueryConverter::fillNumericAttributeMap()
M("ClickCost", "Clicks.Cost[1]") M("ClickCost", "Clicks.Cost[1]")
M("ClickURLHash", "Clicks.URLHash[1]") M("ClickURLHash", "Clicks.URLHash[1]")
M("ClickOrderID", "Clicks.OrderID[1]") M("ClickOrderID", "Clicks.OrderID[1]")
M("ClickTargetPhraseID", "Clicks.TargetPhraseID[1]")
M("GoalReachesAny", "GoalReachesAny") M("GoalReachesAny", "GoalReachesAny")
M("GoalReachesDepth", "GoalReachesDepth") M("GoalReachesDepth", "GoalReachesDepth")
M("GoalReachesURL", "GoalReachesURL") M("GoalReachesURL", "GoalReachesURL")

View File

@ -1,21 +1,16 @@
#include <set>
#include <boost/bind.hpp>
#include <sparsehash/dense_hash_set>
#include <sparsehash/dense_hash_map> #include <sparsehash/dense_hash_map>
#include <sparsehash/dense_hash_set>
#include <DB/Columns/ColumnNested.h> #include <DB/Storages/ITableDeclaration.h>
#include <DB/DataTypes/DataTypeNested.h> #include <DB/DataTypes/DataTypeNested.h>
#include <DB/Storages/IStorage.h>
#include <DB/Parsers/ASTIdentifier.h> #include <DB/Parsers/ASTIdentifier.h>
#include <DB/Parsers/ASTNameTypePair.h> #include <DB/Parsers/ASTNameTypePair.h>
#include <DB/Interpreters/Context.h> #include <DB/Interpreters/Context.h>
#include <boost/bind.hpp>
namespace DB namespace DB
{ {
bool IStorage::hasRealColumn(const String &column_name) const bool ITableDeclaration::hasRealColumn(const String &column_name) const
{ {
const NamesAndTypesList & real_columns = getColumnsList(); const NamesAndTypesList & real_columns = getColumnsList();
for (auto & it : real_columns) for (auto & it : real_columns)
@ -25,7 +20,7 @@ bool IStorage::hasRealColumn(const String &column_name) const
} }
NameAndTypePair IStorage::getRealColumn(const String &column_name) const NameAndTypePair ITableDeclaration::getRealColumn(const String &column_name) const
{ {
const NamesAndTypesList & real_columns = getColumnsList(); const NamesAndTypesList & real_columns = getColumnsList();
for (auto & it : real_columns) for (auto & it : real_columns)
@ -35,30 +30,30 @@ NameAndTypePair IStorage::getRealColumn(const String &column_name) const
} }
bool IStorage::hasColumn(const String &column_name) const bool ITableDeclaration::hasColumn(const String &column_name) const
{ {
return hasRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет. return hasRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
} }
NameAndTypePair IStorage::getColumn(const String &column_name) const NameAndTypePair ITableDeclaration::getColumn(const String &column_name) const
{ {
return getRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет. return getRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
} }
const DataTypePtr IStorage::getDataTypeByName(const String & column_name) const const DataTypePtr ITableDeclaration::getDataTypeByName(const String & column_name) const
{ {
const NamesAndTypesList & names_and_types = getColumnsList(); const NamesAndTypesList & names_and_types = getColumnsList();
for (NamesAndTypesList::const_iterator it = names_and_types.begin(); it != names_and_types.end(); ++it) for (NamesAndTypesList::const_iterator it = names_and_types.begin(); it != names_and_types.end(); ++it)
if (it->first == column_name) if (it->first == column_name)
return it->second; return it->second;
throw Exception("There is no column " + column_name + " in table " + getTableName(), ErrorCodes::NO_SUCH_COLUMN_IN_TABLE); throw Exception("There is no column " + column_name + " in table " + getTableName(), ErrorCodes::NO_SUCH_COLUMN_IN_TABLE);
} }
Block IStorage::getSampleBlock() const Block ITableDeclaration::getSampleBlock() const
{ {
Block res; Block res;
const NamesAndTypesList & names_and_types = getColumnsList(); const NamesAndTypesList & names_and_types = getColumnsList();
@ -71,7 +66,7 @@ Block IStorage::getSampleBlock() const
col.column = col.type->createColumn(); col.column = col.type->createColumn();
res.insert(col); res.insert(col);
} }
return res; return res;
} }
@ -104,10 +99,10 @@ static NamesAndTypesMap getColumnsMap(const NamesAndTypesList & available_column
} }
void IStorage::check(const Names & column_names) const void ITableDeclaration::check(const Names & column_names) const
{ {
const NamesAndTypesList & available_columns = getColumnsList(); const NamesAndTypesList & available_columns = getColumnsList();
if (column_names.empty()) if (column_names.empty())
throw Exception("Empty list of columns queried for table " + getTableName() throw Exception("Empty list of columns queried for table " + getTableName()
+ ". There are columns: " + listOfColumns(available_columns), + ". There are columns: " + listOfColumns(available_columns),
@ -134,14 +129,14 @@ void IStorage::check(const Names & column_names) const
} }
void IStorage::check(const Block & block, bool need_all) const void ITableDeclaration::check(const Block & block, bool need_all) const
{ {
const NamesAndTypesList & available_columns = getColumnsList(); const NamesAndTypesList & available_columns = getColumnsList();
const NamesAndTypesMap & columns_map = getColumnsMap(available_columns); const NamesAndTypesMap & columns_map = getColumnsMap(available_columns);
typedef std::unordered_set<String> NameSet; typedef std::unordered_set<String> NameSet;
NameSet names_in_block; NameSet names_in_block;
for (size_t i = 0; i < block.columns(); ++i) for (size_t i = 0; i < block.columns(); ++i)
{ {
const ColumnWithNameAndType & column = block.getByPosition(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(), + ". Column has type " + it->second->getName() + ", got type " + column.type->getName(),
ErrorCodes::TYPE_MISMATCH); ErrorCodes::TYPE_MISMATCH);
} }
if (need_all && names_in_block.size() < columns_map.size()) if (need_all && names_in_block.size() < columns_map.size())
{ {
for (NamesAndTypesList::const_iterator it = available_columns.begin(); it != available_columns.end(); ++it) 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 совпадает с частью имени до точки /// одинаковыми считаются имена, если они совпадают целиком или name_without_dot совпадает с частью имени до точки
static bool namesEqual(const String & name_without_dot, const DB::NameAndTypePair & name_type) 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); return (name_with_dot == name_type.first.substr(0, name_without_dot.length() + 1) || name_without_dot == name_type.first);
} }
void IStorage::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) const void ITableDeclaration::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context)
{ {
if (params.type == ASTAlterQuery::ADD) if (params.type == ASTAlterQuery::ADD)
{ {
@ -219,14 +213,14 @@ void IStorage::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTy
else if (params.type == ASTAlterQuery::DROP) else if (params.type == ASTAlterQuery::DROP)
{ {
String column_name = dynamic_cast<const ASTIdentifier &>(*(params.column)).name; String column_name = dynamic_cast<const ASTIdentifier &>(*(params.column)).name;
/// Удаляем колонки из листа columns /// Удаляем колонки из листа columns
bool is_first = true; bool is_first = true;
NamesAndTypesList::iterator column_it; NamesAndTypesList::iterator column_it;
do do
{ {
column_it = std::find_if(columns->begin(), columns->end(), boost::bind(namesEqual, column_name, _1)); column_it = std::find_if(columns->begin(), columns->end(), boost::bind(namesEqual, column_name, _1));
if (column_it == columns->end()) if (column_it == columns->end())
{ {
if (is_first) if (is_first)
@ -252,7 +246,7 @@ void IStorage::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTy
column_it->second = data_type; column_it->second = data_type;
} }
else else
throw Exception("Wrong parameter type in ALTER query", ErrorCodes::LOGICAL_ERROR); throw Exception("Wrong parameter type in ALTER query", ErrorCodes::LOGICAL_ERROR);
} }
} }

View File

@ -0,0 +1,9 @@
#include <DB/Storages/MergeTree/DiskSpaceMonitor.h>
namespace DB
{
size_t DiskSpaceMonitor::reserved_bytes;
Poco::FastMutex DiskSpaceMonitor::reserved_bytes_mutex;
}

View File

@ -0,0 +1,690 @@
#include <DB/Storages/MergeTree/MergeTreeData.h>
#include <Yandex/time2str.h>
#include <Poco/Ext/ScopedTry.h>
#include <DB/Interpreters/ExpressionAnalyzer.h>
#include <DB/Common/escapeForFileName.h>
#include <DB/Storages/MergeTree/MergeTreeReader.h>
#include <DB/Storages/MergeTree/MergeTreeBlockInputStream.h>
#include <DB/Storages/MergeTree/MergedBlockOutputStream.h>
#include <DB/Parsers/ASTIdentifier.h>
#include <DB/Parsers/ASTNameTypePair.h>
#include <DB/DataStreams/ExpressionBlockInputStream.h>
namespace DB
{
static String lastTwoPathComponents(String path)
{
if (!path.empty() && *path.rbegin() == '/')
path.erase(path.end() - 1);
size_t slash = path.rfind('/');
if (slash == String::npos || slash == 0)
return path;
slash = path.rfind('/', slash - 1);
if (slash == String::npos)
return path;
return path.substr(slash + 1);
}
MergeTreeData::MergeTreeData(
const String & full_path_, NamesAndTypesListPtr columns_,
const Context & context_,
ASTPtr & primary_expr_ast_,
const String & date_column_name_, const ASTPtr & sampling_expression_,
size_t index_granularity_,
Mode mode_,
const String & sign_column_,
const MergeTreeSettings & settings_)
: context(context_),
date_column_name(date_column_name_), sampling_expression(sampling_expression_),
index_granularity(index_granularity_),
mode(mode_), sign_column(sign_column_),
settings(settings_), primary_expr_ast(primary_expr_ast_->clone()),
full_path(full_path_), columns(columns_),
log(&Logger::get("MergeTreeData: " + lastTwoPathComponents(full_path))),
file_name_regexp("^(\\d{8})_(\\d{8})_(\\d+)_(\\d+)_(\\d+)")
{
/// создаём директорию, если её нет
Poco::File(full_path).createDirectories();
/// инициализируем описание сортировки
sort_descr.reserve(primary_expr_ast->children.size());
for (ASTs::iterator it = primary_expr_ast->children.begin();
it != primary_expr_ast->children.end();
++it)
{
String name = (*it)->getColumnName();
sort_descr.push_back(SortColumnDescription(name, 1));
}
primary_expr = ExpressionAnalyzer(primary_expr_ast, context, *columns).getActions(false);
ExpressionActionsPtr projected_expr = ExpressionAnalyzer(primary_expr_ast, context, *columns).getActions(true);
primary_key_sample = projected_expr->getSampleBlock();
loadDataParts();
clearOldParts();
}
UInt64 MergeTreeData::getMaxDataPartIndex()
{
UInt64 max_part_id = 0;
for (DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it)
{
max_part_id = std::max(max_part_id, (*it)->right);
}
return max_part_id;
}
std::string MergeTreeData::getModePrefix() const
{
switch (mode)
{
case Ordinary: return "";
case Collapsing: return "Collapsing";
case Summing: return "Summing";
default:
throw Exception("Unknown mode of operation for MergeTreeData: " + toString(mode), ErrorCodes::LOGICAL_ERROR);
}
}
String MergeTreeData::getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level)
{
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
/// Имя директории для куска иммет вид: YYYYMMDD_YYYYMMDD_N_N_L.
String res;
{
unsigned left_date_id = Date2OrderedIdentifier(date_lut.fromDayNum(left_date));
unsigned right_date_id = Date2OrderedIdentifier(date_lut.fromDayNum(right_date));
WriteBufferFromString wb(res);
writeIntText(left_date_id, wb);
writeChar('_', wb);
writeIntText(right_date_id, wb);
writeChar('_', wb);
writeIntText(left_id, wb);
writeChar('_', wb);
writeIntText(right_id, wb);
writeChar('_', wb);
writeIntText(level, wb);
}
return res;
}
void MergeTreeData::parsePartName(const String & file_name, const Poco::RegularExpression::MatchVec & matches, DataPart & part)
{
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
part.left_date = date_lut.toDayNum(OrderedIdentifier2Date(file_name.substr(matches[1].offset, matches[1].length)));
part.right_date = date_lut.toDayNum(OrderedIdentifier2Date(file_name.substr(matches[2].offset, matches[2].length)));
part.left = parse<UInt64>(file_name.substr(matches[3].offset, matches[3].length));
part.right = parse<UInt64>(file_name.substr(matches[4].offset, matches[4].length));
part.level = parse<UInt32>(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<Poco::FastMutex> lock(data_parts_mutex);
Poco::ScopedLock<Poco::FastMutex> lock_all(all_data_parts_mutex);
data_parts.clear();
Strings part_file_names;
Strings old_file_names;
Poco::DirectoryIterator end;
for (Poco::DirectoryIterator it(full_path); it != end; ++it)
{
String file_name = it.name();
/// Удаляем временные директории старше суток.
if (0 == file_name.compare(0, strlen("tmp_"), "tmp_"))
{
Poco::File tmp_dir(full_path + file_name);
if (tmp_dir.isDirectory() && tmp_dir.getLastModified().epochTime() + 86400 < time(0))
{
LOG_WARNING(log, "Removing temporary directory " << full_path << file_name);
Poco::File(full_path + file_name).remove(true);
}
continue;
}
if (0 == file_name.compare(0, strlen("old_"), "old_"))
old_file_names.push_back(file_name);
else
part_file_names.push_back(file_name);
}
Poco::RegularExpression::MatchVec matches;
while (!part_file_names.empty())
{
String file_name = part_file_names.back();
part_file_names.pop_back();
if (!isPartDirectory(file_name, matches))
continue;
MutableDataPartPtr part = std::make_shared<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 " << full_path + file_name << " because is't impossible to repair.");
part->remove();
}
else
{
Strings new_parts = tryRestorePart(full_path, file_name, old_file_names);
part_file_names.insert(part_file_names.begin(), new_parts.begin(), new_parts.end());
}
continue;
}
/// Размер - в количестве засечек.
part->size = Poco::File(full_path + file_name + "/" + escapeForFileName(columns->front().first) + ".mrk").getSize()
/ MERGE_TREE_MARK_SIZE;
part->modification_time = Poco::File(full_path + file_name).getLastModified().epochTime();
try
{
part->loadIndex();
}
catch (...)
{
/// Не будем вставлять в набор кусок с битым индексом. Пропустим кусок и позволим серверу запуститься.
tryLogCurrentException(__PRETTY_FUNCTION__);
continue;
}
data_parts.insert(part);
}
all_data_parts = data_parts;
/** Удаляем из набора актуальных кусков куски, которые содержатся в другом куске (которые были склеены),
* но по каким-то причинам остались лежать в файловой системе.
* Удаление файлов будет произведено потом в методе clearOldParts.
*/
if (data_parts.size() >= 2)
{
DataParts::iterator prev_jt = data_parts.begin();
DataParts::iterator curr_jt = prev_jt;
++curr_jt;
while (curr_jt != data_parts.end())
{
/// Куски данных за разные месяцы рассматривать не будем
if ((*curr_jt)->left_month != (*curr_jt)->right_month
|| (*curr_jt)->right_month != (*prev_jt)->left_month
|| (*prev_jt)->left_month != (*prev_jt)->right_month)
{
++prev_jt;
++curr_jt;
continue;
}
if ((*curr_jt)->contains(**prev_jt))
{
LOG_WARNING(log, "Part " << (*curr_jt)->name << " contains " << (*prev_jt)->name);
data_parts.erase(prev_jt);
prev_jt = curr_jt;
++curr_jt;
}
else if ((*prev_jt)->contains(**curr_jt))
{
LOG_WARNING(log, "Part " << (*prev_jt)->name << " contains " << (*curr_jt)->name);
data_parts.erase(curr_jt++);
}
else
{
++prev_jt;
++curr_jt;
}
}
}
LOG_DEBUG(log, "Loaded data parts (" << data_parts.size() << " items)");
}
void MergeTreeData::clearOldParts()
{
Poco::ScopedTry<Poco::FastMutex> lock;
/// Если метод уже вызван из другого потока (или если all_data_parts прямо сейчас меняют), то можно ничего не делать.
if (!lock.lock(&all_data_parts_mutex))
{
LOG_TRACE(log, "Already clearing or modifying old parts");
return;
}
LOG_TRACE(log, "Clearing old parts");
for (DataParts::iterator it = all_data_parts.begin(); it != all_data_parts.end();)
{
int ref_count = it->use_count();
if (ref_count == 1) /// После этого ref_count не может увеличиться.
{
LOG_DEBUG(log, "'Removing' part " << (*it)->name << " (prepending old_ to its name)");
(*it)->renameToOld();
all_data_parts.erase(it++);
}
else
++it;
}
/// Удалим старые old_ куски.
Poco::DirectoryIterator end;
for (Poco::DirectoryIterator it(full_path); it != end; ++it)
{
if (0 != it.name().compare(0, strlen("old_"), "old_"))
continue;
if (it->isDirectory() && it->getLastModified().epochTime() + settings.old_parts_lifetime < time(0))
{
it->remove(true);
}
}
}
void MergeTreeData::setPath(const String & new_full_path)
{
Poco::File(full_path).renameTo(new_full_path);
full_path = new_full_path;
context.getUncompressedCache()->reset();
context.getMarkCache()->reset();
log = &Logger::get(lastTwoPathComponents(full_path));
}
void MergeTreeData::dropAllData()
{
data_parts.clear();
all_data_parts.clear();
context.getUncompressedCache()->reset();
context.getMarkCache()->reset();
Poco::File(full_path).remove(true);
}
void MergeTreeData::removeColumnFiles(String column_name)
{
Poco::ScopedLock<Poco::FastMutex> lock(data_parts_mutex);
Poco::ScopedLock<Poco::FastMutex> lock_all(all_data_parts_mutex);
/// Регэксп выбирает файлы столбца для удаления
Poco::RegularExpression re(column_name + "(?:(?:\\.|\\%2E).+){0,1}" +"(?:\\.mrk|\\.bin|\\.size\\d+\\.bin|\\.size\\d+\\.mrk)");
/// Цикл по всем директориям кусочков
Poco::RegularExpression::MatchVec matches;
Poco::DirectoryIterator end;
for (Poco::DirectoryIterator it_dir = Poco::DirectoryIterator(full_path); it_dir != end; ++it_dir)
{
std::string dir_name = it_dir.name();
if (!isPartDirectory(dir_name, matches))
continue;
/// Цикл по каждому из файлов в директории кусочков
String full_dir_name = full_path + dir_name + "/";
for (Poco::DirectoryIterator it_file(full_dir_name); it_file != end; ++it_file)
{
if (re.match(it_file.name()))
{
Poco::File file(full_dir_name + it_file.name());
if (file.exists())
file.remove();
}
}
}
}
void MergeTreeData::createConvertExpression(const String & in_column_name, const String & out_type, ExpressionActionsPtr & out_expression, String & out_column)
{
ASTFunction * function = new ASTFunction;
ASTPtr function_ptr = function;
ASTExpressionList * arguments = new ASTExpressionList;
ASTPtr arguments_ptr = arguments;
function->name = "to" + out_type;
function->arguments = arguments_ptr;
function->children.push_back(arguments_ptr);
ASTIdentifier * in_column = new ASTIdentifier;
ASTPtr in_column_ptr = in_column;
arguments->children.push_back(in_column_ptr);
in_column->name = in_column_name;
in_column->kind = ASTIdentifier::Column;
out_expression = ExpressionAnalyzer(function_ptr, context, *columns).getActions(false);
out_column = function->getColumnName();
}
static DataTypePtr getDataTypeByName(const String & name, const NamesAndTypesList & columns)
{
for (const auto & it : columns)
{
if (it.first == name)
return it.second;
}
throw Exception("No column " + name + " in table", ErrorCodes::NO_SUCH_COLUMN_IN_TABLE);
}
void MergeTreeData::alter(const ASTAlterQuery::Parameters & params)
{
{
Poco::ScopedLock<Poco::FastMutex> lock(data_parts_mutex);
Poco::ScopedLock<Poco::FastMutex> lock_all(all_data_parts_mutex);
alterColumns(params, columns, context);
}
if (params.type == ASTAlterQuery::DROP)
{
String column_name = dynamic_cast<const ASTIdentifier &>(*params.column).name;
removeColumnFiles(column_name);
context.getUncompressedCache()->reset();
context.getMarkCache()->reset();
}
}
void MergeTreeData::prepareAlterModify(const ASTAlterQuery::Parameters & params)
{
DataPartsVector parts;
{
Poco::ScopedLock<Poco::FastMutex> lock(data_parts_mutex);
parts = DataPartsVector(data_parts.begin(), data_parts.end());
}
Names column_name;
const ASTNameTypePair & name_type = dynamic_cast<const ASTNameTypePair &>(*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<DataTypeNested *>(old_type_ptr.get()) || dynamic_cast<DataTypeArray *>(old_type_ptr.get()) ||
dynamic_cast<DataTypeNested *>(new_type_ptr.get()) || dynamic_cast<DataTypeArray *>(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<Poco::FastMutex> lock(data_parts_mutex);
parts = DataPartsVector(data_parts.begin(), data_parts.end());
}
const ASTNameTypePair & name_type = dynamic_cast<const ASTNameTypePair &>(*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<Poco::FastMutex> lock(data_parts_mutex);
Poco::ScopedLock<Poco::FastMutex> lock_all(all_data_parts_mutex);
alterColumns(params, columns, context);
}
}
bool MergeTreeData::isPartDirectory(const String & dir_name, Poco::RegularExpression::MatchVec & matches) const
{
return (file_name_regexp.match(dir_name, 0, matches) && 6 == matches.size());
}
bool MergeTreeData::isBrokenPart(const String & path)
{
/// Проверяем, что первичный ключ непуст.
Poco::File index_file(path + "/primary.idx");
if (!index_file.exists() || index_file.getSize() == 0)
{
LOG_ERROR(log, "Part " << path << " is broken: primary key is empty.");
return true;
}
/// Проверяем, что все засечки непусты и имеют одинаковый размер.
ssize_t marks_size = -1;
for (NamesAndTypesList::const_iterator it = columns->begin(); it != columns->end(); ++it)
{
Poco::File marks_file(path + "/" + escapeForFileName(it->first) + ".mrk");
/// при добавлении нового столбца в таблицу файлы .mrk не создаются. Не будем ничего удалять.
if (!marks_file.exists())
continue;
if (marks_size == -1)
{
marks_size = marks_file.getSize();
if (0 == marks_size)
{
LOG_ERROR(log, "Part " << path << " is broken: " << marks_file.path() << " is empty.");
return true;
}
}
else
{
if (static_cast<ssize_t>(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<int>(old_parts.size()) - 1; i >= 0; --i)
{
DataPart old_part(*this);
String name = old_parts[i].substr(strlen("old_"));
if (!isPartDirectory(name, matches))
{
LOG_ERROR(log, "Strange file name: " << path + old_parts[i] << "; ignoring");
old_parts.erase(old_parts.begin() + i);
continue;
}
parsePartName(name, matches, old_part);
if (broken_part.contains(old_part))
{
/// Восстанавливаем все содержащиеся куски. Если некоторые из них содержатся в других, их удалит loadDataParts.
LOG_ERROR(log, "Restoring part " << path + old_parts[i]);
Poco::File(path + old_parts[i]).renameTo(path + name);
old_parts.erase(old_parts.begin() + i);
restored_parts.push_back(name);
}
}
if (restored_parts.size() >= 2)
{
LOG_ERROR(log, "Removing broken part " << path + file_name << " because at least 2 old_ parts were restored in its place");
Poco::File(path + file_name).remove(true);
}
else
{
LOG_ERROR(log, "Not removing broken part " << path + file_name
<< " because less than 2 old_ parts were restored in its place. You need to resolve this manually");
}
return restored_parts;
}
void MergeTreeData::replaceParts(DataPartsVector old_parts, DataPartPtr new_part)
{
Poco::ScopedLock<Poco::FastMutex> lock(data_parts_mutex);
Poco::ScopedLock<Poco::FastMutex> all_lock(all_data_parts_mutex);
for (size_t i = 0; i < old_parts.size(); ++i)
if (data_parts.end() == data_parts.find(old_parts[i]))
throw Exception("Logical error: cannot find data part " + old_parts[i]->name + " in list", ErrorCodes::LOGICAL_ERROR);
data_parts.insert(new_part);
all_data_parts.insert(new_part);
for (size_t i = 0; i < old_parts.size(); ++i)
data_parts.erase(data_parts.find(old_parts[i]));
}
void MergeTreeData::renameTempPartAndAdd(MutableDataPartPtr part, Increment * increment)
{
LOG_TRACE(log, "Renaming.");
Poco::ScopedLock<Poco::FastMutex> lock(data_parts_mutex);
Poco::ScopedLock<Poco::FastMutex> lock_all(all_data_parts_mutex);
String old_path = getFullPath() + part->name + "/";
UInt64 part_id = part->left;
/** Важно, что получение номера куска происходит атомарно с добавлением этого куска в набор.
* Иначе есть race condition - может произойти слияние пары кусков, диапазоны номеров которых
* содержат ещё не добавленный кусок.
*/
if (increment)
part_id = increment->get(false);
part->left = part->right = part_id;
part->name = getPartName(part->left_date, part->right_date, part_id, part_id, 0);
String new_path = getFullPath() + part->name + "/";
/// Переименовываем кусок.
Poco::File(old_path).renameTo(new_path);
data_parts.insert(part);
all_data_parts.insert(part);
}
MergeTreeData::DataParts MergeTreeData::getDataParts()
{
Poco::ScopedLock<Poco::FastMutex> lock(data_parts_mutex);
return data_parts;
}
}

View File

@ -0,0 +1,348 @@
#include <DB/Storages/MergeTree/MergeTreeDataMerger.h>
#include <DB/Storages/MergeTree/MergeTreeBlockInputStream.h>
#include <DB/Storages/MergeTree/MergedBlockOutputStream.h>
#include <DB/DataStreams/ExpressionBlockInputStream.h>
#include <DB/DataStreams/MergingSortedBlockInputStream.h>
#include <DB/DataStreams/CollapsingSortedBlockInputStream.h>
#include <DB/DataStreams/SummingSortedBlockInputStream.h>
namespace DB
{
/// Не будем соглашаться мерджить куски, если места на диске менее чем во столько раз больше суммарного размера кусков.
static const double DISK_USAGE_COEFFICIENT_TO_SELECT = 1.6;
/// Объединяя куски, зарезервируем столько места на диске. Лучше сделать немного меньше, чем DISK_USAGE_COEFFICIENT_TO_SELECT,
/// потому что между выбором кусков и резервированием места места может стать немного меньше.
static const double DISK_USAGE_COEFFICIENT_TO_RESERVE = 1.4;
/// Выбираем отрезок из не более чем max_parts_to_merge_at_once кусков так, чтобы максимальный размер был меньше чем в max_size_ratio_to_merge_parts раз больше суммы остальных.
/// Это обеспечивает в худшем случае время O(n log n) на все слияния, независимо от выбора сливаемых кусков, порядка слияния и добавления.
/// При max_parts_to_merge_at_once >= log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts),
/// несложно доказать, что всегда будет что сливать, пока количество кусков больше
/// log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts)*(количество кусков размером больше max_rows_to_merge_parts).
/// Дальше эвристики.
/// Будем выбирать максимальный по включению подходящий отрезок.
/// Из всех таких выбираем отрезок с минимальным максимумом размера.
/// Из всех таких выбираем отрезок с минимальным минимумом размера.
/// Из всех таких выбираем отрезок с максимальной длиной.
/// Дополнительно:
/// 1) с 1:00 до 5:00 ограничение сверху на размер куска в основном потоке увеличивается в несколько раз
/// 2) в зависимоти от возраста кусков меняется допустимая неравномерность при слиянии
/// 3) Молодые куски крупного размера (примерно больше 1 Гб) можно сливать не меньше чем по три
/// 4) Если в одном из потоков идет мердж крупных кусков, то во втором сливать только маленькие кусочки
/// 5) С ростом логарифма суммарного размера кусочков в мердже увеличиваем требование сбалансированности
bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & parts, size_t available_disk_space,
bool merge_anything_for_old_months, bool aggressive, bool only_small, const AllowedMergingPredicate & can_merge)
{
LOG_DEBUG(log, "Selecting parts to merge");
MergeTreeData::DataParts data_parts = data.getDataParts();
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
size_t min_max = -1U;
size_t min_min = -1U;
int max_len = 0;
MergeTreeData::DataParts::iterator best_begin;
bool found = false;
DayNum_t now_day = date_lut.toDayNum(time(0));
DayNum_t now_month = date_lut.toFirstDayNumOfMonth(now_day);
int now_hour = date_lut.toHourInaccurate(time(0));
/// Сколько кусков, начиная с текущего, можно включить в валидный отрезок, начинающийся левее текущего куска.
/// Нужно для определения максимальности по включению.
int max_count_from_left = 0;
size_t cur_max_rows_to_merge_parts = data.settings.max_rows_to_merge_parts;
/// Если ночь, можем мерджить сильно большие куски
if (now_hour >= 1 && now_hour <= 5)
cur_max_rows_to_merge_parts *= data.settings.merge_parts_at_night_inc;
if (only_small)
{
cur_max_rows_to_merge_parts = data.settings.max_rows_to_merge_parts_second;
}
/// Левый конец отрезка.
for (MergeTreeData::DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it)
{
const MergeTreeData::DataPartPtr & first_part = *it;
max_count_from_left = std::max(0, max_count_from_left - 1);
/// Кусок достаточно мал или слияние "агрессивное".
if (first_part->size * data.index_granularity > cur_max_rows_to_merge_parts
&& !aggressive)
continue;
/// Кусок в одном месяце.
if (first_part->left_month != first_part->right_month)
{
LOG_WARNING(log, "Part " << first_part->name << " spans more than one month");
continue;
}
/// Самый длинный валидный отрезок, начинающийся здесь.
size_t cur_longest_max = -1U;
size_t cur_longest_min = -1U;
int cur_longest_len = 0;
/// Текущий отрезок, не обязательно валидный.
size_t cur_max = first_part->size;
size_t cur_min = first_part->size;
size_t cur_sum = first_part->size;
size_t cur_total_size = first_part->size_in_bytes;
int cur_len = 1;
DayNum_t month = first_part->left_month;
UInt64 cur_id = first_part->right;
/// Этот месяц кончился хотя бы день назад.
bool is_old_month = now_day - now_month >= 1 && now_month > month;
time_t oldest_modification_time = first_part->modification_time;
/// Правый конец отрезка.
MergeTreeData::DataParts::iterator jt = it;
for (; cur_len < static_cast<int>(data.settings.max_parts_to_merge_at_once);)
{
const MergeTreeData::DataPartPtr & prev_part = *jt;
++jt;
if (jt == data_parts.end())
break;
const MergeTreeData::DataPartPtr & last_part = *jt;
/// Кусок разрешено сливать с предыдущим, и в одном правильном месяце.
if (!can_merge(prev_part, last_part) ||
last_part->left_month != last_part->right_month ||
last_part->left_month != month)
break;
/// Кусок достаточно мал или слияние "агрессивное".
if (last_part->size * data.index_granularity > cur_max_rows_to_merge_parts
&& !aggressive)
break;
/// Кусок правее предыдущего.
if (last_part->left < cur_id)
{
LOG_WARNING(log, "Part " << last_part->name << " intersects previous part");
break;
}
oldest_modification_time = std::max(oldest_modification_time, last_part->modification_time);
cur_max = std::max(cur_max, last_part->size);
cur_min = std::min(cur_min, last_part->size);
cur_sum += last_part->size;
cur_total_size += last_part->size_in_bytes;
++cur_len;
cur_id = last_part->right;
int min_len = 2;
int cur_age_in_sec = time(0) - oldest_modification_time;
/// Если куски примерно больше 1 Gb и образовались меньше 6 часов назад, то мерджить не меньше чем по 3.
if (cur_max * data.index_granularity * 150 > 1024*1024*1024 && cur_age_in_sec < 6*3600)
min_len = 3;
/// Равен 0.5 если возраст порядка 0, равен 5 если возраст около месяца.
double time_ratio_modifier = 0.5 + 9 * static_cast<double>(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<double>(cur_max) / (cur_sum - cur_max) < ratio
/// За старый месяц объединяем что угодно, если разрешено и если этому хотя бы 15 дней
|| (is_old_month && merge_anything_for_old_months && cur_age_in_sec > 3600*24*15)
/// Если слияние "агрессивное", то сливаем что угодно
|| aggressive))
{
/// Достаточно места на диске, чтобы покрыть новый мердж с запасом.
if (available_disk_space > cur_total_size * DISK_USAGE_COEFFICIENT_TO_SELECT)
{
cur_longest_max = cur_max;
cur_longest_min = cur_min;
cur_longest_len = cur_len;
}
else
LOG_WARNING(log, "Won't merge parts from " << first_part->name << " to " << last_part->name
<< " because not enough free space: " << available_disk_space << " free and unreserved, "
<< cur_total_size << " required now (+" << static_cast<int>((DISK_USAGE_COEFFICIENT_TO_SELECT - 1.0) * 100)
<< "% on overhead)");
}
}
/// Это максимальный по включению валидный отрезок.
if (cur_longest_len > max_count_from_left)
{
max_count_from_left = cur_longest_len;
if (!found
|| std::make_pair(std::make_pair(cur_longest_max, cur_longest_min), -cur_longest_len)
< std::make_pair(std::make_pair(min_max, min_min), -max_len))
{
found = true;
min_max = cur_longest_max;
min_min = cur_longest_min;
max_len = cur_longest_len;
best_begin = it;
}
}
}
if (found)
{
parts.clear();
MergeTreeData::DataParts::iterator it = best_begin;
for (int i = 0; i < max_len; ++i)
{
parts.push_back(*it);
++it;
}
LOG_DEBUG(log, "Selected " << parts.size() << " parts from " << parts.front()->name << " to " << parts.back()->name);
}
else
{
LOG_DEBUG(log, "No parts to merge");
}
return found;
}
/// parts должны быть отсортированы.
String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & parts)
{
LOG_DEBUG(log, "Merging " << parts.size() << " parts: from " << parts.front()->name << " to " << parts.back()->name);
Names all_column_names;
NamesAndTypesList columns_list = data.getColumnsList();
for (const auto & it : columns_list)
all_column_names.push_back(it.first);
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
MergeTreeData::MutableDataPartPtr new_data_part = std::make_shared<MergeTreeData::DataPart>(data);
new_data_part->left_date = std::numeric_limits<UInt16>::max();
new_data_part->right_date = std::numeric_limits<UInt16>::min();
new_data_part->left = parts.front()->left;
new_data_part->right = parts.back()->right;
new_data_part->level = 0;
for (size_t i = 0; i < parts.size(); ++i)
{
new_data_part->level = std::max(new_data_part->level, parts[i]->level);
new_data_part->left_date = std::min(new_data_part->left_date, parts[i]->left_date);
new_data_part->right_date = std::max(new_data_part->right_date, parts[i]->right_date);
}
++new_data_part->level;
new_data_part->name = MergeTreeData::getPartName(
new_data_part->left_date, new_data_part->right_date, new_data_part->left, new_data_part->right, new_data_part->level);
new_data_part->left_month = date_lut.toFirstDayNumOfMonth(new_data_part->left_date);
new_data_part->right_month = date_lut.toFirstDayNumOfMonth(new_data_part->right_date);
/** Читаем из всех кусков, сливаем и пишем в новый.
* Попутно вычисляем выражение для сортировки.
*/
BlockInputStreams src_streams;
for (size_t i = 0; i < parts.size(); ++i)
{
MarkRanges ranges(1, MarkRange(0, parts[i]->size));
src_streams.push_back(new ExpressionBlockInputStream(new MergeTreeBlockInputStream(
data.getFullPath() + parts[i]->name + '/', DEFAULT_MERGE_BLOCK_SIZE, all_column_names, data,
parts[i], ranges, false, NULL, ""), data.getPrimaryExpression()));
}
/// Порядок потоков важен: при совпадении ключа элементы идут в порядке номера потока-источника.
/// В слитом куске строки с одинаковым ключом должны идти в порядке возрастания идентификатора исходного куска, то есть (примерного) возрастания времени вставки.
BlockInputStreamPtr merged_stream;
switch (data.mode)
{
case MergeTreeData::Ordinary:
merged_stream = new MergingSortedBlockInputStream(src_streams, data.getSortDescription(), DEFAULT_MERGE_BLOCK_SIZE);
break;
case MergeTreeData::Collapsing:
merged_stream = new CollapsingSortedBlockInputStream(src_streams, data.getSortDescription(), data.sign_column, DEFAULT_MERGE_BLOCK_SIZE);
break;
case MergeTreeData::Summing:
merged_stream = new SummingSortedBlockInputStream(src_streams, data.getSortDescription(), DEFAULT_MERGE_BLOCK_SIZE);
break;
default:
throw Exception("Unknown mode of operation for MergeTreeData: " + toString(data.mode), ErrorCodes::LOGICAL_ERROR);
}
MergedBlockOutputStreamPtr to = new MergedBlockOutputStream(data,
new_data_part->left_date, new_data_part->right_date, new_data_part->left, new_data_part->right, new_data_part->level);
merged_stream->readPrefix();
to->writePrefix();
Block block;
while (!canceled && (block = merged_stream->read()))
to->write(block);
if (canceled)
{
LOG_INFO(log, "Canceled merging parts.");
return "";
}
merged_stream->readSuffix();
to->writeSuffix();
/// В обычном режиме строчки не могут удалиться при мердже.
if (0 == to->marksCount() && data.mode == MergeTreeData::Ordinary)
throw Exception("Empty part after merge", ErrorCodes::LOGICAL_ERROR);
new_data_part->size = to->marksCount();
new_data_part->modification_time = time(0);
if (0 == to->marksCount())
{
LOG_INFO(log, "All rows have been deleted while merging from " << parts.front()->name << " to " << parts.back()->name);
return "";
}
/// NOTE Только что записанный индекс заново считывается с диска. Можно было бы формировать его сразу при записи.
new_data_part->loadIndex();
/// Добавляем новый кусок в набор.
data.replaceParts(parts, new_data_part);
LOG_TRACE(log, "Merged " << parts.size() << " parts: from " << parts.front()->name << " to " << parts.back()->name);
return new_data_part->name;
}
size_t MergeTreeDataMerger::estimateDiskSpaceForMerge(const MergeTreeData::DataPartsVector & parts)
{
size_t res = 0;
for (const MergeTreeData::DataPartPtr & part : parts)
{
res += part->size_in_bytes;
}
return static_cast<size_t>(res * DISK_USAGE_COEFFICIENT_TO_RESERVE);
}
}

View File

@ -0,0 +1,466 @@
#include <DB/Storages/MergeTree/MergeTreeDataSelectExecutor.h>
#include <DB/Interpreters/ExpressionAnalyzer.h>
#include <DB/Parsers/ASTIdentifier.h>
#include <DB/DataStreams/ExpressionBlockInputStream.h>
#include <DB/DataStreams/FilterBlockInputStream.h>
#include <DB/DataStreams/ConcatBlockInputStream.h>
#include <DB/DataStreams/CollapsingFinalBlockInputStream.h>
#include <DB/DataTypes/DataTypesNumberFixed.h>
namespace DB
{
MergeTreeDataSelectExecutor::MergeTreeDataSelectExecutor(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataSelectExecutor"))
{
min_marks_for_seek = (data.settings.min_rows_for_seek + data.index_granularity - 1) / data.index_granularity;
min_marks_for_concurrent_read = (data.settings.min_rows_for_concurrent_read + data.index_granularity - 1) / data.index_granularity;
max_marks_to_use_cache = (data.settings.max_rows_to_use_cache + data.index_granularity - 1) / data.index_granularity;
}
BlockInputStreams MergeTreeDataSelectExecutor::read(
const Names & column_names_to_return,
ASTPtr query,
const Settings & settings,
QueryProcessingStage::Enum & processed_stage,
size_t max_block_size,
unsigned threads)
{
data.check(column_names_to_return);
processed_stage = QueryProcessingStage::FetchColumns;
PKCondition key_condition(query, data.context, data.getColumnsList(), data.getSortDescription());
PKCondition date_condition(query, data.context, data.getColumnsList(), SortDescription(1, SortColumnDescription(data.date_column_name, 1)));
MergeTreeData::DataPartsVector parts;
/// Выберем куски, в которых могут быть данные, удовлетворяющие date_condition.
{
MergeTreeData::DataParts data_parts = data.getDataParts();
for (MergeTreeData::DataParts::iterator it = data_parts.begin(); it != data_parts.end(); ++it)
{
Field left = static_cast<UInt64>((*it)->left_date);
Field right = static_cast<UInt64>((*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<ASTFunction> ASTFunctionPtr;
ASTFunctionPtr filter_function;
ExpressionActionsPtr filter_expression;
ASTSelectQuery & select = *dynamic_cast<ASTSelectQuery*>(&*query);
if (select.sample_size)
{
double size = apply_visitor(FieldVisitorConvertToNumber<double>(),
dynamic_cast<ASTLiteral&>(*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<UInt64>(), dynamic_cast<ASTLiteral&>(*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<double>(requested_count) / total_count);
LOG_DEBUG(log, "Selected relative sample size: " << size);
}
UInt64 sampling_column_max = 0;
DataTypePtr type = data.getPrimaryExpression()->getSampleBlock().getByName(data.sampling_expression->getColumnName()).type;
if (type->getName() == "UInt64")
sampling_column_max = std::numeric_limits<UInt64>::max();
else if (type->getName() == "UInt32")
sampling_column_max = std::numeric_limits<UInt32>::max();
else if (type->getName() == "UInt16")
sampling_column_max = std::numeric_limits<UInt16>::max();
else if (type->getName() == "UInt8")
sampling_column_max = std::numeric_limits<UInt8>::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<UInt64>(size * sampling_column_max);
if (!key_condition.addCondition(data.sampling_expression->getColumnName(),
Range::createRightBounded(sampling_column_value_limit, true)))
throw Exception("Sampling column not in primary key", ErrorCodes::ILLEGAL_COLUMN);
/// Выражение для фильтрации: sampling_expression <= sampling_column_value_limit
ASTPtr filter_function_args = new ASTExpressionList;
filter_function_args->children.push_back(data.sampling_expression);
filter_function_args->children.push_back(new ASTLiteral(StringRange(), sampling_column_value_limit));
filter_function = new ASTFunction;
filter_function->name = "lessOrEquals";
filter_function->arguments = filter_function_args;
filter_function->children.push_back(filter_function->arguments);
filter_expression = ExpressionAnalyzer(filter_function, data.context, data.getColumnsList()).getActions(false);
/// Добавим столбцы, нужные для sampling_expression.
std::vector<String> add_columns = filter_expression->getRequiredColumns();
column_names_to_read.insert(column_names_to_read.end(), add_columns.begin(), add_columns.end());
std::sort(column_names_to_read.begin(), column_names_to_read.end());
column_names_to_read.erase(std::unique(column_names_to_read.begin(), column_names_to_read.end()), column_names_to_read.end());
}
LOG_DEBUG(log, "Key condition: " << key_condition.toString());
LOG_DEBUG(log, "Date condition: " << date_condition.toString());
/// PREWHERE
ExpressionActionsPtr prewhere_actions;
String prewhere_column;
if (select.prewhere_expression)
{
ExpressionAnalyzer analyzer(select.prewhere_expression, data.context, data.getColumnsList());
prewhere_actions = analyzer.getActions(false);
prewhere_column = select.prewhere_expression->getColumnName();
/// TODO: Чтобы работали подзапросы в PREWHERE, можно тут сохранить analyzer.getSetsWithSubqueries(), а потом их выполнить.
}
RangesInDataParts parts_with_ranges;
/// Найдем, какой диапазон читать из каждого куска.
size_t sum_marks = 0;
size_t sum_ranges = 0;
for (size_t i = 0; i < parts.size(); ++i)
{
MergeTreeData::DataPartPtr & part = parts[i];
RangesInDataPart ranges(part);
ranges.ranges = markRangesFromPkRange(part->index, key_condition);
if (!ranges.ranges.empty())
{
parts_with_ranges.push_back(ranges);
sum_ranges += ranges.ranges.size();
for (size_t j = 0; j < ranges.ranges.size(); ++j)
{
sum_marks += ranges.ranges[j].end - ranges.ranges[j].begin;
}
}
}
LOG_DEBUG(log, "Selected " << parts.size() << " parts by date, " << parts_with_ranges.size() << " parts by key, "
<< sum_marks << " marks to read from " << sum_ranges << " ranges");
BlockInputStreams res;
if (select.final)
{
/// Добавим столбцы, нужные для вычисления первичного ключа и знака.
std::vector<String> add_columns = data.getPrimaryExpression()->getRequiredColumns();
column_names_to_read.insert(column_names_to_read.end(), add_columns.begin(), add_columns.end());
column_names_to_read.push_back(data.sign_column);
std::sort(column_names_to_read.begin(), column_names_to_read.end());
column_names_to_read.erase(std::unique(column_names_to_read.begin(), column_names_to_read.end()), column_names_to_read.end());
res = spreadMarkRangesAmongThreadsFinal(
parts_with_ranges,
threads,
column_names_to_read,
max_block_size,
settings.use_uncompressed_cache,
prewhere_actions,
prewhere_column);
}
else
{
res = spreadMarkRangesAmongThreads(
parts_with_ranges,
threads,
column_names_to_read,
max_block_size,
settings.use_uncompressed_cache,
prewhere_actions,
prewhere_column);
}
if (select.sample_size)
{
for (size_t i = 0; i < res.size(); ++i)
{
BlockInputStreamPtr original_stream = res[i];
BlockInputStreamPtr expression_stream = new ExpressionBlockInputStream(original_stream, filter_expression);
BlockInputStreamPtr filter_stream = new FilterBlockInputStream(expression_stream, filter_function->getColumnName());
res[i] = filter_stream;
}
}
return res;
}
BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreads(
RangesInDataParts parts,
size_t threads,
const Names & column_names,
size_t max_block_size,
bool use_uncompressed_cache,
ExpressionActionsPtr prewhere_actions,
const String & prewhere_column)
{
/// На всякий случай перемешаем куски.
std::random_shuffle(parts.begin(), parts.end());
/// Посчитаем засечки для каждого куска.
std::vector<size_t> sum_marks_in_parts(parts.size());
size_t sum_marks = 0;
for (size_t i = 0; i < parts.size(); ++i)
{
/// Пусть отрезки будут перечислены справа налево, чтобы можно было выбрасывать самый левый отрезок с помощью pop_back().
std::reverse(parts[i].ranges.begin(), parts[i].ranges.end());
sum_marks_in_parts[i] = 0;
for (size_t j = 0; j < parts[i].ranges.size(); ++j)
{
MarkRange & range = parts[i].ranges[j];
sum_marks_in_parts[i] += range.end - range.begin;
}
sum_marks += sum_marks_in_parts[i];
}
if (sum_marks > max_marks_to_use_cache)
use_uncompressed_cache = false;
BlockInputStreams res;
if (sum_marks > 0)
{
size_t min_marks_per_thread = (sum_marks - 1) / threads + 1;
for (size_t i = 0; i < threads && !parts.empty(); ++i)
{
size_t need_marks = min_marks_per_thread;
BlockInputStreams streams;
/// Цикл по кускам.
while (need_marks > 0 && !parts.empty())
{
RangesInDataPart & part = parts.back();
size_t & marks_in_part = sum_marks_in_parts.back();
/// Не будем брать из куска слишком мало строк.
if (marks_in_part >= min_marks_for_concurrent_read &&
need_marks < min_marks_for_concurrent_read)
need_marks = min_marks_for_concurrent_read;
/// Не будем оставлять в куске слишком мало строк.
if (marks_in_part > need_marks &&
marks_in_part - need_marks < min_marks_for_concurrent_read)
need_marks = marks_in_part;
/// Возьмем весь кусок, если он достаточно мал.
if (marks_in_part <= need_marks)
{
/// Восстановим порядок отрезков.
std::reverse(part.ranges.begin(), part.ranges.end());
streams.push_back(new MergeTreeBlockInputStream(
data.getFullPath() + part.data_part->name + '/', max_block_size, column_names, data,
part.data_part, part.ranges, use_uncompressed_cache,
prewhere_actions, prewhere_column));
need_marks -= marks_in_part;
parts.pop_back();
sum_marks_in_parts.pop_back();
continue;
}
MarkRanges ranges_to_get_from_part;
/// Цикл по отрезкам куска.
while (need_marks > 0)
{
if (part.ranges.empty())
throw Exception("Unexpected end of ranges while spreading marks among threads", ErrorCodes::LOGICAL_ERROR);
MarkRange & range = part.ranges.back();
size_t marks_in_range = range.end - range.begin;
size_t marks_to_get_from_range = std::min(marks_in_range, need_marks);
ranges_to_get_from_part.push_back(MarkRange(range.begin, range.begin + marks_to_get_from_range));
range.begin += marks_to_get_from_range;
marks_in_part -= marks_to_get_from_range;
need_marks -= marks_to_get_from_range;
if (range.begin == range.end)
part.ranges.pop_back();
}
streams.push_back(new MergeTreeBlockInputStream(
data.getFullPath() + part.data_part->name + '/', max_block_size, column_names, data,
part.data_part, ranges_to_get_from_part, use_uncompressed_cache,
prewhere_actions, prewhere_column));
}
if (streams.size() == 1)
res.push_back(streams[0]);
else
res.push_back(new ConcatBlockInputStream(streams));
}
if (!parts.empty())
throw Exception("Couldn't spread marks among threads", ErrorCodes::LOGICAL_ERROR);
}
return res;
}
BlockInputStreams MergeTreeDataSelectExecutor::spreadMarkRangesAmongThreadsFinal(
RangesInDataParts parts,
size_t threads,
const Names & column_names,
size_t max_block_size,
bool use_uncompressed_cache,
ExpressionActionsPtr prewhere_actions,
const String & prewhere_column)
{
size_t sum_marks = 0;
for (size_t i = 0; i < parts.size(); ++i)
for (size_t j = 0; j < parts[i].ranges.size(); ++j)
sum_marks += parts[i].ranges[j].end - parts[i].ranges[j].begin;
if (sum_marks > max_marks_to_use_cache)
use_uncompressed_cache = false;
ExpressionActionsPtr sign_filter_expression;
String sign_filter_column;
createPositiveSignCondition(sign_filter_expression, sign_filter_column);
BlockInputStreams to_collapse;
for (size_t part_index = 0; part_index < parts.size(); ++part_index)
{
RangesInDataPart & part = parts[part_index];
BlockInputStreamPtr source_stream = new MergeTreeBlockInputStream(
data.getFullPath() + part.data_part->name + '/', max_block_size, column_names, data,
part.data_part, part.ranges, use_uncompressed_cache,
prewhere_actions, prewhere_column);
to_collapse.push_back(new ExpressionBlockInputStream(source_stream, data.getPrimaryExpression()));
}
BlockInputStreams res;
if (to_collapse.size() == 1)
res.push_back(new FilterBlockInputStream(new ExpressionBlockInputStream(to_collapse[0], sign_filter_expression), sign_filter_column));
else if (to_collapse.size() > 1)
res.push_back(new CollapsingFinalBlockInputStream(to_collapse, data.getSortDescription(), data.sign_column));
return res;
}
void MergeTreeDataSelectExecutor::createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column)
{
ASTFunction * function = new ASTFunction;
ASTPtr function_ptr = function;
ASTExpressionList * arguments = new ASTExpressionList;
ASTPtr arguments_ptr = arguments;
ASTIdentifier * sign = new ASTIdentifier;
ASTPtr sign_ptr = sign;
ASTLiteral * one = new ASTLiteral;
ASTPtr one_ptr = one;
function->name = "equals";
function->arguments = arguments_ptr;
function->children.push_back(arguments_ptr);
arguments->children.push_back(sign_ptr);
arguments->children.push_back(one_ptr);
sign->name = data.sign_column;
sign->kind = ASTIdentifier::Column;
one->type = new DataTypeInt8;
one->value = Field(static_cast<Int64>(1));
out_expression = ExpressionAnalyzer(function_ptr, data.context, data.getColumnsList()).getActions(false);
out_column = function->getColumnName();
}
/// Получает набор диапазонов засечек, вне которых не могут находиться ключи из заданного диапазона.
MarkRanges MergeTreeDataSelectExecutor::markRangesFromPkRange(const MergeTreeData::DataPart::Index & index, PKCondition & key_condition)
{
MarkRanges res;
size_t key_size = data.getSortDescription().size();
size_t marks_count = index.size() / key_size;
/// Если индекс не используется.
if (key_condition.alwaysTrue())
{
res.push_back(MarkRange(0, marks_count));
}
else
{
/** В стеке всегда будут находиться непересекающиеся подозрительные отрезки, самый левый наверху (back).
* На каждом шаге берем левый отрезок и проверяем, подходит ли он.
* Если подходит, разбиваем его на более мелкие и кладем их в стек. Если нет - выбрасываем его.
* Если отрезок уже длиной в одну засечку, добавляем его в ответ и выбрасываем.
*/
std::vector<MarkRange> 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;
}
}

View File

@ -0,0 +1,248 @@
#include <DB/Storages/MergeTree/MergeTreeDataWriter.h>
#include <DB/Common/escapeForFileName.h>
#include <DB/DataTypes/DataTypeNested.h>
#include <DB/DataTypes/DataTypeArray.h>
namespace DB
{
BlocksWithDateIntervals MergeTreeDataWriter::splitBlockIntoParts(const Block & block)
{
data.check(block, true);
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
size_t rows = block.rows();
size_t columns = block.columns();
/// Достаём столбец с датой.
const ColumnUInt16::Container_t & dates =
dynamic_cast<const ColumnUInt16 &>(*block.getByName(data.date_column_name).column).getData();
/// Минимальная и максимальная дата.
UInt16 min_date = std::numeric_limits<UInt16>::max();
UInt16 max_date = std::numeric_limits<UInt16>::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<UInt16, BlockWithDateInterval *> BlocksByMonth;
BlocksByMonth blocks_by_month;
for (size_t i = 0; i < rows; ++i)
{
UInt16 month = date_lut.toFirstDayNumOfMonth(DayNum_t(dates[i]));
BlockWithDateInterval *& block_for_month = blocks_by_month[month];
if (!block_for_month)
{
block_for_month = &*res.insert(res.end(), BlockWithDateInterval());
block_for_month->block = block.cloneEmpty();
}
if (dates[i] < block_for_month->min_date)
block_for_month->min_date = dates[i];
if (dates[i] > block_for_month->max_date)
block_for_month->max_date = dates[i];
for (size_t j = 0; j < columns; ++j)
block_for_month->block.getByPosition(j).column->insert((*block.getByPosition(j).column)[i]);
}
return res;
}
MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDateInterval & block_with_dates, UInt64 temp_index)
{
Block & block = block_with_dates.block;
UInt16 min_date = block_with_dates.min_date;
UInt16 max_date = block_with_dates.max_date;
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
size_t rows = block.rows();
size_t columns = block.columns();
size_t part_size = (rows + data.index_granularity - 1) / data.index_granularity;
String tmp_part_name = "tmp_" + data.getPartName(
DayNum_t(min_date), DayNum_t(max_date),
temp_index, temp_index, 0);
String part_tmp_path = data.getFullPath() + tmp_part_name + "/";
Poco::File(part_tmp_path).createDirectories();
LOG_TRACE(log, "Calculating primary expression.");
/// Если для сортировки надо вычислить некоторые столбцы - делаем это.
data.getPrimaryExpression()->execute(block);
LOG_TRACE(log, "Sorting by primary key.");
SortDescription sort_descr = data.getSortDescription();
/// Сортируем.
stableSortBlock(block, sort_descr);
/// Наконец-то можно писать данные на диск.
LOG_TRACE(log, "Writing index.");
/// Сначала пишем индекс. Индекс содержит значение PK для каждой index_granularity строки.
MergeTreeData::DataPart::Index index_vec;
index_vec.reserve(part_size * sort_descr.size());
{
WriteBufferFromFile index(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, flags);
typedef std::vector<const ColumnWithNameAndType *> PrimaryColumns;
PrimaryColumns primary_columns;
for (size_t i = 0, size = sort_descr.size(); i < size; ++i)
primary_columns.push_back(
!sort_descr[i].column_name.empty()
? &block.getByName(sort_descr[i].column_name)
: &block.getByPosition(sort_descr[i].column_number));
for (size_t i = 0; i < rows; i += data.index_granularity)
{
for (PrimaryColumns::const_iterator it = primary_columns.begin(); it != primary_columns.end(); ++it)
{
index_vec.push_back((*(*it)->column)[i]);
(*it)->type->serializeBinary(index_vec.back(), index);
}
}
index.next();
}
LOG_TRACE(log, "Writing data.");
/// Множество записанных столбцов со смещениями, чтобы не писать общие для вложенных структур столбцы несколько раз
OffsetColumns offset_columns;
for (size_t i = 0; i < columns; ++i)
{
const ColumnWithNameAndType & column = block.getByPosition(i);
writeData(part_tmp_path, column.name, *column.type, *column.column, offset_columns);
}
MergeTreeData::MutableDataPartPtr new_data_part = std::make_shared<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<const DataTypeArray *>(&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<const DataTypeNested *>(&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();
}
}
}

View File

@ -10,6 +10,7 @@
#include <DB/Parsers/ParserCreateQuery.h> #include <DB/Parsers/ParserCreateQuery.h>
#include <DB/Parsers/formatAST.h> #include <DB/Parsers/formatAST.h>
#include <DB/Interpreters/executeQuery.h> #include <DB/Interpreters/executeQuery.h>
#include <DB/Interpreters/InterpreterDropQuery.h>
#include <DB/DataStreams/copyData.h> #include <DB/DataStreams/copyData.h>
#include <DB/DataStreams/ConcatBlockInputStream.h> #include <DB/DataStreams/ConcatBlockInputStream.h>
#include <DB/DataStreams/narrowBlockInputStreams.h> #include <DB/DataStreams/narrowBlockInputStreams.h>
@ -85,7 +86,7 @@ BlockInputStreams StorageChunkMerger::read(
{ {
if (chunk_ref->source_database_name != source_database) 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; continue;
} }
if (!chunks_table_names.count(chunk_ref->source_table_name)) if (!chunks_table_names.count(chunk_ref->source_table_name))
@ -97,7 +98,7 @@ BlockInputStreams StorageChunkMerger::read(
} }
else else
{ {
LOG_WARNING(log, "ChunkRef " + chunk_ref->getTableName() + " points to non-existing Chunks table, ignoring"); LOG_WARNING(log, "ChunkRef " + it->first + " points to non-existing Chunks table, ignoring");
} }
} }
} }
@ -109,6 +110,14 @@ BlockInputStreams StorageChunkMerger::read(
} }
} }
TableLocks table_locks;
/// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем.
for (auto table : selected_tables)
{
table_locks.push_back(table->lockStructure(false));
}
BlockInputStreams res; BlockInputStreams res;
/// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную. /// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную.
@ -130,33 +139,36 @@ BlockInputStreams StorageChunkMerger::read(
else /// Иначе, считаем допустимыми все возможные значения else /// Иначе, считаем допустимыми все возможные значения
virtual_columns = new OneBlockInputStream(virtual_columns_block); virtual_columns = new OneBlockInputStream(virtual_columns_block);
std::set<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name); std::multiset<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name);
bool all_inclusive = (values.size() == virtual_columns_block.rows()); 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; continue;
/// Список виртуальных столбцов, которые мы заполним сейчас и список столбцов, которые передадим дальше /// Список виртуальных столбцов, которые мы заполним сейчас и список столбцов, которые передадим дальше
Names virt_column_names, real_column_names; Names virt_column_names, real_column_names;
for (const auto & column : 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); virt_column_names.push_back(column);
else else
real_column_names.push_back(column); real_column_names.push_back(column);
/// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой. /// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой.
if (real_column_names.size() == 0) 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(); ASTPtr modified_query_ast = query->clone();
/// Подменяем виртуальный столбец на его значение /// Подменяем виртуальный столбец на его значение
if (!virt_column_names.empty()) 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, real_column_names,
modified_query_ast, modified_query_ast,
settings, settings,
@ -164,6 +176,11 @@ BlockInputStreams StorageChunkMerger::read(
max_block_size, max_block_size,
selected_tables.size() > threads ? 1 : (threads / selected_tables.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) for (const auto & virtual_column : virt_column_names)
{ {
@ -171,7 +188,7 @@ BlockInputStreams StorageChunkMerger::read(
{ {
for (auto & stream : source_streams) for (auto & stream : source_streams)
{ {
stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, (*it)->getTableName(), _table_column_name); stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, table->getTableName(), _table_column_name);
} }
} }
} }
@ -335,6 +352,14 @@ static ASTPtr newIdentifier(const std::string & name, ASTIdentifier::Kind kind)
bool StorageChunkMerger::mergeChunks(const Storages & chunks) bool StorageChunkMerger::mergeChunks(const Storages & chunks)
{ {
typedef std::map<std::string, DataTypePtr> ColumnsMap; typedef std::map<std::string, DataTypePtr> ColumnsMap;
TableLocks table_locks;
/// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем.
for (auto table : chunks)
{
table_locks.push_back(table->lockStructure(false));
}
/// Объединим множества столбцов сливаемых чанков. /// Объединим множества столбцов сливаемых чанков.
ColumnsMap known_columns_types(columns->begin(), columns->end()); ColumnsMap known_columns_types(columns->begin(), columns->end());
@ -387,16 +412,16 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
} }
currently_written_groups.insert(new_table_full_name); 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<StorageChunks &>(*new_storage_ptr); StorageChunks & new_storage = dynamic_cast<StorageChunks &>(*new_storage_ptr);
@ -435,7 +460,7 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
DEFAULT_MERGE_BLOCK_SIZE); DEFAULT_MERGE_BLOCK_SIZE);
BlockInputStreamPtr input = new AddingDefaultBlockInputStream(new ConcatBlockInputStream(input_streams), required_columns); BlockInputStreamPtr input = new AddingDefaultBlockInputStream(new ConcatBlockInputStream(input_streams), required_columns);
input->readPrefix(); input->readPrefix();
output->writePrefix(); output->writePrefix();
@ -455,6 +480,9 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
} }
/// Атомарно подменим исходные таблицы ссылками на новую. /// Атомарно подменим исходные таблицы ссылками на новую.
/// При этом удалять таблицы под мьютексом контекста нельзя, пока только отцепим их.
Storages tables_to_drop;
{ {
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex()); Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
@ -467,13 +495,13 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
std::string src_name = src_storage->getTableName(); std::string src_name = src_storage->getTableName();
/// Если таблицу успели удалить, ничего не делаем. /// Если таблицу успели удалить, ничего не делаем.
if (!context.getDatabases()[source_database].count(src_name)) if (!context.isTableExist(source_database, src_name))
continue; continue;
/// Роняем исходную таблицу. /// Отцепляем исходную таблицу. Ее данные и метаданные остаются на диске.
executeQuery("DROP TABLE " + backQuoteIfNeed(source_database) + "." + backQuoteIfNeed(src_name), context, true); tables_to_drop.push_back(context.detachTable(source_database, src_name));
/// Создаем на ее месте ChunkRef /// Создаем на ее месте ChunkRef. Это возможно только потому что у ChunkRef нет ни, ни метаданных.
try try
{ {
context.addTable(source_database, src_name, StorageChunkRef::create(src_name, context, source_database, new_table_name, false)); context.addTable(source_database, src_name, StorageChunkRef::create(src_name, context, source_database, new_table_name, false));
@ -490,6 +518,15 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
currently_written_groups.erase(new_table_full_name); currently_written_groups.erase(new_table_full_name);
} }
/// Теперь удалим данные отцепленных таблиц.
table_locks.clear();
for (StoragePtr table : tables_to_drop)
{
InterpreterDropQuery::dropDetachedTable(source_database, table, context);
/// NOTE: Если между подменой таблицы и этой строчкой кто-то успеет попытаться создать новую таблицу на ее месте,
/// что-нибудь может сломаться.
}
/// Сейчас на new_storage ссылаются таблицы типа ChunkRef. Удалим лишнюю ссылку, которая была при создании. /// Сейчас на new_storage ссылаются таблицы типа ChunkRef. Удалим лишнюю ссылку, которая была при создании.
new_storage.removeReference(); new_storage.removeReference();

View File

@ -43,7 +43,7 @@ ASTPtr StorageChunkRef::getCustomCreateQuery(const Context & context) const
return res; return res;
} }
void StorageChunkRef::dropImpl() void StorageChunkRef::drop()
{ {
try try
{ {

View File

@ -57,7 +57,7 @@ BlockInputStreams StorageChunks::read(
Block virtual_columns_block = getBlockWithVirtualColumns(); Block virtual_columns_block = getBlockWithVirtualColumns();
BlockInputStreamPtr virtual_columns = BlockInputStreamPtr virtual_columns =
VirtualColumnUtils::getVirtualColumnsBlocks(query->clone(), virtual_columns_block, context); VirtualColumnUtils::getVirtualColumnsBlocks(query->clone(), virtual_columns_block, context);
std::set<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name); std::multiset<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name);
bool all_inclusive = (values.size() == virtual_columns_block.rows()); bool all_inclusive = (values.size() == virtual_columns_block.rows());
if (all_inclusive) if (all_inclusive)

View File

@ -177,12 +177,12 @@ BlockInputStreams StorageDistributed::read(
else /// Иначе, считаем допустимыми все возможные значения else /// Иначе, считаем допустимыми все возможные значения
virtual_columns = new OneBlockInputStream(virtual_columns_block); virtual_columns = new OneBlockInputStream(virtual_columns_block);
std::set< std::pair<String, UInt16> > values = std::multiset< std::pair<String, UInt16> > values =
VirtualColumnUtils::extractTwoValuesFromBlocks<String, UInt16>(virtual_columns, _host_column_name, _port_column_name); VirtualColumnUtils::extractTwoValuesFromBlocks<String, UInt16>(virtual_columns, _host_column_name, _port_column_name);
bool all_inclusive = values.size() == virtual_columns_block.rows(); bool all_inclusive = values.size() == virtual_columns_block.rows();
size_t result_size = values.size(); 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; result_size += cluster.getLocalNodesNum() - 1;
processed_stage = result_size == 1 processed_stage = result_size == 1
@ -239,10 +239,6 @@ BlockInputStreams StorageDistributed::read(
} }
} }
/// Не дадим уничтожать объект до конца обработки запроса.
for (auto & stream : res)
stream->setOwnedStorage(thisPtr());
return res; return res;
} }

View File

@ -29,6 +29,31 @@
namespace DB 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<const ASTFunction *>(&*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( StoragePtr StorageFactory::get(
const String & name, const String & name,
const String & data_path, const String & data_path,
@ -182,17 +207,12 @@ StoragePtr StorageFactory::get(
String date_column_name = dynamic_cast<ASTIdentifier &>(*args[0]).name; String date_column_name = dynamic_cast<ASTIdentifier &>(*args[0]).name;
ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1]; ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1];
UInt64 index_granularity = safeGet<UInt64>(dynamic_cast<ASTLiteral &>(*args[arg_offset + 2]).value); UInt64 index_granularity = safeGet<UInt64>(dynamic_cast<ASTLiteral &>(*args[arg_offset + 2]).value);
ASTFunction & primary_expr_func = dynamic_cast<ASTFunction &>(*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( 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); name == "SummingMergeTree" ? MergeTreeData::Summing : MergeTreeData::Ordinary);
} }
else if (name == "CollapsingMergeTree") else if (name == "CollapsingMergeTree")
{ {
@ -224,17 +244,12 @@ StoragePtr StorageFactory::get(
ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1]; ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1];
UInt64 index_granularity = safeGet<UInt64>(dynamic_cast<ASTLiteral &>(*args[arg_offset + 2]).value); UInt64 index_granularity = safeGet<UInt64>(dynamic_cast<ASTLiteral &>(*args[arg_offset + 2]).value);
String sign_column_name = dynamic_cast<ASTIdentifier &>(*args[arg_offset + 3]).name; String sign_column_name = dynamic_cast<ASTIdentifier &>(*args[arg_offset + 3]).name;
ASTFunction & primary_expr_func = dynamic_cast<ASTFunction &>(*args[arg_offset + 1]);
if (primary_expr_func.name != "tuple") ASTPtr primary_expr_list = extractPrimaryKey(args[arg_offset + 1], name);
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);
return StorageMergeTree::create( 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); sampling_expression, index_granularity, MergeTreeData::Collapsing, sign_column_name);
} }
else if (name == "SystemNumbers") else if (name == "SystemNumbers")
{ {

View File

@ -31,12 +31,26 @@ namespace DB
using Poco::SharedPtr; using Poco::SharedPtr;
LogBlockInputStream::LogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage, size_t mark_number_, size_t rows_limit_) LogBlockInputStream::LogBlockInputStream(size_t block_size_, const Names & column_names_, StorageLog & storage_, size_t mark_number_, size_t rows_limit_)
: IProfilingBlockInputStream(owned_storage), block_size(block_size_), column_names(column_names_), storage(dynamic_cast<StorageLog &>(*owned_storage)), mark_number(mark_number_), rows_limit(rows_limit_), rows_read(0), current_mark(mark_number_) : 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 LogBlockInputStream::readImpl()
{ {
Block res; Block res;
@ -232,8 +246,8 @@ void LogBlockInputStream::readData(const String & name, const IDataType & type,
} }
LogBlockOutputStream::LogBlockOutputStream(StoragePtr owned_storage) LogBlockOutputStream::LogBlockOutputStream(StorageLog & storage_)
: IBlockOutputStream(owned_storage), storage(dynamic_cast<StorageLog &>(*owned_storage)), : storage(storage_),
lock(storage.rwlock), marks_stream(storage.marks_file.path(), 4096, O_APPEND | O_CREAT | O_WRONLY) 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) 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( res.push_back(new LogBlockInputStream(
max_block_size, max_block_size,
column_names, column_names,
thisPtr(), *this,
0, std::numeric_limits<size_t>::max())); 0, std::numeric_limits<size_t>::max()));
} }
else else
@ -621,7 +635,7 @@ BlockInputStreams StorageLog::read(
res.push_back(new LogBlockInputStream( res.push_back(new LogBlockInputStream(
max_block_size, max_block_size,
column_names, column_names,
thisPtr(), *this,
from_mark + thread * (to_mark - from_mark) / threads, from_mark + thread * (to_mark - from_mark) / threads,
marks[from_mark + (thread + 1) * (to_mark - from_mark) / threads - 1].rows - marks[from_mark + (thread + 1) * (to_mark - from_mark) / threads - 1].rows -
((thread == 0 && from_mark == 0) ((thread == 0 && from_mark == 0)
@ -650,7 +664,7 @@ BlockOutputStreamPtr StorageLog::write(
ASTPtr query) ASTPtr query)
{ {
loadMarks(); loadMarks();
return new LogBlockOutputStream(thisPtr()); return new LogBlockOutputStream(*this);
} }

View File

@ -74,7 +74,8 @@ BlockOutputStreamPtr StorageMaterializedView::write(ASTPtr query)
return data->write(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)); context.getGlobalContext().removeDependency(DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(database_name, table_name));
/// Состваляем и выполняем запрос drop для внутреннего хранилища. /// Состваляем и выполняем запрос drop для внутреннего хранилища.

View File

@ -12,8 +12,8 @@ namespace DB
using Poco::SharedPtr; using Poco::SharedPtr;
MemoryBlockInputStream::MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_, StoragePtr owned_storage) MemoryBlockInputStream::MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_)
: IProfilingBlockInputStream(owned_storage), column_names(column_names_), begin(begin_), end(end_), it(begin) : column_names(column_names_), begin(begin_), end(end_), it(begin)
{ {
} }
@ -39,8 +39,8 @@ Block MemoryBlockInputStream::readImpl()
} }
MemoryBlockOutputStream::MemoryBlockOutputStream(StoragePtr owned_storage) MemoryBlockOutputStream::MemoryBlockOutputStream(StorageMemory & storage_)
: IBlockOutputStream(owned_storage), storage(dynamic_cast<StorageMemory &>(*owned_storage)) : storage(storage_)
{ {
} }
@ -92,7 +92,7 @@ BlockInputStreams StorageMemory::read(
std::advance(begin, thread * size / threads); std::advance(begin, thread * size / threads);
std::advance(end, (thread + 1) * 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; return res;
@ -102,11 +102,11 @@ BlockInputStreams StorageMemory::read(
BlockOutputStreamPtr StorageMemory::write( BlockOutputStreamPtr StorageMemory::write(
ASTPtr query) ASTPtr query)
{ {
return new MemoryBlockOutputStream(thisPtr()); return new MemoryBlockOutputStream(*this);
} }
void StorageMemory::dropImpl() void StorageMemory::drop()
{ {
Poco::ScopedLock<Poco::FastMutex> lock(mutex); Poco::ScopedLock<Poco::FastMutex> lock(mutex);
data.clear(); data.clear();

View File

@ -55,7 +55,7 @@ BlockInputStreams StorageMerge::read(
else else
virt_column_names.push_back(it); virt_column_names.push_back(it);
SelectedTables selected_tables; StorageVector selected_tables;
/// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную. /// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную.
processed_stage = QueryProcessingStage::Complete; processed_stage = QueryProcessingStage::Complete;
@ -72,6 +72,14 @@ BlockInputStreams StorageMerge::read(
getSelectedTables(selected_tables); getSelectedTables(selected_tables);
} }
TableLocks table_locks;
/// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем.
for (auto table : selected_tables)
{
table_locks.push_back(table->lockStructure(false));
}
Block virtual_columns_block = getBlockWithVirtualColumns(selected_tables); Block virtual_columns_block = getBlockWithVirtualColumns(selected_tables);
BlockInputStreamPtr virtual_columns; BlockInputStreamPtr virtual_columns;
@ -81,23 +89,26 @@ BlockInputStreams StorageMerge::read(
else /// Иначе, считаем допустимыми все возможные значения else /// Иначе, считаем допустимыми все возможные значения
virtual_columns = new OneBlockInputStream(virtual_columns_block); virtual_columns = new OneBlockInputStream(virtual_columns_block);
std::set<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name); std::multiset<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name);
bool all_inclusive = (values.size() == virtual_columns_block.rows()); 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; continue;
/// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой. /// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой.
if (real_column_names.size() == 0) 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(); 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, real_column_names,
modified_query_ast, modified_query_ast,
settings, settings,
@ -105,17 +116,21 @@ BlockInputStreams StorageMerge::read(
max_block_size, max_block_size,
selected_tables.size() > threads ? 1 : (threads / selected_tables.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) for (auto & virtual_column : virt_column_names)
{ {
if (virtual_column == _table_column_name) if (virtual_column == _table_column_name)
{ {
for (auto & stream : source_streams) for (auto & stream : source_streams)
stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, (*it)->getTableName(), _table_column_name); stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, table->getTableName(), _table_column_name);
} }
} }
for (BlockInputStreams::iterator jt = source_streams.begin(); jt != source_streams.end(); ++jt) res.insert(res.end(), source_streams.begin(), source_streams.end());
res.push_back(*jt);
if (tmp_processed_stage < processed_stage) if (tmp_processed_stage < processed_stage)
processed_stage = tmp_processed_stage; processed_stage = tmp_processed_stage;
@ -135,7 +150,7 @@ Block StorageMerge::getBlockWithVirtualColumns(const std::vector<StoragePtr> & s
Block res; Block res;
ColumnWithNameAndType _table(new ColumnString, new DataTypeString, _table_column_name); 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()); _table.column->insert((*it)->getTableName());
res.insert(_table); res.insert(_table);
@ -146,7 +161,7 @@ void StorageMerge::getSelectedTables(StorageVector & selected_tables)
{ {
const Tables & tables = context.getDatabases().at(source_database); const Tables & tables = context.getDatabases().at(source_database);
for (Tables::const_iterator it = tables.begin(); it != tables.end(); ++it) 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); selected_tables.push_back(it->second);
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
#include <DB/Storages/StoragePtr.h>
#include <DB/Storages/IStorage.h>
#include <Yandex/logger_useful.h>
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(...)
{
}
}
}

View File

@ -27,12 +27,25 @@ namespace DB
using Poco::SharedPtr; using Poco::SharedPtr;
TinyLogBlockInputStream::TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage) TinyLogBlockInputStream::TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StorageTinyLog & storage_)
: IProfilingBlockInputStream(owned_storage), block_size(block_size_), column_names(column_names_), storage(dynamic_cast<StorageTinyLog &>(*owned_storage)), finished(false) : 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 TinyLogBlockInputStream::readImpl()
{ {
Block res; Block res;
@ -172,8 +185,8 @@ void TinyLogBlockInputStream::readData(const String & name, const IDataType & ty
} }
TinyLogBlockOutputStream::TinyLogBlockOutputStream(StoragePtr owned_storage) TinyLogBlockOutputStream::TinyLogBlockOutputStream(StorageTinyLog & storage_)
: IBlockOutputStream(owned_storage), storage(dynamic_cast<StorageTinyLog &>(*owned_storage)) : storage(storage_)
{ {
for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it) for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it)
addStream(it->first, *it->second); addStream(it->first, *it->second);
@ -363,18 +376,18 @@ BlockInputStreams StorageTinyLog::read(
{ {
check(column_names); check(column_names);
processed_stage = QueryProcessingStage::FetchColumns; 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( BlockOutputStreamPtr StorageTinyLog::write(
ASTPtr query) ASTPtr query)
{ {
return new TinyLogBlockOutputStream(thisPtr()); return new TinyLogBlockOutputStream(*this);
} }
void StorageTinyLog::dropImpl() void StorageTinyLog::drop()
{ {
for (Files_t::iterator it = files.begin(); it != files.end(); ++it) for (Files_t::iterator it = files.begin(); it != files.end(); ++it)
if (it->second.data_file.exists()) if (it->second.data_file.exists())

View File

@ -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)); context.getGlobalContext().removeDependency(DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(database_name, table_name));
} }

View File

@ -37,7 +37,7 @@ DataParts copy(const DataParts &a)
const int RowsPerSec = 100000; const int RowsPerSec = 100000;
StorageMergeTreeSettings settings; MergeTreeSettings settings;
size_t index_granularity = 1; size_t index_granularity = 1;

4
dbms/tests/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.result
*.diff
*.error
test_data

100
dbms/tests/clickhouse-test Executable file
View File

@ -0,0 +1,100 @@
#!/bin/bash
# Скрипт для тестирования запросов к ClickHouse.
# Из файлов *.sql в заданной директории, в алфавитном порядке, отправляются все запросы.
# Результаты сравниваются с эталонами.
QUERIES_DIR="./queries"
CLIENT_PROGRAM="curl -sS http://localhost:8123/ --data-binary @-"
COLOR_RESET="\033[0m"
COLOR_WHITE="\033[1;37m"
COLOR_FAIL="\033[1;31m"
COLOR_UNKNOWN="\033[1;30m"
COLOR_OK="\033[1;32m"
MSG_FAIL="${COLOR_WHITE}[ ${COLOR_FAIL}FAIL${COLOR_WHITE} ]${COLOR_RESET}"
MSG_UNKNOWN="${COLOR_WHITE}[ ${COLOR_UNKNOWN}UNKNOWN${COLOR_WHITE} ]${COLOR_RESET}"
MSG_OK="${COLOR_WHITE}[ ${COLOR_OK}OK${COLOR_WHITE} ]${COLOR_RESET}"
MSG_GENERATED="${COLOR_WHITE}[ ${COLOR_UNKNOWN}GENERATED${COLOR_WHITE} ]${COLOR_RESET}"
ERRORS=0
for dir in $(ls $QUERIES_DIR)
do
tests_name=$(echo $dir | sed -E 's/^[0-9_]+//')
echo
echo "Running $tests_name tests."
echo
if [[ "$tests_name" =~ "stateful" && 0 -eq $(echo "EXISTS TABLE test.hits" | $CLIENT_PROGRAM) ]]; then
echo "Won't run stateful tests because test data wasn't loaded. See README.txt."
continue
fi
for query_file in $(ls $QUERIES_DIR/$dir/*.sql)
do
test_name=$(basename $query_file .sql)
result_file=$QUERIES_DIR/$dir/$test_name.result
error_file=$QUERIES_DIR/$dir/$test_name.error
reference_file=$QUERIES_DIR/$dir/$test_name.reference
diff_file=$QUERIES_DIR/$dir/$test_name.diff
printf "%-60s" "$test_name: "
$CLIENT_PROGRAM < $query_file > $result_file 2> $error_file
ret_code=$?
if [ $ret_code -ne 0 ]; then
ERRORS=$(($ERRORS + 1))
echo -e "$MSG_FAIL - return code $ret_code"
if [ -s "$error_file" ]; then
cat $error_file
fi
# разорвано соединение с сервером
if grep -q -E "Connection refused|Attempt to read after eof" $error_file; then
exit 1;
fi
elif [ -s "$error_file" ]; then
ERRORS=$(($ERRORS + 1))
echo -e "$MSG_FAIL - having stderror:"
cat $error_file
elif grep -q "Exception" $result_file; then
ERRORS=$(($ERRORS + 1))
echo -e "$MSG_FAIL - having exception:"
cat $result_file
elif [ ! -e "$reference_file" ]; then
# надо сгенерировать эталонный результат
if [ "$1" == "--generate" ]; then
cp $result_file $reference_file
echo -e "$MSG_GENERATED - no reference file"
else
echo -e "$MSG_UNKNOWN - no reference file (use --generate to create)"
fi
else
diff $reference_file $result_file > $diff_file
if [ -s "$diff_file" ]; then
ERRORS=$(($ERRORS + 1))
echo -e "$MSG_FAIL - result differs with reference:"
cat $diff_file
else
echo -e "$MSG_OK"
rm $error_file $result_file $diff_file
fi
fi
done
done
echo
if [ $ERRORS -gt 0 ]; then
echo -e "${COLOR_FAIL}Having $ERRORS errors!${COLOR_RESET}"
exit 1
else
echo -e "${COLOR_OK}All tests passed.${COLOR_RESET}"
exit 0
fi

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
SELECT 1

View File

@ -0,0 +1,10 @@
0
1
2
3
4
5
6
7
8
9

View File

@ -0,0 +1 @@
SELECT * FROM system.numbers LIMIT 10

View File

@ -0,0 +1 @@
33232

View File

@ -0,0 +1 @@
SELECT number FROM system.numbers WHERE reinterpretAsString(number) = 'Ё' LIMIT 1

View File

@ -0,0 +1 @@
SELECT (dummy AS x) - 1 FROM remote('127.0.0.{1,2}', system, one)

View File

@ -0,0 +1 @@
SELECT count() FROM remote('127.0.0.{1,2}', system, one) WHERE arrayExists((x) -> x = 1, [1, 2, 3])

View File

@ -0,0 +1 @@
SET GLOBAL extremes = 1

View File

@ -0,0 +1,18 @@
{
"meta":
[
{
"name": "'Hello, world'",
"type": "String"
}
],
"data":
[
],
"rows": 0,
"rows_before_limit_at_least": 10
}

View File

@ -0,0 +1,2 @@
SELECT 'Hello, world' FROM (SELECT number FROM system.numbers LIMIT 10) WHERE number < 0
FORMAT JSONCompact

View File

@ -0,0 +1 @@
SET GLOBAL extremes = 0

View File

@ -0,0 +1 @@
['Hello','Goodbye']

View File

@ -0,0 +1 @@
SELECT ['Hello', 'Goodbye']

Some files were not shown because too many files have changed in this diff Show More