2012-09-05 19:51:09 +00:00
|
|
|
|
#include <Poco/Util/Application.h>
|
|
|
|
|
#include <Poco/Util/AbstractConfiguration.h>
|
|
|
|
|
|
2012-05-22 19:32:56 +00:00
|
|
|
|
#include <DB/Parsers/ASTCreateQuery.h>
|
|
|
|
|
#include <DB/Parsers/ASTIdentifier.h>
|
2012-05-30 05:53:09 +00:00
|
|
|
|
#include <DB/Parsers/ASTLiteral.h>
|
2012-05-22 19:32:56 +00:00
|
|
|
|
|
|
|
|
|
#include <DB/Interpreters/Context.h>
|
2014-09-23 16:02:04 +00:00
|
|
|
|
#include <DB/Interpreters/reinterpretAsIdentifier.h>
|
2012-05-22 19:32:56 +00:00
|
|
|
|
|
2011-10-31 17:30:44 +00:00
|
|
|
|
#include <DB/Storages/StorageLog.h>
|
2012-06-25 00:17:19 +00:00
|
|
|
|
#include <DB/Storages/StorageTinyLog.h>
|
2011-10-31 17:55:06 +00:00
|
|
|
|
#include <DB/Storages/StorageMemory.h>
|
2014-10-26 00:01:36 +00:00
|
|
|
|
#include <DB/Storages/StorageBuffer.h>
|
2014-06-16 17:32:31 +00:00
|
|
|
|
#include <DB/Storages/StorageNull.h>
|
2012-05-30 05:53:09 +00:00
|
|
|
|
#include <DB/Storages/StorageMerge.h>
|
2012-07-18 19:30:33 +00:00
|
|
|
|
#include <DB/Storages/StorageMergeTree.h>
|
2012-05-22 19:32:56 +00:00
|
|
|
|
#include <DB/Storages/StorageDistributed.h>
|
2011-10-31 17:30:44 +00:00
|
|
|
|
#include <DB/Storages/StorageSystemNumbers.h>
|
|
|
|
|
#include <DB/Storages/StorageSystemOne.h>
|
|
|
|
|
#include <DB/Storages/StorageFactory.h>
|
2013-10-30 13:52:02 +00:00
|
|
|
|
#include <DB/Storages/StorageView.h>
|
2013-11-08 17:43:03 +00:00
|
|
|
|
#include <DB/Storages/StorageMaterializedView.h>
|
2013-02-07 13:03:19 +00:00
|
|
|
|
#include <DB/Storages/StorageChunks.h>
|
2013-02-07 13:40:59 +00:00
|
|
|
|
#include <DB/Storages/StorageChunkRef.h>
|
2013-02-11 09:19:48 +00:00
|
|
|
|
#include <DB/Storages/StorageChunkMerger.h>
|
2014-03-21 19:17:59 +00:00
|
|
|
|
#include <DB/Storages/StorageReplicatedMergeTree.h>
|
2015-01-27 00:52:03 +00:00
|
|
|
|
#include <DB/Storages/StorageSet.h>
|
2015-01-28 00:08:45 +00:00
|
|
|
|
#include <DB/Storages/StorageJoin.h>
|
2011-10-31 17:30:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
2014-03-21 19:17:59 +00:00
|
|
|
|
static bool endsWith(const std::string & s, const std::string & suffix)
|
|
|
|
|
{
|
2014-06-12 19:23:06 +00:00
|
|
|
|
return s.size() >= suffix.size() && 0 == strncmp(s.data() + s.size() - suffix.size(), suffix.data(), suffix.size());
|
2014-03-21 19:17:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool startsWith(const std::string & s, const std::string & prefix)
|
|
|
|
|
{
|
2014-06-12 19:23:06 +00:00
|
|
|
|
return s.size() >= prefix.size() && 0 == strncmp(s.data(), prefix.data(), prefix.size());
|
2014-03-21 19:17:59 +00:00
|
|
|
|
}
|
2014-03-18 17:20:44 +00:00
|
|
|
|
|
|
|
|
|
/** Для StorageMergeTree: достать первичный ключ в виде ASTExpressionList.
|
|
|
|
|
* Он может быть указан в кортеже: (CounterID, Date),
|
|
|
|
|
* или как один столбец: CounterID.
|
|
|
|
|
*/
|
2014-11-22 02:22:30 +00:00
|
|
|
|
static ASTPtr extractPrimaryKey(const ASTPtr & node)
|
2014-03-18 17:20:44 +00:00
|
|
|
|
{
|
2014-06-26 00:58:14 +00:00
|
|
|
|
const ASTFunction * primary_expr_func = typeid_cast<const ASTFunction *>(&*node);
|
2014-03-18 17:20:44 +00:00
|
|
|
|
|
|
|
|
|
if (primary_expr_func && primary_expr_func->name == "tuple")
|
|
|
|
|
{
|
|
|
|
|
/// Первичный ключ указан в кортеже.
|
|
|
|
|
return primary_expr_func->children.at(0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/// Первичный ключ состоит из одного столбца.
|
|
|
|
|
ASTExpressionList * res = new ASTExpressionList;
|
|
|
|
|
ASTPtr res_ptr = res;
|
|
|
|
|
res->children.push_back(node);
|
|
|
|
|
return res_ptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
/** Для StorageMergeTree: достать список имён столбцов.
|
|
|
|
|
* Он может быть указан в кортеже: (Clicks, Cost),
|
|
|
|
|
* или как один столбец: Clicks.
|
|
|
|
|
*/
|
|
|
|
|
static Names extractColumnNames(const ASTPtr & node)
|
|
|
|
|
{
|
|
|
|
|
const ASTFunction * expr_func = typeid_cast<const ASTFunction *>(&*node);
|
|
|
|
|
|
|
|
|
|
if (expr_func && expr_func->name == "tuple")
|
|
|
|
|
{
|
|
|
|
|
const auto & elements = expr_func->children.at(0)->children;
|
|
|
|
|
Names res;
|
|
|
|
|
res.reserve(elements.size());
|
|
|
|
|
for (const auto & elem : elements)
|
|
|
|
|
res.push_back(typeid_cast<const ASTIdentifier &>(*elem).name);
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return { typeid_cast<const ASTIdentifier &>(*node).name };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-18 17:20:44 +00:00
|
|
|
|
|
2011-10-31 17:30:44 +00:00
|
|
|
|
StoragePtr StorageFactory::get(
|
|
|
|
|
const String & name,
|
|
|
|
|
const String & data_path,
|
|
|
|
|
const String & table_name,
|
2013-02-07 13:03:19 +00:00
|
|
|
|
const String & database_name,
|
2014-09-23 15:28:21 +00:00
|
|
|
|
Context & local_context,
|
2012-05-22 19:32:56 +00:00
|
|
|
|
Context & context,
|
|
|
|
|
ASTPtr & query,
|
2013-01-17 20:21:03 +00:00
|
|
|
|
NamesAndTypesListPtr columns,
|
2014-10-03 15:30:10 +00:00
|
|
|
|
const NamesAndTypesList & materialized_columns,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
const NamesAndTypesList & alias_columns,
|
|
|
|
|
const ColumnDefaults & column_defaults,
|
2013-01-17 20:21:03 +00:00
|
|
|
|
bool attach) const
|
2011-10-31 17:30:44 +00:00
|
|
|
|
{
|
|
|
|
|
if (name == "Log")
|
2012-05-22 19:32:56 +00:00
|
|
|
|
{
|
2014-09-30 03:08:47 +00:00
|
|
|
|
return StorageLog::create(
|
2014-10-03 15:30:10 +00:00
|
|
|
|
data_path, table_name, columns,
|
|
|
|
|
materialized_columns, alias_columns, column_defaults,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
context.getSettings().max_compress_block_size);
|
2012-06-25 00:17:19 +00:00
|
|
|
|
}
|
2013-02-07 13:03:19 +00:00
|
|
|
|
else if (name == "Chunks")
|
|
|
|
|
{
|
2014-09-30 03:08:47 +00:00
|
|
|
|
return StorageChunks::create(
|
2014-10-03 15:30:10 +00:00
|
|
|
|
data_path, table_name, database_name, columns,
|
|
|
|
|
materialized_columns, alias_columns, column_defaults,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
context, attach);
|
2013-02-07 13:03:19 +00:00
|
|
|
|
}
|
2013-02-07 13:40:59 +00:00
|
|
|
|
else if (name == "ChunkRef")
|
|
|
|
|
{
|
2013-06-17 07:01:31 +00:00
|
|
|
|
throw Exception("Table with storage ChunkRef must not be created manually.", ErrorCodes::TABLE_MUST_NOT_BE_CREATED_MANUALLY);
|
2013-11-08 17:43:03 +00:00
|
|
|
|
}
|
|
|
|
|
else if (name == "View")
|
|
|
|
|
{
|
2014-09-30 03:08:47 +00:00
|
|
|
|
return StorageView::create(
|
2014-10-03 15:30:10 +00:00
|
|
|
|
table_name, database_name, context, query, columns,
|
|
|
|
|
materialized_columns, alias_columns, column_defaults);
|
2013-11-08 17:43:03 +00:00
|
|
|
|
}
|
|
|
|
|
else if (name == "MaterializedView")
|
2013-10-30 13:52:02 +00:00
|
|
|
|
{
|
2014-09-30 03:08:47 +00:00
|
|
|
|
return StorageMaterializedView::create(
|
2014-10-03 15:30:10 +00:00
|
|
|
|
table_name, database_name, context, query, columns,
|
|
|
|
|
materialized_columns, alias_columns, column_defaults,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
attach);
|
2013-02-07 13:40:59 +00:00
|
|
|
|
}
|
2013-02-11 09:19:48 +00:00
|
|
|
|
else if (name == "ChunkMerger")
|
|
|
|
|
{
|
2014-06-26 00:58:14 +00:00
|
|
|
|
ASTs & args_func = typeid_cast<ASTFunction &>(*typeid_cast<ASTCreateQuery &>(*query).storage).children;
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2013-02-11 09:19:48 +00:00
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
if (args_func.size() != 1)
|
|
|
|
|
break;
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
ASTs & args = typeid_cast<ASTExpressionList &>(*args_func.at(0)).children;
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2013-06-17 07:01:31 +00:00
|
|
|
|
if (args.size() < 3 || args.size() > 4)
|
2013-02-11 09:19:48 +00:00
|
|
|
|
break;
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2014-09-23 16:02:04 +00:00
|
|
|
|
String source_database = reinterpretAsIdentifier(args[0], local_context).name;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
String source_table_name_regexp = safeGet<const String &>(typeid_cast<ASTLiteral &>(*args[1]).value);
|
|
|
|
|
size_t chunks_to_merge = safeGet<UInt64>(typeid_cast<ASTLiteral &>(*args[2]).value);
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2013-03-14 11:43:43 +00:00
|
|
|
|
String destination_name_prefix = "group_";
|
|
|
|
|
String destination_database = source_database;
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2013-03-14 11:43:43 +00:00
|
|
|
|
if (args.size() > 3)
|
2014-06-26 00:58:14 +00:00
|
|
|
|
destination_name_prefix = typeid_cast<ASTIdentifier &>(*args[3]).name;
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2014-09-30 03:08:47 +00:00
|
|
|
|
return StorageChunkMerger::create(
|
2014-10-03 15:30:10 +00:00
|
|
|
|
database_name, table_name, columns,
|
|
|
|
|
materialized_columns, alias_columns, column_defaults,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
source_database, source_table_name_regexp,
|
|
|
|
|
destination_name_prefix, chunks_to_merge, context);
|
|
|
|
|
} while (false);
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2013-06-17 07:01:31 +00:00
|
|
|
|
throw Exception("Storage ChunkMerger requires from 3 to 4 parameters:"
|
|
|
|
|
" source database, regexp for source table names, number of chunks to merge, [destination tables name prefix].",
|
2013-02-11 09:19:48 +00:00
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
}
|
2012-06-25 00:51:23 +00:00
|
|
|
|
else if (name == "TinyLog")
|
2012-06-25 00:17:19 +00:00
|
|
|
|
{
|
2014-09-30 03:08:47 +00:00
|
|
|
|
return StorageTinyLog::create(
|
2014-10-03 15:30:10 +00:00
|
|
|
|
data_path, table_name, columns,
|
|
|
|
|
materialized_columns, alias_columns, column_defaults,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
attach, context.getSettings().max_compress_block_size);
|
2012-05-22 19:32:56 +00:00
|
|
|
|
}
|
2015-01-27 00:52:03 +00:00
|
|
|
|
else if (name == "Set")
|
|
|
|
|
{
|
|
|
|
|
return StorageSet::create(
|
|
|
|
|
data_path, table_name, columns,
|
|
|
|
|
materialized_columns, alias_columns, column_defaults);
|
|
|
|
|
}
|
2015-01-28 00:38:10 +00:00
|
|
|
|
else if (name == "Join")
|
|
|
|
|
{
|
|
|
|
|
/// Join(ANY, LEFT, k1, k2, ...)
|
|
|
|
|
|
|
|
|
|
ASTs & args_func = typeid_cast<ASTFunction &>(*typeid_cast<ASTCreateQuery &>(*query).storage).children;
|
|
|
|
|
|
|
|
|
|
constexpr auto params_error_message = "Storage Join requires at least 3 parameters: Join(ANY|ALL, LEFT|INNER, keys...).";
|
|
|
|
|
|
|
|
|
|
if (args_func.size() != 1)
|
|
|
|
|
throw Exception(params_error_message, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
|
|
|
|
|
ASTs & args = typeid_cast<ASTExpressionList &>(*args_func.at(0)).children;
|
|
|
|
|
|
|
|
|
|
if (args.size() < 3)
|
|
|
|
|
throw Exception(params_error_message, ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
|
|
|
|
|
const ASTIdentifier * strictness_id = typeid_cast<ASTIdentifier *>(&*args[0]);
|
|
|
|
|
if (!strictness_id)
|
|
|
|
|
throw Exception("First parameter of storage Join must be ANY or ALL (without quotes).", ErrorCodes::BAD_ARGUMENTS);
|
|
|
|
|
|
|
|
|
|
const String strictness_str = Poco::toLower(strictness_id->name);
|
|
|
|
|
ASTJoin::Strictness strictness;
|
|
|
|
|
if (strictness_str == "any")
|
|
|
|
|
strictness = ASTJoin::Strictness::Any;
|
|
|
|
|
else if (strictness_str == "all")
|
|
|
|
|
strictness = ASTJoin::Strictness::All;
|
|
|
|
|
else
|
|
|
|
|
throw Exception("First parameter of storage Join must be ANY or ALL (without quotes).", ErrorCodes::BAD_ARGUMENTS);
|
|
|
|
|
|
|
|
|
|
const ASTIdentifier * kind_id = typeid_cast<ASTIdentifier *>(&*args[1]);
|
|
|
|
|
if (!kind_id)
|
|
|
|
|
throw Exception("Second parameter of storage Join must be LEFT or INNER (without quotes).", ErrorCodes::BAD_ARGUMENTS);
|
|
|
|
|
|
2015-01-28 02:37:05 +00:00
|
|
|
|
const String kind_str = Poco::toLower(kind_id->name);
|
2015-01-28 00:38:10 +00:00
|
|
|
|
ASTJoin::Kind kind;
|
|
|
|
|
if (kind_str == "left")
|
|
|
|
|
kind = ASTJoin::Kind::Left;
|
|
|
|
|
else if (kind_str == "inner")
|
|
|
|
|
kind = ASTJoin::Kind::Inner;
|
|
|
|
|
else
|
|
|
|
|
throw Exception("Second parameter of storage Join must be LEFT or INNER (without quotes).", ErrorCodes::BAD_ARGUMENTS);
|
|
|
|
|
|
|
|
|
|
Names key_names;
|
|
|
|
|
key_names.reserve(args.size() - 2);
|
|
|
|
|
for (size_t i = 2, size = args.size(); i < size; ++i)
|
|
|
|
|
{
|
|
|
|
|
const ASTIdentifier * key = typeid_cast<ASTIdentifier *>(&*args[i]);
|
|
|
|
|
if (!key)
|
|
|
|
|
throw Exception("Parameter №" + toString(i + 1) + " of storage Join don't look like column name.", ErrorCodes::BAD_ARGUMENTS);
|
|
|
|
|
|
|
|
|
|
key_names.push_back(key->name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return StorageJoin::create(
|
|
|
|
|
data_path, table_name,
|
|
|
|
|
key_names, kind, strictness,
|
|
|
|
|
columns, materialized_columns, alias_columns, column_defaults);
|
|
|
|
|
}
|
2011-10-31 17:55:06 +00:00
|
|
|
|
else if (name == "Memory")
|
2012-05-22 19:32:56 +00:00
|
|
|
|
{
|
2014-10-03 15:30:10 +00:00
|
|
|
|
return StorageMemory::create(table_name, columns, materialized_columns, alias_columns, column_defaults);
|
2012-05-22 19:32:56 +00:00
|
|
|
|
}
|
2014-06-16 17:32:31 +00:00
|
|
|
|
else if (name == "Null")
|
|
|
|
|
{
|
2014-10-03 15:30:10 +00:00
|
|
|
|
return StorageNull::create(table_name, columns, materialized_columns, alias_columns, column_defaults);
|
2014-06-16 17:32:31 +00:00
|
|
|
|
}
|
2012-05-30 05:53:09 +00:00
|
|
|
|
else if (name == "Merge")
|
|
|
|
|
{
|
|
|
|
|
/** В запросе в качестве аргумента для движка указано имя БД, в которой находятся таблицы-источники,
|
|
|
|
|
* а также регексп для имён таблиц-источников.
|
|
|
|
|
*/
|
2014-06-26 00:58:14 +00:00
|
|
|
|
ASTs & args_func = typeid_cast<ASTFunction &>(*typeid_cast<ASTCreateQuery &>(*query).storage).children;
|
2012-05-30 05:53:09 +00:00
|
|
|
|
|
|
|
|
|
if (args_func.size() != 1)
|
|
|
|
|
throw Exception("Storage Merge requires exactly 2 parameters"
|
|
|
|
|
" - name of source database and regexp for table names.",
|
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
ASTs & args = typeid_cast<ASTExpressionList &>(*args_func.at(0)).children;
|
2012-05-30 05:53:09 +00:00
|
|
|
|
|
|
|
|
|
if (args.size() != 2)
|
|
|
|
|
throw Exception("Storage Merge requires exactly 2 parameters"
|
|
|
|
|
" - name of source database and regexp for table names.",
|
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
|
2014-09-23 16:02:04 +00:00
|
|
|
|
String source_database = reinterpretAsIdentifier(args[0], local_context).name;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
String table_name_regexp = safeGet<const String &>(typeid_cast<ASTLiteral &>(*args[1]).value);
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2014-09-30 03:08:47 +00:00
|
|
|
|
return StorageMerge::create(
|
2014-10-03 15:30:10 +00:00
|
|
|
|
table_name, columns,
|
|
|
|
|
materialized_columns, alias_columns, column_defaults,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
source_database, table_name_regexp, context);
|
2012-05-30 05:53:09 +00:00
|
|
|
|
}
|
2012-05-22 19:32:56 +00:00
|
|
|
|
else if (name == "Distributed")
|
|
|
|
|
{
|
|
|
|
|
/** В запросе в качестве аргумента для движка указано имя конфигурационной секции,
|
2012-05-30 05:53:09 +00:00
|
|
|
|
* в которой задан список удалённых серверов, а также имя удалённой БД и имя удалённой таблицы.
|
2012-05-22 19:32:56 +00:00
|
|
|
|
*/
|
2014-06-26 00:58:14 +00:00
|
|
|
|
ASTs & args_func = typeid_cast<ASTFunction &>(*typeid_cast<ASTCreateQuery &>(*query).storage).children;
|
2012-05-22 20:18:45 +00:00
|
|
|
|
|
|
|
|
|
if (args_func.size() != 1)
|
2014-06-12 19:23:06 +00:00
|
|
|
|
throw Exception("Storage Distributed requires 3 parameters"
|
|
|
|
|
" - name of configuration section with list of remote servers, name of remote database, name of remote table.",
|
2012-05-22 20:18:45 +00:00
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
ASTs & args = typeid_cast<ASTExpressionList &>(*args_func.at(0)).children;
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2014-08-12 13:46:46 +00:00
|
|
|
|
if (args.size() != 3 && args.size() != 4)
|
2014-08-21 12:07:29 +00:00
|
|
|
|
throw Exception("Storage Distributed requires 3 or 4 parameters"
|
|
|
|
|
" - name of configuration section with list of remote servers, name of remote database, name of remote table,"
|
|
|
|
|
" sharding key expression (optional).",
|
2012-05-22 19:32:56 +00:00
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
2014-06-12 19:23:06 +00:00
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
String cluster_name = typeid_cast<ASTIdentifier &>(*args[0]).name;
|
2014-09-23 16:02:04 +00:00
|
|
|
|
String remote_database = reinterpretAsIdentifier(args[1], local_context).name;
|
2014-06-26 00:58:14 +00:00
|
|
|
|
String remote_table = typeid_cast<ASTIdentifier &>(*args[2]).name;
|
2012-11-06 17:04:38 +00:00
|
|
|
|
|
2014-08-12 13:46:46 +00:00
|
|
|
|
const auto & sharding_key = args.size() == 4 ? args[3] : nullptr;
|
|
|
|
|
|
|
|
|
|
return StorageDistributed::create(
|
2014-10-03 15:30:10 +00:00
|
|
|
|
table_name, columns,
|
|
|
|
|
materialized_columns, alias_columns, column_defaults,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
remote_database, remote_table, cluster_name,
|
|
|
|
|
context, sharding_key, data_path);
|
2012-05-22 19:32:56 +00:00
|
|
|
|
}
|
2014-10-26 00:01:36 +00:00
|
|
|
|
else if (name == "Buffer")
|
|
|
|
|
{
|
|
|
|
|
/** Buffer(db, table, num_buckets, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes)
|
|
|
|
|
*
|
|
|
|
|
* db, table - в какую таблицу сбрасывать данные из буфера.
|
|
|
|
|
* num_buckets - уровень параллелизма.
|
|
|
|
|
* min_time, max_time, min_rows, max_rows, min_bytes, max_bytes - условия вытеснения из буфера.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
ASTs & args_func = typeid_cast<ASTFunction &>(*typeid_cast<ASTCreateQuery &>(*query).storage).children;
|
|
|
|
|
|
|
|
|
|
if (args_func.size() != 1)
|
|
|
|
|
throw Exception("Storage Buffer requires 9 parameters: "
|
|
|
|
|
" destination database, destination table, num_buckets, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes.",
|
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
|
|
|
|
|
ASTs & args = typeid_cast<ASTExpressionList &>(*args_func.at(0)).children;
|
|
|
|
|
|
|
|
|
|
if (args.size() != 9)
|
|
|
|
|
throw Exception("Storage Buffer requires 9 parameters: "
|
|
|
|
|
" destination_database, destination_table, num_buckets, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes.",
|
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
|
|
|
|
|
String destination_database = reinterpretAsIdentifier(args[0], local_context).name;
|
|
|
|
|
String destination_table = typeid_cast<ASTIdentifier &>(*args[1]).name;
|
|
|
|
|
|
|
|
|
|
size_t num_buckets = apply_visitor(FieldVisitorConvertToNumber<size_t>(), typeid_cast<ASTLiteral &>(*args[2]).value);
|
|
|
|
|
|
|
|
|
|
time_t min_time = apply_visitor(FieldVisitorConvertToNumber<size_t>(), typeid_cast<ASTLiteral &>(*args[3]).value);
|
|
|
|
|
time_t max_time = apply_visitor(FieldVisitorConvertToNumber<size_t>(), typeid_cast<ASTLiteral &>(*args[4]).value);
|
|
|
|
|
size_t min_rows = apply_visitor(FieldVisitorConvertToNumber<size_t>(), typeid_cast<ASTLiteral &>(*args[5]).value);
|
|
|
|
|
size_t max_rows = apply_visitor(FieldVisitorConvertToNumber<size_t>(), typeid_cast<ASTLiteral &>(*args[6]).value);
|
|
|
|
|
size_t min_bytes = apply_visitor(FieldVisitorConvertToNumber<size_t>(), typeid_cast<ASTLiteral &>(*args[7]).value);
|
|
|
|
|
size_t max_bytes = apply_visitor(FieldVisitorConvertToNumber<size_t>(), typeid_cast<ASTLiteral &>(*args[8]).value);
|
|
|
|
|
|
|
|
|
|
return StorageBuffer::create(
|
|
|
|
|
table_name, columns, context,
|
|
|
|
|
num_buckets, {min_time, min_rows, min_bytes}, {max_time, max_rows, max_bytes},
|
|
|
|
|
destination_database, destination_table);
|
|
|
|
|
}
|
2014-03-21 19:17:59 +00:00
|
|
|
|
else if (endsWith(name, "MergeTree"))
|
2012-07-18 19:30:33 +00:00
|
|
|
|
{
|
2014-11-22 02:22:30 +00:00
|
|
|
|
/** Движки [Replicated][Summing|Collapsing|Aggregating|]MergeTree (8 комбинаций)
|
2014-03-21 19:17:59 +00:00
|
|
|
|
* В качестве аргумента для движка должно быть указано:
|
|
|
|
|
* - (для Replicated) Путь к таблице в ZooKeeper
|
|
|
|
|
* - (для Replicated) Имя реплики в ZooKeeper
|
2012-07-18 19:30:33 +00:00
|
|
|
|
* - имя столбца с датой;
|
2014-05-22 18:58:07 +00:00
|
|
|
|
* - (не обязательно) выражение для семплирования (запрос с SAMPLE x будет выбирать строки, у которых в этом столбце значение меньше, чем x*UINT32_MAX);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
* - выражение для сортировки (либо скалярное выражение, либо tuple из нескольких);
|
|
|
|
|
* - index_granularity;
|
|
|
|
|
* - (для Collapsing) имя столбца, содержащего тип строчки с изменением "визита" (принимающего значения 1 и -1).
|
|
|
|
|
* Например: ENGINE = ReplicatedCollapsingMergeTree('/tables/mytable', 'rep02', EventDate, (CounterID, EventDate, intHash32(UniqID), VisitID), 8192, Sign).
|
2014-11-22 02:22:30 +00:00
|
|
|
|
* - (для Summing, не обязательно) кортеж столбцов, которых следует суммировать. Если не задано - используются все числовые столбцы, не входящие в первичный ключ.
|
|
|
|
|
* Например: ENGINE = ReplicatedCollapsingMergeTree('/tables/mytable', 'rep02', EventDate, (CounterID, EventDate, intHash32(UniqID), VisitID), 8192, Sign).
|
|
|
|
|
*
|
|
|
|
|
* MergeTree(date, [sample_key], primary_key, index_granularity)
|
|
|
|
|
* CollapsingMergeTree(date, [sample_key], primary_key, index_granularity, sign)
|
|
|
|
|
* SummingMergeTree(date, [sample_key], primary_key, index_granularity, [columns_to_sum])
|
|
|
|
|
* AggregatingMergeTree(date, [sample_key], primary_key, index_granularity)
|
2012-07-18 19:30:33 +00:00
|
|
|
|
*/
|
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
const char * verbose_help = R"(
|
|
|
|
|
|
|
|
|
|
MergeTree is family of storage engines.
|
|
|
|
|
|
|
|
|
|
MergeTrees is different in two ways:
|
|
|
|
|
- it may be replicated and non-replicated;
|
|
|
|
|
- it may do different actions on merge: nothing; sign collapse; sum; apply aggregete functions.
|
|
|
|
|
|
|
|
|
|
So we have 8 combinations:
|
|
|
|
|
MergeTree, CollapsingMergeTree, SummingMergeTree, AggregatingMergeTree,
|
|
|
|
|
ReplicatedMergeTree, ReplicatedCollapsingMergeTree, ReplicatedSummingMergeTree, ReplicatedAggregatingMergeTree.
|
|
|
|
|
|
|
|
|
|
In most of cases, you need MergeTree or ReplicatedMergeTree.
|
|
|
|
|
|
|
|
|
|
For replicated merge trees, you need to supply path in ZooKeeper and replica name as first two parameters.
|
|
|
|
|
Path in ZooKeeper is like '/clickhouse/tables/01/' where /clickhouse/tables/ is common prefix and 01 is shard name.
|
|
|
|
|
Replica name is like 'mtstat01-1' - it may be hostname or any suitable string identifying replica.
|
|
|
|
|
You may use macro substitutions for these parameters. It's like ReplicatedMergeTree('/clickhouse/tables/{shard}/', '{replica}'...
|
|
|
|
|
Look at <macros> section in server configuration file.
|
|
|
|
|
|
|
|
|
|
Next parameter (which is first for unreplicated tables and third for replicated tables) is name of date column.
|
|
|
|
|
Date column must exist in table and have type Date (not DateTime).
|
|
|
|
|
It is used for internal data partitioning and works like some kind of index.
|
|
|
|
|
|
|
|
|
|
If your source data doesn't have column of type Date, but have DateTime column, you may add values for Date column while loading,
|
|
|
|
|
or you may INSERT your source data to table of type Log and then transform it with INSERT INTO t SELECT toDate(time) AS date, * FROM ...
|
|
|
|
|
If your source data doesn't have any date or time, you may just pass any constant for date column while loading.
|
|
|
|
|
|
|
|
|
|
Next parameter is optional sampling expression. Sampling expression is used to implement SAMPLE clause in query for approximate query execution.
|
|
|
|
|
If you don't need approximate query execution, simply omit this parameter.
|
|
|
|
|
Sample expression must be one of elements of primary key tuple. For example, if your primary key is (CounterID, EventDate, intHash64(UserID)), your sampling expression might be intHash64(UserID).
|
|
|
|
|
|
|
|
|
|
Next parameter is primary key tuple. It's like (CounterID, EventDate, intHash64(UserID)) - list of column names or functional expressions in round brackets. If your primary key have just one element, you may omit round brackets.
|
|
|
|
|
|
|
|
|
|
Careful choice of primary key is extremely important for processing short-time queries.
|
|
|
|
|
|
|
|
|
|
Next parameter is index (primary key) granularity. Good value is 8192. You have no reasons to use any other value.
|
|
|
|
|
|
|
|
|
|
For Collapsing mode, last parameter is name of sign column - special column that is used to 'collapse' rows with same primary key while merge.
|
|
|
|
|
|
|
|
|
|
For Summing mode, last parameter is optional list of columns to sum while merge. List is passed in round brackets, like (PageViews, Cost).
|
|
|
|
|
If this parameter is omitted, storage will sum all numeric columns except columns participated in primary key.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
|
|
MergeTree(EventDate, (CounterID, EventDate), 8192)
|
|
|
|
|
|
|
|
|
|
MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID), EventTime), 8192)
|
|
|
|
|
|
|
|
|
|
CollapsingMergeTree(StartDate, intHash32(UserID), (CounterID, StartDate, intHash32(UserID), VisitID), 8192, Sign)
|
|
|
|
|
|
|
|
|
|
SummingMergeTree(EventDate, (OrderID, EventDate, BannerID, PhraseID, ContextType, RegionID, PageID, IsFlat, TypeID, ResourceNo), 8192)
|
|
|
|
|
|
|
|
|
|
SummingMergeTree(EventDate, (OrderID, EventDate, BannerID, PhraseID, ContextType, RegionID, PageID, IsFlat, TypeID, ResourceNo), 8192, (Shows, Clicks, Cost, CostCur, ShowsSumPosition, ClicksSumPosition, SessionNum, SessionLen, SessionCost, GoalsNum, SessionDepth))
|
|
|
|
|
|
|
|
|
|
ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/hits', '{replica}', EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID), EventTime), 8192)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For further info please read the documentation: http://clickhouse.yandex-team.ru/
|
|
|
|
|
)";
|
|
|
|
|
|
2014-03-21 19:17:59 +00:00
|
|
|
|
String name_part = name.substr(0, name.size() - strlen("MergeTree"));
|
2012-07-18 19:30:33 +00:00
|
|
|
|
|
2014-03-21 19:17:59 +00:00
|
|
|
|
bool replicated = startsWith(name_part, "Replicated");
|
|
|
|
|
if (replicated)
|
|
|
|
|
name_part = name_part.substr(strlen("Replicated"));
|
2012-07-18 19:30:33 +00:00
|
|
|
|
|
2014-03-21 19:17:59 +00:00
|
|
|
|
MergeTreeData::Mode mode = MergeTreeData::Ordinary;
|
2012-07-18 19:30:33 +00:00
|
|
|
|
|
2014-03-21 19:17:59 +00:00
|
|
|
|
if (name_part == "Collapsing")
|
|
|
|
|
mode = MergeTreeData::Collapsing;
|
|
|
|
|
else if (name_part == "Summing")
|
|
|
|
|
mode = MergeTreeData::Summing;
|
2014-05-28 14:54:42 +00:00
|
|
|
|
else if (name_part == "Aggregating")
|
|
|
|
|
mode = MergeTreeData::Aggregating;
|
2014-03-21 19:17:59 +00:00
|
|
|
|
else if (!name_part.empty())
|
2014-11-22 02:22:30 +00:00
|
|
|
|
throw Exception("Unknown storage " + name + verbose_help, ErrorCodes::UNKNOWN_STORAGE);
|
2012-12-12 14:25:55 +00:00
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
ASTs & args_func = typeid_cast<ASTFunction &>(*typeid_cast<ASTCreateQuery &>(*query).storage).children;
|
2012-08-16 17:27:40 +00:00
|
|
|
|
|
2014-03-24 13:59:04 +00:00
|
|
|
|
ASTs args;
|
2012-08-16 17:27:40 +00:00
|
|
|
|
|
2014-03-24 13:59:04 +00:00
|
|
|
|
if (args_func.size() == 1)
|
2014-06-26 00:58:14 +00:00
|
|
|
|
args = typeid_cast<ASTExpressionList &>(*args_func.at(0)).children;
|
2012-08-16 17:27:40 +00:00
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
/// NOTE Слегка запутанно.
|
|
|
|
|
size_t num_additional_params = (replicated ? 2 : 0) + (mode == MergeTreeData::Collapsing);
|
|
|
|
|
|
|
|
|
|
if (mode != MergeTreeData::Summing
|
|
|
|
|
&& args.size() != num_additional_params + 3
|
|
|
|
|
&& args.size() != num_additional_params + 4)
|
2014-03-21 19:17:59 +00:00
|
|
|
|
{
|
|
|
|
|
String params;
|
2014-11-22 02:22:30 +00:00
|
|
|
|
|
2014-03-21 19:17:59 +00:00
|
|
|
|
if (replicated)
|
2014-11-22 02:22:30 +00:00
|
|
|
|
params +=
|
|
|
|
|
"\npath in ZooKeeper,"
|
|
|
|
|
"\nreplica name,";
|
|
|
|
|
|
|
|
|
|
params +=
|
|
|
|
|
"\nname of column with date,"
|
|
|
|
|
"\n[sampling element of primary key],"
|
|
|
|
|
"\nprimary key expression,"
|
|
|
|
|
"\nindex granularity\n";
|
|
|
|
|
|
2014-03-21 19:17:59 +00:00
|
|
|
|
if (mode == MergeTreeData::Collapsing)
|
2014-06-30 07:36:06 +00:00
|
|
|
|
params += ", sign column";
|
2014-11-22 02:22:30 +00:00
|
|
|
|
|
|
|
|
|
throw Exception("Storage " + name + " requires "
|
|
|
|
|
+ toString(num_additional_params + 3) + " or "
|
|
|
|
|
+ toString(num_additional_params + 4) + " parameters: " + params + verbose_help,
|
2012-08-16 17:27:40 +00:00
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
}
|
2012-08-16 17:27:40 +00:00
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
if (mode == MergeTreeData::Summing
|
|
|
|
|
&& args.size() != num_additional_params + 3
|
|
|
|
|
&& args.size() != num_additional_params + 4
|
|
|
|
|
&& args.size() != num_additional_params + 5)
|
|
|
|
|
{
|
|
|
|
|
String params;
|
|
|
|
|
|
|
|
|
|
if (replicated)
|
|
|
|
|
params +=
|
|
|
|
|
"\npath in ZooKeeper,"
|
|
|
|
|
"\nreplica name,";
|
|
|
|
|
|
|
|
|
|
params +=
|
|
|
|
|
"\nname of column with date,"
|
|
|
|
|
"\n[sampling element of primary key],"
|
|
|
|
|
"\nprimary key expression,"
|
|
|
|
|
"\nindex granularity,"
|
|
|
|
|
"\n[list of columns to sum]\n";
|
|
|
|
|
|
|
|
|
|
throw Exception("Storage " + name + " requires "
|
|
|
|
|
+ toString(num_additional_params + 3) + " or "
|
|
|
|
|
+ toString(num_additional_params + 4) + " or "
|
|
|
|
|
+ toString(num_additional_params + 5) + " parameters: " + params + verbose_help,
|
|
|
|
|
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Для Replicated.
|
2014-03-21 19:17:59 +00:00
|
|
|
|
String zookeeper_path;
|
|
|
|
|
String replica_name;
|
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
/// Для всех.
|
2014-03-21 19:17:59 +00:00
|
|
|
|
String date_column_name;
|
|
|
|
|
ASTPtr primary_expr_list;
|
|
|
|
|
ASTPtr sampling_expression;
|
|
|
|
|
UInt64 index_granularity;
|
2012-08-16 17:27:40 +00:00
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
/// Для Collapsing.
|
2014-03-21 19:17:59 +00:00
|
|
|
|
String sign_column_name;
|
2012-08-16 17:27:40 +00:00
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
/// Для Summing.
|
|
|
|
|
Names columns_to_sum;
|
|
|
|
|
|
2014-03-21 19:17:59 +00:00
|
|
|
|
if (replicated)
|
|
|
|
|
{
|
2014-06-26 00:58:14 +00:00
|
|
|
|
auto ast = typeid_cast<ASTLiteral *>(&*args[0]);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
if (ast && ast->value.getType() == Field::Types::String)
|
|
|
|
|
zookeeper_path = safeGet<String>(ast->value);
|
|
|
|
|
else
|
2014-11-22 02:22:30 +00:00
|
|
|
|
throw Exception(String("Path in ZooKeeper must be a string literal") + verbose_help, ErrorCodes::BAD_ARGUMENTS);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
ast = typeid_cast<ASTLiteral *>(&*args[1]);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
if (ast && ast->value.getType() == Field::Types::String)
|
|
|
|
|
replica_name = safeGet<String>(ast->value);
|
|
|
|
|
else
|
2014-11-22 02:22:30 +00:00
|
|
|
|
throw Exception(String("Replica name must be a string literal") + verbose_help, ErrorCodes::BAD_ARGUMENTS);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
|
2014-04-14 10:18:23 +00:00
|
|
|
|
if (replica_name.empty())
|
2014-11-22 02:22:30 +00:00
|
|
|
|
throw Exception(String("No replica name in config") + verbose_help, ErrorCodes::NO_REPLICA_NAME_GIVEN);
|
2014-04-14 10:18:23 +00:00
|
|
|
|
|
2014-03-21 19:17:59 +00:00
|
|
|
|
args.erase(args.begin(), args.begin() + 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mode == MergeTreeData::Collapsing)
|
|
|
|
|
{
|
2014-06-26 00:58:14 +00:00
|
|
|
|
if (auto ast = typeid_cast<ASTIdentifier *>(&*args.back()))
|
2014-03-21 19:17:59 +00:00
|
|
|
|
sign_column_name = ast->name;
|
|
|
|
|
else
|
2014-11-22 02:22:30 +00:00
|
|
|
|
throw Exception(String("Sign column name must be an unquoted string") + verbose_help, ErrorCodes::BAD_ARGUMENTS);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
|
|
|
|
|
args.pop_back();
|
|
|
|
|
}
|
2014-11-22 02:22:30 +00:00
|
|
|
|
else if (mode == MergeTreeData::Summing)
|
|
|
|
|
{
|
|
|
|
|
/// Если последний элемент - не index granularity (литерал), то это - список суммируемых столбцов.
|
|
|
|
|
if (!typeid_cast<const ASTLiteral *>(&*args.back()))
|
|
|
|
|
{
|
|
|
|
|
columns_to_sum = extractColumnNames(args.back());
|
|
|
|
|
args.pop_back();
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-03-21 19:17:59 +00:00
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
/// Если присутствует выражение для сэмплирования. MergeTree(date, [sample_key], primary_key, index_granularity)
|
2014-03-21 19:17:59 +00:00
|
|
|
|
if (args.size() == 4)
|
|
|
|
|
{
|
|
|
|
|
sampling_expression = args[1];
|
|
|
|
|
args.erase(args.begin() + 1);
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
/// Теперь осталось только три параметра - date, primary_key, index_granularity.
|
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
if (auto ast = typeid_cast<ASTIdentifier *>(&*args[0]))
|
2014-03-21 19:17:59 +00:00
|
|
|
|
date_column_name = ast->name;
|
|
|
|
|
else
|
2014-11-22 02:22:30 +00:00
|
|
|
|
throw Exception(String("Date column name must be an unquoted string") + verbose_help, ErrorCodes::BAD_ARGUMENTS);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
|
2014-11-22 02:22:30 +00:00
|
|
|
|
primary_expr_list = extractPrimaryKey(args[1]);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
|
2014-06-26 00:58:14 +00:00
|
|
|
|
auto ast = typeid_cast<ASTLiteral *>(&*args[2]);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
if (ast && ast->value.getType() == Field::Types::UInt64)
|
|
|
|
|
index_granularity = safeGet<UInt64>(ast->value);
|
|
|
|
|
else
|
2014-11-22 02:22:30 +00:00
|
|
|
|
throw Exception(String("Index granularity must be a positive integer") + verbose_help, ErrorCodes::BAD_ARGUMENTS);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
|
|
|
|
|
if (replicated)
|
2014-09-30 03:08:47 +00:00
|
|
|
|
return StorageReplicatedMergeTree::create(
|
|
|
|
|
zookeeper_path, replica_name, attach, data_path, database_name, table_name,
|
2014-10-03 15:30:10 +00:00
|
|
|
|
columns, materialized_columns, alias_columns, column_defaults,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
context, primary_expr_list, date_column_name,
|
2014-11-22 02:22:30 +00:00
|
|
|
|
sampling_expression, index_granularity, mode, sign_column_name, columns_to_sum);
|
2014-03-21 19:17:59 +00:00
|
|
|
|
else
|
2014-09-30 03:08:47 +00:00
|
|
|
|
return StorageMergeTree::create(
|
|
|
|
|
data_path, database_name, table_name,
|
2014-10-03 15:30:10 +00:00
|
|
|
|
columns, materialized_columns, alias_columns, column_defaults,
|
2014-09-30 03:08:47 +00:00
|
|
|
|
context, primary_expr_list, date_column_name,
|
2014-11-22 02:22:30 +00:00
|
|
|
|
sampling_expression, index_granularity, mode, sign_column_name, columns_to_sum);
|
2012-08-16 17:27:40 +00:00
|
|
|
|
}
|
2011-10-31 17:30:44 +00:00
|
|
|
|
else
|
|
|
|
|
throw Exception("Unknown storage " + name, ErrorCodes::UNKNOWN_STORAGE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|