risingwave_frontend/optimizer/plan_node/
stream_temporal_join.rs1use itertools::Itertools;
16use pretty_xmlish::{Pretty, XmlNode};
17use risingwave_common::util::sort_util::OrderType;
18use risingwave_pb::plan_common::JoinType;
19use risingwave_pb::stream_plan::TemporalJoinNode;
20use risingwave_pb::stream_plan::stream_node::NodeBody;
21use risingwave_sqlparser::ast::AsOf;
22
23use super::stream::prelude::*;
24use super::utils::{Distill, childless_record, watermark_pretty};
25use super::{ExprRewritable, PlanBase, PlanTreeNodeBinary, StreamPlanRef as PlanRef, generic};
26use crate::TableCatalog;
27use crate::expr::{Expr, ExprRewriter, ExprVisitor};
28use crate::optimizer::plan_node::expr_visitable::ExprVisitable;
29use crate::optimizer::plan_node::generic::GenericPlanNode;
30use crate::optimizer::plan_node::plan_tree_node::PlanTreeNodeUnary;
31use crate::optimizer::plan_node::utils::{IndicesDisplay, TableCatalogBuilder};
32use crate::optimizer::plan_node::{
33 EqJoinPredicate, EqJoinPredicateDisplay, StreamExchange, StreamTableScan, TryToStreamPb,
34};
35use crate::optimizer::property::Distribution;
36use crate::scheduler::SchedulerResult;
37use crate::stream_fragmenter::BuildFragmentGraphState;
38use crate::utils::ColIndexMappingRewriteExt;
39
40#[derive(Debug, Clone, PartialEq, Eq, Hash)]
41pub struct StreamTemporalJoin {
42 pub base: PlanBase<Stream>,
43 core: generic::Join<PlanRef>,
44 append_only: bool,
45 is_nested_loop: bool,
46 is_broadcast: bool,
47}
48
49impl StreamTemporalJoin {
50 pub fn new(core: generic::Join<PlanRef>, is_nested_loop: bool) -> Result<Self> {
51 core.on
52 .as_eq_predicate_ref()
53 .expect("StreamTemporalJoin requires JoinOn::EqPredicate in core");
54 assert!(core.join_type == JoinType::Inner || core.join_type == JoinType::LeftOuter);
55 let stream_kind = reject_upsert_input!(core.left);
57 let append_only = stream_kind.is_append_only();
58 assert!(!is_nested_loop || append_only);
59
60 let right = core.right.clone();
61 let exchange: &StreamExchange = right
62 .as_stream_exchange()
63 .expect("temporal join lookup side should have an exchange");
64 let exchange_input = exchange.input();
65 let scan: &StreamTableScan = exchange_input
66 .as_stream_table_scan()
67 .expect("should be a stream table scan");
68 let is_broadcast = matches!(scan.core().as_of, Some(AsOf::ProcessTimeBroadcast));
69 assert!(matches!(
70 scan.core().as_of,
71 Some(AsOf::ProcessTime | AsOf::ProcessTimeBroadcast)
72 ));
73 if is_broadcast {
74 assert!(!exchange.no_shuffle());
75 assert_eq!(exchange.distribution(), &Distribution::Broadcast);
76 assert!(!is_nested_loop);
77 } else {
78 assert!(exchange.no_shuffle());
79 }
80
81 let dist = if is_nested_loop {
82 let r2o = core.r2i_col_mapping().composite(&core.i2o_col_mapping());
84 r2o.rewrite_provided_distribution(core.right.distribution())
85 } else {
86 let l2o = core.l2i_col_mapping().composite(&core.i2o_col_mapping());
89 l2o.rewrite_provided_distribution(core.left.distribution())
90 };
91
92 let watermark_columns = core
94 .left
95 .watermark_columns()
96 .map_clone(&core.l2i_col_mapping())
97 .map_clone(&core.i2o_col_mapping());
98
99 let columns_monotonicity = core.i2o_col_mapping().rewrite_monotonicity_map(
100 &core
101 .l2i_col_mapping()
102 .rewrite_monotonicity_map(core.left.columns_monotonicity()),
103 );
104
105 let base = PlanBase::new_stream_with_core(
106 &core,
107 dist,
108 stream_kind,
109 false, watermark_columns,
111 columns_monotonicity,
112 );
113
114 Ok(Self {
115 base,
116 core,
117 append_only,
118 is_nested_loop,
119 is_broadcast,
120 })
121 }
122
123 pub fn join_type(&self) -> JoinType {
125 self.core.join_type
126 }
127
128 pub fn eq_join_predicate(&self) -> &EqJoinPredicate {
129 self.core
130 .on
131 .as_eq_predicate_ref()
132 .expect("StreamTemporalJoin should store predicate as EqJoinPredicate")
133 }
134
135 pub fn append_only(&self) -> bool {
136 self.append_only
137 }
138
139 pub fn is_nested_loop(&self) -> bool {
140 self.is_nested_loop
141 }
142
143 pub fn is_broadcast(&self) -> bool {
144 self.is_broadcast
145 }
146
147 pub fn infer_memo_table_catalog(&self, right_scan: &StreamTableScan) -> TableCatalog {
162 let left_eq_indexes = self.eq_join_predicate().left_eq_indexes();
163 let left_stream_key = self.core.left.expect_stream_key();
164 let memo_prefix_indices = left_eq_indexes
165 .iter()
166 .chain(left_stream_key)
167 .copied()
168 .collect_vec();
169 let read_prefix_len_hint = memo_prefix_indices.len();
170
171 let mut internal_table_catalog_builder = TableCatalogBuilder::default();
173 let right_scan_schema = right_scan.core().schema();
175 for field in right_scan_schema.fields() {
176 internal_table_catalog_builder.add_column(field);
177 }
178 for field in memo_prefix_indices
180 .iter()
181 .map(|idx| &self.core.left.schema().fields()[*idx])
182 {
183 internal_table_catalog_builder.add_column(field);
184 }
185
186 let mut pk_indices = vec![];
187 pk_indices
188 .extend(right_scan_schema.len()..(right_scan_schema.len() + read_prefix_len_hint));
189 pk_indices.extend(right_scan.stream_key().unwrap());
190
191 pk_indices.iter().for_each(|idx| {
192 internal_table_catalog_builder.add_order_column(*idx, OrderType::ascending())
193 });
194
195 let internal_table_dist_keys = if self.is_broadcast {
196 let left_dist_key = self
197 .core
198 .left
199 .distribution()
200 .dist_column_indices_opt()
201 .expect("broadcast temporal join left input must have a concrete distribution");
202 left_dist_key
203 .iter()
204 .map(|dist_idx| {
205 let position = left_stream_key
206 .iter()
207 .position(|idx| idx == dist_idx)
208 .expect("left distribution key must be part of the left stream key");
209 right_scan_schema.len() + left_eq_indexes.len() + position
210 })
211 .collect()
212 } else {
213 let dist_key_len = right_scan
214 .core()
215 .distribution_key()
216 .map(|keys| keys.len())
217 .unwrap_or(0);
218 (right_scan_schema.len()..(right_scan_schema.len() + dist_key_len)).collect()
219 };
220 internal_table_catalog_builder.build(internal_table_dist_keys, read_prefix_len_hint)
221 }
222}
223
224impl Distill for StreamTemporalJoin {
225 fn distill<'a>(&self) -> XmlNode<'a> {
226 let verbose = self.base.ctx().is_explain_verbose();
227 let mut vec = Vec::with_capacity(if verbose { 3 } else { 2 });
228 vec.push(("type", Pretty::debug(&self.core.join_type)));
229 vec.push(("append_only", Pretty::debug(&self.append_only)));
230
231 let concat_schema = self.core.concat_schema();
232 vec.push((
233 "predicate",
234 Pretty::debug(&EqJoinPredicateDisplay {
235 eq_join_predicate: self.eq_join_predicate(),
236 input_schema: &concat_schema,
237 }),
238 ));
239
240 vec.push(("nested_loop", Pretty::debug(&self.is_nested_loop)));
241 if self.is_broadcast {
242 vec.push(("broadcast", Pretty::debug(&self.is_broadcast)));
243 }
244
245 if let Some(ow) = watermark_pretty(self.base.watermark_columns(), self.schema()) {
246 vec.push(("output_watermarks", ow));
247 }
248
249 if verbose {
250 let data = IndicesDisplay::from_join(&self.core, &concat_schema);
251 vec.push(("output", data));
252 }
253
254 childless_record("StreamTemporalJoin", vec)
255 }
256}
257
258impl PlanTreeNodeBinary<Stream> for StreamTemporalJoin {
259 fn left(&self) -> PlanRef {
260 self.core.left.clone()
261 }
262
263 fn right(&self) -> PlanRef {
264 self.core.right.clone()
265 }
266
267 fn clone_with_left_right(&self, left: PlanRef, right: PlanRef) -> Self {
268 let mut core = self.core.clone();
269 core.left = left;
270 core.right = right;
271 Self::new(core, self.is_nested_loop).unwrap()
272 }
273}
274
275impl_plan_tree_node_for_binary! { Stream, StreamTemporalJoin }
276
277impl TryToStreamPb for StreamTemporalJoin {
278 fn try_to_stream_prost_body(
279 &self,
280 state: &mut BuildFragmentGraphState,
281 ) -> SchedulerResult<NodeBody> {
282 let left_jk_indices = self.eq_join_predicate().left_eq_indexes();
283 let right_jk_indices = self.eq_join_predicate().right_eq_indexes();
284 let left_jk_indices_prost = left_jk_indices.iter().map(|idx| *idx as i32).collect_vec();
285 let right_jk_indices_prost = right_jk_indices.iter().map(|idx| *idx as i32).collect_vec();
286
287 let null_safe_prost = self.eq_join_predicate().null_safes().into_iter().collect();
288
289 let right = self.right();
290 let exchange: &StreamExchange = right
291 .as_stream_exchange()
292 .expect("temporal join lookup side should have an exchange");
293 if self.is_broadcast {
294 assert!(!exchange.no_shuffle());
295 assert_eq!(exchange.distribution(), &Distribution::Broadcast);
296 } else {
297 assert!(exchange.no_shuffle());
298 }
299 let exchange_input = exchange.input();
300 let scan: &StreamTableScan = exchange_input
301 .as_stream_table_scan()
302 .expect("should be a stream table scan");
303
304 Ok(NodeBody::TemporalJoin(Box::new(TemporalJoinNode {
305 join_type: self.core.join_type as i32,
306 left_key: left_jk_indices_prost,
307 right_key: right_jk_indices_prost,
308 null_safe: null_safe_prost,
309 condition: self
310 .eq_join_predicate()
311 .other_cond()
312 .as_expr_unless_true()
313 .map(|expr| {
314 expr.to_expr_proto_checked_pure(
315 self.left().stream_kind().is_retract()
316 || self.right().stream_kind().is_retract(),
317 "JOIN condition",
318 )
319 })
320 .transpose()?,
321 output_indices: self.core.output_indices.iter().map(|&x| x as u32).collect(),
322 table_desc: Some(scan.core().table_catalog.table_desc().try_to_protobuf()?),
323 table_output_indices: scan.core().output_col_idx.iter().map(|&i| i as _).collect(),
324 memo_table: if self.append_only {
325 None
326 } else {
327 let mut memo_table = self.infer_memo_table_catalog(scan);
328 memo_table = memo_table.with_id(state.gen_table_id_wrapped());
329 Some(memo_table.to_internal_table_prost())
330 },
331 is_nested_loop: self.is_nested_loop,
332 is_broadcast: self.is_broadcast,
333 })))
334 }
335}
336
337impl ExprRewritable<Stream> for StreamTemporalJoin {
338 fn has_rewritable_expr(&self) -> bool {
339 true
340 }
341
342 fn rewrite_exprs(&self, r: &mut dyn ExprRewriter) -> PlanRef {
343 let mut core = self.core.clone();
344 core.rewrite_exprs(r);
345 Self::new(core, self.is_nested_loop).unwrap().into()
346 }
347}
348
349impl ExprVisitable for StreamTemporalJoin {
350 fn visit_exprs(&self, v: &mut dyn ExprVisitor) {
351 self.core.visit_exprs(v);
352 }
353}