Skip to main content

catalog/system_schema/information_schema/
flows.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
58// rows of information_schema.flows
59// pk is (flow_name, flow_id, table_catalog)
60pub 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/// The `information_schema.flows` to provides information about flows in databases.
78#[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    /// for complex fields(including [`SOURCE_TABLE_IDS`], [`FLOWNODE_IDS`], [`OPTIONS`] and
101    /// [`FLOWNODE_ADDRS`]), it will be serialized to json string for now
102    /// TODO(discord9): use a better way to store complex fields like json type
103    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    /// Generates the CREATE FLOW statement for the flow_definition column
134    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
231/// Builds the `information_schema.FLOWS` table row by row
232///
233/// columns are based on [`FlowInfoValue`]
234struct 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    /// Construct the `information_schema.flows` virtual table
291    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        // TODO(discord9): use `AsyncIterator` once it's stable-ish
298        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        // `raw_sql` stores only the query part (the `AS` clause).
520        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}