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)
{
// std::cerr << "i: " << i << std::endl;
/// Насколько размножить массив.
size_t size_to_replicate = replicate_offsets[i] - prev_replicate_offset;
// std::cerr << "size_to_replicate: " << size_to_replicate << std::endl;
/// Количество строк в массиве.
size_t value_size = cur_offsets[i] - prev_cur_offset;
// std::cerr << "value_size: " << value_size << std::endl;
size_t sum_chars_size = 0;
for (size_t j = 0; j < size_to_replicate; ++j)
{
// std::cerr << "j: " << j << std::endl;
current_res_offset += value_size;
res_offsets.push_back(current_res_offset);
// std::cerr << "current_res_offset: " << current_res_offset << std::endl;
sum_chars_size = 0;
size_t prev_cur_string_offset_local = prev_cur_string_offset;
for (size_t k = 0; k < value_size; ++k)
{
// std::cerr << "k: " << k << std::endl;
/// Размер одной строки.
size_t chars_size = cur_string_offsets[k + prev_cur_offset] - prev_cur_string_offset_local;
// std::cerr << "chars_size: " << chars_size << std::endl;
current_res_string_offset += chars_size;
res_string_offsets.push_back(current_res_string_offset);
// std::cerr << "current_res_string_offset: " << current_res_string_offset << std::endl;
/// Копирование символов одной строки.
res_chars.resize(res_chars.size() + chars_size);
memcpy(&res_chars[res_chars.size() - chars_size], &cur_chars[prev_cur_string_offset_local], chars_size);
// std::cerr << "copied: " << mysqlxx::escape << std::string(reinterpret_cast<const char *>(&cur_chars[prev_cur_string_offset_local]), chars_size) << std::endl;
sum_chars_size += chars_size;
prev_cur_string_offset_local += chars_size;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -78,8 +78,8 @@ private:
class IProfilingBlockInputStream : public IBlockInputStream
{
public:
IProfilingBlockInputStream(StoragePtr owned_storage_ = StoragePtr())
: IBlockInputStream(owned_storage_), is_cancelled(false), process_list_elem(NULL),
IProfilingBlockInputStream()
: is_cancelled(false), process_list_elem(NULL),
enabled_extremes(false), quota(NULL), prev_elapsed(0) {}
Block read();

View File

@ -19,7 +19,7 @@ class MergingSortedBlockInputStream : public IProfilingBlockInputStream
{
public:
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
MergingSortedBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, size_t max_block_size_, size_t limit_ = 0)
MergingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_, size_t limit_ = 0)
: description(description_), max_block_size(max_block_size_), limit(limit_), total_merged_rows(0), first(true), has_collation(false),
num_columns(0), source_blocks(inputs_.size()), cursors(inputs_.size()), log(&Logger::get("MergingSortedBlockInputStream"))
{

View File

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

View File

@ -18,7 +18,7 @@ namespace DB
class SummingSortedBlockInputStream : public MergingSortedBlockInputStream
{
public:
SummingSortedBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, size_t max_block_size_)
SummingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_)
: MergingSortedBlockInputStream(inputs_, description_, max_block_size_),
log(&Logger::get("SummingSortedBlockInputStream")), current_row_is_zero(false)
{

View File

@ -20,12 +20,12 @@ namespace DB
*/
struct Memory : boost::noncopyable
{
size_t m_capacity;
size_t m_size;
char * m_data;
size_t alignment;
size_t m_capacity = 0;
size_t m_size = 0;
char * m_data = nullptr;
size_t alignment = 0;
Memory() : m_capacity(0), m_size(0), m_data(NULL), alignment(0) {}
Memory() {}
/// Если alignment != 0, то будет выделяться память, выровненная на alignment.
Memory(size_t size_, size_t alignment_ = 0) : m_capacity(size_), m_size(m_capacity), alignment(alignment_)
@ -38,6 +38,21 @@ struct Memory : boost::noncopyable
dealloc();
}
Memory(Memory && rhs)
{
*this = std::move(rhs);
}
Memory & operator=(Memory && rhs)
{
std::swap(m_capacity, rhs.m_capacity);
std::swap(m_size, rhs.m_size);
std::swap(m_data, rhs.m_data);
std::swap(alignment, rhs.alignment);
return *this;
}
size_t size() const { return m_size; }
const char & operator[](size_t i) const { return m_data[i]; }
char & operator[](size_t i) { return m_data[i]; }

View File

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

View File

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

View File

@ -76,12 +76,9 @@ private:
NamesAndTypesList columns;
{
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);
columns = context.getTable(ast.database, ast.table)->getColumnsList();
StoragePtr table = context.getTable(ast.database, ast.table);
auto table_lock = table->lockStructure(false);
columns = table->getColumnsList();
}
ColumnString * name_column = new ColumnString;

View File

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

View File

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

View File

@ -20,7 +20,9 @@ public:
void execute()
{
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:

View File

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

View File

@ -68,18 +68,9 @@ private:
{
const ASTShowCreateQuery & ast = dynamic_cast<const ASTShowCreateQuery &>(*query_ptr);
String res;
{
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();
}
String res = stream.str();
ColumnWithNameAndType col;
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/Exception.h>
#include <DB/Core/QueryProcessingStage.h>
#include <DB/DataStreams/IBlockInputStream.h>
#include <DB/DataStreams/IBlockOutputStream.h>
#include <DB/Parsers/IAST.h>
#include <DB/Parsers/ASTAlterQuery.h>
#include <DB/Interpreters/Settings.h>
#include <DB/Storages/StoragePtr.h>
#include "DatabaseDropper.h"
#include <DB/Storages/ITableDeclaration.h>
#include <Poco/File.h>
#include <Poco/RWLock.h>
namespace DB
{
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.)
*/
class IStorage : private boost::noncopyable
class IStorage : private boost::noncopyable, public ITableDeclaration
{
public:
/// Основное имя типа таблицы (например, StorageWithoutKey).
/// Основное имя типа таблицы (например, StorageMergeTree).
virtual std::string getName() const = 0;
/// Имя самой таблицы (например, hits)
virtual std::string getTableName() const = 0;
/** Получить список имён и типов столбцов таблицы, только невиртуальные.
*/
virtual const NamesAndTypesList & getColumnsList() const = 0;
/** Получить описание реального (невиртуального) столбца по его имени.
*/
virtual NameAndTypePair getRealColumn(const String & column_name) const;
/** Присутствует ли реальный (невиртуальный) столбец с таким именем.
*/
virtual bool hasRealColumn(const String & column_name) const;
/** Получить описание любого столбца по его имени.
*/
virtual NameAndTypePair getColumn(const String & column_name) const;
/** Присутствует ли столбец с таким именем.
*/
virtual bool hasColumn(const String & column_name) const;
const DataTypePtr getDataTypeByName(const String & column_name) const;
/** То же самое, но в виде блока-образца.
*/
Block getSampleBlock() const;
/** Возвращает true, если хранилище получает данные с удалённого сервера или серверов.
*/
virtual bool isRemote() const { return false; }
@ -81,6 +56,76 @@ public:
*/
virtual bool supportsPrewhere() const { return false; }
/** Не дает изменять описание таблицы (в том числе переименовывать и удалять таблицу).
* Если в течение какой-то операции структура таблицы должна оставаться неизменной, нужно держать такой лок на все ее время.
* Например, нужно держать такой лок на время всего запроса SELECT или INSERT и на все время слияния набора кусков
* (но между выбором кусков для слияния и их слиянием структура таблицы может измениться).
* NOTE: Это лок на "чтение" описания таблицы. Чтобы изменить описание таблицы, нужно взять TableStructureWriteLock.
*/
class TableStructureReadLock
{
private:
friend class IStorage;
/// Порядок важен.
Poco::SharedPtr<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 - рекомендация, сколько потоков возвращать,
* если хранилище может возвращать разное количество потоков.
*
* Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP).
*/
virtual BlockInputStreams read(
const Names & column_names,
@ -112,6 +159,8 @@ public:
/** Пишет данные в таблицу.
* Принимает описание запроса, в котором может содержаться информация о методе записи данных.
* Возвращает объект, с помощью которого можно последовательно писать данные.
*
* Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP).
*/
virtual BlockOutputStreamPtr write(
ASTPtr query)
@ -119,21 +168,15 @@ public:
throw Exception("Method write is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
}
/** Удалить данные таблицы. После вызова этого метода, использование объекта некорректно (его можно лишь уничтожить).
*/
void drop()
{
drop_on_destroy = true;
}
/** Вызывается перед удалением директории с данными и вызовом деструктора.
/** Удалить данные таблицы. Вызывается перед удалением директории с данными.
* Если не требуется никаких действий, кроме удаления директории с данными, этот метод можно оставить пустым.
*/
virtual void dropImpl() {}
virtual void drop() {}
/** Переименовать таблицу.
* Переименование имени в файле с метаданными, имени в списке таблиц в оперативке, осуществляется отдельно.
* В этой функции нужно переименовать директорию с данными, если она есть.
* Вызывается при заблокированной на запись структуре таблицы.
*/
virtual void rename(const String & new_path_to_db, const String & new_name)
{
@ -142,12 +185,29 @@ public:
/** ALTER таблицы в виде изменения столбцов, не затрагивающий изменение Storage или его параметров.
* (ALTER, затрагивающий изменение движка, делается внешним кодом, путём копирования данных.)
* Вызывается при заблокированной на запись структуре таблицы.
* Для ALTER MODIFY можно использовать другие методы (см. ниже).
*/
virtual void alter(const ASTAlterQuery::Parameters & params)
{
throw Exception("Method alter is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
}
/** ALTER MODIFY (изменение типа столбца) выполняется в два вызова:
* Сначала вызывается prepareAlterModify при заблокированной записи данных, но незаблокированной структуре таблицы.
* В нем можно выполнить долгую работу по записи сконвертированных данных, оставляя доступными существующие данные.
* Потом вызывается commitAlterModify при заблокированной структуре таблицы.
* В нем нужно закончить изменение типа столбца.
* Для движков с тривиальным ALTER MODIFY можно оставить реализацию по умолчанию, вызывающую alter.
*/
virtual void prepareAlterModify(const ASTAlterQuery::Parameters & params) {}
virtual void commitAlterModify(const ASTAlterQuery::Parameters & params)
{
alter(params);
}
/** Выполнить какую-либо фоновую работу. Например, объединение кусков в таблице типа MergeTree.
* Возвращает - была ли выполнена какая-либо работа.
*/
@ -169,56 +229,56 @@ public:
/** Если при уничтожении объекта надо сделать какую-то сложную работу - сделать её заранее.
* Например, если таблица содержит какие-нибудь потоки для фоновой работы - попросить их завершиться и дождаться завершения.
* По-умолчанию - ничего не делать.
* Может вызываться одновременно из разных потоков, даже после вызова drop().
*/
virtual void shutdown() {}
virtual ~IStorage() {}
/** Проверить, что все запрошенные имена есть в таблице и заданы корректно.
* (список имён не пустой и имена не повторяются)
*/
void check(const Names & column_names) const;
/** Проверить, что блок с данными для записи содержит все столбцы таблицы с правильными типами,
* содержит только столбцы таблицы, и все столбцы различны.
* Если need_all, еще проверяет, что все столбцы таблицы есть в блоке.
*/
void check(const Block & block, bool need_all = false) const;
/** Возвращает владеющий указатель на себя.
*/
StoragePtr thisPtr()
std::shared_ptr<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));
this_ptr = p;
return StoragePtr(this_ptr);
}
else
{
return StoragePtr(this_ptr);
res.reset(this);
this_ptr = res;
}
return res;
}
/** Не дает удалить БД до удаления таблицы. Присваивается перед удалением таблицы или БД.
*/
DatabaseDropperPtr database_to_drop;
bool drop_on_destroy;
/** Директория с данными. Будет удалена после удаления таблицы (после вызова dropImpl).
*/
std::string path_to_remove_on_drop;
bool is_dropped;
protected:
IStorage() : drop_on_destroy(false) {}
IStorage() : is_dropped(false) {}
/// реализация alter, модифицирующая список столбцов.
void alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) const;
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 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
#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/MergeTreeReader.h>
@ -14,19 +13,18 @@ namespace DB
class MergeTreeBlockInputStream : public IProfilingBlockInputStream
{
public:
/// Параметры storage_ и owned_storage разделены, чтобы можно было сделать поток, не владеющий своим storage
/// (например, поток, сливаящий куски). В таком случае сам storage должен следить, чтобы не удалить данные, пока их читают.
MergeTreeBlockInputStream(const String & path_, /// Путь к куску
size_t block_size_, const Names & column_names_,
StorageMergeTree & storage_, const StorageMergeTree::DataPartPtr & owned_data_part_,
const MarkRanges & mark_ranges_, StoragePtr owned_storage, bool use_uncompressed_cache_,
MergeTreeData & storage_, const MergeTreeData::DataPartPtr & owned_data_part_,
const MarkRanges & mark_ranges_, bool use_uncompressed_cache_,
ExpressionActionsPtr prewhere_actions_, String prewhere_column_)
: IProfilingBlockInputStream(owned_storage),
:
path(path_), block_size(block_size_), column_names(column_names_),
storage(storage_), owned_data_part(owned_data_part_),
all_mark_ranges(mark_ranges_), remaining_mark_ranges(mark_ranges_),
use_uncompressed_cache(use_uncompressed_cache_),
prewhere_actions(prewhere_actions_), prewhere_column(prewhere_column_)
prewhere_actions(prewhere_actions_), prewhere_column(prewhere_column_),
log(&Logger::get("MergeTreeBlockInputStream"))
{
std::reverse(remaining_mark_ranges.begin(), remaining_mark_ranges.end());
@ -48,7 +46,7 @@ public:
}
column_name_set.insert(column_names.begin(), column_names.end());
LOG_TRACE(storage.log, "Reading " << all_mark_ranges.size() << " ranges from part " << owned_data_part->name
LOG_TRACE(log, "Reading " << all_mark_ranges.size() << " ranges from part " << owned_data_part->name
<< ", up to " << (all_mark_ranges.back().end - all_mark_ranges.front().begin) * storage.index_granularity
<< " rows starting from " << all_mark_ranges.front().begin * storage.index_granularity);
}
@ -58,7 +56,7 @@ public:
String getID() const
{
std::stringstream res;
res << "MergeTree(" << owned_storage->getTableName() << ", " << path << ", columns";
res << "MergeTree(" << path << ", columns";
for (size_t i = 0; i < column_names.size(); ++i)
res << ", " << column_names[i];
@ -72,70 +70,6 @@ public:
return res.str();
}
/// Получает набор диапазонов засечек, вне которых не могут находиться ключи из заданного диапазона.
static MarkRanges markRangesFromPkRange(
const StorageMergeTree::DataPart::Index & index,
StorageMergeTree & storage,
PKCondition & key_condition)
{
MarkRanges res;
size_t key_size = storage.sort_descr.size();
size_t marks_count = index.size() / key_size;
/// Если индекс не используется.
if (key_condition.alwaysTrue())
{
res.push_back(MarkRange(0, marks_count));
}
else
{
/** В стеке всегда будут находиться непересекающиеся подозрительные отрезки, самый левый наверху (back).
* На каждом шаге берем левый отрезок и проверяем, подходит ли он.
* Если подходит, разбиваем его на более мелкие и кладем их в стек. Если нет - выбрасываем его.
* Если отрезок уже длиной в одну засечку, добавляем его в ответ и выбрасываем.
*/
std::vector<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:
/// Будем вызывать progressImpl самостоятельно.
void progress(size_t rows, size_t bytes) {}
@ -321,8 +255,8 @@ private:
Names column_names;
NameSet column_name_set;
Names pre_column_names;
StorageMergeTree & storage;
const StorageMergeTree::DataPartPtr owned_data_part; /// Кусок не будет удалён, пока им владеет этот объект.
MergeTreeData & storage;
const MergeTreeData::DataPartPtr owned_data_part; /// Кусок не будет удалён, пока им владеет этот объект.
MarkRanges all_mark_ranges; /// В каких диапазонах засечек читать. В порядке возрастания номеров.
MarkRanges remaining_mark_ranges; /// В каких диапазонах засечек еще не прочли.
/// В порядке убывания номеров, чтобы можно было выбрасывать из конца.
@ -332,6 +266,8 @@ private:
ExpressionActionsPtr prewhere_actions;
String prewhere_column;
bool remove_prewhere_column;
Logger * log;
};
}

View File

@ -1,298 +1,30 @@
#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>
namespace DB
{
class MergeTreeBlockOutputStream : public IBlockOutputStream
{
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)
{
Poco::ScopedReadRWLock write_lock(storage.write_lock);
storage.check(block, true);
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
size_t rows = block.rows();
size_t columns = block.columns();
/// Достаём столбец с датой.
const ColumnUInt16::Container_t & dates =
dynamic_cast<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)
auto part_blocks = storage.writer.splitBlockIntoParts(block);
for (auto & current_block : part_blocks)
{
if (*it < min_date)
min_date = *it;
if (*it > max_date)
max_date = *it;
UInt64 temp_index = storage.increment.get();
MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, temp_index);
storage.data.renameTempPartAndAdd(part, &storage.increment);
storage.merge(2);
}
/// Разделяем на блоки по месяцам. Для каждого ещё посчитаем минимальную и максимальную дату.
typedef std::map<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:
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
#include <DB/Storages/StorageMergeTree.h>
#include <DB/Storages/MergeTree/MergeTreeData.h>
#include <DB/DataTypes/IDataType.h>
#include <DB/DataTypes/DataTypeNested.h>
#include <DB/DataTypes/DataTypeArray.h>
#include <DB/Core/NamesAndTypes.h>
#include <DB/Common/escapeForFileName.h>
#include <DB/IO/CachedCompressedReadBuffer.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))
@ -15,6 +18,20 @@
namespace DB
{
/** Пара засечек, определяющая диапазон строк в куске. Именно, диапазон имеет вид [begin * index_granularity, end * index_granularity).
*/
struct MarkRange
{
size_t begin;
size_t end;
MarkRange() {}
MarkRange(size_t begin_, size_t end_) : begin(begin_), end(end_) {}
};
typedef std::vector<MarkRange> MarkRanges;
/** Умеет читать данные между парой засечек из одного куска. При чтении последовательных отрезков не делает лишних seek-ов.
* При чтении почти последовательных отрезков делает seek-и быстро, не выбрасывая содержимое буфера.
*/
@ -22,7 +39,7 @@ class MergeTreeReader
{
public:
MergeTreeReader(const String & path_, /// Путь к куску
const Names & columns_names_, bool use_uncompressed_cache_, StorageMergeTree & storage_)
const Names & columns_names_, bool use_uncompressed_cache_, MergeTreeData & storage_)
: path(path_), column_names(columns_names_), use_uncompressed_cache(use_uncompressed_cache_), storage(storage_)
{
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
@ -220,7 +237,7 @@ private:
FileStreams streams;
Names column_names;
bool use_uncompressed_cache;
StorageMergeTree & storage;
MergeTreeData & storage;
void addStream(const String & name, const IDataType & type, size_t level = 0)
{

View File

@ -3,7 +3,7 @@
#include <DB/IO/WriteBufferFromFile.h>
#include <DB/IO/CompressedWriteBuffer.h>
#include <DB/Storages/StorageMergeTree.h>
#include <DB/Storages/MergeTree/MergeTreeData.h>
namespace DB
@ -11,7 +11,7 @@ namespace DB
class IMergedBlockOutputStream : public IBlockOutputStream
{
public:
IMergedBlockOutputStream(StorageMergeTree & storage_) : storage(storage_), index_offset(0)
IMergedBlockOutputStream(MergeTreeData & storage_) : storage(storage_), index_offset(0)
{
}
@ -182,7 +182,7 @@ protected:
}
}
StorageMergeTree & storage;
MergeTreeData & storage;
ColumnStreams column_streams;
@ -196,7 +196,7 @@ protected:
class MergedBlockOutputStream : public IMergedBlockOutputStream
{
public:
MergedBlockOutputStream(StorageMergeTree & storage_,
MergedBlockOutputStream(MergeTreeData & storage_,
UInt16 min_date, UInt16 max_date, UInt64 min_part_id, UInt64 max_part_id, UInt32 level)
: IMergedBlockOutputStream(storage_), marks_count(0)
{
@ -204,15 +204,16 @@ public:
DayNum_t(min_date), DayNum_t(max_date),
min_part_id, max_part_id, level);
part_tmp_path = storage.full_path + "tmp_" + part_name + "/";
part_res_path = storage.full_path + part_name + "/";
part_tmp_path = storage.getFullPath() + "tmp_" + part_name + "/";
part_res_path = storage.getFullPath() + part_name + "/";
Poco::File(part_tmp_path).createDirectories();
index_stream = new WriteBufferFromFile(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, O_TRUNC | O_CREAT | O_WRONLY);
for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it)
addStream(part_tmp_path, it->first, *it->second);
columns_list = storage.getColumnsList();
for (const auto & it : columns_list)
addStream(part_tmp_path, it.first, *it.second);
}
void write(const Block & block)
@ -223,11 +224,11 @@ public:
typedef std::vector<const ColumnWithNameAndType *> PrimaryColumns;
PrimaryColumns primary_columns;
for (size_t i = 0, size = storage.sort_descr.size(); i < size; ++i)
for (const auto & descr : storage.getSortDescription())
primary_columns.push_back(
!storage.sort_descr[i].column_name.empty()
? &block.getByName(storage.sort_descr[i].column_name)
: &block.getByPosition(storage.sort_descr[i].column_number));
!descr.column_name.empty()
? &block.getByName(descr.column_name)
: &block.getByPosition(descr.column_number));
for (size_t i = index_offset; i < rows; i += storage.index_granularity)
{
@ -243,9 +244,9 @@ public:
OffsetColumns offset_columns;
/// Теперь пишем данные.
for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it)
for (const auto & it : columns_list)
{
const ColumnWithNameAndType & column = block.getByName(it->first);
const ColumnWithNameAndType & column = block.getByName(it.first);
writeData(column.name, *column.type, *column.column, offset_columns);
}
@ -285,6 +286,8 @@ public:
}
private:
NamesAndTypesList columns_list;
String part_name;
String part_tmp_path;
String part_res_path;
@ -299,7 +302,7 @@ typedef Poco::SharedPtr<MergedBlockOutputStream> MergedBlockOutputStreamPtr;
class MergedColumnOnlyOutputStream : public IMergedBlockOutputStream
{
public:
MergedColumnOnlyOutputStream(StorageMergeTree & storage_, String part_path_, bool sync_ = false) :
MergedColumnOnlyOutputStream(MergeTreeData & storage_, String part_path_, bool sync_ = false) :
IMergedBlockOutputStream(storage_), part_path(part_path_), initialized(false), sync(sync_)
{
}

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@
#include <DB/IO/CompressedWriteBuffer.h>
#include <DB/Storages/IStorage.h>
#include <DB/DataStreams/IProfilingBlockInputStream.h>
#include <DB/DataStreams/IBlockOutputStream.h>
namespace DB
@ -36,20 +37,10 @@ typedef std::vector<Mark> Marks;
class LogBlockInputStream : public IProfilingBlockInputStream
{
public:
LogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage, size_t mark_number_, size_t rows_limit_);
LogBlockInputStream(size_t block_size_, const Names & column_names_, StorageLog & storage_, size_t mark_number_, size_t rows_limit_);
String getName() const { return "LogBlockInputStream"; }
String getID() const
{
std::stringstream res;
res << "Log(" << owned_storage->getTableName() << ", " << &*owned_storage << ", " << mark_number << ", " << rows_limit;
for (size_t i = 0; i < column_names.size(); ++i)
res << ", " << column_names[i];
res << ")";
return res.str();
}
String getID() const;
protected:
Block readImpl();
@ -88,7 +79,7 @@ private:
class LogBlockOutputStream : public IBlockOutputStream
{
public:
LogBlockOutputStream(StoragePtr owned_storage);
LogBlockOutputStream(StorageLog & storage_);
void write(const Block & block);
void writeSuffix();
private:

View File

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

View File

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

View File

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

View File

@ -1,123 +1,21 @@
#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>
#include <DB/Storages/MergeTree/MergeTreeData.h>
#include "MergeTree/MergeTreeDataSelectExecutor.h"
#include "MergeTree/MergeTreeDataWriter.h"
#include "MergeTree/MergeTreeDataMerger.h"
#include "MergeTree/DiskSpaceMonitor.h"
namespace DB
{
/** Движок, использующий merge tree для инкрементальной сортировки данных.
* Таблица представлена набором сортированных кусков.
* При вставке, данные сортируются по указанному выражению (первичному ключу) и пишутся в новый кусок.
* Куски объединяются в фоне, согласно некоторой эвристике.
* Для каждого куска, создаётся индексный файл, содержащий значение первичного ключа для каждой n-ой строки.
* Таким образом, реализуется эффективная выборка по диапазону первичного ключа.
*
* Дополнительно:
*
* Указывается столбец, содержащий дату.
* Для каждого куска пишется минимальная и максимальная дата.
* (по сути - ещё один индекс)
*
* Данные разделяются по разным месяцам (пишутся в разные куски для разных месяцев).
* Куски для разных месяцев не объединяются - для простоты эксплуатации.
* (дают локальность обновлений, что удобно для синхронизации и бэкапа)
*
* Структура файлов:
* / increment.txt - файл, содержащий одно число, увеличивающееся на 1 - для генерации идентификаторов кусков.
* / min-date _ max-date _ min-id _ max-id _ level / - директория с куском.
* / min-date _ max-date _ min-id _ max-id _ level / primary.idx - индексный файл.
* Внутри директории с куском:
* Column.bin - данные столбца
* Column.mrk - засечки, указывающие, откуда начинать чтение, чтобы пропустить n * k строк.
*
* Имеется несколько режимов работы, определяющих, что делать при мердже:
* - Ordinary - ничего дополнительно не делать;
* - Collapsing - при склейке кусков "схлопывать"
* пары записей с разными значениями sign_column для одного значения первичного ключа.
* (см. CollapsingSortedBlockInputStream.h)
* - Summing - при склейке кусков, при совпадении PK суммировать все числовые столбцы, не входящие в PK.
/** См. описание структуры данных в MergeTreeData.
*/
struct StorageMergeTreeSettings
{
/// Набор кусков разрешено объединить, если среди них максимальный размер не более чем во столько раз больше суммы остальных.
double max_size_ratio_to_merge_parts = 5;
/// Сколько за раз сливать кусков.
/// Трудоемкость выбора кусков O(N * max_parts_to_merge_at_once), так что не следует делать это число слишком большим.
/// С другой стороны, чтобы слияния точно не могли зайти в тупик, нужно хотя бы
/// log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts).
size_t max_parts_to_merge_at_once = 10;
/// Куски настолько большого размера в основном потоке объединять нельзя вообще.
size_t max_rows_to_merge_parts = 100 * 1024 * 1024;
/// Куски настолько большого размера во втором потоке объединять нельзя вообще.
size_t max_rows_to_merge_parts_second = 1024 * 1024;
/// Во столько раз ночью увеличиваем коэффициент.
size_t merge_parts_at_night_inc = 10;
/// Сколько потоков использовать для объединения кусков.
size_t merging_threads = 2;
/// Если из одного файла читается хотя бы столько строк, чтение можно распараллелить.
size_t min_rows_for_concurrent_read = 20 * 8192;
/// Можно пропускать чтение более чем стольки строк ценой одного seek по файлу.
size_t min_rows_for_seek = 5 * 8192;
/// Если отрезок индекса может содержать нужные ключи, делим его на столько частей и рекурсивно проверяем их.
size_t coarse_index_granularity = 8;
/** Максимальное количество строк на запрос, для использования кэша разжатых данных. Если запрос большой - кэш не используется.
* (Чтобы большие запросы не вымывали кэш.)
*/
size_t max_rows_to_use_cache = 1024 * 1024;
/// Через сколько секунд удалять old_куски.
time_t old_parts_lifetime = 5 * 60;
};
/// Пара засечек, определяющая диапазон строк в куске. Именно, диапазон имеет вид [begin * index_granularity, end * index_granularity).
struct MarkRange
{
size_t begin;
size_t end;
MarkRange() {}
MarkRange(size_t begin_, size_t end_) : begin(begin_), end(end_) {}
};
typedef std::vector<MarkRange> MarkRanges;
class StorageMergeTree : public IStorage
{
friend class MergeTreeReader;
friend class MergeTreeBlockInputStream;
friend class MergeTreeBlockOutputStream;
friend class IMergedBlockOutputStream;
friend class MergedBlockOutputStream;
friend class MergedColumnOnlyOutputStream;
public:
/// Режим работы. См. выше.
enum Mode
{
Ordinary,
Collapsing,
Summing,
};
/** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце),
* (корректность имён и путей не проверяется)
* состоящую из указанных столбцов.
@ -132,36 +30,26 @@ public:
const String & date_column_name_,
const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается.
size_t index_granularity_,
Mode mode_ = Ordinary,
MergeTreeData::Mode mode_ = MergeTreeData::Ordinary,
const String & sign_column_ = "",
const StorageMergeTreeSettings & settings_ = StorageMergeTreeSettings());
const MergeTreeSettings & settings_ = MergeTreeSettings());
void shutdown();
~StorageMergeTree();
std::string getName() const
{
switch (mode)
{
case Ordinary: return "MergeTree";
case Collapsing: return "CollapsingMergeTree";
case Summing: return "SummingMergeTree";
default:
throw Exception("Unknown mode of operation for StorageMergeTree: " + toString(mode), ErrorCodes::LOGICAL_ERROR);
}
return data.getModePrefix() + "MergeTree";
}
std::string getTableName() const { return name; }
std::string getSignColumnName() const { return sign_column; }
bool supportsSampling() const { return !!sampling_expression; }
bool supportsFinal() const { return !sign_column.empty(); }
bool supportsPrewhere() const { return true; }
std::string getSignColumnName() const { return data.getSignColumnName(); }
bool supportsSampling() const { return data.supportsSampling(); }
bool supportsFinal() const { return data.supportsFinal(); }
bool supportsPrewhere() const { return data.supportsPrewhere(); }
const NamesAndTypesList & getColumnsList() const { return *columns; }
const NamesAndTypesList & getColumnsList() const { return data.getColumnsList(); }
/** При чтении, выбирается набор кусков, покрывающий нужный диапазон индекса.
*/
BlockInputStreams read(
const Names & column_names,
ASTPtr query,
@ -170,8 +58,6 @@ public:
size_t max_block_size = DEFAULT_BLOCK_SIZE,
unsigned threads = 1);
/** При записи, данные сортируются и пишутся в новые куски.
*/
BlockOutputStreamPtr write(ASTPtr query);
/** Выполнить очередной шаг объединения кусков.
@ -182,264 +68,75 @@ public:
return true;
}
void dropImpl();
void drop() override;
void rename(const String & new_path_to_db, const String & new_name);
/// Метод ALTER позволяет добавлять и удалять столбцы.
/// Метод ALTER нужно применять, когда обращения к базе приостановлены.
/// Например если параллельно с INSERT выполнить ALTER, то ALTER выполниться, а INSERT бросит исключение
void alter(const ASTAlterQuery::Parameters & params);
class BigLock
{
public:
BigLock(StorageMergeTree & storage) : merge_lock(storage.merge_lock),
write_lock(storage.write_lock), read_lock(storage.read_lock)
{
}
private:
Poco::ScopedWriteRWLock merge_lock;
Poco::ScopedWriteRWLock write_lock;
Poco::ScopedWriteRWLock read_lock;
};
typedef Poco::SharedPtr<BigLock> BigLockPtr;
BigLockPtr lockAllOperations()
{
return new BigLock(*this);
}
void prepareAlterModify(const ASTAlterQuery::Parameters & params);
void commitAlterModify(const ASTAlterQuery::Parameters & params);
private:
String path;
String name;
String full_path;
NamesAndTypesListPtr columns;
const Context & context;
ASTPtr primary_expr_ast;
String date_column_name;
ASTPtr sampling_expression;
size_t index_granularity;
size_t min_marks_for_seek;
size_t min_marks_for_concurrent_read;
size_t max_marks_to_use_cache;
/// Режим работы - какие дополнительные действия делать при мердже.
Mode mode;
/// Для схлопывания записей об изменениях, если используется Collapsing режим работы.
String sign_column;
StorageMergeTreeSettings settings;
ExpressionActionsPtr primary_expr;
SortDescription sort_descr;
Block primary_key_sample;
Increment increment;
MergeTreeData data;
MergeTreeDataSelectExecutor reader;
MergeTreeDataWriter writer;
MergeTreeDataMerger merger;
MergeTreeData::DataParts currently_merging;
Poco::FastMutex currently_merging_mutex;
Logger * log;
volatile bool shutdown_called;
/// Регулярное выражение соответсвующее названию директории с кусочками
Poco::RegularExpression file_name_regexp;
Poco::SharedPtr<boost::threadpool::pool> merge_threads;
/// Описание куска с данными.
struct DataPart
{
DataPart(StorageMergeTree & storage_) : storage(storage_), size_in_bytes(0), currently_merging(false) {}
StorageMergeTree & storage;
DayNum_t left_date;
DayNum_t right_date;
UInt64 left;
UInt64 right;
/// Уровень игнорируется. Использовался предыдущей эвристикой слияния.
UInt32 level;
std::string name;
size_t size; /// в количестве засечек.
size_t size_in_bytes; /// размер в байтах, 0 - если не посчитано
time_t modification_time;
DayNum_t left_month;
DayNum_t right_month;
/// Смотреть и изменять это поле следует под залоченным data_parts_mutex.
bool currently_merging;
/// Первичный ключ. Всегда загружается в оперативку.
typedef std::vector<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 и пересчитывает общий объем сливаемых данных.
/// Пока существует, помечает части как currently_merging и держит резерв места.
/// Вероятно, что части будут помечены заранее.
class CurrentlyMergingPartsTagger
struct CurrentlyMergingPartsTagger
{
public:
std::vector<DataPartPtr> parts;
Poco::FastMutex & data_mutex;
MergeTreeData::DataPartsVector parts;
DiskSpaceMonitor::ReservationPtr reserved_space;
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, где он уже залочен
/// Poco::ScopedLock<Poco::FastMutex> lock(data_mutex);
for (size_t i = 0; i < parts.size(); ++i)
/// Здесь не лочится мьютекс, так как конструктор вызывается внутри mergeThread, где он уже залочен.
reserved_space = DiskSpaceMonitor::reserve(storage.full_path, total_size); /// Может бросить исключение.
for (const auto & part : parts)
{
parts[i]->currently_merging = true;
StorageMergeTree::total_size_of_currently_merging_parts += parts[i]->size_in_bytes;
if (storage.currently_merging.count(part))
throw Exception("Tagging alreagy tagged part " + part->name + ". This is a bug.", ErrorCodes::LOGICAL_ERROR);
}
storage.currently_merging.insert(parts.begin(), parts.end());
}
~CurrentlyMergingPartsTagger()
{
Poco::ScopedLock<Poco::FastMutex> lock(data_mutex);
for (size_t i = 0; i < parts.size(); ++i)
try
{
parts[i]->currently_merging = false;
StorageMergeTree::total_size_of_currently_merging_parts -= parts[i]->size_in_bytes;
Poco::ScopedLock<Poco::FastMutex> lock(storage.currently_merging_mutex);
for (const auto & part : parts)
{
if (!storage.currently_merging.count(part))
throw Exception("Untagging already untagged part " + part->name + ". This is a bug.", ErrorCodes::LOGICAL_ERROR);
storage.currently_merging.erase(part);
}
}
catch (...)
{
tryLogCurrentException("~CurrentlyMergingPartsTagger");
}
}
};
/// Сумарный размер currently_merging кусочков в байтах.
/// Нужно чтобы оценить количество места на диске, которое может понадобится для завершения этих мерджей.
static size_t total_size_of_currently_merging_parts;
typedef std::vector<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;
typedef Poco::SharedPtr<CurrentlyMergingPartsTagger> CurrentlyMergingPartsTaggerPtr;
StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_,
const Context & context_,
@ -447,38 +144,11 @@ private:
const String & date_column_name_,
const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается.
size_t index_granularity_,
Mode mode_ = Ordinary,
const String & sign_column_ = "",
const StorageMergeTreeSettings & settings_ = StorageMergeTreeSettings());
MergeTreeData::Mode mode_,
const String & sign_column_,
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, объединяет, пока это возможно.
* Если aggressive - выбрать куски не обращая внимание на соотношение размеров и их новизну (для запроса OPTIMIZE).
@ -488,33 +158,11 @@ private:
/// Если while_can, объединяет в цикле, пока можно; иначе выбирает и объединяет только одну пару кусков.
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();
Poco::SharedPtr<boost::threadpool::pool> merge_threads;
void removeColumnFiles(String column_name);
/// Возвращает true если имя директории совпадает с форматом имени директории кусочков
bool isPartDirectory(const String & dir_name, Poco::RegularExpression::MatchVec & matches) const;
/// Кладет в DataPart данные из имени кусочка.
void parsePartName(const String & file_name, const Poco::RegularExpression::MatchVec & matches, DataPart & part);
/// Определить, не битые ли данные в директории. Проверяет индекс и засечеки, но не сами данные.
bool isBrokenPart(const String & path);
/// Найти самые большие old_куски, из которых получен этот кусок.
/// Переименовать их, убрав префикс old_ и вернуть их имена.
Strings tryRestorePart(const String & path, const String & file_name, Strings & old_parts);
void createConvertExpression(const String & in_column_name, const String & out_type, ExpressionActionsPtr & out_expression, String & out_column);
/// Вызывается во время выбора кусков для слияния.
bool canMergeParts(const MergeTreeData::DataPartPtr & left, const MergeTreeData::DataPartPtr & right);
};
}

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/Storages/IStorage.h>
#include <DB/DataStreams/IProfilingBlockInputStream.h>
#include <DB/DataStreams/IBlockOutputStream.h>
namespace DB
@ -22,20 +23,10 @@ class StorageTinyLog;
class TinyLogBlockInputStream : public IProfilingBlockInputStream
{
public:
TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage);
TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StorageTinyLog & storage_);
String getName() const { return "TinyLogBlockInputStream"; }
String getID() const
{
std::stringstream res;
res << "TinyLog(" << owned_storage->getTableName() << ", " << &*owned_storage;
for (size_t i = 0; i < column_names.size(); ++i)
res << ", " << column_names[i];
res << ")";
return res.str();
}
String getID() const;
protected:
Block readImpl();
@ -68,7 +59,7 @@ private:
class TinyLogBlockOutputStream : public IBlockOutputStream
{
public:
TinyLogBlockOutputStream(StoragePtr owned_storage);
TinyLogBlockOutputStream(StorageTinyLog & storage_);
void write(const Block & block);
void writeSuffix();
private:
@ -134,7 +125,7 @@ public:
BlockOutputStreamPtr write(
ASTPtr query);
void dropImpl();
void drop() override;
void rename(const String & new_path_to_db, const String & new_name);

View File

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

View File

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

View File

@ -55,15 +55,26 @@ private:
{
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());
{
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<String> shards = parseDescription(descripton, 0, descripton.size(), ',');
for (size_t i = 0; i < shards.size(); ++i)
names.push_back(parseDescription(shards[i], 0, shards[i].size(), '|'));
if (names.empty())
throw Exception("Shard list is empty after parsing first argument", ErrorCodes::BAD_ARGUMENTS);
SharedPtr<Cluster> cluster = new Cluster(context.getSettings(), context.getDataTypeFactory(), names, username, password);
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/ASTLiteral.h>
#include <DB/Parsers/ASTSelectQuery.h>
#include <DB/Storages/StoragePtr.h>
#include <DB/Storages/IStorage.h>
#include <DB/Interpreters/InterpreterSelectQuery.h>
namespace DB

View File

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

View File

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

View File

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

View File

@ -74,11 +74,26 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
return StoragePtr();
}
StoragePtr res;
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;
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());
@ -92,12 +107,6 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
throw Exception("Table " + database_name + "." + table_name + " already exists.", ErrorCodes::TABLE_ALREADY_EXISTS);
}
if (!create.as_table.empty())
context.assertTableExists(as_database_name, as_table_name);
if (create.select && !create.attach)
interpreter_select = new InterpreterSelectQuery(create.select, context);
/// Получаем список столбцов
if (create.columns)
{
@ -112,13 +121,12 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
}
}
else if (!create.as_table.empty())
columns = new NamesAndTypesList(context.getTable(as_database_name, as_table_name)->getColumnsList());
columns = new NamesAndTypesList(as_storage->getColumnsList());
else if (create.select)
{
Block sample = interpreter_select->getSampleBlock();
columns = new NamesAndTypesList;
for (size_t i = 0; i < sample.columns(); ++i)
columns->push_back(NameAndTypePair(sample.getByPosition(i).name, sample.getByPosition(i).type));
for (size_t i = 0; i < select_sample.columns(); ++i)
columns->push_back(NameAndTypePair(select_sample.getByPosition(i).name, select_sample.getByPosition(i).type));
}
else
throw Exception("Incorrect CREATE query: required list of column descriptions or AS section or SELECT.", ErrorCodes::INCORRECT_QUERY);
@ -159,7 +167,7 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
}
else if (!create.as_table.empty())
{
storage_name = context.getTable(as_database_name, as_table_name)->getName();
storage_name = as_storage->getName();
create.storage = dynamic_cast<const ASTCreateQuery &>(*context.getCreateQuery(as_database_name, as_table_name)).storage;
}
else if (create.is_view)

View File

@ -1,10 +1,9 @@
#include <Poco/File.h>
#include <DB/Common/escapeForFileName.h>
#include <DB/Parsers/ASTDropQuery.h>
#include <DB/Interpreters/InterpreterDropQuery.h>
#include <DB/Storages/IStorage.h>
namespace DB
@ -19,8 +18,6 @@ InterpreterDropQuery::InterpreterDropQuery(ASTPtr query_ptr_, Context & context_
void InterpreterDropQuery::execute()
{
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
String path = context.getPath();
String current_database = context.getCurrentDatabase();
@ -28,92 +25,116 @@ void InterpreterDropQuery::execute()
String database_name = drop.database.empty() ? current_database : drop.database;
String database_name_escaped = escapeForFileName(database_name);
String table_name = drop.table;
String table_name_escaped = escapeForFileName(table_name);
String data_path = path + "data/" + database_name_escaped + "/" + table_name_escaped;
String metadata_path = path + "metadata/" + database_name_escaped + "/" + (!table_name.empty() ? table_name_escaped + ".sql" : "");
String data_path = path + "data/" + database_name_escaped + "/";
String metadata_path = path + "metadata/" + database_name_escaped + "/";
if (!drop.if_exists)
context.assertDatabaseExists(database_name);
StorageVector tables_to_drop;
if (!drop.table.empty())
{
/// Удаление таблицы
if (!context.isTableExist(database_name, table_name))
{
if (!drop.if_exists)
throw Exception("Table " + database_name + "." + table_name + " doesn't exist.", ErrorCodes::UNKNOWN_TABLE);
StoragePtr table;
if (drop.if_exists)
table = context.tryGetTable(database_name, drop.table);
else
table = context.getTable(database_name, drop.table);
if (table)
tables_to_drop.push_back(table);
else
return;
}
else
{
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);
}
}
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)
{
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();
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(metadata_path).exists())
Poco::File(metadata_path).remove();
}
if (Poco::File(current_metadata_path).exists())
Poco::File(current_metadata_path).remove();
/// Удаляем информацию о таблице из оперативки
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
{
if (context.isDatabaseExist(database_name))
{
/// Удаление базы данных
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();
table->is_dropped = true;
if (Poco::File(current_data_path).exists())
Poco::File(current_data_path).remove(true);
}
}
Poco::File(metadata_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

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

View File

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

View File

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

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),
ErrorCodes::TOO_DEEP_SUBQUERIES);
/// Если имееем дело с табличной функцией
if (query.table && dynamic_cast<ASTSelectQuery *>(&*query.table))
{
if (table_column_names.empty())
context.setColumns(InterpreterSelectQuery(query.table, context).getSampleBlock().getColumnsList());
}
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);
/// Выполнить ее и запомнить результат
table_function_storage = table_function_ptr->execute(query.table, 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);
}
if (table_function_storage)
context.setColumns(table_function_storage->getColumnsList());
else if (!table_column_names.empty())
table_lock = storage->lockStructure(false);
if (table_column_names.empty())
context.setColumns(storage->getColumnsList());
}
if (!table_column_names.empty())
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())
throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN);
query_analyzer = new ExpressionAnalyzer(query_ptr, context, table_function_storage, subquery_depth);
query_analyzer = new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth);
if (input_)
streams.push_back(input_);
@ -127,26 +141,6 @@ void InterpreterSelectQuery::getDatabaseAndTableNames(String & database_name, St
}
StoragePtr InterpreterSelectQuery::getTable()
{
String database_name;
String table_name;
getDatabaseAndTableNames(database_name, table_name);
return context.getTable(database_name, table_name);
}
ASTPtr InterpreterSelectQuery::getCreateQuery()
{
String database_name;
String table_name;
getDatabaseAndTableNames(database_name, table_name);
return context.getCreateQuery(database_name, table_name);
}
DataTypes InterpreterSelectQuery::getReturnTypes()
{
DataTypes res;
@ -210,6 +204,7 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
bool has_having = false;
bool has_order_by = !query.order_expression_list.isNull();
ExpressionActionsPtr array_join;
ExpressionActionsPtr before_where;
ExpressionActionsPtr before_aggregation;
ExpressionActionsPtr before_having;
@ -225,7 +220,8 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
if (from_stage < QueryProcessingStage::WithMergeableState
&& to_stage >= QueryProcessingStage::WithMergeableState)
{
query_analyzer->appendArrayJoin(chain);
if (query_analyzer->appendArrayJoin(chain))
array_join = chain.getLastActions();
if (query_analyzer->appendWhere(chain))
{
@ -312,6 +308,16 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
if (need_aggregate)
executeAggregation(streams, before_aggregation, aggregate_overflow_row, aggregate_final);
if (array_join && !has_where && !need_aggregate && to_stage == QueryProcessingStage::WithMergeableState)
{
/** Если есть ARRAY JOIN, его действие сначала старается оказаться в
* before_where, before_aggregation или before_order_and_select.
* Если ни одного из них нет, array_join нужно выполнить отдельно.
*/
executeExpression(streams, array_join);
}
/** Оптимизация - при распределённой обработке запроса,
* если не указаны DISTINCT, GROUP, HAVING, ORDER, но указан LIMIT,
* то выполним предварительный LIMIT на удалёном сервере.
@ -339,7 +345,7 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
executeHaving(streams, before_having);
}
executeOuterExpression(streams, before_order_and_select);
executeExpression(streams, before_order_and_select);
if (has_order_by)
executeOrder(streams);
@ -424,20 +430,28 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
if (!streams.empty())
return QueryProcessingStage::FetchColumns;
/// Таблица, откуда читать данные, если не подзапрос.
StoragePtr table;
/// Интерпретатор подзапроса, если подзапрос
SharedPtr<InterpreterSelectQuery> interpreter_subquery;
/// Список столбцов, которых нужно прочитать, чтобы выполнить запрос.
Names required_columns = query_analyzer->getRequiredColumns();
if (table_function_storage)
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);
if (query.table && dynamic_cast<ASTSelectQuery *>(&*query.table))
{
/** Для подзапроса не действуют ограничения на максимальный размер результата.
* Так как результат поздапроса - ещё не результат всего запроса.
*/
Context subquery_context = context;
Settings subquery_settings = context.getSettings();
subquery_settings.limits.max_result_rows = 0;
subquery_settings.limits.max_result_bytes = 0;
/// Вычисление extremes не имеет смысла и не нужно (если его делать, то в результате всего запроса могут взяться extremes подзапроса).
subquery_settings.extremes = 0;
subquery_context.setSettings(subquery_settings);
interpreter_subquery = new InterpreterSelectQuery(
query.table, subquery_context, required_columns, QueryProcessingStage::Complete, subquery_depth + 1);
}
/// если в настройках установлен default_sample != 1, то все запросы выполняем с сэмплингом
/// если таблица не поддерживает сэмплинг получим исключение
@ -445,13 +459,13 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
if (!query.sample_size && settings.default_sample != 1)
query.sample_size = new ASTLiteral(StringRange(NULL, NULL), Float64(settings.default_sample));
if (query.sample_size && (!table || !table->supportsSampling()))
if (query.sample_size && (!storage || !storage->supportsSampling()))
throw Exception("Illegal SAMPLE: table doesn't support sampling", ErrorCodes::SAMPLING_NOT_SUPPORTED);
if (query.final && (!table || !table->supportsFinal()))
if (query.final && (!storage || !storage->supportsFinal()))
throw Exception("Illegal FINAL", ErrorCodes::ILLEGAL_FINAL);
if (query.prewhere_expression && (!table || !table->supportsPrewhere()))
if (query.prewhere_expression && (!storage || !storage->supportsPrewhere()))
throw Exception("Illegal PREWHERE", ErrorCodes::ILLEGAL_PREWHERE);
/** При распределённой обработке запроса, в потоках почти не делается вычислений,
@ -466,7 +480,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
* и там должно быть оригинальное значение max_threads, а не увеличенное.
*/
Settings settings_for_storage = settings;
if (table && table->isRemote())
if (storage && storage->isRemote())
settings.max_threads = settings.max_distributed_connections;
/// Ограничение на количество столбцов для чтения.
@ -495,10 +509,18 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns;
/// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос?
if (!query.table || !dynamic_cast<ASTSelectQuery *>(&*query.table))
streams = table->read(required_columns, query_ptr, settings_for_storage, from_stage, settings.max_block_size, settings.max_threads);
if (!interpreter_subquery)
{
streams = storage->read(required_columns, query_ptr, settings_for_storage, from_stage, settings.max_block_size, settings.max_threads);
for (auto stream : streams)
{
stream->addTableLock(table_lock);
}
}
else
{
streams.push_back(maybeAsynchronous(interpreter_subquery->execute(), settings.asynchronous));
}
/** Если истчоников слишком много, то склеим их в max_threads источников.
* (Иначе действия в каждом маленьком источнике, а затем объединение состояний, слишком неэффективно.)
@ -510,7 +532,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
* Такие ограничения проверяются на сервере-инициаторе запроса, а не на удалённых серверах.
* Потому что сервер-инициатор имеет суммарные данные о выполнении запроса на всех серверах.
*/
if (table && to_stage == QueryProcessingStage::Complete)
if (storage && to_stage == QueryProcessingStage::Complete)
{
IProfilingBlockInputStream::LocalLimits limits;
limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL;
@ -647,7 +669,7 @@ void InterpreterSelectQuery::executeTotalsAndHaving(BlockInputStreams & streams,
}
void InterpreterSelectQuery::executeOuterExpression(BlockInputStreams & streams, ExpressionActionsPtr expression)
void InterpreterSelectQuery::executeExpression(BlockInputStreams & streams, ExpressionActionsPtr expression)
{
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)

View File

@ -168,9 +168,7 @@ void formatAST(const ASTSelectQuery & ast, std::ostream & s, size_t indent, bo
if (ast.prewhere_expression)
{
s << (hilite ? hilite_keyword : "") << nl_or_ws << indent_str << "PREWHERE " << (hilite ? hilite_none : "");
one_line
? formatAST(*ast.prewhere_expression, s, indent, hilite, one_line)
: formatExpressionListMultiline(dynamic_cast<const ASTExpressionList &>(*ast.prewhere_expression), s, indent, hilite);
formatAST(*ast.prewhere_expression, s, indent, hilite, one_line);
}
if (ast.where_expression)
@ -224,11 +222,12 @@ void formatAST(const ASTSelectQuery & ast, std::ostream & s, size_t indent, bo
void formatAST(const ASTSubquery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
{
std::string indent_str = one_line ? "" : std::string(4 * indent, ' ');
std::string nl_or_nothing = one_line ? "" : "\n";
s << nl_or_nothing << "(" << nl_or_nothing;
s << nl_or_nothing << indent_str << "(" << nl_or_nothing;
formatAST(*ast.children[0], s, indent + 1, hilite, one_line);
s << nl_or_nothing << ")";
s << nl_or_nothing << indent_str << ")";
}
void formatAST(const ASTCreateQuery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
@ -448,6 +447,10 @@ static void writeAlias(const String & name, std::ostream & s, bool hilite, bool
void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
{
/// Если есть алиас, то требуются скобки вокруг всего выражения, включая алиас. Потому что запись вида 0 AS x + 0 синтаксически некорректна.
if (need_parens && !ast.alias.empty())
s << '(';
/// Стоит ли записать эту функцию в виде оператора?
bool written = false;
if (ast.arguments && !ast.parameters)
@ -557,34 +560,34 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool
written = true;
}
}
}
if (!written && 0 == strcmp(ast.name.c_str(), "array"))
if (!written && ast.arguments->children.size() >= 1 && 0 == strcmp(ast.name.c_str(), "array"))
{
s << '[';
s << (hilite ? hilite_operator : "") << '[' << (hilite ? hilite_none : "");
for (size_t i = 0; i < ast.arguments->children.size(); ++i)
{
if (i != 0)
s << ", ";
formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
}
s << ']';
s << (hilite ? hilite_operator : "") << ']' << (hilite ? hilite_none : "");
written = true;
}
if (!written && 0 == strcmp(ast.name.c_str(), "tuple"))
if (!written && ast.arguments->children.size() >= 2 && 0 == strcmp(ast.name.c_str(), "tuple"))
{
s << '(';
s << (hilite ? hilite_operator : "") << '(' << (hilite ? hilite_none : "");
for (size_t i = 0; i < ast.arguments->children.size(); ++i)
{
if (i != 0)
s << ", ";
formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
}
s << ')';
s << (hilite ? hilite_operator : "") << ')' << (hilite ? hilite_none : "");
written = true;
}
}
}
if (!written)
{
@ -608,11 +611,18 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool
}
if (!ast.alias.empty())
{
writeAlias(ast.alias, s, hilite, one_line);
if (need_parens)
s << ')';
}
}
void formatAST(const ASTIdentifier & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
{
if (need_parens && !ast.alias.empty())
s << '(';
s << (hilite ? hilite_identifier : "");
WriteBufferFromOStream wb(s, 32);
@ -622,15 +632,26 @@ void formatAST(const ASTIdentifier & ast, std::ostream & s, size_t indent, bo
s << (hilite ? hilite_none : "");
if (!ast.alias.empty())
{
writeAlias(ast.alias, s, hilite, one_line);
if (need_parens)
s << ')';
}
}
void formatAST(const ASTLiteral & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
{
if (need_parens && !ast.alias.empty())
s << '(';
s << apply_visitor(FieldVisitorToString(), ast.value);
if (!ast.alias.empty())
{
writeAlias(ast.alias, s, hilite, one_line);
if (need_parens)
s << ')';
}
}
void formatAST(const ASTNameTypePair & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)

View File

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

View File

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

View File

@ -1,21 +1,16 @@
#include <set>
#include <boost/bind.hpp>
#include <sparsehash/dense_hash_set>
#include <sparsehash/dense_hash_map>
#include <DB/Columns/ColumnNested.h>
#include <sparsehash/dense_hash_set>
#include <DB/Storages/ITableDeclaration.h>
#include <DB/DataTypes/DataTypeNested.h>
#include <DB/Storages/IStorage.h>
#include <DB/Parsers/ASTIdentifier.h>
#include <DB/Parsers/ASTNameTypePair.h>
#include <DB/Interpreters/Context.h>
#include <boost/bind.hpp>
namespace DB
{
bool IStorage::hasRealColumn(const String &column_name) const
bool ITableDeclaration::hasRealColumn(const String &column_name) const
{
const NamesAndTypesList & real_columns = getColumnsList();
for (auto & it : real_columns)
@ -25,7 +20,7 @@ bool IStorage::hasRealColumn(const String &column_name) const
}
NameAndTypePair IStorage::getRealColumn(const String &column_name) const
NameAndTypePair ITableDeclaration::getRealColumn(const String &column_name) const
{
const NamesAndTypesList & real_columns = getColumnsList();
for (auto & it : real_columns)
@ -35,19 +30,19 @@ NameAndTypePair IStorage::getRealColumn(const String &column_name) const
}
bool IStorage::hasColumn(const String &column_name) const
bool ITableDeclaration::hasColumn(const String &column_name) const
{
return hasRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
}
NameAndTypePair IStorage::getColumn(const String &column_name) const
NameAndTypePair ITableDeclaration::getColumn(const String &column_name) const
{
return getRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
}
const DataTypePtr IStorage::getDataTypeByName(const String & column_name) const
const DataTypePtr ITableDeclaration::getDataTypeByName(const String & column_name) const
{
const NamesAndTypesList & names_and_types = getColumnsList();
for (NamesAndTypesList::const_iterator it = names_and_types.begin(); it != names_and_types.end(); ++it)
@ -58,7 +53,7 @@ const DataTypePtr IStorage::getDataTypeByName(const String & column_name) const
}
Block IStorage::getSampleBlock() const
Block ITableDeclaration::getSampleBlock() const
{
Block res;
const NamesAndTypesList & names_and_types = getColumnsList();
@ -104,7 +99,7 @@ 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();
@ -134,7 +129,7 @@ void IStorage::check(const Names & column_names) const
}
void IStorage::check(const Block & block, bool need_all) const
void ITableDeclaration::check(const Block & block, bool need_all) const
{
const NamesAndTypesList & available_columns = getColumnsList();
const NamesAndTypesMap & columns_map = getColumnsMap(available_columns);
@ -174,7 +169,6 @@ void IStorage::check(const Block & block, bool need_all) const
}
}
/// одинаковыми считаются имена, если они совпадают целиком или name_without_dot совпадает с частью имени до точки
static bool namesEqual(const String & name_without_dot, const DB::NameAndTypePair & name_type)
{
@ -182,7 +176,7 @@ static bool namesEqual(const String & name_without_dot, const DB::NameAndTypePai
return (name_with_dot == name_type.first.substr(0, name_without_dot.length() + 1) || name_without_dot == name_type.first);
}
void IStorage::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) const
void ITableDeclaration::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context)
{
if (params.type == ASTAlterQuery::ADD)
{

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/formatAST.h>
#include <DB/Interpreters/executeQuery.h>
#include <DB/Interpreters/InterpreterDropQuery.h>
#include <DB/DataStreams/copyData.h>
#include <DB/DataStreams/ConcatBlockInputStream.h>
#include <DB/DataStreams/narrowBlockInputStreams.h>
@ -85,7 +86,7 @@ BlockInputStreams StorageChunkMerger::read(
{
if (chunk_ref->source_database_name != source_database)
{
LOG_WARNING(log, "ChunkRef " + chunk_ref->getTableName() + " points to another database, ignoring");
LOG_WARNING(log, "ChunkRef " + it->first + " points to another database, ignoring");
continue;
}
if (!chunks_table_names.count(chunk_ref->source_table_name))
@ -97,7 +98,7 @@ BlockInputStreams StorageChunkMerger::read(
}
else
{
LOG_WARNING(log, "ChunkRef " + chunk_ref->getTableName() + " points to non-existing Chunks table, ignoring");
LOG_WARNING(log, "ChunkRef " + it->first + " points to non-existing Chunks table, ignoring");
}
}
}
@ -109,6 +110,14 @@ BlockInputStreams StorageChunkMerger::read(
}
}
TableLocks table_locks;
/// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем.
for (auto table : selected_tables)
{
table_locks.push_back(table->lockStructure(false));
}
BlockInputStreams res;
/// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную.
@ -130,33 +139,36 @@ BlockInputStreams StorageChunkMerger::read(
else /// Иначе, считаем допустимыми все возможные значения
virtual_columns = new OneBlockInputStream(virtual_columns_block);
std::set<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());
for (Storages::iterator it = selected_tables.begin(); it != selected_tables.end(); ++it)
for (size_t i = 0; i < selected_tables.size(); ++i)
{
if ((*it)->getName() != "Chunks" && !all_inclusive && values.find((*it)->getTableName()) == values.end())
StoragePtr table = selected_tables[i];
auto table_lock = table_locks[i];
if (table->getName() != "Chunks" && !all_inclusive && values.find(table->getTableName()) == values.end())
continue;
/// Список виртуальных столбцов, которые мы заполним сейчас и список столбцов, которые передадим дальше
Names virt_column_names, real_column_names;
for (const auto & column : column_names)
if (column == _table_column_name && (*it)->getName() != "Chunks") /// таблица Chunks сама заполняет столбец _table
if (column == _table_column_name && table->getName() != "Chunks") /// таблица Chunks сама заполняет столбец _table
virt_column_names.push_back(column);
else
real_column_names.push_back(column);
/// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой.
if (real_column_names.size() == 0)
real_column_names.push_back(ExpressionActions::getSmallestColumn((*it)->getColumnsList()));
real_column_names.push_back(ExpressionActions::getSmallestColumn(table->getColumnsList()));
ASTPtr modified_query_ast = query->clone();
/// Подменяем виртуальный столбец на его значение
if (!virt_column_names.empty())
VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, (*it)->getTableName());
VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, table->getTableName());
BlockInputStreams source_streams = (*it)->read(
BlockInputStreams source_streams = table->read(
real_column_names,
modified_query_ast,
settings,
@ -164,6 +176,11 @@ BlockInputStreams StorageChunkMerger::read(
max_block_size,
selected_tables.size() > threads ? 1 : (threads / selected_tables.size()));
for (auto & stream : source_streams)
{
stream->addTableLock(table_lock);
}
/// Добавляем в ответ вирутальные столбцы
for (const auto & virtual_column : virt_column_names)
{
@ -171,7 +188,7 @@ BlockInputStreams StorageChunkMerger::read(
{
for (auto & stream : source_streams)
{
stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, (*it)->getTableName(), _table_column_name);
stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, table->getTableName(), _table_column_name);
}
}
}
@ -336,6 +353,14 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
{
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());
NamesAndTypesListPtr required_columns = new NamesAndTypesList;
@ -387,6 +412,7 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
}
currently_written_groups.insert(new_table_full_name);
}
/// Уроним Chunks таблицу с таким именем, если она есть. Она могла остаться в результате прерванного слияния той же группы чанков.
executeQuery("DROP TABLE IF EXISTS " + new_table_full_name, context, true);
@ -395,7 +421,6 @@ bool StorageChunkMerger::mergeChunks(const Storages & 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);
@ -455,6 +480,9 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
}
/// Атомарно подменим исходные таблицы ссылками на новую.
/// При этом удалять таблицы под мьютексом контекста нельзя, пока только отцепим их.
Storages tables_to_drop;
{
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
@ -467,13 +495,13 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
std::string src_name = src_storage->getTableName();
/// Если таблицу успели удалить, ничего не делаем.
if (!context.getDatabases()[source_database].count(src_name))
if (!context.isTableExist(source_database, src_name))
continue;
/// Роняем исходную таблицу.
executeQuery("DROP TABLE " + backQuoteIfNeed(source_database) + "." + backQuoteIfNeed(src_name), context, true);
/// Отцепляем исходную таблицу. Ее данные и метаданные остаются на диске.
tables_to_drop.push_back(context.detachTable(source_database, src_name));
/// Создаем на ее месте ChunkRef
/// Создаем на ее месте ChunkRef. Это возможно только потому что у ChunkRef нет ни, ни метаданных.
try
{
context.addTable(source_database, src_name, StorageChunkRef::create(src_name, context, source_database, new_table_name, false));
@ -490,6 +518,15 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
currently_written_groups.erase(new_table_full_name);
}
/// Теперь удалим данные отцепленных таблиц.
table_locks.clear();
for (StoragePtr table : tables_to_drop)
{
InterpreterDropQuery::dropDetachedTable(source_database, table, context);
/// NOTE: Если между подменой таблицы и этой строчкой кто-то успеет попытаться создать новую таблицу на ее месте,
/// что-нибудь может сломаться.
}
/// Сейчас на new_storage ссылаются таблицы типа ChunkRef. Удалим лишнюю ссылку, которая была при создании.
new_storage.removeReference();

View File

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

View File

@ -57,7 +57,7 @@ BlockInputStreams StorageChunks::read(
Block virtual_columns_block = getBlockWithVirtualColumns();
BlockInputStreamPtr virtual_columns =
VirtualColumnUtils::getVirtualColumnsBlocks(query->clone(), virtual_columns_block, context);
std::set<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());
if (all_inclusive)

View File

@ -177,12 +177,12 @@ BlockInputStreams StorageDistributed::read(
else /// Иначе, считаем допустимыми все возможные значения
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);
bool all_inclusive = values.size() == virtual_columns_block.rows();
size_t result_size = values.size();
if (values.find(std::make_pair("localhost", clickhouse_port)) != values.end())
if (cluster.getLocalNodesNum() > 0 && values.find(std::make_pair("localhost", clickhouse_port)) != values.end())
result_size += cluster.getLocalNodesNum() - 1;
processed_stage = result_size == 1
@ -239,10 +239,6 @@ BlockInputStreams StorageDistributed::read(
}
}
/// Не дадим уничтожать объект до конца обработки запроса.
for (auto & stream : res)
stream->setOwnedStorage(thisPtr());
return res;
}

View File

@ -29,6 +29,31 @@
namespace DB
{
/** Для StorageMergeTree: достать первичный ключ в виде ASTExpressionList.
* Он может быть указан в кортеже: (CounterID, Date),
* или как один столбец: CounterID.
*/
static ASTPtr extractPrimaryKey(const ASTPtr & node, const std::string & storage_name)
{
const ASTFunction * primary_expr_func = dynamic_cast<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(
const String & name,
const String & data_path,
@ -182,17 +207,12 @@ StoragePtr StorageFactory::get(
String date_column_name = dynamic_cast<ASTIdentifier &>(*args[0]).name;
ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1];
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(
data_path, table_name, columns, context, primary_expr, date_column_name, sampling_expression, index_granularity,
name == "SummingMergeTree" ? StorageMergeTree::Summing : StorageMergeTree::Ordinary);
data_path, table_name, columns, context, primary_expr_list, date_column_name, sampling_expression, index_granularity,
name == "SummingMergeTree" ? MergeTreeData::Summing : MergeTreeData::Ordinary);
}
else if (name == "CollapsingMergeTree")
{
@ -224,17 +244,12 @@ StoragePtr StorageFactory::get(
ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1];
UInt64 index_granularity = safeGet<UInt64>(dynamic_cast<ASTLiteral &>(*args[arg_offset + 2]).value);
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")
throw Exception("Primary expression for storage CollapsingMergeTree must be in parentheses.",
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
ASTPtr primary_expr = primary_expr_func.children.at(0);
ASTPtr primary_expr_list = extractPrimaryKey(args[arg_offset + 1], name);
return StorageMergeTree::create(
data_path, table_name, columns, context, primary_expr, date_column_name,
sampling_expression, index_granularity, StorageMergeTree::Collapsing, sign_column_name);
data_path, table_name, columns, context, primary_expr_list, date_column_name,
sampling_expression, index_granularity, MergeTreeData::Collapsing, sign_column_name);
}
else if (name == "SystemNumbers")
{

View File

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

View File

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

View File

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

View File

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

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

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));
}

View File

@ -37,7 +37,7 @@ DataParts copy(const DataParts &a)
const int RowsPerSec = 100000;
StorageMergeTreeSettings settings;
MergeTreeSettings settings;
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