1use std::collections::BTreeMap;
18use std::sync::Arc;
19#[cfg(test)]
20use std::sync::atomic::AtomicUsize;
21use std::sync::atomic::{AtomicBool, Ordering};
22
23use common_base::memory_limit::MemoryLimit;
24use common_config::Configurable;
25use common_error::ext::BoxedError;
26use common_meta::distributed_time_constants::BASE_HEARTBEAT_INTERVAL;
27use common_meta::key::TableMetadataManagerRef;
28use common_options::memory::MemoryOptions;
29use common_recordbatch::map_dictionary_to_values_data_type;
30use common_stat::get_total_cpu_cores;
31use common_telemetry::logging::{LoggingOptions, TracingOptions};
32use common_telemetry::{error, info};
33use datafusion_common::TableReference;
34use datafusion_common::tree_node::TreeNode;
35use datafusion_expr::logical_plan::Distinct;
36use datafusion_expr::{Expr, LogicalPlan};
37use datatypes::schema::{ColumnSchema, SchemaRef};
38use itertools::Itertools;
39use meta_client::MetaClientOptions;
40use query::QueryEngine;
41use query::options::QueryOptions;
42use serde::{Deserialize, Serialize};
43use servers::grpc::GrpcOptions;
44use servers::http::HttpOptions;
45use snafu::{IntoError, OptionExt, ResultExt, ensure};
46use store_api::storage::{ConcreteDataType, RegionId};
47use tokio::sync::RwLock;
48use tokio::time::Instant;
49
50use crate::adapter::stateless::StatelessFlow;
51use crate::adapter::table_source::ManagedTableSource;
52use crate::adapter::util::{
53 relation_desc_to_column_schemas_with_fallback, table_info_value_to_relation_desc,
54};
55use crate::batching_mode::BatchingModeOptions;
56use crate::batching_mode::frontend_client::FrontendClient;
57use crate::batching_mode::utils::sql_to_df_plan;
58use crate::error::{
59 DatafusionSnafu, Error, ExternalSnafu, FlowNotFoundSnafu, InsertIntoFlowSnafu, InternalSnafu,
60 InvalidQuerySnafu, UnexpectedSnafu,
61};
62use crate::repr::{ColumnType, DiffRow, RelationDesc, Row};
63use crate::{CreateFlowArgs, FlowId, TableName};
64
65pub(crate) mod flownode_impl;
66pub(crate) mod stateless;
67pub(crate) mod table_source;
68#[cfg(test)]
69mod tests;
70pub(crate) mod util;
71
72fn output_column_schemas(
76 plan: &LogicalPlan,
77 source_schema: &SchemaRef,
78) -> Result<(Vec<ColumnSchema>, Vec<Option<usize>>), Error> {
79 let expressions = match plan {
82 LogicalPlan::Projection(projection) => Some(&projection.expr),
83 LogicalPlan::Distinct(Distinct::All(input)) => match input.as_ref() {
84 LogicalPlan::Projection(projection) => Some(&projection.expr),
85 _ => None,
86 },
87 _ => None,
88 };
89 let is_pass_through = matches!(plan, LogicalPlan::TableScan(_) | LogicalPlan::Filter(_))
90 || matches!(plan, LogicalPlan::Distinct(Distinct::All(input)) if matches!(input.as_ref(), LogicalPlan::TableScan(_) | LogicalPlan::Filter(_)));
91 let mut lineage = Vec::new();
92 let columns = plan
93 .schema()
94 .fields()
95 .iter()
96 .enumerate()
97 .map(|(idx, field)| {
98 let mut column = ColumnSchema::try_from(field.as_ref())
99 .map_err(BoxedError::new)
100 .context(ExternalSnafu)?;
101 let source_index = expressions
102 .and_then(|exprs| {
103 let expr = exprs.get(idx)?;
104 let expr = match expr {
105 Expr::Column(column) => Some(column),
106 Expr::Alias(alias) => match alias.expr.as_ref() {
107 Expr::Column(column) => Some(column),
108 _ => None,
109 },
110 _ => None,
111 }?;
112 source_schema.column_index_by_name(&expr.name)
113 })
114 .or_else(|| {
115 is_pass_through
116 .then(|| source_schema.column_index_by_name(field.name()))
117 .flatten()
118 });
119 if let Some(source_index) = source_index {
120 let mut source = source_schema.column_schemas()[source_index].clone();
121 source.name = field.name().clone();
122 source.data_type = map_dictionary_to_values_data_type(&source.data_type);
123 column = source;
124 } else {
125 column.data_type = map_dictionary_to_values_data_type(&column.data_type);
126 if expressions.is_some() {
130 column = column.with_time_index(false);
131 }
132 }
133 lineage.push(source_index);
134 Ok(column)
135 })
136 .collect::<Result<Vec<_>, Error>>()?;
137 Ok((columns, lineage))
138}
139
140fn relation_desc_from_output(
141 columns: &[ColumnSchema],
142 lineage: &[Option<usize>],
143 source_primary_key_indices: &[usize],
144 plan: &LogicalPlan,
145) -> RelationDesc {
146 let mut keys = source_primary_key_indices
147 .iter()
148 .filter_map(|source_index| {
149 lineage
150 .iter()
151 .position(|index| index == &Some(*source_index))
152 })
153 .collect_vec();
154 if matches!(plan, LogicalPlan::Distinct(Distinct::All(_))) {
157 keys = columns
158 .iter()
159 .positions(|column| !column.is_time_index())
160 .collect();
161 }
162 let time_index = columns.iter().position(ColumnSchema::is_time_index);
163 RelationDesc {
164 typ: crate::repr::RelationType {
165 column_types: columns
166 .iter()
167 .map(|column| ColumnType::new(column.data_type.clone(), column.is_nullable()))
168 .collect(),
169 keys: if keys.is_empty() {
170 vec![]
171 } else {
172 vec![crate::repr::Key::from(keys)]
173 },
174 time_index,
175 auto_columns: vec![],
176 },
177 names: columns
178 .iter()
179 .map(|column| Some(column.name.clone()))
180 .collect(),
181 }
182}
183
184fn default_num_workers() -> usize {
185 get_total_cpu_cores().div_ceil(2)
186}
187
188pub(crate) fn is_explicit_source_timestamp_compatibility(
194 output_schema: &[ColumnSchema],
195 output_lineage: &[Option<usize>],
196 sink_schema: &[ColumnSchema],
197 source_schema: &SchemaRef,
198) -> bool {
199 if sink_schema.len() != output_schema.len() + 1 || output_lineage.len() != output_schema.len() {
200 return false;
201 }
202 if !output_schema
203 .iter()
204 .zip(&sink_schema[..output_schema.len()])
205 .all(|(output, sink)| output.data_type == sink.data_type)
206 {
207 return false;
208 }
209
210 let Some(source_timestamp_index) = source_schema.timestamp_index() else {
211 return false;
212 };
213 let sink_timestamp = &sink_schema[output_schema.len()];
214 sink_timestamp.data_type == source_schema.column_schemas()[source_timestamp_index].data_type
215 && sink_timestamp.data_type.is_timestamp()
216 && sink_timestamp.is_time_index()
217 && sink_timestamp.default_constraint().is_some()
218 && sink_timestamp.name != AUTO_CREATED_UPDATE_AT_TS_COL
219 && sink_timestamp.name != AUTO_CREATED_PLACEHOLDER_TS_COL
220 && !output_lineage.contains(&Some(source_timestamp_index))
221}
222
223pub const AUTO_CREATED_PLACEHOLDER_TS_COL: &str = "__ts_placeholder";
224pub const AUTO_CREATED_UPDATE_AT_TS_COL: &str = "update_at";
225
226pub(crate) fn resolve_sink_layout(
232 output_schema: &[ColumnSchema],
233 sink_schema: &[ColumnSchema],
234) -> Result<Vec<ColumnSchema>, Error> {
235 ensure!(
236 sink_schema.len() >= output_schema.len() && sink_schema.len() - output_schema.len() <= 2,
237 InvalidQuerySnafu {
238 reason: format!(
239 "Flow output has {} columns, but sink has {} columns; only zero, one, or two trailing auto columns are supported",
240 output_schema.len(),
241 sink_schema.len()
242 )
243 }
244 );
245 let suffix_len = sink_schema.len() - output_schema.len();
246 for (idx, (output, sink)) in output_schema.iter().zip(sink_schema.iter()).enumerate() {
247 ensure!(
248 output.data_type == sink.data_type,
249 InvalidQuerySnafu {
250 reason: format!(
251 "Flow output column {idx} has type {:?}, but sink column {} has type {:?}",
252 output.data_type, sink.name, sink.data_type
253 )
254 }
255 );
256 }
257 let suffix = &sink_schema[output_schema.len()..];
258 match suffix_len {
259 0 => Ok(vec![]),
260 1 => {
261 let column = &suffix[0];
262 ensure!(
263 column.name == AUTO_CREATED_UPDATE_AT_TS_COL && column.data_type.is_timestamp(),
264 InvalidQuerySnafu {
265 reason: format!(
266 "The trailing sink column must be timestamp {}",
267 AUTO_CREATED_UPDATE_AT_TS_COL
268 )
269 }
270 );
271 Ok(suffix.to_vec())
272 }
273 2 => {
274 let update_at = &suffix[0];
275 let placeholder = &suffix[1];
276 ensure!(
277 update_at.name == AUTO_CREATED_UPDATE_AT_TS_COL
278 && update_at.data_type.is_timestamp()
279 && placeholder.name == AUTO_CREATED_PLACEHOLDER_TS_COL
280 && placeholder.data_type.is_timestamp()
281 && placeholder.is_time_index(),
282 InvalidQuerySnafu {
283 reason: "The two trailing sink columns must be timestamp update_at followed by timestamp time-index __ts_placeholder".to_string()
284 }
285 );
286 Ok(suffix.to_vec())
287 }
288 _ => unreachable!(),
289 }
290}
291
292pub(crate) fn sink_output_column_count(sink_schema: &[ColumnSchema]) -> Result<usize, Error> {
295 let mut count = sink_schema.len();
296 if sink_schema
297 .last()
298 .is_some_and(|column| column.name == AUTO_CREATED_PLACEHOLDER_TS_COL)
299 {
300 let placeholder = &sink_schema[count - 1];
301 ensure!(
302 placeholder.data_type.is_timestamp() && placeholder.is_time_index(),
303 InvalidQuerySnafu {
304 reason: format!(
305 "Auto-created sink column {} must be a timestamp time index",
306 AUTO_CREATED_PLACEHOLDER_TS_COL
307 )
308 }
309 );
310 count -= 1;
311 }
312 if sink_schema
313 .get(count.saturating_sub(1))
314 .is_some_and(|column| column.name == AUTO_CREATED_UPDATE_AT_TS_COL)
315 {
316 ensure!(
317 sink_schema[count - 1].data_type.is_timestamp(),
318 InvalidQuerySnafu {
319 reason: format!(
320 "Auto-created sink column {} must be a timestamp",
321 AUTO_CREATED_UPDATE_AT_TS_COL
322 )
323 }
324 );
325 count -= 1;
326 }
327 Ok(count)
328}
329
330pub(crate) fn validate_sink_layout(
331 output_schema: &[ColumnSchema],
332 sink_schema: &[ColumnSchema],
333) -> Result<(), Error> {
334 resolve_sink_layout(output_schema, sink_schema).map(|_| ())
335}
336
337pub(crate) fn validate_auto_column_names(output_schema: &[ColumnSchema]) -> Result<(), Error> {
338 for column in output_schema {
339 ensure!(
340 column.name != AUTO_CREATED_UPDATE_AT_TS_COL
341 && column.name != AUTO_CREATED_PLACEHOLDER_TS_COL,
342 InvalidQuerySnafu {
343 reason: format!(
344 "Flow output column {} is reserved for an auto-created sink column",
345 column.name
346 )
347 }
348 );
349 }
350 Ok(())
351}
352
353pub(crate) fn validate_sink_layout_with_suffix(
354 output_schema: &[ColumnSchema],
355 sink_schema: &[ColumnSchema],
356 suffix: &[ColumnSchema],
357) -> Result<(), Error> {
358 ensure!(
359 sink_schema.len() == output_schema.len() + suffix.len()
360 && sink_schema[output_schema.len()..] == *suffix,
361 InvalidQuerySnafu {
362 reason: "Stored sink auto-column layout no longer matches the sink schema".to_string()
363 }
364 );
365 for (idx, (output, sink)) in output_schema
366 .iter()
367 .zip(&sink_schema[..output_schema.len()])
368 .enumerate()
369 {
370 ensure!(
371 output.data_type == sink.data_type,
372 InvalidQuerySnafu {
373 reason: format!(
374 "Flow output column {idx} has type {:?}, but sink column {} has type {:?}",
375 output.data_type, sink.name, sink.data_type
376 )
377 }
378 );
379 }
380 Ok(())
381}
382
383#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
384#[serde(default)]
385pub struct FlowConfig {
386 #[deprecated(note = "flow workers have been removed; this field is ignored")]
388 #[serde(default = "default_num_workers")]
389 pub num_workers: usize,
390 pub batching_mode: BatchingModeOptions,
391}
392
393#[allow(deprecated)]
394impl Default for FlowConfig {
395 fn default() -> Self {
396 Self {
397 num_workers: get_total_cpu_cores().div_ceil(2),
398 batching_mode: BatchingModeOptions::default(),
399 }
400 }
401}
402
403#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
404#[serde(default)]
405pub struct FlownodeOptions {
406 pub node_id: Option<u64>,
407 pub flow: FlowConfig,
408 pub grpc: GrpcOptions,
409 pub http: HttpOptions,
410 pub meta_client: Option<MetaClientOptions>,
411 pub logging: LoggingOptions,
412 pub tracing: TracingOptions,
413 pub query: QueryOptions,
414 pub memory: MemoryOptions,
415}
416
417impl Default for FlownodeOptions {
418 fn default() -> Self {
419 Self {
420 node_id: None,
421 flow: FlowConfig::default(),
422 grpc: GrpcOptions::default().with_bind_addr("127.0.0.1:3004"),
423 http: HttpOptions::default(),
424 meta_client: None,
425 logging: LoggingOptions::default(),
426 tracing: TracingOptions::default(),
427 query: QueryOptions {
428 parallelism: 1,
429 allow_query_fallback: false,
430 memory_pool_size: MemoryLimit::default(),
431 enable_per_region_metrics: false,
432 ..Default::default()
433 },
434 memory: MemoryOptions::default(),
435 }
436 }
437}
438
439impl Configurable for FlownodeOptions {
440 fn validate_sanitize(&mut self) -> common_config::error::Result<()> {
441 Ok(())
442 }
443}
444
445pub type FlowStreamingEngineRef = Arc<StreamingEngine>;
446
447#[derive(Default)]
448struct StatelessFlowRuntime {
449 flow: Option<Arc<StatelessFlow>>,
450 failed_rebuild: Option<(u32, Instant)>,
451}
452
453struct StatelessFlowSlot {
454 runtime: Arc<RwLock<StatelessFlowRuntime>>,
455 active: std::sync::atomic::AtomicBool,
457 #[cfg(test)]
458 rebuild_attempts: AtomicUsize,
459}
460
461fn validate_captured_slot(
462 slot: &StatelessFlowSlot,
463 current_source_table_id: Option<table::metadata::TableId>,
464 expected_table_id: table::metadata::TableId,
465 flow_id: FlowId,
466) -> Result<(), Error> {
467 ensure!(
468 slot.active.load(Ordering::Acquire),
469 FlowNotFoundSnafu { id: flow_id }
470 );
471 let current_source_table_id =
472 current_source_table_id.context(FlowNotFoundSnafu { id: flow_id })?;
473 ensure!(
474 current_source_table_id == expected_table_id,
475 InvalidQuerySnafu {
476 reason: format!("Flow {flow_id} source table changed while it was selected")
477 }
478 );
479 Ok(())
480}
481
482pub struct StreamingEngine {
483 pub query_engine: Arc<dyn QueryEngine>,
484 pub frontend_client: Arc<FrontendClient>,
485 table_info_source: ManagedTableSource,
486 stateless_flows: RwLock<BTreeMap<FlowId, Arc<StatelessFlowSlot>>>,
487 pub node_id: Option<u32>,
488}
489
490impl StreamingEngine {
491 pub fn new(
492 node_id: Option<u32>,
493 query_engine: Arc<dyn QueryEngine>,
494 table_meta: TableMetadataManagerRef,
495 frontend_client: Arc<FrontendClient>,
496 ) -> Self {
497 let table_info_source = ManagedTableSource::new(
498 table_meta.table_info_manager().clone(),
499 table_meta.table_name_manager().clone(),
500 );
501 Self {
502 query_engine,
503 frontend_client,
504 table_info_source,
505 stateless_flows: Default::default(),
506 node_id,
507 }
508 }
509
510 pub async fn handle_write_request(
511 &self,
512 region_id: RegionId,
513 rows: Vec<DiffRow>,
514 batch_datatypes: &[ConcreteDataType],
515 source_schema_version: u32,
516 ) -> Result<(), Error> {
517 let table_id = region_id.table_id();
518 let flow_ids = self.flow_ids_for_table(table_id).await;
519 let mut failed_flow_ids = Vec::new();
520 let mut first_error = None;
521 for (flow_id, slot) in flow_ids {
522 let result = self
523 .execute_flow(
524 flow_id,
525 slot,
526 table_id,
527 rows.clone(),
528 batch_datatypes,
529 source_schema_version,
530 )
531 .await;
532 if let Err(err) = result {
533 error!(err; "Failed to insert into flow={}, region_id={}", flow_id, region_id);
534 failed_flow_ids.push(flow_id);
535 if first_error.is_none() {
536 first_error = Some(BoxedError::new(err));
537 }
538 }
539 }
540 match first_error {
541 Some(source) => Err(InsertIntoFlowSnafu {
542 region_id: u64::from(region_id),
543 flow_ids: failed_flow_ids,
544 }
545 .into_error(source)),
546 None => Ok(()),
547 }
548 }
549
550 async fn flow_ids_for_table(
551 &self,
552 table_id: table::metadata::TableId,
553 ) -> Vec<(FlowId, Arc<StatelessFlowSlot>)> {
554 let slots = self
555 .stateless_flows
556 .read()
557 .await
558 .iter()
559 .map(|(id, slot)| (*id, Arc::clone(slot)))
560 .collect::<Vec<_>>();
561 let mut flows = Vec::new();
562 for (id, slot) in slots {
563 if slot
564 .runtime
565 .read()
566 .await
567 .flow
568 .as_ref()
569 .is_some_and(|flow| flow.source_table_id == table_id)
570 {
571 flows.push((id, slot));
572 }
573 }
574 flows
575 }
576
577 async fn execute_flow(
578 &self,
579 flow_id: FlowId,
580 slot: Arc<StatelessFlowSlot>,
581 expected_table_id: table::metadata::TableId,
582 rows: Vec<DiffRow>,
583 batch_datatypes: &[ConcreteDataType],
584 source_schema_version: u32,
585 ) -> Result<usize, Error> {
586 let mut guard = slot.runtime.clone().read_owned().await;
588 let current = guard
589 .flow
590 .as_ref()
591 .context(FlowNotFoundSnafu { id: flow_id })?;
592 validate_captured_slot(
593 &slot,
594 Some(current.source_table_id),
595 expected_table_id,
596 flow_id,
597 )?;
598 if current.source_schema_version != source_schema_version {
599 if guard
600 .failed_rebuild
601 .as_ref()
602 .is_some_and(|(version, until)| {
603 *version == source_schema_version && *until > Instant::now()
604 })
605 {
606 return InvalidQuerySnafu {
607 reason: format!(
608 "Flow {flow_id} schema rebuild for source version {source_schema_version} is cooling down"
609 ),
610 }
611 .fail();
612 }
613 drop(guard);
614 let mut published = slot.runtime.clone().write_owned().await;
617 let current = published
618 .flow
619 .as_ref()
620 .context(FlowNotFoundSnafu { id: flow_id })?;
621 validate_captured_slot(
622 &slot,
623 Some(current.source_table_id),
624 expected_table_id,
625 flow_id,
626 )?;
627 if current.source_schema_version != source_schema_version {
628 if published
629 .failed_rebuild
630 .as_ref()
631 .is_some_and(|(version, until)| {
632 *version == source_schema_version && *until > Instant::now()
633 })
634 {
635 return InvalidQuerySnafu {
636 reason: format!(
637 "Flow {flow_id} schema rebuild for source version {source_schema_version} is cooling down"
638 ),
639 }
640 .fail();
641 }
642 #[cfg(test)]
643 slot.rebuild_attempts.fetch_add(1, Ordering::Relaxed);
644 let replacement = self
645 .build_stateless_flow(¤t.create_args, false)
646 .await
647 .and_then(|replacement| {
648 ensure!(
649 replacement.source_table_id == expected_table_id
650 && replacement.source_schema_version == source_schema_version,
651 InvalidQuerySnafu {
652 reason: format!(
653 "Source schema changed while rebuilding flow {flow_id}"
654 )
655 }
656 );
657 Ok(replacement)
658 });
659 match replacement {
660 Ok(replacement) => {
661 published.flow = Some(Arc::new(replacement));
662 published.failed_rebuild = None;
663 }
664 Err(error) => {
665 published.failed_rebuild = Some((
668 source_schema_version,
669 Instant::now() + BASE_HEARTBEAT_INTERVAL,
670 ));
671 return Err(error);
672 }
673 }
674 }
675 guard = tokio::sync::OwnedRwLockWriteGuard::downgrade(published);
677 }
678 let flow = guard
679 .flow
680 .as_ref()
681 .context(FlowNotFoundSnafu { id: flow_id })?;
682 let latest = self
685 .table_info_source
686 .get_table_info_value(&flow.source_table_id)
687 .await?
688 .context(UnexpectedSnafu {
689 reason: "Source table metadata is missing",
690 })?
691 .table_info
692 .meta
693 .schema
694 .version();
695 ensure!(
696 source_schema_version == latest && flow.source_schema_version == latest,
697 InvalidQuerySnafu {
698 reason: format!("Source schema version changed before flow {flow_id} execution")
699 }
700 );
701 stateless::execute(
702 flow,
703 &rows,
704 batch_datatypes,
705 &self.query_engine,
706 &self.frontend_client,
707 latest,
708 )
709 .await
710 }
711
712 pub async fn remove_flow_inner(&self, flow_id: FlowId) -> Result<(), Error> {
713 let slot = self
716 .stateless_flows
717 .write()
718 .await
719 .remove(&flow_id)
720 .context(FlowNotFoundSnafu { id: flow_id })?;
721 slot.active.store(false, Ordering::Release);
722 let mut runtime = slot.runtime.write().await;
723 runtime.flow.take();
724 runtime.failed_rebuild = None;
725 Ok(())
726 }
727
728 async fn publish_initial_flow(
729 &self,
730 slot: Arc<StatelessFlowSlot>,
731 flow: Arc<StatelessFlow>,
732 create_if_not_exists: bool,
733 or_replace: bool,
734 ) -> Result<bool, Error> {
735 let mut runtime = slot.runtime.write().await;
736 ensure!(
737 slot.active.load(Ordering::Acquire),
738 FlowNotFoundSnafu {
739 id: flow.create_args.flow_id
740 }
741 );
742 if runtime.flow.is_some() && !or_replace {
743 if create_if_not_exists {
744 return Ok(false);
745 }
746 return crate::error::FlowAlreadyExistSnafu {
747 id: flow.create_args.flow_id,
748 }
749 .fail();
750 }
751 let latest = self
752 .table_info_source
753 .get_table_info_value(&flow.source_table_id)
754 .await?
755 .context(UnexpectedSnafu {
756 reason: "Source table metadata is missing",
757 })?
758 .table_info
759 .meta
760 .schema
761 .version();
762 ensure!(
763 latest == flow.source_schema_version,
764 InvalidQuerySnafu {
765 reason: format!(
766 "Source schema changed while building flow: built version {}, current version {latest}",
767 flow.source_schema_version
768 )
769 }
770 );
771 runtime.flow = Some(flow);
772 runtime.failed_rebuild = None;
773 Ok(true)
774 }
775
776 pub async fn create_flow_inner(&self, args: CreateFlowArgs) -> Result<Option<FlowId>, Error> {
777 let flow_id = args.flow_id;
778 let flow = Arc::new(self.build_stateless_flow(&args, true).await?);
779 let slot = {
780 let mut slots = self.stateless_flows.write().await;
781 if let Some(slot) = slots.get(&flow_id) {
782 Arc::clone(slot)
783 } else {
784 let slot = Arc::new(StatelessFlowSlot {
785 runtime: Arc::new(RwLock::new(StatelessFlowRuntime::default())),
786 active: AtomicBool::new(true),
787 #[cfg(test)]
788 rebuild_attempts: AtomicUsize::new(0),
789 });
790 slots.insert(flow_id, Arc::clone(&slot));
791 slot
792 }
793 };
794 let published = self
795 .publish_initial_flow(
796 slot.clone(),
797 flow,
798 args.create_if_not_exists,
799 args.or_replace,
800 )
801 .await?;
802 ensure!(
803 self.stateless_flows
804 .read()
805 .await
806 .get(&flow_id)
807 .is_some_and(|current| Arc::ptr_eq(current, &slot)),
808 FlowNotFoundSnafu { id: flow_id }
809 );
810 if published {
811 info!("Successfully create flow with id={flow_id}");
812 Ok(Some(flow_id))
813 } else {
814 Ok(None)
815 }
816 }
817
818 async fn build_stateless_flow(
819 &self,
820 args: &CreateFlowArgs,
821 create_sink: bool,
822 ) -> Result<StatelessFlow, Error> {
823 let CreateFlowArgs {
824 flow_id,
825 sink_table_name,
826 source_table_ids,
827 sql,
828 query_ctx,
829 ..
830 } = args;
831 ensure!(
832 source_table_ids.len() == 1,
833 InvalidQuerySnafu {
834 reason: "Stateless streaming flow does not support multiple source tables",
835 }
836 );
837
838 let query_ctx = query_ctx.clone().map(Arc::new).context(UnexpectedSnafu {
839 reason: "Query context is missing",
840 })?;
841 let source_table_id = source_table_ids[0];
842 let source_table_name = self
843 .table_info_source
844 .get_table_name(&source_table_id)
845 .await?;
846 let source_table_info = self
847 .table_info_source
848 .get_table_info_value(&source_table_id)
849 .await?
850 .context(UnexpectedSnafu {
851 reason: "Source table metadata is missing",
852 })?;
853 let flow_plan =
854 sql_to_df_plan(query_ctx.clone(), self.query_engine.clone(), sql, true).await?;
855 stateless::validate_plan(&flow_plan)?;
856 let source_meta = source_table_info.table_info.meta;
857 let source_schema = source_meta.schema;
858 let source_primary_key_indices = source_meta.primary_key_indices;
859 stateless::validate_source_scan(&flow_plan, source_table_id, &source_schema)?;
860 let (inferred_schema, lineage) = output_column_schemas(&flow_plan, &source_schema)?;
861 let inferred_relation = relation_desc_from_output(
862 &inferred_schema,
863 &lineage,
864 &source_primary_key_indices,
865 &flow_plan,
866 );
867 let sink_exists = self.fetch_table_pk_schema(sink_table_name).await?.is_some();
868 if !sink_exists {
869 validate_auto_column_names(&inferred_schema)?;
870 }
871 if !sink_exists
872 && create_sink
873 && !self
874 .create_table_from_relation(
875 &format!("flow-id={flow_id}"),
876 sink_table_name,
877 &inferred_relation,
878 )
879 .await?
880 {
881 return UnexpectedSnafu {
882 reason: format!("Failed to create table {sink_table_name:?}"),
883 }
884 .fail();
885 }
886 ensure!(
887 sink_exists || create_sink,
888 UnexpectedSnafu {
889 reason: format!("Sink table metadata is missing: {sink_table_name:?}"),
890 }
891 );
892
893 let (sink_primary_keys, _, sink_schema) = self
896 .fetch_table_pk_schema(sink_table_name)
897 .await?
898 .context(UnexpectedSnafu {
899 reason: format!("Sink table metadata is missing: {sink_table_name:?}"),
900 })?;
901 let (auto_columns, plan) = match resolve_sink_layout(&inferred_schema, &sink_schema) {
902 Ok(auto_columns) => (auto_columns, flow_plan),
903 Err(normal_error) => {
904 let has_distinct = {
907 let mut found = false;
908 flow_plan
909 .apply(|node| {
910 if matches!(node, LogicalPlan::Distinct(Distinct::All(_))) {
911 found = true;
912 }
913 Ok(datafusion_common::tree_node::TreeNodeRecursion::Continue)
914 })
915 .context(DatafusionSnafu {
916 context: "Failed to inspect flow plan",
917 })?;
918 found
919 };
920 if has_distinct
921 || !sink_exists
922 || !is_explicit_source_timestamp_compatibility(
923 &inferred_schema,
924 &lineage,
925 &sink_schema,
926 &source_schema,
927 )
928 {
929 return Err(normal_error);
930 }
931 let source_timestamp_index = source_schema.timestamp_index().unwrap();
932 let source_timestamp_name =
933 &source_schema.column_schemas()[source_timestamp_index].name;
934 let plan = stateless::rewrite_source_timestamp(
935 flow_plan,
936 &TableReference::full(
937 source_table_name[0].clone(),
938 source_table_name[1].clone(),
939 source_table_name[2].clone(),
940 ),
941 source_timestamp_name,
942 )?;
943 let (effective_output, _) = output_column_schemas(&plan, &source_schema)?;
944 ensure!(
945 effective_output.len() == sink_schema.len()
946 && effective_output
947 .iter()
948 .zip(&sink_schema)
949 .all(|(output, sink)| output.data_type == sink.data_type),
950 InvalidQuerySnafu {
951 reason: "Compatibility plan output does not match the full sink schema"
952 }
953 );
954 (vec![], plan)
955 }
956 };
957
958 Ok(StatelessFlow {
959 source_table_id,
960 source_table_name,
961 source_schema_version: source_schema.version(),
962 source_schema,
963 sink_table_name: sink_table_name.clone(),
964 sink_schema,
965 sink_primary_keys,
966 auto_columns,
967 plan,
968 query_ctx,
969 create_args: args.clone(),
970 })
971 }
972
973 pub async fn flush_flow_inner(&self, _flow_id: FlowId) -> Result<usize, Error> {
974 Ok(0)
975 }
976
977 pub(crate) async fn stateless_flow_ids(&self) -> Vec<FlowId> {
978 let slots = self.stateless_flows.read().await;
979 let mut ids = Vec::new();
980 for (id, slot) in slots.iter() {
981 if slot.runtime.read().await.flow.is_some() {
982 ids.push(*id);
983 }
984 }
985 ids
986 }
987
988 pub async fn flow_exist_inner(&self, flow_id: FlowId) -> Result<bool, Error> {
989 let slot = self.stateless_flows.read().await.get(&flow_id).cloned();
990 Ok(match slot {
991 Some(slot) => slot.runtime.read().await.flow.is_some(),
992 None => false,
993 })
994 }
995
996 async fn fetch_table_pk_schema(
997 &self,
998 table_name: &TableName,
999 ) -> Result<Option<(Vec<String>, Option<usize>, Vec<ColumnSchema>)>, Error> {
1000 if let Some(table_id) = self
1001 .table_info_source
1002 .get_opt_table_id_from_name(table_name)
1003 .await?
1004 {
1005 let table_info = self
1006 .table_info_source
1007 .get_table_info_value(&table_id)
1008 .await?
1009 .unwrap();
1010 let meta = table_info.table_info.meta;
1011 let schema = meta.schema.column_schemas().to_vec();
1012 let primary_keys = meta
1013 .primary_key_indices
1014 .into_iter()
1015 .map(|i| schema[i].name.clone())
1016 .collect_vec();
1017 Ok(Some((primary_keys, meta.schema.timestamp_index(), schema)))
1018 } else {
1019 Ok(None)
1020 }
1021 }
1022
1023 async fn adjust_auto_created_table_schema(
1024 &self,
1025 schema: &RelationDesc,
1026 ) -> Result<(Vec<String>, Vec<ColumnSchema>, bool), Error> {
1027 let primary_keys = schema
1028 .typ()
1029 .keys
1030 .first()
1031 .map(|key| {
1032 key.column_indices
1033 .iter()
1034 .map(|i| {
1035 schema
1036 .get_name(*i)
1037 .clone()
1038 .unwrap_or_else(|| format!("col_{i}"))
1039 })
1040 .collect_vec()
1041 })
1042 .unwrap_or_default();
1043 let mut columns = relation_desc_to_column_schemas_with_fallback(schema);
1044 columns.push(ColumnSchema::new(
1045 AUTO_CREATED_UPDATE_AT_TS_COL,
1046 ConcreteDataType::timestamp_millisecond_datatype(),
1047 true,
1048 ));
1049 let no_time_index = schema.typ().time_index.is_none();
1050 if no_time_index {
1051 columns.push(
1052 ColumnSchema::new(
1053 AUTO_CREATED_PLACEHOLDER_TS_COL,
1054 ConcreteDataType::timestamp_millisecond_datatype(),
1055 true,
1056 )
1057 .with_time_index(true),
1058 );
1059 }
1060 Ok((primary_keys, columns, no_time_index))
1061 }
1062}
1063
1064impl StreamingEngine {
1065 async fn handle_inserts_inner(
1066 &self,
1067 request: api::v1::region::InsertRequests,
1068 ) -> Result<(), Error> {
1069 let mut grouped: BTreeMap<_, (_, Vec<DiffRow>, Vec<ConcreteDataType>, u32)> =
1072 BTreeMap::new();
1073 let mut poisoned_tables = std::collections::HashSet::new();
1074 let mut first_error = None;
1075 for write_request in request.requests {
1076 let region_id = RegionId::from(write_request.region_id);
1077 let table_id = region_id.table_id();
1078 if poisoned_tables.contains(&table_id) {
1079 continue;
1080 }
1081 match self.handle_insert_request(write_request).await {
1082 Ok((rows, types, version)) => {
1083 let entry = grouped
1084 .entry(table_id)
1085 .or_insert_with(|| (region_id, vec![], types.clone(), version));
1086 if entry.2 != types || entry.3 != version {
1087 poisoned_tables.insert(table_id);
1088 grouped.remove(&table_id);
1089 let err = InvalidQuerySnafu { reason: format!("Source table {table_id} metadata changed within one insert envelope") }.build();
1090 let ids: Vec<FlowId> = self
1091 .flow_ids_for_table(table_id)
1092 .await
1093 .into_iter()
1094 .map(|(id, _)| id)
1095 .collect();
1096 let err = InsertIntoFlowSnafu {
1097 region_id: u64::from(region_id),
1098 flow_ids: ids,
1099 }
1100 .into_error(BoxedError::new(err));
1101 error!(err; "Failed to normalize flow insert request for region_id={region_id}");
1102 if first_error.is_none() {
1103 first_error = Some(err);
1104 }
1105 } else {
1106 entry.1.extend(rows);
1107 }
1108 }
1109 Err(err) => {
1110 let ids: Vec<FlowId> = self
1111 .flow_ids_for_table(table_id)
1112 .await
1113 .into_iter()
1114 .map(|(id, _)| id)
1115 .collect();
1116 poisoned_tables.insert(table_id);
1117 grouped.remove(&table_id);
1118 let err = InsertIntoFlowSnafu {
1119 region_id: u64::from(region_id),
1120 flow_ids: ids,
1121 }
1122 .into_error(BoxedError::new(err));
1123 error!(err; "Failed to normalize flow insert request for region_id={region_id}");
1124 if first_error.is_none() {
1125 first_error = Some(err);
1126 }
1127 }
1128 }
1129 }
1130 for (_, (region_id, rows, types, version)) in grouped {
1131 if let Err(err) = self
1132 .handle_write_request(region_id, rows, &types, version)
1133 .await
1134 && first_error.is_none()
1135 {
1136 first_error = Some(err);
1137 }
1138 }
1139 match first_error {
1140 Some(err) => Err(err),
1141 None => Ok(()),
1142 }
1143 }
1144
1145 async fn handle_insert_request(
1146 &self,
1147 write_request: api::v1::region::InsertRequest,
1148 ) -> Result<(Vec<DiffRow>, Vec<ConcreteDataType>, u32), Error> {
1149 let region_id = write_request.region_id;
1150 let table_id = RegionId::from(region_id).table_id();
1151 let (insert_schema, rows_proto) = write_request
1152 .rows
1153 .map(|r| (r.schema, r.rows))
1154 .unwrap_or_default();
1155 let now = common_time::util::current_time_millis();
1156 let (table_types, fetch_order, source_schema_version) = {
1157 let table_info = self
1161 .table_info_source
1162 .get_table_info_value(&table_id)
1163 .await?
1164 .context(UnexpectedSnafu {
1165 reason: format!("Table metadata is missing for table id {table_id}"),
1166 })?;
1167 let source_schema_version = table_info.table_info.meta.schema.version();
1168 let table_schema = table_info_value_to_relation_desc(table_info)?;
1169 let defaults = table_schema
1170 .default_values
1171 .iter()
1172 .zip(table_schema.relation_desc.typ().column_types.iter())
1173 .map(|(value, ty)| {
1174 value.as_ref().and_then(|value| {
1175 value.create_default(ty.scalar_type(), ty.nullable()).ok()
1176 })
1177 })
1178 .collect_vec();
1179 let types = table_schema
1180 .relation_desc
1181 .typ()
1182 .column_types
1183 .iter()
1184 .map(|ty| ty.scalar_type.clone())
1185 .collect_vec();
1186 let names = table_schema
1187 .relation_desc
1188 .names
1189 .iter()
1190 .enumerate()
1191 .map(|(idx, name)| {
1192 name.clone().context(InternalSnafu {
1193 reason: format!("Column {idx} of table {table_id} has no name"),
1194 })
1195 })
1196 .collect::<Result<Vec<_>, _>>()?;
1197 let input_columns = insert_schema
1198 .iter()
1199 .enumerate()
1200 .map(|(idx, column)| (&column.column_name, idx))
1201 .collect::<std::collections::HashMap<_, _>>();
1202 let order = names
1203 .iter()
1204 .zip(defaults)
1205 .map(|(name, default)| {
1206 input_columns
1207 .get(name)
1208 .copied()
1209 .map(FetchFromRow::Idx)
1210 .or_else(|| default.map(FetchFromRow::Default))
1211 .with_context(|| UnexpectedSnafu {
1212 reason: format!("Column not found: {name}"),
1213 })
1214 })
1215 .collect::<Result<Vec<_>, _>>()?;
1216 (types, order, source_schema_version)
1217 };
1218 let rows = rows_proto
1219 .into_iter()
1220 .map(|row| {
1221 let row = Row::from(row);
1222 let row = fetch_order
1223 .iter()
1224 .map(|item| item.fetch(&row))
1225 .collect_vec();
1226 (Row::new(row), now, 1)
1227 })
1228 .collect_vec();
1229 Ok((rows, table_types, source_schema_version))
1230 }
1231}
1232
1233#[derive(Debug, Clone)]
1234enum FetchFromRow {
1235 Idx(usize),
1236 Default(datatypes::value::Value),
1237}
1238
1239impl FetchFromRow {
1240 fn fetch(&self, row: &Row) -> datatypes::value::Value {
1241 match self {
1242 Self::Idx(idx) => row
1243 .get(*idx)
1244 .cloned()
1245 .unwrap_or(datatypes::value::Value::Null),
1246 Self::Default(value) => value.clone(),
1247 }
1248 }
1249}