This commit is contained in:
Michael Kolupaev 2014-03-27 21:49:08 +04:00
commit 634c33c766
154 changed files with 1831 additions and 574 deletions

View File

@ -0,0 +1,120 @@
#pragma once
#include <DB/Columns/ColumnArray.h>
#include <DB/DataTypes/DataTypeArray.h>
#include <DB/DataTypes/DataTypesNumberFixed.h>
#include <DB/AggregateFunctions/IAggregateFunction.h>
namespace DB
{
/** Не агрегатная функция, а адаптер агрегатных функций,
* который любую агрегатную функцию agg(x) делает агрегатной функцией вида aggArray(x).
* Адаптированная агрегатная функция вычисляет вложенную агрегатную функцию для каждого элемента массива.
*/
class AggregateFunctionArray : public IAggregateFunction
{
private:
AggregateFunctionPtr nested_func_owner;
IAggregateFunction * nested_func;
int num_agruments;
public:
AggregateFunctionArray(AggregateFunctionPtr nested_) : nested_func_owner(nested_), nested_func(nested_func_owner.get()) {}
String getName() const
{
return nested_func->getName() + "Array";
}
DataTypePtr getReturnType() const
{
return nested_func->getReturnType();
}
void setArguments(const DataTypes & arguments)
{
num_agruments = arguments.size();
DataTypes nested_arguments;
for (int i = 0; i < num_agruments; ++i)
{
if (const DataTypeArray * array = dynamic_cast<const DataTypeArray *>(&*arguments[i]))
nested_arguments.push_back(array->getNestedType());
else
throw Exception("Illegal type " + arguments[i]->getName() + " of argument #" + toString(i + 1) + " for aggregate function " + getName() + ". Must be array.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
}
nested_func->setArguments(nested_arguments);
}
void setParameters(const Array & params)
{
nested_func->setParameters(params);
}
void create(AggregateDataPtr place) const
{
nested_func->create(place);
}
void destroy(AggregateDataPtr place) const
{
nested_func->destroy(place);
}
bool hasTrivialDestructor() const
{
return nested_func->hasTrivialDestructor();
}
size_t sizeOfData() const
{
return nested_func->sizeOfData();
}
size_t alignOfData() const
{
return nested_func->alignOfData();
}
void add(AggregateDataPtr place, const IColumn ** columns, size_t row_num) const
{
const IColumn ** nested = new const IColumn*[num_agruments];
std::vector<ColumnPtr> column_ptrs;
for (int i = 0; i < num_agruments; ++i)
{
ColumnPtr single_value_column = dynamic_cast<const ColumnArray &>(*columns[i]).cut(row_num, 1);
column_ptrs.push_back(single_value_column);
nested[i] = dynamic_cast<const ColumnArray &>(*single_value_column).getDataPtr().get();
}
for (int i = 0; i < num_agruments; ++i)
if (nested[i]->size() != nested[0]->size())
throw Exception("All arrays must be of the same size. Aggregate function " + getName(), ErrorCodes::BAD_ARGUMENTS);
for (size_t i = 0; i < nested[0]->size(); ++i)
nested_func->add(place, nested, i);
}
void merge(AggregateDataPtr place, ConstAggregateDataPtr rhs) const
{
nested_func->merge(place, rhs);
}
void serialize(ConstAggregateDataPtr place, WriteBuffer & buf) const
{
nested_func->serialize(place, buf);
}
void deserializeMerge(AggregateDataPtr place, ReadBuffer & buf) const
{
nested_func->deserializeMerge(place, buf);
}
void insertResultInto(ConstAggregateDataPtr place, IColumn & to) const
{
nested_func->insertResultInto(place, to);
}
};
}

View File

@ -47,7 +47,7 @@ public:
nested_func->setArguments(nested_arguments);
}
void setParameters(const Row & params)
void setParameters(const Array & params)
{
nested_func->setParameters(params);
}

View File

@ -56,7 +56,7 @@ public:
type = argument;
}
void setParameters(const Row & params)
void setParameters(const Array & params)
{
if (params.size() != 1)
throw Exception("Aggregate function " + getName() + " requires exactly one parameter.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
@ -130,7 +130,7 @@ public:
type = argument;
}
void setParameters(const Row & params)
void setParameters(const Array & params)
{
if (params.empty())
throw Exception("Aggregate function " + getName() + " requires at least one parameter.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);

View File

@ -324,11 +324,13 @@ private:
void toLarge()
{
large = new detail::QuantileTimingLarge;
/// На время копирования данных из tiny, устанавливать значение large ещё нельзя (иначе оно перезатрёт часть данных).
detail::QuantileTimingLarge * tmp_large = new detail::QuantileTimingLarge;
for (size_t i = 0; i < tiny.count; ++i)
large->insert(tiny.elems[i]);
tmp_large->insert(tiny.elems[i]);
large = tmp_large;
tiny.count = TINY_MAX_ELEMS + 1;
}
@ -511,7 +513,7 @@ public:
{
}
void setParameters(const Row & params)
void setParameters(const Array & params)
{
if (params.size() != 1)
throw Exception("Aggregate function " + getName() + " requires exactly one parameter.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
@ -570,7 +572,7 @@ public:
{
}
void setParameters(const Row & params)
void setParameters(const Array & params)
{
if (params.empty())
throw Exception("Aggregate function " + getName() + " requires at least one parameter.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);

View File

@ -39,9 +39,10 @@ public:
* Если параметры не предусмотрены или переданные параметры недопустимы - кинуть исключение.
* Если параметры есть - необходимо вызывать перед остальными вызовами, иначе - не вызывать.
*/
virtual void setParameters(const Row & params)
virtual void setParameters(const Array & params)
{
throw Exception("Aggregate function " + getName() + " doesn't allow parameters.", ErrorCodes::AGGREGATE_FUNCTION_DOESNT_ALLOW_PARAMETERS);
throw Exception("Aggregate function " + getName() + " doesn't allow parameters.",
ErrorCodes::AGGREGATE_FUNCTION_DOESNT_ALLOW_PARAMETERS);
}
/// Получить тип результата.

View File

@ -4,8 +4,8 @@
#include <Poco/Net/StreamSocket.h>
#include <DB/Core/Defines.h>
#include <DB/Core/Block.h>
#include <DB/Core/Defines.h>
#include <DB/Core/Progress.h>
#include <DB/Core/Protocol.h>
#include <DB/Core/QueryProcessingStage.h>
@ -24,6 +24,10 @@ namespace DB
using Poco::SharedPtr;
/// Поток блоков читающих из таблицы и ее имя
typedef std::pair<BlockInputStreamPtr, std::string> ExternalTableData;
/// Вектор пар, описывающих таблицы
typedef std::vector<ExternalTableData> ExternalTablesData;
/** Соединение с сервером БД для использования в клиенте.
* Как использовать - см. Core/Protocol.h
@ -79,11 +83,15 @@ public:
/// Адрес сервера - для сообщений в логе и в эксепшенах.
String getServerAddress() const;
/// Если последний флаг true, то затем необходимо вызвать sendExternalTablesData
void sendQuery(const String & query, const String & query_id_ = "", UInt64 stage = QueryProcessingStage::Complete,
const Settings * settings = NULL);
const Settings * settings = NULL, bool with_pending_data = false);
void sendCancel();
void sendData(const Block & block);
/// Отправить блок данных, на сервере сохранить во временную таблицу name
void sendData(const Block & block, const String & name = "");
/// Отправить все содержимое внешних таблиц
void sendExternalTablesData(ExternalTablesData & data);
/// Проверить, если ли данные, которые можно прочитать.
bool poll(size_t timeout_microseconds = 0);

View File

@ -229,7 +229,7 @@ public:
return res;
}
int compareAt(size_t n, size_t m, const IColumn & rhs_, int nan_direction_hint) const
int compareAt(size_t n, size_t m, const IColumn & rhs_, int nan_direction_hint) const final
{
const ColumnArray & rhs = static_cast<const ColumnArray &>(rhs_);
@ -238,7 +238,7 @@ public:
size_t rhs_size = rhs.sizeAt(m);
size_t min_size = std::min(lhs_size, rhs_size);
for (size_t i = 0; i < min_size; ++i)
if (int res = data->compareAt(offsetAt(n) + i, rhs.offsetAt(m) + i, *rhs.data, nan_direction_hint))
if (int res = data.get()->compareAt(offsetAt(n) + i, rhs.offsetAt(m) + i, *rhs.data.get(), nan_direction_hint))
return res;
return lhs_size < rhs_size
@ -252,50 +252,41 @@ public:
struct less
{
const ColumnArray & parent;
const Permutation & nested_perm;
less(const ColumnArray & parent_, const Permutation & nested_perm_) : parent(parent_), nested_perm(nested_perm_) {}
less(const ColumnArray & parent_) : parent(parent_) {}
bool operator()(size_t lhs, size_t rhs) const
{
size_t lhs_size = parent.sizeAt(lhs);
size_t rhs_size = parent.sizeAt(rhs);
size_t min_size = std::min(lhs_size, rhs_size);
for (size_t i = 0; i < min_size; ++i)
{
if (nested_perm[parent.offsetAt(lhs) + i] < nested_perm[parent.offsetAt(rhs) + i])
return positive;
else if (nested_perm[parent.offsetAt(lhs) + i] > nested_perm[parent.offsetAt(rhs) + i])
return !positive;
}
return positive == (lhs_size < rhs_size);
if (positive)
return parent.compareAt(lhs, rhs, parent, 1) < 0;
else
return parent.compareAt(lhs, rhs, parent, -1) > 0;
}
};
void getPermutation(bool reverse, size_t limit, Permutation & res) const
{
Permutation nested_perm;
data->getPermutation(reverse, limit, nested_perm);
size_t s = size();
if (limit > s)
limit = 0;
res.resize(s);
for (size_t i = 0; i < s; ++i)
res[i] = i;
if (limit > s)
limit = 0;
if (limit)
{
if (reverse)
std::partial_sort(res.begin(), res.begin() + limit, res.end(), less<false>(*this, nested_perm));
std::partial_sort(res.begin(), res.begin() + limit, res.end(), less<false>(*this));
else
std::partial_sort(res.begin(), res.begin() + limit, res.end(), less<true>(*this, nested_perm));
std::partial_sort(res.begin(), res.begin() + limit, res.end(), less<true>(*this));
}
else
{
if (reverse)
std::sort(res.begin(), res.end(), less<false>(*this, nested_perm));
std::sort(res.begin(), res.end(), less<false>(*this));
else
std::sort(res.begin(), res.end(), less<true>(*this, nested_perm));
std::sort(res.begin(), res.end(), less<true>(*this));
}
}

View File

@ -20,7 +20,7 @@ namespace DB
* sizeof равен размеру одного указателя.
*
* Не exception-safe.
* Копирование и присваивание разрушающее: исходный объект становится пустым.
* Копирование не поддерживается. Перемещение опустошает исходный объект.
* То есть, использовать этот массив во многих случаях неудобно.
*
* Предназначен для ситуаций, в которых создаётся много массивов одинакового небольшого размера,
@ -82,24 +82,24 @@ public:
init(size_, dont_init_elems);
}
/** Разрушающее копирование.
/** Премещение.
*/
AutoArray(const AutoArray & src)
AutoArray(AutoArray && src)
{
//std::cerr << this << " AutoArray(const AutoArray & src)" << std::endl;
if (this == &src)
return;
setEmpty();
data = src.data;
const_cast<AutoArray<T> &>(src).setEmpty();
src.setEmpty();
}
AutoArray & operator= (const AutoArray & src)
AutoArray & operator= (AutoArray && src)
{
//std::cerr << this << " operator=(const AutoArray & src)" << std::endl;
if (this == &src)
return *this;
uninit();
data = src.data;
const_cast<AutoArray<T> &>(src).setEmpty();
src.setEmpty();
return *this;
}

View File

@ -40,3 +40,4 @@
#define DBMS_MIN_REVISION_WITH_USER_PASSWORD 34482
#define DBMS_MIN_REVISION_WITH_TOTALS_EXTREMES 35265
#define DBMS_MIN_REVISION_WITH_STRING_QUERY_ID 39002
#define DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES 50263

View File

@ -233,6 +233,9 @@ namespace ErrorCodes
UNEXPECTED_AST_STRUCTURE,
REPLICA_IS_ALREADY_ACTIVE,
NO_ZOOKEEPER,
NO_FILE_IN_DATA_PART,
UNEXPECTED_FILE_IN_DATA_PART,
BAD_SIZE_OF_FILE_IN_DATA_PART,
POCO_EXCEPTION = 1000,
STD_EXCEPTION,

View File

@ -48,9 +48,10 @@ public:
}
RemoteBlockInputStream(ConnectionPool::Entry pool_entry_, const String & query_, const Settings * settings_,
const String & _host_column_, const String & _port_column_, QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete)
const String & _host_column_, const String & _port_column_, const Tables & external_tables_, QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete)
: pool_entry(pool_entry_), connection(*pool_entry), query(query_), _host_column(_host_column_),
_port_column(_port_column_), stage(stage_), sent_query(false), finished(false), was_cancelled(false),
_port_column(_port_column_), external_tables(external_tables_), stage(stage_), sent_query(false), finished(false),
was_cancelled(false),
got_exception_from_server(false), log(&Logger::get("RemoteBlockInputStream (" + connection.getServerAddress() + ")"))
{
if (settings_)
@ -122,11 +123,30 @@ protected:
}
}
/// Отправить на удаленные сервера все временные таблицы
void sendExternalTables()
{
ExternalTablesData res;
Tables::const_iterator it;
for (it = external_tables.begin(); it != external_tables.end(); it ++)
{
StoragePtr cur = it->second;
QueryProcessingStage::Enum stage = QueryProcessingStage::Complete;
DB::BlockInputStreams input = cur->read(cur->getColumnNamesList(), ASTPtr(), settings, stage, DEFAULT_BLOCK_SIZE, 1);
if (input.size() == 0)
res.push_back(std::make_pair(new OneBlockInputStream(cur->getSampleBlock()), it->first));
else
res.push_back(std::make_pair(input[0], it->first));
}
connection.sendExternalTablesData(res);
}
Block readImpl()
{
if (!sent_query)
{
connection.sendQuery(query, "", stage, send_settings ? &settings : NULL);
connection.sendQuery(query, "", stage, send_settings ? &settings : NULL, true);
sendExternalTables();
sent_query = true;
}
@ -253,6 +273,8 @@ private:
String _host_column;
/// Имя столбца, куда записать номер порта (Например "_port"). Пустая строка, если записывать не надо.
String _port_column;
/// Временные таблицы, которые необходимо переслать на удаленные сервера.
Tables external_tables;
QueryProcessingStage::Enum stage;
/// Отправили запрос (это делается перед получением первого блока).

View File

@ -11,19 +11,19 @@ namespace DB
using Poco::SharedPtr;
/** Тип - состояние агрегатной функции.
* Параметры типа - это агрегатная функция и типы её аргументов.
* Параметры типа - это агрегатная функция, типы её аргументов и её параметры (для параметрических агрегатных функций).
*/
class DataTypeAggregateFunction : public IDataType
{
private:
AggregateFunctionPtr function;
DataTypes argument_types;
Array parameters;
public:
DataTypeAggregateFunction(const AggregateFunctionPtr & function_, const DataTypes & argument_types_)
: function(function_), argument_types(argument_types_)
DataTypeAggregateFunction(const AggregateFunctionPtr & function_, const DataTypes & argument_types_, const Array & parameters_)
: function(function_), argument_types(argument_types_), parameters(parameters_)
{
function->setArguments(argument_types);
}
std::string getName() const
@ -31,6 +31,18 @@ public:
std::stringstream stream;
stream << "AggregateFunction(" << function->getName();
if (!parameters.empty())
{
stream << "(";
for (size_t i = 0; i < parameters.size(); ++i)
{
if (i)
stream << ", ";
stream << apply_visitor(DB::FieldVisitorToString(), parameters[i]);
}
stream << ")";
}
for (DataTypes::const_iterator it = argument_types.begin(); it != argument_types.end(); ++it)
stream << ", " << (*it)->getName();
@ -38,7 +50,7 @@ public:
return stream.str();
}
DataTypePtr clone() const { return new DataTypeAggregateFunction(function, argument_types); }
DataTypePtr clone() const { return new DataTypeAggregateFunction(function, argument_types, parameters); }
void serializeBinary(const Field & field, WriteBuffer & ostr) const;
void deserializeBinary(Field & field, ReadBuffer & istr) const;

View File

@ -285,14 +285,18 @@ class FunctionIn : public IFunction
{
private:
bool negative;
bool global;
public:
FunctionIn(bool negative_ = false) : negative(negative_) {}
FunctionIn(bool negative_ = false, bool global_ = false) : negative(negative_), global(global_) {}
/// Получить имя функции.
String getName() const
{
return negative ? "notIn" : "in";
if (global)
return negative ? "globalNotIn" : "globalIn";
else
return negative ? "notIn" : "in";
}
/// Получить тип результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение.

View File

@ -26,7 +26,6 @@ protected:
/// Если в буфере compressed_in помещается целый сжатый блок - используем его. Иначе - копируем данные по кусочкам в own_compressed_buffer.
PODArray<char> own_compressed_buffer;
char * compressed_buffer;
size_t size_compressed;
qlz_state_decompress * qlz_state;

View File

@ -19,7 +19,7 @@ private:
* - size_compressed содержит сжатый размер этого блока.
*/
ReadBufferFromFile file_in;
size_t size_compressed;
size_t size_compressed = 0;
bool nextImpl()
{

View File

@ -0,0 +1,97 @@
#pragma once
#include <city.h>
#include <DB/IO/WriteBuffer.h>
#include <DB/IO/BufferWithOwnMemory.h>
#define DBMS_DEFAULT_HASHING_BLOCK_SIZE 2048ULL
namespace DB
{
/** Вычисляет хеш от записываемых данных и передает их в указанный WriteBuffer.
* В качестве основного буфера используется буфер вложенного WriteBuffer.
*/
class HashingWriteBuffer : public BufferWithOwnMemory<WriteBuffer>
{
private:
WriteBuffer & out;
size_t block_size;
size_t block_pos;
uint128 state;
void append(Position data)
{
state = CityHash128WithSeed(data, block_size, state);
}
void nextImpl() override
{
size_t len = offset();
if (len)
{
Position data = working_buffer.begin();
if (block_pos + len < block_size)
{
memcpy(&memory[block_pos], data, len);
block_pos += len;
}
else
{
if (block_pos)
{
size_t n = block_size - block_pos;
memcpy(&memory[block_pos], data, n);
append(&memory[0]);
len -= n;
data += n;
block_pos = 0;
}
while (len >= block_size)
{
append(data);
len -= block_size;
data += block_size;
}
if (len)
{
memcpy(&memory[0], data, len);
block_pos = len;
}
}
}
out.position() = pos;
out.next();
working_buffer = out.buffer();
}
public:
HashingWriteBuffer(
WriteBuffer & out_,
size_t block_size_ = DBMS_DEFAULT_HASHING_BLOCK_SIZE)
: BufferWithOwnMemory<WriteBuffer>(block_size_), out(out_), block_size(block_size_), block_pos(0)
{
out.next(); /// Если до нас в out что-то уже писали, не дадим остаткам этих данных повлиять на хеш.
working_buffer = out.buffer();
pos = working_buffer.begin();
state = uint128(0, 0);
}
uint128 getHash()
{
next();
if (block_pos)
return CityHash128WithSeed(&memory[0], block_pos, state);
else
return state;
}
};
}

View File

@ -114,6 +114,7 @@ inline void readChar(char & x, ReadBuffer & buf)
}
void assertString(const char * s, ReadBuffer & buf);
void assertEOF(ReadBuffer & buf);
inline void assertString(const String & s, ReadBuffer & buf)
{

View File

@ -27,6 +27,7 @@ namespace DB
struct AggregateDescription
{
AggregateFunctionPtr function;
Array parameters; /// Параметры (параметрической) агрегатной функции.
ColumnNumbers arguments;
Names argument_names; /// Используются, если arguments не заданы.
String column_name; /// Какое имя использовать для столбца со значениями агрегатной функции

View File

@ -86,10 +86,22 @@ private:
* Элемент может остаться на месте, или переместиться в новое место "справа",
* или переместиться левее по цепочке разрешения коллизий, из-за того, что элементы левее него были перемещены в новое место "справа".
*/
for (size_t i = 0; i < old_size; ++i)
size_t i = 0;
for (; i < old_size; ++i)
if (buf[i].version == version)
reinsert(buf[i]);
/** Также имеется особый случай:
* если элемент должен был быть в конце старого буфера, [ x]
* но находится в начале из-за цепочки разрешения коллизий, [o x]
* то после ресайза, он сначала снова окажется не на своём месте, [ xo ]
* и для того, чтобы перенести его куда надо,
* надо будет после переноса всех элементов из старой половинки [ o x ]
* обработать ещё хвостик из цепочки разрешения коллизий сразу после неё [ o x ]
*/
for (; buf[i].version == version; ++i)
reinsert(buf[i]);
#ifdef DBMS_HASH_MAP_DEBUG_RESIZES
watch.stop();
std::cerr << std::fixed << std::setprecision(3)

View File

@ -50,7 +50,6 @@ typedef std::pair<String, String> DatabaseAndTableName;
typedef std::map<DatabaseAndTableName, std::set<DatabaseAndTableName> > ViewDependencies;
typedef std::vector<DatabaseAndTableName> Dependencies;
/** Набор известных объектов, которые могут быть использованы в запросе.
* Разделяемая часть. Порядок членов (порядок их уничтожения) очень важен.
*/
@ -180,7 +179,7 @@ private:
String default_format; /// Формат, используемый, если сервер сам форматирует данные, и если в запросе не задан FORMAT.
/// То есть, используется в HTTP-интерфейсе. Может быть не задан - тогда используется некоторый глобальный формат по-умолчанию.
Tables external_tables; /// Временные таблицы.
Context * session_context; /// Контекст сессии или NULL, если его нет. (Возможно, равен this.)
Context * global_context; /// Глобальный контекст или NULL, если его нет. (Возможно, равен this.)
@ -217,8 +216,11 @@ public:
void assertDatabaseExists(const String & database_name) const;
void assertDatabaseDoesntExist(const String & database_name) const;
Tables getExternalTables() const;
StoragePtr tryGetExternalTable(const String & table_name) const;
StoragePtr getTable(const String & database_name, const String & table_name) const;
StoragePtr tryGetTable(const String & database_name, const String & table_name) const;
void addExternalTable(const String & table_name, StoragePtr storage);
void addTable(const String & database_name, const String & table_name, StoragePtr table);
void addDatabase(const String & database_name);

View File

@ -64,6 +64,8 @@ public:
* chain.finalize();
*/
void processGlobalOperations();
/// До агрегации:
bool appendArrayJoin(ExpressionActionsChain & chain);
bool appendWhere(ExpressionActionsChain & chain);
@ -95,6 +97,8 @@ public:
/// Если ast - запрос SELECT, получает имена (алиасы) и типы столбцов из секции SELECT.
Block getSelectSampleBlock();
/// Все новые временные таблицы, полученные при выполнении подзапросов Global In
std::vector<StoragePtr> external_tables;
private:
typedef std::set<String> NamesSet;
@ -124,7 +128,6 @@ private:
AggregateDescriptions aggregate_descriptions;
std::map<std::string, SetPtr> sets_with_subqueries;
typedef std::map<String, ASTPtr> Aliases;
Aliases aliases;
@ -257,10 +260,16 @@ private:
*/
void normalizeTree();
void normalizeTreeImpl(ASTPtr & ast, MapOfASTs & finished_asts, SetOfASTs & current_asts, std::string current_alias, bool in_sign_rewritten);
/// Обходит запрос и сохраняет найденные глобальные функции (например Global in)
void findGlobalFunctions(ASTPtr & ast, std::vector<ASTPtr> & global_nodes);
/// Превратить перечисление значений или подзапрос в ASTSet. node - функция in или notIn.
void makeSet(ASTFunction * node, const Block & sample_block);
/// Выполнить подзапрос в секции global in и запомнить результат во временную таблицу типа memory
/// Все новые временные таблицы хранятся в переменной external_tables
void addExternalStorage(ASTFunction * node, size_t & name_id);
void getArrayJoinedColumns();
void getArrayJoinedColumnsImpl(ASTPtr ast);
void addMultipleArrayJoinAction(ExpressionActions & actions);

View File

@ -181,10 +181,22 @@ private:
* Элемент может остаться на месте, или переместиться в новое место "справа",
* или переместиться левее по цепочке разрешения коллизий, из-за того, что элементы левее него были перемещены в новое место "справа".
*/
for (size_t i = 0; i < old_size; ++i)
size_t i = 0;
for (; i < old_size; ++i)
if (!ZeroTraits::check(buf[i].first))
reinsert(buf[i]);
/** Также имеется особый случай:
* если элемент должен был быть в конце старого буфера, [ x]
* но находится в начале из-за цепочки разрешения коллизий, [o x]
* то после ресайза, он сначала снова окажется не на своём месте, [ xo ]
* и для того, чтобы перенести его куда надо,
* надо будет после переноса всех элементов из старой половинки [ o x ]
* обработать ещё хвостик из цепочки разрешения коллизий сразу после неё [ o x ]
*/
for (; !ZeroTraits::check(buf[i].first); ++i)
reinsert(buf[i]);
#ifdef DBMS_HASH_MAP_DEBUG_RESIZES
watch.stop();
std::cerr << std::fixed << std::setprecision(3)

View File

@ -87,10 +87,22 @@ private:
* Элемент может остаться на месте, или переместиться в новое место "справа",
* или переместиться левее по цепочке разрешения коллизий, из-за того, что элементы левее него были перемещены в новое место "справа".
*/
for (size_t i = 0; i < old_size; ++i)
size_t i = 0;
for (; i < old_size; ++i)
if (!ZeroTraits::check(buf[i]))
reinsert(buf[i]);
/** Также имеется особый случай:
* если элемент должен был быть в конце старого буфера, [ x]
* но находится в начале из-за цепочки разрешения коллизий, [o x]
* то после ресайза, он сначала снова окажется не на своём месте, [ xo ]
* и для того, чтобы перенести его куда надо,
* надо будет после переноса всех элементов из старой половинки [ o x ]
* обработать ещё хвостик из цепочки разрешения коллизий сразу после неё [ o x ]
*/
for (; !ZeroTraits::check(buf[i]); ++i)
reinsert(buf[i]);
#ifdef DBMS_HASH_MAP_DEBUG_RESIZES
watch.stop();
std::cerr << std::fixed << std::setprecision(3)

View File

@ -18,6 +18,7 @@ public:
bool is_view;
bool is_materialized_view;
bool is_populate;
bool is_temporary;
String database;
String table;
ASTPtr columns;
@ -27,8 +28,8 @@ public:
String as_table;
ASTPtr select;
ASTCreateQuery() : attach(false), if_not_exists(false), is_view(false), is_materialized_view(false), is_populate(false) {}
ASTCreateQuery(StringRange range_) : IAST(range_), attach(false), if_not_exists(false), is_view(false), is_materialized_view(false),is_populate(false) {}
ASTCreateQuery() : attach(false), if_not_exists(false), is_view(false), is_materialized_view(false), is_populate(false), is_temporary(false) {}
ASTCreateQuery(StringRange range_) : IAST(range_), attach(false), if_not_exists(false), is_view(false), is_materialized_view(false), is_populate(false), is_temporary(false) {}
/** Получить текст, который идентифицирует этот элемент. */
String getID() const { return (attach ? "AttachQuery_" : "CreateQuery_") + database + "_" + table; };

View File

@ -27,6 +27,11 @@ typedef SharedPtr<IBlockInputStream> BlockInputStreamPtr;
typedef std::vector<BlockInputStreamPtr> BlockInputStreams;
class IStorage;
typedef std::shared_ptr<IStorage> StoragePtr;
/** Хранилище. Отвечает за:
* - хранение данных таблицы;
* - определение, в каком файле (или не файле) хранятся данные;
@ -44,6 +49,11 @@ public:
*/
virtual bool isRemote() const { return false; }
virtual void storeExternalTables(const std::map<String, StoragePtr> & tables_)
{
throw Exception("Method storeExternalTables is not supported by storage " + getName(), ErrorCodes::NOT_IMPLEMENTED);
}
/** Возвращает true, если хранилище поддерживает запросы с секцией SAMPLE.
*/
virtual bool supportsSampling() const { return false; }
@ -59,7 +69,7 @@ public:
/** Не дает изменять описание таблицы (в том числе переименовывать и удалять таблицу).
* Если в течение какой-то операции структура таблицы должна оставаться неизменной, нужно держать такой лок на все ее время.
* Например, нужно держать такой лок на время всего запроса SELECT или INSERT и на все время слияния набора кусков
* (но между выбором кусков для слияния и их слиянием структура таблицы может измениться).
* (но между выбором кусков для слияния и их слиянием структура таблицы может измениться).
* NOTE: Это лок на "чтение" описания таблицы. Чтобы изменить описание таблицы, нужно взять TableStructureWriteLock.
*/
class TableStructureReadLock
@ -67,13 +77,15 @@ public:
private:
friend class IStorage;
StoragePtr storage;
/// Порядок важен.
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) {}
TableStructureReadLock(StoragePtr storage_, bool lock_structure, bool lock_data)
: storage(storage_),
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;
@ -88,7 +100,7 @@ public:
*/
TableStructureReadLockPtr lockStructure(bool will_modify_data)
{
TableStructureReadLockPtr res = new TableStructureReadLock(*this, true, will_modify_data);
TableStructureReadLockPtr res = new TableStructureReadLock(thisPtr(), true, will_modify_data);
if (is_dropped)
throw Exception("Table is dropped", ErrorCodes::TABLE_IS_DROPPED);
return res;
@ -277,7 +289,6 @@ private:
mutable Poco::RWLock structure_lock;
};
typedef std::shared_ptr<IStorage> StoragePtr;
typedef std::vector<StoragePtr> StorageVector;
typedef IStorage::TableStructureReadLocks TableLocks;

View File

@ -25,6 +25,10 @@ public:
*/
virtual const NamesAndTypesList & getColumnsList() const = 0;
/** Получить список имён столбцов таблицы, только невиртуальные.
*/
virtual Names getColumnNamesList() const;
/** Получить описание реального (невиртуального) столбца по его имени.
*/
virtual NameAndTypePair getRealColumn(const String & column_name) const;

View File

@ -33,6 +33,7 @@ namespace DB
* Структура файлов:
* / min-date _ max-date _ min-id _ max-id _ level / - директория с куском.
* Внутри директории с куском:
* checksums.txt - список файлов с их размерами и контрольными суммами.
* primary.idx - индексный файл.
* Column.bin - данные столбца
* Column.mrk - засечки, указывающие, откуда начинать чтение, чтобы пропустить n * k строк.
@ -99,6 +100,33 @@ public:
/// Описание куска с данными.
struct DataPart
{
/** Контрольные суммы всех не временных файлов.
* Для сжатых файлов хранятся чексумма и размер разжатых данных, чтобы не зависеть от способа сжатия.
*/
struct Checksums
{
struct Checksum
{
size_t size;
uint128 hash;
};
typedef std::map<String, Checksum> FileChecksums;
FileChecksums files;
/// Проверяет, что множество столбцов и их контрольные суммы совпадают. Если нет - бросает исключение.
void check(const Checksums & rhs) const;
/// Сериализует и десериализует в человекочитаемом виде.
void readText(ReadBuffer & in);
void writeText(WriteBuffer & out) const;
bool empty() const
{
return files.empty();
}
};
DataPart(MergeTreeData & storage_) : storage(storage_), size_in_bytes(0) {}
MergeTreeData & storage;
@ -121,6 +149,8 @@ public:
typedef std::vector<Field> Index;
Index index;
Checksums checksums;
/// NOTE можно загружать засечки тоже в оперативку
/// Вычисляем сумарный размер всей директории со всеми файлами
@ -204,6 +234,18 @@ public:
size_in_bytes = calcTotalSize(storage.full_path + name + "/");
}
/// Прочитать контрольные суммы, если есть.
bool loadChecksums()
{
String path = storage.full_path + name + "/checksums.txt";
if (!Poco::File(path).exists())
return false;
ReadBufferFromFile file(path, std::min(static_cast<size_t>(DBMS_DEFAULT_BUFFER_SIZE), Poco::File(path).getSize()));
checksums.readText(file);
assertEOF(file);
return true;
}
};
typedef std::shared_ptr<DataPart> MutableDataPartPtr;

View File

@ -31,15 +31,15 @@ typedef std::list<BlockWithDateInterval> BlocksWithDateIntervals;
class MergeTreeDataWriter
{
public:
MergeTreeDataWriter(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataWriter")), flags(O_TRUNC | O_CREAT | O_WRONLY) {}
MergeTreeDataWriter(MergeTreeData & data_) : data(data_), log(&Logger::get("MergeTreeDataWriter")) {}
/** Разбивает блок на блоки, каждый из которых нужно записать в отдельный кусок.
* (читай: разбивает строки по месяцам)
* (читай: разбивает строки по месяцам)
* Работает детерминированно: если отдать на вход такой же блок, на выходе получатся такие же блоки в таком же порядке.
*/
BlocksWithDateIntervals splitBlockIntoParts(const Block & block);
/** Все строки должны относиться к одному месяцу. Возвращает название временного куска.
/** Все строки должны относиться к одному месяцу.
* temp_index - значение left и right для нового куска. Можно будет изменить при переименовании.
* Возвращает кусок с именем, начинающимся с tmp_, еще не добавленный в MergeTreeData.
*/
@ -49,14 +49,6 @@ private:
MergeTreeData & data;
Logger * log;
const int flags;
typedef std::set<std::string> OffsetColumns;
/// Записать данные одного столбца.
void writeData(const String & path, const String & name, const IDataType & type, const IColumn & column,
OffsetColumns & offset_columns, size_t level = 0);
};
}

View File

@ -37,6 +37,8 @@ typedef std::vector<MarkRange> MarkRanges;
*/
class MergeTreeReader
{
typedef std::map<std::string, ColumnPtr> OffsetColumns;
public:
MergeTreeReader(const String & path_, /// Путь к куску
const Names & columns_names_, bool use_uncompressed_cache_, MergeTreeData & storage_)
@ -60,7 +62,6 @@ public:
/// Указатели на столбцы смещений, общие для столбцов из вложенных структур данных
/// Если append, все значения NULL, и offset_columns используется только для проверки, что столбец смещений уже прочитан.
typedef std::map<std::string, ColumnPtr> OffsetColumns;
OffsetColumns offset_columns;
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
@ -124,6 +125,24 @@ public:
{
try
{
/** Для недостающих столбцов из вложенной структуры нужно создавать не столбец пустых массивов, а столбец массивов
* правильных длин.
* TODO: Если для какой-то вложенной структуры были запрошены только отсутствующие столбцы, для них вернутся пустые
* массивы, даже если в куске есть смещения для этой вложенной структуры. Это можно исправить.
*/
/// Сначала запомним столбцы смещений для всех массивов в блоке.
OffsetColumns offset_columns;
for (size_t i = 0; i < res.columns(); ++i)
{
const ColumnWithNameAndType & column = res.getByPosition(i);
if (const ColumnArray * array = dynamic_cast<const ColumnArray *>(&*column.column))
{
String offsets_name = DataTypeNested::extractNestedTableName(column.name);
offset_columns[offsets_name] = array->getOffsetsColumn();
}
}
size_t pos = 0; /// Позиция, куда надо вставить недостающий столбец.
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it, ++pos)
{
@ -133,11 +152,27 @@ public:
column.name = *it;
column.type = storage.getDataTypeByName(*it);
/** Нужно превратить константный столбец в полноценный, так как в части блоков (из других кусков),
* он может быть полноценным (а то интерпретатор может посчитать, что он константный везде).
*/
column.column = dynamic_cast<IColumnConst &>(*column.type->createConstColumn(
res.rows(), column.type->getDefault())).convertToFullColumn();
String offsets_name = DataTypeNested::extractNestedTableName(column.name);
if (offset_columns.count(offsets_name))
{
ColumnPtr offsets_column = offset_columns[offsets_name];
DataTypePtr nested_type = dynamic_cast<DataTypeArray &>(*column.type).getNestedType();
size_t nested_rows = offsets_column->empty() ? 0
: dynamic_cast<ColumnUInt64 &>(*offsets_column).getData().back();
ColumnPtr nested_column = dynamic_cast<IColumnConst &>(*nested_type->createConstColumn(
nested_rows, nested_type->getDefault())).convertToFullColumn();
column.column = new ColumnArray(nested_column, offsets_column);
}
else
{
/** Нужно превратить константный столбец в полноценный, так как в части блоков (из других кусков),
* он может быть полноценным (а то интерпретатор может посчитать, что он константный везде).
*/
column.column = dynamic_cast<IColumnConst &>(*column.type->createConstColumn(
res.rows(), column.type->getDefault())).convertToFullColumn();
}
res.insert(pos, column);
}
@ -266,17 +301,6 @@ private:
addStream(name, *type_arr->getNestedType(), level + 1);
}
else if (const DataTypeNested * type_nested = dynamic_cast<const DataTypeNested *>(&type))
{
String size_name = name + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
String escaped_size_name = escaped_column_name + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
streams[size_name] = new Stream(path + escaped_size_name, uncompressed_cache, mark_cache);
const NamesAndTypesList & columns = *type_nested->getNestedTypesList();
for (NamesAndTypesList::const_iterator it = columns.begin(); it != columns.end(); ++it)
addStream(DataTypeNested::concatenateNestedName(name, it->first), *it->second, level + 1);
}
else
streams[name] = new Stream(path + escaped_column_name, uncompressed_cache, mark_cache);
}

View File

@ -2,8 +2,12 @@
#include <DB/IO/WriteBufferFromFile.h>
#include <DB/IO/CompressedWriteBuffer.h>
#include <DB/IO/HashingWriteBuffer.h>
#include <DB/Storages/MergeTree/MergeTreeData.h>
#include <DB/Common/escapeForFileName.h>
#include <DB/DataTypes/DataTypeNested.h>
#include <DB/DataTypes/DataTypeArray.h>
namespace DB
@ -19,26 +23,41 @@ protected:
typedef std::set<std::string> OffsetColumns;
struct ColumnStream
{
ColumnStream(const String & data_path, const std::string & marks_path) :
plain(data_path, DBMS_DEFAULT_BUFFER_SIZE, O_TRUNC | O_CREAT | O_WRONLY),
compressed(plain),
marks(marks_path, 4096, O_TRUNC | O_CREAT | O_WRONLY) {}
ColumnStream(const String & escaped_column_name_, const String & data_path, const std::string & marks_path) :
escaped_column_name(escaped_column_name_),
plain_file(data_path, DBMS_DEFAULT_BUFFER_SIZE, O_TRUNC | O_CREAT | O_WRONLY),
compressed_buf(plain_file),
marks_file(marks_path, 4096, O_TRUNC | O_CREAT | O_WRONLY),
compressed(compressed_buf), marks(marks_file) {}
WriteBufferFromFile plain;
CompressedWriteBuffer compressed;
WriteBufferFromFile marks;
String escaped_column_name;
WriteBufferFromFile plain_file;
CompressedWriteBuffer compressed_buf;
WriteBufferFromFile marks_file;
HashingWriteBuffer compressed;
HashingWriteBuffer marks;
void finalize()
{
compressed.next();
plain.next();
plain_file.next();
marks.next();
}
void sync()
{
plain.sync();
marks.sync();
plain_file.sync();
marks_file.sync();
}
void addToChecksums(MergeTreeData::DataPart::Checksums & checksums, String name = "")
{
if (name == "")
name = escaped_column_name;
checksums.files[name + ".bin"].size = compressed.count();
checksums.files[name + ".bin"].hash = compressed.getHash();
checksums.files[name + ".mrk"].size = marks.count();
checksums.files[name + ".mrk"].hash = marks.getHash();
}
};
@ -61,26 +80,15 @@ protected:
+ ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
column_streams[size_name] = new ColumnStream(
escaped_size_name,
path + escaped_size_name + ".bin",
path + escaped_size_name + ".mrk");
addStream(path, name, *type_arr->getNestedType(), level + 1);
}
else if (const DataTypeNested * type_nested = dynamic_cast<const DataTypeNested *>(&type))
{
String size_name = name + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
String escaped_size_name = escaped_column_name + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
column_streams[size_name] = new ColumnStream(
path + escaped_size_name + ".bin",
path + escaped_size_name + ".mrk");
const NamesAndTypesList & columns = *type_nested->getNestedTypesList();
for (NamesAndTypesList::const_iterator it = columns.begin(); it != columns.end(); ++it)
addStream(path, DataTypeNested::concatenateNestedName(name, it->first), *it->second, level + 1);
}
else
column_streams[name] = new ColumnStream(
escaped_column_name,
path + escaped_column_name + ".bin",
path + escaped_column_name + ".mrk");
}
@ -116,7 +124,7 @@ protected:
else
{
limit = storage.index_granularity;
writeIntBinary(stream.plain.count(), stream.marks);
writeIntBinary(stream.plain_file.count(), stream.marks);
writeIntBinary(stream.compressed.offset(), stream.marks);
}
@ -126,34 +134,6 @@ protected:
}
}
}
else if (const DataTypeNested * type_nested = dynamic_cast<const DataTypeNested *>(&type))
{
String size_name = name + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
ColumnStream & stream = *column_streams[size_name];
size_t prev_mark = 0;
while (prev_mark < size)
{
size_t limit = 0;
/// Если есть index_offset, то первая засечка идёт не сразу, а после этого количества строк.
if (prev_mark == 0 && index_offset != 0)
{
limit = index_offset;
}
else
{
limit = storage.index_granularity;
writeIntBinary(stream.plain.count(), stream.marks);
writeIntBinary(stream.compressed.offset(), stream.marks);
}
type_nested->serializeOffsets(column, stream.compressed, prev_mark, limit);
stream.compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего.
prev_mark += limit;
}
}
{
ColumnStream & stream = *column_streams[name];
@ -171,7 +151,7 @@ protected:
else
{
limit = storage.index_granularity;
writeIntBinary(stream.plain.count(), stream.marks);
writeIntBinary(stream.plain_file.count(), stream.marks);
writeIntBinary(stream.compressed.offset(), stream.marks);
}
@ -190,30 +170,22 @@ protected:
size_t index_offset;
};
/** Для записи куска, полученного слиянием нескольких других.
* Данные уже отсортированы, относятся к одному месяцу, и пишутся в один кускок.
/** Для записи одного куска. Данные уже отсортированы, относятся к одному месяцу, и пишутся в один кускок.
*/
class MergedBlockOutputStream : public IMergedBlockOutputStream
{
public:
MergedBlockOutputStream(MergeTreeData & storage_,
UInt16 min_date, UInt16 max_date, UInt64 min_part_id, UInt64 max_part_id, UInt32 level)
: IMergedBlockOutputStream(storage_), marks_count(0)
MergedBlockOutputStream(MergeTreeData & storage_, String part_path_, const NamesAndTypesList & columns_list_)
: IMergedBlockOutputStream(storage_), columns_list(columns_list_), part_path(part_path_), marks_count(0)
{
part_name = storage.getPartName(
DayNum_t(min_date), DayNum_t(max_date),
min_part_id, max_part_id, level);
Poco::File(part_path).createDirectories();
part_tmp_path = storage.getFullPath() + "tmp_" + part_name + "/";
part_res_path = storage.getFullPath() + part_name + "/";
Poco::File(part_tmp_path).createDirectories();
index_stream = new WriteBufferFromFile(part_tmp_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, O_TRUNC | O_CREAT | O_WRONLY);
index_file_stream = new WriteBufferFromFile(part_path + "primary.idx", DBMS_DEFAULT_BUFFER_SIZE, O_TRUNC | O_CREAT | O_WRONLY);
index_stream = new HashingWriteBuffer(*index_file_stream);
columns_list = storage.getColumnsList();
for (const auto & it : columns_list)
addStream(part_tmp_path, it.first, *it.second);
addStream(part_path, it.first, *it.second);
}
void write(const Block & block)
@ -234,7 +206,8 @@ public:
{
for (PrimaryColumns::const_iterator it = primary_columns.begin(); it != primary_columns.end(); ++it)
{
(*it)->type->serializeBinary((*(*it)->column)[i], *index_stream);
index_vec.push_back((*(*it)->column)[i]);
(*it)->type->serializeBinary(index_vec.back(), *index_stream);
}
++marks_count;
@ -253,30 +226,49 @@ public:
size_t written_for_last_mark = (storage.index_granularity - index_offset + rows) % storage.index_granularity;
index_offset = (storage.index_granularity - written_for_last_mark) % storage.index_granularity;
}
void writeSuffix()
void writeSuffix() override
{
/// Заканчиваем запись.
throw Exception("Method writeSuffix is not supported by MergedBlockOutputStream", ErrorCodes::NOT_IMPLEMENTED);
}
MergeTreeData::DataPart::Checksums writeSuffixAndGetChecksums()
{
/// Заканчиваем запись и достаем чексуммы.
MergeTreeData::DataPart::Checksums checksums;
index_stream->next();
index_stream = NULL;
checksums.files["primary.idx"].size = index_stream->count();
checksums.files["primary.idx"].hash = index_stream->getHash();
for (ColumnStreams::iterator it = column_streams.begin(); it != column_streams.end(); ++it)
{
it->second->finalize();
it->second->addToChecksums(checksums);
}
index_stream = NULL;
column_streams.clear();
if (marks_count == 0)
{
/// Кусок пустой - все записи удалились.
Poco::File(part_tmp_path).remove(true);
Poco::File(part_path).remove(true);
checksums.files.clear();
}
else
{
/// Переименовываем кусок.
Poco::File(part_tmp_path).renameTo(part_res_path);
/// А добавление нового куска в набор (и удаление исходных кусков) сделает вызывающая сторона.
/// Записываем файл с чексуммами.
WriteBufferFromFile out(part_path + "checksums.txt", 1024);
checksums.writeText(out);
}
return checksums;
}
MergeTreeData::DataPart::Index & getIndex()
{
return index_vec;
}
/// Сколько засечек уже записано.
@ -287,13 +279,13 @@ public:
private:
NamesAndTypesList columns_list;
String part_path;
String part_name;
String part_tmp_path;
String part_res_path;
size_t marks_count;
SharedPtr<WriteBufferFromFile> index_stream;
SharedPtr<WriteBufferFromFile> index_file_stream;
SharedPtr<HashingWriteBuffer> index_stream;
MergeTreeData::DataPart::Index index_vec;
};
typedef Poco::SharedPtr<MergedBlockOutputStream> MergedBlockOutputStreamPtr;
@ -315,7 +307,7 @@ public:
for (size_t i = 0; i < block.columns(); ++i)
{
addStream(part_path, block.getByPosition(i).name,
*block.getByPosition(i).type, 0, prefix + block.getByPosition(i).name);
*block.getByPosition(i).type, 0, prefix + block.getByPosition(i).name);
}
initialized = true;
}
@ -333,19 +325,30 @@ public:
index_offset = (storage.index_granularity - written_for_last_mark) % storage.index_granularity;
}
void writeSuffix()
void writeSuffix() override
{
throw Exception("Method writeSuffix is not supported by MergedColumnOnlyOutputStream", ErrorCodes::NOT_IMPLEMENTED);
}
MergeTreeData::DataPart::Checksums writeSuffixAndGetChecksums()
{
MergeTreeData::DataPart::Checksums checksums;
for (auto & column_stream : column_streams)
{
column_stream.second->finalize();
if (sync)
column_stream.second->sync();
std::string column = escapeForFileName(column_stream.first);
column_stream.second->addToChecksums(checksums, column);
Poco::File(part_path + prefix + column + ".bin").renameTo(part_path + column + ".bin");
Poco::File(part_path + prefix + column + ".mrk").renameTo(part_path + column + ".mrk");
}
column_streams.clear();
initialized = false;
return checksums;
}
private:

View File

@ -49,6 +49,8 @@ public:
bool hasColumn(const String &column_name) const;
bool isRemote() const { return true; }
/// Сохранить временные таблицы, чтобы при следующем вызове метода read переслать их на удаленные сервера
void storeExternalTables(const Tables & tables_) { external_tables = tables_; }
BlockInputStreams read(
const Names & column_names,
@ -92,6 +94,10 @@ private:
const Context & context;
/// Временные таблицы, которые необходимо отправить на сервер. Переменная очищается после каждого вызова метода read
/// Для подготовки к отправке нужно использовтаь метод storeExternalTables
Tables external_tables;
/// Используется только, если таблица должна владеть объектом Cluster, которым больше никто не владеет - для реализации TableFunctionRemote.
SharedPtr<Cluster> owned_cluster;

View File

@ -69,6 +69,8 @@ public:
const NamesAndTypesList & getColumnsList() const { return *columns; }
size_t getSize() const { return data.size(); }
BlockInputStreams read(
const Names & column_names,
ASTPtr query,

View File

@ -44,7 +44,7 @@ public:
String table_name_regexp = safeGet<const String &>(dynamic_cast<ASTLiteral &>(*args[1]).value);
/// В InterpreterSelectQuery будет создан ExpressionAnalzyer, который при обработке запроса наткнется на этот Identifier.
/// Нам необходимо его пометить как имя базы данных, посколку по умолчанию стоит значение column
/// Нам необходимо его пометить как имя базы данных, поскольку по умолчанию стоит значение column
dynamic_cast<ASTIdentifier &>(*args[0]).kind = ASTIdentifier::Database;
return StorageMerge::create(getName(), chooseColumns(source_database, table_name_regexp, context), source_database, table_name_regexp, context);

View File

@ -50,7 +50,7 @@ public:
String password = args.size() == 5 ? safeGet<const String &>(dynamic_cast<ASTLiteral &>(*args[4]).value) : "";
/// В InterpreterSelectQuery будет создан ExpressionAnalzyer, который при обработке запроса наткнется на эти Identifier.
/// Нам необходимо их пометить как имя базы данных и таблицы посколку по умолчанию стоит значение column
/// Нам необходимо их пометить как имя базы данных и таблицы поскольку по умолчанию стоит значение column
dynamic_cast<ASTIdentifier &>(*args[1]).kind = ASTIdentifier::Database;
dynamic_cast<ASTIdentifier &>(*args[2]).kind = ASTIdentifier::Table;

View File

@ -11,6 +11,7 @@
#include <DB/AggregateFunctions/AggregateFunctionQuantile.h>
#include <DB/AggregateFunctions/AggregateFunctionQuantileTiming.h>
#include <DB/AggregateFunctions/AggregateFunctionIf.h>
#include <DB/AggregateFunctions/AggregateFunctionArray.h>
#include <DB/AggregateFunctions/AggregateFunctionFactory.h>
@ -317,9 +318,25 @@ AggregateFunctionPtr AggregateFunctionFactory::get(const String & name, const Da
/// Для агрегатных функций вида aggIf, где agg - имя другой агрегатной функции.
DataTypes nested_dt = argument_types;
nested_dt.pop_back();
AggregateFunctionPtr nested = get(String(name.data(), name.size() - 2), nested_dt);
AggregateFunctionPtr nested = get(String(name.data(), name.size() - 2), nested_dt, recursion_level + 1);
return new AggregateFunctionIf(nested);
}
else if (recursion_level <= 1 && name.size() >= 6 && name.substr(name.size()-5) == "Array")
{
/// Для агрегатных функций вида aggArray, где agg - имя другой агрегатной функции.
size_t num_agruments = argument_types.size();
DataTypes nested_arguments;
for (size_t i = 0; i < num_agruments; ++i)
{
if (const DataTypeArray * array = dynamic_cast<const DataTypeArray *>(&*argument_types[i]))
nested_arguments.push_back(array->getNestedType());
else
throw Exception("Illegal type " + argument_types[i]->getName() + " of argument #" + toString(i + 1) + " for aggregate function " + name + ". Must be array.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
}
AggregateFunctionPtr nested = get(String(name.data(), name.size() - 5), nested_arguments, recursion_level + 1);
return new AggregateFunctionArray(nested);
}
else
throw Exception("Unknown aggregate function " + name, ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION);
}
@ -369,6 +386,9 @@ bool AggregateFunctionFactory::isAggregateFunctionName(const String & name, int
/// Для агрегатных функций вида aggIf, где agg - имя другой агрегатной функции.
if (recursion_level == 0 && name.size() >= 3 && name[name.size() - 2] == 'I' && name[name.size() - 1] == 'f')
return isAggregateFunctionName(String(name.data(), name.size() - 2), 1);
/// Для агрегатных функций вида aggArray, где agg - имя другой агрегатной функции.
if (recursion_level <= 1 && name.size() >= 6 && name.substr(name.size()-5) == "Array")
return isAggregateFunctionName(String(name.data(), name.size() - 5), 1);
return false;
}

View File

@ -15,6 +15,7 @@
#include <unordered_set>
#include <boost/assign/list_inserter.hpp>
#include <boost/program_options.hpp>
#include <Poco/File.h>
#include <Poco/SharedPtr.h>
@ -34,6 +35,7 @@
#include <DB/IO/ReadHelpers.h>
#include <DB/IO/WriteHelpers.h>
#include <DB/IO/copyData.h>
#include <DB/IO/ReadBufferFromIStream.h>
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
@ -56,6 +58,129 @@ namespace DB
using Poco::SharedPtr;
/// Описание внешней таблицы
class ExternalTable
{
public:
std::string file; /// Файл с данными или '-' если stdin
std::string name; /// Имя таблицы
std::string format; /// Название формата хранения данных
/// Описание структуры таблицы: (имя столбца, имя типа данных)
std::vector<std::pair<std::string, std::string> > structure;
ReadBuffer *read_buffer;
Block sample_block;
void initReadBuffer()
{
if (file == "-")
read_buffer = new ReadBufferFromIStream(std::cin);
else
read_buffer = new ReadBufferFromFile(file);
}
void initSampleBlock(const Context &context)
{
for (size_t i = 0; i < structure.size(); ++i)
{
ColumnWithNameAndType column;
column.name = structure[i].first;
column.type = context.getDataTypeFactory().get(structure[i].second);
column.column = column.type->createColumn();
sample_block.insert(column);
}
}
ExternalTableData getData(const Context &context)
{
initReadBuffer();
initSampleBlock(context);
ExternalTableData res = std::make_pair(new AsynchronousBlockInputStream(context.getFormatFactory().getInput(
format, *read_buffer, sample_block, DEFAULT_BLOCK_SIZE, context.getDataTypeFactory())), name);
return res;
}
/// Функция для отладочного вывода информации
void write()
{
std::cerr << "file " << file << std::endl;
std::cerr << "name " << name << std::endl;
std::cerr << "format " << format << std::endl;
std::cerr << "structure: \n";
for (size_t i = 0; i < structure.size(); ++i)
std::cerr << "\t" << structure[i].first << " " << structure[i].second << std::endl;
}
/// Извлечение параметров из variables_map, которая строится по командной строке
ExternalTable(const boost::program_options::variables_map & external_options)
{
if (external_options.count("file"))
file = external_options["file"].as<std::string>();
else
throw Exception("--file field have not been provided for external table", ErrorCodes::BAD_ARGUMENTS);
if (external_options.count("name"))
name = external_options["name"].as<std::string>();
else
throw Exception("--name field have not been provided for external table", ErrorCodes::BAD_ARGUMENTS);
if (external_options.count("format"))
format = external_options["format"].as<std::string>();
else
throw Exception("--format field have not been provided for external table", ErrorCodes::BAD_ARGUMENTS);
if (external_options.count("structure"))
{
std::vector<std::string> temp = external_options["structure"].as<std::vector<std::string>>();
std::string argument;
for (size_t i = 0; i < temp.size(); ++i)
argument = argument + temp[i] + " ";
std::vector<std::string> vals = split(argument, " ,");
if (vals.size() & 1)
throw Exception("Odd number of attributes in section structure", ErrorCodes::BAD_ARGUMENTS);
for (size_t i = 0; i < vals.size(); i += 2)
structure.push_back(std::make_pair(vals[i], vals[i+1]));
}
else if (external_options.count("types"))
{
std::vector<std::string> temp = external_options["types"].as<std::vector<std::string>>();
std::string argument;
for (size_t i = 0; i < temp.size(); ++i)
argument = argument + temp[i] + " ";
std::vector<std::string> vals = split(argument, " ,");
for (size_t i = 0; i < vals.size(); ++i)
structure.push_back(std::make_pair("_" + toString(i + 1), vals[i]));
}
else
throw Exception("Neither --structure nor --types have not been provided for external table", ErrorCodes::BAD_ARGUMENTS);
}
static std::vector<std::string> split(const std::string & s, const std::string &d)
{
std::vector<std::string> res;
std::string now;
for (size_t i = 0; i < s.size(); ++i)
{
if (d.find(s[i]) != std::string::npos)
{
if (!now.empty())
res.push_back(now);
now = "";
continue;
}
now += s[i];
}
if (!now.empty())
res.push_back(now);
return res;
}
};
class Client : public Poco::Util::Application
{
@ -111,6 +236,9 @@ private:
size_t written_progress_chars;
bool written_first_block;
/// Информация о внешних таблицах
std::vector<ExternalTable> external_tables;
void initialize(Poco::Util::Application & self)
{
@ -428,10 +556,25 @@ private:
}
/// Преобразовать внешние таблицы к ExternalTableData и переслать через connection
void sendExternalTables()
{
const ASTSelectQuery * select = dynamic_cast<const ASTSelectQuery *>(&*parsed_query);
if (!select && !external_tables.empty())
throw Exception("External tables could be sent only with select query", ErrorCodes::BAD_ARGUMENTS);
std::vector<ExternalTableData> data;
for (size_t i = 0; i < external_tables.size(); ++i)
data.push_back(external_tables[i].getData(context));
connection->sendExternalTablesData(data);
}
/// Обработать запрос, который не требует передачи блоков данных на сервер.
void processOrdinaryQuery()
{
connection->sendQuery(query, "", QueryProcessingStage::Complete);
connection->sendQuery(query, "", QueryProcessingStage::Complete, NULL, true);
sendExternalTables();
receiveResult();
}
@ -448,7 +591,8 @@ private:
if ((is_interactive && !parsed_insert_query.data) || (stdin_is_not_tty && std_in.eof()))
throw Exception("No data to insert", ErrorCodes::NO_DATA_TO_INSERT);
connection->sendQuery(query_without_data, "", QueryProcessingStage::Complete);
connection->sendQuery(query_without_data, "", QueryProcessingStage::Complete, NULL, true);
sendExternalTables();
/// Получим структуру таблицы
Block sample = receiveSampleBlock();
@ -788,66 +932,91 @@ private:
if (is_interactive && !written_first_block)
std::cout << "Ok." << std::endl;
}
void defineOptions(Poco::Util::OptionSet & options)
public:
void init(int argc, char ** argv)
{
Poco::Util::Application::defineOptions(options);
options.addOption(
Poco::Util::Option("config-file", "c")
.required(false)
.repeatable(false)
.argument("<file>")
.binding("config-file"));
/// Останавливаем внутреннюю обработку командной строки
stopOptionsProcessing();
options.addOption(
Poco::Util::Option("host", "h")
.required(false)
.repeatable(false)
.argument("<host>")
.binding("host"));
/// Перечисляем основные опции командной строки относящиеся к функциональности клиента
boost::program_options::options_description main_description("Main options");
main_description.add_options()
("config-file,c", boost::program_options::value<std::string> (), "config-file")
("host,h", boost::program_options::value<std::string> ()->default_value("localhost"), "host")
("port,p", boost::program_options::value<int> ()->default_value(9000), "port")
("user,u", boost::program_options::value<int> (), "user")
("password,p", boost::program_options::value<int> (), "password")
("query,q", boost::program_options::value<std::string> (), "query")
("database,d", boost::program_options::value<std::string> (), "database")
("multiline,m", "multiline")
;
options.addOption(
Poco::Util::Option("port", "")
.required(false)
.repeatable(false)
.argument("<number>")
.binding("port"));
/// Перечисляем опции командной строки относящиеся к внешним таблицам
boost::program_options::options_description external_description("Main options");
external_description.add_options()
("file", boost::program_options::value<std::string> (), "data file or - for stdin")
("name", boost::program_options::value<std::string> ()->default_value("_data"), "name of the table")
("format", boost::program_options::value<std::string> ()->default_value("TabSeparated"), "data format")
("structure", boost::program_options::value<std::vector<std::string>> ()->multitoken(), "structure")
("types", boost::program_options::value<std::vector<std::string>> ()->multitoken(), "types")
;
options.addOption(
Poco::Util::Option("user", "u")
.required(false)
.repeatable(false)
.argument("<number>")
.binding("user"));
std::vector<int> positions;
options.addOption(
Poco::Util::Option("password", "")
.required(false)
.repeatable(false)
.argument("<number>")
.binding("password"));
positions.push_back(0);
for (int i = 1; i < argc; ++i)
if (strcmp(argv[i], "--external") == 0)
positions.push_back(i);
positions.push_back(argc);
options.addOption(
Poco::Util::Option("query", "e")
.required(false)
.repeatable(false)
.argument("<string>")
.binding("query"));
/// Парсим основные опции командной строки
boost::program_options::variables_map options;
boost::program_options::store(boost::program_options::parse_command_line(positions[1] - positions[0], argv, main_description), options);
options.addOption(
Poco::Util::Option("database", "d")
.required(false)
.repeatable(false)
.argument("<string>")
.binding("database"));
options.addOption(
Poco::Util::Option("multiline", "m")
.required(false)
.repeatable(false)
.binding("multiline"));
size_t stdin_count = 0;
for (size_t i = 1; i + 1 < positions.size(); ++i)
{
boost::program_options::variables_map external_options;
boost::program_options::store(boost::program_options::parse_command_line(
positions[i+1] - positions[i], &argv[positions[i]], external_description), external_options);
try
{
external_tables.push_back(ExternalTable(external_options));
if (external_tables.back().file == "-")
stdin_count ++;
if (stdin_count > 1)
throw Exception("Two or more external tables has stdin (-) set as --file field", ErrorCodes::BAD_ARGUMENTS);
}
catch (const Exception & e)
{
std::string text = e.displayText();
std::cerr << "Code: " << e.code() << ". " << text << std::endl;
std::cerr << "Table #" << i << std::endl << std::endl;
exit(e.code());
}
}
/// Сохраняем полученные данные во внутренний конфиг
if (options.count("config-file"))
config().setString("config-file", options["config-file"].as<std::string>());
if (options.count("host"))
config().setString("host", options["host"].as<std::string>());
if (options.count("query"))
config().setString("query", options["query"].as<std::string>());
if (options.count("database"))
config().setString("database", options["database"].as<std::string>());
if (options.count("port"))
config().setInt("port", options["port"].as<int>());
if (options.count("user"))
config().setInt("user", options["user"].as<int>());
if (options.count("password"))
config().setInt("password", options["password"].as<int>());
if (options.count("multiline"))
config().setBool("multiline", true);
}
};

View File

@ -196,7 +196,7 @@ bool Connection::ping()
}
void Connection::sendQuery(const String & query, const String & query_id_, UInt64 stage, const Settings * settings)
void Connection::sendQuery(const String & query, const String & query_id_, UInt64 stage, const Settings * settings, bool with_pending_data)
{
forceConnected();
@ -231,6 +231,10 @@ void Connection::sendQuery(const String & query, const String & query_id_, UInt6
maybe_compressed_out = NULL;
block_in = NULL;
block_out = NULL;
/// Если версия сервера достаточно новая и стоит флаг, отправляем пустой блок, символизируя конец передачи данных.
if (server_revision >= DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES && !with_pending_data)
sendData(Block());
}
@ -243,7 +247,7 @@ void Connection::sendCancel()
}
void Connection::sendData(const Block & block)
void Connection::sendData(const Block & block, const String & name)
{
//LOG_TRACE(log, "Sending data (" << getServerAddress() << ")");
@ -258,12 +262,34 @@ void Connection::sendData(const Block & block)
}
writeVarUInt(Protocol::Client::Data, *out);
if (server_revision >= DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES)
writeStringBinary(name, *out);
block.checkNestedArraysOffsets();
block_out->write(block);
maybe_compressed_out->next();
out->next();
}
void Connection::sendExternalTablesData(ExternalTablesData & data)
{
/// Если работаем со старым сервером, то никакой информации не отправляем
if (server_revision < DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES)
return;
for (size_t i = 0; i < data.size(); ++i)
{
data[i].first->readPrefix();
while(Block block = data[i].first->read())
sendData(block, data[i].second);
data[i].first->readSuffix();
}
/// Отправляем пустой блок, символизируя конец передачи данных
sendData(Block());
}
bool Connection::poll(size_t timeout_microseconds)
{
@ -336,6 +362,11 @@ Block Connection::receiveData()
initBlockInput();
String external_table_name;
if (server_revision >= DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES)
readStringBinary(external_table_name, *in);
/// Прочитать из сети один блок
return block_in->read();
}

View File

@ -55,7 +55,7 @@ int main(int argc, char ** argv)
std::cerr << std::endl;
Arr arr2 = arr;
Arr arr2 = std::move(arr);
std::cerr << arr.size() << ", " << arr2.size() << std::endl;
@ -79,7 +79,7 @@ int main(int argc, char ** argv)
for (size_t j = 0; j < n; ++j)
key[j] = DB::toString(rand());
map[key] = "Hello, world! " + DB::toString(i);
map[std::move(key)] = "Hello, world! " + DB::toString(i);
}
for (Map::const_iterator it = map.begin(); it != map.end(); ++it)
@ -94,7 +94,7 @@ int main(int argc, char ** argv)
std::cerr << std::endl;
Map map2 = map;
Map map2 = std::move(map);
for (Map::const_iterator it = map2.begin(); it != map2.end(); ++it)
{
@ -123,7 +123,7 @@ int main(int argc, char ** argv)
for (size_t j = 0; j < n; ++j)
key[j] = DB::toString(rand());
vec.push_back(key);
vec.push_back(std::move(key));
}
for (Vec::const_iterator it = vec.begin(); it != vec.end(); ++it)
@ -136,7 +136,7 @@ int main(int argc, char ** argv)
std::cerr << std::endl;
Vec vec2 = vec;
Vec vec2 = std::move(vec);
for (Vec::const_iterator it = vec2.begin(); it != vec2.end(); ++it)
{
@ -224,7 +224,7 @@ int main(int argc, char ** argv)
arr2[i] = "Goodbye, world! " + DB::toString(i);
}
arr2 = arr1;
arr2 = std::move(arr1);
arr1.resize(n);
std::cerr

View File

@ -129,7 +129,7 @@ void CollapsingSortedBlockInputStream::merge(Block & merged_block, ColumnPlainPt
/// Запишем данные для предыдущего визита.
insertRows(merged_columns, merged_rows);
current_key = next_key;
current_key = std::move(next_key);
next_key.resize(description.size());
count_negative = 0;

View File

@ -90,7 +90,7 @@ void SummingSortedBlockInputStream::merge(Block & merged_block, ColumnPlainPtrs
insertCurrentRow(merged_columns);
}
current_key = next_key;
current_key = std::move(next_key);
next_key.resize(description.size());
setRow(current_row, current);

View File

@ -21,14 +21,15 @@
#include <DB/Parsers/ParserCreateQuery.h>
#include <DB/Parsers/ASTExpressionList.h>
#include <DB/Parsers/ASTNameTypePair.h>
#include <DB/Parsers/ASTLiteral.h>
namespace DB
{
DataTypeFactory::DataTypeFactory()
: fixed_string_regexp("^FixedString\\s*\\(\\s*(\\d+)\\s*\\)$"),
nested_regexp("^(\\w+)\\s*\\(\\s*(.+)\\s*\\)$", Poco::RegularExpression::RE_MULTILINE | Poco::RegularExpression::RE_DOTALL)
: fixed_string_regexp(R"--(^FixedString\s*\(\s*(\d+)\s*\)$)--"),
nested_regexp(R"--(^(\w+)\s*\(\s*(.+)\s*\)$)--", Poco::RegularExpression::RE_MULTILINE | Poco::RegularExpression::RE_DOTALL)
{
boost::assign::insert(non_parametric_data_types)
("UInt8", new DataTypeUInt8)
@ -71,6 +72,7 @@ DataTypePtr DataTypeFactory::get(const String & name) const
String function_name;
AggregateFunctionPtr function;
DataTypes argument_types;
Array params_row;
ParserExpressionList args_parser;
ASTPtr args_ast;
@ -87,14 +89,38 @@ DataTypePtr DataTypeFactory::get(const String & name) const
throw Exception("Data type AggregateFunction requires parameters: "
"name of aggregate function and list of data types for arguments", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
ASTs::iterator it = args_list.children.begin();
function_name = (*it)->getColumnName();
if (ASTFunction * parametric = dynamic_cast<ASTFunction *>(&*args_list.children[0]))
{
if (parametric->parameters)
throw Exception("Unexpected level of parameters to aggregate function", ErrorCodes::SYNTAX_ERROR);
function_name = parametric->name;
for (++it; it != args_list.children.end(); ++it)
argument_types.push_back(get((*it)->getColumnName()));
ASTs & parameters = dynamic_cast<ASTExpressionList &>(*parametric->arguments).children;
params_row.resize(parameters.size());
for (size_t i = 0; i < parameters.size(); ++i)
{
ASTLiteral * lit = dynamic_cast<ASTLiteral *>(&*parameters[i]);
if (!lit)
throw Exception("Parameters to aggregate functions must be literals",
ErrorCodes::PARAMETERS_TO_AGGREGATE_FUNCTIONS_MUST_BE_LITERALS);
params_row[i] = lit->value;
}
}
else
{
function_name = args_list.children[0]->getColumnName();
}
for (size_t i = 1; i < args_list.children.size(); ++i)
argument_types.push_back(get(args_list.children[i]->getColumnName()));
function = AggregateFunctionFactory().get(function_name, argument_types);
return new DataTypeAggregateFunction(function, argument_types);
if (!params_row.empty())
function->setParameters(params_row);
function->setArguments(argument_types);
return new DataTypeAggregateFunction(function, argument_types, params_row);
}
if (base_name == "Nested")

View File

@ -191,8 +191,10 @@ FunctionPtr FunctionFactory::get(
else if (name == "tuple") return new FunctionTuple;
else if (name == "tupleElement") return new FunctionTupleElement;
else if (name == "in") return new FunctionIn;
else if (name == "notIn") return new FunctionIn(true);
else if (name == "in") return new FunctionIn(false, false);
else if (name == "notIn") return new FunctionIn(true, false);
else if (name == "globalIn") return new FunctionIn(false, true);
else if (name == "globalNotIn") return new FunctionIn(true, true);
else if (name == "array") return new FunctionArray;
else if (name == "arrayElement") return new FunctionArrayElement;

View File

@ -36,6 +36,12 @@ void assertString(const char * s, ReadBuffer & buf)
}
}
void assertEOF(ReadBuffer & buf)
{
if (!buf.eof())
throwAtAssertionFailed("eof", buf);
}
void readString(String & s, ReadBuffer & buf)
{
s = "";

View File

@ -0,0 +1,102 @@
#include <DB/IO/HashingWriteBuffer.h>
#include <DB/IO/WriteBufferFromFile.h>
#define FAIL(msg) { std::cout << msg; exit(1); }
uint128 referenceHash(char * data, size_t len)
{
const size_t block_size = DBMS_DEFAULT_HASHING_BLOCK_SIZE;
uint128 state(0, 0);
size_t pos;
for (pos = 0; pos + block_size <= len; pos += block_size)
{
state = CityHash128WithSeed(data + pos, block_size, state);
}
if (pos < len)
state = CityHash128WithSeed(data + pos, len - pos, state);
return state;
}
void test(size_t data_size)
{
std::vector<char> vec(data_size);
char * data = &vec[0];
for (size_t i = 0; i < data_size; ++i)
data[i] = rand() & 255;
uint128 reference = referenceHash(data, data_size);
DB::WriteBufferFromFile sink("/dev/null", 1 << 16);
{
DB::HashingWriteBuffer buf(sink);
for (size_t pos = 0; pos < data_size;)
{
size_t len = std::min(static_cast<size_t>(rand() % 10000 + 1), data_size - pos);
buf.write(data + pos, len);
buf.next();
pos += len;
}
if (buf.getHash() != reference)
FAIL("failed on data size " << data_size << " writing random chunks of up to 10000 bytes");
}
{
DB::HashingWriteBuffer buf(sink);
for (size_t pos = 0; pos < data_size;)
{
size_t len = std::min(static_cast<size_t>(rand() % 5 + 1), data_size - pos);
buf.write(data + pos, len);
buf.next();
pos += len;
}
if (buf.getHash() != reference)
FAIL("failed on data size " << data_size << " writing random chunks of up to 5 bytes");
}
{
DB::HashingWriteBuffer buf(sink);
for (size_t pos = 0; pos < data_size;)
{
size_t len = std::min(static_cast<size_t>(2048 + rand() % 3 - 1), data_size - pos);
buf.write(data + pos, len);
buf.next();
pos += len;
}
if (buf.getHash() != reference)
FAIL("failed on data size " << data_size << " writing random chunks of 2048 +-1 bytes");
}
{
DB::HashingWriteBuffer buf(sink);
buf.write(data, data_size);
if (buf.getHash() != reference)
FAIL("failed on data size " << data_size << " writing all at once");
}
}
int main()
{
test(5);
test(100);
test(2048);
test(2049);
test(100000);
test(1 << 17);
return 0;
}

View File

@ -86,7 +86,7 @@ void Aggregator::initialize(Block & block)
for (size_t j = 0; j < arguments_size; ++j)
argument_types[j] = block.getByPosition(aggregates[i].arguments[j]).type;
col.type = new DataTypeAggregateFunction(aggregates[i].function, argument_types);
col.type = new DataTypeAggregateFunction(aggregates[i].function, argument_types, aggregates[i].parameters);
col.column = new ColumnAggregateFunction(aggregates[i].function);
sample.insert(col);

View File

@ -161,27 +161,80 @@ void Context::assertDatabaseDoesntExist(const String & database_name) const
}
Tables Context::getExternalTables() const
{
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
Tables res = external_tables;
if (session_context && session_context != this)
{
Tables buf = session_context->getExternalTables();
res.insert(buf.begin(), buf.end());
}
else if (global_context && global_context != this)
{
Tables buf = global_context->getExternalTables();
res.insert(buf.begin(), buf.end());
}
return res;
}
StoragePtr Context::tryGetExternalTable(const String & table_name) const
{
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
Tables::const_iterator jt;
if (external_tables.end() == (jt = external_tables.find(table_name)))
return StoragePtr();
return jt->second;
}
StoragePtr Context::getTable(const String & database_name, const String & table_name) const
{
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
Databases::const_iterator it;
Tables::const_iterator jt;
if (database_name.empty())
{
StoragePtr res;
if (res = tryGetExternalTable(table_name))
return res;
if (session_context && (res = session_context->tryGetExternalTable(table_name)))
return res;
if (global_context && (res = global_context->tryGetExternalTable(table_name)))
return res;
}
String db = database_name.empty() ? current_database : database_name;
Databases::const_iterator it;
if (shared->databases.end() == (it = shared->databases.find(db)))
throw Exception("Database " + db + " doesn't exist", ErrorCodes::UNKNOWN_DATABASE);
Tables::const_iterator jt;
if (it->second.end() == (jt = it->second.find(table_name)))
throw Exception("Table " + db + "." + table_name + " doesn't exist.", ErrorCodes::UNKNOWN_TABLE);
return jt->second;
}
StoragePtr Context::tryGetTable(const String & database_name, const String & table_name) const
{
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
if (database_name.empty())
{
StoragePtr res;
if (res = tryGetExternalTable(table_name))
return res;
if (session_context && (res = session_context->tryGetExternalTable(table_name)))
return res;
if (global_context && (res = global_context->tryGetExternalTable(table_name)))
return res;
}
String db = database_name.empty() ? current_database : database_name;
Databases::const_iterator it;
@ -196,6 +249,14 @@ StoragePtr Context::tryGetTable(const String & database_name, const String & tab
}
void Context::addExternalTable(const String & table_name, StoragePtr storage)
{
if (external_tables.end() != external_tables.find(table_name))
throw Exception("Temporary table " + table_name + " already exists.", ErrorCodes::TABLE_ALREADY_EXISTS);
external_tables[table_name] = storage;
}
void Context::addTable(const String & database_name, const String & table_name, StoragePtr table)
{
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);

View File

@ -9,6 +9,7 @@
#include <DB/Parsers/ASTSubquery.h>
#include <DB/Parsers/ASTSet.h>
#include <DB/Parsers/ASTOrderByElement.h>
#include <DB/Parsers/ParserSelectQuery.h>
#include <DB/DataTypes/DataTypeSet.h>
#include <DB/DataTypes/DataTypeTuple.h>
@ -22,6 +23,11 @@
#include <DB/Storages/StorageMergeTree.h>
#include <DB/Storages/StorageDistributed.h>
#include <DB/Storages/StorageMemory.h>
#include <DB/DataStreams/copyData.h>
#include <DB/Parsers/formatAST.h>
namespace DB
@ -397,6 +403,10 @@ void ExpressionAnalyzer::normalizeTreeImpl(ASTPtr & ast, MapOfASTs & finished_as
current_asts.insert(ast);
replaced = true;
}
/// может быть указано in t, где t - таблица, что равносильно select * from t.
if (node->name == "in" || node->name == "notIn" || node->name == "globalIn" || node->name == "globalNotIn")
if (ASTIdentifier * right = dynamic_cast<ASTIdentifier *>(&*node->arguments->children[1]))
right->kind = ASTIdentifier::Table;
}
else if (ASTIdentifier * node = dynamic_cast<ASTIdentifier *>(&*ast))
{
@ -505,10 +515,91 @@ void ExpressionAnalyzer::normalizeTreeImpl(ASTPtr & ast, MapOfASTs & finished_as
}
void ExpressionAnalyzer::findGlobalFunctions(ASTPtr & ast, std::vector<ASTPtr> & global_nodes)
{
/// Рекурсивные вызовы. Не опускаемся в подзапросы.
for (ASTs::iterator it = ast->children.begin(); it != ast->children.end(); ++it)
if (!dynamic_cast<ASTSelectQuery *>(&**it))
findGlobalFunctions(*it, global_nodes);
if (ASTFunction * node = dynamic_cast<ASTFunction *>(&*ast))
{
if (node->name == "globalIn" || node->name == "globalNotIn")
{
global_nodes.push_back(ast);
}
}
}
void ExpressionAnalyzer::addExternalStorage(ASTFunction * node, size_t & name_id)
{
IAST & args = *node->arguments;
ASTPtr & arg = args.children[1];
StoragePtr external_storage = StoragePtr();
/// Если подзапрос или имя таблицы для селекта
if (dynamic_cast<ASTSubquery *>(&*arg) || dynamic_cast<ASTIdentifier *>(&*arg))
{
/** Для подзапроса в секции IN не действуют ограничения на максимальный размер результата.
* Так как результат этого поздапроса - ещё не результат всего запроса.
* Вместо этого работают ограничения max_rows_in_set, max_bytes_in_set, set_overflow_mode.
*/
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);
ASTPtr subquery;
if (ASTIdentifier * table = dynamic_cast<ASTIdentifier *>(&*arg))
{
ParserSelectQuery parser;
if (context.tryGetExternalTable(table->name))
return;
String query = "SELECT * FROM " + table->name;
const char * begin = query.data();
const char * end = begin + query.size();
const char * pos = begin;
const char * expected = "";
bool parse_res = parser.parse(pos, end, subquery, expected);
if (!parse_res)
throw Exception("Error in parsing select query while creating set for table " + table->name + ".",
ErrorCodes::LOGICAL_ERROR);
}
else
subquery = arg->children[0];
InterpreterSelectQuery interpreter(subquery, subquery_context, QueryProcessingStage::Complete, subquery_depth + 1);
Block sample = interpreter.getSampleBlock();
NamesAndTypesListPtr columns = new NamesAndTypesList(sample.getColumnsList());
String external_table_name = "_data" + toString(name_id++);
external_storage = StorageMemory::create(external_table_name, columns);
BlockOutputStreamPtr output = external_storage->write(ASTPtr());
copyData(*interpreter.execute(), *output);
ASTIdentifier * ast_ident = new ASTIdentifier();
ast_ident->kind = ASTIdentifier::Table;
ast_ident->name = external_storage->getTableName();
arg = ast_ident;
external_tables.push_back(external_storage);
}
else
throw Exception("Global in (notIn) supports only select data.", ErrorCodes::BAD_ARGUMENTS);
}
void ExpressionAnalyzer::makeSet(ASTFunction * node, const Block & sample_block)
{
/** Нужно преобразовать правый аргумент в множество.
* Это может быть значение, перечисление значений или подзапрос.
* Это может быть имя таблицы, значение, перечисление значений или подзапрос.
* Перечисление значений парсится как функция tuple.
*/
IAST & args = *node->arguments;
@ -517,7 +608,8 @@ void ExpressionAnalyzer::makeSet(ASTFunction * node, const Block & sample_block)
if (dynamic_cast<ASTSet *>(&*arg))
return;
if (dynamic_cast<ASTSubquery *>(&*arg))
/// Если подзапрос или имя таблицы для селекта
if (dynamic_cast<ASTSubquery *>(&*arg) || dynamic_cast<ASTIdentifier *>(&*arg))
{
/// Получаем поток блоков для подзапроса, отдаем его множеству, и кладём это множество на место подзапроса.
ASTSet * ast_set = new ASTSet(arg->getColumnName());
@ -541,7 +633,27 @@ void ExpressionAnalyzer::makeSet(ASTFunction * node, const Block & sample_block)
subquery_settings.extremes = 0;
subquery_context.setSettings(subquery_settings);
InterpreterSelectQuery interpreter(arg->children[0], subquery_context, QueryProcessingStage::Complete, subquery_depth + 1);
ASTPtr subquery;
if (ASTIdentifier * table = dynamic_cast<ASTIdentifier *>(&*arg))
{
ParserSelectQuery parser;
String query = "SELECT * FROM " + table->name;
const char * begin = query.data();
const char * end = begin + query.size();
const char * pos = begin;
const char * expected = "";
bool parse_res = parser.parse(pos, end, subquery, expected);
if (!parse_res)
throw Exception("Error in parsing select query while creating set for table " + table->name + ".",
ErrorCodes::LOGICAL_ERROR);
}
else
subquery = arg->children[0];
InterpreterSelectQuery interpreter(subquery, subquery_context, QueryProcessingStage::Complete, subquery_depth + 1);
ast_set->set = new Set(settings.limits);
ast_set->set->setSource(interpreter.execute());
sets_with_subqueries[ast_set->getColumnName()] = ast_set->set;
@ -773,7 +885,7 @@ void ExpressionAnalyzer::getActionsImpl(ASTPtr ast, bool no_subqueries, bool onl
if (node->kind == ASTFunction::FUNCTION)
{
if (node->name == "in" || node->name == "notIn")
if (node->name == "in" || node->name == "notIn" || node->name == "globalIn" || node->name == "globalNotIn")
{
if (!no_subqueries)
{
@ -1004,7 +1116,7 @@ void ExpressionAnalyzer::getAggregatesImpl(ASTPtr ast, ExpressionActions & actio
if (node->parameters)
{
ASTs & parameters = dynamic_cast<ASTExpressionList &>(*node->parameters).children;
Row params_row(parameters.size());
Array params_row(parameters.size());
for (size_t i = 0; i < parameters.size(); ++i)
{
@ -1015,6 +1127,7 @@ void ExpressionAnalyzer::getAggregatesImpl(ASTPtr ast, ExpressionActions & actio
params_row[i] = lit->value;
}
aggregate.parameters = params_row;
aggregate.function->setParameters(params_row);
}
@ -1067,6 +1180,20 @@ void ExpressionAnalyzer::addMultipleArrayJoinAction(ExpressionActions & actions)
actions.add(ExpressionActions::Action::arrayJoin(result_columns));
}
void ExpressionAnalyzer::processGlobalOperations()
{
std::vector<ASTPtr> global_nodes;
findGlobalFunctions(ast, global_nodes);
size_t id = 1;
for (size_t i = 0; i < global_nodes.size(); ++i)
{
String external_table_name = "_data";
while (context.tryGetExternalTable(external_table_name + toString(id)))
id ++;
addExternalStorage(dynamic_cast<ASTFunction *>(&*global_nodes[i]), id);
}
}
bool ExpressionAnalyzer::appendArrayJoin(ExpressionActionsChain & chain)
{

View File

@ -133,8 +133,6 @@ void InterpreterAlterQuery::execute()
/// Это позволит сделать большую часть первого MODIFY, не останавливая чтение из таблицы.
IStorage::TableStructureWriteLockPtr table_hard_lock;
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
/// todo cycle over sub tables and tables
/// Применяем изменения
for (ASTAlterQuery::ParameterContainer::const_iterator alter_it = alter.parameters.begin();

View File

@ -97,14 +97,17 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
{
Poco::ScopedLock<Poco::Mutex> lock(context.getMutex());
context.assertDatabaseExists(database_name);
if (context.isTableExist(database_name, table_name))
if (!create.is_temporary)
{
if (create.if_not_exists)
return context.getTable(database_name, table_name);
else
throw Exception("Table " + database_name + "." + table_name + " already exists.", ErrorCodes::TABLE_ALREADY_EXISTS);
context.assertDatabaseExists(database_name);
if (context.isTableExist(database_name, table_name))
{
if (create.if_not_exists)
return context.getTable(database_name, table_name);
else
throw Exception("Table " + database_name + "." + table_name + " already exists.", ErrorCodes::TABLE_ALREADY_EXISTS);
}
}
/// Получаем список столбцов
@ -170,6 +173,13 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
storage_name = as_storage->getName();
create.storage = dynamic_cast<const ASTCreateQuery &>(*context.getCreateQuery(as_database_name, as_table_name)).storage;
}
else if (create.is_temporary)
{
storage_name = "Memory";
ASTFunction * func = new ASTFunction();
func->name = storage_name;
create.storage = func;
}
else if (create.is_view)
{
storage_name = "View";
@ -191,7 +201,7 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
storage_name, data_path, table_name, database_name, context.getGlobalContext(), query_ptr, columns, create.attach);
/// Проверка наличия метаданных таблицы на диске и создание метаданных
if (!assume_metadata_exists)
if (!assume_metadata_exists && !create.is_temporary)
{
if (Poco::File(metadata_path).exists())
{
@ -225,7 +235,13 @@ StoragePtr InterpreterCreateQuery::execute(bool assume_metadata_exists)
}
}
context.addTable(database_name, table_name, res);
if (create.is_temporary)
{
res->is_dropped = true;
context.getSessionContext().addExternalTable(table_name, res);
}
else
context.addTable(database_name, table_name, res);
}
/// Если запрос CREATE SELECT, то вставим в таблицу данные

View File

@ -78,6 +78,13 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndType
query_analyzer = new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth);
/// Выполняем все Global in подзапросы, результаты будут сохранены в query_analyzer->external_tables
query_analyzer->processGlobalOperations();
/// Сохраняем в query context новые временные таблицы
for (auto & it : query_analyzer->external_tables)
context.addExternalTable(it->getTableName(), it);
if (input_)
streams.push_back(input_);
}
@ -126,18 +133,23 @@ void InterpreterSelectQuery::getDatabaseAndTableNames(String & database_name, St
/** Если таблица не указана - используем таблицу system.one.
* Если база данных не указана - используем текущую базу данных.
*/
if (query.database)
database_name = dynamic_cast<ASTIdentifier &>(*query.database).name;
if (query.table)
table_name = dynamic_cast<ASTIdentifier &>(*query.table).name;
if (!query.table)
{
database_name = "system";
table_name = "one";
}
else if (!query.database)
database_name = context.getCurrentDatabase();
if (query.database)
database_name = dynamic_cast<ASTIdentifier &>(*query.database).name;
if (query.table)
table_name = dynamic_cast<ASTIdentifier &>(*query.table).name;
{
if (context.tryGetTable("", table_name))
database_name = "";
else
database_name = context.getCurrentDatabase();
}
}
@ -293,7 +305,7 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
settings.limits.max_rows_to_group_by &&
settings.limits.group_by_overflow_mode == OverflowMode::ANY &&
settings.totals_mode != TotalsMode::AFTER_HAVING_EXCLUSIVE;
/// Нужно ли после агрегации сразу финализироыать агрегатные функции.
/// Нужно ли после агрегации сразу финализировать агрегатные функции.
bool aggregate_final =
need_aggregate &&
to_stage > QueryProcessingStage::WithMergeableState &&
@ -511,6 +523,8 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
/// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос?
if (!interpreter_subquery)
{
if (storage->isRemote())
storage->storeExternalTables(context.getExternalTables());
streams = storage->read(required_columns, query_ptr, settings_for_storage, from_stage, settings.max_block_size, settings.max_threads);
for (auto stream : streams)
{

View File

@ -48,7 +48,7 @@
* Но в этом тесте осталось нечто похожее на старый сценарий использования хэш-таблиц при агрегации.
*/
#define USE_AUTO_ARRAY 1
#define USE_AUTO_ARRAY 0
int main(int argc, char ** argv)
@ -131,7 +131,7 @@ int main(int argc, char ** argv)
map.emplace(data[i], it, inserted);
if (inserted)
{
new(&it->second) Value(value);
new(&it->second) Value(std::move(value));
INIT;
}
}
@ -155,7 +155,7 @@ int main(int argc, char ** argv)
std::unordered_map<Key, Value, DB::default_hash<Key> >::iterator it;
for (size_t i = 0; i < n; ++i)
{
it = map.insert(std::make_pair(data[i], value)).first;
it = map.insert(std::make_pair(data[i], std::move(value))).first;
INIT;
}
@ -176,7 +176,7 @@ int main(int argc, char ** argv)
map.set_empty_key(-1ULL);
for (size_t i = 0; i < n; ++i)
{
it = map.insert(std::make_pair(data[i], value)).first;
it = map.insert(std::make_pair(data[i], std::move(value))).first;
INIT;
}
@ -196,7 +196,7 @@ int main(int argc, char ** argv)
google::sparse_hash_map<Key, Value, DB::default_hash<Key> >::iterator it;
for (size_t i = 0; i < n; ++i)
{
map.insert(std::make_pair(data[i], value));
map.insert(std::make_pair(data[i], std::move(value)));
INIT;
}

View File

@ -47,6 +47,8 @@ const char * ParserComparisonExpression::operators[] =
"NOT LIKE", "notLike",
"IN", "in",
"NOT IN", "notIn",
"GLOBAL IN", "globalIn",
"GLOBAL NOT IN","globalNotIn",
nullptr
};

View File

@ -168,6 +168,7 @@ bool ParserCreateQuery::parseImpl(Pos & pos, Pos end, ASTPtr & node, const char
ParserWhiteSpaceOrComments ws;
ParserString s_create("CREATE", true, true);
ParserString s_temporary("TEMPORARY", true, true);
ParserString s_attach("ATTACH", true, true);
ParserString s_table("TABLE", true, true);
ParserString s_database("DATABASE", true, true);
@ -199,6 +200,7 @@ bool ParserCreateQuery::parseImpl(Pos & pos, Pos end, ASTPtr & node, const char
bool is_view = false;
bool is_materialized_view = false;
bool is_populate = false;
bool is_temporary = false;
ws.ignore(pos, end);
@ -212,6 +214,12 @@ bool ParserCreateQuery::parseImpl(Pos & pos, Pos end, ASTPtr & node, const char
ws.ignore(pos, end);
if (s_temporary.ignore(pos, end, expected))
{
is_temporary = true;
ws.ignore(pos, end);
}
if (s_database.ignore(pos, end, expected))
{
ws.ignore(pos, end);
@ -403,6 +411,7 @@ bool ParserCreateQuery::parseImpl(Pos & pos, Pos end, ASTPtr & node, const char
query->is_view = is_view;
query->is_materialized_view = is_materialized_view;
query->is_populate = is_populate;
query->is_temporary = is_temporary;
if (database)
query->database = dynamic_cast<ASTIdentifier &>(*database).name;

View File

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

View File

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

View File

@ -22,6 +22,8 @@
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
#include <DB/Interpreters/executeQuery.h>
#include <DB/Storages/StorageMemory.h>
#include "TCPHandler.h"
@ -112,13 +114,25 @@ void TCPHandler::runImpl()
if (!receivePacket())
continue;
/// Получить блоки временных таблиц
if (client_revision >= DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES)
readData(global_settings);
/// Пересоздаем, поскольку получая данные внешних таблиц, мы получили пустой блок.
/// Из-за этого весь stream помечен как cancelled
state.block_in = BlockInputStreamPtr();
/// Обрабатываем Query
state.io = executeQuery(state.query, query_context, false, state.stage);
if (state.io.out)
state.is_insert = true;
after_check_cancelled.restart();
after_send_progress.restart();
/// Запрос требует приёма данных от клиента?
if (state.io.out)
if (state.is_insert)
processInsertQuery(global_settings);
else
processOrdinaryQuery();
@ -203,13 +217,8 @@ void TCPHandler::runImpl()
}
void TCPHandler::processInsertQuery(const Settings & global_settings)
void TCPHandler::readData(const Settings & global_settings)
{
/// Отправляем клиенту блок - структура таблицы.
Block block = state.io.out_sample;
sendData(block);
state.io.out->writePrefix();
while (1)
{
/// Ждём пакета от клиента. При этом, каждые POLL_INTERVAL сек. проверяем, не требуется ли завершить работу.
@ -223,6 +232,17 @@ void TCPHandler::processInsertQuery(const Settings & global_settings)
if (!receivePacket())
break;
}
}
void TCPHandler::processInsertQuery(const Settings & global_settings)
{
/// Отправляем клиенту блок - структура таблицы.
Block block = state.io.out_sample;
sendData(block);
state.io.out->writePrefix();
readData(global_settings);
state.io.out->writeSuffix();
}
@ -518,20 +538,40 @@ void TCPHandler::receiveQuery()
LOG_DEBUG(log, "Query ID: " << state.query_id);
LOG_DEBUG(log, "Query: " << state.query);
LOG_DEBUG(log, "Requested stage: " << QueryProcessingStage::toString(stage));
state.io = executeQuery(state.query, query_context, false, state.stage);
}
bool TCPHandler::receiveData()
{
initBlockInput();
/// Прочитать из сети один блок и засунуть его в state.io.out (данные для INSERT-а)
/// Имя временной таблицы для записи данных, по умолчанию пустая строка
String external_table_name;
if (client_revision >= DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES)
readStringBinary(external_table_name, *in);
/// Прочитать из сети один блок и записать его
Block block = state.block_in->read();
if (block)
{
state.io.out->write(block);
/// Если запрос на вставку, то данные нужно писать напрямую в state.io.out.
/// Иначе пишем блоки во временную таблицу external_table_name.
if (!state.is_insert)
{
StoragePtr storage;
/// Если такой таблицы не существовало, создаем ее.
if (!(storage = query_context.tryGetExternalTable(external_table_name)))
{
NamesAndTypesListPtr columns = new NamesAndTypesList(block.getColumnsList());
storage = StorageMemory::create(external_table_name, columns);
query_context.addExternalTable(external_table_name, storage);
}
/// Данные будем писать напрямую в таблицу.
state.io.out = storage->write(ASTPtr());
}
if (block)
state.io.out->write(block);
return true;
}
else
@ -614,6 +654,8 @@ void TCPHandler::sendData(Block & block)
initBlockOutput();
writeVarUInt(Protocol::Server::Data, *out);
if (client_revision >= DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES)
writeStringBinary("", *out);
state.block_out->write(block);
state.maybe_compressed_out->next();

View File

@ -45,6 +45,8 @@ struct QueryState
bool is_empty;
/// Данные были отправлены.
bool sent_all_data;
/// Запрос на вставку или нет.
bool is_insert;
/// Для вывода прогресса - разница после предыдущей отправки прогресса.
volatile size_t rows_processed;
@ -52,7 +54,7 @@ struct QueryState
QueryState() : query_id(""), stage(QueryProcessingStage::Complete), compression(Protocol::Compression::Disable),
is_cancelled(false), is_empty(true), sent_all_data(false), rows_processed(0), bytes_processed(0) {}
is_cancelled(false), is_empty(true), sent_all_data(false), is_insert(false), rows_processed(0), bytes_processed(0) {}
void reset()
{
@ -107,6 +109,7 @@ private:
bool receivePacket();
void receiveQuery();
bool receiveData();
void readData(const Settings & global_settings);
/// Обработать запрос INSERT
void processInsertQuery(const Settings & global_settings);

View File

@ -20,6 +20,16 @@ bool ITableDeclaration::hasRealColumn(const String &column_name) const
}
Names ITableDeclaration::getColumnNamesList() const
{
const NamesAndTypesList & real_columns = getColumnsList();
Names res;
for (auto & it : real_columns)
res.push_back(it.first);
return res;
}
NameAndTypePair ITableDeclaration::getRealColumn(const String &column_name) const
{
const NamesAndTypesList & real_columns = getColumnsList();

View File

@ -9,6 +9,7 @@
#include <DB/Parsers/ASTIdentifier.h>
#include <DB/Parsers/ASTNameTypePair.h>
#include <DB/DataStreams/ExpressionBlockInputStream.h>
#include <DB/DataStreams/copyData.h>
@ -210,6 +211,7 @@ void MergeTreeData::loadDataParts()
try
{
part->loadIndex();
part->loadChecksums();
}
catch (...)
{
@ -363,26 +365,15 @@ void MergeTreeData::removeColumnFiles(String column_name)
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;
Names out_names;
out_expression = new ExpressionActions(
NamesAndTypesList(1, NameAndTypePair(in_column_name, getDataTypeByName(in_column_name))), context.getSettingsRef());
ASTExpressionList * arguments = new ASTExpressionList;
ASTPtr arguments_ptr = arguments;
FunctionPtr function = context.getFunctionFactory().get("to" + out_type, context);
out_expression->add(ExpressionActions::Action::applyFunction(function, Names(1, in_column_name)), out_names);
out_expression->add(ExpressionActions::Action::removeColumn(in_column_name));
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();
out_column = out_names[0];
}
static DataTypePtr getDataTypeByName(const String & name, const NamesAndTypesList & columns)
@ -442,18 +433,29 @@ void MergeTreeData::prepareAlterModify(const ASTAlterQuery::Parameters & params)
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);
in.readPrefix();
out.writePrefix();
try
{
while(DB::Block b = in.read())
{
/// оставляем только столбец с результатом
b.erase(0);
out.write(b);
in.readSuffix();
DataPart::Checksums add_checksums = out.writeSuffixAndGetChecksums();
/// Запишем обновленные контрольные суммы во временный файл.
if (!part->checksums.empty())
{
DataPart::Checksums new_checksums = part->checksums;
std::string escaped_name = escapeForFileName(name_type.name);
std::string escaped_out_column = escapeForFileName(out_column);
new_checksums.files[escaped_name + ".bin"] = add_checksums.files[escaped_out_column + ".bin"];
new_checksums.files[escaped_name + ".mrk"] = add_checksums.files[escaped_out_column + ".mrk"];
WriteBufferFromFile checksums_file(full_path + part->name + '/' + escaped_out_column + ".checksums.txt", 1024);
new_checksums.writeText(checksums_file);
}
LOG_TRACE(log, "Write Suffix");
out.writeSuffix();
}
catch (const Exception & e)
{
@ -483,40 +485,63 @@ void MergeTreeData::commitAlterModify(const ASTAlterQuery::Parameters & params)
/// переименовываем старые столбцы, добавляя расширение .old
for (DataPartPtr & part : parts)
{
std::string path = full_path + part->name + '/' + escapeForFileName(name_type.name);
std::string part_path = full_path + part->name + '/';
std::string path = part_path + 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");
if (Poco::File(part_path + "checksums.txt").exists())
{
LOG_TRACE(log, "Renaming " << part_path + "checksums.txt" << " to " << part_path + "checksums.txt" + ".old");
Poco::File(part_path + "checksums.txt").renameTo(part_path + "checksums.txt" + ".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);
std::string part_path = full_path + part->name + '/';
std::string name = escapeForFileName(out_column);
std::string new_name = escapeForFileName(name_type.name);
std::string path = part_path + name;
std::string new_path = part_path + new_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");
if (Poco::File(path + ".checksums.txt").exists())
{
LOG_TRACE(log, "Renaming " << path + ".checksums.txt" << " to " << part_path + ".checksums.txt");
Poco::File(path + ".checksums.txt").renameTo(part_path + "checksums.txt");
}
}
}
// удаляем старые столбцы
for (DataPartPtr & part : parts)
{
std::string path = full_path + part->name + '/' + escapeForFileName(name_type.name);
std::string part_path = full_path + part->name + '/';
std::string path = part_path + 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();
if (Poco::File(part_path + "checksums.txt" + ".old").exists())
{
LOG_TRACE(log, "Removing old checksums " << part_path + "checksums.txt" + ".old");
Poco::File(part_path + "checksums.txt" + ".old").remove();
}
}
}
@ -685,4 +710,80 @@ MergeTreeData::DataParts MergeTreeData::getDataParts()
return data_parts;
}
void MergeTreeData::DataPart::Checksums::check(const Checksums & rhs) const
{
for (const auto & it : rhs.files)
{
const String & name = it.first;
if (!files.count(name))
throw Exception("Unexpected file " + name + " in data part", ErrorCodes::UNEXPECTED_FILE_IN_DATA_PART);
}
for (const auto & it : files)
{
const String & name = it.first;
auto jt = rhs.files.find(name);
if (jt == rhs.files.end())
throw Exception("No file " + name + " in data part", ErrorCodes::NO_FILE_IN_DATA_PART);
const Checksum & expected = it.second;
const Checksum & found = jt->second;
if (expected.size != found.size)
throw Exception("Unexpected size of file " + name + " in data part", ErrorCodes::BAD_SIZE_OF_FILE_IN_DATA_PART);
if (expected.hash != found.hash)
throw Exception("Checksum mismatch for file " + name + " in data part", ErrorCodes::CHECKSUM_DOESNT_MATCH);
}
}
void MergeTreeData::DataPart::Checksums::readText(ReadBuffer & in)
{
files.clear();
size_t count;
DB::assertString("checksums format version: 1\n", in);
DB::readText(count, in);
DB::assertString(" files:\n", in);
for (size_t i = 0; i < count; ++i)
{
String name;
Checksum sum;
DB::readString(name, in);
DB::assertString("\n\tsize: ", in);
DB::readText(sum.size, in);
DB::assertString("\n\thash: ", in);
DB::readText(sum.hash.first, in);
DB::assertString(" ", in);
DB::readText(sum.hash.second, in);
DB::assertString("\n", in);
files.insert(std::make_pair(name, sum));
}
}
void MergeTreeData::DataPart::Checksums::writeText(WriteBuffer & out) const
{
DB::writeString("checksums format version: 1\n", out);
DB::writeText(files.size(), out);
DB::writeString(" files:\n", out);
for (const auto & it : files)
{
DB::writeString(it.first, out);
DB::writeString("\n\tsize: ", out);
DB::writeText(it.second.size, out);
DB::writeString("\n\thash: ", out);
DB::writeText(it.second.hash.first, out);
DB::writeString(" ", out);
DB::writeText(it.second.hash.second, out);
DB::writeString("\n", out);
}
}
}

View File

@ -292,8 +292,10 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa
throw Exception("Unknown mode of operation for MergeTreeData: " + toString(data.mode), ErrorCodes::LOGICAL_ERROR);
}
MergedBlockOutputStreamPtr to = new MergedBlockOutputStream(data,
new_data_part->left_date, new_data_part->right_date, new_data_part->left, new_data_part->right, new_data_part->level);
String new_part_tmp_path = data.getFullPath() + "tmp_" + new_data_part->name + "/";
String new_part_res_path = data.getFullPath() + new_data_part->name + "/";
MergedBlockOutputStreamPtr to = new MergedBlockOutputStream(data, new_part_tmp_path, data.getColumnsList());
merged_stream->readPrefix();
to->writePrefix();
@ -309,7 +311,9 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa
}
merged_stream->readSuffix();
to->writeSuffix();
new_data_part->checksums = to->writeSuffixAndGetChecksums();
new_data_part->index.swap(to->getIndex());
/// В обычном режиме строчки не могут удалиться при мердже.
if (0 == to->marksCount() && data.mode == MergeTreeData::Ordinary)
@ -324,8 +328,8 @@ String MergeTreeDataMerger::mergeParts(const MergeTreeData::DataPartsVector & pa
return "";
}
/// NOTE Только что записанный индекс заново считывается с диска. Можно было бы формировать его сразу при записи.
new_data_part->loadIndex();
/// Переименовываем кусок.
Poco::File(new_part_tmp_path).renameTo(new_part_res_path);
/// Добавляем новый кусок в набор.
data.replaceParts(parts, new_data_part);

View File

@ -1,7 +1,9 @@
#include <DB/Storages/MergeTree/MergeTreeDataWriter.h>
#include <DB/Storages/MergeTree/MergedBlockOutputStream.h>
#include <DB/Common/escapeForFileName.h>
#include <DB/DataTypes/DataTypeNested.h>
#include <DB/DataTypes/DataTypeArray.h>
#include <DB/IO/HashingWriteBuffer.h>
namespace DB
{
@ -77,9 +79,7 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDa
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;
size_t part_size = (block.rows() + data.index_granularity - 1) / data.index_granularity;
String tmp_part_name = "tmp_" + data.getPartName(
DayNum_t(min_date), DayNum_t(max_date),
@ -101,47 +101,12 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDa
/// Сортируем.
stableSortBlock(block, sort_descr);
/// Наконец-то можно писать данные на диск.
LOG_TRACE(log, "Writing index.");
MergedBlockOutputStream out(data, part_tmp_path, block.getColumnsList());
out.getIndex().reserve(part_size * sort_descr.size());
/// Сначала пишем индекс. Индекс содержит значение 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);
}
out.writePrefix();
out.write(block);
MergeTreeData::DataPart::Checksums checksums = out.writeSuffixAndGetChecksums();
MergeTreeData::MutableDataPartPtr new_data_part = std::make_shared<MergeTreeData::DataPart>(data);
new_data_part->left_date = DayNum_t(min_date);
@ -154,95 +119,10 @@ MergeTreeData::MutableDataPartPtr MergeTreeDataWriter::writeTempPart(BlockWithDa
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);
new_data_part->index.swap(out.getIndex());
new_data_part->checksums = checksums;
return new_data_part;
}
void MergeTreeDataWriter::writeData(const String & path, const String & name, const IDataType & type, const IColumn & column,
OffsetColumns & offset_columns, size_t level)
{
String escaped_column_name = escapeForFileName(name);
size_t size = column.size();
/// Для массивов требуется сначала сериализовать размеры, а потом значения.
if (const DataTypeArray * type_arr = dynamic_cast<const DataTypeArray *>(&type))
{
String size_name = escapeForFileName(DataTypeNested::extractNestedTableName(name))
+ ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
if (offset_columns.count(size_name) == 0)
{
offset_columns.insert(size_name);
WriteBufferFromFile plain(path + size_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags);
WriteBufferFromFile marks(path + size_name + ".mrk", 4096, flags);
CompressedWriteBuffer compressed(plain);
size_t prev_mark = 0;
while (prev_mark < size)
{
/// Каждая засечка - это: (смещение в файле до начала сжатого блока, смещение внутри блока)
writeIntBinary(plain.count(), marks);
writeIntBinary(compressed.offset(), marks);
type_arr->serializeOffsets(column, compressed, prev_mark, data.index_granularity);
prev_mark += data.index_granularity;
compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего.
}
compressed.next();
plain.next();
marks.next();
}
}
if (const DataTypeNested * type_nested = dynamic_cast<const DataTypeNested *>(&type))
{
String size_name = escaped_column_name + ARRAY_SIZES_COLUMN_NAME_SUFFIX + toString(level);
WriteBufferFromFile plain(path + size_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags);
WriteBufferFromFile marks(path + size_name + ".mrk", 4096, flags);
CompressedWriteBuffer compressed(plain);
size_t prev_mark = 0;
while (prev_mark < size)
{
/// Каждая засечка - это: (смещение в файле до начала сжатого блока, смещение внутри блока)
writeIntBinary(plain.count(), marks);
writeIntBinary(compressed.offset(), marks);
type_nested->serializeOffsets(column, compressed, prev_mark, data.index_granularity);
prev_mark += data.index_granularity;
compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего.
}
compressed.next();
plain.next();
marks.next();
}
{
WriteBufferFromFile plain(path + escaped_column_name + ".bin", DBMS_DEFAULT_BUFFER_SIZE, flags);
WriteBufferFromFile marks(path + escaped_column_name + ".mrk", 4096, flags);
CompressedWriteBuffer compressed(plain);
size_t prev_mark = 0;
while (prev_mark < size)
{
writeIntBinary(plain.count(), marks);
writeIntBinary(compressed.offset(), marks);
type.serializeBinary(column, compressed, prev_mark, data.index_granularity);
prev_mark += data.index_granularity;
compressed.nextIfAtEnd(); /// Чтобы вместо засечек, указывающих на конец сжатого блока, были засечки, указывающие на начало следующего.
}
compressed.next();
plain.next();
marks.next();
}
}
}

View File

@ -210,6 +210,7 @@ BlockInputStreams StorageDistributed::read(
&new_settings,
need_host_column ? _host_column_name : "",
need_port_column ? _port_column_name : "",
external_tables,
processed_stage);
if (processed_stage == QueryProcessingStage::WithMergeableState || columns_to_remove.empty())
@ -218,7 +219,7 @@ BlockInputStreams StorageDistributed::read(
res.push_back(new RemoveColumnsBlockInputStream(temp, columns_to_remove));
}
if (all_inclusive || values.find(std::make_pair("localhost", clickhouse_port)) != values.end())
if (cluster.getLocalNodesNum() > 0 && (all_inclusive || values.find(std::make_pair("localhost", clickhouse_port)) != values.end()))
{
ASTPtr modified_query_ast = remakeQuery(
query,
@ -228,6 +229,9 @@ BlockInputStreams StorageDistributed::read(
/// Добавляем запросы к локальному ClickHouse
DB::Context new_context = context;
new_context.setSettings(new_settings);
for (auto & it : external_tables)
if (!new_context.tryGetExternalTable(it.first))
new_context.addExternalTable(it.first, it.second);
for(size_t i = 0; i < cluster.getLocalNodesNum(); ++i)
{
@ -238,6 +242,7 @@ BlockInputStreams StorageDistributed::read(
res.push_back(new RemoveColumnsBlockInputStream(interpreter.execute(), columns_to_remove));
}
}
external_tables.clear();
return res;
}

View File

@ -134,6 +134,8 @@ void StorageMergeTree::mergeThread(bool while_can, bool aggressive)
{
while (!shutdown_called)
{
auto structure_lock = lockStructure(false);
/// Удаляем старые куски. На случай, если в слиянии что-то сломано, и из следующего блока вылетит исключение.
data.clearOldParts();
@ -161,17 +163,13 @@ void StorageMergeTree::mergeThread(bool while_can, bool aggressive)
}
}
{
auto structure_lock = lockStructure(false);
if (!merger.selectPartsToMerge(parts, disk_space, false, aggressive, only_small, can_merge) &&
!merger.selectPartsToMerge(parts, disk_space, true, aggressive, only_small, can_merge))
break;
}
if (!merger.selectPartsToMerge(parts, disk_space, false, aggressive, only_small, can_merge) &&
!merger.selectPartsToMerge(parts, disk_space, true, aggressive, only_small, can_merge))
break;
merging_tagger = new CurrentlyMergingPartsTagger(parts, merger.estimateDiskSpaceForMerge(parts), *this);
}
auto structure_lock = lockStructure(true);
merger.mergeParts(merging_tagger->parts);
}

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

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

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

@ -0,0 +1,109 @@
#!/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
if [ "$1" == "--generate" ]; then
GENERATE=1
shift
else
GENERATE=0
fi
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 [[ $GENERATE -eq 1 && ( -z "$1" || "$@" =~ "$test_name") ]]; then
cp $result_file $reference_file
echo -e "$MSG_GENERATED - no reference file"
else
echo -e "$MSG_UNKNOWN - no reference file (use --generate [test_name]... to create)"
fi
else
diff $reference_file $result_file > $diff_file
if [ -s "$diff_file" ]; then
ERRORS=$(($ERRORS + 1))
echo -e "$MSG_FAIL - result differs with reference:"
cat $diff_file
else
echo -e "$MSG_OK"
rm $error_file $result_file $diff_file
fi
fi
done
done
echo
if [ $ERRORS -gt 0 ]; then
echo -e "${COLOR_FAIL}Having $ERRORS errors!${COLOR_RESET}"
exit 1
else
echo -e "${COLOR_OK}All tests passed.${COLOR_RESET}"
exit 0
fi

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1 @@
SELECT 1

View File

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

View File

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

View File

@ -0,0 +1 @@
33232

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
Hello
Goodbye

View File

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

View File

@ -0,0 +1,2 @@
Hello
Goodbye

View File

@ -0,0 +1 @@
SELECT x FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x)

View File

@ -0,0 +1,6 @@
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye

View File

@ -0,0 +1 @@
SELECT x FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x, [1, 2, 3] AS arr) ARRAY JOIN arr

View File

@ -0,0 +1,6 @@
Hello 1
Hello 2
Hello 3
Goodbye 1
Goodbye 2
Goodbye 3

View File

@ -0,0 +1 @@
SELECT x, a FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x, [1, 2, 3] AS arr) ARRAY JOIN arr AS a

View File

@ -0,0 +1,6 @@
Hello 1 [1,2,3]
Hello 2 [1,2,3]
Hello 3 [1,2,3]
Goodbye 1 [1,2,3]
Goodbye 2 [1,2,3]
Goodbye 3 [1,2,3]

View File

@ -0,0 +1 @@
SELECT x, a, arr FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x, [1, 2, 3] AS arr) ARRAY JOIN arr AS a

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS arrays_test

View File

@ -0,0 +1 @@
CREATE TABLE arrays_test (s String, arr Array(UInt8)) ENGINE = Memory

View File

@ -0,0 +1 @@
INSERT INTO arrays_test VALUES ('Hello', [1,2]), ('World', [3,4,5]), ('Goodbye', [])

View File

@ -0,0 +1,3 @@
Hello [1,2]
World [3,4,5]
Goodbye []

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