diff --git a/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp b/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp index e136440556f..a32ab800dae 100644 --- a/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp +++ b/src/Analyzer/Passes/LogicalExpressionOptimizerPass.cpp @@ -179,6 +179,26 @@ public: , join_node(join_node_) {} + bool needChildVisit(const QueryTreeNodePtr & parent, const QueryTreeNodePtr &) + { + /** Optimization can change the value of some expression from NULL to FALSE. + * For example: + * when `a` is `NULL`, the expression `a = b AND a IS NOT NULL` returns `NULL` + * and it will be optimized to `a = b`, which returns `FALSE`. + * This is valid for JOIN ON condition and for the functions `AND`/`OR` inside it. + * (When we replace `AND`/`OR` operands from `NULL` to `FALSE`, the result value can also change only from `NULL` to `FALSE`) + * However, in the general case, the result can be wrong. + * For example, for NOT: `NOT NULL` is `NULL`, but `NOT FALSE` is `TRUE`. + * Therefore, optimize only top-level expression or expressions inside `AND`/`OR`. + */ + if (const auto * function_node = parent->as()) + { + const auto & func_name = function_node->getFunctionName(); + return func_name == "or" || func_name == "and"; + } + return parent->getNodeType() == QueryTreeNodeType::LIST; + } + void enterImpl(QueryTreeNodePtr & node) { auto * function_node = node->as(); diff --git a/tests/queries/0_stateless/02911_join_on_nullsafe_optimization.reference b/tests/queries/0_stateless/02911_join_on_nullsafe_optimization.reference index 352e02941c3..2d8eff88ef3 100644 --- a/tests/queries/0_stateless/02911_join_on_nullsafe_optimization.reference +++ b/tests/queries/0_stateless/02911_join_on_nullsafe_optimization.reference @@ -64,3 +64,4 @@ SELECT * FROM t1n as t1 JOIN t2n as t2 ON (t1.x == t2.x AND ((t2.x IS NOT NULL) 0 2 2 +1 diff --git a/tests/queries/0_stateless/02911_join_on_nullsafe_optimization.sql b/tests/queries/0_stateless/02911_join_on_nullsafe_optimization.sql index e1e095a6eae..9189b3f4ab9 100644 --- a/tests/queries/0_stateless/02911_join_on_nullsafe_optimization.sql +++ b/tests/queries/0_stateless/02911_join_on_nullsafe_optimization.sql @@ -67,6 +67,10 @@ SELECT count() FROM ( EXPLAIN QUERY TREE SELECT * FROM t1 JOIN t2 ON t1.x <=> t2.x AND (t1.x = t1.y OR t1.x IS NULL AND t1.y IS NULL) ) WHERE explain like '%CONSTANT%' OR explain ilike '%is%null%'; +SELECT count() FROM ( EXPLAIN QUERY TREE + SELECT * FROM t1 JOIN t2 ON t1.x = t2.x AND NOT (t1.x = 1 OR t1.x IS NULL) +) WHERE explain ilike '%function_name: isNull%'; + DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; DROP TABLE IF EXISTS t1n;