risingwave_stream/from_proto/
match_recognize.rs1use risingwave_common::types::DataType;
16use risingwave_common::util::sort_util::{ColumnOrder, OrderType};
17use risingwave_expr::expr::build_non_strict_from_prost;
18use risingwave_pb::stream_plan::{MatchRecognizeInputMode, MatchRecognizeNode};
19use risingwave_storage::StateStore;
20
21use super::ExecutorBuilder;
22use crate::common::table::state_table::StateTableBuilder;
23use crate::error::StreamResult;
24use crate::executor::Executor;
25use crate::executor::match_recognize::executor::{
26 CompiledDefine, CompiledMeasure, DeadlineErrorReport, MatchRecognizeExecutor,
27 MatchRecognizeExecutorArgs,
28};
29use crate::executor::match_recognize::nfa::{Nfa, SkipMode};
30use crate::executor::match_recognize::proto::pattern_from_protobuf;
31use crate::task::ExecutorParams;
32
33pub struct MatchRecognizeExecutorBuilder;
34
35impl_stream_node_body!(MatchRecognize(MatchRecognizeNode) => MatchRecognizeExecutorBuilder);
36
37impl ExecutorBuilder for MatchRecognizeExecutorBuilder {
38 type Node = MatchRecognizeNode;
39
40 async fn new_boxed_executor(
41 params: ExecutorParams,
42 node: &MatchRecognizeNode,
43 store: impl StateStore,
44 ) -> StreamResult<Executor> {
45 let [input]: [_; 1] = params.input.try_into().unwrap();
46
47 if node.input_mode != 0 && node.input_mode() == MatchRecognizeInputMode::Unspecified {
56 return Err(
57 anyhow::anyhow!("unknown MATCH_RECOGNIZE input mode: {}", node.input_mode).into(),
58 );
59 }
60 match node.input_mode() {
61 MatchRecognizeInputMode::Unspecified | MatchRecognizeInputMode::EventTime => {}
62 other => {
63 return Err(
64 anyhow::anyhow!("unsupported MATCH_RECOGNIZE input mode: {other:?}").into(),
65 );
66 }
67 }
68
69 let partition_key_indices = node.partition_by.iter().map(|&i| i as usize).collect();
70 let order_key_indices = node
74 .order_by
75 .iter()
76 .map(|c| {
77 let co = ColumnOrder::from_protobuf(c);
78 if co.order_type != OrderType::ascending() {
79 return Err(anyhow::anyhow!(
80 "MATCH_RECOGNIZE only supports the default ascending ORDER BY, got {:?}",
81 co.order_type
82 )
83 .into());
84 }
85 Ok(co.column_index)
86 })
87 .collect::<StreamResult<Vec<usize>>>()?;
88 if order_key_indices.is_empty() {
91 return Err(anyhow::anyhow!("MATCH_RECOGNIZE plan carries an empty ORDER BY").into());
92 }
93
94 let defines = node
95 .defines
96 .iter()
97 .map(|d| CompiledDefine::from_protobuf(d, params.eval_error_report.clone()))
98 .collect::<crate::executor::StreamExecutorResult<Vec<_>>>()?;
99 let measures = node
100 .measures
101 .iter()
102 .map(|m| CompiledMeasure::from_protobuf(m, params.eval_error_report.clone()))
103 .collect::<crate::executor::StreamExecutorResult<Vec<_>>>()?;
104
105 let pattern_node = node
106 .pattern_node
107 .as_ref()
108 .ok_or_else(|| anyhow::anyhow!("MATCH_RECOGNIZE node missing pattern"))?;
109 let pattern = pattern_from_protobuf(pattern_node)
110 .map_err(|e| anyhow::anyhow!("invalid MATCH_RECOGNIZE pattern: {e}"))?;
111 let nfa = Nfa::compile(&pattern);
112
113 let skip = {
116 use risingwave_pb::stream_plan::match_recognize_after_match_skip::Mode;
117 let pb_skip = node
118 .after_match_skip
119 .as_ref()
120 .ok_or_else(|| anyhow::anyhow!("MATCH_RECOGNIZE node missing after_match_skip"))?;
121 let target = || {
122 pb_skip.target.clone().ok_or_else(|| {
123 anyhow::anyhow!("AFTER MATCH SKIP TO FIRST/LAST missing its target variable")
124 })
125 };
126 match pb_skip.mode() {
127 Mode::PastLastRow => SkipMode::PastLastRow,
128 Mode::ToNextRow => SkipMode::ToNextRow,
129 Mode::ToFirst => SkipMode::ToFirst(target()?),
130 Mode::ToLast => SkipMode::ToLast(target()?),
131 Mode::Unspecified => {
132 return Err(anyhow::anyhow!(
133 "invalid MATCH_RECOGNIZE after_match_skip mode: {}",
134 pb_skip.mode
135 )
136 .into());
137 }
138 }
139 };
140
141 let within = node
142 .within
143 .as_ref()
144 .map(|e| build_non_strict_from_prost(e, params.eval_error_report.clone()))
145 .transpose()?;
146 let within_deadline = node
150 .within_deadline
151 .as_ref()
152 .map(|e| {
153 build_non_strict_from_prost(
154 e,
155 DeadlineErrorReport::new(params.eval_error_report.clone()),
156 )
157 })
158 .transpose()?;
159 if within.is_some() != within_deadline.is_some() {
166 return Err(anyhow::anyhow!(
167 "MATCH_RECOGNIZE carries only one of the two WITHIN expressions \
168 (predicate: {}, deadline: {}); the binder emits both or neither",
169 within.is_some(),
170 within_deadline.is_some(),
171 )
172 .into());
173 }
174 let order_key_type = input
179 .schema()
180 .fields
181 .get(order_key_indices[0])
182 .map(|f| f.data_type())
183 .ok_or_else(|| {
184 anyhow::anyhow!(
185 "MATCH_RECOGNIZE ORDER BY column {} is out of range for an input of {} columns",
186 order_key_indices[0],
187 input.schema().len()
188 )
189 })?;
190 if let Some(deadline) = &within_deadline
191 && deadline.return_type() != order_key_type
192 {
193 return Err(anyhow::anyhow!(
194 "MATCH_RECOGNIZE WITHIN deadline has type {} but the ORDER BY column has type {}; \
195 the two are compared directly",
196 deadline.return_type(),
197 order_key_type,
198 )
199 .into());
200 }
201 if let Some(predicate) = &within
202 && predicate.return_type() != DataType::Boolean
203 {
204 return Err(anyhow::anyhow!(
205 "MATCH_RECOGNIZE WITHIN span predicate has type {}, expected boolean",
206 predicate.return_type(),
207 )
208 .into());
209 }
210
211 let vnode_bitmap = params.vnode_bitmap.clone().map(std::sync::Arc::new);
212 let state_table_catalog = node
213 .state_table
214 .as_ref()
215 .ok_or_else(|| anyhow::anyhow!("MATCH_RECOGNIZE node missing its state table"))?;
216 let state_table =
217 StateTableBuilder::new(state_table_catalog, store.clone(), vnode_bitmap.clone())
218 .forbid_preload_all_rows()
219 .build()
220 .await;
221 let exec = MatchRecognizeExecutor::new(MatchRecognizeExecutorArgs {
222 ctx: params.actor_context,
223 input,
224 schema: params.info.schema.clone(),
225 chunk_size: params.config.developer.chunk_size,
226 partition_key_indices,
227 order_key_indices,
228 measures,
229 defines,
230 within,
231 nfa,
232 skip,
233 eval_error_report: params.eval_error_report,
234 within_deadline,
235 state_table,
236 });
237
238 Ok((params.info, exec).into())
239 }
240}