risingwave_frontend/optimizer/plan_node/generic/
match_recognize.rs1use pretty_xmlish::{Pretty, Str, XmlNode};
16use risingwave_common::catalog::{Field, Schema};
17use risingwave_common::types::DataType;
18use risingwave_sqlparser::ast::{AfterMatchSkip, MatchRecognizePattern, RowsPerMatch};
19
20use super::{DistillUnit, GenericPlanNode, GenericPlanRef};
21use crate::OptimizerContextRef;
22use crate::binder::{BoundMeasure, BoundSymbolDefinition, MeasureSlotKind};
23use crate::expr::{Expr, ExprDisplay, ExprImpl, ExprRewriter, ExprVisitor};
24use crate::optimizer::plan_node::ColIndexMapping;
25use crate::optimizer::plan_node::utils::childless_record;
26use crate::optimizer::property::FunctionalDependencySet;
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32pub struct MatchRecognize<PlanRef> {
33 pub input: PlanRef,
34 pub partition_by: Vec<ExprImpl>,
35 pub order_by: Vec<ExprImpl>,
36 pub measures: Vec<BoundMeasure>,
37 pub rows_per_match: Option<RowsPerMatch>,
38 pub after_match_skip: Option<AfterMatchSkip>,
39 pub pattern: MatchRecognizePattern,
40 pub defines: Vec<BoundSymbolDefinition>,
41 pub within: Option<ExprImpl>,
43 pub within_deadline: Option<ExprImpl>,
48}
49
50impl<PlanRef: GenericPlanRef> GenericPlanNode for MatchRecognize<PlanRef> {
51 fn schema(&self) -> Schema {
52 let mut fields = Vec::with_capacity(self.partition_by.len() + self.measures.len() + 1);
53 for (i, e) in self.partition_by.iter().enumerate() {
54 fields.push(Field::with_name(e.return_type(), format!("partition_{i}")));
55 }
56 for m in &self.measures {
57 fields.push(Field::with_name(m.expr.return_type(), m.name.clone()));
58 }
59 fields.push(Field::with_name(DataType::Int64, "_match_id"));
61 Schema::new(fields)
62 }
63
64 fn functional_dependency(&self) -> FunctionalDependencySet {
65 FunctionalDependencySet::new(self.partition_by.len() + self.measures.len() + 1)
66 }
67
68 fn stream_key(&self) -> Option<Vec<usize>> {
69 let match_id = self.partition_by.len() + self.measures.len();
75 let mut key: Vec<usize> = (0..self.partition_by.len()).collect();
76 key.push(match_id);
77 Some(key)
78 }
79
80 fn ctx(&self) -> OptimizerContextRef {
81 self.input.ctx()
82 }
83}
84
85impl<PlanRef: GenericPlanRef> crate::optimizer::plan_node::expr_visitable::ExprVisitable
86 for MatchRecognize<PlanRef>
87{
88 fn visit_exprs(&self, v: &mut dyn ExprVisitor) {
89 self.partition_by.iter().for_each(|e| v.visit_expr(e));
90 self.order_by.iter().for_each(|e| v.visit_expr(e));
91 self.measures.iter().for_each(|m| v.visit_expr(&m.expr));
92 self.defines
93 .iter()
94 .for_each(|d| v.visit_expr(&d.definition));
95 if let Some(within) = &self.within {
96 v.visit_expr(within);
97 }
98 if let Some(within_deadline) = &self.within_deadline {
99 v.visit_expr(within_deadline);
100 }
101 }
102}
103
104impl<PlanRef> MatchRecognize<PlanRef> {
105 pub fn partition_key_indices(&self) -> Option<Vec<usize>> {
107 self.partition_by
108 .iter()
109 .map(|e| e.as_input_ref().map(|r| r.index()))
110 .collect()
111 }
112
113 pub fn order_key_indices(&self) -> Option<Vec<usize>> {
115 self.order_by
116 .iter()
117 .map(|e| e.as_input_ref().map(|r| r.index()))
118 .collect()
119 }
120
121 pub fn rewrite_exprs(&mut self, r: &mut dyn ExprRewriter) {
122 self.partition_by
123 .iter_mut()
124 .for_each(|e| *e = r.rewrite_expr(e.clone()));
125 self.order_by
126 .iter_mut()
127 .for_each(|e| *e = r.rewrite_expr(e.clone()));
128 self.measures
129 .iter_mut()
130 .for_each(|m| m.expr = r.rewrite_expr(m.expr.clone()));
131 self.defines
132 .iter_mut()
133 .for_each(|d| d.definition = r.rewrite_expr(d.definition.clone()));
134 if let Some(within) = &mut self.within {
135 *within = r.rewrite_expr(within.clone());
136 }
137 if let Some(within_deadline) = &mut self.within_deadline {
138 *within_deadline = r.rewrite_expr(within_deadline.clone());
139 }
140 }
141
142 pub fn rewrite_with_col_index_mapping(&mut self, mapping: &mut ColIndexMapping) {
143 self.partition_by
144 .iter_mut()
145 .for_each(|e| *e = mapping.rewrite_expr(e.clone()));
146 self.order_by
147 .iter_mut()
148 .for_each(|e| *e = mapping.rewrite_expr(e.clone()));
149 for m in &mut self.measures {
152 for slot in &mut m.slots {
153 if !matches!(slot.kind, MeasureSlotKind::Classifier) {
154 slot.col_idx = mapping.map(slot.col_idx);
155 }
156 }
157 }
158 for d in &mut self.defines {
159 for slot in &mut d.slots {
160 slot.col_idx = mapping.map(slot.col_idx);
161 }
162 }
163 }
164}
165
166impl<PlanRef: GenericPlanRef> DistillUnit for MatchRecognize<PlanRef> {
167 fn distill_with_name<'a>(&self, name: impl Into<Str<'a>>) -> XmlNode<'a> {
168 let input_schema = self.input.schema();
170 let exprs = |es: &[ExprImpl]| {
171 Pretty::Array(
172 es.iter()
173 .map(|e| {
174 Pretty::display(&ExprDisplay {
175 expr: e,
176 input_schema,
177 })
178 })
179 .collect(),
180 )
181 };
182 let measure_names: Vec<_> = self.measures.iter().map(|m| m.name.as_str()).collect();
183 let fields = vec![
184 ("partition_by", exprs(&self.partition_by)),
185 ("order_by", exprs(&self.order_by)),
186 ("measures", Pretty::debug(&measure_names)),
187 ("pattern", Pretty::display(&self.pattern)),
188 ];
189 childless_record(name, fields)
190 }
191}