1use std::sync::{Arc, Weak};
16
17use common_catalog::consts::INFORMATION_SCHEMA_FLOW_TABLE_ID;
18use common_error::ext::BoxedError;
19use common_meta::ddl::create_flow::{FlowType, effective_eval_schedule_from_flow_info};
20use common_meta::key::FlowId;
21use common_meta::key::flow::FlowMetadataManager;
22use common_meta::key::flow::flow_info::FlowInfoValue;
23use common_meta::key::flow::flow_state::FlowStat;
24use common_recordbatch::adapter::RecordBatchStreamAdapter;
25use common_recordbatch::{DfSendableRecordBatchStream, RecordBatch, SendableRecordBatchStream};
26use datafusion::execution::TaskContext;
27use datafusion::physical_plan::stream::RecordBatchStreamAdapter as DfRecordBatchStreamAdapter;
28use datafusion::physical_plan::streaming::PartitionStream as DfPartitionStream;
29use datatypes::prelude::ConcreteDataType as CDT;
30use datatypes::scalars::ScalarVectorBuilder;
31use datatypes::schema::{ColumnSchema, Schema, SchemaRef};
32use datatypes::timestamp::TimestampMillisecond;
33use datatypes::value::Value;
34use datatypes::vectors::{
35 Int64VectorBuilder, StringVectorBuilder, TimestampMillisecondVectorBuilder,
36 UInt32VectorBuilder, UInt64VectorBuilder, VectorRef,
37};
38use futures::TryStreamExt;
39use snafu::{OptionExt, ResultExt};
40use sql::ast::Ident;
41use sql::dialect::GreptimeDbDialect;
42use sql::parser::ParserContext;
43use sql::statements::create::{CreateFlow, SqlOrTql};
44use sql::statements::statement::Statement;
45use store_api::storage::{ScanRequest, TableId};
46
47use crate::CatalogManager;
48use crate::error::{
49 CreateRecordBatchSnafu, FlowInfoNotFoundSnafu, InternalSnafu, JsonSnafu, ListFlowsSnafu,
50 Result, UpgradeWeakCatalogManagerRefSnafu,
51};
52use crate::information_schema::{FLOWS, Predicates};
53use crate::system_schema::SystemTable;
54use crate::system_schema::information_schema::{InformationSchemaTableScope, InformationTable};
55use crate::system_schema::utils;
56
57const INIT_CAPACITY: usize = 42;
58
59pub const FLOW_NAME: &str = "flow_name";
62pub const FLOW_ID: &str = "flow_id";
63pub const STATE_SIZE: &str = "state_size";
64pub const TABLE_CATALOG: &str = "table_catalog";
65pub const FLOW_DEFINITION: &str = "flow_definition";
66pub const COMMENT: &str = "comment";
67pub const EXPIRE_AFTER: &str = "expire_after";
68pub const SOURCE_TABLE_IDS: &str = "source_table_ids";
69pub const SINK_TABLE_NAME: &str = "sink_table_name";
70pub const FLOWNODE_IDS: &str = "flownode_ids";
71pub const OPTIONS: &str = "options";
72pub const CREATED_TIME: &str = "created_time";
73pub const UPDATED_TIME: &str = "updated_time";
74pub const LAST_EXECUTION_TIME: &str = "last_execution_time";
75pub const SOURCE_TABLE_NAMES: &str = "source_table_names";
76pub const FLOWNODE_ADDRS: &str = "flownode_addrs";
77
78#[derive(Debug)]
80pub(super) struct InformationSchemaFlows {
81 schema: SchemaRef,
82 catalog_name: String,
83 catalog_manager: Weak<dyn CatalogManager>,
84 flow_metadata_manager: Arc<FlowMetadataManager>,
85}
86
87impl InformationSchemaFlows {
88 pub(super) fn new(
89 catalog_name: String,
90 catalog_manager: Weak<dyn CatalogManager>,
91 flow_metadata_manager: Arc<FlowMetadataManager>,
92 ) -> Self {
93 Self {
94 schema: Self::schema(),
95 catalog_name,
96 catalog_manager,
97 flow_metadata_manager,
98 }
99 }
100
101 pub(crate) fn schema() -> SchemaRef {
105 Arc::new(Schema::new(
106 vec![
107 (FLOW_NAME, CDT::string_datatype(), false),
108 (FLOW_ID, CDT::uint32_datatype(), false),
109 (STATE_SIZE, CDT::uint64_datatype(), true),
110 (TABLE_CATALOG, CDT::string_datatype(), false),
111 (FLOW_DEFINITION, CDT::string_datatype(), false),
112 (COMMENT, CDT::string_datatype(), true),
113 (EXPIRE_AFTER, CDT::int64_datatype(), true),
114 (SOURCE_TABLE_IDS, CDT::string_datatype(), true),
115 (SINK_TABLE_NAME, CDT::string_datatype(), false),
116 (FLOWNODE_IDS, CDT::string_datatype(), true),
117 (OPTIONS, CDT::string_datatype(), true),
118 (CREATED_TIME, CDT::timestamp_millisecond_datatype(), false),
119 (UPDATED_TIME, CDT::timestamp_millisecond_datatype(), false),
120 (
121 LAST_EXECUTION_TIME,
122 CDT::timestamp_millisecond_datatype(),
123 true,
124 ),
125 (SOURCE_TABLE_NAMES, CDT::string_datatype(), true),
126 (FLOWNODE_ADDRS, CDT::string_datatype(), true),
127 ]
128 .into_iter()
129 .map(|(name, ty, nullable)| ColumnSchema::new(name, ty, nullable))
130 .collect(),
131 ))
132 }
133
134 pub(crate) fn generate_show_create_flow(flow_info: &FlowInfoValue) -> Result<String> {
136 let mut parser_ctx = ParserContext::new(&GreptimeDbDialect {}, flow_info.raw_sql())
137 .map_err(BoxedError::new)
138 .context(InternalSnafu)?;
139
140 let query = parser_ctx
141 .parse_statement()
142 .map_err(BoxedError::new)
143 .context(InternalSnafu)?;
144
145 let raw_query = match &query {
146 Statement::Tql(_) => flow_info.raw_sql().clone(),
147 _ => query.to_string(),
148 };
149
150 let query = Box::new(
151 SqlOrTql::try_from_statement(query, &raw_query)
152 .map_err(BoxedError::new)
153 .context(InternalSnafu)?,
154 );
155
156 let comment = if flow_info.comment().is_empty() {
157 None
158 } else {
159 Some(flow_info.comment().clone())
160 };
161
162 let stmt = CreateFlow {
163 flow_name: sql::ast::ObjectName::from(vec![Ident::new(flow_info.flow_name())]),
164 sink_table_name: sql::ast::ObjectName::from(vec![
165 Ident::new(&flow_info.sink_table_name().schema_name),
166 Ident::new(&flow_info.sink_table_name().table_name),
167 ]),
168 or_replace: false,
169 if_not_exists: true,
170 expire_after: flow_info.expire_after(),
171 eval_interval: flow_info.eval_interval(),
172 eval_offset: effective_eval_schedule_from_flow_info(flow_info)
173 .map_err(BoxedError::new)
174 .context(InternalSnafu)?
175 .map(|schedule| schedule.anchor_secs)
176 .filter(|anchor_secs| *anchor_secs != 0),
177 comment,
178 flow_options: sql::statements::OptionMap::from_filtered_string_map(
179 flow_info.options(),
180 &[FlowType::FLOW_TYPE_KEY],
181 ),
182 query,
183 };
184
185 Ok(stmt.to_string())
186 }
187
188 fn builder(&self) -> InformationSchemaFlowsBuilder {
189 InformationSchemaFlowsBuilder::new(
190 self.schema.clone(),
191 self.catalog_name.clone(),
192 self.catalog_manager.clone(),
193 &self.flow_metadata_manager,
194 )
195 }
196}
197
198impl InformationTable for InformationSchemaFlows {
199 fn scope(&self) -> InformationSchemaTableScope {
200 InformationSchemaTableScope::Catalog
201 }
202}
203
204impl SystemTable for InformationSchemaFlows {
205 fn table_id(&self) -> TableId {
206 INFORMATION_SCHEMA_FLOW_TABLE_ID
207 }
208
209 fn table_name(&self) -> &'static str {
210 FLOWS
211 }
212
213 fn schema(&self) -> SchemaRef {
214 self.schema.clone()
215 }
216
217 fn to_stream(&self, request: ScanRequest) -> Result<SendableRecordBatchStream> {
218 let schema = self.schema.arrow_schema().clone();
219 let mut builder = self.builder();
220 let stream = Box::pin(DfRecordBatchStreamAdapter::new(
221 schema,
222 futures::stream::once(async move {
223 builder
224 .make_flows(Some(request))
225 .await
226 .map(|x| x.into_df_record_batch())
227 .map_err(|err| datafusion::error::DataFusionError::External(Box::new(err)))
228 }),
229 ));
230 Ok(Box::pin(
231 RecordBatchStreamAdapter::try_new(stream)
232 .map_err(BoxedError::new)
233 .context(InternalSnafu)?,
234 ))
235 }
236}
237
238struct InformationSchemaFlowsBuilder {
242 schema: SchemaRef,
243 catalog_name: String,
244 catalog_manager: Weak<dyn CatalogManager>,
245 flow_metadata_manager: Arc<FlowMetadataManager>,
246
247 flow_names: StringVectorBuilder,
248 flow_ids: UInt32VectorBuilder,
249 state_sizes: UInt64VectorBuilder,
250 table_catalogs: StringVectorBuilder,
251 raw_sqls: StringVectorBuilder,
252 comments: StringVectorBuilder,
253 expire_afters: Int64VectorBuilder,
254 source_table_id_groups: StringVectorBuilder,
255 sink_table_names: StringVectorBuilder,
256 flownode_id_groups: StringVectorBuilder,
257 option_groups: StringVectorBuilder,
258 created_time: TimestampMillisecondVectorBuilder,
259 updated_time: TimestampMillisecondVectorBuilder,
260 last_execution_time: TimestampMillisecondVectorBuilder,
261 source_table_names: StringVectorBuilder,
262 flownode_addr_groups: StringVectorBuilder,
263}
264
265impl InformationSchemaFlowsBuilder {
266 fn new(
267 schema: SchemaRef,
268 catalog_name: String,
269 catalog_manager: Weak<dyn CatalogManager>,
270 flow_metadata_manager: &Arc<FlowMetadataManager>,
271 ) -> Self {
272 Self {
273 schema,
274 catalog_name,
275 catalog_manager,
276 flow_metadata_manager: flow_metadata_manager.clone(),
277
278 flow_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
279 flow_ids: UInt32VectorBuilder::with_capacity(INIT_CAPACITY),
280 state_sizes: UInt64VectorBuilder::with_capacity(INIT_CAPACITY),
281 table_catalogs: StringVectorBuilder::with_capacity(INIT_CAPACITY),
282 raw_sqls: StringVectorBuilder::with_capacity(INIT_CAPACITY),
283 comments: StringVectorBuilder::with_capacity(INIT_CAPACITY),
284 expire_afters: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
285 source_table_id_groups: StringVectorBuilder::with_capacity(INIT_CAPACITY),
286 sink_table_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
287 flownode_id_groups: StringVectorBuilder::with_capacity(INIT_CAPACITY),
288 option_groups: StringVectorBuilder::with_capacity(INIT_CAPACITY),
289 created_time: TimestampMillisecondVectorBuilder::with_capacity(INIT_CAPACITY),
290 updated_time: TimestampMillisecondVectorBuilder::with_capacity(INIT_CAPACITY),
291 last_execution_time: TimestampMillisecondVectorBuilder::with_capacity(INIT_CAPACITY),
292 source_table_names: StringVectorBuilder::with_capacity(INIT_CAPACITY),
293 flownode_addr_groups: StringVectorBuilder::with_capacity(INIT_CAPACITY),
294 }
295 }
296
297 async fn make_flows(&mut self, request: Option<ScanRequest>) -> Result<RecordBatch> {
299 let catalog_name = self.catalog_name.clone();
300 let predicates = Predicates::from_scan_request(&request);
301
302 let flow_info_manager = self.flow_metadata_manager.clone();
303
304 let mut stream = flow_info_manager
306 .flow_name_manager()
307 .flow_names(&catalog_name)
308 .await;
309
310 let flow_stat = {
311 let information_extension = utils::information_extension(&self.catalog_manager)?;
312 information_extension.flow_stats().await?
313 };
314
315 while let Some((flow_name, flow_id)) = stream
316 .try_next()
317 .await
318 .map_err(BoxedError::new)
319 .context(ListFlowsSnafu {
320 catalog: &catalog_name,
321 })?
322 {
323 let flow_info = flow_info_manager
324 .flow_info_manager()
325 .get(flow_id.flow_id())
326 .await
327 .map_err(BoxedError::new)
328 .context(InternalSnafu)?
329 .with_context(|| FlowInfoNotFoundSnafu {
330 catalog_name: catalog_name.clone(),
331 flow_name: flow_name.clone(),
332 })?;
333 self.add_flow(&predicates, flow_id.flow_id(), flow_info, &flow_stat)
334 .await?;
335 }
336
337 self.finish()
338 }
339
340 async fn add_flow(
341 &mut self,
342 predicates: &Predicates,
343 flow_id: FlowId,
344 flow_info: FlowInfoValue,
345 flow_stat: &Option<FlowStat>,
346 ) -> Result<()> {
347 let row = [
348 (FLOW_NAME, &Value::from(flow_info.flow_name().clone())),
349 (FLOW_ID, &Value::from(flow_id)),
350 (
351 TABLE_CATALOG,
352 &Value::from(flow_info.catalog_name().clone()),
353 ),
354 ];
355 if !predicates.eval(&row) {
356 return Ok(());
357 }
358 self.flow_names.push(Some(flow_info.flow_name()));
359 self.flow_ids.push(Some(flow_id));
360 self.state_sizes.push(
361 flow_stat
362 .as_ref()
363 .and_then(|state| state.state_size.get(&flow_id).map(|v| *v as u64)),
364 );
365 self.table_catalogs.push(Some(flow_info.catalog_name()));
366 self.raw_sqls
367 .push(Some(&InformationSchemaFlows::generate_show_create_flow(
368 &flow_info,
369 )?));
370 self.comments.push(Some(flow_info.comment()));
371 self.expire_afters.push(flow_info.expire_after());
372 self.source_table_id_groups.push(Some(
373 &serde_json::to_string(flow_info.source_table_ids()).context(JsonSnafu {
374 input: format!("{:?}", flow_info.source_table_ids()),
375 })?,
376 ));
377 self.sink_table_names
378 .push(Some(&flow_info.sink_table_name().to_string()));
379 self.flownode_id_groups.push(Some(
380 &serde_json::to_string(flow_info.flownode_ids()).context({
381 JsonSnafu {
382 input: format!("{:?}", flow_info.flownode_ids()),
383 }
384 })?,
385 ));
386 self.option_groups
387 .push(Some(&serde_json::to_string(flow_info.options()).context(
388 JsonSnafu {
389 input: format!("{:?}", flow_info.options()),
390 },
391 )?));
392 self.created_time
393 .push(Some(flow_info.created_time().timestamp_millis().into()));
394 self.updated_time
395 .push(Some(flow_info.updated_time().timestamp_millis().into()));
396 self.last_execution_time
397 .push(flow_stat.as_ref().and_then(|state| {
398 state
399 .last_exec_time_map
400 .get(&flow_id)
401 .map(|v| TimestampMillisecond::new(*v))
402 }));
403 let flownode_addrs = self
404 .flow_metadata_manager
405 .flownode_addrs(flow_id)
406 .await
407 .map_err(BoxedError::new)
408 .context(InternalSnafu)?;
409 if flownode_addrs.is_empty() {
410 self.flownode_addr_groups.push(None);
411 } else {
412 let flownode_addrs_json =
413 serde_json::to_string(&flownode_addrs).with_context(|_| JsonSnafu {
414 input: format!("{:?}", flownode_addrs),
415 })?;
416 self.flownode_addr_groups.push(Some(&flownode_addrs_json));
417 }
418
419 let mut source_table_names = vec![];
420 let catalog_manager = self
421 .catalog_manager
422 .upgrade()
423 .context(UpgradeWeakCatalogManagerRefSnafu)?;
424 for table_id in flow_info.source_table_ids() {
425 if let Some(table_info) = catalog_manager.table_info_by_id(*table_id).await? {
426 source_table_names.push(table_info.full_table_name());
427 }
428 }
429
430 let source_table_names = source_table_names.join(",");
431 self.source_table_names.push(Some(&source_table_names));
432
433 Ok(())
434 }
435
436 fn finish(&mut self) -> Result<RecordBatch> {
437 let columns: Vec<VectorRef> = vec![
438 Arc::new(self.flow_names.finish()),
439 Arc::new(self.flow_ids.finish()),
440 Arc::new(self.state_sizes.finish()),
441 Arc::new(self.table_catalogs.finish()),
442 Arc::new(self.raw_sqls.finish()),
443 Arc::new(self.comments.finish()),
444 Arc::new(self.expire_afters.finish()),
445 Arc::new(self.source_table_id_groups.finish()),
446 Arc::new(self.sink_table_names.finish()),
447 Arc::new(self.flownode_id_groups.finish()),
448 Arc::new(self.option_groups.finish()),
449 Arc::new(self.created_time.finish()),
450 Arc::new(self.updated_time.finish()),
451 Arc::new(self.last_execution_time.finish()),
452 Arc::new(self.source_table_names.finish()),
453 Arc::new(self.flownode_addr_groups.finish()),
454 ];
455 RecordBatch::new(self.schema.clone(), columns).context(CreateRecordBatchSnafu)
456 }
457}
458
459impl DfPartitionStream for InformationSchemaFlows {
460 fn schema(&self) -> &arrow_schema::SchemaRef {
461 self.schema.arrow_schema()
462 }
463
464 fn execute(&self, _: Arc<TaskContext>) -> DfSendableRecordBatchStream {
465 let schema: Arc<arrow_schema::Schema> = self.schema.arrow_schema().clone();
466 let mut builder = self.builder();
467 Box::pin(DfRecordBatchStreamAdapter::new(
468 schema,
469 futures::stream::once(async move {
470 builder
471 .make_flows(None)
472 .await
473 .map(|x| x.into_df_record_batch())
474 .map_err(Into::into)
475 }),
476 ))
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use std::collections::{BTreeMap, HashMap};
483
484 use common_meta::key::flow::flow_info::{FlowMissedTickPolicy, FlowScheduleConfig, FlowStatus};
485 use sql::parser::ParseOptions;
486 use table::table_name::TableName;
487
488 use super::*;
489
490 fn flow_info_for_show_create(
491 raw_sql: &str,
492 eval_interval_secs: Option<i64>,
493 anchor_secs: i64,
494 options: HashMap<String, String>,
495 ) -> FlowInfoValue {
496 let created_time = chrono::DateTime::from_timestamp(1_700_000_000, 0).unwrap();
497 FlowInfoValue {
498 source_table_ids: vec![],
499 all_source_table_names: vec![],
500 unresolved_source_table_names: vec![],
501 sink_table_name: TableName::new("greptime", "public", "sink"),
502 flownode_ids: BTreeMap::new(),
503 catalog_name: "greptime".to_string(),
504 query_context: None,
505 flow_name: "my_flow".to_string(),
506 raw_sql: raw_sql.to_string(),
507 expire_after: None,
508 eval_interval_secs,
509 comment: String::new(),
510 options,
511 status: FlowStatus::Active,
512 created_time,
513 updated_time: created_time,
514 eval_schedule: eval_interval_secs.map(|interval| FlowScheduleConfig {
515 anchor_secs,
516 start_secs: anchor_secs + interval,
517 missed_tick_policy: FlowMissedTickPolicy::BoundedCatchUp,
518 catchup_max_runs: 3,
519 catchup_max_lag_secs: 300,
520 }),
521 }
522 }
523
524 #[test]
525 fn test_generate_show_create_flow_with_eval_offset() {
526 let raw_sql = "SELECT max(c1) FROM public.src";
528 let flow_info = flow_info_for_show_create(raw_sql, Some(3600), 120, HashMap::new());
529 let sql = InformationSchemaFlows::generate_show_create_flow(&flow_info).unwrap();
530 assert!(
531 sql.contains("EVAL OFFSET '120 s'"),
532 "EVAL OFFSET must be emitted, got:\n{sql}"
533 );
534 assert!(
535 !sql.contains("__greptime_internal_eval_offset_secs"),
536 "internal key must be absent, got:\n{sql}"
537 );
538
539 let stmts = ParserContext::create_with_dialect(
540 &sql,
541 &GreptimeDbDialect {},
542 ParseOptions::default(),
543 )
544 .unwrap();
545 let Statement::CreateFlow(reparsed) = &stmts[0] else {
546 panic!("unexpected stmt: {:?}", stmts[0]);
547 };
548 assert_eq!(reparsed.eval_interval, Some(3600));
549 assert_eq!(reparsed.eval_offset, Some(120));
550 }
551
552 #[test]
553 fn test_generate_show_create_flow_omits_zero_offset() {
554 let raw_sql = "SELECT max(c1) FROM public.src";
555 let flow_info = flow_info_for_show_create(raw_sql, Some(3600), 0, HashMap::new());
556 let sql = InformationSchemaFlows::generate_show_create_flow(&flow_info).unwrap();
557 assert!(
558 !sql.contains("EVAL OFFSET"),
559 "zero offset must be omitted, got:\n{sql}"
560 );
561 let stmts = ParserContext::create_with_dialect(
562 &sql,
563 &GreptimeDbDialect {},
564 ParseOptions::default(),
565 )
566 .unwrap();
567 let Statement::CreateFlow(reparsed) = &stmts[0] else {
568 panic!("unexpected stmt: {:?}", stmts[0]);
569 };
570 assert_eq!(reparsed.eval_offset, None);
571 }
572}