fix check for nondeterministic mutations

This commit is contained in:
Alexander Tokmakov 2021-10-07 15:04:54 +03:00
parent 5365cc686f
commit afd69ef833
3 changed files with 44 additions and 23 deletions

View File

@ -54,24 +54,33 @@ public:
{
ContextPtr context;
std::optional<String> nondeterministic_function_name;
bool subquery = false;
};
static bool needChildVisit(const ASTPtr & /*node*/, const ASTPtr & child)
static bool needChildVisit(const ASTPtr & /*node*/, const ASTPtr & /*child*/)
{
return child != nullptr;
return true;
}
static void visit(const ASTPtr & node, Data & data)
{
if (data.nondeterministic_function_name)
if (data.nondeterministic_function_name || data.subquery)
return;
if (const auto * function = typeid_cast<const ASTFunction *>(node.get()))
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.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.nondeterministic_function_name = func->getName();
@ -81,10 +90,11 @@ public:
};
using FirstNonDeterministicFunctionFinder = InDepthNodeVisitor<FirstNonDeterministicFunctionMatcher, true>;
using FirstNonDeterministicFunctionData = FirstNonDeterministicFunctionMatcher::Data;
std::optional<String> findFirstNonDeterministicFunctionName(const MutationCommand & command, ContextPtr context)
FirstNonDeterministicFunctionData findFirstNonDeterministicFunctionName(const MutationCommand & command, ContextPtr context)
{
FirstNonDeterministicFunctionMatcher::Data finder_data{context, std::nullopt};
FirstNonDeterministicFunctionMatcher::Data finder_data{context, std::nullopt, false};
switch (command.type)
{
@ -94,7 +104,7 @@ std::optional<String> findFirstNonDeterministicFunctionName(const MutationComman
FirstNonDeterministicFunctionFinder(finder_data).visit(update_assignments_ast);
if (finder_data.nondeterministic_function_name)
return finder_data.nondeterministic_function_name;
return finder_data;
/// Currently UPDATE and DELETE both always have predicates so we can use fallthrough
[[fallthrough]];
@ -105,7 +115,7 @@ std::optional<String> findFirstNonDeterministicFunctionName(const MutationComman
auto predicate_ast = command.predicate->clone();
FirstNonDeterministicFunctionFinder(finder_data).visit(predicate_ast);
return finder_data.nondeterministic_function_name;
return finder_data;
}
default:
@ -918,12 +928,15 @@ void MutationsInterpreter::validate()
{
for (const auto & command : commands)
{
const auto nondeterministic_func_name = findFirstNonDeterministicFunctionName(command, context);
if (nondeterministic_func_name)
throw Exception(
"ALTER UPDATE/ALTER DELETE statements must use only deterministic functions! "
"Function '" + *nondeterministic_func_name + "' is non-deterministic",
ErrorCodes::BAD_ARGUMENTS);
const auto nondeterministic_func_data = findFirstNonDeterministicFunctionName(command, context);
if (nondeterministic_func_data.subquery)
throw Exception(ErrorCodes::BAD_ARGUMENTS, "ALTER UPDATE/ALTER DELETE statement with subquery may be nondeterministic, "
"see allow_nondeterministic_mutations setting");
if (nondeterministic_func_data.nondeterministic_function_name)
throw Exception(ErrorCodes::BAD_ARGUMENTS,
"ALTER UPDATE/ALTER DELETE statements must use only deterministic functions. "
"Function '{}' is non-deterministic", *nondeterministic_func_data.nondeterministic_function_name);
}
}

View File

@ -4,7 +4,6 @@ MergeTree
2
0
50 6225 0
0
50 6225 1900
ReplicatedMergeTree
1
@ -12,15 +11,13 @@ ReplicatedMergeTree
2
0
50 6225 0
2
50 6225 0
50 6225 1900
Memory
1
2
2
0
50 6225 0
0
50 6225 1900
Join
1
@ -28,5 +25,4 @@ Join
2
0
50 6225 0
0
50 6225 0

View File

@ -16,17 +16,29 @@ do
$CLICKHOUSE_CLIENT -q "insert into t values (1)"
$CLICKHOUSE_CLIENT -q "insert into t values (2)"
$CLICKHOUSE_CLIENT -q "select * from t order by n"
$CLICKHOUSE_CLIENT --mutations_sync=1 -q "alter table t delete where n global in (select * from (select * from t where n global in (1::Int32)))"
$CLICKHOUSE_CLIENT --allow_nondeterministic_mutations=1 --mutations_sync=1 -q "alter table t
delete where n global in (select * from (select * from t where n global in (1::Int32)))"
$CLICKHOUSE_CLIENT -q "select * from t order by n"
$CLICKHOUSE_CLIENT --mutations_sync=1 -q "alter table t delete where n global in (select t1.n from t as t1 full join t as t2 on t1.n=t2.n where t1.n global in (select 2::Int32))"
$CLICKHOUSE_CLIENT --allow_nondeterministic_mutations=1 --mutations_sync=1 -q "alter table t
delete where n global in (select t1.n from t as t1 full join t as t2 on t1.n=t2.n where t1.n global in (select 2::Int32))"
$CLICKHOUSE_CLIENT -q "select count() from t"
$CLICKHOUSE_CLIENT -q "drop table t"
$CLICKHOUSE_CLIENT -q "drop table if exists test"
$CLICKHOUSE_CLIENT -q "CREATE TABLE test ENGINE=$engine AS SELECT number + 100 AS n, 0 AS test FROM numbers(50)"
$CLICKHOUSE_CLIENT -q "select count(), sum(n), sum(test) from test"
# FIXME it's not clear if the following query should fail or not
$CLICKHOUSE_CLIENT --mutations_sync=1 -q "ALTER TABLE test UPDATE test = (SELECT groupArray(id) FROM t1 GROUP BY 1)[n - 99] WHERE 1" 2>&1| grep -c "Unknown function"
if [[ $engine == *"ReplicatedMergeTree"* ]]; then
$CLICKHOUSE_CLIENT -q "ALTER TABLE test
UPDATE test = (SELECT groupArray(id) FROM t1 GROUP BY 1)[n - 99] WHERE 1" 2>&1| grep -Fa "DB::Exception: " | grep -Fv "statement with subquery may be nondeterministic"
$CLICKHOUSE_CLIENT --allow_nondeterministic_mutations=1 --mutations_sync=1 -q "ALTER TABLE test
UPDATE test = (SELECT groupArray(id) FROM t1 GROUP BY 1)[n - 99] WHERE 1"
elif [[ $engine == *"Join"* ]]; then
$CLICKHOUSE_CLIENT -q "ALTER TABLE test
UPDATE test = (SELECT groupArray(id) FROM t1 GROUP BY 1)[n - 99] WHERE 1" 2>&1| grep -Fa "DB::Exception: " | grep -Fv "Table engine Join supports only DELETE mutations"
else
$CLICKHOUSE_CLIENT --mutations_sync=1 -q "ALTER TABLE test
UPDATE test = (SELECT groupArray(id) FROM t1 GROUP BY 1)[n - 99] WHERE 1"
fi
$CLICKHOUSE_CLIENT -q "select count(), sum(n), sum(test) from test"
$CLICKHOUSE_CLIENT -q "drop table test"
done