Check for non-deterministic functions in keys, including constant expressions

This commit is contained in:
Alexey Milovidov 2021-07-12 08:58:19 +03:00
parent 8f1d7e67ca
commit ff5c433f10
6 changed files with 59 additions and 18 deletions

View File

@ -113,7 +113,8 @@ public:
virtual ~IFunctionBase() = default;
virtual ColumnPtr execute(const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count, bool dry_run = false) const
virtual ColumnPtr execute(
const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, size_t input_rows_count, bool dry_run = false) const
{
return prepare(arguments)->execute(arguments, result_type, input_rows_count, dry_run);
}
@ -161,7 +162,8 @@ public:
* Arguments are passed without modifications, useDefaultImplementationForNulls, useDefaultImplementationForConstants,
* useDefaultImplementationForLowCardinality are not applied.
*/
virtual ColumnPtr getConstantResultForNonConstArguments(const ColumnsWithTypeAndName & /* arguments */, const DataTypePtr & /* result_type */) const { return nullptr; }
virtual ColumnPtr getConstantResultForNonConstArguments(
const ColumnsWithTypeAndName & /* arguments */, const DataTypePtr & /* result_type */) const { return nullptr; }
/** Function is called "injective" if it returns different result for different values of arguments.
* Example: hex, negate, tuple...

View File

@ -26,6 +26,7 @@ namespace ErrorCodes
extern const int THERE_IS_NO_COLUMN;
extern const int ILLEGAL_COLUMN;
extern const int NOT_FOUND_COLUMN_IN_BLOCK;
extern const int BAD_ARGUMENTS;
}
const char * ActionsDAG::typeToString(ActionsDAG::ActionType type)
@ -203,6 +204,14 @@ const ActionsDAG::Node & ActionsDAG::addFunction(
node.result_type = node.function_base->getResultType();
node.function = node.function_base->prepare(arguments);
std::cerr << node.function_base->getName() << ": " << node.function_base->isDeterministic() << "\n";
if (is_deterministic && !node.function_base->isDeterministic())
{
is_deterministic = false;
non_deterministic_function = node.function_base->getName();
}
/// If all arguments are constants, and function is suitable to be executed in 'prepare' stage - execute function.
if (node.function_base->isSuitableForConstantFolding())
{
@ -981,6 +990,25 @@ bool ActionsDAG::trivial() const
return true;
}
bool ActionsDAG::isDeterministic() const
{
std::cerr << "isDeterministic: " << is_deterministic << "\n";
/// We cannot calculate it on the fly as above because non-deterministic
/// but isDeterministicInScopeOfQuery/isSuitableForConstantFolding
/// functions can be already constant folded.
return is_deterministic;
}
void ActionsDAG::assertDeterministic() const
{
std::cerr << "isDeterministic: " << is_deterministic << "\n";
if (!is_deterministic)
throw Exception(ErrorCodes::BAD_ARGUMENTS,
"Expression must be deterministic but it contains non-deterministic function {}", non_deterministic_function);
}
void ActionsDAG::addMaterializingOutputActions()
{
for (auto & node : index)

View File

@ -103,6 +103,9 @@ private:
bool project_input = false;
bool projected_output = false;
bool is_deterministic = true;
String non_deterministic_function; /// For exception message.
public:
ActionsDAG() = default;
ActionsDAG(ActionsDAG &&) = default;
@ -175,6 +178,8 @@ public:
bool hasArrayJoin() const;
bool hasStatefulFunctions() const;
bool trivial() const; /// If actions has no functions or array join.
bool isDeterministic() const; /// All functions are 'isDeterministic'.
void assertDeterministic() const; /// Throw if not isDeterministic.
#if USE_EMBEDDED_COMPILER
void compileExpressions(size_t min_count_to_compile_expression);

View File

@ -531,11 +531,17 @@ Names ExpressionActions::getRequiredColumns() const
bool ExpressionActions::hasArrayJoin() const
{
for (const auto & action : actions)
if (action.node->type == ActionsDAG::ActionType::ARRAY_JOIN)
return true;
return getActionsDAG().hasArrayJoin();
}
return false;
bool ExpressionActions::isDeterministic() const
{
return getActionsDAG().isDeterministic();
}
void ExpressionActions::assertDeterministic() const
{
getActionsDAG().assertDeterministic();
}

View File

@ -103,6 +103,8 @@ public:
void execute(Block & block, bool dry_run = false) const;
bool hasArrayJoin() const;
bool isDeterministic() const;
void assertDeterministic() const;
/// Obtain a sample block that contains the names and types of result columns.
const Block & getSampleBlock() const { return sample_block; }

View File

@ -270,19 +270,17 @@ StoragePolicyPtr MergeTreeData::getStoragePolicy() const
static void checkKeyExpression(const ExpressionActions & expr, const Block & sample_block, const String & key_name, bool allow_nullable_key)
{
for (const auto & action : expr.getActions())
{
if (action.node->type == ActionsDAG::ActionType::ARRAY_JOIN)
throw Exception(key_name + " key cannot contain array joins", ErrorCodes::ILLEGAL_COLUMN);
if (expr.hasArrayJoin())
throw Exception(key_name + " key cannot contain array joins", ErrorCodes::ILLEGAL_COLUMN);
if (action.node->type == ActionsDAG::ActionType::FUNCTION)
{
IFunctionBase & func = *action.node->function_base;
if (!func.isDeterministic())
throw Exception(key_name + " key cannot contain non-deterministic functions, "
"but contains function " + func.getName(),
ErrorCodes::BAD_ARGUMENTS);
}
try
{
expr.assertDeterministic();
}
catch (Exception & e)
{
e.addMessage(fmt::format("for {} key", key_name));
throw;
}
for (const ColumnWithTypeAndName & element : sample_block)