Repository: ClickHouse/ClickHouse
File: src/Planner/PlannerJoinTree.cpp (C++)

Code before:
            till_stage = QueryProcessingStage::WithMergeableState;
        }
        else
        {
            if (!select_query_options.only_analyze)
            {
                auto & row_level_filter = table_expression_query_info.row_level_filter;
                auto & prewhere_info = table_expression_query_info.prewhere_info;
                const auto & prewhere_actions = table_expression_data.getPrewhereFilterActions();
                const auto & columns_names = table_expression_data.getColumnNames();

                std::vector<std::pair<FilterDAGInfo, DescriptionHolderPtr>> where_filters;

                if (prewhere_actions && select_query_options.build_logical_plan)
                {
                    /// Collect columns needed by row policy and additional filters
                    NameSet columns_needed_by_other_filters;

                    /// Pre-build row policy filter to know what columns it needs
                    auto row_policy_filter_info_temp
                        = buildRowPolicyFilterIfNeeded(storage, table_expression_query_info, planner_context, used_row_policies);
                    if (row_policy_filter_info_temp)
                    {
                        for (const auto * input : row_policy_filter_info_temp->actions.getInputs())
                            columns_needed_by_other_filters.insert(input->result_name);
                    }

                    /// Pre-build additional table filter to know what columns it needs
                    const auto & table_expression_alias = table_expression->getOriginalAlias();
                    auto additional_filters_info_temp = buildAdditionalFiltersIfNeeded(
                        storage, table_expression_alias, table_expression_query_info, prewhere_info, planner_context);
                    if (additional_filters_info_temp)
                    {
                        for (const auto * input : additional_filters_info_temp->actions.getInputs())
                            columns_needed_by_other_filters.insert(input->result_name);
                    }

                    /// Clone prewhere actions and add required columns to outputs
                    auto prewhere_actions_clone = prewhere_actions->clone();
                    const auto prewhere_column_name = prewhere_actions_clone.getOutputs().at(0)->result_name;

                    /// Add columns needed by other filters to prewhere outputs
                    std::unordered_set<const ActionsDAG::Node *> nodes_to_add;
                    for (const auto * input : prewhere_actions_clone.getInputs())
                    {
                        if (columns_needed_by_other_filters.contains(input->result_name))
                            nodes_to_add.insert(input);
                    }

                    /// Remove column from nodes_to_add if it's already in prewhere output
                    auto & prewhere_outputs = prewhere_actions_clone.getOutputs();

Review comment (suggestion):
I think the row policy filter should always be executed before the PREWHERE.
Shouldn't we use the opposite order? (e.g., add outputs to row policy if it's needed as a prewhere filter?)

Code after:
            till_stage = QueryProcessingStage::WithMergeableState;
        }
        else
        {
            if (!select_query_options.only_analyze)
            {
                auto & row_level_filter = table_expression_query_info.row_level_filter;
                auto & prewhere_info = table_expression_query_info.prewhere_info;
                const auto & prewhere_actions = table_expression_data.getPrewhereFilterActions();
                const auto & columns_names = table_expression_data.getColumnNames();

                std::vector<std::pair<FilterDAGInfo, DescriptionHolderPtr>> where_filters;

                if (prewhere_actions && select_query_options.build_logical_plan)
                {
                    /// Collect columns needed by row policy and additional filters
                    NameSet columns_needed_by_other_filters;

                    /// Pre-build additional table filter to know what columns it needs
                    const auto & table_expression_alias = table_expression->getOriginalAlias();
                    auto additional_filters_info_temp = buildAdditionalFiltersIfNeeded(
                        storage, table_expression_alias, table_expression_query_info, prewhere_info, planner_context);
                    if (additional_filters_info_temp)
                    {
                        for (const auto * input : additional_filters_info_temp->actions.getInputs())
                            columns_needed_by_other_filters.insert(input->result_name);
                    }

                    /// Clone prewhere actions and add required columns to outputs
                    auto prewhere_actions_clone = prewhere_actions->clone();
                    const auto prewhere_column_name = prewhere_actions_clone.getOutputs().at(0)->result_name;

                    /// Add columns needed by other filters to prewhere outputs
                    auto & prewhere_outputs = prewhere_actions_clone.getOutputs();

                    /// Build set of existing outputs for fast lookup
                    std::unordered_set<const ActionsDAG::Node *> existing_outputs(
                        prewhere_outputs.begin(), prewhere_outputs.end());

                    /// Iterate inputs in deterministic order and add missing nodes
                    for (const auto * input : prewhere_actions_clone.getInputs())
                    {
                        if (columns_needed_by_other_filters.contains(input->result_name)
                            && !existing_outputs.contains(input))
                        {
                            prewhere_outputs.push_back(input);
                        }
                    }

                    /// Check if prewhere filter column should be removed
                    const bool keep_for_query = std::ranges::contains(columns_names, prewhere_column_name);