risingwave_frontend/optimizer/
backfill_order_strategy.rs1use risingwave_pb::common::Uint32Vector;
16use risingwave_pb::id::RelationId;
17use risingwave_pb::stream_plan::BackfillOrder;
18use risingwave_sqlparser::ast::BackfillOrderStrategy;
19
20use crate::error::Result;
21use crate::optimizer::backfill_order_strategy::auto::plan_auto_strategy;
22use crate::optimizer::backfill_order_strategy::fixed::plan_fixed_strategy;
23use crate::optimizer::plan_node::StreamPlanRef;
24use crate::session::SessionImpl;
25
26pub mod auto {
27 use std::collections::{HashMap, HashSet};
28
29 use risingwave_pb::id::RelationId;
30
31 use crate::optimizer::backfill_order_strategy::common::has_cycle;
32 use crate::optimizer::plan_node::{StreamPlanNodeType, StreamPlanRef};
33 use crate::session::SessionImpl;
34
35 #[derive(Debug)]
36 pub(super) enum BackfillTreeNode {
37 Join {
38 lhs: Box<BackfillTreeNode>,
39 rhs: Box<BackfillTreeNode>,
40 },
41 Scan {
42 id: RelationId,
43 },
44 Union {
45 children: Vec<BackfillTreeNode>,
46 },
47 Ignored,
48 }
49
50 fn plan_graph_to_backfill_tree(
52 session: &SessionImpl,
53 plan: StreamPlanRef,
54 ) -> Option<BackfillTreeNode> {
55 match plan.node_type() {
56 StreamPlanNodeType::StreamHashJoin => {
57 assert_eq!(plan.inputs().len(), 2);
58 let mut inputs = plan.inputs().into_iter();
59 let l = inputs.next().unwrap();
60 let r = inputs.next().unwrap();
61 Some(BackfillTreeNode::Join {
62 lhs: Box::new(plan_graph_to_backfill_tree(session, l)?),
63 rhs: Box::new(plan_graph_to_backfill_tree(session, r)?),
64 })
65 }
66 StreamPlanNodeType::StreamTableScan => {
67 let table_scan = plan.as_stream_table_scan().expect("table scan");
68 let relation_id = table_scan.core().table_catalog.id().as_relation_id();
69 Some(BackfillTreeNode::Scan { id: relation_id })
70 }
71 StreamPlanNodeType::StreamSourceScan => {
72 let source_scan = plan.as_stream_source_scan().expect("source scan");
73 let relation_id = source_scan.source_catalog().id.as_relation_id();
74 Some(BackfillTreeNode::Scan { id: relation_id })
75 }
76 StreamPlanNodeType::StreamUnion => {
77 let inputs = plan.inputs();
78 let mut children = Vec::with_capacity(inputs.len());
79 for child in inputs {
80 let subtree = plan_graph_to_backfill_tree(session, child)?;
81 if matches!(subtree, BackfillTreeNode::Ignored) {
82 continue;
83 }
84 children.push(subtree);
85 }
86 Some(BackfillTreeNode::Union { children })
87 }
88 node_type => {
89 let inputs = plan.inputs();
90 match inputs.len() {
91 0 => Some(BackfillTreeNode::Ignored),
92 1 => {
93 let mut inputs = inputs.into_iter();
94 let child = inputs.next().unwrap();
95 plan_graph_to_backfill_tree(session, child)
96 }
97 _ => {
98 session.notice_to_user(format!(
99 "Backfill order strategy is not supported for {:?}",
100 node_type
101 ));
102 None
103 }
104 }
105 }
106 }
107 }
108
109 fn fold_backfill_tree_to_partial_order(
163 tree: BackfillTreeNode,
164 ) -> HashMap<RelationId, HashSet<RelationId>> {
165 let mut order: HashMap<RelationId, HashSet<RelationId>> = HashMap::new();
166
167 fn traverse_backfill_tree(
170 tree: BackfillTreeNode,
171 order: &mut HashMap<RelationId, HashSet<RelationId>>,
172 is_leftmost_child: bool,
173 mut prior_terminal_nodes: HashSet<RelationId>,
174 ) -> HashSet<RelationId> {
175 match tree {
176 BackfillTreeNode::Ignored => HashSet::new(),
177 BackfillTreeNode::Scan { id } => {
178 if is_leftmost_child {
179 for prior_terminal_node in prior_terminal_nodes {
180 order.entry(prior_terminal_node).or_default().insert(id);
181 }
182 }
183 HashSet::from([id])
184 }
185 BackfillTreeNode::Union { children } => {
186 let mut terminal_nodes = HashSet::new();
187 for child in children {
188 let child_terminal_nodes = traverse_backfill_tree(
189 child,
190 order,
191 is_leftmost_child,
192 prior_terminal_nodes.clone(),
193 );
194 terminal_nodes.extend(child_terminal_nodes);
195 }
196 terminal_nodes
197 }
198 BackfillTreeNode::Join { lhs, rhs } => {
199 let rhs_terminal_nodes =
200 traverse_backfill_tree(*rhs, order, false, HashSet::new());
201 prior_terminal_nodes.extend(rhs_terminal_nodes.iter().cloned());
202 traverse_backfill_tree(*lhs, order, true, prior_terminal_nodes)
203 }
204 }
205 }
206
207 traverse_backfill_tree(tree, &mut order, false, HashSet::new());
208
209 order
210 }
211
212 pub(super) fn plan_auto_strategy(
213 session: &SessionImpl,
214 plan: StreamPlanRef,
215 ) -> HashMap<RelationId, HashSet<RelationId>> {
216 if let Some(tree) = plan_graph_to_backfill_tree(session, plan) {
217 let order = fold_backfill_tree_to_partial_order(tree);
218 if has_cycle(&order) {
219 tracing::warn!(?order, "Backfill order strategy has a cycle");
220 session.notice_to_user("Backfill order strategy has a cycle");
221 return Default::default();
222 }
223 return order;
224 }
225 Default::default()
226 }
227}
228
229mod fixed {
230 use std::collections::{HashMap, HashSet};
231
232 use risingwave_common::bail;
233 use risingwave_pb::id::RelationId;
234 use risingwave_sqlparser::ast::ObjectName;
235
236 use crate::error::Result;
237 use crate::optimizer::backfill_order_strategy::common::{
238 bind_backfill_relation_id_by_name, has_cycle,
239 };
240 use crate::optimizer::plan_node::{StreamPlanNodeType, StreamPlanRef};
241 use crate::session::SessionImpl;
242
243 fn collect_scanned_relation_ids(
249 session: &SessionImpl,
250 plan: StreamPlanRef,
251 ) -> HashMap<RelationId, HashSet<RelationId>> {
252 let mut relation_ids: HashMap<RelationId, HashSet<RelationId>> = HashMap::new();
253
254 fn visit(
255 session: &SessionImpl,
256 plan: StreamPlanRef,
257 relation_ids: &mut HashMap<RelationId, HashSet<RelationId>>,
258 ) {
259 match plan.node_type() {
260 StreamPlanNodeType::StreamTableScan => {
261 let table_scan = plan.as_stream_table_scan().expect("table scan");
262 let table_catalog = &table_scan.core().table_catalog;
263 let scanned_relation_id = table_catalog.id().as_relation_id();
264 relation_ids
265 .entry(scanned_relation_id)
266 .or_default()
267 .insert(scanned_relation_id);
268
269 if table_catalog.is_index() {
270 let reader = session.env().catalog_reader().read_guard();
271 let schema_catalog = reader
272 .get_schema_by_id(table_catalog.database_id, table_catalog.schema_id)
273 .expect("schema of an index table should exist");
274 let index_catalog = schema_catalog
275 .iter_index()
276 .find(|index| index.index_table().id == table_catalog.id)
277 .expect("index catalog of an index table should exist");
278 relation_ids
279 .entry(index_catalog.primary_table.id().as_relation_id())
280 .or_default()
281 .insert(scanned_relation_id);
282 }
283 }
284 StreamPlanNodeType::StreamSourceScan => {
285 let source_scan = plan.as_stream_source_scan().expect("source scan");
286 let relation_id = source_scan.source_catalog().id.as_relation_id();
287 relation_ids
288 .entry(relation_id)
289 .or_default()
290 .insert(relation_id);
291 }
292 _ => {}
293 }
294
295 for child in plan.inputs() {
297 visit(session, child, relation_ids);
298 }
299 }
300
301 visit(session, plan, &mut relation_ids);
302 relation_ids
303 }
304
305 pub(super) fn plan_fixed_strategy(
306 session: &SessionImpl,
307 orders: Vec<(ObjectName, ObjectName)>,
308 plan: StreamPlanRef,
309 ) -> Result<HashMap<RelationId, HashSet<RelationId>>> {
310 let scanned_relation_ids = collect_scanned_relation_ids(session, plan);
312
313 let mut order: HashMap<RelationId, HashSet<RelationId>> = HashMap::new();
314 for (start_name, end_name) in orders {
315 let start_relation_id = bind_backfill_relation_id_by_name(session, start_name.clone())?;
316 let end_relation_id = bind_backfill_relation_id_by_name(session, end_name.clone())?;
317
318 let Some(start_scanned_relation_ids) = scanned_relation_ids.get(&start_relation_id)
320 else {
321 bail!(
322 "Table or source '{}' specified in backfill_order is not used in the query",
323 start_name
324 );
325 };
326 let Some(end_scanned_relation_ids) = scanned_relation_ids.get(&end_relation_id) else {
327 bail!(
328 "Table or source '{}' specified in backfill_order is not used in the query",
329 end_name
330 );
331 };
332
333 for start_scanned_relation_id in start_scanned_relation_ids {
334 order
335 .entry(*start_scanned_relation_id)
336 .or_default()
337 .extend(end_scanned_relation_ids.iter().copied());
338 }
339 }
340 if has_cycle(&order) {
341 bail!("Backfill order strategy has a cycle");
342 }
343 Ok(order)
344 }
345}
346
347mod common {
348 use std::collections::{HashMap, HashSet};
349
350 use risingwave_pb::id::RelationId;
351 use risingwave_sqlparser::ast::ObjectName;
352
353 use crate::Binder;
354 use crate::catalog::CatalogError;
355 use crate::catalog::root_catalog::SchemaPath;
356 use crate::catalog::schema_catalog::SchemaCatalog;
357 use crate::error::Result;
358 use crate::session::SessionImpl;
359
360 pub(super) fn has_cycle(order: &HashMap<RelationId, HashSet<RelationId>>) -> bool {
362 fn dfs(
363 node: RelationId,
364 order: &HashMap<RelationId, HashSet<RelationId>>,
365 visited: &mut HashSet<RelationId>,
366 stack: &mut HashSet<RelationId>,
367 ) -> bool {
368 if stack.contains(&node) {
369 return true; }
371
372 if visited.insert(node) {
373 stack.insert(node);
374 if let Some(downstreams) = order.get(&node) {
375 for neighbor in downstreams {
376 if dfs(*neighbor, order, visited, stack) {
377 return true;
378 }
379 }
380 }
381 stack.remove(&node);
382 }
383 false
384 }
385
386 let mut visited = HashSet::new();
387 let mut stack = HashSet::new();
388 for &start in order.keys() {
389 if dfs(start, order, &mut visited, &mut stack) {
390 return true;
391 }
392 }
393
394 false
395 }
396
397 pub(super) fn bind_backfill_relation_id_by_name(
398 session: &SessionImpl,
399 name: ObjectName,
400 ) -> Result<RelationId> {
401 let (db_name, schema_name, rel_name) = Binder::resolve_db_schema_qualified_name(&name)?;
402 let db_name = db_name.unwrap_or(session.database());
403
404 let reader = session.env().catalog_reader().read_guard();
405
406 match schema_name {
407 Some(name) => {
408 let schema_catalog = reader.get_schema_by_name(&db_name, &name)?;
409 bind_table(schema_catalog, &rel_name)
410 }
411 None => {
412 let search_path = session.config().search_path();
413 let user_name = session.user_name();
414 let schema_path = SchemaPath::Path(&search_path, &user_name);
415 let result: crate::error::Result<Option<(RelationId, &str)>> = schema_path
416 .try_find(|schema_name| {
417 if let Ok(schema_catalog) = reader.get_schema_by_name(&db_name, schema_name)
418 && let Ok(relation_id) = bind_table(schema_catalog, &rel_name)
419 {
420 Ok(Some(relation_id))
421 } else {
422 Ok(None)
423 }
424 });
425 if let Some((relation_id, _schema_name)) = result? {
426 return Ok(relation_id);
427 }
428 Err(CatalogError::not_found("table", &rel_name).into())
429 }
430 }
431 }
432
433 fn bind_table(
434 schema_catalog: &SchemaCatalog,
435 name: &String,
436 ) -> crate::error::Result<RelationId> {
437 if let Some(table) = schema_catalog.get_created_table_by_name(name) {
438 Ok(table.id().as_relation_id())
439 } else if let Some(source) = schema_catalog.get_source_by_name(name) {
440 Ok(source.id.as_relation_id())
441 } else {
442 Err(CatalogError::not_found("table or source", name).into())
443 }
444 }
445}
446
447pub mod display {
448 use risingwave_pb::stream_plan::BackfillOrder;
449
450 use crate::session::SessionImpl;
451
452 fn get_table_name(session: &SessionImpl, id: u32) -> crate::error::Result<String> {
453 let catalog_reader = session.env().catalog_reader().read_guard();
454 let table_catalog = catalog_reader.get_any_table_by_id(id.into())?;
455 let table_name = table_catalog.name();
456 let db_id = table_catalog.database_id;
457 let schema_id = table_catalog.schema_id;
458 let schema_catalog = catalog_reader.get_schema_by_id(db_id, schema_id)?;
459 let schema_name = schema_catalog.name();
460 let name = format!("{}.{}", schema_name, table_name);
461 Ok(name)
462 }
463
464 pub(super) fn print_backfill_order_in_dot_format(
465 session: &SessionImpl,
466 order: BackfillOrder,
467 ) -> crate::error::Result<String> {
468 let mut result = String::new();
469 result.push_str("digraph G {\n");
470 let mut edges = vec![];
473 for (start, end) in order.order {
474 let start_name = get_table_name(session, start.as_raw_id())?;
475 for end in end.data {
476 let end_name = get_table_name(session, end)?;
477 edges.push(format!(" \"{}\" -> \"{}\";\n", start_name, end_name));
478 }
479 }
480 edges.sort();
481 for edge in edges {
482 result.push_str(&edge);
483 }
484 result.push_str("}\n");
485 Ok(result)
486 }
487}
488
489pub fn plan_backfill_order(
498 session: &SessionImpl,
499 backfill_order_strategy: BackfillOrderStrategy,
500 plan: StreamPlanRef,
501) -> Result<BackfillOrder> {
502 let order = match backfill_order_strategy {
503 BackfillOrderStrategy::Default | BackfillOrderStrategy::None => Default::default(),
504 BackfillOrderStrategy::Auto => plan_auto_strategy(session, plan),
505 BackfillOrderStrategy::Fixed(orders) => plan_fixed_strategy(session, orders, plan)?,
506 };
507 Ok(BackfillOrder {
508 order: order
509 .into_iter()
510 .map(|(relation_id, dependencies)| {
511 (
512 relation_id,
513 Uint32Vector {
514 data: dependencies
515 .into_iter()
516 .map(RelationId::as_raw_id)
517 .collect(),
518 },
519 )
520 })
521 .collect(),
522 })
523}
524
525pub fn explain_backfill_order_in_dot_format(
527 session: &SessionImpl,
528 backfill_order_strategy: BackfillOrderStrategy,
529 plan: StreamPlanRef,
530) -> Result<String> {
531 let order = plan_backfill_order(session, backfill_order_strategy, plan)?;
532 let dot_formatted_backfill_order = display::print_backfill_order_in_dot_format(session, order)?;
533 Ok(dot_formatted_backfill_order)
534}