mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-20 14:42:02 +00:00
Merge
This commit is contained in:
commit
e9b57e989a
@ -452,41 +452,32 @@ private:
|
|||||||
|
|
||||||
for (size_t i = 0; i < col_size; ++i)
|
for (size_t i = 0; i < col_size; ++i)
|
||||||
{
|
{
|
||||||
// std::cerr << "i: " << i << std::endl;
|
|
||||||
/// Насколько размножить массив.
|
/// Насколько размножить массив.
|
||||||
size_t size_to_replicate = replicate_offsets[i] - prev_replicate_offset;
|
size_t size_to_replicate = replicate_offsets[i] - prev_replicate_offset;
|
||||||
// std::cerr << "size_to_replicate: " << size_to_replicate << std::endl;
|
|
||||||
/// Количество строк в массиве.
|
/// Количество строк в массиве.
|
||||||
size_t value_size = cur_offsets[i] - prev_cur_offset;
|
size_t value_size = cur_offsets[i] - prev_cur_offset;
|
||||||
// std::cerr << "value_size: " << value_size << std::endl;
|
|
||||||
|
|
||||||
size_t sum_chars_size = 0;
|
size_t sum_chars_size = 0;
|
||||||
|
|
||||||
for (size_t j = 0; j < size_to_replicate; ++j)
|
for (size_t j = 0; j < size_to_replicate; ++j)
|
||||||
{
|
{
|
||||||
// std::cerr << "j: " << j << std::endl;
|
|
||||||
current_res_offset += value_size;
|
current_res_offset += value_size;
|
||||||
res_offsets.push_back(current_res_offset);
|
res_offsets.push_back(current_res_offset);
|
||||||
// std::cerr << "current_res_offset: " << current_res_offset << std::endl;
|
|
||||||
|
|
||||||
sum_chars_size = 0;
|
sum_chars_size = 0;
|
||||||
|
|
||||||
size_t prev_cur_string_offset_local = prev_cur_string_offset;
|
size_t prev_cur_string_offset_local = prev_cur_string_offset;
|
||||||
for (size_t k = 0; k < value_size; ++k)
|
for (size_t k = 0; k < value_size; ++k)
|
||||||
{
|
{
|
||||||
// std::cerr << "k: " << k << std::endl;
|
|
||||||
/// Размер одной строки.
|
/// Размер одной строки.
|
||||||
size_t chars_size = cur_string_offsets[k + prev_cur_offset] - prev_cur_string_offset_local;
|
size_t chars_size = cur_string_offsets[k + prev_cur_offset] - prev_cur_string_offset_local;
|
||||||
// std::cerr << "chars_size: " << chars_size << std::endl;
|
|
||||||
|
|
||||||
current_res_string_offset += chars_size;
|
current_res_string_offset += chars_size;
|
||||||
res_string_offsets.push_back(current_res_string_offset);
|
res_string_offsets.push_back(current_res_string_offset);
|
||||||
// std::cerr << "current_res_string_offset: " << current_res_string_offset << std::endl;
|
|
||||||
|
|
||||||
/// Копирование символов одной строки.
|
/// Копирование символов одной строки.
|
||||||
res_chars.resize(res_chars.size() + chars_size);
|
res_chars.resize(res_chars.size() + chars_size);
|
||||||
memcpy(&res_chars[res_chars.size() - chars_size], &cur_chars[prev_cur_string_offset_local], chars_size);
|
memcpy(&res_chars[res_chars.size() - chars_size], &cur_chars[prev_cur_string_offset_local], chars_size);
|
||||||
// std::cerr << "copied: " << mysqlxx::escape << std::string(reinterpret_cast<const char *>(&cur_chars[prev_cur_string_offset_local]), chars_size) << std::endl;
|
|
||||||
|
|
||||||
sum_chars_size += chars_size;
|
sum_chars_size += chars_size;
|
||||||
prev_cur_string_offset_local += chars_size;
|
prev_cur_string_offset_local += chars_size;
|
||||||
|
@ -16,6 +16,9 @@ class ColumnSet : public IColumnDummy
|
|||||||
public:
|
public:
|
||||||
ColumnSet(size_t s_, SetPtr data_) : IColumnDummy(s_), data(data_) {}
|
ColumnSet(size_t s_, SetPtr data_) : IColumnDummy(s_), data(data_) {}
|
||||||
|
|
||||||
|
/// Столбец не константный. Иначе столбец будет использоваться в вычислениях в ExpressionActions::prepare, когда множество из подзапроса ещё не готово.
|
||||||
|
bool isConst() const { return false; }
|
||||||
|
|
||||||
std::string getName() const { return "ColumnSet"; }
|
std::string getName() const { return "ColumnSet"; }
|
||||||
ColumnPtr cloneDummy(size_t s_) const { return new ColumnSet(s_, data); }
|
ColumnPtr cloneDummy(size_t s_) const { return new ColumnSet(s_, data); }
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@
|
|||||||
#include <DB/Parsers/ASTExpressionList.h>
|
#include <DB/Parsers/ASTExpressionList.h>
|
||||||
#include <DB/Parsers/ASTLiteral.h>
|
#include <DB/Parsers/ASTLiteral.h>
|
||||||
#include <DB/Parsers/ASTSelectQuery.h>
|
#include <DB/Parsers/ASTSelectQuery.h>
|
||||||
#include <DB/Storages/StoragePtr.h>
|
|
||||||
#include <DB/Interpreters/InterpreterSelectQuery.h>
|
|
||||||
#include <DB/Columns/ColumnString.h>
|
#include <DB/Columns/ColumnString.h>
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
@ -36,9 +34,9 @@ BlockInputStreamPtr getVirtualColumnsBlocks(ASTPtr query, const Block & input, c
|
|||||||
|
|
||||||
/// Извлечь из входного потока множество значений столбца name
|
/// Извлечь из входного потока множество значений столбца name
|
||||||
template<typename T1>
|
template<typename T1>
|
||||||
std::set<T1> extractSingleValueFromBlocks(BlockInputStreamPtr input, const String & name)
|
std::multiset<T1> extractSingleValueFromBlocks(BlockInputStreamPtr input, const String & name)
|
||||||
{
|
{
|
||||||
std::set<T1> res;
|
std::multiset<T1> res;
|
||||||
input->readPrefix();
|
input->readPrefix();
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
@ -53,10 +51,10 @@ std::set<T1> extractSingleValueFromBlocks(BlockInputStreamPtr input, const Strin
|
|||||||
|
|
||||||
/// Извлечь из входного потока множество пар значений в столбцах first_name и second_name
|
/// Извлечь из входного потока множество пар значений в столбцах first_name и second_name
|
||||||
template<typename T1, typename T2>
|
template<typename T1, typename T2>
|
||||||
std::set< std::pair<T1, T2> > extractTwoValuesFromBlocks(BlockInputStreamPtr input,
|
std::multiset< std::pair<T1, T2> > extractTwoValuesFromBlocks(BlockInputStreamPtr input,
|
||||||
const String & first_name, const String & second_name)
|
const String & first_name, const String & second_name)
|
||||||
{
|
{
|
||||||
std::set< std::pair<T1, T2> > res;
|
std::multiset< std::pair<T1, T2> > res;
|
||||||
input->readPrefix();
|
input->readPrefix();
|
||||||
while(1)
|
while(1)
|
||||||
{
|
{
|
||||||
|
@ -225,6 +225,8 @@ namespace ErrorCodes
|
|||||||
NOT_AN_AGGREGATE,
|
NOT_AN_AGGREGATE,
|
||||||
QUERY_WITH_SAME_ID_IS_ALREADY_RUNNING,
|
QUERY_WITH_SAME_ID_IS_ALREADY_RUNNING,
|
||||||
CLIENT_HAS_CONNECTED_TO_WRONG_PORT,
|
CLIENT_HAS_CONNECTED_TO_WRONG_PORT,
|
||||||
|
TABLE_IS_DROPPED,
|
||||||
|
DATABASE_NOT_EMPTY,
|
||||||
|
|
||||||
POCO_EXCEPTION = 1000,
|
POCO_EXCEPTION = 1000,
|
||||||
STD_EXCEPTION,
|
STD_EXCEPTION,
|
||||||
|
@ -14,7 +14,7 @@ namespace DB
|
|||||||
class CollapsingFinalBlockInputStream : public IProfilingBlockInputStream
|
class CollapsingFinalBlockInputStream : public IProfilingBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CollapsingFinalBlockInputStream(BlockInputStreams inputs_, SortDescription & description_,
|
CollapsingFinalBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_,
|
||||||
const String & sign_column_)
|
const String & sign_column_)
|
||||||
: description(description_), sign_column(sign_column_),
|
: description(description_), sign_column(sign_column_),
|
||||||
log(&Logger::get("CollapsingSortedBlockInputStream")),
|
log(&Logger::get("CollapsingSortedBlockInputStream")),
|
||||||
|
@ -23,7 +23,7 @@ namespace DB
|
|||||||
class CollapsingSortedBlockInputStream : public MergingSortedBlockInputStream
|
class CollapsingSortedBlockInputStream : public MergingSortedBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CollapsingSortedBlockInputStream(BlockInputStreams inputs_, SortDescription & description_,
|
CollapsingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_,
|
||||||
const String & sign_column_, size_t max_block_size_)
|
const String & sign_column_, size_t max_block_size_)
|
||||||
: MergingSortedBlockInputStream(inputs_, description_, max_block_size_),
|
: MergingSortedBlockInputStream(inputs_, description_, max_block_size_),
|
||||||
sign_column(sign_column_), sign_column_number(0),
|
sign_column(sign_column_), sign_column_number(0),
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include <Poco/SharedPtr.h>
|
#include <Poco/SharedPtr.h>
|
||||||
|
|
||||||
#include <DB/Core/Block.h>
|
#include <DB/Core/Block.h>
|
||||||
#include <DB/Storages/StoragePtr.h>
|
#include <DB/Storages/IStorage.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
@ -30,11 +30,7 @@ public:
|
|||||||
typedef SharedPtr<IBlockInputStream> BlockInputStreamPtr;
|
typedef SharedPtr<IBlockInputStream> BlockInputStreamPtr;
|
||||||
typedef std::vector<BlockInputStreamPtr> BlockInputStreams;
|
typedef std::vector<BlockInputStreamPtr> BlockInputStreams;
|
||||||
|
|
||||||
/** Листовой BlockInputStream обычно требует, чтобы был жив какой-то Storage.
|
IBlockInputStream() {}
|
||||||
* Переданный сюда указатель на Storage будет просто храниться в этом экземпляре,
|
|
||||||
* не позволяя уничтожить Storage раньше этого BlockInputStream.
|
|
||||||
*/
|
|
||||||
IBlockInputStream(StoragePtr owned_storage_ = StoragePtr()) : owned_storage(owned_storage_) {}
|
|
||||||
|
|
||||||
/** Прочитать следующий блок.
|
/** Прочитать следующий блок.
|
||||||
* Если блоков больше нет - вернуть пустой блок (для которого operator bool возвращает false).
|
* Если блоков больше нет - вернуть пустой блок (для которого operator bool возвращает false).
|
||||||
@ -80,10 +76,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
size_t checkDepth(size_t max_depth) const;
|
size_t checkDepth(size_t max_depth) const;
|
||||||
|
|
||||||
void setOwnedStorage(StoragePtr owned_storage_) { owned_storage = owned_storage_; }
|
/** Не давать изменить таблицу, пока жив поток блоков.
|
||||||
|
*/
|
||||||
|
void addTableLock(const IStorage::TableStructureReadLockPtr & lock) { table_locks.push_back(lock); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
StoragePtr owned_storage;
|
IStorage::TableStructureReadLocks table_locks;
|
||||||
|
|
||||||
BlockInputStreams children;
|
BlockInputStreams children;
|
||||||
|
|
||||||
@ -99,8 +97,5 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
typedef IBlockInputStream::BlockInputStreamPtr BlockInputStreamPtr;
|
|
||||||
typedef IBlockInputStream::BlockInputStreams BlockInputStreams;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include <DB/Core/Block.h>
|
#include <DB/Core/Block.h>
|
||||||
#include <DB/Core/Row.h>
|
#include <DB/Core/Row.h>
|
||||||
#include <DB/Storages/StoragePtr.h>
|
#include <DB/Storages/IStorage.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
@ -21,7 +21,7 @@ class IBlockOutputStream : private boost::noncopyable
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
IBlockOutputStream(StoragePtr owned_storage_ = StoragePtr()) : owned_storage(owned_storage_) {}
|
IBlockOutputStream() {}
|
||||||
|
|
||||||
/** Записать блок.
|
/** Записать блок.
|
||||||
*/
|
*/
|
||||||
@ -40,10 +40,12 @@ public:
|
|||||||
|
|
||||||
virtual ~IBlockOutputStream() {}
|
virtual ~IBlockOutputStream() {}
|
||||||
|
|
||||||
|
/** Не давать изменить таблицу, пока жив поток блоков.
|
||||||
|
*/
|
||||||
|
void addTableLock(const IStorage::TableStructureReadLockPtr & lock) { table_locks.push_back(lock); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
StoragePtr owned_storage;
|
IStorage::TableStructureReadLocks table_locks;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef SharedPtr<IBlockOutputStream> BlockOutputStreamPtr;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -78,8 +78,8 @@ private:
|
|||||||
class IProfilingBlockInputStream : public IBlockInputStream
|
class IProfilingBlockInputStream : public IBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IProfilingBlockInputStream(StoragePtr owned_storage_ = StoragePtr())
|
IProfilingBlockInputStream()
|
||||||
: IBlockInputStream(owned_storage_), is_cancelled(false), process_list_elem(NULL),
|
: is_cancelled(false), process_list_elem(NULL),
|
||||||
enabled_extremes(false), quota(NULL), prev_elapsed(0) {}
|
enabled_extremes(false), quota(NULL), prev_elapsed(0) {}
|
||||||
|
|
||||||
Block read();
|
Block read();
|
||||||
|
@ -19,7 +19,7 @@ class MergingSortedBlockInputStream : public IProfilingBlockInputStream
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
|
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
|
||||||
MergingSortedBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, size_t max_block_size_, size_t limit_ = 0)
|
MergingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_, size_t limit_ = 0)
|
||||||
: description(description_), max_block_size(max_block_size_), limit(limit_), total_merged_rows(0), first(true), has_collation(false),
|
: description(description_), max_block_size(max_block_size_), limit(limit_), total_merged_rows(0), first(true), has_collation(false),
|
||||||
num_columns(0), source_blocks(inputs_.size()), cursors(inputs_.size()), log(&Logger::get("MergingSortedBlockInputStream"))
|
num_columns(0), source_blocks(inputs_.size()), cursors(inputs_.size()), log(&Logger::get("MergingSortedBlockInputStream"))
|
||||||
{
|
{
|
||||||
|
@ -23,7 +23,10 @@ public:
|
|||||||
{
|
{
|
||||||
if (database.empty())
|
if (database.empty())
|
||||||
database = context.getCurrentDatabase();
|
database = context.getCurrentDatabase();
|
||||||
|
|
||||||
storage = context.getTable(database, table);
|
storage = context.getTable(database, table);
|
||||||
|
addTableLock(storage->lockStructure(true));
|
||||||
|
|
||||||
Dependencies dependencies = context.getDependencies(DatabaseAndTableName(database, table));
|
Dependencies dependencies = context.getDependencies(DatabaseAndTableName(database, table));
|
||||||
for (size_t i = 0; i < dependencies.size(); ++i)
|
for (size_t i = 0; i < dependencies.size(); ++i)
|
||||||
{
|
{
|
||||||
|
@ -18,7 +18,7 @@ namespace DB
|
|||||||
class SummingSortedBlockInputStream : public MergingSortedBlockInputStream
|
class SummingSortedBlockInputStream : public MergingSortedBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SummingSortedBlockInputStream(BlockInputStreams inputs_, SortDescription & description_, size_t max_block_size_)
|
SummingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_)
|
||||||
: MergingSortedBlockInputStream(inputs_, description_, max_block_size_),
|
: MergingSortedBlockInputStream(inputs_, description_, max_block_size_),
|
||||||
log(&Logger::get("SummingSortedBlockInputStream")), current_row_is_zero(false)
|
log(&Logger::get("SummingSortedBlockInputStream")), current_row_is_zero(false)
|
||||||
{
|
{
|
||||||
|
@ -20,12 +20,12 @@ namespace DB
|
|||||||
*/
|
*/
|
||||||
struct Memory : boost::noncopyable
|
struct Memory : boost::noncopyable
|
||||||
{
|
{
|
||||||
size_t m_capacity;
|
size_t m_capacity = 0;
|
||||||
size_t m_size;
|
size_t m_size = 0;
|
||||||
char * m_data;
|
char * m_data = nullptr;
|
||||||
size_t alignment;
|
size_t alignment = 0;
|
||||||
|
|
||||||
Memory() : m_capacity(0), m_size(0), m_data(NULL), alignment(0) {}
|
Memory() {}
|
||||||
|
|
||||||
/// Если alignment != 0, то будет выделяться память, выровненная на alignment.
|
/// Если alignment != 0, то будет выделяться память, выровненная на alignment.
|
||||||
Memory(size_t size_, size_t alignment_ = 0) : m_capacity(size_), m_size(m_capacity), alignment(alignment_)
|
Memory(size_t size_, size_t alignment_ = 0) : m_capacity(size_), m_size(m_capacity), alignment(alignment_)
|
||||||
@ -38,6 +38,21 @@ struct Memory : boost::noncopyable
|
|||||||
dealloc();
|
dealloc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Memory(Memory && rhs)
|
||||||
|
{
|
||||||
|
*this = std::move(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Memory & operator=(Memory && rhs)
|
||||||
|
{
|
||||||
|
std::swap(m_capacity, rhs.m_capacity);
|
||||||
|
std::swap(m_size, rhs.m_size);
|
||||||
|
std::swap(m_data, rhs.m_data);
|
||||||
|
std::swap(alignment, rhs.alignment);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
size_t size() const { return m_size; }
|
size_t size() const { return m_size; }
|
||||||
const char & operator[](size_t i) const { return m_data[i]; }
|
const char & operator[](size_t i) const { return m_data[i]; }
|
||||||
char & operator[](size_t i) { return m_data[i]; }
|
char & operator[](size_t i) { return m_data[i]; }
|
||||||
|
@ -41,9 +41,6 @@ typedef std::map<String, StoragePtr> Tables;
|
|||||||
/// имя БД -> таблицы
|
/// имя БД -> таблицы
|
||||||
typedef std::map<String, Tables> Databases;
|
typedef std::map<String, Tables> Databases;
|
||||||
|
|
||||||
/// имя БД -> dropper
|
|
||||||
typedef std::map<String, DatabaseDropperPtr> DatabaseDroppers;
|
|
||||||
|
|
||||||
/// (имя базы данных, имя таблицы)
|
/// (имя базы данных, имя таблицы)
|
||||||
typedef std::pair<String, String> DatabaseAndTableName;
|
typedef std::pair<String, String> DatabaseAndTableName;
|
||||||
|
|
||||||
@ -76,7 +73,6 @@ struct ContextShared
|
|||||||
|
|
||||||
String path; /// Путь к директории с данными, со слешем на конце.
|
String path; /// Путь к директории с данными, со слешем на конце.
|
||||||
Databases databases; /// Список БД и таблиц в них.
|
Databases databases; /// Список БД и таблиц в них.
|
||||||
DatabaseDroppers database_droppers; /// Reference counter'ы для ленивого удаления БД.
|
|
||||||
TableFunctionFactory table_function_factory; /// Табличные функции.
|
TableFunctionFactory table_function_factory; /// Табличные функции.
|
||||||
FunctionFactory function_factory; /// Обычные функции.
|
FunctionFactory function_factory; /// Обычные функции.
|
||||||
AggregateFunctionFactory aggregate_function_factory; /// Агрегатные функции.
|
AggregateFunctionFactory aggregate_function_factory; /// Агрегатные функции.
|
||||||
@ -137,7 +133,6 @@ struct ContextShared
|
|||||||
|
|
||||||
{
|
{
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(mutex);
|
Poco::ScopedLock<Poco::Mutex> lock(mutex);
|
||||||
database_droppers.clear();
|
|
||||||
current_databases = databases;
|
current_databases = databases;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,8 +227,6 @@ public:
|
|||||||
String getDefaultFormat() const; /// Если default_format не задан - возвращается некоторый глобальный формат по-умолчанию.
|
String getDefaultFormat() const; /// Если default_format не задан - возвращается некоторый глобальный формат по-умолчанию.
|
||||||
void setDefaultFormat(const String & name);
|
void setDefaultFormat(const String & name);
|
||||||
|
|
||||||
DatabaseDropperPtr getDatabaseDropper(const String & name);
|
|
||||||
|
|
||||||
Settings getSettings() const;
|
Settings getSettings() const;
|
||||||
void setSettings(const Settings & settings_);
|
void setSettings(const Settings & settings_);
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ namespace DB
|
|||||||
{
|
{
|
||||||
|
|
||||||
/** Превращает выражение из синтаксического дерева в последовательность действий для его выполнения.
|
/** Превращает выражение из синтаксического дерева в последовательность действий для его выполнения.
|
||||||
|
*
|
||||||
|
* NOTE: если ast - запрос SELECT из таблицы, структура этой таблицы не должна меняться во все время жизни ExpressionAnalyzer-а.
|
||||||
*/
|
*/
|
||||||
class ExpressionAnalyzer : private boost::noncopyable
|
class ExpressionAnalyzer : private boost::noncopyable
|
||||||
{
|
{
|
||||||
|
@ -76,12 +76,9 @@ private:
|
|||||||
NamesAndTypesList columns;
|
NamesAndTypesList columns;
|
||||||
|
|
||||||
{
|
{
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
StoragePtr table = context.getTable(ast.database, ast.table);
|
||||||
|
auto table_lock = table->lockStructure(false);
|
||||||
if (!context.isTableExist(ast.database, ast.table))
|
columns = table->getColumnsList();
|
||||||
throw Exception("Table " + (ast.database.empty() ? "" : ast.database + ".") + ast.table + " doesn't exist", ErrorCodes::UNKNOWN_TABLE);
|
|
||||||
|
|
||||||
columns = context.getTable(ast.database, ast.table)->getColumnsList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnString * name_column = new ColumnString;
|
ColumnString * name_column = new ColumnString;
|
||||||
|
@ -18,6 +18,9 @@ public:
|
|||||||
/// Удаляет таблицу.
|
/// Удаляет таблицу.
|
||||||
void execute();
|
void execute();
|
||||||
|
|
||||||
|
/// Удаляет таблицу, уже отцепленную от контекста (Context::detach).
|
||||||
|
static void dropDetachedTable(String database_name, StoragePtr table, Context & context);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ASTPtr query_ptr;
|
ASTPtr query_ptr;
|
||||||
Context context;
|
Context context;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <DB/DataStreams/IBlockOutputStream.h>
|
#include <DB/DataStreams/IBlockOutputStream.h>
|
||||||
|
#include <DB/DataStreams/BlockIO.h>
|
||||||
#include <DB/Interpreters/Context.h>
|
#include <DB/Interpreters/Context.h>
|
||||||
|
|
||||||
|
|
||||||
@ -24,13 +25,13 @@ public:
|
|||||||
/** Подготовить запрос к выполнению. Вернуть поток блоков, в который можно писать данные для выполнения запроса.
|
/** Подготовить запрос к выполнению. Вернуть поток блоков, в который можно писать данные для выполнения запроса.
|
||||||
* Или вернуть NULL, если запрос INSERT SELECT (самодостаточный запрос - не принимает входные данные).
|
* Или вернуть NULL, если запрос INSERT SELECT (самодостаточный запрос - не принимает входные данные).
|
||||||
*/
|
*/
|
||||||
BlockOutputStreamPtr execute();
|
BlockIO execute();
|
||||||
|
|
||||||
Block getSampleBlock();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
StoragePtr getTable();
|
StoragePtr getTable();
|
||||||
|
|
||||||
|
Block getSampleBlock();
|
||||||
|
|
||||||
ASTPtr query_ptr;
|
ASTPtr query_ptr;
|
||||||
Context context;
|
Context context;
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,9 @@ public:
|
|||||||
void execute()
|
void execute()
|
||||||
{
|
{
|
||||||
const ASTOptimizeQuery & ast = dynamic_cast<const ASTOptimizeQuery &>(*query_ptr);
|
const ASTOptimizeQuery & ast = dynamic_cast<const ASTOptimizeQuery &>(*query_ptr);
|
||||||
context.getTable(ast.database, ast.table)->optimize();
|
StoragePtr table = context.getTable(ast.database, ast.table);
|
||||||
|
auto table_lock = table->lockStructure(true);
|
||||||
|
table->optimize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -34,10 +34,6 @@ public:
|
|||||||
DataTypes getReturnTypes();
|
DataTypes getReturnTypes();
|
||||||
Block getSampleBlock();
|
Block getSampleBlock();
|
||||||
|
|
||||||
/** Получить CREATE запрос для таблицы, из которой идёт выбор.
|
|
||||||
*/
|
|
||||||
ASTPtr getCreateQuery();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef Poco::SharedPtr<ExpressionAnalyzer> ExpressionAnalyzerPtr;
|
typedef Poco::SharedPtr<ExpressionAnalyzer> ExpressionAnalyzerPtr;
|
||||||
|
|
||||||
@ -47,8 +43,6 @@ private:
|
|||||||
*/
|
*/
|
||||||
void getDatabaseAndTableNames(String & database_name, String & table_name);
|
void getDatabaseAndTableNames(String & database_name, String & table_name);
|
||||||
|
|
||||||
StoragePtr getTable();
|
|
||||||
|
|
||||||
/** Выбрать из списка столбцов какой-нибудь, лучше - минимального размера.
|
/** Выбрать из списка столбцов какой-нибудь, лучше - минимального размера.
|
||||||
*/
|
*/
|
||||||
String getAnyColumn();
|
String getAnyColumn();
|
||||||
@ -65,7 +59,7 @@ private:
|
|||||||
void executeTotalsAndHaving( BlockInputStreams & streams, bool has_having, ExpressionActionsPtr expression,
|
void executeTotalsAndHaving( BlockInputStreams & streams, bool has_having, ExpressionActionsPtr expression,
|
||||||
bool overflow_row);
|
bool overflow_row);
|
||||||
void executeHaving( BlockInputStreams & streams, ExpressionActionsPtr expression);
|
void executeHaving( BlockInputStreams & streams, ExpressionActionsPtr expression);
|
||||||
void executeOuterExpression( BlockInputStreams & streams, ExpressionActionsPtr expression);
|
void executeExpression( BlockInputStreams & streams, ExpressionActionsPtr expression);
|
||||||
void executeOrder( BlockInputStreams & streams);
|
void executeOrder( BlockInputStreams & streams);
|
||||||
void executePreLimit( BlockInputStreams & streams);
|
void executePreLimit( BlockInputStreams & streams);
|
||||||
void executeUnion( BlockInputStreams & streams);
|
void executeUnion( BlockInputStreams & streams);
|
||||||
@ -83,7 +77,10 @@ private:
|
|||||||
size_t subquery_depth;
|
size_t subquery_depth;
|
||||||
ExpressionAnalyzerPtr query_analyzer;
|
ExpressionAnalyzerPtr query_analyzer;
|
||||||
BlockInputStreams streams;
|
BlockInputStreams streams;
|
||||||
StoragePtr table_function_storage;
|
|
||||||
|
/// Таблица, откуда читать данные, если не подзапрос.
|
||||||
|
StoragePtr storage;
|
||||||
|
IStorage::TableStructureReadLockPtr table_lock;
|
||||||
|
|
||||||
Logger * log;
|
Logger * log;
|
||||||
};
|
};
|
||||||
|
@ -68,18 +68,9 @@ private:
|
|||||||
{
|
{
|
||||||
const ASTShowCreateQuery & ast = dynamic_cast<const ASTShowCreateQuery &>(*query_ptr);
|
const ASTShowCreateQuery & ast = dynamic_cast<const ASTShowCreateQuery &>(*query_ptr);
|
||||||
|
|
||||||
String res;
|
|
||||||
|
|
||||||
{
|
|
||||||
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;
|
std::stringstream stream;
|
||||||
formatAST(*context.getCreateQuery(ast.database, ast.table), stream, 0, false, true);
|
formatAST(*context.getCreateQuery(ast.database, ast.table), stream, 0, false, true);
|
||||||
res = stream.str();
|
String res = stream.str();
|
||||||
}
|
|
||||||
|
|
||||||
ColumnWithNameAndType col;
|
ColumnWithNameAndType col;
|
||||||
col.name = "statement";
|
col.name = "statement";
|
||||||
|
@ -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;
|
|
@ -7,20 +7,24 @@
|
|||||||
#include <DB/Core/NamesAndTypes.h>
|
#include <DB/Core/NamesAndTypes.h>
|
||||||
#include <DB/Core/Exception.h>
|
#include <DB/Core/Exception.h>
|
||||||
#include <DB/Core/QueryProcessingStage.h>
|
#include <DB/Core/QueryProcessingStage.h>
|
||||||
#include <DB/DataStreams/IBlockInputStream.h>
|
|
||||||
#include <DB/DataStreams/IBlockOutputStream.h>
|
|
||||||
#include <DB/Parsers/IAST.h>
|
#include <DB/Parsers/IAST.h>
|
||||||
#include <DB/Parsers/ASTAlterQuery.h>
|
#include <DB/Parsers/ASTAlterQuery.h>
|
||||||
#include <DB/Interpreters/Settings.h>
|
#include <DB/Interpreters/Settings.h>
|
||||||
#include <DB/Storages/StoragePtr.h>
|
#include <DB/Storages/ITableDeclaration.h>
|
||||||
#include "DatabaseDropper.h"
|
|
||||||
#include <Poco/File.h>
|
#include <Poco/File.h>
|
||||||
|
#include <Poco/RWLock.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
class Context;
|
class Context;
|
||||||
|
class IBlockInputStream;
|
||||||
|
class IBlockOutputStream;
|
||||||
|
|
||||||
|
typedef SharedPtr<IBlockOutputStream> BlockOutputStreamPtr;
|
||||||
|
typedef SharedPtr<IBlockInputStream> BlockInputStreamPtr;
|
||||||
|
typedef std::vector<BlockInputStreamPtr> BlockInputStreams;
|
||||||
|
|
||||||
|
|
||||||
/** Хранилище. Отвечает за:
|
/** Хранилище. Отвечает за:
|
||||||
@ -30,41 +34,12 @@ class Context;
|
|||||||
* - структура хранения данных (сжатие, etc.)
|
* - структура хранения данных (сжатие, etc.)
|
||||||
* - конкуррентный доступ к данным (блокировки, etc.)
|
* - конкуррентный доступ к данным (блокировки, etc.)
|
||||||
*/
|
*/
|
||||||
class IStorage : private boost::noncopyable
|
class IStorage : private boost::noncopyable, public ITableDeclaration
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Основное имя типа таблицы (например, StorageWithoutKey).
|
/// Основное имя типа таблицы (например, StorageMergeTree).
|
||||||
virtual std::string getName() const = 0;
|
virtual std::string getName() const = 0;
|
||||||
|
|
||||||
/// Имя самой таблицы (например, hits)
|
|
||||||
virtual std::string getTableName() const = 0;
|
|
||||||
|
|
||||||
/** Получить список имён и типов столбцов таблицы, только невиртуальные.
|
|
||||||
*/
|
|
||||||
virtual const NamesAndTypesList & getColumnsList() const = 0;
|
|
||||||
|
|
||||||
/** Получить описание реального (невиртуального) столбца по его имени.
|
|
||||||
*/
|
|
||||||
virtual NameAndTypePair getRealColumn(const String & column_name) const;
|
|
||||||
|
|
||||||
/** Присутствует ли реальный (невиртуальный) столбец с таким именем.
|
|
||||||
*/
|
|
||||||
virtual bool hasRealColumn(const String & column_name) const;
|
|
||||||
|
|
||||||
/** Получить описание любого столбца по его имени.
|
|
||||||
*/
|
|
||||||
virtual NameAndTypePair getColumn(const String & column_name) const;
|
|
||||||
|
|
||||||
/** Присутствует ли столбец с таким именем.
|
|
||||||
*/
|
|
||||||
virtual bool hasColumn(const String & column_name) const;
|
|
||||||
|
|
||||||
const DataTypePtr getDataTypeByName(const String & column_name) const;
|
|
||||||
|
|
||||||
/** То же самое, но в виде блока-образца.
|
|
||||||
*/
|
|
||||||
Block getSampleBlock() const;
|
|
||||||
|
|
||||||
/** Возвращает true, если хранилище получает данные с удалённого сервера или серверов.
|
/** Возвращает true, если хранилище получает данные с удалённого сервера или серверов.
|
||||||
*/
|
*/
|
||||||
virtual bool isRemote() const { return false; }
|
virtual bool isRemote() const { return false; }
|
||||||
@ -81,6 +56,76 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual bool supportsPrewhere() const { return false; }
|
virtual bool supportsPrewhere() const { return false; }
|
||||||
|
|
||||||
|
/** Не дает изменять описание таблицы (в том числе переименовывать и удалять таблицу).
|
||||||
|
* Если в течение какой-то операции структура таблицы должна оставаться неизменной, нужно держать такой лок на все ее время.
|
||||||
|
* Например, нужно держать такой лок на время всего запроса SELECT или INSERT и на все время слияния набора кусков
|
||||||
|
* (но между выбором кусков для слияния и их слиянием структура таблицы может измениться).
|
||||||
|
* NOTE: Это лок на "чтение" описания таблицы. Чтобы изменить описание таблицы, нужно взять TableStructureWriteLock.
|
||||||
|
*/
|
||||||
|
class TableStructureReadLock
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
friend class IStorage;
|
||||||
|
|
||||||
|
/// Порядок важен.
|
||||||
|
Poco::SharedPtr<Poco::ScopedReadRWLock> data_lock;
|
||||||
|
Poco::SharedPtr<Poco::ScopedReadRWLock> structure_lock;
|
||||||
|
|
||||||
|
TableStructureReadLock(IStorage & storage, bool lock_structure, bool lock_data)
|
||||||
|
: data_lock(lock_data ? new Poco::ScopedReadRWLock(storage. data_lock) : nullptr),
|
||||||
|
structure_lock(lock_structure ? new Poco::ScopedReadRWLock(storage.structure_lock) : nullptr) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef Poco::SharedPtr<TableStructureReadLock> TableStructureReadLockPtr;
|
||||||
|
typedef std::vector<TableStructureReadLockPtr> TableStructureReadLocks;
|
||||||
|
|
||||||
|
/** Не дает изменять структуру или имя таблицы.
|
||||||
|
* Если в рамках этого лока будут изменены данные в таблице, нужно указать will_modify_data=true.
|
||||||
|
* Это возьмет дополнительный лок, не позволяющий начать ALTER MODIFY.
|
||||||
|
*
|
||||||
|
* WARNING: Вызывать методы из ITableDeclaration нужно под такой блокировкой. Без нее они не thread safe.
|
||||||
|
* WARNING: Чтобы не было дедлоков, нельзя вызывать это метод при захваченном мьютексе в Context.
|
||||||
|
*/
|
||||||
|
TableStructureReadLockPtr lockStructure(bool will_modify_data)
|
||||||
|
{
|
||||||
|
TableStructureReadLockPtr res = new TableStructureReadLock(*this, true, will_modify_data);
|
||||||
|
if (is_dropped)
|
||||||
|
throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Poco::SharedPtr<Poco::ScopedWriteRWLock> TableStructureWriteLockPtr;
|
||||||
|
typedef Poco::SharedPtr<Poco::ScopedWriteRWLock> TableDataWriteLockPtr;
|
||||||
|
typedef std::pair<TableDataWriteLockPtr, TableStructureWriteLockPtr> TableFullWriteLockPtr;
|
||||||
|
|
||||||
|
/** Не дает читать структуру таблицы. Берется для ALTER, RENAME и DROP.
|
||||||
|
*/
|
||||||
|
TableFullWriteLockPtr lockForAlter()
|
||||||
|
{
|
||||||
|
return std::make_pair(lockDataForAlter(), lockStructureForAlter());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Не дает изменять данные в таблице. (Более того, не дает посмотреть на структуру таблицы с намерением изменить данные).
|
||||||
|
* Берется на время записи временных данных в ALTER MODIFY.
|
||||||
|
* Под этим локом можно брать lockStructureForAlter(), чтобы изменить структуру таблицы.
|
||||||
|
*/
|
||||||
|
TableDataWriteLockPtr lockDataForAlter()
|
||||||
|
{
|
||||||
|
TableDataWriteLockPtr res = new Poco::ScopedWriteRWLock(data_lock);
|
||||||
|
if (is_dropped)
|
||||||
|
throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableStructureWriteLockPtr lockStructureForAlter()
|
||||||
|
{
|
||||||
|
TableStructureWriteLockPtr res = new Poco::ScopedWriteRWLock(structure_lock);
|
||||||
|
if (is_dropped)
|
||||||
|
throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Читать набор столбцов из таблицы.
|
/** Читать набор столбцов из таблицы.
|
||||||
* Принимает список столбцов, которых нужно прочитать, а также описание запроса,
|
* Принимает список столбцов, которых нужно прочитать, а также описание запроса,
|
||||||
* из которого может быть извлечена информация о том, каким способом извлекать данные
|
* из которого может быть извлечена информация о том, каким способом извлекать данные
|
||||||
@ -97,6 +142,8 @@ public:
|
|||||||
*
|
*
|
||||||
* threads - рекомендация, сколько потоков возвращать,
|
* threads - рекомендация, сколько потоков возвращать,
|
||||||
* если хранилище может возвращать разное количество потоков.
|
* если хранилище может возвращать разное количество потоков.
|
||||||
|
*
|
||||||
|
* Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP).
|
||||||
*/
|
*/
|
||||||
virtual BlockInputStreams read(
|
virtual BlockInputStreams read(
|
||||||
const Names & column_names,
|
const Names & column_names,
|
||||||
@ -112,6 +159,8 @@ public:
|
|||||||
/** Пишет данные в таблицу.
|
/** Пишет данные в таблицу.
|
||||||
* Принимает описание запроса, в котором может содержаться информация о методе записи данных.
|
* Принимает описание запроса, в котором может содержаться информация о методе записи данных.
|
||||||
* Возвращает объект, с помощью которого можно последовательно писать данные.
|
* Возвращает объект, с помощью которого можно последовательно писать данные.
|
||||||
|
*
|
||||||
|
* Гарантируется, что структура таблицы не изменится за время жизни возвращенных потоков (то есть не будет ALTER, RENAME и DROP).
|
||||||
*/
|
*/
|
||||||
virtual BlockOutputStreamPtr write(
|
virtual BlockOutputStreamPtr write(
|
||||||
ASTPtr query)
|
ASTPtr query)
|
||||||
@ -119,21 +168,15 @@ public:
|
|||||||
throw Exception("Method write is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
|
throw Exception("Method write is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Удалить данные таблицы. После вызова этого метода, использование объекта некорректно (его можно лишь уничтожить).
|
/** Удалить данные таблицы. Вызывается перед удалением директории с данными.
|
||||||
*/
|
|
||||||
void drop()
|
|
||||||
{
|
|
||||||
drop_on_destroy = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Вызывается перед удалением директории с данными и вызовом деструктора.
|
|
||||||
* Если не требуется никаких действий, кроме удаления директории с данными, этот метод можно оставить пустым.
|
* Если не требуется никаких действий, кроме удаления директории с данными, этот метод можно оставить пустым.
|
||||||
*/
|
*/
|
||||||
virtual void dropImpl() {}
|
virtual void drop() {}
|
||||||
|
|
||||||
/** Переименовать таблицу.
|
/** Переименовать таблицу.
|
||||||
* Переименование имени в файле с метаданными, имени в списке таблиц в оперативке, осуществляется отдельно.
|
* Переименование имени в файле с метаданными, имени в списке таблиц в оперативке, осуществляется отдельно.
|
||||||
* В этой функции нужно переименовать директорию с данными, если она есть.
|
* В этой функции нужно переименовать директорию с данными, если она есть.
|
||||||
|
* Вызывается при заблокированной на запись структуре таблицы.
|
||||||
*/
|
*/
|
||||||
virtual void rename(const String & new_path_to_db, const String & new_name)
|
virtual void rename(const String & new_path_to_db, const String & new_name)
|
||||||
{
|
{
|
||||||
@ -142,12 +185,29 @@ public:
|
|||||||
|
|
||||||
/** ALTER таблицы в виде изменения столбцов, не затрагивающий изменение Storage или его параметров.
|
/** ALTER таблицы в виде изменения столбцов, не затрагивающий изменение Storage или его параметров.
|
||||||
* (ALTER, затрагивающий изменение движка, делается внешним кодом, путём копирования данных.)
|
* (ALTER, затрагивающий изменение движка, делается внешним кодом, путём копирования данных.)
|
||||||
|
* Вызывается при заблокированной на запись структуре таблицы.
|
||||||
|
* Для ALTER MODIFY можно использовать другие методы (см. ниже).
|
||||||
*/
|
*/
|
||||||
virtual void alter(const ASTAlterQuery::Parameters & params)
|
virtual void alter(const ASTAlterQuery::Parameters & params)
|
||||||
{
|
{
|
||||||
throw Exception("Method alter is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
|
throw Exception("Method alter is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ALTER MODIFY (изменение типа столбца) выполняется в два вызова:
|
||||||
|
* Сначала вызывается prepareAlterModify при заблокированной записи данных, но незаблокированной структуре таблицы.
|
||||||
|
* В нем можно выполнить долгую работу по записи сконвертированных данных, оставляя доступными существующие данные.
|
||||||
|
* Потом вызывается commitAlterModify при заблокированной структуре таблицы.
|
||||||
|
* В нем нужно закончить изменение типа столбца.
|
||||||
|
* Для движков с тривиальным ALTER MODIFY можно оставить реализацию по умолчанию, вызывающую alter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
virtual void prepareAlterModify(const ASTAlterQuery::Parameters & params) {}
|
||||||
|
|
||||||
|
virtual void commitAlterModify(const ASTAlterQuery::Parameters & params)
|
||||||
|
{
|
||||||
|
alter(params);
|
||||||
|
}
|
||||||
|
|
||||||
/** Выполнить какую-либо фоновую работу. Например, объединение кусков в таблице типа MergeTree.
|
/** Выполнить какую-либо фоновую работу. Например, объединение кусков в таблице типа MergeTree.
|
||||||
* Возвращает - была ли выполнена какая-либо работа.
|
* Возвращает - была ли выполнена какая-либо работа.
|
||||||
*/
|
*/
|
||||||
@ -169,56 +229,56 @@ public:
|
|||||||
/** Если при уничтожении объекта надо сделать какую-то сложную работу - сделать её заранее.
|
/** Если при уничтожении объекта надо сделать какую-то сложную работу - сделать её заранее.
|
||||||
* Например, если таблица содержит какие-нибудь потоки для фоновой работы - попросить их завершиться и дождаться завершения.
|
* Например, если таблица содержит какие-нибудь потоки для фоновой работы - попросить их завершиться и дождаться завершения.
|
||||||
* По-умолчанию - ничего не делать.
|
* По-умолчанию - ничего не делать.
|
||||||
|
* Может вызываться одновременно из разных потоков, даже после вызова drop().
|
||||||
*/
|
*/
|
||||||
virtual void shutdown() {}
|
virtual void shutdown() {}
|
||||||
|
|
||||||
virtual ~IStorage() {}
|
|
||||||
|
|
||||||
/** Проверить, что все запрошенные имена есть в таблице и заданы корректно.
|
|
||||||
* (список имён не пустой и имена не повторяются)
|
|
||||||
*/
|
|
||||||
void check(const Names & column_names) const;
|
|
||||||
|
|
||||||
/** Проверить, что блок с данными для записи содержит все столбцы таблицы с правильными типами,
|
|
||||||
* содержит только столбцы таблицы, и все столбцы различны.
|
|
||||||
* Если need_all, еще проверяет, что все столбцы таблицы есть в блоке.
|
|
||||||
*/
|
|
||||||
void check(const Block & block, bool need_all = false) const;
|
|
||||||
|
|
||||||
/** Возвращает владеющий указатель на себя.
|
/** Возвращает владеющий указатель на себя.
|
||||||
*/
|
*/
|
||||||
StoragePtr thisPtr()
|
std::shared_ptr<IStorage> thisPtr()
|
||||||
{
|
{
|
||||||
if (!this_ptr.lock())
|
std::shared_ptr<IStorage> res = this_ptr.lock();
|
||||||
|
if (!res)
|
||||||
{
|
{
|
||||||
boost::shared_ptr<StoragePtr::Wrapper> p(new StoragePtr::Wrapper(this));
|
res.reset(this);
|
||||||
this_ptr = p;
|
this_ptr = res;
|
||||||
return StoragePtr(this_ptr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return StoragePtr(this_ptr);
|
|
||||||
}
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Не дает удалить БД до удаления таблицы. Присваивается перед удалением таблицы или БД.
|
bool is_dropped;
|
||||||
*/
|
|
||||||
DatabaseDropperPtr database_to_drop;
|
|
||||||
|
|
||||||
bool drop_on_destroy;
|
|
||||||
|
|
||||||
/** Директория с данными. Будет удалена после удаления таблицы (после вызова dropImpl).
|
|
||||||
*/
|
|
||||||
std::string path_to_remove_on_drop;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
IStorage() : drop_on_destroy(false) {}
|
IStorage() : is_dropped(false) {}
|
||||||
|
|
||||||
/// реализация alter, модифицирующая список столбцов.
|
|
||||||
void alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) const;
|
|
||||||
private:
|
private:
|
||||||
boost::weak_ptr<StoragePtr::Wrapper> this_ptr;
|
std::weak_ptr<IStorage> this_ptr;
|
||||||
|
|
||||||
|
/// Брать следующие два лока всегда нужно в этом порядке.
|
||||||
|
|
||||||
|
/** Берется на чтение на все время запроса INSERT и на все время слияния кусков (для MergeTree).
|
||||||
|
* Берется на запись на все время ALTER MODIFY.
|
||||||
|
*
|
||||||
|
* Формально:
|
||||||
|
* Ввзятие на запись гарантирует, что:
|
||||||
|
* 1) данные в таблице не изменится, пока лок жив,
|
||||||
|
* 2) все изменения данных после отпускания лока будут основаны на структуре таблицы на момент после отпускания лока.
|
||||||
|
* Нужно брать на чтение на все время операции, изменяющей данные.
|
||||||
|
*/
|
||||||
|
mutable Poco::RWLock data_lock;
|
||||||
|
|
||||||
|
/** Лок для множества столбцов и пути к таблице. Берется на запись в RENAME, ALTER (для ALTER MODIFY ненадолго) и DROP.
|
||||||
|
* Берется на чтение на все время SELECT, INSERT и слияния кусков (для MergeTree).
|
||||||
|
*
|
||||||
|
* Взятие этого лока на запись - строго более "сильная" операция, чем взятие parts_writing_lock на запись.
|
||||||
|
* То есть, если этот лок взят на запись, о parts_writing_lock можно не заботиться.
|
||||||
|
* parts_writing_lock нужен только для случаев, когда не хочется брать table_structure_lock надолго (ALTER MODIFY).
|
||||||
|
*/
|
||||||
|
mutable Poco::RWLock structure_lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef std::shared_ptr<IStorage> StoragePtr;
|
||||||
typedef std::vector<StoragePtr> StorageVector;
|
typedef std::vector<StoragePtr> StorageVector;
|
||||||
|
typedef IStorage::TableStructureReadLocks TableLocks;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
67
dbms/include/DB/Storages/ITableDeclaration.h
Normal file
67
dbms/include/DB/Storages/ITableDeclaration.h
Normal 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() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
87
dbms/include/DB/Storages/MergeTree/DiskSpaceMonitor.h
Normal file
87
dbms/include/DB/Storages/MergeTree/DiskSpaceMonitor.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
||||||
#include <DB/Storages/StorageMergeTree.h>
|
#include <DB/Storages/MergeTree/MergeTreeData.h>
|
||||||
#include <DB/Storages/MergeTree/PKCondition.h>
|
#include <DB/Storages/MergeTree/PKCondition.h>
|
||||||
|
|
||||||
#include <DB/Storages/MergeTree/MergeTreeReader.h>
|
#include <DB/Storages/MergeTree/MergeTreeReader.h>
|
||||||
|
|
||||||
|
|
||||||
@ -14,19 +13,18 @@ namespace DB
|
|||||||
class MergeTreeBlockInputStream : public IProfilingBlockInputStream
|
class MergeTreeBlockInputStream : public IProfilingBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Параметры storage_ и owned_storage разделены, чтобы можно было сделать поток, не владеющий своим storage
|
|
||||||
/// (например, поток, сливаящий куски). В таком случае сам storage должен следить, чтобы не удалить данные, пока их читают.
|
|
||||||
MergeTreeBlockInputStream(const String & path_, /// Путь к куску
|
MergeTreeBlockInputStream(const String & path_, /// Путь к куску
|
||||||
size_t block_size_, const Names & column_names_,
|
size_t block_size_, const Names & column_names_,
|
||||||
StorageMergeTree & storage_, const StorageMergeTree::DataPartPtr & owned_data_part_,
|
MergeTreeData & storage_, const MergeTreeData::DataPartPtr & owned_data_part_,
|
||||||
const MarkRanges & mark_ranges_, StoragePtr owned_storage, bool use_uncompressed_cache_,
|
const MarkRanges & mark_ranges_, bool use_uncompressed_cache_,
|
||||||
ExpressionActionsPtr prewhere_actions_, String prewhere_column_)
|
ExpressionActionsPtr prewhere_actions_, String prewhere_column_)
|
||||||
: IProfilingBlockInputStream(owned_storage),
|
:
|
||||||
path(path_), block_size(block_size_), column_names(column_names_),
|
path(path_), block_size(block_size_), column_names(column_names_),
|
||||||
storage(storage_), owned_data_part(owned_data_part_),
|
storage(storage_), owned_data_part(owned_data_part_),
|
||||||
all_mark_ranges(mark_ranges_), remaining_mark_ranges(mark_ranges_),
|
all_mark_ranges(mark_ranges_), remaining_mark_ranges(mark_ranges_),
|
||||||
use_uncompressed_cache(use_uncompressed_cache_),
|
use_uncompressed_cache(use_uncompressed_cache_),
|
||||||
prewhere_actions(prewhere_actions_), prewhere_column(prewhere_column_)
|
prewhere_actions(prewhere_actions_), prewhere_column(prewhere_column_),
|
||||||
|
log(&Logger::get("MergeTreeBlockInputStream"))
|
||||||
{
|
{
|
||||||
std::reverse(remaining_mark_ranges.begin(), remaining_mark_ranges.end());
|
std::reverse(remaining_mark_ranges.begin(), remaining_mark_ranges.end());
|
||||||
|
|
||||||
@ -48,7 +46,7 @@ public:
|
|||||||
}
|
}
|
||||||
column_name_set.insert(column_names.begin(), column_names.end());
|
column_name_set.insert(column_names.begin(), column_names.end());
|
||||||
|
|
||||||
LOG_TRACE(storage.log, "Reading " << all_mark_ranges.size() << " ranges from part " << owned_data_part->name
|
LOG_TRACE(log, "Reading " << all_mark_ranges.size() << " ranges from part " << owned_data_part->name
|
||||||
<< ", up to " << (all_mark_ranges.back().end - all_mark_ranges.front().begin) * storage.index_granularity
|
<< ", up to " << (all_mark_ranges.back().end - all_mark_ranges.front().begin) * storage.index_granularity
|
||||||
<< " rows starting from " << all_mark_ranges.front().begin * storage.index_granularity);
|
<< " rows starting from " << all_mark_ranges.front().begin * storage.index_granularity);
|
||||||
}
|
}
|
||||||
@ -58,7 +56,7 @@ public:
|
|||||||
String getID() const
|
String getID() const
|
||||||
{
|
{
|
||||||
std::stringstream res;
|
std::stringstream res;
|
||||||
res << "MergeTree(" << owned_storage->getTableName() << ", " << path << ", columns";
|
res << "MergeTree(" << path << ", columns";
|
||||||
|
|
||||||
for (size_t i = 0; i < column_names.size(); ++i)
|
for (size_t i = 0; i < column_names.size(); ++i)
|
||||||
res << ", " << column_names[i];
|
res << ", " << column_names[i];
|
||||||
@ -72,70 +70,6 @@ public:
|
|||||||
return res.str();
|
return res.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Получает набор диапазонов засечек, вне которых не могут находиться ключи из заданного диапазона.
|
|
||||||
static MarkRanges markRangesFromPkRange(
|
|
||||||
const StorageMergeTree::DataPart::Index & index,
|
|
||||||
StorageMergeTree & storage,
|
|
||||||
PKCondition & key_condition)
|
|
||||||
{
|
|
||||||
MarkRanges res;
|
|
||||||
|
|
||||||
size_t key_size = storage.sort_descr.size();
|
|
||||||
size_t marks_count = index.size() / key_size;
|
|
||||||
|
|
||||||
/// Если индекс не используется.
|
|
||||||
if (key_condition.alwaysTrue())
|
|
||||||
{
|
|
||||||
res.push_back(MarkRange(0, marks_count));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/** В стеке всегда будут находиться непересекающиеся подозрительные отрезки, самый левый наверху (back).
|
|
||||||
* На каждом шаге берем левый отрезок и проверяем, подходит ли он.
|
|
||||||
* Если подходит, разбиваем его на более мелкие и кладем их в стек. Если нет - выбрасываем его.
|
|
||||||
* Если отрезок уже длиной в одну засечку, добавляем его в ответ и выбрасываем.
|
|
||||||
*/
|
|
||||||
std::vector<MarkRange> ranges_stack;
|
|
||||||
ranges_stack.push_back(MarkRange(0, marks_count));
|
|
||||||
while (!ranges_stack.empty())
|
|
||||||
{
|
|
||||||
MarkRange range = ranges_stack.back();
|
|
||||||
ranges_stack.pop_back();
|
|
||||||
|
|
||||||
bool may_be_true;
|
|
||||||
if (range.end == marks_count)
|
|
||||||
may_be_true = key_condition.mayBeTrueAfter(&index[range.begin * key_size]);
|
|
||||||
else
|
|
||||||
may_be_true = key_condition.mayBeTrueInRange(&index[range.begin * key_size], &index[range.end * key_size]);
|
|
||||||
|
|
||||||
if (!may_be_true)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (range.end == range.begin + 1)
|
|
||||||
{
|
|
||||||
/// Увидели полезный промежуток между соседними засечками. Либо добавим его к последнему диапазону, либо начнем новый диапазон.
|
|
||||||
if (res.empty() || range.begin - res.back().end > storage.min_marks_for_seek)
|
|
||||||
res.push_back(range);
|
|
||||||
else
|
|
||||||
res.back().end = range.end;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/// Разбиваем отрезок и кладем результат в стек справа налево.
|
|
||||||
size_t step = (range.end - range.begin - 1) / storage.settings.coarse_index_granularity + 1;
|
|
||||||
size_t end;
|
|
||||||
|
|
||||||
for (end = range.end; end > range.begin + step; end -= step)
|
|
||||||
ranges_stack.push_back(MarkRange(end - step, end));
|
|
||||||
|
|
||||||
ranges_stack.push_back(MarkRange(range.begin, end));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Будем вызывать progressImpl самостоятельно.
|
/// Будем вызывать progressImpl самостоятельно.
|
||||||
void progress(size_t rows, size_t bytes) {}
|
void progress(size_t rows, size_t bytes) {}
|
||||||
@ -321,8 +255,8 @@ private:
|
|||||||
Names column_names;
|
Names column_names;
|
||||||
NameSet column_name_set;
|
NameSet column_name_set;
|
||||||
Names pre_column_names;
|
Names pre_column_names;
|
||||||
StorageMergeTree & storage;
|
MergeTreeData & storage;
|
||||||
const StorageMergeTree::DataPartPtr owned_data_part; /// Кусок не будет удалён, пока им владеет этот объект.
|
const MergeTreeData::DataPartPtr owned_data_part; /// Кусок не будет удалён, пока им владеет этот объект.
|
||||||
MarkRanges all_mark_ranges; /// В каких диапазонах засечек читать. В порядке возрастания номеров.
|
MarkRanges all_mark_ranges; /// В каких диапазонах засечек читать. В порядке возрастания номеров.
|
||||||
MarkRanges remaining_mark_ranges; /// В каких диапазонах засечек еще не прочли.
|
MarkRanges remaining_mark_ranges; /// В каких диапазонах засечек еще не прочли.
|
||||||
/// В порядке убывания номеров, чтобы можно было выбрасывать из конца.
|
/// В порядке убывания номеров, чтобы можно было выбрасывать из конца.
|
||||||
@ -332,6 +266,8 @@ private:
|
|||||||
ExpressionActionsPtr prewhere_actions;
|
ExpressionActionsPtr prewhere_actions;
|
||||||
String prewhere_column;
|
String prewhere_column;
|
||||||
bool remove_prewhere_column;
|
bool remove_prewhere_column;
|
||||||
|
|
||||||
|
Logger * log;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,298 +1,30 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <DB/IO/WriteBufferFromFile.h>
|
|
||||||
#include <DB/IO/CompressedWriteBuffer.h>
|
|
||||||
|
|
||||||
#include <DB/Columns/ColumnsNumber.h>
|
|
||||||
|
|
||||||
#include <DB/Interpreters/sortBlock.h>
|
|
||||||
|
|
||||||
#include <DB/Storages/StorageMergeTree.h>
|
#include <DB/Storages/StorageMergeTree.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
class MergeTreeBlockOutputStream : public IBlockOutputStream
|
class MergeTreeBlockOutputStream : public IBlockOutputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MergeTreeBlockOutputStream(StoragePtr owned_storage) : IBlockOutputStream(owned_storage), storage(dynamic_cast<StorageMergeTree &>(*owned_storage)), flags(O_TRUNC | O_CREAT | O_WRONLY)
|
MergeTreeBlockOutputStream(StorageMergeTree & storage_)
|
||||||
{
|
: storage(storage_) {}
|
||||||
}
|
|
||||||
|
|
||||||
void write(const Block & block)
|
void write(const Block & block)
|
||||||
{
|
{
|
||||||
Poco::ScopedReadRWLock write_lock(storage.write_lock);
|
auto part_blocks = storage.writer.splitBlockIntoParts(block);
|
||||||
|
for (auto & current_block : part_blocks)
|
||||||
storage.check(block, true);
|
|
||||||
|
|
||||||
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
|
|
||||||
|
|
||||||
size_t rows = block.rows();
|
|
||||||
size_t columns = block.columns();
|
|
||||||
|
|
||||||
/// Достаём столбец с датой.
|
|
||||||
const ColumnUInt16::Container_t & dates =
|
|
||||||
dynamic_cast<const ColumnUInt16 &>(*block.getByName(storage.date_column_name).column).getData();
|
|
||||||
|
|
||||||
/// Минимальная и максимальная дата.
|
|
||||||
UInt16 min_date = std::numeric_limits<UInt16>::max();
|
|
||||||
UInt16 max_date = std::numeric_limits<UInt16>::min();
|
|
||||||
for (ColumnUInt16::Container_t::const_iterator it = dates.begin(); it != dates.end(); ++it)
|
|
||||||
{
|
{
|
||||||
if (*it < min_date)
|
UInt64 temp_index = storage.increment.get();
|
||||||
min_date = *it;
|
MergeTreeData::MutableDataPartPtr part = storage.writer.writeTempPart(current_block, temp_index);
|
||||||
if (*it > max_date)
|
storage.data.renameTempPartAndAdd(part, &storage.increment);
|
||||||
max_date = *it;
|
storage.merge(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Разделяем на блоки по месяцам. Для каждого ещё посчитаем минимальную и максимальную дату.
|
|
||||||
typedef std::map<UInt16, BlockWithDateInterval> BlocksByMonth;
|
|
||||||
BlocksByMonth blocks_by_month;
|
|
||||||
|
|
||||||
UInt16 min_month = date_lut.toFirstDayNumOfMonth(DayNum_t(min_date));
|
|
||||||
UInt16 max_month = date_lut.toFirstDayNumOfMonth(DayNum_t(max_date));
|
|
||||||
|
|
||||||
/// Типичный случай - когда месяц один (ничего разделять не нужно).
|
|
||||||
if (min_month == max_month)
|
|
||||||
blocks_by_month[min_month] = BlockWithDateInterval(block, min_date, max_date);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < rows; ++i)
|
|
||||||
{
|
|
||||||
UInt16 month = date_lut.toFirstDayNumOfMonth(DayNum_t(dates[i]));
|
|
||||||
|
|
||||||
BlockWithDateInterval & block_for_month = blocks_by_month[month];
|
|
||||||
if (!block_for_month.block)
|
|
||||||
block_for_month.block = block.cloneEmpty();
|
|
||||||
|
|
||||||
if (dates[i] < block_for_month.min_date)
|
|
||||||
block_for_month.min_date = dates[i];
|
|
||||||
if (dates[i] > block_for_month.max_date)
|
|
||||||
block_for_month.max_date = dates[i];
|
|
||||||
|
|
||||||
for (size_t j = 0; j < columns; ++j)
|
|
||||||
block_for_month.block.getByPosition(j).column->insert((*block.getByPosition(j).column)[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Для каждого месяца.
|
|
||||||
for (BlocksByMonth::iterator it = blocks_by_month.begin(); it != blocks_by_month.end(); ++it)
|
|
||||||
writePart(it->second.block, it->second.min_date, it->second.max_date);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
StorageMergeTree & storage;
|
StorageMergeTree & storage;
|
||||||
|
|
||||||
const int flags;
|
|
||||||
|
|
||||||
struct BlockWithDateInterval
|
|
||||||
{
|
|
||||||
Block block;
|
|
||||||
UInt16 min_date;
|
|
||||||
UInt16 max_date;
|
|
||||||
|
|
||||||
BlockWithDateInterval() : min_date(std::numeric_limits<UInt16>::max()), max_date(0) {}
|
|
||||||
BlockWithDateInterval(const Block & block_, UInt16 min_date_, UInt16 max_date_)
|
|
||||||
: block(block_), min_date(min_date_), max_date(max_date_) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::set<std::string> OffsetColumns;
|
|
||||||
|
|
||||||
void writePart(Block & block, UInt16 min_date, UInt16 max_date)
|
|
||||||
{
|
|
||||||
DateLUTSingleton & date_lut = DateLUTSingleton::instance();
|
|
||||||
|
|
||||||
size_t rows = block.rows();
|
|
||||||
size_t columns = block.columns();
|
|
||||||
UInt64 tmp_part_id = storage.increment.get(false);
|
|
||||||
size_t part_size = (rows + storage.index_granularity - 1) / storage.index_granularity;
|
|
||||||
|
|
||||||
String tmp_part_name = storage.getPartName(
|
|
||||||
DayNum_t(min_date), DayNum_t(max_date),
|
|
||||||
tmp_part_id, tmp_part_id, 0);
|
|
||||||
|
|
||||||
String part_tmp_path = storage.full_path + "tmp_" + tmp_part_name + "/";
|
|
||||||
|
|
||||||
Poco::File(part_tmp_path).createDirectories();
|
|
||||||
|
|
||||||
LOG_TRACE(storage.log, "Calculating primary expression.");
|
|
||||||
|
|
||||||
/// Если для сортировки надо вычислить некоторые столбцы - делаем это.
|
|
||||||
storage.primary_expr->execute(block);
|
|
||||||
|
|
||||||
LOG_TRACE(storage.log, "Sorting by primary key.");
|
|
||||||
|
|
||||||
/// Сортируем.
|
|
||||||
stableSortBlock(block, storage.sort_descr);
|
|
||||||
|
|
||||||
/// Наконец-то можно писать данные на диск.
|
|
||||||
LOG_TRACE(storage.log, "Writing index.");
|
|
||||||
|
|
||||||
/// Сначала пишем индекс. Индекс содержит значение PK для каждой index_granularity строки.
|
|
||||||
StorageMergeTree::DataPart::Index index_vec;
|
|
||||||
index_vec.reserve(part_size * storage.sort_descr.size());
|
|
||||||
|
|
||||||
{
|
|
||||||
WriteBufferFromFile index(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, flags);
|
|
||||||
|
|
||||||
typedef std::vector<const ColumnWithNameAndType *> PrimaryColumns;
|
|
||||||
PrimaryColumns primary_columns;
|
|
||||||
|
|
||||||
for (size_t i = 0, size = storage.sort_descr.size(); i < size; ++i)
|
|
||||||
primary_columns.push_back(
|
|
||||||
!storage.sort_descr[i].column_name.empty()
|
|
||||||
? &block.getByName(storage.sort_descr[i].column_name)
|
|
||||||
: &block.getByPosition(storage.sort_descr[i].column_number));
|
|
||||||
|
|
||||||
for (size_t i = 0; i < rows; i += storage.index_granularity)
|
|
||||||
{
|
|
||||||
for (PrimaryColumns::const_iterator it = primary_columns.begin(); it != primary_columns.end(); ++it)
|
|
||||||
{
|
|
||||||
index_vec.push_back((*(*it)->column)[i]);
|
|
||||||
(*it)->type->serializeBinary(index_vec.back(), index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
index.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_TRACE(storage.log, "Writing data.");
|
|
||||||
|
|
||||||
/// Множество записанных столбцов со смещениями, чтобы не писать общие для вложенных структур столбцы несколько раз
|
|
||||||
OffsetColumns offset_columns;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < columns; ++i)
|
|
||||||
{
|
|
||||||
const ColumnWithNameAndType & column = block.getByPosition(i);
|
|
||||||
writeData(part_tmp_path, column.name, *column.type, *column.column, offset_columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_TRACE(storage.log, "Renaming.");
|
|
||||||
|
|
||||||
/// Добавляем новый кусок в набор.
|
|
||||||
{
|
|
||||||
Poco::ScopedLock<Poco::FastMutex> lock(storage.data_parts_mutex);
|
|
||||||
Poco::ScopedLock<Poco::FastMutex> lock_all(storage.all_data_parts_mutex);
|
|
||||||
|
|
||||||
/** Важно, что получение номера куска происходит атомарно с добавлением этого куска в набор.
|
|
||||||
* Иначе есть race condition - может произойти слияние пары кусков, диапазоны номеров которых
|
|
||||||
* содержат ещё не добавленный кусок.
|
|
||||||
*/
|
|
||||||
UInt64 part_id = storage.increment.get(false);
|
|
||||||
String part_name = storage.getPartName(DayNum_t(min_date), DayNum_t(max_date), part_id, part_id, 0);
|
|
||||||
String part_res_path = storage.full_path + part_name + "/";
|
|
||||||
|
|
||||||
StorageMergeTree::DataPartPtr new_data_part = new StorageMergeTree::DataPart(storage);
|
|
||||||
new_data_part->left_date = DayNum_t(min_date);
|
|
||||||
new_data_part->right_date = DayNum_t(max_date);
|
|
||||||
new_data_part->left = part_id;
|
|
||||||
new_data_part->right = part_id;
|
|
||||||
new_data_part->level = 0;
|
|
||||||
new_data_part->name = part_name;
|
|
||||||
new_data_part->size = part_size;
|
|
||||||
new_data_part->modification_time = time(0);
|
|
||||||
new_data_part->left_month = date_lut.toFirstDayNumOfMonth(new_data_part->left_date);
|
|
||||||
new_data_part->right_month = date_lut.toFirstDayNumOfMonth(new_data_part->right_date);
|
|
||||||
new_data_part->index.swap(index_vec);
|
|
||||||
|
|
||||||
/// Переименовываем кусок.
|
|
||||||
Poco::File(part_tmp_path).renameTo(part_res_path);
|
|
||||||
|
|
||||||
storage.data_parts.insert(new_data_part);
|
|
||||||
storage.all_data_parts.insert(new_data_part);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Если на каждую запись делать по две итерации слияния, то дерево будет максимально компактно.
|
|
||||||
storage.merge(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Записать данные одного столбца.
|
|
||||||
void writeData(const String & path, const String & name, const IDataType & type, const IColumn & column,
|
|
||||||
OffsetColumns & offset_columns, size_t level = 0)
|
|
||||||
{
|
|
||||||
String escaped_column_name = escapeForFileName(name);
|
|
||||||
size_t size = column.size();
|
|
||||||
|
|
||||||
/// Для массивов требуется сначала сериализовать размеры, а потом значения.
|
|
||||||
if (const DataTypeArray * type_arr = dynamic_cast<const DataTypeArray *>(&type))
|
|
||||||
{
|
|
||||||
String size_name = escapeForFileName(DataTypeNested::extractNestedTableName(name))
|
|
||||||
+ ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
|
|
||||||
if (offset_columns.count(size_name) == 0)
|
|
||||||
{
|
|
||||||
offset_columns.insert(size_name);
|
|
||||||
|
|
||||||
WriteBufferFromFile plain(path + size_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags);
|
|
||||||
WriteBufferFromFile marks(path + size_name + ".mrk", 4096, flags);
|
|
||||||
CompressedWriteBuffer compressed(plain);
|
|
||||||
|
|
||||||
size_t prev_mark = 0;
|
|
||||||
while (prev_mark < size)
|
|
||||||
{
|
|
||||||
/// Каждая засечка - это: (смещение в файле до начала сжатого блока, смещение внутри блока)
|
|
||||||
writeIntBinary(plain.count(), marks);
|
|
||||||
writeIntBinary(compressed.offset(), marks);
|
|
||||||
|
|
||||||
type_arr->serializeOffsets(column, compressed, prev_mark, storage.index_granularity);
|
|
||||||
prev_mark += storage.index_granularity;
|
|
||||||
|
|
||||||
compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего.
|
|
||||||
}
|
|
||||||
|
|
||||||
compressed.next();
|
|
||||||
plain.next();
|
|
||||||
marks.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (const DataTypeNested * type_nested = dynamic_cast<const DataTypeNested *>(&type))
|
|
||||||
{
|
|
||||||
String size_name = escaped_column_name + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
|
|
||||||
|
|
||||||
WriteBufferFromFile plain(path + size_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags);
|
|
||||||
WriteBufferFromFile marks(path + size_name + ".mrk", 4096, flags);
|
|
||||||
CompressedWriteBuffer compressed(plain);
|
|
||||||
|
|
||||||
size_t prev_mark = 0;
|
|
||||||
while (prev_mark < size)
|
|
||||||
{
|
|
||||||
/// Каждая засечка - это: (смещение в файле до начала сжатого блока, смещение внутри блока)
|
|
||||||
writeIntBinary(plain.count(), marks);
|
|
||||||
writeIntBinary(compressed.offset(), marks);
|
|
||||||
|
|
||||||
type_nested->serializeOffsets(column, compressed, prev_mark, storage.index_granularity);
|
|
||||||
prev_mark += storage.index_granularity;
|
|
||||||
|
|
||||||
compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего.
|
|
||||||
}
|
|
||||||
|
|
||||||
compressed.next();
|
|
||||||
plain.next();
|
|
||||||
marks.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
WriteBufferFromFile plain(path + escaped_column_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags);
|
|
||||||
WriteBufferFromFile marks(path + escaped_column_name + ".mrk", 4096, flags);
|
|
||||||
CompressedWriteBuffer compressed(plain);
|
|
||||||
|
|
||||||
size_t prev_mark = 0;
|
|
||||||
while (prev_mark < size)
|
|
||||||
{
|
|
||||||
writeIntBinary(plain.count(), marks);
|
|
||||||
writeIntBinary(compressed.offset(), marks);
|
|
||||||
|
|
||||||
type.serializeBinary(column, compressed, prev_mark, storage.index_granularity);
|
|
||||||
prev_mark += storage.index_granularity;
|
|
||||||
|
|
||||||
compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего.
|
|
||||||
}
|
|
||||||
|
|
||||||
compressed.next();
|
|
||||||
plain.next();
|
|
||||||
marks.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
356
dbms/include/DB/Storages/MergeTree/MergeTreeData.h
Normal file
356
dbms/include/DB/Storages/MergeTree/MergeTreeData.h
Normal 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
52
dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h
Normal file
52
dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
62
dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h
Normal file
62
dbms/include/DB/Storages/MergeTree/MergeTreeDataWriter.h
Normal 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -1,12 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <DB/Storages/StorageMergeTree.h>
|
#include <DB/Storages/MergeTree/MergeTreeData.h>
|
||||||
#include <DB/DataTypes/IDataType.h>
|
#include <DB/DataTypes/IDataType.h>
|
||||||
#include <DB/DataTypes/DataTypeNested.h>
|
#include <DB/DataTypes/DataTypeNested.h>
|
||||||
|
#include <DB/DataTypes/DataTypeArray.h>
|
||||||
#include <DB/Core/NamesAndTypes.h>
|
#include <DB/Core/NamesAndTypes.h>
|
||||||
#include <DB/Common/escapeForFileName.h>
|
#include <DB/Common/escapeForFileName.h>
|
||||||
#include <DB/IO/CachedCompressedReadBuffer.h>
|
#include <DB/IO/CachedCompressedReadBuffer.h>
|
||||||
#include <DB/IO/CompressedReadBufferFromFile.h>
|
#include <DB/IO/CompressedReadBufferFromFile.h>
|
||||||
|
#include <DB/Columns/ColumnArray.h>
|
||||||
|
#include <DB/Columns/ColumnNested.h>
|
||||||
|
|
||||||
|
|
||||||
#define MERGE_TREE_MARK_SIZE (2 * sizeof(size_t))
|
#define MERGE_TREE_MARK_SIZE (2 * sizeof(size_t))
|
||||||
@ -15,6 +18,20 @@
|
|||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** Пара засечек, определяющая диапазон строк в куске. Именно, диапазон имеет вид [begin * index_granularity, end * index_granularity).
|
||||||
|
*/
|
||||||
|
struct MarkRange
|
||||||
|
{
|
||||||
|
size_t begin;
|
||||||
|
size_t end;
|
||||||
|
|
||||||
|
MarkRange() {}
|
||||||
|
MarkRange(size_t begin_, size_t end_) : begin(begin_), end(end_) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<MarkRange> MarkRanges;
|
||||||
|
|
||||||
|
|
||||||
/** Умеет читать данные между парой засечек из одного куска. При чтении последовательных отрезков не делает лишних seek-ов.
|
/** Умеет читать данные между парой засечек из одного куска. При чтении последовательных отрезков не делает лишних seek-ов.
|
||||||
* При чтении почти последовательных отрезков делает seek-и быстро, не выбрасывая содержимое буфера.
|
* При чтении почти последовательных отрезков делает seek-и быстро, не выбрасывая содержимое буфера.
|
||||||
*/
|
*/
|
||||||
@ -22,7 +39,7 @@ class MergeTreeReader
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MergeTreeReader(const String & path_, /// Путь к куску
|
MergeTreeReader(const String & path_, /// Путь к куску
|
||||||
const Names & columns_names_, bool use_uncompressed_cache_, StorageMergeTree & storage_)
|
const Names & columns_names_, bool use_uncompressed_cache_, MergeTreeData & storage_)
|
||||||
: path(path_), column_names(columns_names_), use_uncompressed_cache(use_uncompressed_cache_), storage(storage_)
|
: path(path_), column_names(columns_names_), use_uncompressed_cache(use_uncompressed_cache_), storage(storage_)
|
||||||
{
|
{
|
||||||
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
|
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
|
||||||
@ -220,7 +237,7 @@ private:
|
|||||||
FileStreams streams;
|
FileStreams streams;
|
||||||
Names column_names;
|
Names column_names;
|
||||||
bool use_uncompressed_cache;
|
bool use_uncompressed_cache;
|
||||||
StorageMergeTree & storage;
|
MergeTreeData & storage;
|
||||||
|
|
||||||
void addStream(const String & name, const IDataType & type, size_t level = 0)
|
void addStream(const String & name, const IDataType & type, size_t level = 0)
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
#include <DB/IO/WriteBufferFromFile.h>
|
#include <DB/IO/WriteBufferFromFile.h>
|
||||||
#include <DB/IO/CompressedWriteBuffer.h>
|
#include <DB/IO/CompressedWriteBuffer.h>
|
||||||
|
|
||||||
#include <DB/Storages/StorageMergeTree.h>
|
#include <DB/Storages/MergeTree/MergeTreeData.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
@ -11,7 +11,7 @@ namespace DB
|
|||||||
class IMergedBlockOutputStream : public IBlockOutputStream
|
class IMergedBlockOutputStream : public IBlockOutputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IMergedBlockOutputStream(StorageMergeTree & storage_) : storage(storage_), index_offset(0)
|
IMergedBlockOutputStream(MergeTreeData & storage_) : storage(storage_), index_offset(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ protected:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageMergeTree & storage;
|
MergeTreeData & storage;
|
||||||
|
|
||||||
ColumnStreams column_streams;
|
ColumnStreams column_streams;
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ protected:
|
|||||||
class MergedBlockOutputStream : public IMergedBlockOutputStream
|
class MergedBlockOutputStream : public IMergedBlockOutputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MergedBlockOutputStream(StorageMergeTree & storage_,
|
MergedBlockOutputStream(MergeTreeData & storage_,
|
||||||
UInt16 min_date, UInt16 max_date, UInt64 min_part_id, UInt64 max_part_id, UInt32 level)
|
UInt16 min_date, UInt16 max_date, UInt64 min_part_id, UInt64 max_part_id, UInt32 level)
|
||||||
: IMergedBlockOutputStream(storage_), marks_count(0)
|
: IMergedBlockOutputStream(storage_), marks_count(0)
|
||||||
{
|
{
|
||||||
@ -204,15 +204,16 @@ public:
|
|||||||
DayNum_t(min_date), DayNum_t(max_date),
|
DayNum_t(min_date), DayNum_t(max_date),
|
||||||
min_part_id, max_part_id, level);
|
min_part_id, max_part_id, level);
|
||||||
|
|
||||||
part_tmp_path = storage.full_path + "tmp_" + part_name + "/";
|
part_tmp_path = storage.getFullPath() + "tmp_" + part_name + "/";
|
||||||
part_res_path = storage.full_path + part_name + "/";
|
part_res_path = storage.getFullPath() + part_name + "/";
|
||||||
|
|
||||||
Poco::File(part_tmp_path).createDirectories();
|
Poco::File(part_tmp_path).createDirectories();
|
||||||
|
|
||||||
index_stream = new WriteBufferFromFile(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, O_TRUNC | O_CREAT | O_WRONLY);
|
index_stream = new WriteBufferFromFile(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, O_TRUNC | O_CREAT | O_WRONLY);
|
||||||
|
|
||||||
for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it)
|
columns_list = storage.getColumnsList();
|
||||||
addStream(part_tmp_path, it->first, *it->second);
|
for (const auto & it : columns_list)
|
||||||
|
addStream(part_tmp_path, it.first, *it.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(const Block & block)
|
void write(const Block & block)
|
||||||
@ -223,11 +224,11 @@ public:
|
|||||||
typedef std::vector<const ColumnWithNameAndType *> PrimaryColumns;
|
typedef std::vector<const ColumnWithNameAndType *> PrimaryColumns;
|
||||||
PrimaryColumns primary_columns;
|
PrimaryColumns primary_columns;
|
||||||
|
|
||||||
for (size_t i = 0, size = storage.sort_descr.size(); i < size; ++i)
|
for (const auto & descr : storage.getSortDescription())
|
||||||
primary_columns.push_back(
|
primary_columns.push_back(
|
||||||
!storage.sort_descr[i].column_name.empty()
|
!descr.column_name.empty()
|
||||||
? &block.getByName(storage.sort_descr[i].column_name)
|
? &block.getByName(descr.column_name)
|
||||||
: &block.getByPosition(storage.sort_descr[i].column_number));
|
: &block.getByPosition(descr.column_number));
|
||||||
|
|
||||||
for (size_t i = index_offset; i < rows; i += storage.index_granularity)
|
for (size_t i = index_offset; i < rows; i += storage.index_granularity)
|
||||||
{
|
{
|
||||||
@ -243,9 +244,9 @@ public:
|
|||||||
OffsetColumns offset_columns;
|
OffsetColumns offset_columns;
|
||||||
|
|
||||||
/// Теперь пишем данные.
|
/// Теперь пишем данные.
|
||||||
for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it)
|
for (const auto & it : columns_list)
|
||||||
{
|
{
|
||||||
const ColumnWithNameAndType & column = block.getByName(it->first);
|
const ColumnWithNameAndType & column = block.getByName(it.first);
|
||||||
writeData(column.name, *column.type, *column.column, offset_columns);
|
writeData(column.name, *column.type, *column.column, offset_columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +286,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
NamesAndTypesList columns_list;
|
||||||
|
|
||||||
String part_name;
|
String part_name;
|
||||||
String part_tmp_path;
|
String part_tmp_path;
|
||||||
String part_res_path;
|
String part_res_path;
|
||||||
@ -299,7 +302,7 @@ typedef Poco::SharedPtr<MergedBlockOutputStream> MergedBlockOutputStreamPtr;
|
|||||||
class MergedColumnOnlyOutputStream : public IMergedBlockOutputStream
|
class MergedColumnOnlyOutputStream : public IMergedBlockOutputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MergedColumnOnlyOutputStream(StorageMergeTree & storage_, String part_path_, bool sync_ = false) :
|
MergedColumnOnlyOutputStream(MergeTreeData & storage_, String part_path_, bool sync_ = false) :
|
||||||
IMergedBlockOutputStream(storage_), part_path(part_path_), initialized(false), sync(sync_)
|
IMergedBlockOutputStream(storage_), part_path(part_path_), initialized(false), sync(sync_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include <DB/Interpreters/Context.h>
|
#include <DB/Interpreters/Context.h>
|
||||||
#include <DB/Storages/IStorage.h>
|
|
||||||
#include <DB/Core/SortDescription.h>
|
#include <DB/Core/SortDescription.h>
|
||||||
#include <DB/Parsers/ASTExpressionList.h>
|
#include <DB/Parsers/ASTExpressionList.h>
|
||||||
#include <DB/Parsers/ASTSelectQuery.h>
|
#include <DB/Parsers/ASTSelectQuery.h>
|
||||||
|
@ -32,7 +32,7 @@ public:
|
|||||||
|
|
||||||
ASTPtr getCustomCreateQuery(const Context & context) const;
|
ASTPtr getCustomCreateQuery(const Context & context) const;
|
||||||
|
|
||||||
void dropImpl();
|
void drop() override;
|
||||||
|
|
||||||
String source_database_name;
|
String source_database_name;
|
||||||
String source_table_name;
|
String source_table_name;
|
||||||
|
@ -58,7 +58,7 @@ public:
|
|||||||
size_t max_block_size = DEFAULT_BLOCK_SIZE,
|
size_t max_block_size = DEFAULT_BLOCK_SIZE,
|
||||||
unsigned threads = 1);
|
unsigned threads = 1);
|
||||||
|
|
||||||
void dropImpl() {}
|
void drop() override {}
|
||||||
void rename(const String & new_path_to_db, const String & new_name) { name = new_name; }
|
void rename(const String & new_path_to_db, const String & new_name) { name = new_name; }
|
||||||
/// в подтаблицах добавлять и удалять столбы нужно вручную
|
/// в подтаблицах добавлять и удалять столбы нужно вручную
|
||||||
/// структура подтаблиц не проверяется
|
/// структура подтаблиц не проверяется
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <DB/IO/CompressedWriteBuffer.h>
|
#include <DB/IO/CompressedWriteBuffer.h>
|
||||||
#include <DB/Storages/IStorage.h>
|
#include <DB/Storages/IStorage.h>
|
||||||
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
||||||
|
#include <DB/DataStreams/IBlockOutputStream.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
@ -36,20 +37,10 @@ typedef std::vector<Mark> Marks;
|
|||||||
class LogBlockInputStream : public IProfilingBlockInputStream
|
class LogBlockInputStream : public IProfilingBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage, size_t mark_number_, size_t rows_limit_);
|
LogBlockInputStream(size_t block_size_, const Names & column_names_, StorageLog & storage_, size_t mark_number_, size_t rows_limit_);
|
||||||
String getName() const { return "LogBlockInputStream"; }
|
String getName() const { return "LogBlockInputStream"; }
|
||||||
|
|
||||||
String getID() const
|
String getID() const;
|
||||||
{
|
|
||||||
std::stringstream res;
|
|
||||||
res << "Log(" << owned_storage->getTableName() << ", " << &*owned_storage << ", " << mark_number << ", " << rows_limit;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < column_names.size(); ++i)
|
|
||||||
res << ", " << column_names[i];
|
|
||||||
|
|
||||||
res << ")";
|
|
||||||
return res.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Block readImpl();
|
Block readImpl();
|
||||||
@ -88,7 +79,7 @@ private:
|
|||||||
class LogBlockOutputStream : public IBlockOutputStream
|
class LogBlockOutputStream : public IBlockOutputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LogBlockOutputStream(StoragePtr owned_storage);
|
LogBlockOutputStream(StorageLog & storage_);
|
||||||
void write(const Block & block);
|
void write(const Block & block);
|
||||||
void writeSuffix();
|
void writeSuffix();
|
||||||
private:
|
private:
|
||||||
|
@ -16,7 +16,7 @@ public:
|
|||||||
std::string getInnerTableName() const { return ".inner." + table_name; }
|
std::string getInnerTableName() const { return ".inner." + table_name; }
|
||||||
|
|
||||||
BlockOutputStreamPtr write(ASTPtr query);
|
BlockOutputStreamPtr write(ASTPtr query);
|
||||||
void dropImpl();
|
void drop() override;
|
||||||
bool optimize();
|
bool optimize();
|
||||||
|
|
||||||
BlockInputStreams read(
|
BlockInputStreams read(
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <DB/Core/NamesAndTypes.h>
|
#include <DB/Core/NamesAndTypes.h>
|
||||||
#include <DB/Storages/IStorage.h>
|
#include <DB/Storages/IStorage.h>
|
||||||
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
||||||
|
#include <DB/DataStreams/IBlockOutputStream.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
@ -15,13 +16,13 @@ class StorageMemory;
|
|||||||
class MemoryBlockInputStream : public IProfilingBlockInputStream
|
class MemoryBlockInputStream : public IProfilingBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_, StoragePtr owned_storage);
|
MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_);
|
||||||
String getName() const { return "MemoryBlockInputStream"; }
|
String getName() const { return "MemoryBlockInputStream"; }
|
||||||
|
|
||||||
String getID() const
|
String getID() const
|
||||||
{
|
{
|
||||||
std::stringstream res;
|
std::stringstream res;
|
||||||
res << "Memory(" << owned_storage->getTableName() << ", " << &*owned_storage << ", " << &*begin << ", " << &*end;
|
res << "Memory(" << &*begin << ", " << &*end;
|
||||||
|
|
||||||
for (size_t i = 0; i < column_names.size(); ++i)
|
for (size_t i = 0; i < column_names.size(); ++i)
|
||||||
res << ", " << column_names[i];
|
res << ", " << column_names[i];
|
||||||
@ -43,7 +44,7 @@ private:
|
|||||||
class MemoryBlockOutputStream : public IBlockOutputStream
|
class MemoryBlockOutputStream : public IBlockOutputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MemoryBlockOutputStream(StoragePtr owned_storage);
|
MemoryBlockOutputStream(StorageMemory & storage_);
|
||||||
void write(const Block & block);
|
void write(const Block & block);
|
||||||
private:
|
private:
|
||||||
StorageMemory & storage;
|
StorageMemory & storage;
|
||||||
@ -79,7 +80,7 @@ public:
|
|||||||
BlockOutputStreamPtr write(
|
BlockOutputStreamPtr write(
|
||||||
ASTPtr query);
|
ASTPtr query);
|
||||||
|
|
||||||
void dropImpl();
|
void drop() override;
|
||||||
void rename(const String & new_path_to_db, const String & new_name) { name = new_name; }
|
void rename(const String & new_path_to_db, const String & new_name) { name = new_name; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -17,8 +17,6 @@ typedef Poco::SharedPtr<StorageMerge> StorageMergePtr;
|
|||||||
*/
|
*/
|
||||||
class StorageMerge : public IStorage
|
class StorageMerge : public IStorage
|
||||||
{
|
{
|
||||||
typedef std::vector<StoragePtr> SelectedTables;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static StoragePtr create(
|
static StoragePtr create(
|
||||||
const std::string & name_, /// Имя таблицы.
|
const std::string & name_, /// Имя таблицы.
|
||||||
@ -43,7 +41,7 @@ public:
|
|||||||
size_t max_block_size = DEFAULT_BLOCK_SIZE,
|
size_t max_block_size = DEFAULT_BLOCK_SIZE,
|
||||||
unsigned threads = 1);
|
unsigned threads = 1);
|
||||||
|
|
||||||
void dropImpl() {}
|
void drop() override {}
|
||||||
void rename(const String & new_path_to_db, const String & new_name) { name = new_name; }
|
void rename(const String & new_path_to_db, const String & new_name) { name = new_name; }
|
||||||
|
|
||||||
void getSelectedTables(StorageVector & selected_tables);
|
void getSelectedTables(StorageVector & selected_tables);
|
||||||
|
@ -1,123 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <statdaemons/Increment.h>
|
#include <DB/Storages/MergeTree/MergeTreeData.h>
|
||||||
#include <statdaemons/threadpool.hpp>
|
#include "MergeTree/MergeTreeDataSelectExecutor.h"
|
||||||
|
#include "MergeTree/MergeTreeDataWriter.h"
|
||||||
#include <DB/Core/SortDescription.h>
|
#include "MergeTree/MergeTreeDataMerger.h"
|
||||||
#include <DB/Interpreters/Context.h>
|
#include "MergeTree/DiskSpaceMonitor.h"
|
||||||
#include <DB/Interpreters/ExpressionActions.h>
|
|
||||||
#include <DB/Storages/IStorage.h>
|
|
||||||
#include <Poco/RWLock.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
/** Движок, использующий merge tree для инкрементальной сортировки данных.
|
/** См. описание структуры данных в MergeTreeData.
|
||||||
* Таблица представлена набором сортированных кусков.
|
|
||||||
* При вставке, данные сортируются по указанному выражению (первичному ключу) и пишутся в новый кусок.
|
|
||||||
* Куски объединяются в фоне, согласно некоторой эвристике.
|
|
||||||
* Для каждого куска, создаётся индексный файл, содержащий значение первичного ключа для каждой n-ой строки.
|
|
||||||
* Таким образом, реализуется эффективная выборка по диапазону первичного ключа.
|
|
||||||
*
|
|
||||||
* Дополнительно:
|
|
||||||
*
|
|
||||||
* Указывается столбец, содержащий дату.
|
|
||||||
* Для каждого куска пишется минимальная и максимальная дата.
|
|
||||||
* (по сути - ещё один индекс)
|
|
||||||
*
|
|
||||||
* Данные разделяются по разным месяцам (пишутся в разные куски для разных месяцев).
|
|
||||||
* Куски для разных месяцев не объединяются - для простоты эксплуатации.
|
|
||||||
* (дают локальность обновлений, что удобно для синхронизации и бэкапа)
|
|
||||||
*
|
|
||||||
* Структура файлов:
|
|
||||||
* / increment.txt - файл, содержащий одно число, увеличивающееся на 1 - для генерации идентификаторов кусков.
|
|
||||||
* / min-date _ max-date _ min-id _ max-id _ level / - директория с куском.
|
|
||||||
* / min-date _ max-date _ min-id _ max-id _ level / primary.idx - индексный файл.
|
|
||||||
* Внутри директории с куском:
|
|
||||||
* Column.bin - данные столбца
|
|
||||||
* Column.mrk - засечки, указывающие, откуда начинать чтение, чтобы пропустить n * k строк.
|
|
||||||
*
|
|
||||||
* Имеется несколько режимов работы, определяющих, что делать при мердже:
|
|
||||||
* - Ordinary - ничего дополнительно не делать;
|
|
||||||
* - Collapsing - при склейке кусков "схлопывать"
|
|
||||||
* пары записей с разными значениями sign_column для одного значения первичного ключа.
|
|
||||||
* (см. CollapsingSortedBlockInputStream.h)
|
|
||||||
* - Summing - при склейке кусков, при совпадении PK суммировать все числовые столбцы, не входящие в PK.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct StorageMergeTreeSettings
|
|
||||||
{
|
|
||||||
/// Набор кусков разрешено объединить, если среди них максимальный размер не более чем во столько раз больше суммы остальных.
|
|
||||||
double max_size_ratio_to_merge_parts = 5;
|
|
||||||
|
|
||||||
/// Сколько за раз сливать кусков.
|
|
||||||
/// Трудоемкость выбора кусков O(N * max_parts_to_merge_at_once), так что не следует делать это число слишком большим.
|
|
||||||
/// С другой стороны, чтобы слияния точно не могли зайти в тупик, нужно хотя бы
|
|
||||||
/// log(max_rows_to_merge_parts/index_granularity)/log(max_size_ratio_to_merge_parts).
|
|
||||||
size_t max_parts_to_merge_at_once = 10;
|
|
||||||
|
|
||||||
/// Куски настолько большого размера в основном потоке объединять нельзя вообще.
|
|
||||||
size_t max_rows_to_merge_parts = 100 * 1024 * 1024;
|
|
||||||
|
|
||||||
/// Куски настолько большого размера во втором потоке объединять нельзя вообще.
|
|
||||||
size_t max_rows_to_merge_parts_second = 1024 * 1024;
|
|
||||||
|
|
||||||
/// Во столько раз ночью увеличиваем коэффициент.
|
|
||||||
size_t merge_parts_at_night_inc = 10;
|
|
||||||
|
|
||||||
/// Сколько потоков использовать для объединения кусков.
|
|
||||||
size_t merging_threads = 2;
|
|
||||||
|
|
||||||
/// Если из одного файла читается хотя бы столько строк, чтение можно распараллелить.
|
|
||||||
size_t min_rows_for_concurrent_read = 20 * 8192;
|
|
||||||
|
|
||||||
/// Можно пропускать чтение более чем стольки строк ценой одного seek по файлу.
|
|
||||||
size_t min_rows_for_seek = 5 * 8192;
|
|
||||||
|
|
||||||
/// Если отрезок индекса может содержать нужные ключи, делим его на столько частей и рекурсивно проверяем их.
|
|
||||||
size_t coarse_index_granularity = 8;
|
|
||||||
|
|
||||||
/** Максимальное количество строк на запрос, для использования кэша разжатых данных. Если запрос большой - кэш не используется.
|
|
||||||
* (Чтобы большие запросы не вымывали кэш.)
|
|
||||||
*/
|
|
||||||
size_t max_rows_to_use_cache = 1024 * 1024;
|
|
||||||
|
|
||||||
/// Через сколько секунд удалять old_куски.
|
|
||||||
time_t old_parts_lifetime = 5 * 60;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Пара засечек, определяющая диапазон строк в куске. Именно, диапазон имеет вид [begin * index_granularity, end * index_granularity).
|
|
||||||
struct MarkRange
|
|
||||||
{
|
|
||||||
size_t begin;
|
|
||||||
size_t end;
|
|
||||||
|
|
||||||
MarkRange() {}
|
|
||||||
MarkRange(size_t begin_, size_t end_) : begin(begin_), end(end_) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::vector<MarkRange> MarkRanges;
|
|
||||||
|
|
||||||
|
|
||||||
class StorageMergeTree : public IStorage
|
class StorageMergeTree : public IStorage
|
||||||
{
|
{
|
||||||
friend class MergeTreeReader;
|
|
||||||
friend class MergeTreeBlockInputStream;
|
|
||||||
friend class MergeTreeBlockOutputStream;
|
friend class MergeTreeBlockOutputStream;
|
||||||
friend class IMergedBlockOutputStream;
|
|
||||||
friend class MergedBlockOutputStream;
|
|
||||||
friend class MergedColumnOnlyOutputStream;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Режим работы. См. выше.
|
|
||||||
enum Mode
|
|
||||||
{
|
|
||||||
Ordinary,
|
|
||||||
Collapsing,
|
|
||||||
Summing,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце),
|
/** Подцепить таблицу с соответствующим именем, по соответствующему пути (с / на конце),
|
||||||
* (корректность имён и путей не проверяется)
|
* (корректность имён и путей не проверяется)
|
||||||
* состоящую из указанных столбцов.
|
* состоящую из указанных столбцов.
|
||||||
@ -132,36 +30,26 @@ public:
|
|||||||
const String & date_column_name_,
|
const String & date_column_name_,
|
||||||
const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается.
|
const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается.
|
||||||
size_t index_granularity_,
|
size_t index_granularity_,
|
||||||
Mode mode_ = Ordinary,
|
MergeTreeData::Mode mode_ = MergeTreeData::Ordinary,
|
||||||
const String & sign_column_ = "",
|
const String & sign_column_ = "",
|
||||||
const StorageMergeTreeSettings & settings_ = StorageMergeTreeSettings());
|
const MergeTreeSettings & settings_ = MergeTreeSettings());
|
||||||
|
|
||||||
void shutdown();
|
void shutdown();
|
||||||
~StorageMergeTree();
|
~StorageMergeTree();
|
||||||
|
|
||||||
std::string getName() const
|
std::string getName() const
|
||||||
{
|
{
|
||||||
switch (mode)
|
return data.getModePrefix() + "MergeTree";
|
||||||
{
|
|
||||||
case Ordinary: return "MergeTree";
|
|
||||||
case Collapsing: return "CollapsingMergeTree";
|
|
||||||
case Summing: return "SummingMergeTree";
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw Exception("Unknown mode of operation for StorageMergeTree: " + toString(mode), ErrorCodes::LOGICAL_ERROR);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getTableName() const { return name; }
|
std::string getTableName() const { return name; }
|
||||||
std::string getSignColumnName() const { return sign_column; }
|
std::string getSignColumnName() const { return data.getSignColumnName(); }
|
||||||
bool supportsSampling() const { return !!sampling_expression; }
|
bool supportsSampling() const { return data.supportsSampling(); }
|
||||||
bool supportsFinal() const { return !sign_column.empty(); }
|
bool supportsFinal() const { return data.supportsFinal(); }
|
||||||
bool supportsPrewhere() const { return true; }
|
bool supportsPrewhere() const { return data.supportsPrewhere(); }
|
||||||
|
|
||||||
const NamesAndTypesList & getColumnsList() const { return *columns; }
|
const NamesAndTypesList & getColumnsList() const { return data.getColumnsList(); }
|
||||||
|
|
||||||
/** При чтении, выбирается набор кусков, покрывающий нужный диапазон индекса.
|
|
||||||
*/
|
|
||||||
BlockInputStreams read(
|
BlockInputStreams read(
|
||||||
const Names & column_names,
|
const Names & column_names,
|
||||||
ASTPtr query,
|
ASTPtr query,
|
||||||
@ -170,8 +58,6 @@ public:
|
|||||||
size_t max_block_size = DEFAULT_BLOCK_SIZE,
|
size_t max_block_size = DEFAULT_BLOCK_SIZE,
|
||||||
unsigned threads = 1);
|
unsigned threads = 1);
|
||||||
|
|
||||||
/** При записи, данные сортируются и пишутся в новые куски.
|
|
||||||
*/
|
|
||||||
BlockOutputStreamPtr write(ASTPtr query);
|
BlockOutputStreamPtr write(ASTPtr query);
|
||||||
|
|
||||||
/** Выполнить очередной шаг объединения кусков.
|
/** Выполнить очередной шаг объединения кусков.
|
||||||
@ -182,264 +68,75 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dropImpl();
|
void drop() override;
|
||||||
|
|
||||||
void rename(const String & new_path_to_db, const String & new_name);
|
void rename(const String & new_path_to_db, const String & new_name);
|
||||||
|
|
||||||
/// Метод ALTER позволяет добавлять и удалять столбцы.
|
|
||||||
/// Метод ALTER нужно применять, когда обращения к базе приостановлены.
|
|
||||||
/// Например если параллельно с INSERT выполнить ALTER, то ALTER выполниться, а INSERT бросит исключение
|
|
||||||
void alter(const ASTAlterQuery::Parameters & params);
|
void alter(const ASTAlterQuery::Parameters & params);
|
||||||
|
void prepareAlterModify(const ASTAlterQuery::Parameters & params);
|
||||||
class BigLock
|
void commitAlterModify(const ASTAlterQuery::Parameters & params);
|
||||||
{
|
|
||||||
public:
|
|
||||||
BigLock(StorageMergeTree & storage) : merge_lock(storage.merge_lock),
|
|
||||||
write_lock(storage.write_lock), read_lock(storage.read_lock)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Poco::ScopedWriteRWLock merge_lock;
|
|
||||||
Poco::ScopedWriteRWLock write_lock;
|
|
||||||
Poco::ScopedWriteRWLock read_lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef Poco::SharedPtr<BigLock> BigLockPtr;
|
|
||||||
BigLockPtr lockAllOperations()
|
|
||||||
{
|
|
||||||
return new BigLock(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String path;
|
String path;
|
||||||
String name;
|
String name;
|
||||||
String full_path;
|
String full_path;
|
||||||
NamesAndTypesListPtr columns;
|
|
||||||
|
|
||||||
const Context & context;
|
|
||||||
ASTPtr primary_expr_ast;
|
|
||||||
String date_column_name;
|
|
||||||
ASTPtr sampling_expression;
|
|
||||||
size_t index_granularity;
|
|
||||||
|
|
||||||
size_t min_marks_for_seek;
|
|
||||||
size_t min_marks_for_concurrent_read;
|
|
||||||
size_t max_marks_to_use_cache;
|
|
||||||
|
|
||||||
/// Режим работы - какие дополнительные действия делать при мердже.
|
|
||||||
Mode mode;
|
|
||||||
/// Для схлопывания записей об изменениях, если используется Collapsing режим работы.
|
|
||||||
String sign_column;
|
|
||||||
|
|
||||||
StorageMergeTreeSettings settings;
|
|
||||||
|
|
||||||
ExpressionActionsPtr primary_expr;
|
|
||||||
SortDescription sort_descr;
|
|
||||||
Block primary_key_sample;
|
|
||||||
|
|
||||||
Increment increment;
|
Increment increment;
|
||||||
|
|
||||||
|
MergeTreeData data;
|
||||||
|
MergeTreeDataSelectExecutor reader;
|
||||||
|
MergeTreeDataWriter writer;
|
||||||
|
MergeTreeDataMerger merger;
|
||||||
|
|
||||||
|
MergeTreeData::DataParts currently_merging;
|
||||||
|
Poco::FastMutex currently_merging_mutex;
|
||||||
|
|
||||||
Logger * log;
|
Logger * log;
|
||||||
|
|
||||||
volatile bool shutdown_called;
|
volatile bool shutdown_called;
|
||||||
|
|
||||||
/// Регулярное выражение соответсвующее названию директории с кусочками
|
Poco::SharedPtr<boost::threadpool::pool> merge_threads;
|
||||||
Poco::RegularExpression file_name_regexp;
|
|
||||||
|
|
||||||
/// Описание куска с данными.
|
/// Пока существует, помечает части как currently_merging и держит резерв места.
|
||||||
struct DataPart
|
|
||||||
{
|
|
||||||
DataPart(StorageMergeTree & storage_) : storage(storage_), size_in_bytes(0), currently_merging(false) {}
|
|
||||||
|
|
||||||
StorageMergeTree & storage;
|
|
||||||
DayNum_t left_date;
|
|
||||||
DayNum_t right_date;
|
|
||||||
UInt64 left;
|
|
||||||
UInt64 right;
|
|
||||||
/// Уровень игнорируется. Использовался предыдущей эвристикой слияния.
|
|
||||||
UInt32 level;
|
|
||||||
|
|
||||||
std::string name;
|
|
||||||
size_t size; /// в количестве засечек.
|
|
||||||
size_t size_in_bytes; /// размер в байтах, 0 - если не посчитано
|
|
||||||
time_t modification_time;
|
|
||||||
|
|
||||||
DayNum_t left_month;
|
|
||||||
DayNum_t right_month;
|
|
||||||
|
|
||||||
/// Смотреть и изменять это поле следует под залоченным data_parts_mutex.
|
|
||||||
bool currently_merging;
|
|
||||||
|
|
||||||
/// Первичный ключ. Всегда загружается в оперативку.
|
|
||||||
typedef std::vector<Field> Index;
|
|
||||||
Index index;
|
|
||||||
|
|
||||||
/// NOTE можно загружать засечки тоже в оперативку
|
|
||||||
|
|
||||||
/// Вычисляем сумарный размер всей директории со всеми файлами
|
|
||||||
static size_t calcTotalSize(const String &from)
|
|
||||||
{
|
|
||||||
Poco::File cur(from);
|
|
||||||
if (cur.isFile())
|
|
||||||
return cur.getSize();
|
|
||||||
std::vector<std::string> files;
|
|
||||||
cur.list(files);
|
|
||||||
size_t res = 0;
|
|
||||||
for (size_t i = 0; i < files.size(); ++i)
|
|
||||||
res += calcTotalSize(from + files[i]);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove() const
|
|
||||||
{
|
|
||||||
String from = storage.full_path + name + "/";
|
|
||||||
String to = storage.full_path + "tmp2_" + name + "/";
|
|
||||||
|
|
||||||
Poco::File(from).renameTo(to);
|
|
||||||
Poco::File(to).remove(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void renameToOld() const
|
|
||||||
{
|
|
||||||
String from = storage.full_path + name + "/";
|
|
||||||
String to = storage.full_path + "old_" + name + "/";
|
|
||||||
|
|
||||||
Poco::File f(from);
|
|
||||||
f.setLastModified(Poco::Timestamp::fromEpochTime(time(0)));
|
|
||||||
f.renameTo(to);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator< (const DataPart & rhs) const
|
|
||||||
{
|
|
||||||
if (left_month < rhs.left_month)
|
|
||||||
return true;
|
|
||||||
if (left_month > rhs.left_month)
|
|
||||||
return false;
|
|
||||||
if (right_month < rhs.right_month)
|
|
||||||
return true;
|
|
||||||
if (right_month > rhs.right_month)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (left < rhs.left)
|
|
||||||
return true;
|
|
||||||
if (left > rhs.left)
|
|
||||||
return false;
|
|
||||||
if (right < rhs.right)
|
|
||||||
return true;
|
|
||||||
if (right > rhs.right)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (level < rhs.level)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Содержит другой кусок (получен после объединения другого куска с каким-то ещё)
|
|
||||||
bool contains(const DataPart & rhs) const
|
|
||||||
{
|
|
||||||
return left_month == rhs.left_month /// Куски за разные месяцы не объединяются
|
|
||||||
&& right_month == rhs.right_month
|
|
||||||
&& level > rhs.level
|
|
||||||
&& left_date <= rhs.left_date
|
|
||||||
&& right_date >= rhs.right_date
|
|
||||||
&& left <= rhs.left
|
|
||||||
&& right >= rhs.right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Загрузить индекс и вычислить размер.
|
|
||||||
void loadIndex()
|
|
||||||
{
|
|
||||||
size_t key_size = storage.sort_descr.size();
|
|
||||||
index.resize(key_size * size);
|
|
||||||
|
|
||||||
String index_path = storage.full_path + name + "/primary.idx";
|
|
||||||
ReadBufferFromFile index_file(index_path, std::min(static_cast<size_t>(DBMS_DEFAULT_BUFFER_SIZE), Poco::File(index_path).getSize()));
|
|
||||||
|
|
||||||
for (size_t i = 0; i < size; ++i)
|
|
||||||
for (size_t j = 0; j < key_size; ++j)
|
|
||||||
storage.primary_key_sample.getByPosition(j).type->deserializeBinary(index[i * key_size + j], index_file);
|
|
||||||
|
|
||||||
if (!index_file.eof())
|
|
||||||
throw Exception("index file " + index_path + " is unexpectedly long", ErrorCodes::EXPECTED_END_OF_FILE);
|
|
||||||
|
|
||||||
size_in_bytes = calcTotalSize(storage.full_path + name + "/");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef SharedPtr<DataPart> DataPartPtr;
|
|
||||||
struct DataPartPtrLess { bool operator() (const DataPartPtr & lhs, const DataPartPtr & rhs) const { return *lhs < *rhs; } };
|
|
||||||
typedef std::set<DataPartPtr, DataPartPtrLess> DataParts;
|
|
||||||
|
|
||||||
struct RangesInDataPart
|
|
||||||
{
|
|
||||||
DataPartPtr data_part;
|
|
||||||
MarkRanges ranges;
|
|
||||||
|
|
||||||
RangesInDataPart() {}
|
|
||||||
|
|
||||||
RangesInDataPart(DataPartPtr data_part_)
|
|
||||||
: data_part(data_part_)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Пока существует, помечает части как currently_merging и пересчитывает общий объем сливаемых данных.
|
|
||||||
/// Вероятно, что части будут помечены заранее.
|
/// Вероятно, что части будут помечены заранее.
|
||||||
class CurrentlyMergingPartsTagger
|
struct CurrentlyMergingPartsTagger
|
||||||
{
|
{
|
||||||
public:
|
MergeTreeData::DataPartsVector parts;
|
||||||
std::vector<DataPartPtr> parts;
|
DiskSpaceMonitor::ReservationPtr reserved_space;
|
||||||
Poco::FastMutex & data_mutex;
|
StorageMergeTree & storage;
|
||||||
|
|
||||||
CurrentlyMergingPartsTagger(const std::vector<DataPartPtr> & parts_, Poco::FastMutex & data_mutex_) : parts(parts_), data_mutex(data_mutex_)
|
CurrentlyMergingPartsTagger(const MergeTreeData::DataPartsVector & parts_, size_t total_size, StorageMergeTree & storage_)
|
||||||
|
: parts(parts_), storage(storage_)
|
||||||
{
|
{
|
||||||
/// Здесь не лочится мьютекс, так как конструктор вызывается внутри selectPartsToMerge, где он уже залочен
|
/// Здесь не лочится мьютекс, так как конструктор вызывается внутри mergeThread, где он уже залочен.
|
||||||
/// Poco::ScopedLock<Poco::FastMutex> lock(data_mutex);
|
reserved_space = DiskSpaceMonitor::reserve(storage.full_path, total_size); /// Может бросить исключение.
|
||||||
for (size_t i = 0; i < parts.size(); ++i)
|
for (const auto & part : parts)
|
||||||
{
|
{
|
||||||
parts[i]->currently_merging = true;
|
if (storage.currently_merging.count(part))
|
||||||
StorageMergeTree::total_size_of_currently_merging_parts += parts[i]->size_in_bytes;
|
throw Exception("Tagging alreagy tagged part " + part->name + ". This is a bug.", ErrorCodes::LOGICAL_ERROR);
|
||||||
}
|
}
|
||||||
|
storage.currently_merging.insert(parts.begin(), parts.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
~CurrentlyMergingPartsTagger()
|
~CurrentlyMergingPartsTagger()
|
||||||
{
|
{
|
||||||
Poco::ScopedLock<Poco::FastMutex> lock(data_mutex);
|
try
|
||||||
for (size_t i = 0; i < parts.size(); ++i)
|
|
||||||
{
|
{
|
||||||
parts[i]->currently_merging = false;
|
Poco::ScopedLock<Poco::FastMutex> lock(storage.currently_merging_mutex);
|
||||||
StorageMergeTree::total_size_of_currently_merging_parts -= parts[i]->size_in_bytes;
|
for (const auto & part : parts)
|
||||||
|
{
|
||||||
|
if (!storage.currently_merging.count(part))
|
||||||
|
throw Exception("Untagging already untagged part " + part->name + ". This is a bug.", ErrorCodes::LOGICAL_ERROR);
|
||||||
|
storage.currently_merging.erase(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
tryLogCurrentException("~CurrentlyMergingPartsTagger");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Сумарный размер currently_merging кусочков в байтах.
|
typedef Poco::SharedPtr<CurrentlyMergingPartsTagger> CurrentlyMergingPartsTaggerPtr;
|
||||||
/// Нужно чтобы оценить количество места на диске, которое может понадобится для завершения этих мерджей.
|
|
||||||
static size_t total_size_of_currently_merging_parts;
|
|
||||||
|
|
||||||
typedef std::vector<RangesInDataPart> RangesInDataParts;
|
|
||||||
|
|
||||||
/** @warning Если берете насколько блокировок, то берите их везде в одинаковом порядке - в том же как они написаны в этом файле */
|
|
||||||
/** Взятие этого лока на запись, запрещает мердж */
|
|
||||||
Poco::RWLock merge_lock;
|
|
||||||
|
|
||||||
/** Взятие этого лока на запись, запрещает запись */
|
|
||||||
Poco::RWLock write_lock;
|
|
||||||
|
|
||||||
/** Взятие этого лока на запись, запрещает чтение */
|
|
||||||
Poco::RWLock read_lock;
|
|
||||||
|
|
||||||
/** Актуальное множество кусков с данными. */
|
|
||||||
DataParts data_parts;
|
|
||||||
Poco::FastMutex data_parts_mutex;
|
|
||||||
|
|
||||||
/** Множество всех кусков с данными, включая уже слитые в более крупные, но ещё не удалённые. Оно обычно небольшое (десятки элементов).
|
|
||||||
* Ссылки на кусок есть отсюда, из списка актуальных кусков, и из каждого потока чтения, который его сейчас использует.
|
|
||||||
* То есть, если количество ссылок равно 1 - то кусок не актуален и не используется прямо сейчас, и его можно удалить.
|
|
||||||
*/
|
|
||||||
DataParts all_data_parts;
|
|
||||||
Poco::FastMutex all_data_parts_mutex;
|
|
||||||
|
|
||||||
StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_,
|
StorageMergeTree(const String & path_, const String & name_, NamesAndTypesListPtr columns_,
|
||||||
const Context & context_,
|
const Context & context_,
|
||||||
@ -447,38 +144,11 @@ private:
|
|||||||
const String & date_column_name_,
|
const String & date_column_name_,
|
||||||
const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается.
|
const ASTPtr & sampling_expression_, /// NULL, если семплирование не поддерживается.
|
||||||
size_t index_granularity_,
|
size_t index_granularity_,
|
||||||
Mode mode_ = Ordinary,
|
MergeTreeData::Mode mode_,
|
||||||
const String & sign_column_ = "",
|
const String & sign_column_,
|
||||||
const StorageMergeTreeSettings & settings_ = StorageMergeTreeSettings());
|
const MergeTreeSettings & settings_);
|
||||||
|
|
||||||
static String getPartName(DayNum_t left_date, DayNum_t right_date, UInt64 left_id, UInt64 right_id, UInt64 level);
|
|
||||||
|
|
||||||
BlockInputStreams spreadMarkRangesAmongThreads(
|
|
||||||
RangesInDataParts parts,
|
|
||||||
size_t threads,
|
|
||||||
const Names & column_names,
|
|
||||||
size_t max_block_size,
|
|
||||||
bool use_uncompressed_cache,
|
|
||||||
ExpressionActionsPtr prewhere_actions,
|
|
||||||
const String & prewhere_column);
|
|
||||||
|
|
||||||
BlockInputStreams spreadMarkRangesAmongThreadsFinal(
|
|
||||||
RangesInDataParts parts,
|
|
||||||
size_t threads,
|
|
||||||
const Names & column_names,
|
|
||||||
size_t max_block_size,
|
|
||||||
bool use_uncompressed_cache,
|
|
||||||
ExpressionActionsPtr prewhere_actions,
|
|
||||||
const String & prewhere_column);
|
|
||||||
|
|
||||||
/// Создать выражение "Sign == 1".
|
|
||||||
void createPositiveSignCondition(ExpressionActionsPtr & out_expression, String & out_column);
|
|
||||||
|
|
||||||
/// Загрузить множество кусков с данными с диска. Вызывается один раз - при создании объекта.
|
|
||||||
void loadDataParts();
|
|
||||||
|
|
||||||
/// Удалить неактуальные куски.
|
|
||||||
void clearOldParts();
|
|
||||||
|
|
||||||
/** Определяет, какие куски нужно объединять, и запускает их слияние в отдельном потоке. Если iterations = 0, объединяет, пока это возможно.
|
/** Определяет, какие куски нужно объединять, и запускает их слияние в отдельном потоке. Если iterations = 0, объединяет, пока это возможно.
|
||||||
* Если aggressive - выбрать куски не обращая внимание на соотношение размеров и их новизну (для запроса OPTIMIZE).
|
* Если aggressive - выбрать куски не обращая внимание на соотношение размеров и их новизну (для запроса OPTIMIZE).
|
||||||
@ -488,33 +158,11 @@ private:
|
|||||||
/// Если while_can, объединяет в цикле, пока можно; иначе выбирает и объединяет только одну пару кусков.
|
/// Если while_can, объединяет в цикле, пока можно; иначе выбирает и объединяет только одну пару кусков.
|
||||||
void mergeThread(bool while_can, bool aggressive);
|
void mergeThread(bool while_can, bool aggressive);
|
||||||
|
|
||||||
/// Сразу помечает их как currently_merging.
|
|
||||||
/// Если merge_anything_for_old_months, для кусков за прошедшие месяцы снимается ограничение на соотношение размеров.
|
|
||||||
bool selectPartsToMerge(Poco::SharedPtr<CurrentlyMergingPartsTagger> & what, bool merge_anything_for_old_months, bool aggressive);
|
|
||||||
|
|
||||||
void mergeParts(Poco::SharedPtr<CurrentlyMergingPartsTagger> & what);
|
|
||||||
|
|
||||||
/// Дождаться, пока фоновые потоки закончат слияния.
|
/// Дождаться, пока фоновые потоки закончат слияния.
|
||||||
void joinMergeThreads();
|
void joinMergeThreads();
|
||||||
|
|
||||||
Poco::SharedPtr<boost::threadpool::pool> merge_threads;
|
/// Вызывается во время выбора кусков для слияния.
|
||||||
|
bool canMergeParts(const MergeTreeData::DataPartPtr & left, const MergeTreeData::DataPartPtr & right);
|
||||||
void removeColumnFiles(String column_name);
|
|
||||||
|
|
||||||
/// Возвращает true если имя директории совпадает с форматом имени директории кусочков
|
|
||||||
bool isPartDirectory(const String & dir_name, Poco::RegularExpression::MatchVec & matches) const;
|
|
||||||
|
|
||||||
/// Кладет в DataPart данные из имени кусочка.
|
|
||||||
void parsePartName(const String & file_name, const Poco::RegularExpression::MatchVec & matches, DataPart & part);
|
|
||||||
|
|
||||||
/// Определить, не битые ли данные в директории. Проверяет индекс и засечеки, но не сами данные.
|
|
||||||
bool isBrokenPart(const String & path);
|
|
||||||
|
|
||||||
/// Найти самые большие old_куски, из которых получен этот кусок.
|
|
||||||
/// Переименовать их, убрав префикс old_ и вернуть их имена.
|
|
||||||
Strings tryRestorePart(const String & path, const String & file_name, Strings & old_parts);
|
|
||||||
|
|
||||||
void createConvertExpression(const String & in_column_name, const String & out_type, ExpressionActionsPtr & out_expression, String & out_column);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -11,6 +11,7 @@
|
|||||||
#include <DB/IO/CompressedWriteBuffer.h>
|
#include <DB/IO/CompressedWriteBuffer.h>
|
||||||
#include <DB/Storages/IStorage.h>
|
#include <DB/Storages/IStorage.h>
|
||||||
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
||||||
|
#include <DB/DataStreams/IBlockOutputStream.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
@ -22,20 +23,10 @@ class StorageTinyLog;
|
|||||||
class TinyLogBlockInputStream : public IProfilingBlockInputStream
|
class TinyLogBlockInputStream : public IProfilingBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage);
|
TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StorageTinyLog & storage_);
|
||||||
String getName() const { return "TinyLogBlockInputStream"; }
|
String getName() const { return "TinyLogBlockInputStream"; }
|
||||||
|
|
||||||
String getID() const
|
String getID() const;
|
||||||
{
|
|
||||||
std::stringstream res;
|
|
||||||
res << "TinyLog(" << owned_storage->getTableName() << ", " << &*owned_storage;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < column_names.size(); ++i)
|
|
||||||
res << ", " << column_names[i];
|
|
||||||
|
|
||||||
res << ")";
|
|
||||||
return res.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Block readImpl();
|
Block readImpl();
|
||||||
@ -68,7 +59,7 @@ private:
|
|||||||
class TinyLogBlockOutputStream : public IBlockOutputStream
|
class TinyLogBlockOutputStream : public IBlockOutputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TinyLogBlockOutputStream(StoragePtr owned_storage);
|
TinyLogBlockOutputStream(StorageTinyLog & storage_);
|
||||||
void write(const Block & block);
|
void write(const Block & block);
|
||||||
void writeSuffix();
|
void writeSuffix();
|
||||||
private:
|
private:
|
||||||
@ -134,7 +125,7 @@ public:
|
|||||||
BlockOutputStreamPtr write(
|
BlockOutputStreamPtr write(
|
||||||
ASTPtr query);
|
ASTPtr query);
|
||||||
|
|
||||||
void dropImpl();
|
void drop() override;
|
||||||
|
|
||||||
void rename(const String & new_path_to_db, const String & new_name);
|
void rename(const String & new_path_to_db, const String & new_name);
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ public:
|
|||||||
size_t max_block_size = DEFAULT_BLOCK_SIZE,
|
size_t max_block_size = DEFAULT_BLOCK_SIZE,
|
||||||
unsigned threads = 1);
|
unsigned threads = 1);
|
||||||
|
|
||||||
virtual void dropImpl();
|
virtual void drop() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
String select_database_name;
|
String select_database_name;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <Poco/SharedPtr.h>
|
#include <Poco/SharedPtr.h>
|
||||||
|
|
||||||
#include <DB/Storages/StoragePtr.h>
|
#include <DB/Storages/IStorage.h>
|
||||||
#include <DB/Parsers/ASTFunction.h>
|
#include <DB/Parsers/ASTFunction.h>
|
||||||
#include <DB/Interpreters/Context.h>
|
#include <DB/Interpreters/Context.h>
|
||||||
|
|
||||||
|
@ -55,15 +55,26 @@ private:
|
|||||||
{
|
{
|
||||||
OptimizedRegularExpression table_name_regexp(table_name_regexp_);
|
OptimizedRegularExpression table_name_regexp(table_name_regexp_);
|
||||||
|
|
||||||
|
StoragePtr any_table;
|
||||||
|
|
||||||
|
{
|
||||||
/// Список таблиц могут менять в другом потоке.
|
/// Список таблиц могут менять в другом потоке.
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
||||||
context.assertDatabaseExists(source_database);
|
context.assertDatabaseExists(source_database);
|
||||||
const Tables & tables = context.getDatabases().at(source_database);
|
const Tables & tables = context.getDatabases().at(source_database);
|
||||||
for (Tables::const_iterator it = tables.begin(); it != tables.end(); ++it)
|
for (Tables::const_iterator it = tables.begin(); it != tables.end(); ++it)
|
||||||
if (table_name_regexp.match(it->first))
|
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);
|
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());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,9 +56,13 @@ public:
|
|||||||
|
|
||||||
std::vector <std::vector< String> > names;
|
std::vector <std::vector< String> > names;
|
||||||
std::vector<String> shards = parseDescription(descripton, 0, descripton.size(), ',');
|
std::vector<String> shards = parseDescription(descripton, 0, descripton.size(), ',');
|
||||||
|
|
||||||
for (size_t i = 0; i < shards.size(); ++i)
|
for (size_t i = 0; i < shards.size(); ++i)
|
||||||
names.push_back(parseDescription(shards[i], 0, shards[i].size(), '|'));
|
names.push_back(parseDescription(shards[i], 0, shards[i].size(), '|'));
|
||||||
|
|
||||||
|
if (names.empty())
|
||||||
|
throw Exception("Shard list is empty after parsing first argument", ErrorCodes::BAD_ARGUMENTS);
|
||||||
|
|
||||||
SharedPtr<Cluster> cluster = new Cluster(context.getSettings(), context.getDataTypeFactory(), names, username, password);
|
SharedPtr<Cluster> cluster = new Cluster(context.getSettings(), context.getDataTypeFactory(), names, username, password);
|
||||||
|
|
||||||
return StorageDistributed::create(getName(), chooseColumns(*cluster, remote_database, remote_table, context),
|
return StorageDistributed::create(getName(), chooseColumns(*cluster, remote_database, remote_table, context),
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
#include <DB/Parsers/ASTExpressionList.h>
|
#include <DB/Parsers/ASTExpressionList.h>
|
||||||
#include <DB/Parsers/ASTLiteral.h>
|
#include <DB/Parsers/ASTLiteral.h>
|
||||||
#include <DB/Parsers/ASTSelectQuery.h>
|
#include <DB/Parsers/ASTSelectQuery.h>
|
||||||
#include <DB/Storages/StoragePtr.h>
|
#include <DB/Storages/IStorage.h>
|
||||||
#include <DB/Interpreters/InterpreterSelectQuery.h>
|
#include <DB/Interpreters/InterpreterSelectQuery.h>
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
|
@ -240,19 +240,22 @@ void Context::detachDatabase(const String & database_name)
|
|||||||
String db = database_name.empty() ? current_database : database_name;
|
String db = database_name.empty() ? current_database : database_name;
|
||||||
|
|
||||||
assertDatabaseExists(db);
|
assertDatabaseExists(db);
|
||||||
shared->databases.erase(shared->databases.find(db));
|
shared->databases.erase(db);
|
||||||
shared->database_droppers.erase(db);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ASTPtr Context::getCreateQuery(const String & database_name, const String & table_name) const
|
ASTPtr Context::getCreateQuery(const String & database_name, const String & table_name) const
|
||||||
|
{
|
||||||
|
StoragePtr table;
|
||||||
|
String db;
|
||||||
|
|
||||||
{
|
{
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
|
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;
|
auto table_lock = table->lockStructure(false);
|
||||||
|
|
||||||
assertDatabaseExists(db);
|
|
||||||
assertTableExists(db, table_name);
|
|
||||||
|
|
||||||
/// Здесь хранится определение таблицы
|
/// Здесь хранится определение таблицы
|
||||||
String metadata_path = shared->path + "metadata/" + escapeForFileName(db) + "/" + escapeForFileName(table_name) + ".sql";
|
String metadata_path = shared->path + "metadata/" + escapeForFileName(db) + "/" + escapeForFileName(table_name) + ".sql";
|
||||||
@ -262,7 +265,7 @@ ASTPtr Context::getCreateQuery(const String & database_name, const String & tabl
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
/// Если файл .sql не предусмотрен (например, для таблиц типа ChunkRef), то движок может сам предоставить запрос CREATE.
|
/// Если файл .sql не предусмотрен (например, для таблиц типа ChunkRef), то движок может сам предоставить запрос CREATE.
|
||||||
return getTable(database_name, table_name)->getCustomCreateQuery(*this);
|
return table->getCustomCreateQuery(*this);
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
@ -304,22 +307,6 @@ ASTPtr Context::getCreateQuery(const String & database_name, const String & tabl
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DatabaseDropperPtr Context::getDatabaseDropper(const String & name)
|
|
||||||
{
|
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
|
|
||||||
|
|
||||||
String db = name.empty() ? current_database : name;
|
|
||||||
|
|
||||||
if (shared->databases.count(db) == 0)
|
|
||||||
throw Exception("Database " + db + " doesn't exist", ErrorCodes::UNKNOWN_DATABASE);
|
|
||||||
|
|
||||||
if (shared->database_droppers.count(db) == 0)
|
|
||||||
shared->database_droppers[db] = DatabaseDropperPtr(new DatabaseDropper(shared->path + "data/" + escapeForFileName(db)));
|
|
||||||
|
|
||||||
return shared->database_droppers[db];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Settings Context::getSettings() const
|
Settings Context::getSettings() const
|
||||||
{
|
{
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
|
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
|
||||||
|
@ -1229,9 +1229,7 @@ Sets ExpressionAnalyzer::getSetsWithSubqueries()
|
|||||||
{
|
{
|
||||||
Sets res;
|
Sets res;
|
||||||
for (auto & s : sets_with_subqueries)
|
for (auto & s : sets_with_subqueries)
|
||||||
{
|
|
||||||
res.push_back(s.second);
|
res.push_back(s.second);
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,14 +46,7 @@ void InterpreterAlterQuery::execute()
|
|||||||
String database_name = alter.database.empty() ? context.getCurrentDatabase() : alter.database;
|
String database_name = alter.database.empty() ? context.getCurrentDatabase() : alter.database;
|
||||||
|
|
||||||
StoragePtr table = context.getTable(database_name, table_name);
|
StoragePtr table = context.getTable(database_name, table_name);
|
||||||
|
auto table_soft_lock = table->lockDataForAlter();
|
||||||
/// для merge tree запрещаем все операции
|
|
||||||
StorageMergeTree::BigLockPtr merge_tree_lock;
|
|
||||||
if (StorageMergeTree * table_merge_tree = dynamic_cast<StorageMergeTree *>(table.get()))
|
|
||||||
merge_tree_lock = table_merge_tree->lockAllOperations();
|
|
||||||
|
|
||||||
/// Poco::Mutex является рекурсивным, т.е. взятие мьютекса дважды из одного потока не приводит к блокировке
|
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
|
||||||
|
|
||||||
const DataTypeFactory & data_type_factory = context.getDataTypeFactory();
|
const DataTypeFactory & data_type_factory = context.getDataTypeFactory();
|
||||||
String path = context.getPath();
|
String path = context.getPath();
|
||||||
@ -68,7 +61,6 @@ void InterpreterAlterQuery::execute()
|
|||||||
attach.attach = true;
|
attach.attach = true;
|
||||||
ASTs & columns = dynamic_cast<ASTExpressionList &>(*attach.columns).children;
|
ASTs & columns = dynamic_cast<ASTExpressionList &>(*attach.columns).children;
|
||||||
|
|
||||||
|
|
||||||
/// Различные проверки, на возможность выполнения запроса
|
/// Различные проверки, на возможность выполнения запроса
|
||||||
ASTs columns_copy = columns;
|
ASTs columns_copy = columns;
|
||||||
IdentifierNameSet identifier_names;
|
IdentifierNameSet identifier_names;
|
||||||
@ -137,13 +129,33 @@ void InterpreterAlterQuery::execute()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Пока разрешим читать из таблицы. Запретим при первой попытке изменить структуру таблицы.
|
||||||
|
/// Это позволит сделать большую часть первого MODIFY, не останавливая чтение из таблицы.
|
||||||
|
IStorage::TableStructureWriteLockPtr table_hard_lock;
|
||||||
|
|
||||||
/// todo cycle over sub tables and tables
|
/// todo cycle over sub tables and tables
|
||||||
/// Применяем изменения
|
/// Применяем изменения
|
||||||
for (ASTAlterQuery::ParameterContainer::const_iterator alter_it = alter.parameters.begin();
|
for (ASTAlterQuery::ParameterContainer::const_iterator alter_it = alter.parameters.begin();
|
||||||
alter_it != alter.parameters.end(); ++alter_it)
|
alter_it != alter.parameters.end(); ++alter_it)
|
||||||
{
|
{
|
||||||
const ASTAlterQuery::Parameters & params = *alter_it;
|
const ASTAlterQuery::Parameters & params = *alter_it;
|
||||||
|
|
||||||
|
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);
|
table->alter(params);
|
||||||
|
}
|
||||||
|
|
||||||
if (params.type == ASTAlterQuery::ADD)
|
if (params.type == ASTAlterQuery::ADD)
|
||||||
{
|
{
|
||||||
|
@ -74,11 +74,26 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
|
|||||||
return StoragePtr();
|
return StoragePtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
StoragePtr res;
|
|
||||||
SharedPtr<InterpreterSelectQuery> interpreter_select;
|
SharedPtr<InterpreterSelectQuery> interpreter_select;
|
||||||
|
Block select_sample;
|
||||||
|
if (create.select && !create.attach)
|
||||||
|
{
|
||||||
|
interpreter_select = new InterpreterSelectQuery(create.select, context);
|
||||||
|
select_sample = interpreter_select->getSampleBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
StoragePtr res;
|
||||||
String storage_name;
|
String storage_name;
|
||||||
NamesAndTypesListPtr columns = new NamesAndTypesList;
|
NamesAndTypesListPtr columns = new NamesAndTypesList;
|
||||||
|
|
||||||
|
StoragePtr as_storage;
|
||||||
|
IStorage::TableStructureReadLockPtr as_storage_lock;
|
||||||
|
if (!as_table_name.empty())
|
||||||
|
{
|
||||||
|
as_storage = context.getTable(as_database_name, as_table_name);
|
||||||
|
as_storage_lock = as_storage->lockStructure(false);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
||||||
|
|
||||||
@ -92,12 +107,6 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
|
|||||||
throw Exception("Table " + database_name + "." + table_name + " already exists.", ErrorCodes::TABLE_ALREADY_EXISTS);
|
throw Exception("Table " + database_name + "." + table_name + " already exists.", ErrorCodes::TABLE_ALREADY_EXISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!create.as_table.empty())
|
|
||||||
context.assertTableExists(as_database_name, as_table_name);
|
|
||||||
|
|
||||||
if (create.select && !create.attach)
|
|
||||||
interpreter_select = new InterpreterSelectQuery(create.select, context);
|
|
||||||
|
|
||||||
/// Получаем список столбцов
|
/// Получаем список столбцов
|
||||||
if (create.columns)
|
if (create.columns)
|
||||||
{
|
{
|
||||||
@ -112,13 +121,12 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!create.as_table.empty())
|
else if (!create.as_table.empty())
|
||||||
columns = new NamesAndTypesList(context.getTable(as_database_name, as_table_name)->getColumnsList());
|
columns = new NamesAndTypesList(as_storage->getColumnsList());
|
||||||
else if (create.select)
|
else if (create.select)
|
||||||
{
|
{
|
||||||
Block sample = interpreter_select->getSampleBlock();
|
|
||||||
columns = new NamesAndTypesList;
|
columns = new NamesAndTypesList;
|
||||||
for (size_t i = 0; i < sample.columns(); ++i)
|
for (size_t i = 0; i < select_sample.columns(); ++i)
|
||||||
columns->push_back(NameAndTypePair(sample.getByPosition(i).name, sample.getByPosition(i).type));
|
columns->push_back(NameAndTypePair(select_sample.getByPosition(i).name, select_sample.getByPosition(i).type));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw Exception("Incorrect CREATE query: required list of column descriptions or AS section or SELECT.", ErrorCodes::INCORRECT_QUERY);
|
throw Exception("Incorrect CREATE query: required list of column descriptions or AS section or SELECT.", ErrorCodes::INCORRECT_QUERY);
|
||||||
@ -159,7 +167,7 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
|
|||||||
}
|
}
|
||||||
else if (!create.as_table.empty())
|
else if (!create.as_table.empty())
|
||||||
{
|
{
|
||||||
storage_name = context.getTable(as_database_name, as_table_name)->getName();
|
storage_name = as_storage->getName();
|
||||||
create.storage = dynamic_cast<const ASTCreateQuery &>(*context.getCreateQuery(as_database_name, as_table_name)).storage;
|
create.storage = dynamic_cast<const ASTCreateQuery &>(*context.getCreateQuery(as_database_name, as_table_name)).storage;
|
||||||
}
|
}
|
||||||
else if (create.is_view)
|
else if (create.is_view)
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
#include <Poco/File.h>
|
#include <Poco/File.h>
|
||||||
|
|
||||||
#include <DB/Common/escapeForFileName.h>
|
#include <DB/Common/escapeForFileName.h>
|
||||||
|
|
||||||
#include <DB/Parsers/ASTDropQuery.h>
|
#include <DB/Parsers/ASTDropQuery.h>
|
||||||
|
|
||||||
#include <DB/Interpreters/InterpreterDropQuery.h>
|
#include <DB/Interpreters/InterpreterDropQuery.h>
|
||||||
|
#include <DB/Storages/IStorage.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
@ -19,8 +18,6 @@ InterpreterDropQuery::InterpreterDropQuery(ASTPtr query_ptr_, Context & context_
|
|||||||
|
|
||||||
void InterpreterDropQuery::execute()
|
void InterpreterDropQuery::execute()
|
||||||
{
|
{
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
|
||||||
|
|
||||||
String path = context.getPath();
|
String path = context.getPath();
|
||||||
String current_database = context.getCurrentDatabase();
|
String current_database = context.getCurrentDatabase();
|
||||||
|
|
||||||
@ -28,92 +25,116 @@ void InterpreterDropQuery::execute()
|
|||||||
|
|
||||||
String database_name = drop.database.empty() ? current_database : drop.database;
|
String database_name = drop.database.empty() ? current_database : drop.database;
|
||||||
String database_name_escaped = escapeForFileName(database_name);
|
String database_name_escaped = escapeForFileName(database_name);
|
||||||
String table_name = drop.table;
|
|
||||||
String table_name_escaped = escapeForFileName(table_name);
|
|
||||||
|
|
||||||
String data_path = path + "data/" + database_name_escaped + "/" + table_name_escaped;
|
String data_path = path + "data/" + database_name_escaped + "/";
|
||||||
String metadata_path = path + "metadata/" + database_name_escaped + "/" + (!table_name.empty() ? table_name_escaped + ".sql" : "");
|
String metadata_path = path + "metadata/" + database_name_escaped + "/";
|
||||||
|
|
||||||
if (!drop.if_exists)
|
StorageVector tables_to_drop;
|
||||||
context.assertDatabaseExists(database_name);
|
|
||||||
|
|
||||||
if (!drop.table.empty())
|
if (!drop.table.empty())
|
||||||
{
|
{
|
||||||
/// Удаление таблицы
|
StoragePtr table;
|
||||||
if (!context.isTableExist(database_name, table_name))
|
|
||||||
{
|
if (drop.if_exists)
|
||||||
if (!drop.if_exists)
|
table = context.tryGetTable(database_name, drop.table);
|
||||||
throw Exception("Table " + database_name + "." + table_name + " doesn't exist.", ErrorCodes::UNKNOWN_TABLE);
|
else
|
||||||
|
table = context.getTable(database_name, drop.table);
|
||||||
|
|
||||||
|
if (table)
|
||||||
|
tables_to_drop.push_back(table);
|
||||||
|
else
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (!drop.detach)
|
||||||
{
|
{
|
||||||
StoragePtr table = context.getTable(database_name, table_name);
|
String current_data_path = data_path + escapeForFileName(current_table_name);
|
||||||
DatabaseDropperPtr database_dropper = context.getDatabaseDropper(database_name);
|
String current_metadata_path = metadata_path + escapeForFileName(current_table_name) + ".sql";
|
||||||
table->path_to_remove_on_drop = data_path;
|
|
||||||
/// Присвоим database_to_drop на случай, если БД попробуют удалить до завершения удаления этой таблицы.
|
|
||||||
table->database_to_drop = database_dropper;
|
|
||||||
table->drop();
|
|
||||||
|
|
||||||
/// Для таблиц типа ChunkRef, файла с метаданными не существует.
|
/// Для таблиц типа ChunkRef, файла с метаданными не существует.
|
||||||
if (Poco::File(metadata_path).exists())
|
if (Poco::File(current_metadata_path).exists())
|
||||||
Poco::File(metadata_path).remove();
|
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->drop();
|
||||||
table->shutdown();
|
table->is_dropped = true;
|
||||||
table = StoragePtr();
|
|
||||||
|
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);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,11 +65,14 @@ void InterpreterInsertQuery::execute(ReadBuffer * remaining_data_istr)
|
|||||||
ASTInsertQuery & query = dynamic_cast<ASTInsertQuery &>(*query_ptr);
|
ASTInsertQuery & query = dynamic_cast<ASTInsertQuery &>(*query_ptr);
|
||||||
StoragePtr table = getTable();
|
StoragePtr table = getTable();
|
||||||
|
|
||||||
|
auto table_lock = table->lockStructure(true);
|
||||||
|
|
||||||
BlockInputStreamPtr in;
|
BlockInputStreamPtr in;
|
||||||
NamesAndTypesListPtr required_columns = new NamesAndTypesList (table->getSampleBlock().getColumnsList());
|
NamesAndTypesListPtr required_columns = new NamesAndTypesList (table->getSampleBlock().getColumnsList());
|
||||||
/// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку.
|
/// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку.
|
||||||
table->write(query_ptr);
|
table->write(query_ptr);
|
||||||
/// Создаем кортеж из нескольких стримов, в которые будем писать данные.
|
/// Создаем кортеж из нескольких стримов, в которые будем писать данные.
|
||||||
|
|
||||||
BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns);
|
BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns);
|
||||||
|
|
||||||
/// Какой тип запроса: INSERT VALUES | INSERT FORMAT | INSERT SELECT?
|
/// Какой тип запроса: INSERT VALUES | INSERT FORMAT | INSERT SELECT?
|
||||||
@ -112,11 +115,13 @@ void InterpreterInsertQuery::execute(ReadBuffer * remaining_data_istr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BlockOutputStreamPtr InterpreterInsertQuery::execute()
|
BlockIO InterpreterInsertQuery::execute()
|
||||||
{
|
{
|
||||||
ASTInsertQuery & query = dynamic_cast<ASTInsertQuery &>(*query_ptr);
|
ASTInsertQuery & query = dynamic_cast<ASTInsertQuery &>(*query_ptr);
|
||||||
StoragePtr table = getTable();
|
StoragePtr table = getTable();
|
||||||
|
|
||||||
|
auto table_lock = table->lockStructure(true);
|
||||||
|
|
||||||
NamesAndTypesListPtr required_columns = new NamesAndTypesList(table->getSampleBlock().getColumnsList());
|
NamesAndTypesListPtr required_columns = new NamesAndTypesList(table->getSampleBlock().getColumnsList());
|
||||||
|
|
||||||
/// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку.
|
/// Надо убедиться, что запрос идет в таблицу, которая поддерживает вставку.
|
||||||
@ -124,18 +129,23 @@ BlockOutputStreamPtr InterpreterInsertQuery::execute()
|
|||||||
/// Создаем кортеж из нескольких стримов, в которые будем писать данные.
|
/// Создаем кортеж из нескольких стримов, в которые будем писать данные.
|
||||||
BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns);
|
BlockOutputStreamPtr out = new AddingDefaultBlockOutputStream(new PushingToViewsBlockOutputStream(query.database, query.table, context, query_ptr), required_columns);
|
||||||
|
|
||||||
|
BlockIO res;
|
||||||
|
res.out_sample = getSampleBlock();
|
||||||
|
|
||||||
/// Какой тип запроса: INSERT или INSERT SELECT?
|
/// Какой тип запроса: INSERT или INSERT SELECT?
|
||||||
if (!query.select)
|
if (!query.select)
|
||||||
return out;
|
{
|
||||||
|
res.out = out;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
InterpreterSelectQuery interpreter_select(query.select, context);
|
InterpreterSelectQuery interpreter_select(query.select, context);
|
||||||
BlockInputStreamPtr in = interpreter_select.execute();
|
BlockInputStreamPtr in = interpreter_select.execute();
|
||||||
in = new MaterializingBlockInputStream(in);
|
in = new MaterializingBlockInputStream(in);
|
||||||
copyData(*in, *out);
|
copyData(*in, *out);
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,8 +135,7 @@ BlockIO InterpreterQuery::execute()
|
|||||||
{
|
{
|
||||||
throwIfReadOnly();
|
throwIfReadOnly();
|
||||||
InterpreterInsertQuery interpreter(query_ptr, context);
|
InterpreterInsertQuery interpreter(query_ptr, context);
|
||||||
res.out = interpreter.execute();
|
res = interpreter.execute();
|
||||||
res.out_sample = interpreter.getSampleBlock();
|
|
||||||
}
|
}
|
||||||
else if (dynamic_cast<ASTCreateQuery *>(&*query_ptr))
|
else if (dynamic_cast<ASTCreateQuery *>(&*query_ptr))
|
||||||
{
|
{
|
||||||
|
@ -27,9 +27,6 @@ InterpreterRenameQuery::InterpreterRenameQuery(ASTPtr query_ptr_, Context & cont
|
|||||||
|
|
||||||
void InterpreterRenameQuery::execute()
|
void InterpreterRenameQuery::execute()
|
||||||
{
|
{
|
||||||
/** Все таблицы переименовываются под глобальной блокировкой. */
|
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
|
||||||
|
|
||||||
String path = context.getPath();
|
String path = context.getPath();
|
||||||
String current_database = context.getCurrentDatabase();
|
String current_database = context.getCurrentDatabase();
|
||||||
|
|
||||||
@ -53,11 +50,18 @@ void InterpreterRenameQuery::execute()
|
|||||||
String to_table_name_escaped = escapeForFileName(to_table_name);
|
String to_table_name_escaped = escapeForFileName(to_table_name);
|
||||||
String to_metadata_path = path + "metadata/" + to_database_name_escaped + "/" + (!to_table_name.empty() ? to_table_name_escaped + ".sql" : "");
|
String to_metadata_path = path + "metadata/" + to_database_name_escaped + "/" + (!to_table_name.empty() ? to_table_name_escaped + ".sql" : "");
|
||||||
|
|
||||||
context.assertTableExists(from_database_name, from_table_name);
|
/// Заблокировать таблицу нужно при незаблокированном контексте.
|
||||||
|
StoragePtr table = context.getTable(from_database_name, from_table_name);
|
||||||
|
auto table_lock = table->lockForAlter();
|
||||||
|
|
||||||
|
/** Все таблицы переименовываются под глобальной блокировкой. */
|
||||||
|
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
||||||
|
|
||||||
context.assertTableDoesntExist(to_database_name, to_table_name);
|
context.assertTableDoesntExist(to_database_name, to_table_name);
|
||||||
|
|
||||||
/// Уведомляем таблицу о том, что она переименовается. Если таблица не поддерживает переименование - кинется исключение.
|
/// Уведомляем таблицу о том, что она переименовается. Если таблица не поддерживает переименование - кинется исключение.
|
||||||
context.getTable(from_database_name, from_table_name)->rename(path + "data/" + to_database_name_escaped + "/", to_table_name);
|
|
||||||
|
table->rename(path + "data/" + to_database_name_escaped + "/", to_table_name);
|
||||||
|
|
||||||
/// Пишем новый файл с метаданными.
|
/// Пишем новый файл с метаданными.
|
||||||
{
|
{
|
||||||
|
@ -41,28 +41,42 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndType
|
|||||||
throw Exception("Too deep subqueries. Maximum: " + toString(settings.limits.max_subquery_depth),
|
throw Exception("Too deep subqueries. Maximum: " + toString(settings.limits.max_subquery_depth),
|
||||||
ErrorCodes::TOO_DEEP_SUBQUERIES);
|
ErrorCodes::TOO_DEEP_SUBQUERIES);
|
||||||
|
|
||||||
/// Если имееем дело с табличной функцией
|
if (query.table && dynamic_cast<ASTSelectQuery *>(&*query.table))
|
||||||
|
{
|
||||||
|
if (table_column_names.empty())
|
||||||
|
context.setColumns(InterpreterSelectQuery(query.table, context).getSampleBlock().getColumnsList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (query.table && dynamic_cast<const ASTFunction *>(&*query.table))
|
if (query.table && dynamic_cast<const ASTFunction *>(&*query.table))
|
||||||
{
|
{
|
||||||
/// Получить табличную функцию
|
/// Получить табличную функцию
|
||||||
TableFunctionPtr table_function_ptr = context.getTableFunctionFactory().get(dynamic_cast<const ASTFunction *>(&*query.table)->name, context);
|
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)
|
table_lock = storage->lockStructure(false);
|
||||||
context.setColumns(table_function_storage->getColumnsList());
|
if (table_column_names.empty())
|
||||||
else if (!table_column_names.empty())
|
context.setColumns(storage->getColumnsList());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table_column_names.empty())
|
||||||
context.setColumns(table_column_names);
|
context.setColumns(table_column_names);
|
||||||
else
|
|
||||||
context.setColumns(!query.table || !dynamic_cast<ASTSelectQuery *>(&*query.table)
|
|
||||||
? getTable()->getColumnsList()
|
|
||||||
: InterpreterSelectQuery(query.table, context).getSampleBlock().getColumnsList());
|
|
||||||
|
|
||||||
if (context.getColumns().empty())
|
if (context.getColumns().empty())
|
||||||
throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN);
|
throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN);
|
||||||
|
|
||||||
query_analyzer = new ExpressionAnalyzer(query_ptr, context, table_function_storage, subquery_depth);
|
query_analyzer = new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth);
|
||||||
|
|
||||||
if (input_)
|
if (input_)
|
||||||
streams.push_back(input_);
|
streams.push_back(input_);
|
||||||
@ -127,26 +141,6 @@ void InterpreterSelectQuery::getDatabaseAndTableNames(String & database_name, St
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StoragePtr InterpreterSelectQuery::getTable()
|
|
||||||
{
|
|
||||||
String database_name;
|
|
||||||
String table_name;
|
|
||||||
|
|
||||||
getDatabaseAndTableNames(database_name, table_name);
|
|
||||||
return context.getTable(database_name, table_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ASTPtr InterpreterSelectQuery::getCreateQuery()
|
|
||||||
{
|
|
||||||
String database_name;
|
|
||||||
String table_name;
|
|
||||||
|
|
||||||
getDatabaseAndTableNames(database_name, table_name);
|
|
||||||
return context.getCreateQuery(database_name, table_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
DataTypes InterpreterSelectQuery::getReturnTypes()
|
DataTypes InterpreterSelectQuery::getReturnTypes()
|
||||||
{
|
{
|
||||||
DataTypes res;
|
DataTypes res;
|
||||||
@ -210,6 +204,7 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
|
|||||||
bool has_having = false;
|
bool has_having = false;
|
||||||
bool has_order_by = !query.order_expression_list.isNull();
|
bool has_order_by = !query.order_expression_list.isNull();
|
||||||
|
|
||||||
|
ExpressionActionsPtr array_join;
|
||||||
ExpressionActionsPtr before_where;
|
ExpressionActionsPtr before_where;
|
||||||
ExpressionActionsPtr before_aggregation;
|
ExpressionActionsPtr before_aggregation;
|
||||||
ExpressionActionsPtr before_having;
|
ExpressionActionsPtr before_having;
|
||||||
@ -225,7 +220,8 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
|
|||||||
if (from_stage < QueryProcessingStage::WithMergeableState
|
if (from_stage < QueryProcessingStage::WithMergeableState
|
||||||
&& to_stage >= QueryProcessingStage::WithMergeableState)
|
&& to_stage >= QueryProcessingStage::WithMergeableState)
|
||||||
{
|
{
|
||||||
query_analyzer->appendArrayJoin(chain);
|
if (query_analyzer->appendArrayJoin(chain))
|
||||||
|
array_join = chain.getLastActions();
|
||||||
|
|
||||||
if (query_analyzer->appendWhere(chain))
|
if (query_analyzer->appendWhere(chain))
|
||||||
{
|
{
|
||||||
@ -312,6 +308,16 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
|
|||||||
if (need_aggregate)
|
if (need_aggregate)
|
||||||
executeAggregation(streams, before_aggregation, aggregate_overflow_row, aggregate_final);
|
executeAggregation(streams, before_aggregation, aggregate_overflow_row, aggregate_final);
|
||||||
|
|
||||||
|
if (array_join && !has_where && !need_aggregate && to_stage == QueryProcessingStage::WithMergeableState)
|
||||||
|
{
|
||||||
|
/** Если есть ARRAY JOIN, его действие сначала старается оказаться в
|
||||||
|
* before_where, before_aggregation или before_order_and_select.
|
||||||
|
* Если ни одного из них нет, array_join нужно выполнить отдельно.
|
||||||
|
*/
|
||||||
|
|
||||||
|
executeExpression(streams, array_join);
|
||||||
|
}
|
||||||
|
|
||||||
/** Оптимизация - при распределённой обработке запроса,
|
/** Оптимизация - при распределённой обработке запроса,
|
||||||
* если не указаны DISTINCT, GROUP, HAVING, ORDER, но указан LIMIT,
|
* если не указаны DISTINCT, GROUP, HAVING, ORDER, но указан LIMIT,
|
||||||
* то выполним предварительный LIMIT на удалёном сервере.
|
* то выполним предварительный LIMIT на удалёном сервере.
|
||||||
@ -339,7 +345,7 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
|
|||||||
executeHaving(streams, before_having);
|
executeHaving(streams, before_having);
|
||||||
}
|
}
|
||||||
|
|
||||||
executeOuterExpression(streams, before_order_and_select);
|
executeExpression(streams, before_order_and_select);
|
||||||
|
|
||||||
if (has_order_by)
|
if (has_order_by)
|
||||||
executeOrder(streams);
|
executeOrder(streams);
|
||||||
@ -424,20 +430,28 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
|
|||||||
if (!streams.empty())
|
if (!streams.empty())
|
||||||
return QueryProcessingStage::FetchColumns;
|
return QueryProcessingStage::FetchColumns;
|
||||||
|
|
||||||
/// Таблица, откуда читать данные, если не подзапрос.
|
|
||||||
StoragePtr table;
|
|
||||||
/// Интерпретатор подзапроса, если подзапрос
|
/// Интерпретатор подзапроса, если подзапрос
|
||||||
SharedPtr<InterpreterSelectQuery> interpreter_subquery;
|
SharedPtr<InterpreterSelectQuery> interpreter_subquery;
|
||||||
|
|
||||||
/// Список столбцов, которых нужно прочитать, чтобы выполнить запрос.
|
/// Список столбцов, которых нужно прочитать, чтобы выполнить запрос.
|
||||||
Names required_columns = query_analyzer->getRequiredColumns();
|
Names required_columns = query_analyzer->getRequiredColumns();
|
||||||
|
|
||||||
if (table_function_storage)
|
if (query.table && dynamic_cast<ASTSelectQuery *>(&*query.table))
|
||||||
table = table_function_storage; /// Если в запросе была указана табличная функция, данные читаем из нее.
|
{
|
||||||
else if (!query.table || !dynamic_cast<ASTSelectQuery *>(&*query.table))
|
/** Для подзапроса не действуют ограничения на максимальный размер результата.
|
||||||
table = getTable();
|
* Так как результат поздапроса - ещё не результат всего запроса.
|
||||||
else if (dynamic_cast<ASTSelectQuery *>(&*query.table))
|
*/
|
||||||
interpreter_subquery = new InterpreterSelectQuery(query.table, context, required_columns, QueryProcessingStage::Complete, subquery_depth + 1);
|
Context subquery_context = context;
|
||||||
|
Settings subquery_settings = context.getSettings();
|
||||||
|
subquery_settings.limits.max_result_rows = 0;
|
||||||
|
subquery_settings.limits.max_result_bytes = 0;
|
||||||
|
/// Вычисление extremes не имеет смысла и не нужно (если его делать, то в результате всего запроса могут взяться extremes подзапроса).
|
||||||
|
subquery_settings.extremes = 0;
|
||||||
|
subquery_context.setSettings(subquery_settings);
|
||||||
|
|
||||||
|
interpreter_subquery = new InterpreterSelectQuery(
|
||||||
|
query.table, subquery_context, required_columns, QueryProcessingStage::Complete, subquery_depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
/// если в настройках установлен default_sample != 1, то все запросы выполняем с сэмплингом
|
/// если в настройках установлен default_sample != 1, то все запросы выполняем с сэмплингом
|
||||||
/// если таблица не поддерживает сэмплинг получим исключение
|
/// если таблица не поддерживает сэмплинг получим исключение
|
||||||
@ -445,13 +459,13 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
|
|||||||
if (!query.sample_size && settings.default_sample != 1)
|
if (!query.sample_size && settings.default_sample != 1)
|
||||||
query.sample_size = new ASTLiteral(StringRange(NULL, NULL), Float64(settings.default_sample));
|
query.sample_size = new ASTLiteral(StringRange(NULL, NULL), Float64(settings.default_sample));
|
||||||
|
|
||||||
if (query.sample_size && (!table || !table->supportsSampling()))
|
if (query.sample_size && (!storage || !storage->supportsSampling()))
|
||||||
throw Exception("Illegal SAMPLE: table doesn't support sampling", ErrorCodes::SAMPLING_NOT_SUPPORTED);
|
throw Exception("Illegal SAMPLE: table doesn't support sampling", ErrorCodes::SAMPLING_NOT_SUPPORTED);
|
||||||
|
|
||||||
if (query.final && (!table || !table->supportsFinal()))
|
if (query.final && (!storage || !storage->supportsFinal()))
|
||||||
throw Exception("Illegal FINAL", ErrorCodes::ILLEGAL_FINAL);
|
throw Exception("Illegal FINAL", ErrorCodes::ILLEGAL_FINAL);
|
||||||
|
|
||||||
if (query.prewhere_expression && (!table || !table->supportsPrewhere()))
|
if (query.prewhere_expression && (!storage || !storage->supportsPrewhere()))
|
||||||
throw Exception("Illegal PREWHERE", ErrorCodes::ILLEGAL_PREWHERE);
|
throw Exception("Illegal PREWHERE", ErrorCodes::ILLEGAL_PREWHERE);
|
||||||
|
|
||||||
/** При распределённой обработке запроса, в потоках почти не делается вычислений,
|
/** При распределённой обработке запроса, в потоках почти не делается вычислений,
|
||||||
@ -466,7 +480,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
|
|||||||
* и там должно быть оригинальное значение max_threads, а не увеличенное.
|
* и там должно быть оригинальное значение max_threads, а не увеличенное.
|
||||||
*/
|
*/
|
||||||
Settings settings_for_storage = settings;
|
Settings settings_for_storage = settings;
|
||||||
if (table && table->isRemote())
|
if (storage && storage->isRemote())
|
||||||
settings.max_threads = settings.max_distributed_connections;
|
settings.max_threads = settings.max_distributed_connections;
|
||||||
|
|
||||||
/// Ограничение на количество столбцов для чтения.
|
/// Ограничение на количество столбцов для чтения.
|
||||||
@ -495,10 +509,18 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
|
|||||||
QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns;
|
QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns;
|
||||||
|
|
||||||
/// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос?
|
/// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос?
|
||||||
if (!query.table || !dynamic_cast<ASTSelectQuery *>(&*query.table))
|
if (!interpreter_subquery)
|
||||||
streams = table->read(required_columns, query_ptr, settings_for_storage, from_stage, settings.max_block_size, settings.max_threads);
|
{
|
||||||
|
streams = storage->read(required_columns, query_ptr, settings_for_storage, from_stage, settings.max_block_size, settings.max_threads);
|
||||||
|
for (auto stream : streams)
|
||||||
|
{
|
||||||
|
stream->addTableLock(table_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
streams.push_back(maybeAsynchronous(interpreter_subquery->execute(), settings.asynchronous));
|
streams.push_back(maybeAsynchronous(interpreter_subquery->execute(), settings.asynchronous));
|
||||||
|
}
|
||||||
|
|
||||||
/** Если истчоников слишком много, то склеим их в max_threads источников.
|
/** Если истчоников слишком много, то склеим их в max_threads источников.
|
||||||
* (Иначе действия в каждом маленьком источнике, а затем объединение состояний, слишком неэффективно.)
|
* (Иначе действия в каждом маленьком источнике, а затем объединение состояний, слишком неэффективно.)
|
||||||
@ -510,7 +532,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
|
|||||||
* Такие ограничения проверяются на сервере-инициаторе запроса, а не на удалённых серверах.
|
* Такие ограничения проверяются на сервере-инициаторе запроса, а не на удалённых серверах.
|
||||||
* Потому что сервер-инициатор имеет суммарные данные о выполнении запроса на всех серверах.
|
* Потому что сервер-инициатор имеет суммарные данные о выполнении запроса на всех серверах.
|
||||||
*/
|
*/
|
||||||
if (table && to_stage == QueryProcessingStage::Complete)
|
if (storage && to_stage == QueryProcessingStage::Complete)
|
||||||
{
|
{
|
||||||
IProfilingBlockInputStream::LocalLimits limits;
|
IProfilingBlockInputStream::LocalLimits limits;
|
||||||
limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL;
|
limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL;
|
||||||
@ -647,7 +669,7 @@ void InterpreterSelectQuery::executeTotalsAndHaving(BlockInputStreams & streams,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void InterpreterSelectQuery::executeOuterExpression(BlockInputStreams & streams, ExpressionActionsPtr expression)
|
void InterpreterSelectQuery::executeExpression(BlockInputStreams & streams, ExpressionActionsPtr expression)
|
||||||
{
|
{
|
||||||
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
|
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
|
||||||
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
|
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
|
||||||
|
@ -168,9 +168,7 @@ void formatAST(const ASTSelectQuery & ast, std::ostream & s, size_t indent, bo
|
|||||||
if (ast.prewhere_expression)
|
if (ast.prewhere_expression)
|
||||||
{
|
{
|
||||||
s << (hilite ? hilite_keyword : "") << nl_or_ws << indent_str << "PREWHERE " << (hilite ? hilite_none : "");
|
s << (hilite ? hilite_keyword : "") << nl_or_ws << indent_str << "PREWHERE " << (hilite ? hilite_none : "");
|
||||||
one_line
|
formatAST(*ast.prewhere_expression, s, indent, hilite, one_line);
|
||||||
? formatAST(*ast.prewhere_expression, s, indent, hilite, one_line)
|
|
||||||
: formatExpressionListMultiline(dynamic_cast<const ASTExpressionList &>(*ast.prewhere_expression), s, indent, hilite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ast.where_expression)
|
if (ast.where_expression)
|
||||||
@ -224,11 +222,12 @@ void formatAST(const ASTSelectQuery & ast, std::ostream & s, size_t indent, bo
|
|||||||
|
|
||||||
void formatAST(const ASTSubquery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
void formatAST(const ASTSubquery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
||||||
{
|
{
|
||||||
|
std::string indent_str = one_line ? "" : std::string(4 * indent, ' ');
|
||||||
std::string nl_or_nothing = one_line ? "" : "\n";
|
std::string nl_or_nothing = one_line ? "" : "\n";
|
||||||
|
|
||||||
s << nl_or_nothing << "(" << nl_or_nothing;
|
s << nl_or_nothing << indent_str << "(" << nl_or_nothing;
|
||||||
formatAST(*ast.children[0], s, indent + 1, hilite, one_line);
|
formatAST(*ast.children[0], s, indent + 1, hilite, one_line);
|
||||||
s << nl_or_nothing << ")";
|
s << nl_or_nothing << indent_str << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
void formatAST(const ASTCreateQuery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
void formatAST(const ASTCreateQuery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
||||||
@ -448,6 +447,10 @@ static void writeAlias(const String & name, std::ostream & s, bool hilite, bool
|
|||||||
|
|
||||||
void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
||||||
{
|
{
|
||||||
|
/// Если есть алиас, то требуются скобки вокруг всего выражения, включая алиас. Потому что запись вида 0 AS x + 0 синтаксически некорректна.
|
||||||
|
if (need_parens && !ast.alias.empty())
|
||||||
|
s << '(';
|
||||||
|
|
||||||
/// Стоит ли записать эту функцию в виде оператора?
|
/// Стоит ли записать эту функцию в виде оператора?
|
||||||
bool written = false;
|
bool written = false;
|
||||||
if (ast.arguments && !ast.parameters)
|
if (ast.arguments && !ast.parameters)
|
||||||
@ -557,34 +560,34 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool
|
|||||||
written = true;
|
written = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!written && 0 == strcmp(ast.name.c_str(), "array"))
|
if (!written && ast.arguments->children.size() >= 1 && 0 == strcmp(ast.name.c_str(), "array"))
|
||||||
{
|
{
|
||||||
s << '[';
|
s << (hilite ? hilite_operator : "") << '[' << (hilite ? hilite_none : "");
|
||||||
for (size_t i = 0; i < ast.arguments->children.size(); ++i)
|
for (size_t i = 0; i < ast.arguments->children.size(); ++i)
|
||||||
{
|
{
|
||||||
if (i != 0)
|
if (i != 0)
|
||||||
s << ", ";
|
s << ", ";
|
||||||
formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
|
formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
|
||||||
}
|
}
|
||||||
s << ']';
|
s << (hilite ? hilite_operator : "") << ']' << (hilite ? hilite_none : "");
|
||||||
written = true;
|
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)
|
for (size_t i = 0; i < ast.arguments->children.size(); ++i)
|
||||||
{
|
{
|
||||||
if (i != 0)
|
if (i != 0)
|
||||||
s << ", ";
|
s << ", ";
|
||||||
formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
|
formatAST(*ast.arguments->children[i], s, indent, hilite, one_line, false);
|
||||||
}
|
}
|
||||||
s << ')';
|
s << (hilite ? hilite_operator : "") << ')' << (hilite ? hilite_none : "");
|
||||||
written = true;
|
written = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!written)
|
if (!written)
|
||||||
{
|
{
|
||||||
@ -608,11 +611,18 @@ void formatAST(const ASTFunction & ast, std::ostream & s, size_t indent, bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ast.alias.empty())
|
if (!ast.alias.empty())
|
||||||
|
{
|
||||||
writeAlias(ast.alias, s, hilite, one_line);
|
writeAlias(ast.alias, s, hilite, one_line);
|
||||||
|
if (need_parens)
|
||||||
|
s << ')';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void formatAST(const ASTIdentifier & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
void formatAST(const ASTIdentifier & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
||||||
{
|
{
|
||||||
|
if (need_parens && !ast.alias.empty())
|
||||||
|
s << '(';
|
||||||
|
|
||||||
s << (hilite ? hilite_identifier : "");
|
s << (hilite ? hilite_identifier : "");
|
||||||
|
|
||||||
WriteBufferFromOStream wb(s, 32);
|
WriteBufferFromOStream wb(s, 32);
|
||||||
@ -622,15 +632,26 @@ void formatAST(const ASTIdentifier & ast, std::ostream & s, size_t indent, bo
|
|||||||
s << (hilite ? hilite_none : "");
|
s << (hilite ? hilite_none : "");
|
||||||
|
|
||||||
if (!ast.alias.empty())
|
if (!ast.alias.empty())
|
||||||
|
{
|
||||||
writeAlias(ast.alias, s, hilite, one_line);
|
writeAlias(ast.alias, s, hilite, one_line);
|
||||||
|
if (need_parens)
|
||||||
|
s << ')';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void formatAST(const ASTLiteral & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
void formatAST(const ASTLiteral & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
||||||
{
|
{
|
||||||
|
if (need_parens && !ast.alias.empty())
|
||||||
|
s << '(';
|
||||||
|
|
||||||
s << apply_visitor(FieldVisitorToString(), ast.value);
|
s << apply_visitor(FieldVisitorToString(), ast.value);
|
||||||
|
|
||||||
if (!ast.alias.empty())
|
if (!ast.alias.empty())
|
||||||
|
{
|
||||||
writeAlias(ast.alias, s, hilite, one_line);
|
writeAlias(ast.alias, s, hilite, one_line);
|
||||||
|
if (need_parens)
|
||||||
|
s << ')';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void formatAST(const ASTNameTypePair & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
void formatAST(const ASTNameTypePair & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens)
|
||||||
|
@ -523,7 +523,6 @@ struct UserAgentID : public IAttributeMetadata
|
|||||||
typedef AttributeIntBase ClickGoodEvent;
|
typedef AttributeIntBase ClickGoodEvent;
|
||||||
typedef AttributeIntBase ClickPriorityID;
|
typedef AttributeIntBase ClickPriorityID;
|
||||||
typedef AttributeIntBase ClickBannerID;
|
typedef AttributeIntBase ClickBannerID;
|
||||||
typedef AttributeIntBase ClickPhraseID;
|
|
||||||
typedef AttributeIntBase ClickPageID;
|
typedef AttributeIntBase ClickPageID;
|
||||||
typedef AttributeIntBase ClickPlaceID;
|
typedef AttributeIntBase ClickPlaceID;
|
||||||
typedef AttributeIntBase ClickTypeID;
|
typedef AttributeIntBase ClickTypeID;
|
||||||
@ -532,7 +531,6 @@ typedef AttributeUIntBase ClickDomainID;
|
|||||||
typedef AttributeUIntBase ClickCost;
|
typedef AttributeUIntBase ClickCost;
|
||||||
typedef AttributeHashBase ClickURLHash;
|
typedef AttributeHashBase ClickURLHash;
|
||||||
typedef AttributeUIntBase ClickOrderID;
|
typedef AttributeUIntBase ClickOrderID;
|
||||||
typedef AttributeUIntBase ClickTargetPhraseID;
|
|
||||||
typedef AttributeUIntBase GoalReachesAny;
|
typedef AttributeUIntBase GoalReachesAny;
|
||||||
typedef AttributeUIntBase GoalReachesDepth;
|
typedef AttributeUIntBase GoalReachesDepth;
|
||||||
typedef AttributeUIntBase GoalReachesURL;
|
typedef AttributeUIntBase GoalReachesURL;
|
||||||
@ -728,7 +726,6 @@ inline AttributeMetadatas GetOLAPAttributeMetadata()
|
|||||||
("ClickGoodEvent", new ClickGoodEvent)
|
("ClickGoodEvent", new ClickGoodEvent)
|
||||||
("ClickPriorityID", new ClickPriorityID)
|
("ClickPriorityID", new ClickPriorityID)
|
||||||
("ClickBannerID", new ClickBannerID)
|
("ClickBannerID", new ClickBannerID)
|
||||||
("ClickPhraseID", new ClickPhraseID)
|
|
||||||
("ClickPageID", new ClickPageID)
|
("ClickPageID", new ClickPageID)
|
||||||
("ClickPlaceID", new ClickPlaceID)
|
("ClickPlaceID", new ClickPlaceID)
|
||||||
("ClickTypeID", new ClickTypeID)
|
("ClickTypeID", new ClickTypeID)
|
||||||
@ -737,7 +734,6 @@ inline AttributeMetadatas GetOLAPAttributeMetadata()
|
|||||||
("ClickCost", new ClickCost)
|
("ClickCost", new ClickCost)
|
||||||
("ClickURLHash", new ClickURLHash)
|
("ClickURLHash", new ClickURLHash)
|
||||||
("ClickOrderID", new ClickOrderID)
|
("ClickOrderID", new ClickOrderID)
|
||||||
("ClickTargetPhraseID", new ClickTargetPhraseID)
|
|
||||||
("GoalReaches", new GoalReaches)
|
("GoalReaches", new GoalReaches)
|
||||||
("GoalReachesAny", new GoalReachesAny)
|
("GoalReachesAny", new GoalReachesAny)
|
||||||
("GoalReachesDepth", new GoalReachesDepth)
|
("GoalReachesDepth", new GoalReachesDepth)
|
||||||
|
@ -550,7 +550,6 @@ void QueryConverter::fillNumericAttributeMap()
|
|||||||
M("ClickGoodEvent", "Clicks.GoodEvent[1]")
|
M("ClickGoodEvent", "Clicks.GoodEvent[1]")
|
||||||
M("ClickPriorityID", "Clicks.PriorityID[1]")
|
M("ClickPriorityID", "Clicks.PriorityID[1]")
|
||||||
M("ClickBannerID", "Clicks.BannerID[1]")
|
M("ClickBannerID", "Clicks.BannerID[1]")
|
||||||
M("ClickPhraseID", "Clicks.PhraseID[1]")
|
|
||||||
M("ClickPageID", "Clicks.PageID[1]")
|
M("ClickPageID", "Clicks.PageID[1]")
|
||||||
M("ClickPlaceID", "Clicks.PlaceID[1]")
|
M("ClickPlaceID", "Clicks.PlaceID[1]")
|
||||||
M("ClickTypeID", "Clicks.TypeID[1]")
|
M("ClickTypeID", "Clicks.TypeID[1]")
|
||||||
@ -559,7 +558,6 @@ void QueryConverter::fillNumericAttributeMap()
|
|||||||
M("ClickCost", "Clicks.Cost[1]")
|
M("ClickCost", "Clicks.Cost[1]")
|
||||||
M("ClickURLHash", "Clicks.URLHash[1]")
|
M("ClickURLHash", "Clicks.URLHash[1]")
|
||||||
M("ClickOrderID", "Clicks.OrderID[1]")
|
M("ClickOrderID", "Clicks.OrderID[1]")
|
||||||
M("ClickTargetPhraseID", "Clicks.TargetPhraseID[1]")
|
|
||||||
M("GoalReachesAny", "GoalReachesAny")
|
M("GoalReachesAny", "GoalReachesAny")
|
||||||
M("GoalReachesDepth", "GoalReachesDepth")
|
M("GoalReachesDepth", "GoalReachesDepth")
|
||||||
M("GoalReachesURL", "GoalReachesURL")
|
M("GoalReachesURL", "GoalReachesURL")
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
#include <set>
|
|
||||||
#include <boost/bind.hpp>
|
|
||||||
|
|
||||||
#include <sparsehash/dense_hash_set>
|
|
||||||
#include <sparsehash/dense_hash_map>
|
#include <sparsehash/dense_hash_map>
|
||||||
|
#include <sparsehash/dense_hash_set>
|
||||||
#include <DB/Columns/ColumnNested.h>
|
#include <DB/Storages/ITableDeclaration.h>
|
||||||
#include <DB/DataTypes/DataTypeNested.h>
|
#include <DB/DataTypes/DataTypeNested.h>
|
||||||
#include <DB/Storages/IStorage.h>
|
|
||||||
#include <DB/Parsers/ASTIdentifier.h>
|
#include <DB/Parsers/ASTIdentifier.h>
|
||||||
#include <DB/Parsers/ASTNameTypePair.h>
|
#include <DB/Parsers/ASTNameTypePair.h>
|
||||||
#include <DB/Interpreters/Context.h>
|
#include <DB/Interpreters/Context.h>
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
bool IStorage::hasRealColumn(const String &column_name) const
|
bool ITableDeclaration::hasRealColumn(const String &column_name) const
|
||||||
{
|
{
|
||||||
const NamesAndTypesList & real_columns = getColumnsList();
|
const NamesAndTypesList & real_columns = getColumnsList();
|
||||||
for (auto & it : real_columns)
|
for (auto & it : real_columns)
|
||||||
@ -25,7 +20,7 @@ bool IStorage::hasRealColumn(const String &column_name) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NameAndTypePair IStorage::getRealColumn(const String &column_name) const
|
NameAndTypePair ITableDeclaration::getRealColumn(const String &column_name) const
|
||||||
{
|
{
|
||||||
const NamesAndTypesList & real_columns = getColumnsList();
|
const NamesAndTypesList & real_columns = getColumnsList();
|
||||||
for (auto & it : real_columns)
|
for (auto & it : real_columns)
|
||||||
@ -35,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); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
|
return hasRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NameAndTypePair IStorage::getColumn(const String &column_name) const
|
NameAndTypePair ITableDeclaration::getColumn(const String &column_name) const
|
||||||
{
|
{
|
||||||
return getRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
|
return getRealColumn(column_name); /// По умолчанию считаем, что виртуальных столбцов в сторадже нет.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const DataTypePtr IStorage::getDataTypeByName(const String & column_name) const
|
const DataTypePtr ITableDeclaration::getDataTypeByName(const String & column_name) const
|
||||||
{
|
{
|
||||||
const NamesAndTypesList & names_and_types = getColumnsList();
|
const NamesAndTypesList & names_and_types = getColumnsList();
|
||||||
for (NamesAndTypesList::const_iterator it = names_and_types.begin(); it != names_and_types.end(); ++it)
|
for (NamesAndTypesList::const_iterator it = names_and_types.begin(); it != names_and_types.end(); ++it)
|
||||||
@ -58,7 +53,7 @@ const DataTypePtr IStorage::getDataTypeByName(const String & column_name) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Block IStorage::getSampleBlock() const
|
Block ITableDeclaration::getSampleBlock() const
|
||||||
{
|
{
|
||||||
Block res;
|
Block res;
|
||||||
const NamesAndTypesList & names_and_types = getColumnsList();
|
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();
|
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 NamesAndTypesList & available_columns = getColumnsList();
|
||||||
const NamesAndTypesMap & columns_map = getColumnsMap(available_columns);
|
const NamesAndTypesMap & columns_map = getColumnsMap(available_columns);
|
||||||
@ -174,7 +169,6 @@ void IStorage::check(const Block & block, bool need_all) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// одинаковыми считаются имена, если они совпадают целиком или name_without_dot совпадает с частью имени до точки
|
/// одинаковыми считаются имена, если они совпадают целиком или name_without_dot совпадает с частью имени до точки
|
||||||
static bool namesEqual(const String & name_without_dot, const DB::NameAndTypePair & name_type)
|
static bool namesEqual(const String & name_without_dot, const DB::NameAndTypePair & name_type)
|
||||||
{
|
{
|
||||||
@ -182,7 +176,7 @@ static bool namesEqual(const String & name_without_dot, const DB::NameAndTypePai
|
|||||||
return (name_with_dot == name_type.first.substr(0, name_without_dot.length() + 1) || name_without_dot == name_type.first);
|
return (name_with_dot == name_type.first.substr(0, name_without_dot.length() + 1) || name_without_dot == name_type.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IStorage::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context) const
|
void ITableDeclaration::alterColumns(const ASTAlterQuery::Parameters & params, NamesAndTypesListPtr & columns, const Context & context)
|
||||||
{
|
{
|
||||||
if (params.type == ASTAlterQuery::ADD)
|
if (params.type == ASTAlterQuery::ADD)
|
||||||
{
|
{
|
9
dbms/src/Storages/MergeTree/DiskSpaceMonitor.cpp
Normal file
9
dbms/src/Storages/MergeTree/DiskSpaceMonitor.cpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#include <DB/Storages/MergeTree/DiskSpaceMonitor.h>
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
|
||||||
|
size_t DiskSpaceMonitor::reserved_bytes;
|
||||||
|
Poco::FastMutex DiskSpaceMonitor::reserved_bytes_mutex;
|
||||||
|
|
||||||
|
}
|
690
dbms/src/Storages/MergeTree/MergeTreeData.cpp
Normal file
690
dbms/src/Storages/MergeTree/MergeTreeData.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
348
dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp
Normal file
348
dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
466
dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp
Normal file
466
dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
248
dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp
Normal file
248
dbms/src/Storages/MergeTree/MergeTreeDataWriter.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -10,6 +10,7 @@
|
|||||||
#include <DB/Parsers/ParserCreateQuery.h>
|
#include <DB/Parsers/ParserCreateQuery.h>
|
||||||
#include <DB/Parsers/formatAST.h>
|
#include <DB/Parsers/formatAST.h>
|
||||||
#include <DB/Interpreters/executeQuery.h>
|
#include <DB/Interpreters/executeQuery.h>
|
||||||
|
#include <DB/Interpreters/InterpreterDropQuery.h>
|
||||||
#include <DB/DataStreams/copyData.h>
|
#include <DB/DataStreams/copyData.h>
|
||||||
#include <DB/DataStreams/ConcatBlockInputStream.h>
|
#include <DB/DataStreams/ConcatBlockInputStream.h>
|
||||||
#include <DB/DataStreams/narrowBlockInputStreams.h>
|
#include <DB/DataStreams/narrowBlockInputStreams.h>
|
||||||
@ -85,7 +86,7 @@ BlockInputStreams StorageChunkMerger::read(
|
|||||||
{
|
{
|
||||||
if (chunk_ref->source_database_name != source_database)
|
if (chunk_ref->source_database_name != source_database)
|
||||||
{
|
{
|
||||||
LOG_WARNING(log, "ChunkRef " + chunk_ref->getTableName() + " points to another database, ignoring");
|
LOG_WARNING(log, "ChunkRef " + it->first + " points to another database, ignoring");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!chunks_table_names.count(chunk_ref->source_table_name))
|
if (!chunks_table_names.count(chunk_ref->source_table_name))
|
||||||
@ -97,7 +98,7 @@ BlockInputStreams StorageChunkMerger::read(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_WARNING(log, "ChunkRef " + chunk_ref->getTableName() + " points to non-existing Chunks table, ignoring");
|
LOG_WARNING(log, "ChunkRef " + it->first + " points to non-existing Chunks table, ignoring");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,6 +110,14 @@ BlockInputStreams StorageChunkMerger::read(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TableLocks table_locks;
|
||||||
|
|
||||||
|
/// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем.
|
||||||
|
for (auto table : selected_tables)
|
||||||
|
{
|
||||||
|
table_locks.push_back(table->lockStructure(false));
|
||||||
|
}
|
||||||
|
|
||||||
BlockInputStreams res;
|
BlockInputStreams res;
|
||||||
|
|
||||||
/// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную.
|
/// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную.
|
||||||
@ -130,33 +139,36 @@ BlockInputStreams StorageChunkMerger::read(
|
|||||||
else /// Иначе, считаем допустимыми все возможные значения
|
else /// Иначе, считаем допустимыми все возможные значения
|
||||||
virtual_columns = new OneBlockInputStream(virtual_columns_block);
|
virtual_columns = new OneBlockInputStream(virtual_columns_block);
|
||||||
|
|
||||||
std::set<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name);
|
std::multiset<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name);
|
||||||
bool all_inclusive = (values.size() == virtual_columns_block.rows());
|
bool all_inclusive = (values.size() == virtual_columns_block.rows());
|
||||||
|
|
||||||
for (Storages::iterator it = selected_tables.begin(); it != selected_tables.end(); ++it)
|
for (size_t i = 0; i < selected_tables.size(); ++i)
|
||||||
{
|
{
|
||||||
if ((*it)->getName() != "Chunks" && !all_inclusive && values.find((*it)->getTableName()) == values.end())
|
StoragePtr table = selected_tables[i];
|
||||||
|
auto table_lock = table_locks[i];
|
||||||
|
|
||||||
|
if (table->getName() != "Chunks" && !all_inclusive && values.find(table->getTableName()) == values.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/// Список виртуальных столбцов, которые мы заполним сейчас и список столбцов, которые передадим дальше
|
/// Список виртуальных столбцов, которые мы заполним сейчас и список столбцов, которые передадим дальше
|
||||||
Names virt_column_names, real_column_names;
|
Names virt_column_names, real_column_names;
|
||||||
for (const auto & column : column_names)
|
for (const auto & column : column_names)
|
||||||
if (column == _table_column_name && (*it)->getName() != "Chunks") /// таблица Chunks сама заполняет столбец _table
|
if (column == _table_column_name && table->getName() != "Chunks") /// таблица Chunks сама заполняет столбец _table
|
||||||
virt_column_names.push_back(column);
|
virt_column_names.push_back(column);
|
||||||
else
|
else
|
||||||
real_column_names.push_back(column);
|
real_column_names.push_back(column);
|
||||||
|
|
||||||
/// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой.
|
/// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой.
|
||||||
if (real_column_names.size() == 0)
|
if (real_column_names.size() == 0)
|
||||||
real_column_names.push_back(ExpressionActions::getSmallestColumn((*it)->getColumnsList()));
|
real_column_names.push_back(ExpressionActions::getSmallestColumn(table->getColumnsList()));
|
||||||
|
|
||||||
ASTPtr modified_query_ast = query->clone();
|
ASTPtr modified_query_ast = query->clone();
|
||||||
|
|
||||||
/// Подменяем виртуальный столбец на его значение
|
/// Подменяем виртуальный столбец на его значение
|
||||||
if (!virt_column_names.empty())
|
if (!virt_column_names.empty())
|
||||||
VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, (*it)->getTableName());
|
VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, table->getTableName());
|
||||||
|
|
||||||
BlockInputStreams source_streams = (*it)->read(
|
BlockInputStreams source_streams = table->read(
|
||||||
real_column_names,
|
real_column_names,
|
||||||
modified_query_ast,
|
modified_query_ast,
|
||||||
settings,
|
settings,
|
||||||
@ -164,6 +176,11 @@ BlockInputStreams StorageChunkMerger::read(
|
|||||||
max_block_size,
|
max_block_size,
|
||||||
selected_tables.size() > threads ? 1 : (threads / selected_tables.size()));
|
selected_tables.size() > threads ? 1 : (threads / selected_tables.size()));
|
||||||
|
|
||||||
|
for (auto & stream : source_streams)
|
||||||
|
{
|
||||||
|
stream->addTableLock(table_lock);
|
||||||
|
}
|
||||||
|
|
||||||
/// Добавляем в ответ вирутальные столбцы
|
/// Добавляем в ответ вирутальные столбцы
|
||||||
for (const auto & virtual_column : virt_column_names)
|
for (const auto & virtual_column : virt_column_names)
|
||||||
{
|
{
|
||||||
@ -171,7 +188,7 @@ BlockInputStreams StorageChunkMerger::read(
|
|||||||
{
|
{
|
||||||
for (auto & stream : source_streams)
|
for (auto & stream : source_streams)
|
||||||
{
|
{
|
||||||
stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, (*it)->getTableName(), _table_column_name);
|
stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, table->getTableName(), _table_column_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,6 +353,14 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
|
|||||||
{
|
{
|
||||||
typedef std::map<std::string, DataTypePtr> ColumnsMap;
|
typedef std::map<std::string, DataTypePtr> ColumnsMap;
|
||||||
|
|
||||||
|
TableLocks table_locks;
|
||||||
|
|
||||||
|
/// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем.
|
||||||
|
for (auto table : chunks)
|
||||||
|
{
|
||||||
|
table_locks.push_back(table->lockStructure(false));
|
||||||
|
}
|
||||||
|
|
||||||
/// Объединим множества столбцов сливаемых чанков.
|
/// Объединим множества столбцов сливаемых чанков.
|
||||||
ColumnsMap known_columns_types(columns->begin(), columns->end());
|
ColumnsMap known_columns_types(columns->begin(), columns->end());
|
||||||
NamesAndTypesListPtr required_columns = new NamesAndTypesList;
|
NamesAndTypesListPtr required_columns = new NamesAndTypesList;
|
||||||
@ -387,6 +412,7 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
|
|||||||
}
|
}
|
||||||
|
|
||||||
currently_written_groups.insert(new_table_full_name);
|
currently_written_groups.insert(new_table_full_name);
|
||||||
|
}
|
||||||
|
|
||||||
/// Уроним Chunks таблицу с таким именем, если она есть. Она могла остаться в результате прерванного слияния той же группы чанков.
|
/// Уроним Chunks таблицу с таким именем, если она есть. Она могла остаться в результате прерванного слияния той же группы чанков.
|
||||||
executeQuery("DROP TABLE IF EXISTS " + new_table_full_name, context, true);
|
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);
|
executeQuery("CREATE TABLE " + new_table_full_name + " " + formatted_columns + " ENGINE = Chunks", context, true);
|
||||||
|
|
||||||
new_storage_ptr = context.getTable(source_database, new_table_name);
|
new_storage_ptr = context.getTable(source_database, new_table_name);
|
||||||
}
|
|
||||||
|
|
||||||
/// Скопируем данные в новую таблицу.
|
/// Скопируем данные в новую таблицу.
|
||||||
StorageChunks & new_storage = dynamic_cast<StorageChunks &>(*new_storage_ptr);
|
StorageChunks & new_storage = dynamic_cast<StorageChunks &>(*new_storage_ptr);
|
||||||
@ -455,6 +480,9 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Атомарно подменим исходные таблицы ссылками на новую.
|
/// Атомарно подменим исходные таблицы ссылками на новую.
|
||||||
|
/// При этом удалять таблицы под мьютексом контекста нельзя, пока только отцепим их.
|
||||||
|
Storages tables_to_drop;
|
||||||
|
|
||||||
{
|
{
|
||||||
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
|
||||||
|
|
||||||
@ -467,13 +495,13 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
|
|||||||
std::string src_name = src_storage->getTableName();
|
std::string src_name = src_storage->getTableName();
|
||||||
|
|
||||||
/// Если таблицу успели удалить, ничего не делаем.
|
/// Если таблицу успели удалить, ничего не делаем.
|
||||||
if (!context.getDatabases()[source_database].count(src_name))
|
if (!context.isTableExist(source_database, src_name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/// Роняем исходную таблицу.
|
/// Отцепляем исходную таблицу. Ее данные и метаданные остаются на диске.
|
||||||
executeQuery("DROP TABLE " + backQuoteIfNeed(source_database) + "." + backQuoteIfNeed(src_name), context, true);
|
tables_to_drop.push_back(context.detachTable(source_database, src_name));
|
||||||
|
|
||||||
/// Создаем на ее месте ChunkRef
|
/// Создаем на ее месте ChunkRef. Это возможно только потому что у ChunkRef нет ни, ни метаданных.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.addTable(source_database, src_name, StorageChunkRef::create(src_name, context, source_database, new_table_name, false));
|
context.addTable(source_database, src_name, StorageChunkRef::create(src_name, context, source_database, new_table_name, false));
|
||||||
@ -490,6 +518,15 @@ bool StorageChunkMerger::mergeChunks(const Storages & chunks)
|
|||||||
currently_written_groups.erase(new_table_full_name);
|
currently_written_groups.erase(new_table_full_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Теперь удалим данные отцепленных таблиц.
|
||||||
|
table_locks.clear();
|
||||||
|
for (StoragePtr table : tables_to_drop)
|
||||||
|
{
|
||||||
|
InterpreterDropQuery::dropDetachedTable(source_database, table, context);
|
||||||
|
/// NOTE: Если между подменой таблицы и этой строчкой кто-то успеет попытаться создать новую таблицу на ее месте,
|
||||||
|
/// что-нибудь может сломаться.
|
||||||
|
}
|
||||||
|
|
||||||
/// Сейчас на new_storage ссылаются таблицы типа ChunkRef. Удалим лишнюю ссылку, которая была при создании.
|
/// Сейчас на new_storage ссылаются таблицы типа ChunkRef. Удалим лишнюю ссылку, которая была при создании.
|
||||||
new_storage.removeReference();
|
new_storage.removeReference();
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ ASTPtr StorageChunkRef::getCustomCreateQuery(const Context & context) const
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StorageChunkRef::dropImpl()
|
void StorageChunkRef::drop()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -57,7 +57,7 @@ BlockInputStreams StorageChunks::read(
|
|||||||
Block virtual_columns_block = getBlockWithVirtualColumns();
|
Block virtual_columns_block = getBlockWithVirtualColumns();
|
||||||
BlockInputStreamPtr virtual_columns =
|
BlockInputStreamPtr virtual_columns =
|
||||||
VirtualColumnUtils::getVirtualColumnsBlocks(query->clone(), virtual_columns_block, context);
|
VirtualColumnUtils::getVirtualColumnsBlocks(query->clone(), virtual_columns_block, context);
|
||||||
std::set<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name);
|
std::multiset<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name);
|
||||||
bool all_inclusive = (values.size() == virtual_columns_block.rows());
|
bool all_inclusive = (values.size() == virtual_columns_block.rows());
|
||||||
|
|
||||||
if (all_inclusive)
|
if (all_inclusive)
|
||||||
|
@ -177,12 +177,12 @@ BlockInputStreams StorageDistributed::read(
|
|||||||
else /// Иначе, считаем допустимыми все возможные значения
|
else /// Иначе, считаем допустимыми все возможные значения
|
||||||
virtual_columns = new OneBlockInputStream(virtual_columns_block);
|
virtual_columns = new OneBlockInputStream(virtual_columns_block);
|
||||||
|
|
||||||
std::set< std::pair<String, UInt16> > values =
|
std::multiset< std::pair<String, UInt16> > values =
|
||||||
VirtualColumnUtils::extractTwoValuesFromBlocks<String, UInt16>(virtual_columns, _host_column_name, _port_column_name);
|
VirtualColumnUtils::extractTwoValuesFromBlocks<String, UInt16>(virtual_columns, _host_column_name, _port_column_name);
|
||||||
bool all_inclusive = values.size() == virtual_columns_block.rows();
|
bool all_inclusive = values.size() == virtual_columns_block.rows();
|
||||||
|
|
||||||
size_t result_size = values.size();
|
size_t result_size = values.size();
|
||||||
if (values.find(std::make_pair("localhost", clickhouse_port)) != values.end())
|
if (cluster.getLocalNodesNum() > 0 && values.find(std::make_pair("localhost", clickhouse_port)) != values.end())
|
||||||
result_size += cluster.getLocalNodesNum() - 1;
|
result_size += cluster.getLocalNodesNum() - 1;
|
||||||
|
|
||||||
processed_stage = result_size == 1
|
processed_stage = result_size == 1
|
||||||
@ -239,10 +239,6 @@ BlockInputStreams StorageDistributed::read(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Не дадим уничтожать объект до конца обработки запроса.
|
|
||||||
for (auto & stream : res)
|
|
||||||
stream->setOwnedStorage(thisPtr());
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,31 @@
|
|||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/** Для StorageMergeTree: достать первичный ключ в виде ASTExpressionList.
|
||||||
|
* Он может быть указан в кортеже: (CounterID, Date),
|
||||||
|
* или как один столбец: CounterID.
|
||||||
|
*/
|
||||||
|
static ASTPtr extractPrimaryKey(const ASTPtr & node, const std::string & storage_name)
|
||||||
|
{
|
||||||
|
const ASTFunction * primary_expr_func = dynamic_cast<const ASTFunction *>(&*node);
|
||||||
|
|
||||||
|
if (primary_expr_func && primary_expr_func->name == "tuple")
|
||||||
|
{
|
||||||
|
/// Первичный ключ указан в кортеже.
|
||||||
|
return primary_expr_func->children.at(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/// Первичный ключ состоит из одного столбца.
|
||||||
|
ASTExpressionList * res = new ASTExpressionList;
|
||||||
|
ASTPtr res_ptr = res;
|
||||||
|
res->children.push_back(node);
|
||||||
|
return res_ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
StoragePtr StorageFactory::get(
|
StoragePtr StorageFactory::get(
|
||||||
const String & name,
|
const String & name,
|
||||||
const String & data_path,
|
const String & data_path,
|
||||||
@ -182,17 +207,12 @@ StoragePtr StorageFactory::get(
|
|||||||
String date_column_name = dynamic_cast<ASTIdentifier &>(*args[0]).name;
|
String date_column_name = dynamic_cast<ASTIdentifier &>(*args[0]).name;
|
||||||
ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1];
|
ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1];
|
||||||
UInt64 index_granularity = safeGet<UInt64>(dynamic_cast<ASTLiteral &>(*args[arg_offset + 2]).value);
|
UInt64 index_granularity = safeGet<UInt64>(dynamic_cast<ASTLiteral &>(*args[arg_offset + 2]).value);
|
||||||
ASTFunction & primary_expr_func = dynamic_cast<ASTFunction &>(*args[arg_offset + 1]);
|
|
||||||
|
|
||||||
if (primary_expr_func.name != "tuple")
|
ASTPtr primary_expr_list = extractPrimaryKey(args[arg_offset + 1], name);
|
||||||
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);
|
|
||||||
|
|
||||||
return StorageMergeTree::create(
|
return StorageMergeTree::create(
|
||||||
data_path, table_name, columns, context, primary_expr, date_column_name, sampling_expression, index_granularity,
|
data_path, table_name, columns, context, primary_expr_list, date_column_name, sampling_expression, index_granularity,
|
||||||
name == "SummingMergeTree" ? StorageMergeTree::Summing : StorageMergeTree::Ordinary);
|
name == "SummingMergeTree" ? MergeTreeData::Summing : MergeTreeData::Ordinary);
|
||||||
}
|
}
|
||||||
else if (name == "CollapsingMergeTree")
|
else if (name == "CollapsingMergeTree")
|
||||||
{
|
{
|
||||||
@ -224,17 +244,12 @@ StoragePtr StorageFactory::get(
|
|||||||
ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1];
|
ASTPtr sampling_expression = arg_offset == 0 ? NULL : args[1];
|
||||||
UInt64 index_granularity = safeGet<UInt64>(dynamic_cast<ASTLiteral &>(*args[arg_offset + 2]).value);
|
UInt64 index_granularity = safeGet<UInt64>(dynamic_cast<ASTLiteral &>(*args[arg_offset + 2]).value);
|
||||||
String sign_column_name = dynamic_cast<ASTIdentifier &>(*args[arg_offset + 3]).name;
|
String sign_column_name = dynamic_cast<ASTIdentifier &>(*args[arg_offset + 3]).name;
|
||||||
ASTFunction & primary_expr_func = dynamic_cast<ASTFunction &>(*args[arg_offset + 1]);
|
|
||||||
|
|
||||||
if (primary_expr_func.name != "tuple")
|
ASTPtr primary_expr_list = extractPrimaryKey(args[arg_offset + 1], name);
|
||||||
throw Exception("Primary expression for storage CollapsingMergeTree must be in parentheses.",
|
|
||||||
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
|
||||||
|
|
||||||
ASTPtr primary_expr = primary_expr_func.children.at(0);
|
|
||||||
|
|
||||||
return StorageMergeTree::create(
|
return StorageMergeTree::create(
|
||||||
data_path, table_name, columns, context, primary_expr, date_column_name,
|
data_path, table_name, columns, context, primary_expr_list, date_column_name,
|
||||||
sampling_expression, index_granularity, StorageMergeTree::Collapsing, sign_column_name);
|
sampling_expression, index_granularity, MergeTreeData::Collapsing, sign_column_name);
|
||||||
}
|
}
|
||||||
else if (name == "SystemNumbers")
|
else if (name == "SystemNumbers")
|
||||||
{
|
{
|
||||||
|
@ -31,12 +31,26 @@ namespace DB
|
|||||||
using Poco::SharedPtr;
|
using Poco::SharedPtr;
|
||||||
|
|
||||||
|
|
||||||
LogBlockInputStream::LogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage, size_t mark_number_, size_t rows_limit_)
|
LogBlockInputStream::LogBlockInputStream(size_t block_size_, const Names & column_names_, StorageLog & storage_, size_t mark_number_, size_t rows_limit_)
|
||||||
: IProfilingBlockInputStream(owned_storage), block_size(block_size_), column_names(column_names_), storage(dynamic_cast<StorageLog &>(*owned_storage)), mark_number(mark_number_), rows_limit(rows_limit_), rows_read(0), current_mark(mark_number_)
|
: block_size(block_size_), column_names(column_names_), storage(storage_),
|
||||||
|
mark_number(mark_number_), rows_limit(rows_limit_), rows_read(0), current_mark(mark_number_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String LogBlockInputStream::getID() const
|
||||||
|
{
|
||||||
|
std::stringstream res;
|
||||||
|
res << "Log(" << storage.getTableName() << ", " << &storage << ", " << mark_number << ", " << rows_limit;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < column_names.size(); ++i)
|
||||||
|
res << ", " << column_names[i];
|
||||||
|
|
||||||
|
res << ")";
|
||||||
|
return res.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Block LogBlockInputStream::readImpl()
|
Block LogBlockInputStream::readImpl()
|
||||||
{
|
{
|
||||||
Block res;
|
Block res;
|
||||||
@ -232,8 +246,8 @@ void LogBlockInputStream::readData(const String & name, const IDataType & type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
LogBlockOutputStream::LogBlockOutputStream(StoragePtr owned_storage)
|
LogBlockOutputStream::LogBlockOutputStream(StorageLog & storage_)
|
||||||
: IBlockOutputStream(owned_storage), storage(dynamic_cast<StorageLog &>(*owned_storage)),
|
: storage(storage_),
|
||||||
lock(storage.rwlock), marks_stream(storage.marks_file.path(), 4096, O_APPEND | O_CREAT | O_WRONLY)
|
lock(storage.rwlock), marks_stream(storage.marks_file.path(), 4096, O_APPEND | O_CREAT | O_WRONLY)
|
||||||
{
|
{
|
||||||
for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it)
|
for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it)
|
||||||
@ -599,7 +613,7 @@ BlockInputStreams StorageLog::read(
|
|||||||
res.push_back(new LogBlockInputStream(
|
res.push_back(new LogBlockInputStream(
|
||||||
max_block_size,
|
max_block_size,
|
||||||
column_names,
|
column_names,
|
||||||
thisPtr(),
|
*this,
|
||||||
0, std::numeric_limits<size_t>::max()));
|
0, std::numeric_limits<size_t>::max()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -621,7 +635,7 @@ BlockInputStreams StorageLog::read(
|
|||||||
res.push_back(new LogBlockInputStream(
|
res.push_back(new LogBlockInputStream(
|
||||||
max_block_size,
|
max_block_size,
|
||||||
column_names,
|
column_names,
|
||||||
thisPtr(),
|
*this,
|
||||||
from_mark + thread * (to_mark - from_mark) / threads,
|
from_mark + thread * (to_mark - from_mark) / threads,
|
||||||
marks[from_mark + (thread + 1) * (to_mark - from_mark) / threads - 1].rows -
|
marks[from_mark + (thread + 1) * (to_mark - from_mark) / threads - 1].rows -
|
||||||
((thread == 0 && from_mark == 0)
|
((thread == 0 && from_mark == 0)
|
||||||
@ -650,7 +664,7 @@ BlockOutputStreamPtr StorageLog::write(
|
|||||||
ASTPtr query)
|
ASTPtr query)
|
||||||
{
|
{
|
||||||
loadMarks();
|
loadMarks();
|
||||||
return new LogBlockOutputStream(thisPtr());
|
return new LogBlockOutputStream(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,7 +74,8 @@ BlockOutputStreamPtr StorageMaterializedView::write(ASTPtr query)
|
|||||||
return data->write(query);
|
return data->write(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StorageMaterializedView::dropImpl() {
|
void StorageMaterializedView::drop()
|
||||||
|
{
|
||||||
context.getGlobalContext().removeDependency(DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(database_name, table_name));
|
context.getGlobalContext().removeDependency(DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(database_name, table_name));
|
||||||
|
|
||||||
/// Состваляем и выполняем запрос drop для внутреннего хранилища.
|
/// Состваляем и выполняем запрос drop для внутреннего хранилища.
|
||||||
|
@ -12,8 +12,8 @@ namespace DB
|
|||||||
using Poco::SharedPtr;
|
using Poco::SharedPtr;
|
||||||
|
|
||||||
|
|
||||||
MemoryBlockInputStream::MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_, StoragePtr owned_storage)
|
MemoryBlockInputStream::MemoryBlockInputStream(const Names & column_names_, BlocksList::iterator begin_, BlocksList::iterator end_)
|
||||||
: IProfilingBlockInputStream(owned_storage), column_names(column_names_), begin(begin_), end(end_), it(begin)
|
: column_names(column_names_), begin(begin_), end(end_), it(begin)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +39,8 @@ Block MemoryBlockInputStream::readImpl()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
MemoryBlockOutputStream::MemoryBlockOutputStream(StoragePtr owned_storage)
|
MemoryBlockOutputStream::MemoryBlockOutputStream(StorageMemory & storage_)
|
||||||
: IBlockOutputStream(owned_storage), storage(dynamic_cast<StorageMemory &>(*owned_storage))
|
: storage(storage_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ BlockInputStreams StorageMemory::read(
|
|||||||
std::advance(begin, thread * size / threads);
|
std::advance(begin, thread * size / threads);
|
||||||
std::advance(end, (thread + 1) * size / threads);
|
std::advance(end, (thread + 1) * size / threads);
|
||||||
|
|
||||||
res.push_back(new MemoryBlockInputStream(column_names, begin, end, thisPtr()));
|
res.push_back(new MemoryBlockInputStream(column_names, begin, end));
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
@ -102,11 +102,11 @@ BlockInputStreams StorageMemory::read(
|
|||||||
BlockOutputStreamPtr StorageMemory::write(
|
BlockOutputStreamPtr StorageMemory::write(
|
||||||
ASTPtr query)
|
ASTPtr query)
|
||||||
{
|
{
|
||||||
return new MemoryBlockOutputStream(thisPtr());
|
return new MemoryBlockOutputStream(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StorageMemory::dropImpl()
|
void StorageMemory::drop()
|
||||||
{
|
{
|
||||||
Poco::ScopedLock<Poco::FastMutex> lock(mutex);
|
Poco::ScopedLock<Poco::FastMutex> lock(mutex);
|
||||||
data.clear();
|
data.clear();
|
||||||
|
@ -55,7 +55,7 @@ BlockInputStreams StorageMerge::read(
|
|||||||
else
|
else
|
||||||
virt_column_names.push_back(it);
|
virt_column_names.push_back(it);
|
||||||
|
|
||||||
SelectedTables selected_tables;
|
StorageVector selected_tables;
|
||||||
|
|
||||||
/// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную.
|
/// Среди всех стадий, до которых обрабатывается запрос в таблицах-источниках, выберем минимальную.
|
||||||
processed_stage = QueryProcessingStage::Complete;
|
processed_stage = QueryProcessingStage::Complete;
|
||||||
@ -72,6 +72,14 @@ BlockInputStreams StorageMerge::read(
|
|||||||
getSelectedTables(selected_tables);
|
getSelectedTables(selected_tables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TableLocks table_locks;
|
||||||
|
|
||||||
|
/// Нельзя, чтобы эти таблицы кто-нибудь удалил, пока мы их читаем.
|
||||||
|
for (auto table : selected_tables)
|
||||||
|
{
|
||||||
|
table_locks.push_back(table->lockStructure(false));
|
||||||
|
}
|
||||||
|
|
||||||
Block virtual_columns_block = getBlockWithVirtualColumns(selected_tables);
|
Block virtual_columns_block = getBlockWithVirtualColumns(selected_tables);
|
||||||
BlockInputStreamPtr virtual_columns;
|
BlockInputStreamPtr virtual_columns;
|
||||||
|
|
||||||
@ -81,23 +89,26 @@ BlockInputStreams StorageMerge::read(
|
|||||||
else /// Иначе, считаем допустимыми все возможные значения
|
else /// Иначе, считаем допустимыми все возможные значения
|
||||||
virtual_columns = new OneBlockInputStream(virtual_columns_block);
|
virtual_columns = new OneBlockInputStream(virtual_columns_block);
|
||||||
|
|
||||||
std::set<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name);
|
std::multiset<String> values = VirtualColumnUtils::extractSingleValueFromBlocks<String>(virtual_columns, _table_column_name);
|
||||||
bool all_inclusive = (values.size() == virtual_columns_block.rows());
|
bool all_inclusive = (values.size() == virtual_columns_block.rows());
|
||||||
|
|
||||||
for (SelectedTables::iterator it = selected_tables.begin(); it != selected_tables.end(); ++it)
|
for (size_t i = 0; i < selected_tables.size(); ++i)
|
||||||
{
|
{
|
||||||
if (!all_inclusive && values.find((*it)->getTableName()) == values.end())
|
StoragePtr table = selected_tables[i];
|
||||||
|
auto table_lock = table_locks[i];
|
||||||
|
|
||||||
|
if (!all_inclusive && values.find(table->getTableName()) == values.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой.
|
/// Если в запросе только виртуальные столбцы, надо запросить хотя бы один любой другой.
|
||||||
if (real_column_names.size() == 0)
|
if (real_column_names.size() == 0)
|
||||||
real_column_names.push_back(ExpressionActions::getSmallestColumn((*it)->getColumnsList()));
|
real_column_names.push_back(ExpressionActions::getSmallestColumn(table->getColumnsList()));
|
||||||
|
|
||||||
/// Подменяем виртуальный столбец на его значение
|
/// Подменяем виртуальный столбец на его значение
|
||||||
ASTPtr modified_query_ast = query->clone();
|
ASTPtr modified_query_ast = query->clone();
|
||||||
VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, (*it)->getTableName());
|
VirtualColumnUtils::rewriteEntityInAst(modified_query_ast, _table_column_name, table->getTableName());
|
||||||
|
|
||||||
BlockInputStreams source_streams = (*it)->read(
|
BlockInputStreams source_streams = table->read(
|
||||||
real_column_names,
|
real_column_names,
|
||||||
modified_query_ast,
|
modified_query_ast,
|
||||||
settings,
|
settings,
|
||||||
@ -105,17 +116,21 @@ BlockInputStreams StorageMerge::read(
|
|||||||
max_block_size,
|
max_block_size,
|
||||||
selected_tables.size() > threads ? 1 : (threads / selected_tables.size()));
|
selected_tables.size() > threads ? 1 : (threads / selected_tables.size()));
|
||||||
|
|
||||||
|
for (auto & stream : source_streams)
|
||||||
|
{
|
||||||
|
stream->addTableLock(table_lock);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto & virtual_column : virt_column_names)
|
for (auto & virtual_column : virt_column_names)
|
||||||
{
|
{
|
||||||
if (virtual_column == _table_column_name)
|
if (virtual_column == _table_column_name)
|
||||||
{
|
{
|
||||||
for (auto & stream : source_streams)
|
for (auto & stream : source_streams)
|
||||||
stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, (*it)->getTableName(), _table_column_name);
|
stream = new AddingConstColumnBlockInputStream<String>(stream, new DataTypeString, table->getTableName(), _table_column_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (BlockInputStreams::iterator jt = source_streams.begin(); jt != source_streams.end(); ++jt)
|
res.insert(res.end(), source_streams.begin(), source_streams.end());
|
||||||
res.push_back(*jt);
|
|
||||||
|
|
||||||
if (tmp_processed_stage < processed_stage)
|
if (tmp_processed_stage < processed_stage)
|
||||||
processed_stage = tmp_processed_stage;
|
processed_stage = tmp_processed_stage;
|
||||||
@ -135,7 +150,7 @@ Block StorageMerge::getBlockWithVirtualColumns(const std::vector<StoragePtr> & s
|
|||||||
Block res;
|
Block res;
|
||||||
ColumnWithNameAndType _table(new ColumnString, new DataTypeString, _table_column_name);
|
ColumnWithNameAndType _table(new ColumnString, new DataTypeString, _table_column_name);
|
||||||
|
|
||||||
for (SelectedTables::const_iterator it = selected_tables.begin(); it != selected_tables.end(); ++it)
|
for (StorageVector::const_iterator it = selected_tables.begin(); it != selected_tables.end(); ++it)
|
||||||
_table.column->insert((*it)->getTableName());
|
_table.column->insert((*it)->getTableName());
|
||||||
|
|
||||||
res.insert(_table);
|
res.insert(_table);
|
||||||
@ -146,7 +161,7 @@ void StorageMerge::getSelectedTables(StorageVector & selected_tables)
|
|||||||
{
|
{
|
||||||
const Tables & tables = context.getDatabases().at(source_database);
|
const Tables & tables = context.getDatabases().at(source_database);
|
||||||
for (Tables::const_iterator it = tables.begin(); it != tables.end(); ++it)
|
for (Tables::const_iterator it = tables.begin(); it != tables.end(); ++it)
|
||||||
if (it->second != this && table_name_regexp.match(it->first))
|
if (it->second.get() != this && table_name_regexp.match(it->first))
|
||||||
selected_tables.push_back(it->second);
|
selected_tables.push_back(it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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(...)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -27,12 +27,25 @@ namespace DB
|
|||||||
using Poco::SharedPtr;
|
using Poco::SharedPtr;
|
||||||
|
|
||||||
|
|
||||||
TinyLogBlockInputStream::TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StoragePtr owned_storage)
|
TinyLogBlockInputStream::TinyLogBlockInputStream(size_t block_size_, const Names & column_names_, StorageTinyLog & storage_)
|
||||||
: IProfilingBlockInputStream(owned_storage), block_size(block_size_), column_names(column_names_), storage(dynamic_cast<StorageTinyLog &>(*owned_storage)), finished(false)
|
: block_size(block_size_), column_names(column_names_), storage(storage_), finished(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String TinyLogBlockInputStream::getID() const
|
||||||
|
{
|
||||||
|
std::stringstream res;
|
||||||
|
res << "TinyLog(" << storage.getTableName() << ", " << &storage;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < column_names.size(); ++i)
|
||||||
|
res << ", " << column_names[i];
|
||||||
|
|
||||||
|
res << ")";
|
||||||
|
return res.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Block TinyLogBlockInputStream::readImpl()
|
Block TinyLogBlockInputStream::readImpl()
|
||||||
{
|
{
|
||||||
Block res;
|
Block res;
|
||||||
@ -172,8 +185,8 @@ void TinyLogBlockInputStream::readData(const String & name, const IDataType & ty
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TinyLogBlockOutputStream::TinyLogBlockOutputStream(StoragePtr owned_storage)
|
TinyLogBlockOutputStream::TinyLogBlockOutputStream(StorageTinyLog & storage_)
|
||||||
: IBlockOutputStream(owned_storage), storage(dynamic_cast<StorageTinyLog &>(*owned_storage))
|
: storage(storage_)
|
||||||
{
|
{
|
||||||
for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it)
|
for (NamesAndTypesList::const_iterator it = storage.columns->begin(); it != storage.columns->end(); ++it)
|
||||||
addStream(it->first, *it->second);
|
addStream(it->first, *it->second);
|
||||||
@ -363,18 +376,18 @@ BlockInputStreams StorageTinyLog::read(
|
|||||||
{
|
{
|
||||||
check(column_names);
|
check(column_names);
|
||||||
processed_stage = QueryProcessingStage::FetchColumns;
|
processed_stage = QueryProcessingStage::FetchColumns;
|
||||||
return BlockInputStreams(1, new TinyLogBlockInputStream(max_block_size, column_names, thisPtr()));
|
return BlockInputStreams(1, new TinyLogBlockInputStream(max_block_size, column_names, *this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BlockOutputStreamPtr StorageTinyLog::write(
|
BlockOutputStreamPtr StorageTinyLog::write(
|
||||||
ASTPtr query)
|
ASTPtr query)
|
||||||
{
|
{
|
||||||
return new TinyLogBlockOutputStream(thisPtr());
|
return new TinyLogBlockOutputStream(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StorageTinyLog::dropImpl()
|
void StorageTinyLog::drop()
|
||||||
{
|
{
|
||||||
for (Files_t::iterator it = files.begin(); it != files.end(); ++it)
|
for (Files_t::iterator it = files.begin(); it != files.end(); ++it)
|
||||||
if (it->second.data_file.exists())
|
if (it->second.data_file.exists())
|
||||||
|
@ -65,7 +65,7 @@ BlockInputStreams StorageView::read(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StorageView::dropImpl() {
|
void StorageView::drop() {
|
||||||
context.getGlobalContext().removeDependency(DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(database_name, table_name));
|
context.getGlobalContext().removeDependency(DatabaseAndTableName(select_database_name, select_table_name), DatabaseAndTableName(database_name, table_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ DataParts copy(const DataParts &a)
|
|||||||
|
|
||||||
const int RowsPerSec = 100000;
|
const int RowsPerSec = 100000;
|
||||||
|
|
||||||
StorageMergeTreeSettings settings;
|
MergeTreeSettings settings;
|
||||||
|
|
||||||
|
|
||||||
size_t index_granularity = 1;
|
size_t index_granularity = 1;
|
||||||
|
4
dbms/tests/.gitignore
vendored
Normal file
4
dbms/tests/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.result
|
||||||
|
*.diff
|
||||||
|
*.error
|
||||||
|
test_data
|
100
dbms/tests/clickhouse-test
Executable file
100
dbms/tests/clickhouse-test
Executable 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
|
1
dbms/tests/queries/0_stateless/00001_select_1.reference
Normal file
1
dbms/tests/queries/0_stateless/00001_select_1.reference
Normal file
@ -0,0 +1 @@
|
|||||||
|
1
|
1
dbms/tests/queries/0_stateless/00001_select_1.sql
Normal file
1
dbms/tests/queries/0_stateless/00001_select_1.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
SELECT 1
|
@ -0,0 +1,10 @@
|
|||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
1
dbms/tests/queries/0_stateless/00002_system_numbers.sql
Normal file
1
dbms/tests/queries/0_stateless/00002_system_numbers.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
SELECT * FROM system.numbers LIMIT 10
|
@ -0,0 +1 @@
|
|||||||
|
33232
|
@ -0,0 +1 @@
|
|||||||
|
SELECT number FROM system.numbers WHERE reinterpretAsString(number) = 'Ё' LIMIT 1
|
@ -0,0 +1,2 @@
|
|||||||
|
-1
|
||||||
|
-1
|
@ -0,0 +1 @@
|
|||||||
|
SELECT (dummy AS x) - 1 FROM remote('127.0.0.{1,2}', system, one)
|
@ -0,0 +1 @@
|
|||||||
|
2
|
@ -0,0 +1 @@
|
|||||||
|
SELECT count() FROM remote('127.0.0.{1,2}', system, one) WHERE arrayExists((x) -> x = 1, [1, 2, 3])
|
1
dbms/tests/queries/0_stateless/00006_1_set_extremes.sql
Normal file
1
dbms/tests/queries/0_stateless/00006_1_set_extremes.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
SET GLOBAL extremes = 1
|
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"meta":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "'Hello, world'",
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
"rows": 0,
|
||||||
|
|
||||||
|
"rows_before_limit_at_least": 10
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
SELECT 'Hello, world' FROM (SELECT number FROM system.numbers LIMIT 10) WHERE number < 0
|
||||||
|
FORMAT JSONCompact
|
@ -0,0 +1 @@
|
|||||||
|
SET GLOBAL extremes = 0
|
1
dbms/tests/queries/0_stateless/00007_array.reference
Normal file
1
dbms/tests/queries/0_stateless/00007_array.reference
Normal file
@ -0,0 +1 @@
|
|||||||
|
['Hello','Goodbye']
|
1
dbms/tests/queries/0_stateless/00007_array.sql
Normal file
1
dbms/tests/queries/0_stateless/00007_array.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
SELECT ['Hello', 'Goodbye']
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user