Skip to main content

flow/adapter/
refill.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
15//! This module contains the refill flow task, which is used to refill flow with given table id and a time range.
16
17use std::collections::BTreeSet;
18use std::sync::Arc;
19
20use catalog::CatalogManagerRef;
21use client::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
22use common_error::ext::BoxedError;
23use common_meta::key::flow::FlowMetadataManagerRef;
24use common_recordbatch::{RecordBatch, RecordBatches, SendableRecordBatchStream};
25use common_runtime::JoinHandle;
26use common_telemetry::error;
27use datatypes::value::Value;
28use futures::StreamExt;
29use query::parser::QueryLanguageParser;
30use session::context::QueryContextBuilder;
31use snafu::{OptionExt, ResultExt, ensure};
32use table::metadata::TableId;
33
34use crate::adapter::table_source::ManagedTableSource;
35use crate::adapter::{FlowId, FlowStreamingEngineRef, StreamingEngine};
36use crate::error::{FlowNotFoundSnafu, JoinTaskSnafu, UnexpectedSnafu};
37use crate::expr::error::ExternalSnafu;
38use crate::expr::utils::find_plan_time_window_expr_lower_bound;
39use crate::repr::RelationDesc;
40use crate::server::get_all_flow_ids;
41use crate::{Error, FrontendInvoker};
42
43impl StreamingEngine {
44    /// Create and start refill flow tasks in background
45    pub async fn create_and_start_refill_flow_tasks(
46        self: &FlowStreamingEngineRef,
47        flow_metadata_manager: &FlowMetadataManagerRef,
48        catalog_manager: &CatalogManagerRef,
49    ) -> Result<(), Error> {
50        let tasks = self
51            .create_refill_flow_tasks(flow_metadata_manager, catalog_manager)
52            .await?;
53        self.starting_refill_flows(tasks).await?;
54        Ok(())
55    }
56
57    /// Create a series of tasks to refill flow
58    pub async fn create_refill_flow_tasks(
59        &self,
60        flow_metadata_manager: &FlowMetadataManagerRef,
61        catalog_manager: &CatalogManagerRef,
62    ) -> Result<Vec<RefillTask>, Error> {
63        let nodeid = self.node_id.map(|c| c as u64);
64
65        let flow_ids = get_all_flow_ids(flow_metadata_manager, catalog_manager, nodeid).await?;
66        let mut refill_tasks = Vec::new();
67        'flow_id_loop: for flow_id in flow_ids {
68            let info = flow_metadata_manager
69                .flow_info_manager()
70                .get(flow_id)
71                .await
72                .map_err(BoxedError::new)
73                .context(ExternalSnafu)?
74                .context(FlowNotFoundSnafu { id: flow_id })?;
75
76            // TODO(discord9): also check flow is already running
77            for src_table in info.source_table_ids() {
78                // check if source table still exists
79                if !self.table_info_source.check_table_exist(src_table).await? {
80                    error!(
81                        "Source table id = {:?} not found while refill flow_id={}, consider re-create the flow if necessary",
82                        src_table, flow_id
83                    );
84                    continue 'flow_id_loop;
85                }
86            }
87
88            let expire_after = info
89                .expire_after()
90                .map(super::expire_after_secs_to_millis)
91                .transpose()?;
92            // TODO(discord9): better way to get last point
93            let now = self.tick_manager.tick();
94            let plan = self
95                .node_context
96                .read()
97                .await
98                .get_flow_plan(&FlowId::from(flow_id))
99                .context(FlowNotFoundSnafu { id: flow_id })?;
100            let time_range = if let Some(expire_after) = expire_after {
101                let low_bound = common_time::Timestamp::new_millisecond(now - expire_after);
102                let real_low_bound = find_plan_time_window_expr_lower_bound(&plan, low_bound)?;
103                real_low_bound.map(|l| (l, common_time::Timestamp::new_millisecond(now)))
104            } else {
105                None
106            };
107
108            common_telemetry::debug!(
109                "Time range for refill flow_id={} is {:?}",
110                flow_id,
111                time_range
112            );
113
114            for src_table in info.source_table_ids() {
115                let time_index_col = self
116                    .table_info_source
117                    .get_time_index_column_from_table_id(*src_table)
118                    .await?
119                    .1;
120                let time_index_name = time_index_col.name;
121                let task = RefillTask::create(
122                    flow_id as u64,
123                    *src_table,
124                    time_range,
125                    &time_index_name,
126                    &self.table_info_source,
127                )
128                .await?;
129                refill_tasks.push(task);
130            }
131        }
132        Ok(refill_tasks)
133    }
134
135    /// Starting to refill flows, if any error occurs, will rebuild the flow and retry
136    pub(crate) async fn starting_refill_flows(
137        self: &FlowStreamingEngineRef,
138        tasks: Vec<RefillTask>,
139    ) -> Result<(), Error> {
140        // TODO(discord9): add a back pressure mechanism
141        let frontend_invoker =
142            self.frontend_invoker
143                .read()
144                .await
145                .clone()
146                .context(UnexpectedSnafu {
147                    reason: "frontend invoker is not set",
148                })?;
149
150        for mut task in tasks {
151            task.start_running(self.clone(), &frontend_invoker).await?;
152            // TODO(discord9): save refill tasks to a map and check if it's finished when necessary
153            // i.e. when system table need query it's state
154            self.refill_tasks
155                .write()
156                .await
157                .insert(task.data.flow_id, task);
158        }
159        Ok(())
160    }
161}
162
163/// Task to refill flow with given table id and a time range
164pub struct RefillTask {
165    data: TaskData,
166    state: TaskState<()>,
167}
168
169#[derive(Clone)]
170struct TaskData {
171    flow_id: FlowId,
172    table_id: TableId,
173    table_schema: RelationDesc,
174}
175
176impl TaskData {
177    /// validate that incoming batch's schema is the same as table schema(by comparing types&names)
178    fn validate_schema(table_schema: &RelationDesc, rb: &RecordBatch) -> Result<(), Error> {
179        let rb_schema = &rb.schema;
180        ensure!(
181            rb_schema.column_schemas().len() == table_schema.len()?,
182            UnexpectedSnafu {
183                reason: format!(
184                    "RecordBatch schema length does not match table schema length, {}!={}",
185                    rb_schema.column_schemas().len(),
186                    table_schema.len()?
187                )
188            }
189        );
190        for (i, rb_col) in rb_schema.column_schemas().iter().enumerate() {
191            let (rb_name, rb_ty) = (rb_col.name.as_str(), &rb_col.data_type);
192            let (table_name, table_ty) = (
193                table_schema.names[i].as_ref(),
194                &table_schema.typ().column_types[i].scalar_type,
195            );
196            ensure!(
197                Some(rb_name) == table_name.map(|c| c.as_str()),
198                UnexpectedSnafu {
199                    reason: format!(
200                        "Mismatch in column names: expected {:?}, found {}",
201                        table_name, rb_name
202                    )
203                }
204            );
205
206            ensure!(
207                rb_ty == table_ty,
208                UnexpectedSnafu {
209                    reason: format!(
210                        "Mismatch in column types for {}: expected {:?}, found {:?}",
211                        rb_name, table_ty, rb_ty
212                    )
213                }
214            );
215        }
216        Ok(())
217    }
218}
219
220/// Refill task state
221enum TaskState<T> {
222    /// Task is not started
223    Prepared { sql: String },
224    /// Task is running
225    Running {
226        handle: JoinHandle<Result<T, Error>>,
227    },
228    /// Task is finished
229    Finished { res: Result<T, Error> },
230}
231
232impl<T> TaskState<T> {
233    fn new(sql: String) -> Self {
234        Self::Prepared { sql }
235    }
236}
237
238mod test_send {
239    use std::collections::BTreeMap;
240
241    use tokio::sync::RwLock;
242
243    use super::*;
244    fn is_send<T: Send + Sync>() {}
245    fn foo() {
246        is_send::<TaskState<()>>();
247        is_send::<RefillTask>();
248        is_send::<BTreeMap<FlowId, RefillTask>>();
249        is_send::<RwLock<BTreeMap<FlowId, RefillTask>>>();
250    }
251}
252
253impl TaskState<()> {
254    /// check if task is finished
255    async fn is_finished(&mut self) -> Result<bool, Error> {
256        match self {
257            Self::Finished { .. } => Ok(true),
258            Self::Running { handle } => Ok(if handle.is_finished() {
259                *self = Self::Finished {
260                    res: handle.await.context(JoinTaskSnafu)?,
261                };
262                true
263            } else {
264                false
265            }),
266            _ => Ok(false),
267        }
268    }
269
270    fn start_running(
271        &mut self,
272        task_data: &TaskData,
273        manager: FlowStreamingEngineRef,
274        mut output_stream: SendableRecordBatchStream,
275    ) -> Result<(), Error> {
276        let data = (*task_data).clone();
277        let handle: JoinHandle<Result<(), Error>> = common_runtime::spawn_global(async move {
278            while let Some(rb) = output_stream.next().await {
279                let rb = match rb {
280                    Ok(rb) => rb,
281                    Err(err) => Err(BoxedError::new(err)).context(ExternalSnafu)?,
282                };
283                TaskData::validate_schema(&data.table_schema, &rb)?;
284
285                // send rb into flow node
286                manager
287                    .node_context
288                    .read()
289                    .await
290                    .send_rb(data.table_id, rb)
291                    .await?;
292            }
293            common_telemetry::info!(
294                "Refill successful for source table_id={}, flow_id={}",
295                data.table_id,
296                data.flow_id
297            );
298            Ok(())
299        });
300        *self = Self::Running { handle };
301
302        Ok(())
303    }
304}
305
306/// Query stream of RefillTask, simply wrap RecordBatches and RecordBatchStream and check output is not `AffectedRows`
307enum QueryStream {
308    Batches { batches: RecordBatches },
309    Stream { stream: SendableRecordBatchStream },
310}
311
312impl TryFrom<common_query::Output> for QueryStream {
313    type Error = Error;
314    fn try_from(value: common_query::Output) -> Result<Self, Self::Error> {
315        match value.data {
316            common_query::OutputData::Stream(stream) => Ok(QueryStream::Stream { stream }),
317            common_query::OutputData::RecordBatches(batches) => {
318                Ok(QueryStream::Batches { batches })
319            }
320            _ => UnexpectedSnafu {
321                reason: format!("Unexpected output data type: {:?}", value.data),
322            }
323            .fail(),
324        }
325    }
326}
327
328impl QueryStream {
329    fn try_into_stream(self) -> Result<SendableRecordBatchStream, Error> {
330        match self {
331            Self::Batches { batches } => Ok(batches.as_stream()),
332            Self::Stream { stream } => Ok(stream),
333        }
334    }
335}
336
337impl RefillTask {
338    /// Query with "select * from table WHERE time >= range_start and time < range_end"
339    pub async fn create(
340        flow_id: FlowId,
341        table_id: TableId,
342        time_range: Option<(common_time::Timestamp, common_time::Timestamp)>,
343        time_col_name: &str,
344        table_src: &ManagedTableSource,
345    ) -> Result<RefillTask, Error> {
346        let (table_name, table_schema) = table_src.get_table_name_schema(&table_id).await?;
347        let all_col_names: BTreeSet<_> = table_schema
348            .relation_desc
349            .iter_names()
350            .flatten()
351            .map(|s| s.as_str())
352            .collect();
353
354        if !all_col_names.contains(time_col_name) {
355            UnexpectedSnafu {
356                reason: format!(
357                    "Can't find column {} in table {} while refill flow",
358                    time_col_name,
359                    table_name.join(".")
360                ),
361            }
362            .fail()?;
363        }
364
365        let sql = if let Some(time_range) = time_range {
366            format!(
367                "select * from {0} where {1} >= {2} and {1} < {3}",
368                table_name.join("."),
369                time_col_name,
370                Value::from(time_range.0),
371                Value::from(time_range.1),
372            )
373        } else {
374            format!("select * from {0}", table_name.join("."))
375        };
376
377        Ok(RefillTask {
378            data: TaskData {
379                flow_id,
380                table_id,
381                table_schema: table_schema.relation_desc,
382            },
383            state: TaskState::new(sql),
384        })
385    }
386
387    /// Start running the task in background, non-blocking
388    pub async fn start_running(
389        &mut self,
390        manager: FlowStreamingEngineRef,
391        invoker: &FrontendInvoker,
392    ) -> Result<(), Error> {
393        let TaskState::Prepared { sql } = &mut self.state else {
394            UnexpectedSnafu {
395                reason: "task is not prepared",
396            }
397            .fail()?
398        };
399
400        // we don't need information from query context in this query so a default query context is enough
401        let query_ctx = Arc::new(
402            QueryContextBuilder::default()
403                .current_catalog(DEFAULT_CATALOG_NAME.to_string())
404                .current_schema(DEFAULT_SCHEMA_NAME.to_string())
405                .build(),
406        );
407
408        let stmt_exec = invoker.statement_executor();
409
410        let stmt = QueryLanguageParser::parse_sql(sql, &query_ctx)
411            .map_err(BoxedError::new)
412            .context(ExternalSnafu)?;
413        let plan = stmt_exec
414            .plan(&stmt, query_ctx.clone())
415            .await
416            .map_err(BoxedError::new)
417            .context(ExternalSnafu)?;
418
419        let output_data = stmt_exec
420            .exec_plan(plan, query_ctx)
421            .await
422            .map_err(BoxedError::new)
423            .context(ExternalSnafu)?;
424
425        let output_stream = QueryStream::try_from(output_data)?;
426        let output_stream = output_stream.try_into_stream()?;
427
428        self.state
429            .start_running(&self.data, manager, output_stream)?;
430        Ok(())
431    }
432
433    pub async fn is_finished(&mut self) -> Result<bool, Error> {
434        self.state.is_finished().await
435    }
436}