risingwave_frontend/optimizer/rule/
apply_topn_transpose_rule.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use itertools::Itertools;
16use risingwave_pb::plan_common::JoinType;
17
18use super::prelude::{PlanRef, *};
19use crate::optimizer::plan_node::{LogicalApply, LogicalFilter, LogicalTopN};
20use crate::utils::Condition;
21
22/// Transpose `LogicalApply` and `LogicalTopN`.
23///
24/// Before:
25///
26/// ```text
27///     LogicalApply
28///    /            \
29///  Domain      LogicalTopN
30///                  |
31///                Input
32/// ```
33///
34/// After:
35///
36/// ```text
37///      LogicalTopN
38///          |
39///     LogicalApply
40///    /            \
41///  Domain        Input
42/// ```
43pub struct ApplyTopNTransposeRule {}
44impl Rule<Logical> for ApplyTopNTransposeRule {
45    fn apply(&self, plan: PlanRef) -> Option<PlanRef> {
46        let apply: &LogicalApply = plan.as_logical_apply()?;
47        let (left, right, on, join_type, correlated_id, correlated_indices, max_one_row) =
48            apply.clone().decompose();
49        assert_eq!(join_type, JoinType::Inner);
50        let topn: &LogicalTopN = right.as_logical_top_n()?;
51        let (topn_input, limit, offset, with_ties, mut order, mut group_key) =
52            topn.clone().decompose();
53
54        let apply_left_len = left.schema().len();
55
56        if max_one_row {
57            return None;
58        }
59
60        let new_apply = LogicalApply::create(
61            left,
62            topn_input,
63            JoinType::Inner,
64            Condition::true_cond(),
65            correlated_id,
66            correlated_indices,
67            false,
68        );
69
70        let new_topn = {
71            // shift index of topn's `InputRef` with `apply_left_len`.
72            order
73                .column_orders
74                .iter_mut()
75                .for_each(|ord| ord.column_index += apply_left_len);
76            group_key.iter_mut().for_each(|idx| *idx += apply_left_len);
77            let new_group_key = (0..apply_left_len).chain(group_key).collect_vec();
78            LogicalTopN::new(new_apply, limit, offset, with_ties, order, new_group_key)
79        };
80
81        let filter = LogicalFilter::create(new_topn.into(), on);
82        Some(filter)
83    }
84}
85
86impl ApplyTopNTransposeRule {
87    pub fn create() -> BoxedRule {
88        Box::new(ApplyTopNTransposeRule {})
89    }
90}