2015-09-18 13:36:10 +00:00
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <DB/Parsers/ASTSelectQuery.h>
|
|
|
|
|
#include <DB/Parsers/ASTFunction.h>
|
2016-07-22 20:39:28 +00:00
|
|
|
|
#include <DB/Parsers/ASTTablesInSelectQuery.h>
|
2015-09-18 13:36:10 +00:00
|
|
|
|
#include <DB/Parsers/ASTIdentifier.h>
|
|
|
|
|
#include <DB/Storages/IStorage.h>
|
|
|
|
|
#include <DB/Storages/StorageDistributed.h>
|
|
|
|
|
#include <DB/Interpreters/Context.h>
|
|
|
|
|
|
|
|
|
|
#include <deque>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <type_traits>
|
|
|
|
|
|
|
|
|
|
namespace DB
|
|
|
|
|
{
|
|
|
|
|
|
2016-01-11 21:46:36 +00:00
|
|
|
|
namespace ErrorCodes
|
|
|
|
|
{
|
|
|
|
|
extern const int DISTRIBUTED_IN_JOIN_SUBQUERY_DENIED;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-18 13:36:10 +00:00
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
using NameToAttributes = std::unordered_map<std::string, IAST::Attributes>;
|
|
|
|
|
|
|
|
|
|
const NameToAttributes name_to_attributes =
|
|
|
|
|
{
|
|
|
|
|
{ "in", IAST::IsIn },
|
|
|
|
|
{ "notIn", IAST::IsNotIn },
|
|
|
|
|
{ "globalIn", IAST::IsIn | IAST::IsGlobal },
|
|
|
|
|
{ "globalNotIn", IAST::IsNotIn | IAST::IsGlobal }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Из названия секции IN получить соответствующие атрибуты.
|
|
|
|
|
IAST::Attributes getAttributesFromInSubqueryName(const std::string & name)
|
|
|
|
|
{
|
|
|
|
|
auto it = name_to_attributes.find(name);
|
|
|
|
|
if (it != name_to_attributes.end())
|
|
|
|
|
return it->second;
|
|
|
|
|
else
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Из атрибутов составить название секции IN.
|
|
|
|
|
std::string getNameFromInSubqueryAttributes(IAST::Attributes attributes)
|
|
|
|
|
{
|
|
|
|
|
std::string name;
|
|
|
|
|
|
|
|
|
|
if (attributes & IAST::IsIn)
|
|
|
|
|
{
|
|
|
|
|
if (attributes & IAST::IsGlobal)
|
|
|
|
|
name = "globalIn";
|
|
|
|
|
else
|
|
|
|
|
name = "in";
|
|
|
|
|
}
|
|
|
|
|
else if (attributes & IAST::IsNotIn)
|
|
|
|
|
{
|
|
|
|
|
if (attributes & IAST::IsGlobal)
|
|
|
|
|
name = "globalNotIn";
|
|
|
|
|
else
|
|
|
|
|
name = "notIn";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-05 11:11:20 +00:00
|
|
|
|
/// Проверить, указана ли таблица в секции FROM.
|
|
|
|
|
bool isQueryFromTable(const ASTSelectQuery & query)
|
|
|
|
|
{
|
2016-07-22 20:39:28 +00:00
|
|
|
|
auto query_table = query.table();
|
|
|
|
|
|
|
|
|
|
if (query_table)
|
2015-11-05 11:11:20 +00:00
|
|
|
|
{
|
2016-11-14 01:13:56 +00:00
|
|
|
|
if (typeid_cast<const ASTSelectQuery *>(query_table.get()))
|
2015-11-05 11:11:20 +00:00
|
|
|
|
return false;
|
2016-11-14 01:13:56 +00:00
|
|
|
|
else if (typeid_cast<const ASTFunction *>(query_table.get()))
|
2015-11-05 11:11:20 +00:00
|
|
|
|
return false;
|
|
|
|
|
else
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-18 13:36:10 +00:00
|
|
|
|
/// Проверить, является ли движок распределённым с количеством шардов более одного.
|
|
|
|
|
template <typename TStorageDistributed>
|
|
|
|
|
bool isEligibleStorageForInJoinPreprocessing(const StoragePtr & storage)
|
|
|
|
|
{
|
|
|
|
|
if (!storage)
|
|
|
|
|
return false;
|
|
|
|
|
if (!storage->isRemote())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
auto storage_distributed = static_cast<TStorageDistributed *>(storage.get());
|
|
|
|
|
if (storage_distributed->getShardCount() < 2)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Этот класс предоставляет контроль над выполнением распределённых запросов внутри секций IN или JOIN.
|
|
|
|
|
* Мы используем шаблон, потому что движок StorageDistributed слишком сложный, чтобы писать юнит-тесты,
|
|
|
|
|
* которые бы зависели от него.
|
|
|
|
|
*/
|
|
|
|
|
template <typename TStorageDistributed = StorageDistributed, typename Enable = void>
|
|
|
|
|
class InJoinSubqueriesPreprocessor;
|
|
|
|
|
|
|
|
|
|
template <typename TStorageDistributed>
|
|
|
|
|
class InJoinSubqueriesPreprocessor<TStorageDistributed,
|
|
|
|
|
typename std::enable_if<std::is_base_of<IStorage, TStorageDistributed>::value>::type> final
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
InJoinSubqueriesPreprocessor(ASTSelectQuery * select_query_,
|
|
|
|
|
const Context & context_, const StoragePtr & storage_)
|
|
|
|
|
: select_query(select_query_), context(context_), settings(context.getSettingsRef()), storage(storage_)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InJoinSubqueriesPreprocessor(const InJoinSubqueriesPreprocessor &) = delete;
|
|
|
|
|
InJoinSubqueriesPreprocessor & operator=(const InJoinSubqueriesPreprocessor &) = delete;
|
|
|
|
|
|
|
|
|
|
/** В зависимости от профиля пользователя проверить наличие прав на выполнение
|
|
|
|
|
* распределённых подзапросов внутри секций IN или JOIN и обработать эти подзапросы.
|
|
|
|
|
*/
|
|
|
|
|
void perform()
|
|
|
|
|
{
|
|
|
|
|
if (settings.distributed_product_mode == DistributedProductMode::ALLOW)
|
|
|
|
|
{
|
|
|
|
|
/// Согласно профиля пользователя распределённые подзапросы внутри секций IN и JOIN разрешены.
|
|
|
|
|
/// Ничего не делаем.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (select_query == nullptr)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/// Проверить главный запрос. В секции FROM должна быть указана распределённая таблица
|
|
|
|
|
/// с количеством шардов более одного. Табличные функции пропускаем.
|
|
|
|
|
|
|
|
|
|
if (select_query->attributes & IAST::IsPreprocessedForInJoinSubqueries)
|
|
|
|
|
return;
|
|
|
|
|
|
2016-02-02 11:49:57 +00:00
|
|
|
|
if (!isQueryFromTable(*select_query))
|
2015-09-18 13:36:10 +00:00
|
|
|
|
{
|
|
|
|
|
select_query->setAttributes(IAST::IsPreprocessedForInJoinSubqueries);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isEligibleStorageForInJoinPreprocessing<TStorageDistributed>(storage))
|
|
|
|
|
{
|
|
|
|
|
select_query->setAttributes(IAST::IsPreprocessedForInJoinSubqueries);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Собрать информацию про все подзапросы внутри секций IN или JOIN.
|
|
|
|
|
/// Обработать те подзапросы, которые распределённые.
|
|
|
|
|
|
|
|
|
|
std::deque<IAST *> to_preprocess;
|
|
|
|
|
to_preprocess.push_back(select_query);
|
|
|
|
|
|
|
|
|
|
while (!to_preprocess.empty())
|
|
|
|
|
{
|
|
|
|
|
auto node = to_preprocess.back();
|
|
|
|
|
to_preprocess.pop_back();
|
|
|
|
|
|
2016-11-14 01:13:56 +00:00
|
|
|
|
if (ASTFunction * function = typeid_cast<ASTFunction *>(node))
|
2015-09-18 13:36:10 +00:00
|
|
|
|
{
|
|
|
|
|
auto attributes = getAttributesFromInSubqueryName(function->name);
|
|
|
|
|
if (attributes != 0)
|
|
|
|
|
{
|
|
|
|
|
/// Найдена секция IN.
|
|
|
|
|
node->enclosing_in_or_join = node;
|
|
|
|
|
node->attributes |= attributes;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-11-14 01:13:56 +00:00
|
|
|
|
else if (ASTTablesInSelectQueryElement * join = typeid_cast<ASTTablesInSelectQueryElement *>(node))
|
2015-09-18 13:36:10 +00:00
|
|
|
|
{
|
2016-11-14 01:13:56 +00:00
|
|
|
|
if (join->table_join)
|
|
|
|
|
{
|
|
|
|
|
/// Найдена секция JOIN.
|
|
|
|
|
join->enclosing_in_or_join = join->table_join.get();
|
|
|
|
|
join->table_join->attributes |= IAST::IsJoin;
|
|
|
|
|
if (static_cast<const ASTTableJoin &>(*join->table_join).locality == ASTTableJoin::Locality::Global)
|
|
|
|
|
join->table_join->attributes |= IAST::IsGlobal;
|
|
|
|
|
}
|
2015-09-18 13:36:10 +00:00
|
|
|
|
}
|
2016-11-14 01:13:56 +00:00
|
|
|
|
else if (node != static_cast<IAST *>(select_query))
|
2015-09-18 13:36:10 +00:00
|
|
|
|
{
|
2016-11-14 01:13:56 +00:00
|
|
|
|
if (ASTSelectQuery * sub_select_query = typeid_cast<ASTSelectQuery *>(node))
|
2015-09-18 13:36:10 +00:00
|
|
|
|
{
|
2016-11-14 01:13:56 +00:00
|
|
|
|
++node->select_query_depth;
|
|
|
|
|
|
|
|
|
|
if (sub_select_query->enclosing_in_or_join)
|
|
|
|
|
{
|
|
|
|
|
/// Найден подзапрос внутри секции IN или JOIN.
|
|
|
|
|
preprocessSubquery(*sub_select_query);
|
|
|
|
|
}
|
2015-09-18 13:36:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-02 11:49:57 +00:00
|
|
|
|
if (!(node->attributes & IAST::IsPreprocessedForInJoinSubqueries))
|
2015-09-18 13:36:10 +00:00
|
|
|
|
{
|
2016-02-02 11:49:57 +00:00
|
|
|
|
for (auto & child : node->children)
|
2015-09-18 13:36:10 +00:00
|
|
|
|
{
|
2016-02-02 11:49:57 +00:00
|
|
|
|
if (!(child->attributes & IAST::IsPreprocessedForInJoinSubqueries))
|
|
|
|
|
{
|
|
|
|
|
auto n = child.get();
|
|
|
|
|
n->enclosing_in_or_join = node->enclosing_in_or_join;
|
|
|
|
|
n->select_query_depth = node->select_query_depth;
|
|
|
|
|
to_preprocess.push_back(n);
|
|
|
|
|
}
|
2015-09-18 13:36:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-02 11:49:57 +00:00
|
|
|
|
node->attributes |= IAST::IsPreprocessedForInJoinSubqueries;
|
|
|
|
|
}
|
2015-09-18 13:36:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void preprocessSubquery(ASTSelectQuery & sub_select_query)
|
|
|
|
|
{
|
|
|
|
|
auto & enclosing_in_or_join = *sub_select_query.enclosing_in_or_join;
|
|
|
|
|
bool is_global = enclosing_in_or_join.attributes & IAST::IsGlobal;
|
|
|
|
|
|
|
|
|
|
/// Если подзапрос внутри секции IN или JOIN является непосредственным потомком
|
|
|
|
|
/// главного запроса и указано ключевое слово GLOBAL, то подзапрос пропускается.
|
|
|
|
|
if ((sub_select_query.select_query_depth == 1) && is_global)
|
2016-02-02 11:49:57 +00:00
|
|
|
|
{
|
|
|
|
|
sub_select_query.attributes |= IAST::IsPreprocessedForInJoinSubqueries;
|
2015-09-18 13:36:10 +00:00
|
|
|
|
return;
|
2016-02-02 11:49:57 +00:00
|
|
|
|
}
|
2015-09-18 13:36:10 +00:00
|
|
|
|
|
|
|
|
|
auto subquery_table_storage = getDistributedSubqueryStorage(sub_select_query);
|
|
|
|
|
if (!subquery_table_storage)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (settings.distributed_product_mode == DistributedProductMode::DENY)
|
|
|
|
|
{
|
|
|
|
|
/// Согласно профиля пользователя распределённые подзапросы внутри секций IN и JOIN запрещены.
|
2015-09-18 21:35:35 +00:00
|
|
|
|
throw Exception("Double-distributed IN/JOIN subqueries is denied (distributed_product_mode = 'deny')."
|
|
|
|
|
" You may rewrite query to use local tables in subqueries, or use GLOBAL keyword, or set distributed_product_mode to suitable value.",
|
2015-09-18 13:36:10 +00:00
|
|
|
|
ErrorCodes::DISTRIBUTED_IN_JOIN_SUBQUERY_DENIED);
|
|
|
|
|
}
|
|
|
|
|
else if (settings.distributed_product_mode == DistributedProductMode::GLOBAL)
|
|
|
|
|
{
|
|
|
|
|
/// Согласно профиля пользователя распределённые подзапросы внутри секций IN и JOIN разрешены.
|
|
|
|
|
/// Преобразовать [NOT] IN в GLOBAL [NOT] IN, и JOIN в GLOBAL JOIN.
|
|
|
|
|
|
|
|
|
|
if (!is_global)
|
|
|
|
|
{
|
|
|
|
|
if (enclosing_in_or_join.attributes & IAST::IsJoin)
|
|
|
|
|
{
|
2016-07-22 20:39:28 +00:00
|
|
|
|
auto & join = static_cast<ASTTableJoin &>(enclosing_in_or_join);
|
|
|
|
|
join.locality = ASTTableJoin::Locality::Global;
|
2015-09-18 13:36:10 +00:00
|
|
|
|
}
|
|
|
|
|
else if (enclosing_in_or_join.attributes & (IAST::IsIn | IAST::IsNotIn))
|
|
|
|
|
{
|
|
|
|
|
auto & function = static_cast<ASTFunction &>(enclosing_in_or_join);
|
|
|
|
|
function.name = getNameFromInSubqueryAttributes(function.attributes | IAST::IsGlobal);
|
|
|
|
|
}
|
|
|
|
|
else
|
2015-09-18 21:35:35 +00:00
|
|
|
|
throw Exception("InJoinSubqueriesPreprocessor: Internal error", ErrorCodes::LOGICAL_ERROR);
|
2015-09-18 13:36:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (settings.distributed_product_mode == DistributedProductMode::LOCAL)
|
|
|
|
|
{
|
|
|
|
|
/// Согласно профиля пользователя распределённые подзапросы внутри секций IN и JOIN разрешены.
|
|
|
|
|
/// Преобразовать распределённую таблицу в соответствующую удалённую таблицу.
|
|
|
|
|
|
|
|
|
|
auto & distributed_storage = static_cast<TStorageDistributed &>(*subquery_table_storage);
|
2016-07-22 20:39:28 +00:00
|
|
|
|
sub_select_query.replaceDatabaseAndTable(
|
|
|
|
|
distributed_storage.getRemoteDatabaseName(),
|
|
|
|
|
distributed_storage.getRemoteTableName());
|
2015-09-18 13:36:10 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
2015-09-18 21:35:35 +00:00
|
|
|
|
throw Exception("InJoinSubqueriesPreprocessor: Internal error", ErrorCodes::LOGICAL_ERROR);
|
2015-09-18 13:36:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StoragePtr getDistributedSubqueryStorage(const ASTSelectQuery & sub_select_query) const
|
|
|
|
|
{
|
2016-07-22 20:39:28 +00:00
|
|
|
|
auto database = sub_select_query.database();
|
|
|
|
|
auto table = sub_select_query.table();
|
|
|
|
|
|
|
|
|
|
if (!table)
|
2015-09-18 13:36:10 +00:00
|
|
|
|
return {};
|
|
|
|
|
|
2016-07-22 20:39:28 +00:00
|
|
|
|
const auto identifier = typeid_cast<const ASTIdentifier *>(table.get());
|
2015-09-22 15:23:49 +00:00
|
|
|
|
if (identifier == nullptr)
|
2015-09-18 13:36:10 +00:00
|
|
|
|
return {};
|
|
|
|
|
|
2015-09-22 15:23:49 +00:00
|
|
|
|
const std::string & table_name = identifier->name;
|
2015-09-18 13:36:10 +00:00
|
|
|
|
|
|
|
|
|
std::string database_name;
|
2016-07-22 20:39:28 +00:00
|
|
|
|
if (database)
|
|
|
|
|
database_name = typeid_cast<const ASTIdentifier &>(*database).name;
|
2015-09-18 13:36:10 +00:00
|
|
|
|
else
|
2015-09-22 15:23:49 +00:00
|
|
|
|
database_name = "";
|
2015-09-18 13:36:10 +00:00
|
|
|
|
|
|
|
|
|
auto subquery_table_storage = context.tryGetTable(database_name, table_name);
|
|
|
|
|
if (!subquery_table_storage)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
if (!isEligibleStorageForInJoinPreprocessing<TStorageDistributed>(subquery_table_storage))
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
return subquery_table_storage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
ASTSelectQuery * select_query;
|
|
|
|
|
const Context & context;
|
|
|
|
|
const Settings & settings;
|
|
|
|
|
const StoragePtr & storage;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|