mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-22 15:42:02 +00:00
Merge
This commit is contained in:
commit
634c33c766
120
dbms/include/DB/AggregateFunctions/AggregateFunctionArray.h
Normal file
120
dbms/include/DB/AggregateFunctions/AggregateFunctionArray.h
Normal 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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -47,7 +47,7 @@ public:
|
||||
nested_func->setArguments(nested_arguments);
|
||||
}
|
||||
|
||||
void setParameters(const Row & params)
|
||||
void setParameters(const Array & params)
|
||||
{
|
||||
nested_func->setParameters(params);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/// Получить тип результата.
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
/// Отправили запрос (это делается перед получением первого блока).
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
}
|
||||
|
||||
/// Получить тип результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение.
|
||||
|
@ -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;
|
||||
|
||||
|
@ -19,7 +19,7 @@ private:
|
||||
* - size_compressed содержит сжатый размер этого блока.
|
||||
*/
|
||||
ReadBufferFromFile file_in;
|
||||
size_t size_compressed;
|
||||
size_t size_compressed = 0;
|
||||
|
||||
bool nextImpl()
|
||||
{
|
||||
|
97
dbms/include/DB/IO/HashingWriteBuffer.h
Normal file
97
dbms/include/DB/IO/HashingWriteBuffer.h
Normal 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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -27,6 +27,7 @@ namespace DB
|
||||
struct AggregateDescription
|
||||
{
|
||||
AggregateFunctionPtr function;
|
||||
Array parameters; /// Параметры (параметрической) агрегатной функции.
|
||||
ColumnNumbers arguments;
|
||||
Names argument_names; /// Используются, если arguments не заданы.
|
||||
String column_name; /// Какое имя использовать для столбца со значениями агрегатной функции
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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; };
|
||||
|
@ -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;
|
||||
|
||||
|
@ -25,6 +25,10 @@ public:
|
||||
*/
|
||||
virtual const NamesAndTypesList & getColumnsList() const = 0;
|
||||
|
||||
/** Получить список имён столбцов таблицы, только невиртуальные.
|
||||
*/
|
||||
virtual Names getColumnNamesList() const;
|
||||
|
||||
/** Получить описание реального (невиртуального) столбца по его имени.
|
||||
*/
|
||||
virtual NameAndTypePair getRealColumn(const String & column_name) const;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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")
|
||||
|
@ -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;
|
||||
|
@ -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 = "";
|
||||
|
102
dbms/src/IO/tests/hashing_write_buffer.cpp
Normal file
102
dbms/src/IO/tests/hashing_write_buffer.cpp
Normal 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;
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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, то вставим в таблицу данные
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,8 @@ const char * ParserComparisonExpression::operators[] =
|
||||
"NOT LIKE", "notLike",
|
||||
"IN", "in",
|
||||
"NOT IN", "notIn",
|
||||
"GLOBAL IN", "globalIn",
|
||||
"GLOBAL NOT IN","globalNotIn",
|
||||
nullptr
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
4
dbms/tests/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.result
|
||||
*.diff
|
||||
*.error
|
||||
test_data
|
109
dbms/tests/clickhouse-test
Executable file
109
dbms/tests/clickhouse-test
Executable 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
|
1
dbms/tests/queries/0_stateless/00001_select_1.reference
Normal file
1
dbms/tests/queries/0_stateless/00001_select_1.reference
Normal file
@ -0,0 +1 @@
|
||||
1
|
1
dbms/tests/queries/0_stateless/00001_select_1.sql
Normal file
1
dbms/tests/queries/0_stateless/00001_select_1.sql
Normal file
@ -0,0 +1 @@
|
||||
SELECT 1
|
@ -0,0 +1,10 @@
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
1
dbms/tests/queries/0_stateless/00002_system_numbers.sql
Normal file
1
dbms/tests/queries/0_stateless/00002_system_numbers.sql
Normal file
@ -0,0 +1 @@
|
||||
SELECT * FROM system.numbers LIMIT 10
|
@ -0,0 +1 @@
|
||||
33232
|
@ -0,0 +1 @@
|
||||
SELECT number FROM system.numbers WHERE reinterpretAsString(number) = 'Ё' LIMIT 1
|
@ -0,0 +1,2 @@
|
||||
-1
|
||||
-1
|
@ -0,0 +1 @@
|
||||
SELECT (dummy AS x) - 1 FROM remote('127.0.0.{1,2}', system, one)
|
@ -0,0 +1 @@
|
||||
2
|
@ -0,0 +1 @@
|
||||
SELECT count() FROM remote('127.0.0.{1,2}', system, one) WHERE arrayExists((x) -> x = 1, [1, 2, 3])
|
1
dbms/tests/queries/0_stateless/00006_1_set_extremes.sql
Normal file
1
dbms/tests/queries/0_stateless/00006_1_set_extremes.sql
Normal file
@ -0,0 +1 @@
|
||||
SET GLOBAL extremes = 1
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"meta":
|
||||
[
|
||||
{
|
||||
"name": "'Hello, world'",
|
||||
"type": "String"
|
||||
}
|
||||
],
|
||||
|
||||
"data":
|
||||
[
|
||||
|
||||
],
|
||||
|
||||
"rows": 0,
|
||||
|
||||
"rows_before_limit_at_least": 10
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
SELECT 'Hello, world' FROM (SELECT number FROM system.numbers LIMIT 10) WHERE number < 0
|
||||
FORMAT JSONCompact
|
@ -0,0 +1 @@
|
||||
SET GLOBAL extremes = 0
|
1
dbms/tests/queries/0_stateless/00007_array.reference
Normal file
1
dbms/tests/queries/0_stateless/00007_array.reference
Normal file
@ -0,0 +1 @@
|
||||
['Hello','Goodbye']
|
1
dbms/tests/queries/0_stateless/00007_array.sql
Normal file
1
dbms/tests/queries/0_stateless/00007_array.sql
Normal file
@ -0,0 +1 @@
|
||||
SELECT ['Hello', 'Goodbye']
|
@ -0,0 +1,2 @@
|
||||
Hello
|
||||
Goodbye
|
1
dbms/tests/queries/0_stateless/00008_array_join.sql
Normal file
1
dbms/tests/queries/0_stateless/00008_array_join.sql
Normal file
@ -0,0 +1 @@
|
||||
SELECT arrayJoin(['Hello', 'Goodbye'])
|
@ -0,0 +1,2 @@
|
||||
Hello
|
||||
Goodbye
|
@ -0,0 +1 @@
|
||||
SELECT x FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x)
|
@ -0,0 +1,6 @@
|
||||
Hello
|
||||
Hello
|
||||
Hello
|
||||
Goodbye
|
||||
Goodbye
|
||||
Goodbye
|
1
dbms/tests/queries/0_stateless/00010_big_array_join.sql
Normal file
1
dbms/tests/queries/0_stateless/00010_big_array_join.sql
Normal file
@ -0,0 +1 @@
|
||||
SELECT x FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x, [1, 2, 3] AS arr) ARRAY JOIN arr
|
@ -0,0 +1,6 @@
|
||||
Hello 1
|
||||
Hello 2
|
||||
Hello 3
|
||||
Goodbye 1
|
||||
Goodbye 2
|
||||
Goodbye 3
|
@ -0,0 +1 @@
|
||||
SELECT x, a FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x, [1, 2, 3] AS arr) ARRAY JOIN arr AS a
|
@ -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]
|
@ -0,0 +1 @@
|
||||
SELECT x, a, arr FROM (SELECT arrayJoin(['Hello', 'Goodbye']) AS x, [1, 2, 3] AS arr) ARRAY JOIN arr AS a
|
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS arrays_test
|
@ -0,0 +1 @@
|
||||
CREATE TABLE arrays_test (s String, arr Array(UInt8)) ENGINE = Memory
|
@ -0,0 +1 @@
|
||||
INSERT INTO arrays_test VALUES ('Hello', [1,2]), ('World', [3,4,5]), ('Goodbye', [])
|
@ -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
Loading…
Reference in New Issue
Block a user