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::SystemTable;
54use crate::system_schema::information_schema::{InformationSchemaTableScope, InformationTable};
55use crate::system_schema::utils;
56
57const INIT_CAPACITY: usize = 42;
58
59// rows of information_schema.flows
60// pk is (flow_name, flow_id, table_catalog)
61pub 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/// The `information_schema.flows` to provides information about flows in databases.
79#[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    /// for complex fields(including [`SOURCE_TABLE_IDS`], [`FLOWNODE_IDS`], [`OPTIONS`] and
102    /// [`FLOWNODE_ADDRS`]), it will be serialized to json string for now
103    /// TODO(discord9): use a better way to store complex fields like json type
104    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    /// Generates the CREATE FLOW statement for the flow_definition column
135    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
238/// Builds the `information_schema.FLOWS` table row by row
239///
240/// columns are based on [`FlowInfoValue`]
241struct 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    /// Construct the `information_schema.flows` virtual table
298    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        // TODO(discord9): use `AsyncIterator` once it's stable-ish
305        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        // `raw_sql` stores only the query part (the `AS` clause).
527        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}