Merge branch 'master' into feature/suggestions

This commit is contained in:
morty 2018-07-02 11:52:45 +03:00
commit 35c0ab9783
49 changed files with 560 additions and 126 deletions

View File

@ -1,11 +1,4 @@
en:
## Improvements:
* `clickhouse-client`: option --ask-password for interactively ask for credentials #1044
# en:
ru:
## Улучшения:
* `clickhouse-client`: опция --ask-password для интерактивного ввода пароля #1044
# ru:

View File

@ -1,3 +1,73 @@
# ClickHouse release 1.1.54388, 2018-06-28
## Новые возможности:
* Добавлена поддержка запроса `ALTER TABLE t DELETE WHERE` для реплицированных таблиц и таблица `system.mutations`.
* Добавлена поддержка запроса `ALTER TABLE t [REPLACE|ATTACH] PARTITION` для *MergeTree-таблиц.
* Добавлена поддержка запроса `TRUNCATE TABLE` ([Winter Zhang](https://github.com/yandex/ClickHouse/pull/2260))
* Добавлено несколько новых `SYSTEM`-запросов для реплицированных таблиц (`RESTART REPLICAS`, `SYNC REPLICA`, `[STOP|START] [MERGES|FETCHES|REPLICATED SENDS|REPLICATION QUEUES]`).
* Добавлена возможность записи в таблицу с движком MySQL и соответствующую табличную функцию ([sundy-li](https://github.com/yandex/ClickHouse/pull/2294)).
* Добавлена табличная функция `url()` и движок таблиц `URL` ([Александр Сапин](https://github.com/yandex/ClickHouse/pull/2501)).
* Добавлена агрегатная функция `windowFunnel` ([sundy-li](https://github.com/yandex/ClickHouse/pull/2352)).
* Добавлены функции `startsWith` и `endsWith` для строк ([Вадим Плахтинский](https://github.com/yandex/ClickHouse/pull/2429)).
* В табличной функции `numbers()` добавлена возможность указывать offset ([Winter Zhang](https://github.com/yandex/ClickHouse/pull/2535)).
* Добавлена возможность интерактивного ввода пароля в `clickhouse-client`.
* Добавлена возможность отправки логов сервера в syslog ([Александр Крашенинников](https://github.com/yandex/ClickHouse/pull/2459)).
* Добавлена поддержка логирования в словарях с источником shared library ([Александр Сапин](https://github.com/yandex/ClickHouse/pull/2472)).
* Добавлена поддержка произвольного разделителя в формате CSV ([Иван Жуков](https://github.com/yandex/ClickHouse/pull/2263))
* Добавлена настройка `date_time_input_format`. Если переключить эту настройку в значение `'best_effort'`, значения DateTime будут читаться в широком диапазоне форматов.
* Добавлена утилита `clickhouse-obfuscator` для обфускации данных. Пример использования: публикация данных, используемых в тестах производительности.
## Экспериментальные возможности:
* Добавлена возможность вычислять аргументы функции `and` только там, где они нужны ([Анастасия Царькова](https://github.com/yandex/ClickHouse/pull/2272))
* Добавлена возможность JIT-компиляции в нативный код некоторых выражений ([pyos](https://github.com/yandex/ClickHouse/pull/2277)).
## Исправление ошибок:
* Исправлено появление дублей в запросе с `DISTINCT` и `ORDER BY`.
* Запросы с `ARRAY JOIN` и `arrayFilter` раньше возвращали некорректный результат.
* Исправлена ошибка при чтении столбца-массива из Nested-структуры ([#2066](https://github.com/yandex/ClickHouse/issues/2066)).
* Исправлена ошибка при анализе запросов с секцией HAVING вида `HAVING tuple IN (...)`.
* Исправлена ошибка при анализе запросов с рекурсивными алиасами.
* Исправлена ошибка при чтении из ReplacingMergeTree с условием в PREWHERE, фильтрующим все строки ([#2525](https://github.com/yandex/ClickHouse/issues/2525)).
* Настройки профиля пользователя не применялись при использовании сессий в HTTP-интерфейсе.
* Исправлено применение настроек из параметров командной строки в программе clickhouse-local.
* Клиентская библиотека ZooKeeper теперь использует таймаут сессии, полученный от сервера.
* Исправлена ошибка в клиентской библиотеке ZooKeeper, из-за которой ожидание ответа от сервера могло длиться дольше таймаута.
* Исправлено отсечение ненужных кусков при запросе с условием на столбцы ключа партиционирования ([#2342](https://github.com/yandex/ClickHouse/issues/2342)).
* После `CLEAR COLUMN IN PARTITION` в соответствующей партиции теперь возможны слияния ([#2315](https://github.com/yandex/ClickHouse/issues/2315)).
* Исправлено соответствие типов в табличной функции ODBC ([sundy-li](https://github.com/yandex/ClickHouse/pull/2268)).
* Исправлено некорректное сравнение типов `DateTime` с таймзоной и без неё ([Александр Бочаров](https://github.com/yandex/ClickHouse/pull/2400)).
* Исправлен синтаксический разбор и форматирование оператора `CAST`.
* Исправлена вставка в материализованное представление в случае, если движок таблицы представления - Distributed ([Babacar Diassé](https://github.com/yandex/ClickHouse/pull/2411)).
* Исправлен race condition при записи данных из движка `Kafka` в материализованные представления ([Yangkuan Liu](https://github.com/yandex/ClickHouse/pull/2448)).
* Исправлена SSRF в табличной функции remote().
* Исправлен выход из `clickhouse-client` в multiline-режиме ([#2510](https://github.com/yandex/ClickHouse/issues/2510)).
## Улучшения:
* Фоновые задачи в реплицированных таблицах теперь выполняются не в отдельных потоках, а в пуле потоков ([Silviu Caragea](https://github.com/yandex/ClickHouse/pull/1722))
* Улучшена производительность разжатия LZ4.
* Ускорен анализ запроса с большим числом JOIN-ов и подзапросов.
* DNS-кэш теперь автоматически обновляется при большом числе сетевых ошибок.
* Вставка в таблицу теперь не происходит, если вставка в одно из её материализованных представлений невозможна из-за того, что в нём много кусков.
* Исправлено несоответствие в значениях счётчиков событий `Query`, `SelectQuery`, `InsertQuery`.
* Разрешены выражения вида `tuple IN (SELECT tuple)`, если типы кортежей совпадают.
* Сервер с реплицированными таблицами теперь может стартовать, даже если не сконфигурирован ZooKeeper.
* При расчёте количества доступных ядер CPU теперь учитываются ограничения cgroups ([Atri Sharma](https://github.com/yandex/ClickHouse/pull/2325)).
* Добавлен chown директорий конфигов в конфигурационном файле systemd ([Михаил Ширяев](https://github.com/yandex/ClickHouse/pull/2421)).
## Изменения сборки:
* Добавлена возможность сборки компилятором gcc8.
* Добавлена возможность сборки llvm из submodule.
* Используемая версия библиотеки librdkafka обновлена до v0.11.4.
* Добавлена возможность использования библиотеки libcpuid из системы, используемая версия библиотеки обновлена до 0.4.0.
* Исправлена сборка с использованием библиотеки vectorclass ([Babacar Diassé](https://github.com/yandex/ClickHouse/pull/2274)).
* Cmake теперь по умолчанию генерирует файлы для ninja (как при использовании `-G Ninja`).
* Добавлена возможность использования библиотеки libtinfo вместо libtermcap ([Георгий Кондратьев](https://github.com/yandex/ClickHouse/pull/2519)).
* Исправлен конфликт заголовочных файлов в Fedora Rawhide ([#2520](https://github.com/yandex/ClickHouse/issues/2520)).
## Обратно несовместимые изменения:
* Убран escaping в форматах `Vertical` и `Pretty*`, удалён формат `VerticalRaw`.
# ClickHouse release 1.1.54385, 2018-06-01
## Исправление ошибок:
* Исправлена ошибка, которая в некоторых случаях приводила к блокировке операций с ZooKeeper.

View File

@ -2,7 +2,7 @@
## Technical info
Developer guide for writing code for ClickHouse is published on official website alongside the usage and operations documentation:
https://clickhouse.yandex/docs/en/development/index.html
https://clickhouse.yandex/docs/en/development/architecture/
## Legal info

View File

@ -1,7 +1,7 @@
# This strings autochanged from release_lib.sh:
set(VERSION_DESCRIBE v1.1.54387-testing)
set(VERSION_REVISION 54387)
set(VERSION_GITHASH 7ce4ebf1e1a5fefd3161b6f615eec0730d75ec34)
set(VERSION_DESCRIBE v1.1.54388-testing)
set(VERSION_REVISION 54388)
set(VERSION_GITHASH 2447755700f40af317cb80ba8800b94d6350d148)
# end of autochange
set (VERSION_MAJOR 1)

View File

@ -87,6 +87,7 @@ namespace DB
namespace ErrorCodes
{
extern const int LOGICAL_ERROR;
extern const int NOT_IMPLEMENTED;
extern const int CANNOT_SEEK_THROUGH_FILE;
}
@ -503,8 +504,11 @@ private:
CodePoint readCodePoint(const char *& pos, const char * end)
{
size_t length = UTF8::seqLength(*pos);
if (pos + length > end)
length = end - pos;
if (length > sizeof(CodePoint))
length = sizeof(CodePoint);
CodePoint res = 0;
memcpy(&res, pos, length);
@ -679,7 +683,7 @@ public:
}
if (table.end() == it)
throw Exception("Logical error in markov model");
throw Exception("Logical error in markov model", ErrorCodes::LOGICAL_ERROR);
size_t offset_from_begin_of_string = pos - data;
size_t determinator_sliding_window_size = params.determinator_sliding_window_size;
@ -700,7 +704,8 @@ public:
/// If string is greater than desired_size, increase probability of end.
double end_probability_multiplier = 0;
Int64 num_bytes_after_desired_size = (pos - data) - desired_size;
if (num_bytes_after_desired_size)
if (num_bytes_after_desired_size > 0)
end_probability_multiplier = std::pow(1.25, num_bytes_after_desired_size);
CodePoint code = it->second.sample(determinator, end_probability_multiplier);
@ -708,6 +713,14 @@ public:
if (code == END)
break;
if (num_bytes_after_desired_size > 0)
{
/// Heuristic: break at ASCII non-alnum code point.
/// This allows to be close to desired_size but not break natural looking words.
if (code < 128 && !isAlphaNumericASCII(code))
break;
}
if (!writeCodePoint(code, pos, end))
break;
@ -881,7 +894,7 @@ public:
if (auto type = typeid_cast<const DataTypeNullable *>(&data_type))
return std::make_unique<NullableModel>(get(*type->getNestedType(), seed, markov_model_params));
throw Exception("Unsupported data type");
throw Exception("Unsupported data type", ErrorCodes::NOT_IMPLEMENTED);
}
};

View File

@ -421,14 +421,26 @@ void HTTPHandler::processQuery(
std::unique_ptr<ReadBuffer> in;
// Used in case of POST request with form-data, but it not to be expectd to be deleted after that scope
std::string full_query;
/// Support for "external data for query processing".
if (startsWith(request.getContentType().data(), "multipart/form-data"))
{
in = std::move(in_param);
ExternalTablesHandler handler(context, params);
/// Params are of both form params POST and uri (GET params)
params.load(request, istr, handler);
for (const auto & it : params)
{
if (it.first == "query")
{
full_query += it.second;
}
}
in = std::make_unique<ReadBufferFromString>(full_query);
/// Erase unneeded parameters to avoid confusing them later with context settings or query
/// parameters.
for (const auto & it : handler.names)

View File

@ -42,13 +42,14 @@ bool BackgroundSchedulePool::TaskInfo::schedule()
scheduled = true;
if (!executing)
{
if (delayed)
pool.cancelDelayedTask(shared_from_this(), lock);
if (delayed)
pool.cancelDelayedTask(shared_from_this(), lock);
/// If the task is not executing at the moment, enqueue it for immediate execution.
/// But if it is currently executing, do nothing because it will be enqueued
/// at the end of the execute() method.
if (!executing)
pool.queue.enqueueNotification(new TaskNotification(shared_from_this()));
}
return true;
}
@ -123,7 +124,6 @@ void BackgroundSchedulePool::TaskInfo::execute()
if (scheduled)
pool.queue.enqueueNotification(new TaskNotification(shared_from_this()));
}
}
zkutil::WatchCallback BackgroundSchedulePool::TaskInfo::getWatchCallback()

View File

@ -70,6 +70,9 @@ public:
std::mutex exec_mutex;
std::mutex schedule_mutex;
/// Invariants:
/// * If deactivated is true then scheduled, delayed and executing are all false.
/// * scheduled and delayed cannot be true at the same time.
bool deactivated = false;
bool scheduled = false;
bool delayed = false;

View File

@ -54,7 +54,7 @@ struct RadixSortFloatTransform
static KeyBits forward(KeyBits x)
{
return x ^ (-(x >> (sizeof(KeyBits) * 8 - 1) | (KeyBits(1) << (sizeof(KeyBits) * 8 - 1))));
return x ^ ((-(x >> (sizeof(KeyBits) * 8 - 1))) | (KeyBits(1) << (sizeof(KeyBits) * 8 - 1)));
}
static KeyBits backward(KeyBits x)

View File

@ -109,7 +109,8 @@ void ReplacingSortedBlockInputStream::merge(MutableColumns & merged_columns, std
}
/// We will write the data for the last primary key.
insertRow(merged_columns, merged_rows);
if (!selected_row.empty())
insertRow(merged_columns, merged_rows);
finished = true;
}

View File

@ -1304,11 +1304,9 @@ DataTypePtr FunctionArrayEnumerateUniq::getReturnTypeImpl(const DataTypes & argu
void FunctionArrayEnumerateUniq::executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t /*input_rows_count*/)
{
Columns array_columns(arguments.size());
const ColumnArray::Offsets * offsets = nullptr;
ColumnRawPtrs data_columns(arguments.size());
ColumnRawPtrs original_data_columns(arguments.size());
ColumnRawPtrs null_maps(arguments.size());
ColumnRawPtrs data_columns;
data_columns.reserve(arguments.size());
bool has_nullable_columns = false;
@ -1327,7 +1325,7 @@ void FunctionArrayEnumerateUniq::executeImpl(Block & block, const ColumnNumbers
array_ptr = const_array->convertToFullColumn();
array = checkAndGetColumn<ColumnArray>(array_ptr.get());
}
array_columns[i] = array_ptr;
const ColumnArray::Offsets & offsets_i = array->getOffsets();
if (i == 0)
offsets = &offsets_i;
@ -1335,7 +1333,22 @@ void FunctionArrayEnumerateUniq::executeImpl(Block & block, const ColumnNumbers
throw Exception("Lengths of all arrays passed to " + getName() + " must be equal.",
ErrorCodes::SIZES_OF_ARRAYS_DOESNT_MATCH);
data_columns[i] = &array->getData();
auto * array_data = &array->getData();
if (auto * tuple_column = checkAndGetColumn<ColumnTuple>(array_data))
{
for (const auto & element : tuple_column->getColumns())
data_columns.push_back(element.get());
}
else
data_columns.push_back(array_data);
}
size_t num_columns = data_columns.size();
ColumnRawPtrs original_data_columns(num_columns);
ColumnRawPtrs null_maps(num_columns);
for (size_t i = 0; i < num_columns; ++i)
{
original_data_columns[i] = data_columns[i];
if (data_columns[i]->isColumnNullable())
@ -1349,7 +1362,7 @@ void FunctionArrayEnumerateUniq::executeImpl(Block & block, const ColumnNumbers
null_maps[i] = nullptr;
}
const ColumnArray * first_array = checkAndGetColumn<ColumnArray>(array_columns[0].get());
const ColumnArray * first_array = checkAndGetColumn<ColumnArray>(block.getByPosition(arguments.at(0)).column.get());
const IColumn * first_null_map = null_maps[0];
auto res_nested = ColumnUInt32::create();
@ -1357,7 +1370,7 @@ void FunctionArrayEnumerateUniq::executeImpl(Block & block, const ColumnNumbers
if (!offsets->empty())
res_values.resize(offsets->back());
if (arguments.size() == 1)
if (num_columns == 1)
{
if (!( executeNumber<UInt8>(first_array, first_null_map, res_values)
|| executeNumber<UInt16>(first_array, first_null_map, res_values)

View File

@ -191,7 +191,7 @@ struct ContextShared
Context::ConfigReloadCallback config_reload_callback;
ContextShared(std::shared_ptr<IRuntimeComponentsFactory> runtime_components_factory_)
: runtime_components_factory(std::move(runtime_components_factory_))
: runtime_components_factory(std::move(runtime_components_factory_)), macros(std::make_unique<Macros>())
{
/// TODO: make it singleton (?)
static std::atomic<size_t> num_calls{0};

View File

@ -1983,26 +1983,34 @@ bool ExpressionAnalyzer::isThereArrayJoin(const ASTPtr & ast)
void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries, bool only_consts, ScopeStack & actions_stack,
ProjectionManipulatorPtr projection_manipulator)
{
String ast_column_name;
auto getColumnName = [&ast, &ast_column_name]()
{
if (ast_column_name.empty())
ast_column_name = ast->getColumnName();
return ast_column_name;
};
/// If the result of the calculation already exists in the block.
if ((typeid_cast<ASTFunction *>(ast.get()) || typeid_cast<ASTLiteral *>(ast.get()))
&& projection_manipulator->tryToGetFromUpperProjection(ast->getColumnName()))
&& projection_manipulator->tryToGetFromUpperProjection(getColumnName()))
return;
if (ASTIdentifier * node = typeid_cast<ASTIdentifier *>(ast.get()))
if (typeid_cast<ASTIdentifier *>(ast.get()))
{
std::string name = node->getColumnName();
if (!only_consts && !projection_manipulator->tryToGetFromUpperProjection(ast->getColumnName()))
if (!only_consts && !projection_manipulator->tryToGetFromUpperProjection(getColumnName()))
{
/// The requested column is not in the block.
/// If such a column exists in the table, then the user probably forgot to surround it with an aggregate function or add it to GROUP BY.
bool found = false;
for (const auto & column_name_type : source_columns)
if (column_name_type.name == name)
if (column_name_type.name == getColumnName())
found = true;
if (found)
throw Exception("Column " + name + " is not under aggregate function and not in GROUP BY.",
throw Exception("Column " + getColumnName() + " is not under aggregate function and not in GROUP BY.",
ErrorCodes::NOT_AN_AGGREGATE);
}
}
@ -2021,7 +2029,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries,
getActionsImpl(arg, no_subqueries, only_consts, actions_stack, projection_manipulator);
if (!only_consts)
{
String result_name = projection_manipulator->getColumnName(node->getColumnName());
String result_name = projection_manipulator->getColumnName(getColumnName());
actions_stack.addAction(ExpressionAction::copyColumn(projection_manipulator->getColumnName(arg->getColumnName()), result_name));
NameSet joined_columns;
joined_columns.insert(result_name);
@ -2049,7 +2057,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries,
/// We are in the part of the tree that we are not going to compute. You just need to define types.
/// Do not subquery and create sets. We insert an arbitrary column of the correct type.
ColumnWithTypeAndName fake_column;
fake_column.name = projection_manipulator->getColumnName(node->getColumnName());
fake_column.name = projection_manipulator->getColumnName(getColumnName());
fake_column.type = std::make_shared<DataTypeUInt8>();
actions_stack.addAction(ExpressionAction::addColumn(fake_column, projection_manipulator->getProjectionSourceColumn(), false));
getActionsImpl(node->arguments->children.at(0), no_subqueries, only_consts, actions_stack,
@ -2065,7 +2073,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries,
{
actions_stack.addAction(ExpressionAction::addColumn(ColumnWithTypeAndName(
ColumnConst::create(ColumnUInt8::create(1, 1), 1), std::make_shared<DataTypeUInt8>(),
projection_manipulator->getColumnName(node->getColumnName())), projection_manipulator->getProjectionSourceColumn(), false));
projection_manipulator->getColumnName(getColumnName())), projection_manipulator->getProjectionSourceColumn(), false));
return;
}
@ -2073,7 +2081,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries,
return;
const FunctionBuilderPtr & function_builder = FunctionFactory::instance().get(node->name, context);
auto projection_action = getProjectionAction(node->name, actions_stack, projection_manipulator, node->getColumnName(), context);
auto projection_action = getProjectionAction(node->name, actions_stack, projection_manipulator, getColumnName(), context);
Names argument_names;
DataTypes argument_types;
@ -2085,6 +2093,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries,
for (size_t arg = 0; arg < node->arguments->children.size(); ++arg)
{
auto & child = node->arguments->children[arg];
auto child_column_name = child->getColumnName();
ASTFunction * lambda = typeid_cast<ASTFunction *>(child.get());
if (lambda && lambda->name == "lambda")
@ -2115,7 +2124,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries,
if (!set->empty())
column.name = getUniqueName(actions_stack.getSampleBlock(), "__set");
else
column.name = child->getColumnName();
column.name = child_column_name;
column.name = projection_manipulator->getColumnName(column.name);
@ -2135,8 +2144,8 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries,
projection_action->preArgumentAction();
getActionsImpl(child, no_subqueries, only_consts, actions_stack,
projection_manipulator);
std::string name = projection_manipulator->getColumnName(child->getColumnName());
projection_action->postArgumentAction(child->getColumnName());
std::string name = projection_manipulator->getColumnName(child_column_name);
projection_action->postArgumentAction(child_column_name);
if (actions_stack.getSampleBlock().has(name))
{
argument_types.push_back(actions_stack.getSampleBlock().getByName(name).type);
@ -2239,7 +2248,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries,
actions_stack.addAction(
ExpressionAction::applyFunction(function_builder,
argument_names,
projection_manipulator->getColumnName(node->getColumnName()),
projection_manipulator->getColumnName(getColumnName()),
projection_manipulator->getProjectionSourceColumn()));
}
}
@ -2251,7 +2260,7 @@ void ExpressionAnalyzer::getActionsImpl(const ASTPtr & ast, bool no_subqueries,
ColumnWithTypeAndName column;
column.column = type->createColumnConst(1, convertFieldToType(node->value, *type));
column.type = type;
column.name = node->getColumnName();
column.name = getColumnName();
actions_stack.addAction(ExpressionAction::addColumn(column, "", false));
projection_manipulator->tryToGetFromUpperProjection(column.name);

View File

@ -0,0 +1,21 @@
#include <Parsers/ASTAsterisk.h>
#include <IO/WriteBuffer.h>
namespace DB
{
ASTPtr ASTAsterisk::clone() const
{
auto clone = std::make_shared<ASTAsterisk>(*this);
clone->cloneChildren();
return std::move(clone);
}
void ASTAsterisk::appendColumnName(WriteBuffer & ostr) const { ostr.write('*'); }
void ASTAsterisk::formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const
{
settings.ostr << "*";
}
}

View File

@ -10,19 +10,11 @@ class ASTAsterisk : public IAST
{
public:
String getID() const override { return "Asterisk"; }
ASTPtr clone() const override
{
auto clone = std::make_shared<ASTAsterisk>(*this);
clone->cloneChildren();
return std::move(clone);
}
String getColumnName() const override { return "*"; }
ASTPtr clone() const override;
void appendColumnName(WriteBuffer & ostr) const override;
protected:
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override
{
settings.ostr << "*";
}
void formatImpl(const FormatSettings & settings, FormatState &, FormatStateStacked) const override;
};
}

View File

@ -9,32 +9,30 @@
namespace DB
{
String ASTFunction::getColumnNameImpl() const
void ASTFunction::appendColumnNameImpl(WriteBuffer & ostr) const
{
WriteBufferFromOwnString wb;
writeString(name, wb);
writeString(name, ostr);
if (parameters)
{
writeChar('(', wb);
for (ASTs::const_iterator it = parameters->children.begin(); it != parameters->children.end(); ++it)
writeChar('(', ostr);
for (auto it = parameters->children.begin(); it != parameters->children.end(); ++it)
{
if (it != parameters->children.begin())
writeCString(", ", wb);
writeString((*it)->getColumnName(), wb);
writeCString(", ", ostr);
(*it)->appendColumnName(ostr);
}
writeChar(')', wb);
writeChar(')', ostr);
}
writeChar('(', wb);
for (ASTs::const_iterator it = arguments->children.begin(); it != arguments->children.end(); ++it)
writeChar('(', ostr);
for (auto it = arguments->children.begin(); it != arguments->children.end(); ++it)
{
if (it != arguments->children.begin())
writeCString(", ", wb);
writeString((*it)->getColumnName(), wb);
writeCString(", ", ostr);
(*it)->appendColumnName(ostr);
}
writeChar(')', wb);
return wb.str();
writeChar(')', ostr);
}
/** Get the text that identifies this element. */

View File

@ -25,7 +25,7 @@ public:
protected:
void formatImplWithoutAlias(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override;
String getColumnNameImpl() const override;
void appendColumnNameImpl(WriteBuffer & ostr) const override;
};

View File

@ -37,4 +37,9 @@ void ASTIdentifier::formatImplWithoutAlias(const FormatSettings & settings, Form
}
}
void ASTIdentifier::appendColumnNameImpl(WriteBuffer & ostr) const
{
writeString(name, ostr);
}
}

View File

@ -40,7 +40,7 @@ public:
protected:
void formatImplWithoutAlias(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override;
String getColumnNameImpl() const override { return name; }
void appendColumnNameImpl(WriteBuffer & ostr) const override;
};
}

View File

@ -7,8 +7,7 @@
namespace DB
{
String ASTLiteral::getColumnNameImpl() const
void ASTLiteral::appendColumnNameImpl(WriteBuffer & ostr) const
{
/// Special case for very large arrays. Instead of listing all elements, will use hash of them.
/// (Otherwise column name will be too long, that will lead to significant slowdown of expression analysis.)
@ -19,10 +18,17 @@ String ASTLiteral::getColumnNameImpl() const
applyVisitor(FieldVisitorHash(hash), value);
UInt64 low, high;
hash.get128(low, high);
return "__array_" + toString(low) + "_" + toString(high);
writeCString("__array_", ostr);
writeText(low, ostr);
ostr.write('_');
writeText(high, ostr);
}
else
{
String column_name = applyVisitor(FieldVisitorToString(), value);
writeString(column_name, ostr);
}
return applyVisitor(FieldVisitorToString(), value);
}
}

View File

@ -28,7 +28,7 @@ protected:
settings.ostr << applyVisitor(FieldVisitorToString(), value);
}
String getColumnNameImpl() const override;
void appendColumnNameImpl(WriteBuffer & ostr) const override;
};
}

View File

@ -1,12 +1,14 @@
#include <Parsers/ASTQualifiedAsterisk.h>
#include <IO/WriteHelpers.h>
namespace DB
{
String ASTQualifiedAsterisk::getColumnName() const
void ASTQualifiedAsterisk::appendColumnName(WriteBuffer & ostr) const
{
const auto & qualifier = children.at(0);
return qualifier->getColumnName() + ".*";
qualifier->appendColumnName(ostr);
writeCString(".*", ostr);
}
void ASTQualifiedAsterisk::formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const

View File

@ -19,7 +19,7 @@ public:
clone->cloneChildren();
return std::move(clone);
}
String getColumnName() const override;
void appendColumnName(WriteBuffer & ostr) const override;
protected:
void formatImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override;

View File

@ -4,14 +4,30 @@
namespace DB
{
String ASTSubquery::getColumnNameImpl() const
void ASTSubquery::appendColumnNameImpl(WriteBuffer & ostr) const
{
/// This is a hack. We use alias, if available, because otherwise tree could change during analysis.
if (!alias.empty())
return alias;
writeString(alias, ostr);
Hash hash = getTreeHash();
return "__subquery_" + toString(hash.first) + "_" + toString(hash.second);
writeCString("__subquery_", ostr);
writeText(hash.first, ostr);
ostr.write('_');
writeText(hash.second, ostr);
}
void ASTSubquery::formatImplWithoutAlias(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const
{
std::string indent_str = settings.one_line ? "" : std::string(4u * frame.indent, ' ');
std::string nl_or_nothing = settings.one_line ? "" : "\n";
settings.ostr << nl_or_nothing << indent_str << "(" << nl_or_nothing;
FormatStateStacked frame_nested = frame;
frame_nested.need_parens = false;
++frame_nested.indent;
children[0]->formatImpl(settings, state, frame_nested);
settings.ostr << nl_or_nothing << indent_str << ")";
}
}

View File

@ -29,20 +29,8 @@ public:
}
protected:
void formatImplWithoutAlias(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override
{
std::string indent_str = settings.one_line ? "" : std::string(4 * frame.indent, ' ');
std::string nl_or_nothing = settings.one_line ? "" : "\n";
settings.ostr << nl_or_nothing << indent_str << "(" << nl_or_nothing;
FormatStateStacked frame_nested = frame;
frame_nested.need_parens = false;
++frame_nested.indent;
children[0]->formatImpl(settings, state, frame_nested);
settings.ostr << nl_or_nothing << indent_str << ")";
}
String getColumnNameImpl() const override;
void formatImplWithoutAlias(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override;
void appendColumnNameImpl(WriteBuffer & ostr) const override;
};
}

View File

@ -33,4 +33,12 @@ void ASTWithAlias::formatImpl(const FormatSettings & settings, FormatState & sta
}
}
void ASTWithAlias::appendColumnName(WriteBuffer & ostr) const
{
if (prefer_alias_to_column_name && !alias.empty())
writeString(alias, ostr);
else
appendColumnNameImpl(ostr);
}
}

View File

@ -20,8 +20,8 @@ public:
using IAST::IAST;
String getColumnName() const override final { return prefer_alias_to_column_name && !alias.empty() ? alias : getColumnNameImpl(); }
String getAliasOrColumnName() const override { return alias.empty() ? getColumnNameImpl() : alias; }
void appendColumnName(WriteBuffer & ostr) const final;
String getAliasOrColumnName() const override { return alias.empty() ? getColumnName() : alias; }
String tryGetAlias() const override { return alias; }
void setAlias(const String & to) override { alias = to; }
@ -31,7 +31,7 @@ public:
virtual void formatImplWithoutAlias(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const = 0;
protected:
virtual String getColumnNameImpl() const = 0;
virtual void appendColumnNameImpl(WriteBuffer & ostr) const = 0;
};
/// helper for setting aliases and chaining result to other functions

View File

@ -101,4 +101,12 @@ void IAST::cloneChildren()
child = child->clone();
}
String IAST::getColumnName() const
{
WriteBufferFromOwnString write_buffer;
appendColumnName(write_buffer);
return write_buffer.str();
}
}

View File

@ -46,7 +46,11 @@ public:
virtual ~IAST() = default;
/** Get the canonical name of the column if the element is a column */
virtual String getColumnName() const { throw Exception("Trying to get name of not a column: " + getID(), ErrorCodes::NOT_A_COLUMN); }
String getColumnName() const;
virtual void appendColumnName(WriteBuffer &) const
{
throw Exception("Trying to get name of not a column: " + getID(), ErrorCodes::NOT_A_COLUMN);
}
/** Get the alias, if any, or the canonical name of the column, if it is not. */
virtual String getAliasOrColumnName() const { return getColumnName(); }

View File

@ -46,7 +46,6 @@ void ReplicatedMergeTreeCleanupThread::run()
}
task->scheduleAfter(CLEANUP_SLEEP_MS);
}

View File

@ -0,0 +1,175 @@
import time
import threading
import random
from collections import Counter
import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
node1 = cluster.add_instance('node1', with_zookeeper=True)
node2 = cluster.add_instance('node2', with_zookeeper=True)
nodes = [node1, node2]
@pytest.fixture(scope="module")
def started_cluster():
try:
cluster.start()
for node in nodes:
node.query("DROP TABLE IF EXISTS test_mutations")
for node in nodes:
node.query("CREATE TABLE test_mutations(d Date, x UInt32, i UInt32) ENGINE ReplicatedMergeTree('/clickhouse/tables/test/test_mutations', '{instance}') ORDER BY x PARTITION BY toYYYYMM(d)")
yield cluster
finally:
cluster.shutdown()
class Runner:
def __init__(self):
self.mtx = threading.Lock()
self.total_inserted_xs = 0
self.total_inserted_rows = 0
self.total_mutations = 0
self.total_deleted_xs = 0
self.total_deleted_rows = 0
self.current_xs = Counter()
self.currently_inserting_xs = Counter()
self.currently_deleting_xs = set()
self.stop_ev = threading.Event()
def do_insert(self, thread_num):
self.stop_ev.wait(random.random())
# Each thread inserts a small random number of rows with random year, month 01 and day determined
# by the thread number. The idea is to avoid spurious duplicates and to insert into a
# nontrivial number of partitions.
month = '01'
day = str(thread_num + 1).zfill(2)
i = 1
while not self.stop_ev.is_set():
xs = [random.randint(1, 10) for _ in range(random.randint(1, 10))]
with self.mtx:
xs = [x for x in xs if x not in self.currently_deleting_xs]
if len(xs) == 0:
continue
for x in xs:
self.currently_inserting_xs[x] += 1
year = 2000 + random.randint(0, 10)
date_str = '{year}-{month}-{day}'.format(year=year, month=month, day=day)
payload = ''
for x in xs:
payload += '{date_str} {x} {i}\n'.format(date_str=date_str, x=x, i=i)
i += 1
try:
print 'thread {}: insert for {}: {}'.format(thread_num, date_str, ','.join(str(x) for x in xs))
random.choice(nodes).query("INSERT INTO test_mutations FORMAT TSV", payload)
with self.mtx:
for x in xs:
self.current_xs[x] += 1
self.total_inserted_xs += sum(xs)
self.total_inserted_rows += len(xs)
except Exception, e:
print 'Exception while inserting,', e
finally:
with self.mtx:
for x in xs:
self.currently_inserting_xs[x] -= 1
self.stop_ev.wait(0.2 + random.random() / 5)
def do_delete(self, thread_num):
self.stop_ev.wait(1.0 + random.random())
while not self.stop_ev.is_set():
chosen = False
with self.mtx:
if self.current_xs:
x = random.choice(list(self.current_xs.elements()))
if self.currently_inserting_xs[x] == 0 and x not in self.currently_deleting_xs:
chosen = True
self.currently_deleting_xs.add(x)
to_delete_count = self.current_xs[x]
if not chosen:
self.stop_ev.wait(0.1 * random.random())
continue
try:
print 'thread {}: delete {} * {}'.format(thread_num, to_delete_count, x)
random.choice(nodes).query("ALTER TABLE test_mutations DELETE WHERE x = {}".format(x))
with self.mtx:
self.total_mutations += 1
self.current_xs[x] -= to_delete_count
self.total_deleted_xs += to_delete_count * x
self.total_deleted_rows += to_delete_count
except Exception, e:
print 'Exception while deleting,', e
finally:
with self.mtx:
self.currently_deleting_xs.remove(x)
self.stop_ev.wait(1.0 + random.random() * 2)
def test_mutations(started_cluster):
DURATION_SECONDS = 50
runner = Runner()
threads = []
for thread_num in range(5):
threads.append(threading.Thread(target=runner.do_insert, args=(thread_num, )))
for thread_num in (11, 12, 13):
threads.append(threading.Thread(target=runner.do_delete, args=(thread_num,)))
for t in threads:
t.start()
time.sleep(DURATION_SECONDS)
runner.stop_ev.set()
for t in threads:
t.join()
# Sanity check: at least something was inserted and something was deleted
assert runner.total_inserted_rows > 0
assert runner.total_mutations > 0
all_done = False
for i in range(100): # wait for replication 10 seconds max
time.sleep(0.1)
def get_done_mutations(node):
return int(node.query("SELECT sum(is_done) FROM system.mutations WHERE table = 'test_mutations'").rstrip())
if all([get_done_mutations(n) == runner.total_mutations for n in nodes]):
all_done = True
break
print node1.query("SELECT mutation_id, command, parts_to_do, is_done FROM system.mutations WHERE table = 'test_mutations' FORMAT TSVWithNames")
assert all_done
expected_sum = runner.total_inserted_xs - runner.total_deleted_xs
actual_sums = []
for i, node in enumerate(nodes):
actual_sums.append(int(node.query("SELECT sum(x) FROM test_mutations").rstrip()))
assert actual_sums[i] == expected_sum

View File

@ -0,0 +1,3 @@
1
1 Hello
2 World

View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
. $CURDIR/../shell_config.sh
${CLICKHOUSE_CURL} ${CLICKHOUSE_URL}?query="select" -X POST -F "query= 1;" 2>/dev/null
echo -ne '1,Hello\n2,World\n' | ${CLICKHOUSE_CURL} -sSF 'file=@-' "${CLICKHOUSE_URL}?file_format=CSV&file_types=UInt8,String&query=SELE" -X POST -F "query=CT * FROM file" 2>/dev/null

View File

@ -0,0 +1,5 @@
DROP TABLE IF EXISTS test.final_test;
CREATE TABLE test.final_test (id String, version Date) ENGINE = ReplacingMergeTree(version, id, 8192);
INSERT INTO test.final_test (id, version) VALUES ('2018-01-01', '2018-01-01');
SELECT * FROM test.final_test FINAL PREWHERE id == '2018-01-02';
DROP TABLE test.final_test;

View File

@ -0,0 +1 @@
SELECT quantileTDigest(0.5)(arrayJoin([-1, -2, -3]));

View File

@ -0,0 +1,12 @@
[1,1,2,3,2,4]
[1,2,1,3,2,3]
[1,1,1,2,1,2]
[1,1,1,2,1,2]
[1,1,1,2,1,2]
[1,1,1,2,1,2]
[1,1,1,2,3,2,4,2,3]
[1,2,3,1,4,2,3,4,5]
[1,1,1,1,2,1,2,1,2]
[1,1,1,1,2,1,2,1,2]
[1,1,1,1,2,1,2,1,2]
[1,1,1,1,2,1,2,1,2]

View File

@ -0,0 +1,20 @@
drop table if exists test.tab;
create table test.tab (val UInt32, n Nested(x UInt8, y String)) engine = Memory;
insert into test.tab values (1, [1, 2, 1, 1, 2, 1], ['a', 'a', 'b', 'a', 'b', 'b']);
select arrayEnumerateUniq(n.x) from test.tab;
select arrayEnumerateUniq(n.y) from test.tab;
select arrayEnumerateUniq(n.x, n.y) from test.tab;
select arrayEnumerateUniq(arrayMap((a, b) -> (a, b), n.x, n.y)) from test.tab;
select arrayEnumerateUniq(arrayMap((a, b) -> (a, b), n.x, n.y), n.x) from test.tab;
select arrayEnumerateUniq(arrayMap((a, b) -> (a, b), n.x, n.y), arrayMap((a, b) -> (b, a), n.x, n.y)) from test.tab;
drop table test.tab;
create table test.tab (val UInt32, n Nested(x Nullable(UInt8), y String)) engine = Memory;
insert into test.tab values (1, [1, Null, 2, 1, 1, 2, 1, Null, Null], ['a', 'a', 'a', 'b', 'a', 'b', 'b', 'b', 'a']);
select arrayEnumerateUniq(n.x) from test.tab;
select arrayEnumerateUniq(n.y) from test.tab;
select arrayEnumerateUniq(n.x, n.y) from test.tab;
select arrayEnumerateUniq(arrayMap((a, b) -> (a, b), n.x, n.y)) from test.tab;
select arrayEnumerateUniq(arrayMap((a, b) -> (a, b), n.x, n.y), n.x) from test.tab;
select arrayEnumerateUniq(arrayMap((a, b) -> (a, b), n.x, n.y), arrayMap((a, b) -> (b, a), n.x, n.y)) from test.tab;

4
debian/changelog vendored
View File

@ -1,5 +1,5 @@
clickhouse (1.1.54387) unstable; urgency=low
clickhouse (1.1.54388) unstable; urgency=low
* Modified source code
-- <robot-metrika-test@yandex-team.ru> Fri, 22 Jun 2018 21:10:11 +0300
-- <robot-metrika-test@yandex-team.ru> Wed, 27 Jun 2018 16:10:59 +0300

View File

@ -70,7 +70,7 @@ Docker image: <https://hub.docker.com/r/yandex/clickhouse-server/>
RPM packages for CentOS or RHEL: <https://github.com/Altinity/clickhouse-rpm-install>
Gentoo overlay: <https://github.com/kmeaw/clickhouse-overlay>
Gentoo: `emerge clickhouse`
## Launch

View File

@ -7,9 +7,9 @@ Settings are configured in layers, so each subsequent layer redefines the previo
Ways to configure settings, in order of priority:
- Settings in the server config file.
- Settings in the server config file `users.xml`.
Settings from user profiles.
Set it in user profile in `<profiles>` element.
- Session settings.
@ -21,4 +21,3 @@ Similarly, you can use ClickHouse sessions in the HTTP protocol. To do this, you
- When using the HTTP API, pass CGI parameters (`URL?setting_1=value&setting_2=value...`).
Settings that can only be made in the server config file are not covered in this section.

View File

@ -1,13 +1,15 @@
# numbers
`numbers(N)` Returns a table with the single 'number' column (UInt64) that contains integers from 0 to N-1.
`numbers(N, M)` - Returns a table with the single 'number' column (UInt64) that contains integers from N to (N + M - 1).
Similar to the `system.numbers` table, it can be used for testing and generating successive values.
Similar to the `system.numbers` table, it can be used for testing and generating successive values, `numbers(N, M)` more efficient than `system.numbers`.
The following two queries are equivalent:
The following queries are equivalent:
```sql
SELECT * FROM numbers(10);
SELECT * FROM numbers(0, 10);
SELECT * FROM system.numbers LIMIT 10;
```

View File

@ -16,7 +16,7 @@ grep -q sse4_2 /proc/cpuinfo && echo "SSE 4.2 supported" || echo "SSE 4.2 not su
В целях тестирования и разработки, система может быть установлена на один сервер или на рабочий компьютер.
### Установка из пакетов для Debian/Ubuntu
### Установка из пакетов для Debian/Ubuntu
Пропишите в `/etc/apt/sources.list` (или в отдельный файл `/etc/apt/sources.list.d/clickhouse.list`) репозитории:
@ -70,7 +70,7 @@ Docker образ: <https://hub.docker.com/r/yandex/clickhouse-server/>
RPM пакеты для CentOS, RHEL: <https://github.com/Altinity/clickhouse-rpm-install>
Gentoo overlay: <https://github.com/kmeaw/clickhouse-overlay>
Gentoo: `emerge clickhouse`
## Запуск

View File

@ -436,6 +436,48 @@ ALTER TABLE [db.]table FETCH PARTITION 'name' FROM 'path-in-zookeeper'
<a name="query_language_queries_show_databases"></a>
### Мутации
Мутации - разновидность запроса ALTER, позволяющая изменять или удалять данные в таблице. В отличие от стандартных запросов `DELETE` и `UPDATE`, рассчитанных на точечное изменение данных, область применения мутаций - достаточно тяжёлые изменения, затрагивающие много строк в таблице.
Функциональность находится в состоянии beta и доступна начиная с версии 1.1.54388. Реализована поддержка Replicated*MergeTree таблиц (в скором времени будет добавлена поддержка и для нереплицированных MergeTree).
Конвертировать существующие таблицы для работы с мутациями не нужно. Но после применения первой мутации формат данных таблицы становится несовместимым с предыдущими версиями и откатиться на предыдущую версию уже не получится.
На данный момент доступна команда `ALTER DELETE`:
```sql
ALTER TABLE [db.]table DELETE WHERE expr
```
Выражение `expr` должно иметь тип UInt8. Запрос удаляет строки таблицы, для которых это выражение принимает ненулевое значение.
В одном запросе можно указать несколько команд через запятую.
Для *MergeTree-таблиц мутации выполняются, перезаписывая данные по кускам (parts). При этом атомарности нет - куски заменяются на помутированные по мере выполнения и запрос `SELECT`, заданный во время выполнения мутации, увидит данные как из измененных кусков, так и из кусков, которые еще не были изменены.
Мутации линейно упорядочены между собой и накладываются на каждый кусок в порядке добавления. Мутации также упорядочены со вставками - гарантируется, что данные, вставленные в таблицу до начала выполнения запроса мутации, будут изменены, а данные, вставленные после окончания запроса мутации, изменены не будут. При этом мутации никак не блокируют вставки.
Для реплицированных таблиц запрос завершается немедленно после добавления информации о мутации в ZooKeeper. Сама мутация выполняется асинхронно, следить за ходом её выполнения можно по таблице `system.mutations`. Добавленные мутации будут выполняться до конца даже в случае перезапуска серверов ClickHouse. Откатить мутацию после её добавления нельзя.
#### Сиситемная таблица system.mutations
Таблица содержит информацию о ходе выполнения мутаций MergeTree-таблиц. Каждой команде мутации соответствует одна строка. В таблице есть следующие столбцы:
**database**, **table** - имя БД и таблицы, к которой была применена мутация.
**mutation_id** - ID запроса. Для реплицированных таблиц эти ID соответствуют именам записей в директории `<table_path_in_zookeeper>/mutations/` в ZooKeeper.
**command** - Команда мутации (часть запроса после `ALTER TABLE [db.]table`).
**create_time** - Время создания мутации.
**block_numbers.partition_id**, **block_numbers.number** - Nested-столбец, для каждой партиции содержащий номер блока, полученный этой мутацией (в каждой партиции будут изменены только куски, содержащие блоки с номерами, меньшими номера, полученного мутацией в этой партиции).
**parts_to_do** - Количество кусков таблицы, которые ещё предстоит изменить.
**is_done** - Завершена ли мутация. Замечание: даже если `parts_to_do = 0`, возможна ситуация, когда мутация ещё не завершена из-за долго выполняющейся вставки, которая добавляет данные, которые нужно будет мутировать.
## SHOW DATABASES
```sql

View File

@ -11,6 +11,6 @@ SELECT * FROM system.numbers LIMIT 10;
```
Примеры:
```sql
-- генарация последовательности всех дат от 2010-01-01 до 2010-12-31
-- генерация последовательности всех дат от 2010-01-01 до 2010-12-31
select toDate('2010-01-01') + number as d FROM numbers(365);
```

View File

@ -85,17 +85,18 @@ void Connection::connect(const char* db,
throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
/// Set timeouts.
if (mysql_options(driver.get(), MYSQL_OPT_CONNECT_TIMEOUT, reinterpret_cast<const char *>(&timeout)))
if (mysql_options(driver.get(), MYSQL_OPT_CONNECT_TIMEOUT, &timeout))
throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
if (mysql_options(driver.get(), MYSQL_OPT_READ_TIMEOUT, reinterpret_cast<const char *>(&rw_timeout)))
if (mysql_options(driver.get(), MYSQL_OPT_READ_TIMEOUT, &rw_timeout))
throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
if (mysql_options(driver.get(), MYSQL_OPT_WRITE_TIMEOUT, reinterpret_cast<const char *>(&rw_timeout)))
if (mysql_options(driver.get(), MYSQL_OPT_WRITE_TIMEOUT, &rw_timeout))
throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
/// Enables ability to use query LOAD DATA LOCAL INFILE with servers were compiled without --enable-local-infile option.
if (mysql_options(driver.get(), MYSQL_OPT_LOCAL_INFILE, nullptr))
/// Disable LOAD DATA LOCAL INFILE because it is insecure.
unsigned enable_local_infile = 0;
if (mysql_options(driver.get(), MYSQL_OPT_LOCAL_INFILE, &enable_local_infile))
throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
/// Specifies particular ssl key and certificate if it needs

View File

@ -106,9 +106,11 @@ static void mutate(pcg64 & generator, void * src, size_t length)
&& isAlphaASCII(pos[2]))
{
auto res = rand(generator, 0, 3);
if (res == 2)
if (res == 2)
{
std::swap(pos[0], pos[1]);
if (res == 3)
}
else if (res == 3)
std::swap(pos[1], pos[2]);
pos += 3;
@ -124,7 +126,7 @@ static void mutate(pcg64 & generator, void * src, size_t length)
std::swap(pos[1], pos[2]);
std::swap(pos[0], pos[1]);
}
if (res == 3)
else if (res == 3)
{
std::swap(pos[3], pos[2]);
std::swap(pos[4], pos[3]);