1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
18use std::sync::Arc;
19
20use catalog::CatalogManagerRef;
21use common_error::ext::BoxedError;
22use common_function::aggrs::aggr_wrapper::get_aggr_func;
23use common_telemetry::debug;
24use datafusion::arrow::datatypes::DataType as ArrowDataType;
25use datafusion::datasource::DefaultTableSource;
26use datafusion::error::Result as DfResult;
27use datafusion::logical_expr::Expr;
28use datafusion::sql::unparser::Unparser;
29use datafusion_common::tree_node::{
30 Transformed, TreeNode as _, TreeNodeRecursion, TreeNodeRewriter, TreeNodeVisitor,
31};
32use datafusion_common::{
33 Column, DFSchema, DataFusionError, NullEquality, ScalarValue, TableReference,
34};
35use datafusion_expr::logical_plan::{Aggregate, TableScanBuilder};
36use datafusion_expr::{
37 Distinct, ExprSchemable, JoinType, LogicalPlan, LogicalPlanBuilder, Operator, Projection, and,
38 binary_expr, bitwise_and, bitwise_or, bitwise_xor, is_null, or, when,
39};
40use datatypes::prelude::ConcreteDataType;
41use datatypes::schema::{ColumnSchema, SchemaRef};
42use query::QueryEngineRef;
43use query::parser::{DEFAULT_LOOKBACK_STRING, PromQuery, QueryLanguageParser, QueryStatement};
44use session::context::QueryContextRef;
45use snafu::{OptionExt, ResultExt, ensure};
46use sql::parser::{ParseOptions, ParserContext};
47use sql::statements::statement::Statement;
48use sql::statements::tql::Tql;
49use table::TableRef;
50use table::table::adapter::DfTableProviderAdapter;
51
52use crate::adapter::{AUTO_CREATED_PLACEHOLDER_TS_COL, AUTO_CREATED_UPDATE_AT_TS_COL};
53use crate::df_optimizer::apply_df_optimizer;
54use crate::error::{DatafusionSnafu, ExternalSnafu, InvalidQuerySnafu, TableNotFoundSnafu};
55use crate::{Error, TableName};
56
57#[cfg(test)]
58mod test;
59
60#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct IncrementalAggregateMergeColumn {
70 pub input_field_name: String,
73 pub output_field_name: String,
76 pub merge_op: IncrementalAggregateMergeOp,
77}
78
79impl IncrementalAggregateMergeColumn {
80 pub fn new(output_field_name: String, merge_op: IncrementalAggregateMergeOp) -> Self {
82 Self {
83 input_field_name: output_field_name.clone(),
84 output_field_name,
85 merge_op,
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum IncrementalAggregateMergeOp {
92 Sum,
93 Min,
94 Max,
95 BoolAnd,
96 BoolOr,
97 BitAnd,
98 BitOr,
99 BitXor,
100 StateDeltaMerge {
101 function_name: &'static str,
102 params: Vec<Expr>,
103 },
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct IncrementalAggregateAnalysis {
115 pub group_key_names: Vec<String>,
117 pub merge_columns: Vec<IncrementalAggregateMergeColumn>,
118 pub literal_columns: Vec<String>,
120 pub output_field_names: Vec<String>,
122 pub unsupported_exprs: Vec<String>,
123}
124
125fn unqualified_col(name: impl Into<String>) -> Expr {
126 Expr::Column(Column::from_name(name.into()))
127}
128
129fn qualified_col(qualifier: &str, name: impl Into<String>) -> Expr {
130 Expr::Column(Column::new(Some(qualifier), name.into()))
131}
132
133fn qualified_column(qualifier: &str, name: impl Into<String>) -> Column {
134 Column::new(Some(qualifier), name.into())
135}
136
137fn find_group_key_names(plan: &LogicalPlan) -> Result<Vec<String>, Error> {
138 let mut group_finder = FindGroupByFinalName::default();
139 plan.visit(&mut group_finder)
140 .with_context(|_| DatafusionSnafu {
141 context: format!("Failed to inspect group-by columns from logical plan: {plan:?}"),
142 })?;
143
144 let mut group_key_names = group_finder
145 .get_group_expr_names()
146 .unwrap_or_default()
147 .into_iter()
148 .collect::<Vec<_>>();
149 group_key_names.sort();
150 Ok(group_key_names)
151}
152
153fn has_grouping_set(plan: &LogicalPlan) -> bool {
154 match plan {
155 LogicalPlan::Aggregate(aggregate) => aggregate
156 .group_expr
157 .iter()
158 .any(|expr| matches!(expr, Expr::GroupingSet(_))),
159 _ => plan.inputs().into_iter().any(has_grouping_set),
160 }
161}
162
163fn has_aggregate(plan: &LogicalPlan) -> bool {
164 match plan {
165 LogicalPlan::Aggregate(_) => true,
166 _ => plan.inputs().into_iter().any(has_aggregate),
167 }
168}
169
170fn peel_subquery_aliases(mut plan: &LogicalPlan) -> &LogicalPlan {
171 while let LogicalPlan::SubqueryAlias(alias) = plan {
172 plan = alias.input.as_ref();
173 }
174 plan
175}
176
177fn extract_incremental_aggregate(plan: &LogicalPlan) -> Result<Option<&Aggregate>, String> {
178 let plan = match plan {
182 LogicalPlan::Projection(projection) => projection.input.as_ref(),
183 _ => plan,
184 };
185
186 match plan {
187 LogicalPlan::Aggregate(aggregate) => {
188 check_input_plan_shape(aggregate.input.as_ref())?;
189 Ok(Some(aggregate))
190 }
191 LogicalPlan::Filter(filter) if has_aggregate(filter.input.as_ref()) => Err(
192 "unsupported post-aggregate filter (HAVING) in incremental aggregate rewrite"
193 .to_string(),
194 ),
195 _ if has_aggregate(plan) => Err(
196 "unsupported post-aggregate plan shape in incremental aggregate rewrite".to_string(),
197 ),
198 _ => Ok(None),
199 }
200}
201
202fn check_input_plan_shape(plan: &LogicalPlan) -> Result<(), String> {
203 let plan = peel_subquery_aliases(plan);
204 match plan {
205 LogicalPlan::TableScan(_) => Ok(()),
208 LogicalPlan::Filter(filter) => match peel_subquery_aliases(filter.input.as_ref()) {
209 LogicalPlan::TableScan(_) => Ok(()),
210 _ => Err(
211 "unsupported aggregate input plan shape in incremental aggregate rewrite"
212 .to_string(),
213 ),
214 },
215 _ => Err(
216 "unsupported aggregate input plan shape in incremental aggregate rewrite".to_string(),
217 ),
218 }
219}
220
221#[derive(Debug, Default)]
222struct OutputProjectionInfo {
223 has_top_level_projection: bool,
224 aggregate_outputs: Vec<(String, String)>,
226 literal_columns: HashSet<String>,
227 output_field_names: Vec<String>,
228}
229
230impl OutputProjectionInfo {
231 fn output_field_name_set(&self) -> HashSet<String> {
232 self.output_field_names.iter().cloned().collect()
233 }
234
235 fn duplicate_output_names(&self) -> Vec<String> {
236 let mut seen = HashSet::new();
237 let mut duplicates = BTreeSet::new();
238 for name in &self.output_field_names {
239 if !seen.insert(name.clone()) {
240 duplicates.insert(name.clone());
241 }
242 }
243 duplicates.into_iter().collect()
244 }
245}
246
247fn collect_output_projection_info(plan: &LogicalPlan) -> OutputProjectionInfo {
248 let mut projection_info = OutputProjectionInfo {
249 has_top_level_projection: matches!(plan, LogicalPlan::Projection(_)),
250 output_field_names: plan
251 .schema()
252 .fields()
253 .iter()
254 .map(|field| field.name().clone())
255 .collect(),
256 ..Default::default()
257 };
258
259 if let LogicalPlan::Projection(projection) = plan {
260 for expr in &projection.expr {
261 match expr {
262 Expr::Alias(alias) => {
263 let alias_name = alias.name.clone();
268 if let Expr::Column(column) = alias.expr.as_ref() {
269 projection_info
270 .aggregate_outputs
271 .push((column.name.clone(), alias_name));
272 } else if let Expr::Alias(inner_alias) = alias.expr.as_ref()
273 && inner_alias.name.eq_ignore_ascii_case("count(*)")
274 && let Expr::Column(column) = inner_alias.expr.as_ref()
275 {
276 projection_info
277 .aggregate_outputs
278 .push((column.name.clone(), alias_name));
279 } else if is_passthrough_output_column(&alias_name, alias.expr.as_ref()) {
280 projection_info.literal_columns.insert(alias_name);
281 }
282 }
283 Expr::Column(col) => {
284 projection_info
285 .aggregate_outputs
286 .push((col.name.clone(), col.name.clone()));
287 }
288 Expr::Literal(_, _) => {
289 projection_info
290 .literal_columns
291 .insert(expr.qualified_name().1);
292 }
293 _ => {}
294 }
295 }
296 }
297
298 if projection_info
299 .output_field_names
300 .iter()
301 .any(|name| name == AUTO_CREATED_PLACEHOLDER_TS_COL)
302 {
303 projection_info
304 .literal_columns
305 .insert(AUTO_CREATED_PLACEHOLDER_TS_COL.to_string());
306 }
307
308 projection_info
309}
310
311fn is_passthrough_output_column(alias_name: &str, expr: &Expr) -> bool {
312 matches!(expr, Expr::Literal(_, _))
313 || match alias_name {
314 AUTO_CREATED_UPDATE_AT_TS_COL => expr == &datafusion::prelude::now(),
315 AUTO_CREATED_PLACEHOLDER_TS_COL => is_literal_or_cast_literal(expr),
316 _ => false,
317 }
318}
319
320fn is_literal_or_cast_literal(expr: &Expr) -> bool {
321 match expr {
322 Expr::Literal(_, _) => true,
323 Expr::Cast(cast) => is_literal_or_cast_literal(cast.expr.as_ref()),
324 Expr::TryCast(cast) => is_literal_or_cast_literal(cast.expr.as_ref()),
325 _ => false,
326 }
327}
328
329fn merge_op_for_aggregate_expr(
330 aggr_expr: &Expr,
331 input_schema: &DFSchema,
332) -> Result<IncrementalAggregateMergeOp, String> {
333 let Some(aggr_func) = get_aggr_func(aggr_expr) else {
334 return Err(aggr_expr.to_string());
335 };
336 if aggr_func.params.distinct {
337 return Err(format!("unsupported DISTINCT aggregate: {aggr_expr}"));
338 }
339 if !aggr_func.params.order_by.is_empty() {
340 return Err(format!("unsupported aggregate ORDER BY: {aggr_expr}"));
341 }
342 if aggr_func.params.null_treatment.is_some() {
343 return Err(format!("unsupported aggregate NULL treatment: {aggr_expr}"));
344 }
345
346 let state_delta_merge = |function_name, params| {
347 Ok(IncrementalAggregateMergeOp::StateDeltaMerge {
348 function_name,
349 params,
350 })
351 };
352 match aggr_func.func.name().to_ascii_lowercase().as_str() {
353 "sum" | "count" => Ok(IncrementalAggregateMergeOp::Sum),
354 "min" => Ok(IncrementalAggregateMergeOp::Min),
355 "max" => Ok(IncrementalAggregateMergeOp::Max),
356 "bool_and" => Ok(IncrementalAggregateMergeOp::BoolAnd),
357 "bool_or" => Ok(IncrementalAggregateMergeOp::BoolOr),
358 "bit_and" => Ok(IncrementalAggregateMergeOp::BitAnd),
359 "bit_or" => Ok(IncrementalAggregateMergeOp::BitOr),
360 "bit_xor" => Ok(IncrementalAggregateMergeOp::BitXor),
361 "avg_state" if aggr_func.params.args.len() == 1 => {
363 state_delta_merge("__avg_state_delta_merge", vec![])
364 }
365 "hll" if aggr_func.params.args.len() == 1 => state_delta_merge("__hll_delta_merge", vec![]),
366 "stddev_pop_state" if aggr_func.params.args.len() == 1 => {
367 state_delta_merge("__stddev_pop_state_delta_merge", vec![])
368 }
369 "uddsketch_state" if aggr_func.params.args.len() == 3 => {
370 let [bucket_size, error_rate, _] = aggr_func.params.args.as_slice() else {
371 unreachable!();
372 };
373 if !matches!(bucket_size, Expr::Literal(ScalarValue::Int64(Some(_)), _))
374 || !matches!(error_rate, Expr::Literal(ScalarValue::Float64(Some(_)), _))
375 {
376 return Err(aggr_expr.to_string());
377 }
378 state_delta_merge(
379 "__uddsketch_state_delta_merge",
380 vec![bucket_size.clone(), error_rate.clone()],
381 )
382 }
383 "avg_merge"
386 if aggr_func.params.args.len() == 1
387 && aggr_func.params.args[0].get_type(input_schema).ok()
388 == Some(ArrowDataType::Binary) =>
389 {
390 state_delta_merge("__avg_state_delta_merge", vec![])
391 }
392 _ => Err(aggr_expr.to_string()),
393 }
394}
395
396fn resolve_aggregate_output_fields(
397 aggr_expr: &Expr,
398 projection_info: &OutputProjectionInfo,
399 output_field_name_set: &HashSet<String>,
400) -> Vec<(String, String)> {
401 let raw_name = aggr_expr.qualified_name().1;
408 if projection_info.has_top_level_projection {
409 projection_info
410 .aggregate_outputs
411 .iter()
412 .filter(|(input_name, _)| input_name == &raw_name)
413 .cloned()
414 .collect()
415 } else if output_field_name_set.contains(&raw_name) {
416 vec![(raw_name.clone(), raw_name)]
417 } else {
418 vec![]
419 }
420}
421
422fn find_uncovered_output_fields(
423 projection_info: &OutputProjectionInfo,
424 group_key_names: &[String],
425 merge_columns: &[IncrementalAggregateMergeColumn],
426) -> Vec<String> {
427 let group_key_names = group_key_names.iter().cloned().collect::<HashSet<_>>();
428 let merge_column_names = merge_columns
429 .iter()
430 .map(|c| c.output_field_name.clone())
431 .collect::<HashSet<_>>();
432
433 projection_info
434 .output_field_names
435 .iter()
436 .filter(|name| {
437 !group_key_names.contains(*name)
438 && !merge_column_names.contains(*name)
439 && !projection_info.literal_columns.contains(*name)
440 && name.as_str() != AUTO_CREATED_UPDATE_AT_TS_COL
444 && name.as_str() != AUTO_CREATED_PLACEHOLDER_TS_COL
445 })
446 .cloned()
447 .collect()
448}
449
450fn find_unsupported_group_key_projection_outputs(
451 plan: &LogicalPlan,
452 aggregate: &Aggregate,
453 group_key_names: &[String],
454) -> Vec<String> {
455 let LogicalPlan::Projection(projection) = plan else {
456 return vec![];
457 };
458
459 let group_key_names = group_key_names.iter().cloned().collect::<HashSet<_>>();
460 let group_expr_names = aggregate
461 .group_expr
462 .iter()
463 .filter_map(|expr| expr.name_for_alias().ok())
464 .collect::<HashSet<_>>();
465 projection
466 .expr
467 .iter()
468 .filter_map(|expr| {
469 let output_name = expr.qualified_name().1;
470 if !group_key_names.contains(&output_name) {
471 return None;
472 }
473
474 let source_name = match expr {
475 Expr::Alias(alias) => alias.expr.name_for_alias().ok(),
476 _ => expr.name_for_alias().ok(),
477 };
478 if source_name.is_some_and(|name| group_expr_names.contains(&name)) {
479 None
480 } else {
481 Some(format!(
482 "unsupported group key output field is not a transparent group expression: {output_name}"
483 ))
484 }
485 })
486 .collect()
487}
488
489pub fn analyze_incremental_aggregate_plan(
490 plan: &LogicalPlan,
491) -> Result<Option<IncrementalAggregateAnalysis>, Error> {
492 let group_key_names = find_group_key_names(plan)?;
493 let aggregate = match extract_incremental_aggregate(plan) {
494 Ok(Some(aggregate)) => aggregate,
495 Ok(None) => return Ok(None),
496 Err(reason) => {
497 let projection_info = collect_output_projection_info(plan);
498 let mut unsupported_exprs = projection_info
499 .duplicate_output_names()
500 .into_iter()
501 .map(|name| format!("duplicate output field name: {name}"))
502 .collect::<Vec<_>>();
503 unsupported_exprs.push(reason);
504 return Ok(Some(IncrementalAggregateAnalysis {
505 group_key_names,
506 merge_columns: vec![],
507 literal_columns: vec![],
508 output_field_names: projection_info.output_field_names,
509 unsupported_exprs,
510 }));
511 }
512 };
513 let aggr_exprs = aggregate.aggr_expr.clone();
514 let projection_info = collect_output_projection_info(plan);
515 let output_field_name_set = projection_info.output_field_name_set();
516
517 let mut merge_columns = Vec::with_capacity(aggr_exprs.len());
518 let mut unsupported_exprs = projection_info
519 .duplicate_output_names()
520 .into_iter()
521 .map(|name| format!("duplicate output field name: {name}"))
522 .collect::<Vec<_>>();
523 if has_grouping_set(plan) {
524 unsupported_exprs.push(
525 "unsupported GROUPING SETS/CUBE/ROLLUP in incremental aggregate rewrite".to_string(),
526 );
527 }
528 if group_key_names.is_empty() {
529 unsupported_exprs
530 .push("unsupported global aggregate in incremental aggregate rewrite".to_string());
531 }
532 unsupported_exprs.extend(find_unsupported_group_key_projection_outputs(
533 plan,
534 aggregate,
535 &group_key_names,
536 ));
537 for aggr_expr in aggr_exprs {
538 let merge_op = match merge_op_for_aggregate_expr(&aggr_expr, aggregate.input.schema()) {
539 Ok(merge_op) => merge_op,
540 Err(reason) => {
541 unsupported_exprs.push(reason);
542 continue;
543 }
544 };
545 let aggregate_outputs =
546 resolve_aggregate_output_fields(&aggr_expr, &projection_info, &output_field_name_set);
547 if aggregate_outputs.is_empty() {
548 unsupported_exprs.push(aggr_expr.to_string());
549 continue;
550 }
551 let Some((_, input_field_name)) = aggregate_outputs.first() else {
552 continue;
553 };
554 let input_field_name = input_field_name.clone();
555 for (_, output_field_name) in aggregate_outputs {
556 merge_columns.push(IncrementalAggregateMergeColumn {
557 input_field_name: input_field_name.clone(),
558 output_field_name,
559 merge_op: merge_op.clone(),
560 });
561 }
562 }
563 if projection_info.has_top_level_projection {
564 let output_positions = projection_info
565 .output_field_names
566 .iter()
567 .enumerate()
568 .map(|(position, name)| (name.as_str(), position))
569 .collect::<HashMap<_, _>>();
570 merge_columns.sort_by_key(|column| {
571 output_positions
572 .get(column.output_field_name.as_str())
573 .copied()
574 .unwrap_or(usize::MAX)
575 });
576 }
577 unsupported_exprs.extend(
578 find_uncovered_output_fields(&projection_info, &group_key_names, &merge_columns)
579 .into_iter()
580 .map(|name| format!("unsupported output field: {name}")),
581 );
582 if !unsupported_exprs.is_empty() {
583 merge_columns.clear();
584 }
585 let mut literal_columns = projection_info
586 .literal_columns
587 .into_iter()
588 .collect::<Vec<_>>();
589 literal_columns.sort();
590
591 Ok(Some(IncrementalAggregateAnalysis {
592 group_key_names,
593 merge_columns,
594 literal_columns,
595 output_field_names: projection_info.output_field_names,
596 unsupported_exprs,
597 }))
598}
599
600pub async fn rewrite_incremental_aggregate_with_sink_merge(
632 delta_plan: &LogicalPlan,
633 analysis: &IncrementalAggregateAnalysis,
634 engine: &QueryEngineRef,
635 sink_table: TableRef,
636 sink_table_name: &TableName,
637 sink_dirty_filter: Option<Expr>,
638) -> Result<LogicalPlan, Error> {
639 ensure!(
640 analysis.unsupported_exprs.is_empty(),
641 InvalidQuerySnafu {
642 reason: format!(
643 "UNSUPPORTED_INCREMENTAL_AGG: unsupported aggregate expressions {:?}",
644 analysis.unsupported_exprs
645 )
646 }
647 );
648
649 ensure!(
650 !analysis.merge_columns.is_empty(),
651 InvalidQuerySnafu {
652 reason:
653 "UNSUPPORTED_INCREMENTAL_AGG: aggregate query has no mergeable aggregate columns"
654 .to_string()
655 }
656 );
657
658 ensure!(
659 !analysis.group_key_names.is_empty(),
660 InvalidQuerySnafu {
661 reason: "UNSUPPORTED_INCREMENTAL_AGG: global aggregate query is not supported"
662 .to_string()
663 }
664 );
665
666 let delta_alias = "__flow_delta";
667 let sink_alias = "__flow_sink";
668
669 let state_merge = analysis.merge_columns.iter().any(|column| {
670 matches!(
671 &column.merge_op,
672 IncrementalAggregateMergeOp::StateDeltaMerge { .. }
673 )
674 });
675 let mut selected_columns = analysis.group_key_names.clone();
676 selected_columns.extend(
677 analysis
678 .merge_columns
679 .iter()
680 .map(|c| c.output_field_name.clone()),
681 );
682 let mut selected_column_names = HashSet::new();
683 selected_columns.retain(|name| selected_column_names.insert(name.clone()));
684 let mut delta_selected_columns = analysis.group_key_names.clone();
685 delta_selected_columns.extend(
686 analysis
687 .merge_columns
688 .iter()
689 .map(|c| c.input_field_name.clone()),
690 );
691 delta_selected_columns.extend(analysis.literal_columns.iter().cloned());
692 let mut delta_selected_column_names = HashSet::new();
693 delta_selected_columns.retain(|name| delta_selected_column_names.insert(name.clone()));
694
695 let delta_selected_exprs = delta_selected_columns
696 .iter()
697 .cloned()
698 .map(unqualified_col)
699 .collect::<Vec<_>>();
700 let delta_selected = LogicalPlanBuilder::from(delta_plan.clone())
701 .project(delta_selected_exprs)
702 .with_context(|_| DatafusionSnafu {
703 context: "Failed to project delta plan for incremental sink merge".to_string(),
704 })?
705 .alias(delta_alias)
706 .with_context(|_| DatafusionSnafu {
707 context: "Failed to alias delta plan for incremental sink merge".to_string(),
708 })?
709 .build()
710 .with_context(|_| DatafusionSnafu {
711 context: "Failed to build projected delta plan for incremental sink merge".to_string(),
712 })?;
713
714 let table_provider = Arc::new(DfTableProviderAdapter::new(sink_table));
715 let table_source = Arc::new(DefaultTableSource::new(table_provider));
716 let sink_scan = LogicalPlan::TableScan(
717 TableScanBuilder::new(
718 TableReference::Full {
719 catalog: sink_table_name[0].clone().into(),
720 schema: sink_table_name[1].clone().into(),
721 table: sink_table_name[2].clone().into(),
722 },
723 table_source,
724 )
725 .build()
726 .with_context(|_| DatafusionSnafu {
727 context: "Failed to build sink table scan for incremental sink merge".to_string(),
728 })?,
729 );
730
731 let sink_selected_exprs = selected_columns
732 .iter()
733 .cloned()
734 .map(unqualified_col)
735 .collect::<Vec<_>>();
736 let sink_input = if let Some(predicate) = sink_dirty_filter {
737 LogicalPlanBuilder::from(sink_scan)
738 .filter(predicate)
739 .with_context(|_| DatafusionSnafu {
740 context: "Failed to filter sink table scan for incremental sink merge".to_string(),
741 })?
742 .build()
743 .with_context(|_| DatafusionSnafu {
744 context: "Failed to build filtered sink plan for incremental sink merge"
745 .to_string(),
746 })?
747 } else {
748 sink_scan
749 };
750
751 let sink_selected = LogicalPlanBuilder::from(sink_input)
752 .project(sink_selected_exprs)
753 .with_context(|_| DatafusionSnafu {
754 context: "Failed to project sink table scan for incremental sink merge".to_string(),
755 })?
756 .alias(sink_alias)
757 .with_context(|_| DatafusionSnafu {
758 context: "Failed to alias sink plan for incremental sink merge".to_string(),
759 })?
760 .build()
761 .with_context(|_| DatafusionSnafu {
762 context: "Failed to build projected sink plan for incremental sink merge".to_string(),
763 })?;
764
765 let join_keys = (
766 analysis
767 .group_key_names
768 .iter()
769 .cloned()
770 .map(|c| qualified_column(delta_alias, c))
771 .collect::<Vec<_>>(),
772 analysis
773 .group_key_names
774 .iter()
775 .cloned()
776 .map(|c| qualified_column(sink_alias, c))
777 .collect::<Vec<_>>(),
778 );
779
780 let joined = LogicalPlanBuilder::from(delta_selected)
781 .join_detailed(
782 sink_selected,
783 JoinType::Left,
784 join_keys,
785 None,
786 NullEquality::NullEqualsNull,
787 )
788 .with_context(|_| DatafusionSnafu {
789 context: "Failed to left join delta and sink plans for incremental sink merge"
790 .to_string(),
791 })?
792 .build()
793 .with_context(|_| DatafusionSnafu {
794 context: "Failed to build left join plan for incremental sink merge".to_string(),
795 })?;
796
797 let group_key_names = analysis.group_key_names.iter().collect::<HashSet<_>>();
798 let literal_columns = analysis.literal_columns.iter().collect::<HashSet<_>>();
799 let merge_columns = analysis
800 .merge_columns
801 .iter()
802 .map(|c| (&c.output_field_name, c))
803 .collect::<HashMap<_, _>>();
804
805 let mut ordinary_exprs = Vec::with_capacity(analysis.output_field_names.len());
806 let mut state_aggr_exprs = Vec::new();
807 for output_field_name in &analysis.output_field_names {
808 if group_key_names.contains(output_field_name)
809 || literal_columns.contains(output_field_name)
810 {
811 let expr =
812 qualified_col(delta_alias, output_field_name.clone()).alias(output_field_name);
813 ordinary_exprs.push(expr);
814 } else if let Some(merge_col) = merge_columns.get(output_field_name) {
815 if matches!(
816 &merge_col.merge_op,
817 IncrementalAggregateMergeOp::StateDeltaMerge { .. }
818 ) {
819 state_aggr_exprs.push(build_state_delta_merge_expr(engine, merge_col)?);
820 } else {
821 let expr = build_left_join_merge_expr(delta_alias, sink_alias, merge_col)?;
822 ordinary_exprs.push(expr);
823 }
824 } else {
825 return InvalidQuerySnafu {
826 reason: format!(
827 "UNSUPPORTED_INCREMENTAL_AGG: output field {output_field_name} is not covered by group keys, literals, or merge columns"
828 ),
829 }
830 .fail();
831 }
832 }
833
834 if state_merge {
835 let aggregated = LogicalPlanBuilder::from(joined)
836 .aggregate(ordinary_exprs, state_aggr_exprs)
837 .with_context(|_| DatafusionSnafu {
838 context: "Failed to aggregate state delta merge plan".to_string(),
839 })?
840 .build()
841 .with_context(|_| DatafusionSnafu {
842 context: "Failed to build state delta merge plan".to_string(),
843 })?;
844 let output_exprs = analysis
845 .output_field_names
846 .iter()
847 .cloned()
848 .map(unqualified_col)
849 .collect::<Vec<_>>();
850 LogicalPlanBuilder::from(aggregated)
851 .project(output_exprs)
852 .with_context(|_| DatafusionSnafu {
853 context: "Failed to project state delta merge plan".to_string(),
854 })?
855 .build()
856 .with_context(|_| DatafusionSnafu {
857 context: "Failed to finalize incremental aggregate sink merge plan".to_string(),
858 })
859 } else {
860 LogicalPlanBuilder::from(joined)
861 .project(ordinary_exprs)
862 .with_context(|_| DatafusionSnafu {
863 context: "Failed to build projection merge plan for incremental sink merge"
864 .to_string(),
865 })?
866 .build()
867 .with_context(|_| DatafusionSnafu {
868 context: "Failed to finalize incremental aggregate sink merge plan".to_string(),
869 })
870 }
871}
872
873fn build_state_delta_merge_expr(
874 engine: &QueryEngineRef,
875 merge_col: &IncrementalAggregateMergeColumn,
876) -> Result<Expr, Error> {
877 let (function_name, params) = match &merge_col.merge_op {
878 IncrementalAggregateMergeOp::StateDeltaMerge {
879 function_name,
880 params,
881 } => (*function_name, params.clone()),
882 _ => {
883 return InvalidQuerySnafu {
884 reason: "non-state aggregate passed to state delta merge".to_string(),
885 }
886 .fail();
887 }
888 };
889 let Some(udaf) = engine
890 .engine_state()
891 .aggr_function(function_name)
892 .or_else(|| {
893 engine
894 .engine_state()
895 .session_state()
896 .aggregate_functions()
897 .get(function_name)
898 .map(|udaf| udaf.as_ref().clone())
899 })
900 else {
901 return InvalidQuerySnafu {
902 reason: format!("Aggregate function {function_name} is not registered"),
903 }
904 .fail();
905 };
906 let mut args = params;
907 args.push(qualified_col(
908 "__flow_delta",
909 merge_col.input_field_name.clone(),
910 ));
911 args.push(qualified_col(
912 "__flow_sink",
913 merge_col.output_field_name.clone(),
914 ));
915 Ok(udaf.call(args).alias(merge_col.output_field_name.clone()))
916}
917
918fn build_left_join_merge_expr(
919 delta_alias: &str,
920 sink_alias: &str,
921 merge_col: &IncrementalAggregateMergeColumn,
922) -> Result<Expr, Error> {
923 let left = qualified_col(delta_alias, merge_col.input_field_name.clone());
924 let right = qualified_col(sink_alias, merge_col.output_field_name.clone());
925 let merged = match merge_col.merge_op.clone() {
926 IncrementalAggregateMergeOp::Sum => when(is_null(left.clone()), right.clone())
927 .when(is_null(right.clone()), left.clone())
928 .otherwise(binary_expr(left.clone(), Operator::Plus, right.clone()))
929 .with_context(|_| DatafusionSnafu {
930 context: "Failed to build SUM merge expression".to_string(),
931 })?,
932 IncrementalAggregateMergeOp::Min => when(is_null(right.clone()), left.clone())
933 .when(left.clone().lt_eq(right.clone()), left.clone())
934 .otherwise(right.clone())
935 .with_context(|_| DatafusionSnafu {
936 context: "Failed to build MIN merge expression".to_string(),
937 })?,
938 IncrementalAggregateMergeOp::Max => when(is_null(right.clone()), left.clone())
939 .when(left.clone().gt_eq(right.clone()), left.clone())
940 .otherwise(right.clone())
941 .with_context(|_| DatafusionSnafu {
942 context: "Failed to build MAX merge expression".to_string(),
943 })?,
944 IncrementalAggregateMergeOp::BoolAnd => when(is_null(left.clone()), right.clone())
945 .when(is_null(right.clone()), left.clone())
946 .otherwise(and(left.clone(), right.clone()))
947 .with_context(|_| DatafusionSnafu {
948 context: "Failed to build BOOL_AND merge expression".to_string(),
949 })?,
950 IncrementalAggregateMergeOp::BoolOr => when(is_null(left.clone()), right.clone())
951 .when(is_null(right.clone()), left.clone())
952 .otherwise(or(left.clone(), right.clone()))
953 .with_context(|_| DatafusionSnafu {
954 context: "Failed to build BOOL_OR merge expression".to_string(),
955 })?,
956 IncrementalAggregateMergeOp::BitAnd => when(is_null(left.clone()), right.clone())
957 .when(is_null(right.clone()), left.clone())
958 .otherwise(bitwise_and(left.clone(), right.clone()))
959 .with_context(|_| DatafusionSnafu {
960 context: "Failed to build BIT_AND merge expression".to_string(),
961 })?,
962 IncrementalAggregateMergeOp::BitOr => when(is_null(left.clone()), right.clone())
963 .when(is_null(right.clone()), left.clone())
964 .otherwise(bitwise_or(left.clone(), right.clone()))
965 .with_context(|_| DatafusionSnafu {
966 context: "Failed to build BIT_OR merge expression".to_string(),
967 })?,
968 IncrementalAggregateMergeOp::BitXor => when(is_null(left.clone()), right.clone())
969 .when(is_null(right.clone()), left.clone())
970 .otherwise(bitwise_xor(left.clone(), right.clone()))
971 .with_context(|_| DatafusionSnafu {
972 context: "Failed to build BIT_XOR merge expression".to_string(),
973 })?,
974 IncrementalAggregateMergeOp::StateDeltaMerge { .. } => {
975 return InvalidQuerySnafu {
976 reason: "state aggregate must be built with its delta UDAF".to_string(),
977 }
978 .fail();
979 }
980 };
981 Ok(merged.alias(merge_col.output_field_name.clone()))
982}
983
984pub async fn get_table_info_df_schema(
985 catalog_mr: CatalogManagerRef,
986 table_name: TableName,
987) -> Result<(TableRef, Arc<DFSchema>), Error> {
988 let full_table_name = table_name.clone().join(".");
989 let table = catalog_mr
990 .table(&table_name[0], &table_name[1], &table_name[2], None)
991 .await
992 .map_err(BoxedError::new)
993 .context(ExternalSnafu)?
994 .context(TableNotFoundSnafu {
995 name: &full_table_name,
996 })?;
997 let table_info = table.table_info();
998
999 let schema = table_info.meta.schema.clone();
1000
1001 let df_schema: Arc<DFSchema> = Arc::new(
1002 schema
1003 .arrow_schema()
1004 .clone()
1005 .try_into()
1006 .with_context(|_| DatafusionSnafu {
1007 context: format!(
1008 "Failed to convert arrow schema to datafusion schema, arrow_schema={:?}",
1009 schema.arrow_schema()
1010 ),
1011 })?,
1012 );
1013 Ok((table, df_schema))
1014}
1015
1016pub async fn sql_to_df_plan(
1019 query_ctx: QueryContextRef,
1020 engine: QueryEngineRef,
1021 sql: &str,
1022 optimize: bool,
1023) -> Result<LogicalPlan, Error> {
1024 let scheduled_time = query::options::parse_scheduled_time_datetime(&query_ctx.extensions())
1025 .map_err(BoxedError::new)
1026 .context(ExternalSnafu)?;
1027 let stmts = ParserContext::create_with_dialect(
1028 sql,
1029 query_ctx.sql_dialect(),
1030 ParseOptions { scheduled_time },
1031 )
1032 .map_err(BoxedError::new)
1033 .context(ExternalSnafu)?;
1034
1035 ensure!(
1036 stmts.len() == 1,
1037 InvalidQuerySnafu {
1038 reason: format!("Expect only one statement, found {}", stmts.len())
1039 }
1040 );
1041 let stmt = &stmts[0];
1042 let query_stmt = match stmt {
1043 Statement::Tql(tql) => match tql {
1044 Tql::Eval(eval) => {
1045 let eval = eval.clone();
1046 let promql = PromQuery {
1047 start: eval.start,
1048 end: eval.end,
1049 step: eval.step,
1050 query: eval.query,
1051 lookback: eval
1052 .lookback
1053 .unwrap_or_else(|| DEFAULT_LOOKBACK_STRING.to_string()),
1054 alias: eval.alias.clone(),
1055 };
1056
1057 QueryLanguageParser::parse_promql(&promql, &query_ctx)
1058 .map_err(BoxedError::new)
1059 .context(ExternalSnafu)?
1060 }
1061 _ => InvalidQuerySnafu {
1062 reason: format!("TQL statement {tql:?} is not supported, expect only TQL EVAL"),
1063 }
1064 .fail()?,
1065 },
1066 _ => QueryStatement::Sql(stmt.clone()),
1067 };
1068 let plan = engine
1069 .planner()
1070 .plan(&query_stmt, query_ctx.clone())
1071 .await
1072 .map_err(BoxedError::new)
1073 .context(ExternalSnafu)?;
1074
1075 let plan = if optimize {
1076 apply_df_optimizer(plan, &query_ctx).await?
1077 } else {
1078 plan
1079 };
1080 Ok(plan)
1081}
1082
1083pub(crate) async fn gen_plan_with_matching_schema(
1086 sql: &str,
1087 query_ctx: QueryContextRef,
1088 engine: QueryEngineRef,
1089 sink_table_schema: SchemaRef,
1090 primary_key_indices: &[usize],
1091 allow_partial: bool,
1092) -> Result<LogicalPlan, Error> {
1093 gen_plan_with_matching_schema_and_values(
1094 sql,
1095 query_ctx,
1096 engine,
1097 sink_table_schema,
1098 primary_key_indices,
1099 allow_partial,
1100 &BTreeMap::new(),
1101 )
1102 .await
1103}
1104
1105pub(crate) async fn gen_plan_with_matching_schema_and_values(
1107 sql: &str,
1108 query_ctx: QueryContextRef,
1109 engine: QueryEngineRef,
1110 sink_table_schema: SchemaRef,
1111 primary_key_indices: &[usize],
1112 allow_partial: bool,
1113 values: &BTreeMap<String, ScalarValue>,
1114) -> Result<LogicalPlan, Error> {
1115 let plan = sql_to_df_plan(query_ctx.clone(), engine.clone(), sql, false).await?;
1116
1117 let mut add_auto_column = ColumnMatcherRewriter::new_with_values(
1118 sink_table_schema,
1119 primary_key_indices.to_vec(),
1120 allow_partial,
1121 values.clone(),
1122 );
1123 let plan = plan
1124 .clone()
1125 .rewrite(&mut add_auto_column)
1126 .with_context(|_| DatafusionSnafu {
1127 context: "Failed to rewrite plan".to_string(),
1128 })?
1129 .data;
1130 Ok(plan)
1131}
1132
1133pub fn df_plan_to_sql(plan: &LogicalPlan) -> Result<String, Error> {
1134 struct ForceQuoteIdentifiers;
1147 impl datafusion::sql::unparser::dialect::Dialect for ForceQuoteIdentifiers {
1148 fn identifier_quote_style(&self, identifier: &str) -> Option<char> {
1149 let is_plain = !identifier.is_empty()
1150 && !identifier.starts_with(|c: char| c.is_ascii_digit())
1151 && identifier
1152 .chars()
1153 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_');
1154 if is_plain { None } else { Some('"') }
1155 }
1156 }
1157 let unparser = Unparser::new(&ForceQuoteIdentifiers);
1158 let sql = unparser
1160 .plan_to_sql(plan)
1161 .with_context(|_e| DatafusionSnafu {
1162 context: format!("Failed to unparse logical plan {plan:?}"),
1163 })?;
1164 Ok(sql.to_string())
1165}
1166
1167fn is_alias_of_group_expr(group_expr: &Expr, expr: &Expr) -> DfResult<bool> {
1174 Ok(group_expr.name_for_alias()? == expr.name_for_alias()?)
1175}
1176
1177#[derive(Debug, Clone, Default)]
1179pub struct FindGroupByFinalName {
1180 group_exprs: Option<HashSet<datafusion_expr::Expr>>,
1181}
1182
1183impl FindGroupByFinalName {
1184 pub fn get_group_expr_names(&self) -> Option<HashSet<String>> {
1185 self.group_exprs
1186 .as_ref()
1187 .map(|exprs| exprs.iter().map(|expr| expr.qualified_name().1).collect())
1188 }
1189}
1190
1191impl TreeNodeVisitor<'_> for FindGroupByFinalName {
1192 type Node = LogicalPlan;
1193
1194 fn f_down(&mut self, node: &Self::Node) -> datafusion_common::Result<TreeNodeRecursion> {
1195 if let LogicalPlan::Aggregate(aggregate) = node {
1196 self.group_exprs = Some(aggregate.group_expr.iter().cloned().collect());
1197 debug!(
1198 "FindGroupByFinalName: Get Group by exprs from Aggregate: {:?}",
1199 self.group_exprs
1200 );
1201 } else if let LogicalPlan::Distinct(distinct) = node {
1202 debug!("FindGroupByFinalName: Distinct: {}", node);
1203 match distinct {
1204 Distinct::All(input) => {
1205 if let LogicalPlan::TableScan(table_scan) = &**input {
1206 let len = table_scan.projected_schema.fields().len();
1208 let columns = (0..len)
1209 .map(|f| {
1210 let (qualifier, field) =
1211 table_scan.projected_schema.qualified_field(f);
1212 datafusion_common::Column::new(qualifier.cloned(), field.name())
1213 })
1214 .map(datafusion_expr::Expr::Column);
1215 self.group_exprs = Some(columns.collect());
1216 } else {
1217 self.group_exprs = Some(input.expressions().iter().cloned().collect())
1218 }
1219 }
1220 Distinct::On(distinct_on) => {
1221 self.group_exprs = Some(distinct_on.on_expr.iter().cloned().collect())
1222 }
1223 }
1224 debug!(
1225 "FindGroupByFinalName: Get Group by exprs from Distinct: {:?}",
1226 self.group_exprs
1227 );
1228 }
1229
1230 Ok(TreeNodeRecursion::Continue)
1231 }
1232
1233 fn f_up(&mut self, node: &Self::Node) -> datafusion_common::Result<TreeNodeRecursion> {
1235 if let LogicalPlan::Projection(projection) = node {
1236 for expr in &projection.expr {
1239 let Some(group_exprs) = &mut self.group_exprs else {
1240 return Ok(TreeNodeRecursion::Continue);
1241 };
1242 if let datafusion_expr::Expr::Alias(alias) = expr {
1243 let mut renamed_group_expr = None;
1245 for group_expr in group_exprs.iter() {
1246 if is_alias_of_group_expr(group_expr, alias.expr.as_ref())? {
1247 renamed_group_expr = Some(group_expr.clone());
1248 break;
1249 }
1250 }
1251 if let Some(group_expr) = renamed_group_expr {
1252 group_exprs.remove(&group_expr);
1253 group_exprs.insert(expr.clone());
1254 }
1255 }
1256 }
1257 }
1258 debug!("Aliased group by exprs: {:?}", self.group_exprs);
1259 Ok(TreeNodeRecursion::Continue)
1260 }
1261}
1262
1263#[derive(Debug)]
1270pub struct ColumnMatcherRewriter {
1271 pub schema: SchemaRef,
1273 sink_schema: SchemaRef,
1274 values: BTreeMap<String, ScalarValue>,
1275 pub is_rewritten: bool,
1276 pub primary_key_indices: Vec<usize>,
1277 pub allow_partial: bool,
1278}
1279
1280impl ColumnMatcherRewriter {
1281 pub fn new(schema: SchemaRef, primary_key_indices: Vec<usize>, allow_partial: bool) -> Self {
1282 Self {
1283 schema: schema.clone(),
1284 sink_schema: schema,
1285 values: BTreeMap::new(),
1286 is_rewritten: false,
1287 primary_key_indices,
1288 allow_partial,
1289 }
1290 }
1291
1292 pub fn new_with_values(
1295 sink_schema: SchemaRef,
1296 primary_key_indices: Vec<usize>,
1297 allow_partial: bool,
1298 values: BTreeMap<String, ScalarValue>,
1299 ) -> Self {
1300 let value_names = values.keys().collect::<BTreeSet<_>>();
1301 let ordinary_columns = sink_schema
1302 .column_schemas()
1303 .iter()
1304 .filter(|column| !value_names.contains(&column.name))
1305 .cloned()
1306 .collect();
1307 let ordinary_schema = Arc::new(datatypes::schema::Schema::new(ordinary_columns));
1308 let ordinary_primary_key_indices = primary_key_indices
1309 .into_iter()
1310 .filter_map(|index| {
1311 let name = &sink_schema.column_schemas()[index].name;
1312 ordinary_schema
1313 .column_schemas()
1314 .iter()
1315 .position(|column| column.name == *name)
1316 })
1317 .collect();
1318 Self {
1319 schema: ordinary_schema,
1320 sink_schema,
1321 values,
1322 is_rewritten: false,
1323 primary_key_indices: ordinary_primary_key_indices,
1324 allow_partial,
1325 }
1326 }
1327
1328 fn validate_values(&self, output_exprs: &[Expr]) -> DfResult<()> {
1329 let output_names = output_exprs
1330 .iter()
1331 .map(|expr| expr.qualified_name().1)
1332 .collect::<Vec<_>>();
1333 let duplicated = duplicate_names(&output_names);
1334 if !duplicated.is_empty() {
1335 return Err(DataFusionError::Plan(format!(
1336 "Flow output schema contains duplicate column(s) {:?}. {}",
1337 duplicated,
1338 format_flow_sink_schema_mismatch(output_exprs, self.sink_schema.as_ref())
1339 )));
1340 }
1341 for (name, value) in &self.values {
1342 let Some(column) = self
1343 .sink_schema
1344 .column_schemas()
1345 .iter()
1346 .find(|column| column.name == *name)
1347 else {
1348 return Err(DataFusionError::Plan(format!(
1349 "Injected flow value column '{name}' is not found in sink schema"
1350 )));
1351 };
1352 if output_names.iter().any(|output| output == name) {
1353 return Err(DataFusionError::Plan(format!(
1354 "Injected flow value column '{name}' collides with flow output"
1355 )));
1356 }
1357 let value_type = ConcreteDataType::from_arrow_type(&value.data_type());
1358 if value_type != column.data_type {
1359 return Err(DataFusionError::Plan(format!(
1360 "Injected flow value column '{name}' has type {:?}, but sink column has type {:?}",
1361 value_type, column.data_type
1362 )));
1363 }
1364 }
1365 Ok(())
1366 }
1367
1368 fn inject_values_in_physical_order(&self, exprs: Vec<Expr>) -> DfResult<Vec<Expr>> {
1369 if self.values.is_empty() {
1370 return Ok(exprs);
1371 }
1372 let mut ordinary = exprs
1373 .into_iter()
1374 .map(|expr| (expr.qualified_name().1, expr))
1375 .collect::<HashMap<_, _>>();
1376 self.sink_schema
1377 .column_schemas()
1378 .iter()
1379 .map(|column| {
1380 if let Some(value) = self.values.get(&column.name) {
1381 Ok(Expr::Literal(value.clone(), None).alias(column.name.clone()))
1382 } else {
1383 ordinary.remove(&column.name).ok_or_else(|| {
1384 DataFusionError::Plan(format!(
1385 "Flow output is missing sink column '{}' after schema matching",
1386 column.name
1387 ))
1388 })
1389 }
1390 })
1391 .collect()
1392 }
1393
1394 fn modify_project_exprs(
1396 &mut self,
1397 mut exprs: Vec<Expr>,
1398 input_schema: &DFSchema,
1399 ) -> DfResult<Vec<Expr>> {
1400 self.validate_values(&exprs)?;
1401 if self.allow_partial {
1402 let exprs = self.modify_project_exprs_with_partial(exprs)?;
1403 return self.inject_values_in_physical_order(exprs);
1404 }
1405
1406 let original_exprs = exprs.clone();
1407
1408 let all_names = self
1409 .schema
1410 .column_schemas()
1411 .iter()
1412 .map(|c| c.name.clone())
1413 .collect::<BTreeSet<_>>();
1414 let query_col_cnt = exprs.len();
1416 let table_col_cnt = self.schema.column_schemas().len();
1417 debug!("query_col_cnt={query_col_cnt}, table_col_cnt={table_col_cnt}");
1418
1419 let placeholder_ts_expr =
1420 datafusion::logical_expr::lit(ScalarValue::TimestampMillisecond(Some(0), None))
1421 .alias(AUTO_CREATED_PLACEHOLDER_TS_COL);
1422
1423 if query_col_cnt == table_col_cnt {
1424 } else if query_col_cnt + 1 == table_col_cnt {
1426 let last_col_schema = self.schema.column_schemas().last().unwrap();
1427
1428 if last_col_schema.name == AUTO_CREATED_PLACEHOLDER_TS_COL
1430 && self.schema.timestamp_index() == Some(table_col_cnt - 1)
1431 {
1432 exprs.push(placeholder_ts_expr);
1433 } else if last_col_schema.data_type.is_timestamp() {
1434 exprs.push(datafusion::prelude::now().alias(&last_col_schema.name));
1436 } else {
1437 return Err(DataFusionError::Plan(format_flow_sink_schema_mismatch(
1438 &original_exprs,
1439 self.schema.as_ref(),
1440 )));
1441 }
1442 } else if query_col_cnt + 2 == table_col_cnt {
1443 let mut col_iter = self.schema.column_schemas().iter().rev();
1444 let last_col_schema = col_iter.next().unwrap();
1445 let second_last_col_schema = col_iter.next().unwrap();
1446 if second_last_col_schema.data_type.is_timestamp() {
1447 exprs.push(datafusion::prelude::now().alias(&second_last_col_schema.name));
1448 } else {
1449 return Err(DataFusionError::Plan(format!(
1450 "Expect the second last column in the table to be timestamp column, found column {} with type {:?}",
1451 second_last_col_schema.name, second_last_col_schema.data_type
1452 )));
1453 }
1454
1455 if last_col_schema.name == AUTO_CREATED_PLACEHOLDER_TS_COL
1456 && self.schema.timestamp_index() == Some(table_col_cnt - 1)
1457 {
1458 exprs.push(placeholder_ts_expr);
1459 } else {
1460 return Err(DataFusionError::Plan(format!(
1461 "Expect timestamp column {}, found {:?}",
1462 AUTO_CREATED_PLACEHOLDER_TS_COL, last_col_schema
1463 )));
1464 }
1465 } else {
1466 return Err(DataFusionError::Plan(format_flow_sink_schema_mismatch(
1467 &original_exprs,
1468 self.schema.as_ref(),
1469 )));
1470 }
1471
1472 self.inject_values_in_physical_order(self.match_extra_output_columns(
1473 exprs,
1474 input_schema,
1475 &original_exprs,
1476 &all_names,
1477 )?)
1478 }
1479
1480 fn match_extra_output_columns(
1491 &self,
1492 mut exprs: Vec<Expr>,
1493 input_schema: &DFSchema,
1494 original_exprs: &[Expr],
1495 all_names: &BTreeSet<String>,
1496 ) -> DfResult<Vec<Expr>> {
1497 let mut output_names = exprs
1498 .iter()
1499 .map(|expr| expr.qualified_name().1)
1500 .collect::<Vec<_>>();
1501 let output_name_set = output_names.iter().cloned().collect::<BTreeSet<_>>();
1502 let extra_expr_indices = output_names
1503 .iter()
1504 .enumerate()
1505 .filter_map(|(idx, name)| (!all_names.contains(name)).then_some(idx))
1506 .collect::<Vec<_>>();
1507 let missing_sink_indices = self
1508 .schema
1509 .column_schemas()
1510 .iter()
1511 .enumerate()
1512 .filter_map(|(idx, column)| (!output_name_set.contains(&column.name)).then_some(idx))
1513 .collect::<Vec<_>>();
1514
1515 if extra_expr_indices.is_empty() && missing_sink_indices.is_empty() {
1516 return Ok(exprs);
1517 }
1518
1519 if extra_expr_indices.len() != missing_sink_indices.len() {
1520 return Err(DataFusionError::Plan(format_flow_sink_schema_mismatch(
1521 original_exprs,
1522 self.schema.as_ref(),
1523 )));
1524 }
1525
1526 let mut positional_matches = Vec::new();
1527 for expr_idx in extra_expr_indices {
1528 if !missing_sink_indices.contains(&expr_idx) {
1529 return Err(DataFusionError::Plan(format_flow_sink_schema_mismatch(
1530 original_exprs,
1531 self.schema.as_ref(),
1532 )));
1533 }
1534
1535 let target_col_schema = &self.schema.column_schemas()[expr_idx];
1536 let expr_type =
1537 ConcreteDataType::from_arrow_type(&exprs[expr_idx].get_type(input_schema)?);
1538 if is_obviously_incompatible_positional_match(&expr_type, &target_col_schema.data_type)
1539 {
1540 return Err(DataFusionError::Plan(format!(
1541 "Cannot match flow output column '{}' to sink column '{}' by position: incompatible data types, flow output type is {:?}, sink column type is {:?}. {}",
1542 output_names[expr_idx],
1543 target_col_schema.name,
1544 expr_type,
1545 target_col_schema.data_type,
1546 format_flow_sink_schema_mismatch(original_exprs, self.schema.as_ref())
1547 )));
1548 }
1549
1550 let target_name = target_col_schema.name.clone();
1551 positional_matches.push(format!(
1552 "{} -> {} (flow output type: {:?}, sink column type: {:?})",
1553 output_names[expr_idx], target_name, expr_type, target_col_schema.data_type
1554 ));
1555 exprs[expr_idx] = exprs[expr_idx].clone().alias(target_name.clone());
1556 output_names[expr_idx] = target_name;
1557 }
1558
1559 if !positional_matches.is_empty() {
1560 debug!(
1561 "Matched flow output columns to sink columns by position: {:?}",
1562 positional_matches
1563 );
1564 }
1565
1566 let duplicated_output_names = duplicate_names(&output_names);
1567 if !duplicated_output_names.is_empty() {
1568 return Err(DataFusionError::Plan(format!(
1569 "Flow output schema contains duplicate column(s) after schema matching {:?}. {}",
1570 duplicated_output_names,
1571 format_flow_sink_schema_mismatch(&exprs, self.schema.as_ref())
1572 )));
1573 }
1574
1575 Ok(exprs)
1576 }
1577
1578 fn modify_project_exprs_with_partial(&mut self, exprs: Vec<Expr>) -> DfResult<Vec<Expr>> {
1579 let table_col_cnt = self.schema.column_schemas().len();
1580 let query_col_cnt = exprs.len();
1581
1582 if query_col_cnt > table_col_cnt {
1583 return Err(DataFusionError::Plan(format_flow_sink_schema_mismatch(
1584 &exprs,
1585 self.schema.as_ref(),
1586 )));
1587 }
1588
1589 let name_to_expr: HashMap<String, Expr> = exprs
1590 .clone()
1591 .into_iter()
1592 .map(|e| (e.qualified_name().1, e))
1593 .collect();
1594
1595 let required_columns = self.required_columns_for_partial();
1596 let missing: Vec<_> = required_columns
1597 .iter()
1598 .filter(|name| !name_to_expr.contains_key(*name))
1599 .cloned()
1600 .collect();
1601 if !missing.is_empty() {
1602 return Err(DataFusionError::Plan(format!(
1603 "Column(s) {:?} required by sink table are missing from flow output when merge_mode=last_non_null. {}",
1604 missing,
1605 format_flow_sink_schema_mismatch(&exprs, self.schema.as_ref())
1606 )));
1607 }
1608
1609 let placeholder_ts_expr =
1610 datafusion::logical_expr::lit(ScalarValue::TimestampMillisecond(Some(0), None))
1611 .alias(AUTO_CREATED_PLACEHOLDER_TS_COL);
1612
1613 let timestamp_index = self.schema.timestamp_index();
1614 let mut remap = name_to_expr;
1615 let mut new_exprs = Vec::with_capacity(table_col_cnt);
1616
1617 for (idx, col_schema) in self.schema.column_schemas().iter().enumerate() {
1618 let col_name = col_schema.name.clone();
1619 if let Some(expr) = remap.remove(&col_name) {
1620 let expr = if expr.qualified_name().1 == col_name {
1621 expr
1622 } else {
1623 expr.alias(col_name.clone())
1624 };
1625 new_exprs.push(expr);
1626 continue;
1627 }
1628
1629 if col_name == AUTO_CREATED_PLACEHOLDER_TS_COL && timestamp_index == Some(idx) {
1630 new_exprs.push(placeholder_ts_expr.clone());
1631 continue;
1632 }
1633
1634 if col_name == AUTO_CREATED_UPDATE_AT_TS_COL && col_schema.data_type.is_timestamp() {
1635 new_exprs.push(datafusion::prelude::now().alias(&col_name));
1636 continue;
1637 }
1638
1639 new_exprs.push(Self::null_expr(col_schema));
1640 }
1641
1642 if !remap.is_empty() {
1643 let extra: Vec<_> = remap.keys().cloned().collect();
1644 return Err(DataFusionError::Plan(format!(
1645 "Flow output has extra column(s) {:?} not found in sink schema when merge_mode=last_non_null. {}",
1646 extra,
1647 format_flow_sink_schema_mismatch(&exprs, self.schema.as_ref())
1648 )));
1649 }
1650
1651 Ok(new_exprs)
1652 }
1653
1654 fn null_expr(col_schema: &ColumnSchema) -> Expr {
1655 Expr::Literal(ScalarValue::Null, None).alias(col_schema.name.clone())
1656 }
1657
1658 fn required_columns_for_partial(&self) -> HashSet<String> {
1659 let mut required = HashSet::new();
1660 for idx in &self.primary_key_indices {
1661 if let Some(col) = self.schema.column_schemas().get(*idx) {
1662 required.insert(col.name.clone());
1663 }
1664 }
1665
1666 if let Some(ts_idx) = self.schema.timestamp_index()
1667 && let Some(col) = self.schema.column_schemas().get(ts_idx)
1668 && col.name != AUTO_CREATED_PLACEHOLDER_TS_COL
1669 {
1670 required.insert(col.name.clone());
1671 }
1672
1673 required
1674 }
1675}
1676
1677fn is_obviously_incompatible_positional_match(
1678 expr_type: &ConcreteDataType,
1679 sink_type: &ConcreteDataType,
1680) -> bool {
1681 if expr_type.is_null() || expr_type == sink_type {
1686 return false;
1687 }
1688
1689 expr_type.is_timestamp() != sink_type.is_timestamp()
1690 || expr_type.is_string() != sink_type.is_string()
1691 || expr_type.is_boolean() != sink_type.is_boolean()
1692 || expr_type.is_json() != sink_type.is_json()
1693 || expr_type.is_vector() != sink_type.is_vector()
1694}
1695
1696fn duplicate_names(names: &[String]) -> Vec<String> {
1697 let mut seen = HashSet::new();
1698 let mut duplicated = BTreeSet::new();
1699 for name in names {
1700 if !seen.insert(name.as_str()) {
1701 duplicated.insert(name.as_str());
1702 }
1703 }
1704 duplicated.into_iter().map(str::to_string).collect()
1705}
1706
1707fn format_flow_sink_schema_mismatch(
1708 query_exprs: &[Expr],
1709 sink_schema: &datatypes::schema::Schema,
1710) -> String {
1711 let flow_output_columns = query_exprs
1712 .iter()
1713 .map(|expr| expr.qualified_name().1)
1714 .collect::<Vec<_>>();
1715 let sink_table_columns = sink_schema
1716 .column_schemas()
1717 .iter()
1718 .map(|col| col.name.clone())
1719 .collect::<Vec<_>>();
1720
1721 let flow_output_set = flow_output_columns.iter().cloned().collect::<HashSet<_>>();
1722 let sink_table_set = sink_table_columns.iter().cloned().collect::<HashSet<_>>();
1723
1724 let mut extra_flow_columns = flow_output_columns
1725 .iter()
1726 .filter(|name| !sink_table_set.contains(*name))
1727 .cloned()
1728 .collect::<Vec<_>>();
1729 extra_flow_columns.sort();
1730 extra_flow_columns.dedup();
1731
1732 let mut missing_sink_columns = sink_table_columns
1733 .iter()
1734 .filter(|name| !flow_output_set.contains(*name))
1735 .cloned()
1736 .collect::<Vec<_>>();
1737 missing_sink_columns.sort();
1738 missing_sink_columns.dedup();
1739
1740 format!(
1741 "Flow output schema does not match sink table schema: found {} flow output columns and {} sink table columns. flow output columns: {:?}, sink table columns: {:?}, extra flow columns not in sink: {:?}, missing sink columns from flow output: {:?}",
1742 flow_output_columns.len(),
1743 sink_table_columns.len(),
1744 flow_output_columns,
1745 sink_table_columns,
1746 extra_flow_columns,
1747 missing_sink_columns
1748 )
1749}
1750
1751impl TreeNodeRewriter for ColumnMatcherRewriter {
1752 type Node = LogicalPlan;
1753 fn f_down(&mut self, mut node: Self::Node) -> DfResult<Transformed<Self::Node>> {
1754 if self.is_rewritten {
1755 return Ok(Transformed::no(node));
1756 }
1757
1758 if let LogicalPlan::Distinct(Distinct::All(_)) = &node {
1760 let mut exprs = vec![];
1761
1762 for field in node.schema().fields().iter() {
1763 exprs.push(Expr::Column(datafusion::common::Column::new_unqualified(
1764 field.name(),
1765 )));
1766 }
1767
1768 let projection =
1769 LogicalPlan::Projection(Projection::try_new(exprs, Arc::new(node.clone()))?);
1770
1771 node = projection;
1772 }
1773 else if let LogicalPlan::TableScan(table_scan) = node {
1775 let mut exprs = vec![];
1776
1777 for field in table_scan.projected_schema.fields().iter() {
1778 exprs.push(Expr::Column(datafusion::common::Column::new(
1779 Some(table_scan.table_name.clone()),
1780 field.name(),
1781 )));
1782 }
1783
1784 let projection = LogicalPlan::Projection(Projection::try_new(
1785 exprs,
1786 Arc::new(LogicalPlan::TableScan(table_scan)),
1787 )?);
1788
1789 node = projection;
1790 }
1791
1792 if let LogicalPlan::Projection(project) = &node {
1796 let exprs = project.expr.clone();
1797 let exprs = self.modify_project_exprs(exprs, project.input.schema())?;
1798
1799 self.is_rewritten = true;
1800 let new_plan =
1801 node.with_new_exprs(exprs, node.inputs().into_iter().cloned().collect())?;
1802 Ok(Transformed::yes(new_plan))
1803 } else {
1804 let mut exprs = vec![];
1806 for field in node.schema().fields().iter() {
1807 exprs.push(Expr::Column(datafusion::common::Column::new_unqualified(
1808 field.name(),
1809 )));
1810 }
1811 let exprs = self.modify_project_exprs(exprs, node.schema())?;
1812 self.is_rewritten = true;
1813 let new_plan =
1814 LogicalPlan::Projection(Projection::try_new(exprs, Arc::new(node.clone()))?);
1815 Ok(Transformed::yes(new_plan))
1816 }
1817 }
1818
1819 fn f_up(&mut self, node: Self::Node) -> DfResult<Transformed<Self::Node>> {
1821 node.recompute_schema().map(Transformed::yes)
1822 }
1823}
1824
1825#[derive(Debug)]
1827pub struct AddFilterRewriter {
1828 extra_filter: Expr,
1829 is_rewritten: bool,
1830}
1831
1832impl AddFilterRewriter {
1833 pub fn new(filter: Expr) -> Self {
1834 Self {
1835 extra_filter: filter,
1836 is_rewritten: false,
1837 }
1838 }
1839}
1840
1841impl TreeNodeRewriter for AddFilterRewriter {
1842 type Node = LogicalPlan;
1843 fn f_up(&mut self, node: Self::Node) -> DfResult<Transformed<Self::Node>> {
1844 if self.is_rewritten {
1845 return Ok(Transformed::no(node));
1846 }
1847 match node {
1848 LogicalPlan::Filter(mut filter) => {
1849 filter.predicate = filter.predicate.and(self.extra_filter.clone());
1850 self.is_rewritten = true;
1851 Ok(Transformed::yes(LogicalPlan::Filter(filter)))
1852 }
1853 LogicalPlan::TableScan(_) => {
1854 let filter =
1856 datafusion_expr::Filter::try_new(self.extra_filter.clone(), Arc::new(node))?;
1857 self.is_rewritten = true;
1858 Ok(Transformed::yes(LogicalPlan::Filter(filter)))
1859 }
1860 _ => Ok(Transformed::no(node)),
1861 }
1862 }
1863}