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