mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-12-11 17:02:25 +00:00
101 lines
3.3 KiB
C++
101 lines
3.3 KiB
C++
#include "Parsers/IAST_fwd.h"
|
|
#include <Interpreters/MutationsNonDeterministicHelpers.h>
|
|
#include <Parsers/ASTSelectQuery.h>
|
|
#include <Parsers/ASTFunction.h>
|
|
#include <Parsers/ASTLiteral.h>
|
|
#include <Parsers/ASTAlterQuery.h>
|
|
#include <Storages/MutationCommands.h>
|
|
#include <Interpreters/InDepthNodeVisitor.h>
|
|
#include <Interpreters/evaluateConstantExpression.h>
|
|
#include <Interpreters/ExecuteScalarSubqueriesVisitor.h>
|
|
#include <Interpreters/addTypeConversionToAST.h>
|
|
#include <Interpreters/Context.h>
|
|
#include <Functions/FunctionFactory.h>
|
|
|
|
namespace DB
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
/// Helps to detect situations, where non-deterministic functions may be used in mutations.
|
|
class FirstNonDeterministicFunctionMatcher
|
|
{
|
|
public:
|
|
struct Data
|
|
{
|
|
ContextPtr context;
|
|
FirstNonDeterministicFunctionResult result;
|
|
};
|
|
|
|
static bool needChildVisit(const ASTPtr & /*node*/, const ASTPtr & /*child*/)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void visit(const ASTPtr & node, Data & data)
|
|
{
|
|
if (data.result.nondeterministic_function_name || data.result.subquery)
|
|
return;
|
|
|
|
if (node->as<ASTSelectQuery>())
|
|
{
|
|
/// We cannot determine if subquery is deterministic or not,
|
|
/// so we do not allow to use subqueries in mutation without allow_nondeterministic_mutations=1
|
|
data.result.subquery = true;
|
|
}
|
|
else if (const auto * function = typeid_cast<const ASTFunction *>(node.get()))
|
|
{
|
|
/// Property of being deterministic for lambda expression is completely determined
|
|
/// by the contents of its definition, so we just proceed to it.
|
|
if (function->name != "lambda")
|
|
{
|
|
/// NOTE It may be an aggregate function, so get(...) may throw.
|
|
/// However, an aggregate function can be used only in subquery and we do not go into subquery.
|
|
const auto func = FunctionFactory::instance().get(function->name, data.context);
|
|
if (!func->isDeterministic())
|
|
data.result.nondeterministic_function_name = func->getName();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
using FirstNonDeterministicFunctionFinder = InDepthNodeVisitor<FirstNonDeterministicFunctionMatcher, true>;
|
|
using FirstNonDeterministicFunctionData = FirstNonDeterministicFunctionMatcher::Data;
|
|
|
|
}
|
|
|
|
FirstNonDeterministicFunctionResult findFirstNonDeterministicFunction(const MutationCommand & command, ContextPtr context)
|
|
{
|
|
FirstNonDeterministicFunctionMatcher::Data finder_data{context, {}};
|
|
|
|
switch (command.type)
|
|
{
|
|
case MutationCommand::UPDATE:
|
|
{
|
|
auto update_assignments_ast = command.ast->as<const ASTAlterCommand &>().update_assignments->clone();
|
|
FirstNonDeterministicFunctionFinder(finder_data).visit(update_assignments_ast);
|
|
|
|
if (finder_data.result.nondeterministic_function_name)
|
|
return finder_data.result;
|
|
|
|
/// Currently UPDATE and DELETE both always have predicates so we can use fallthrough
|
|
[[fallthrough]];
|
|
}
|
|
|
|
case MutationCommand::DELETE:
|
|
{
|
|
auto predicate_ast = command.predicate->clone();
|
|
FirstNonDeterministicFunctionFinder(finder_data).visit(predicate_ast);
|
|
return finder_data.result;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
}
|