risingwave_frontend/optimizer/plan_node/
stream_table_scan.rs1use std::collections::{BTreeMap, HashMap};
16use std::sync::Arc;
17
18use itertools::Itertools;
19use pretty_xmlish::{Pretty, XmlNode};
20use risingwave_common::catalog::Field;
21use risingwave_common::hash::VirtualNode;
22use risingwave_common::types::DataType;
23use risingwave_common::util::iter_util::ZipEqFast;
24use risingwave_common::util::scan_range::ScanRange;
25use risingwave_common::util::sort_util::OrderType;
26use risingwave_pb::stream_plan::stream_node::{PbNodeBody, PbStreamKind};
27use risingwave_pb::stream_plan::{PbStreamNode, StreamScanType};
28
29use super::stream::prelude::*;
30use super::utils::{Distill, childless_record};
31use super::{
32 BackfillType, ExprRewritable, PlanBase, PlanNodeId, StreamNode, StreamPlanRef as PlanRef,
33 generic,
34};
35use crate::TableCatalog;
36use crate::catalog::ColumnId;
37use crate::expr::{ExprRewriter, ExprVisitor, FunctionCall};
38use crate::optimizer::plan_node::expr_visitable::ExprVisitable;
39use crate::optimizer::plan_node::utils::{IndicesDisplay, TableCatalogBuilder};
40use crate::optimizer::property::{Distribution, DistributionDisplay, MonotonicityMap};
41use crate::scheduler::SchedulerResult;
42use crate::stream_fragmenter::BuildFragmentGraphState;
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
48pub struct StreamTableScan {
49 pub base: PlanBase<Stream>,
50 core: generic::TableScan,
51 batch_plan_id: PlanNodeId,
52 backfill_type: BackfillType,
53 pk_scan_range: Option<ScanRange>,
54}
55
56impl StreamTableScan {
57 pub const BACKFILL_FINISHED_COLUMN_NAME: &str = "backfill_finished";
58 pub const EPOCH_COLUMN_NAME: &str = "epoch";
59 pub const IS_EPOCH_FINISHED_COLUMN_NAME: &str = "is_epoch_finished";
60 pub const ROW_COUNT_COLUMN_NAME: &str = "row_count";
61 pub const VNODE_COLUMN_NAME: &str = "vnode";
62
63 pub fn new_with_backfill_type(core: generic::TableScan, backfill_type: BackfillType) -> Self {
64 Self::new_with_scan_range(core, backfill_type, None)
65 }
66
67 pub fn new_with_scan_range(
68 core: generic::TableScan,
69 backfill_type: BackfillType,
70 pk_scan_range: Option<ScanRange>,
71 ) -> Self {
72 let batch_plan_id = core.ctx.next_plan_node_id();
73
74 if core.cross_database() {
75 assert!(
76 !(backfill_type == BackfillType::Replicated || backfill_type.without_snapshot()),
77 "cross-database replicated or without-snapshot scan is not supported"
78 );
79 }
80
81 let distribution = {
82 match core.distribution_key() {
83 Some(distribution_key) => {
84 if distribution_key.is_empty() {
85 Distribution::Single
86 } else {
87 Distribution::UpstreamHashShard(distribution_key, core.table_catalog.id)
89 }
90 }
91 None => Distribution::SomeShard,
92 }
93 };
94
95 let stream_kind = if core.append_only() {
96 StreamKind::AppendOnly
97 } else if backfill_type.without_snapshot() {
98 StreamKind::Upsert
99 } else {
100 StreamKind::Retract
101 };
102
103 let base = PlanBase::new_stream_with_core(
104 &core,
105 distribution,
106 stream_kind,
107 false,
108 core.watermark_columns(),
109 MonotonicityMap::new(),
110 );
111 Self {
112 base,
113 core,
114 batch_plan_id,
115 backfill_type,
116 pk_scan_range,
117 }
118 }
119
120 pub fn table_name(&self) -> &str {
121 self.core.table_name()
122 }
123
124 pub fn core(&self) -> &generic::TableScan {
125 &self.core
126 }
127
128 pub fn to_index_scan(
129 &self,
130 index_table_catalog: Arc<TableCatalog>,
131 primary_to_secondary_mapping: &BTreeMap<usize, usize>,
132 function_mapping: &HashMap<FunctionCall, usize>,
133 backfill_type: BackfillType,
134 ) -> StreamTableScan {
135 let logical_index_scan = self.core.to_index_scan(
136 index_table_catalog,
137 primary_to_secondary_mapping,
138 function_mapping,
139 );
140 logical_index_scan
141 .distribution_key()
142 .expect("distribution key of stream chain must exist in output columns");
143 StreamTableScan::new_with_backfill_type(logical_index_scan, backfill_type)
144 }
145
146 pub fn stream_scan_type(&self) -> StreamScanType {
147 self.backfill_type
148 .to_stream_scan_type(self.core.cross_database())
149 }
150
151 pub fn backfill_type(&self) -> BackfillType {
152 self.backfill_type
153 }
154
155 pub fn pk_scan_range(&self) -> Option<&ScanRange> {
156 self.pk_scan_range.as_ref()
157 }
158
159 fn get_upstream_state_table(&self) -> &TableCatalog {
161 self.core.table_catalog.as_ref()
162 }
163
164 pub fn build_backfill_state_catalog(
208 &self,
209 state: &mut BuildFragmentGraphState,
210 stream_scan_type: StreamScanType,
211 ) -> TableCatalog {
212 let mut catalog_builder = TableCatalogBuilder::default();
213 let upstream_schema = &self.core.get_table_columns();
214
215 catalog_builder.add_column(&Field::with_name(
218 VirtualNode::RW_TYPE,
219 Self::VNODE_COLUMN_NAME,
220 ));
221 catalog_builder.add_order_column(0, OrderType::ascending());
222
223 #[expect(deprecated)]
224 match stream_scan_type {
225 StreamScanType::Chain
226 | StreamScanType::Rearrange
227 | StreamScanType::Backfill
228 | StreamScanType::UpstreamOnly
229 | StreamScanType::ArrangementBackfill => {
230 for col_order in self.core.primary_key() {
232 let col = &upstream_schema[col_order.column_index];
233 catalog_builder.add_column(&Field::from(&**col));
234 }
235
236 catalog_builder.add_column(&Field::with_name(
238 DataType::Boolean,
239 Self::BACKFILL_FINISHED_COLUMN_NAME,
240 ));
241
242 catalog_builder.add_column(&Field::with_name(
244 DataType::Int64,
245 Self::ROW_COUNT_COLUMN_NAME,
246 ));
247 }
248 StreamScanType::SnapshotBackfill | StreamScanType::CrossDbSnapshotBackfill => {
249 catalog_builder
251 .add_column(&Field::with_name(DataType::Int64, Self::EPOCH_COLUMN_NAME));
252
253 catalog_builder.add_column(&Field::with_name(
255 DataType::Int64,
256 Self::ROW_COUNT_COLUMN_NAME,
257 ));
258
259 catalog_builder.add_column(&Field::with_name(
261 DataType::Boolean,
262 Self::IS_EPOCH_FINISHED_COLUMN_NAME,
263 ));
264
265 for col_order in self.core.primary_key() {
267 let col = &upstream_schema[col_order.column_index];
268 catalog_builder.add_column(&Field::from(&col.column_desc));
269 }
270 }
271 StreamScanType::Unspecified => unreachable!(),
272 }
273
274 catalog_builder.set_vnode_col_idx(0);
276 catalog_builder.set_dist_key_in_pk(vec![0]);
277
278 let num_of_columns = catalog_builder.columns().len();
279 catalog_builder.set_value_indices((1..num_of_columns).collect_vec());
280
281 catalog_builder
282 .build(vec![0], 1)
283 .with_id(state.gen_table_id_wrapped())
284 }
285}
286
287impl_plan_tree_node_for_leaf! { Stream, StreamTableScan }
288
289impl Distill for StreamTableScan {
290 fn distill<'a>(&self) -> XmlNode<'a> {
291 let verbose = self.base.ctx().is_explain_verbose();
292 let mut vec = Vec::with_capacity(4);
293 vec.push(("table", Pretty::from(self.core.table_name().to_owned())));
294 vec.push(("columns", self.core.columns_pretty(verbose)));
295 if let Some(scan_range) = &self.pk_scan_range {
296 let mut parts = Vec::new();
297 let pk_cols = self.core.primary_key();
298 for (pk, datum) in pk_cols
300 .iter()
301 .take(scan_range.eq_conds.len())
302 .zip_eq_fast(scan_range.eq_conds.iter())
303 {
304 let field = &self.core.table_catalog.columns()[pk.column_index];
305 parts.push(format!("{} = {:?}", field.name(), datum));
306 }
307 let range_col_idx = scan_range.eq_conds.len();
309 if range_col_idx < pk_cols.len() {
310 use std::ops::Bound;
311 let field = &self.core.table_catalog.columns()[pk_cols[range_col_idx].column_index];
312 let fmt_bound_val = |v: &Vec<risingwave_common::types::Datum>| -> String {
313 v.first().map_or("NULL".to_owned(), |d| format!("{:?}", d))
314 };
315 match &scan_range.range.0 {
316 Bound::Included(v) => {
317 parts.push(format!("{} >= {}", field.name(), fmt_bound_val(v)))
318 }
319 Bound::Excluded(v) => {
320 parts.push(format!("{} > {}", field.name(), fmt_bound_val(v)))
321 }
322 Bound::Unbounded => {}
323 }
324 match &scan_range.range.1 {
325 Bound::Included(v) => {
326 parts.push(format!("{} <= {}", field.name(), fmt_bound_val(v)))
327 }
328 Bound::Excluded(v) => {
329 parts.push(format!("{} < {}", field.name(), fmt_bound_val(v)))
330 }
331 Bound::Unbounded => {}
332 }
333 }
334 if !parts.is_empty() {
335 vec.push(("pk_scan_range", Pretty::from(parts.join(" AND "))));
336 }
337 }
338
339 if verbose {
340 vec.push(("stream_scan_type", Pretty::debug(&self.stream_scan_type())));
341 let stream_key = IndicesDisplay {
342 indices: self.stream_key().unwrap_or_default(),
343 schema: self.base.schema(),
344 };
345 vec.push(("stream_key", stream_key.distill()));
346 let pk = IndicesDisplay {
347 indices: &self
348 .core
349 .primary_key()
350 .iter()
351 .map(|x| x.column_index)
352 .collect_vec(),
353 schema: &self.core.table_catalog.column_schema(),
354 };
355 vec.push(("pk", pk.distill()));
356 let dist = Pretty::display(&DistributionDisplay {
357 distribution: self.distribution(),
358 input_schema: self.base.schema(),
359 });
360 vec.push(("dist", dist));
361 }
362
363 childless_record("StreamTableScan", vec)
364 }
365}
366
367impl StreamNode for StreamTableScan {
368 fn to_stream_prost_body(&self, _state: &mut BuildFragmentGraphState) -> PbNodeBody {
369 unreachable!(
370 "stream scan cannot be converted into a prost body -- call `adhoc_to_stream_prost` instead."
371 )
372 }
373}
374
375impl StreamTableScan {
376 pub fn adhoc_to_stream_prost(
377 &self,
378 state: &mut BuildFragmentGraphState,
379 ) -> SchedulerResult<PbStreamNode> {
380 use risingwave_pb::stream_plan::*;
381
382 let stream_key = self
383 .stream_key()
384 .unwrap_or(&[])
385 .iter()
386 .map(|x| *x as u32)
387 .collect_vec();
388
389 let stream_scan_type = self.stream_scan_type();
390
391 #[expect(deprecated)]
393 let upstream_column_ids = match stream_scan_type {
394 StreamScanType::Backfill
396 | StreamScanType::ArrangementBackfill
397 | StreamScanType::SnapshotBackfill
398 | StreamScanType::CrossDbSnapshotBackfill => self.core.output_and_pk_column_ids(),
399 StreamScanType::Chain | StreamScanType::Rearrange | StreamScanType::UpstreamOnly => {
400 self.core.output_column_ids()
401 }
402 StreamScanType::Unspecified => unreachable!(),
403 }
404 .iter()
405 .map(ColumnId::get_id)
406 .collect_vec();
407
408 let snapshot_schema = upstream_column_ids
410 .iter()
411 .map(|&id| {
412 let col = self
413 .core
414 .get_table_columns()
415 .iter()
416 .find(|c| c.column_id.get_id() == id)
417 .unwrap();
418 Field::from(&col.column_desc).to_prost()
419 })
420 .collect_vec();
421
422 let upstream_schema = snapshot_schema.clone();
423
424 let batch_plan_node = BatchPlanNode {
426 table_desc: Some(self.core.table_catalog.table_desc().try_to_protobuf()?),
427 column_ids: upstream_column_ids.clone(),
428 };
429
430 let catalog = self
431 .build_backfill_state_catalog(state, stream_scan_type)
432 .to_internal_table_prost();
433
434 let output_indices = self
438 .core
439 .output_column_ids()
440 .iter()
441 .map(|i| {
442 upstream_column_ids
443 .iter()
444 .position(|&x| x == i.get_id())
445 .unwrap() as u32
446 })
447 .collect_vec();
448
449 let arrangement_table = if stream_scan_type == StreamScanType::ArrangementBackfill {
450 let upstream_table_catalog = self.get_upstream_state_table();
451 Some(upstream_table_catalog.to_internal_table_prost())
452 } else {
453 None
454 };
455
456 let input = if stream_scan_type == StreamScanType::CrossDbSnapshotBackfill {
457 vec![]
458 } else {
459 vec![
460 PbStreamNode {
463 node_body: Some(PbNodeBody::Merge(Default::default())),
464 identity: "Upstream".into(),
465 fields: upstream_schema,
466 stream_key: vec![], ..Default::default()
468 },
469 PbStreamNode {
471 node_body: Some(PbNodeBody::BatchPlan(Box::new(batch_plan_node))),
472 operator_id: self.batch_plan_id.to_stream_node_operator_id(),
473 identity: "BatchPlanNode".into(),
474 fields: snapshot_schema,
475 stream_key: vec![], input: vec![],
477 stream_kind: PbStreamKind::AppendOnly as i32,
478 },
479 ]
480 };
481
482 let node_body = PbNodeBody::StreamScan(Box::new(StreamScanNode {
483 table_id: self.core.table_catalog.id,
484 stream_scan_type: stream_scan_type as i32,
485 output_indices,
487 upstream_column_ids,
488 table_desc: Some(self.core.table_catalog.table_desc().try_to_protobuf()?),
490 state_table: Some(catalog),
491 arrangement_table,
492 rate_limit: self.base.ctx().overwrite_options().backfill_rate_limit,
493 pk_scan_range: self.pk_scan_range.as_ref().map(|sr| sr.to_protobuf()),
494 ..Default::default()
495 }));
496
497 Ok(PbStreamNode {
498 fields: self.schema().to_prost(),
499 input,
500 node_body: Some(node_body),
501 stream_key,
502 operator_id: self.base.id().to_stream_node_operator_id(),
503 identity: self.distill_to_string(),
504 stream_kind: self.stream_kind().to_protobuf() as i32,
505 })
506 }
507}
508
509impl ExprRewritable<Stream> for StreamTableScan {
510 fn has_rewritable_expr(&self) -> bool {
511 true
512 }
513
514 fn rewrite_exprs(&self, r: &mut dyn ExprRewriter) -> PlanRef {
515 let mut core = self.core.clone();
516 core.rewrite_exprs(r);
517 Self::new_with_scan_range(core, self.backfill_type, self.pk_scan_range.clone()).into()
518 }
519}
520
521impl ExprVisitable for StreamTableScan {
522 fn visit_exprs(&self, v: &mut dyn ExprVisitor) {
523 self.core.visit_exprs(v);
524 }
525}