| 563 | } |
| 564 | |
| 565 | FilterDAGInfo buildFilterInfo(ASTPtr filter_expression, |
| 566 | const QueryTreeNodePtr & table_expression, |
| 567 | PlannerContextPtr & planner_context, |
| 568 | NameSet table_expression_required_names_without_filter) |
| 569 | { |
| 570 | const auto & query_context = planner_context->getQueryContext(); |
| 571 | |
| 572 | /// If the filter expression is a standalone subquery (e.g. ROW POLICY |
| 573 | /// USING (SELECT 1)), wrap it with notEquals(<subquery>, 0) so that |
| 574 | /// buildQueryTree produces a FunctionNode at the top level instead of |
| 575 | /// a bare QueryNode/UnionNode. QueryAnalyzer::resolve() requires |
| 576 | /// table_expression to be empty for top-level QUERY/UNION nodes; |
| 577 | /// wrapping turns the subquery into a function argument that is |
| 578 | /// resolved through the normal scalar-subquery evaluation path. |
| 579 | /// We use notEquals(x, 0) rather than equals(1, x) to preserve |
| 580 | /// boolean semantics: any non-zero value is truthy (e.g. USING (SELECT 2) |
| 581 | /// should allow rows, not deny them). |
| 582 | if (filter_expression->as<ASTSubquery>() |
| 583 | || filter_expression->as<ASTSelectWithUnionQuery>()) |
| 584 | { |
| 585 | filter_expression = makeASTFunction("notEquals", |
| 586 | filter_expression, |
| 587 | make_intrusive<ASTLiteral>(Field(UInt8(0)))); |
| 588 | } |
| 589 | |
| 590 | auto filter_query_tree = buildQueryTree(filter_expression, query_context); |
| 591 | |
| 592 | QueryAnalysisPass query_analysis_pass(table_expression); |
| 593 | query_analysis_pass.run(filter_query_tree, query_context); |
| 594 | |
| 595 | /// Optimize logical expressions in the filter, e.g. convert OR-chains of |
| 596 | /// equalities into IN (important for row policies that produce many |
| 597 | /// permissive conditions like `x = 1 OR x = 2 OR ... OR x = N`). |
| 598 | LogicalExpressionOptimizerPass logical_expression_optimizer_pass; |
| 599 | logical_expression_optimizer_pass.run(filter_query_tree, query_context); |
| 600 | |
| 601 | return buildFilterInfo(std::move(filter_query_tree), table_expression, planner_context, std::move(table_expression_required_names_without_filter)); |
| 602 | } |
| 603 | |
| 604 | FilterDAGInfo buildFilterInfo(QueryTreeNodePtr filter_query_tree, |
| 605 | const QueryTreeNodePtr & table_expression, |
no test coverage detected