1use std::sync::Arc;
16use std::time::Duration;
17
18use api::v1::value::ValueData;
19use api::v1::{
20 ColumnDataType, ColumnDef, ColumnSchema as PbColumnSchema, Row, RowInsertRequest,
21 RowInsertRequests, Rows, SemanticType,
22};
23use arrow::array::{Array, AsArray};
24use arrow::datatypes::TimestampNanosecondType;
25use common_query::OutputData;
26use common_recordbatch::util as record_util;
27use common_telemetry::{debug, info};
28use common_time::timestamp::{TimeUnit, Timestamp};
29use datafusion::datasource::DefaultTableSource;
30use datafusion::logical_expr::col;
31use datafusion_common::TableReference;
32use datafusion_expr::{DmlStatement, LogicalPlan};
33use datatypes::timestamp::TimestampNanosecond;
34use itertools::Itertools;
35use operator::insert::InserterRef;
36use operator::statement::StatementExecutorRef;
37use query::QueryEngineRef;
38use session::context::{QueryContextBuilder, QueryContextRef};
39use snafu::{OptionExt, ResultExt, ensure};
40use table::TableRef;
41use table::metadata::TableInfo;
42use table::table::adapter::DfTableProviderAdapter;
43
44use crate::error::{
45 BuildDfLogicalPlanSnafu, CastTypeSnafu, CollectRecordsSnafu, DataFrameSnafu, Error,
46 ExecuteInternalStatementSnafu, InsertPipelineSnafu, InvalidPipelineVersionSnafu,
47 MultiPipelineWithDiffSchemaSnafu, PipelineNotFoundSnafu, RecordBatchLenNotMatchSnafu, Result,
48};
49use crate::etl::{Content, Pipeline, parse};
50use crate::manager::pipeline_cache::{PipelineCache, PipelineContent};
51use crate::manager::{PipelineInfo, PipelineVersion};
52use crate::metrics::METRIC_PIPELINE_TABLE_FIND_COUNT;
53use crate::util::prepare_dataframe_conditions;
54
55pub(crate) const PIPELINE_TABLE_NAME: &str = "pipelines";
56pub(crate) const PIPELINE_TABLE_PIPELINE_NAME_COLUMN_NAME: &str = "name";
57const PIPELINE_TABLE_PIPELINE_SCHEMA_COLUMN_NAME: &str = "schema";
58const PIPELINE_TABLE_PIPELINE_CONTENT_TYPE_COLUMN_NAME: &str = "content_type";
59const PIPELINE_TABLE_PIPELINE_CONTENT_COLUMN_NAME: &str = "pipeline";
60pub(crate) const PIPELINE_TABLE_CREATED_AT_COLUMN_NAME: &str = "created_at";
61pub(crate) const EMPTY_SCHEMA_NAME: &str = "";
62
63pub struct PipelineTable {
66 inserter: InserterRef,
67 statement_executor: StatementExecutorRef,
68 table: TableRef,
69 query_engine: QueryEngineRef,
70 cache: PipelineCache,
71}
72
73impl PipelineTable {
74 pub fn new(
76 inserter: InserterRef,
77 statement_executor: StatementExecutorRef,
78 table: TableRef,
79 query_engine: QueryEngineRef,
80 cache_ttl: Duration,
81 ) -> Self {
82 Self {
83 inserter,
84 statement_executor,
85 table,
86 query_engine,
87 cache: PipelineCache::new(cache_ttl),
88 }
89 }
90
91 pub fn build_pipeline_schema() -> (String, Vec<String>, Vec<ColumnDef>) {
94 (
95 PIPELINE_TABLE_CREATED_AT_COLUMN_NAME.to_string(),
96 vec![
97 PIPELINE_TABLE_PIPELINE_SCHEMA_COLUMN_NAME.to_string(),
98 PIPELINE_TABLE_PIPELINE_NAME_COLUMN_NAME.to_string(),
99 PIPELINE_TABLE_PIPELINE_CONTENT_TYPE_COLUMN_NAME.to_string(),
100 ],
101 vec![
102 ColumnDef {
103 name: PIPELINE_TABLE_PIPELINE_NAME_COLUMN_NAME.to_string(),
104 data_type: ColumnDataType::String as i32,
105 is_nullable: false,
106 default_constraint: vec![],
107 semantic_type: SemanticType::Tag as i32,
108 comment: "".to_string(),
109 datatype_extension: None,
110 options: None,
111 },
112 ColumnDef {
113 name: PIPELINE_TABLE_PIPELINE_SCHEMA_COLUMN_NAME.to_string(),
114 data_type: ColumnDataType::String as i32,
115 is_nullable: false,
116 default_constraint: vec![],
117 semantic_type: SemanticType::Tag as i32,
118 comment: "".to_string(),
119 datatype_extension: None,
120 options: None,
121 },
122 ColumnDef {
123 name: PIPELINE_TABLE_PIPELINE_CONTENT_TYPE_COLUMN_NAME.to_string(),
124 data_type: ColumnDataType::String as i32,
125 is_nullable: false,
126 default_constraint: vec![],
127 semantic_type: SemanticType::Tag as i32,
128 comment: "".to_string(),
129 datatype_extension: None,
130 options: None,
131 },
132 ColumnDef {
133 name: PIPELINE_TABLE_PIPELINE_CONTENT_COLUMN_NAME.to_string(),
134 data_type: ColumnDataType::String as i32,
135 is_nullable: false,
136 default_constraint: vec![],
137 semantic_type: SemanticType::Field as i32,
138 comment: "".to_string(),
139 datatype_extension: None,
140 options: None,
141 },
142 ColumnDef {
143 name: PIPELINE_TABLE_CREATED_AT_COLUMN_NAME.to_string(),
144 data_type: ColumnDataType::TimestampNanosecond as i32,
145 is_nullable: false,
146 default_constraint: vec![],
147 semantic_type: SemanticType::Timestamp as i32,
148 comment: "".to_string(),
149 datatype_extension: None,
150 options: None,
151 },
152 ],
153 )
154 }
155
156 fn build_insert_column_schemas() -> Vec<PbColumnSchema> {
158 vec![
159 PbColumnSchema {
160 column_name: PIPELINE_TABLE_PIPELINE_NAME_COLUMN_NAME.to_string(),
161 datatype: ColumnDataType::String.into(),
162 semantic_type: SemanticType::Tag.into(),
163 ..Default::default()
164 },
165 PbColumnSchema {
166 column_name: PIPELINE_TABLE_PIPELINE_SCHEMA_COLUMN_NAME.to_string(),
167 datatype: ColumnDataType::String.into(),
168 semantic_type: SemanticType::Tag.into(),
169 ..Default::default()
170 },
171 PbColumnSchema {
172 column_name: PIPELINE_TABLE_PIPELINE_CONTENT_TYPE_COLUMN_NAME.to_string(),
173 datatype: ColumnDataType::String.into(),
174 semantic_type: SemanticType::Tag.into(),
175 ..Default::default()
176 },
177 PbColumnSchema {
178 column_name: PIPELINE_TABLE_PIPELINE_CONTENT_COLUMN_NAME.to_string(),
179 datatype: ColumnDataType::String.into(),
180 semantic_type: SemanticType::Field.into(),
181 ..Default::default()
182 },
183 PbColumnSchema {
184 column_name: PIPELINE_TABLE_CREATED_AT_COLUMN_NAME.to_string(),
185 datatype: ColumnDataType::TimestampNanosecond.into(),
186 semantic_type: SemanticType::Timestamp.into(),
187 ..Default::default()
188 },
189 ]
190 }
191
192 fn query_ctx(table_info: &TableInfo) -> QueryContextRef {
193 QueryContextBuilder::default()
194 .current_catalog(table_info.catalog_name.clone())
195 .current_schema(table_info.schema_name.clone())
196 .build()
197 .into()
198 }
199
200 pub fn compile_pipeline(pipeline: &str) -> Result<Pipeline> {
202 let yaml_content = Content::Yaml(pipeline);
203 parse(&yaml_content)
204 }
205
206 async fn insert_pipeline_to_pipeline_table(
208 &self,
209 name: &str,
210 content_type: &str,
211 pipeline: &str,
212 ) -> Result<Timestamp> {
213 let now = Timestamp::current_time(TimeUnit::Nanosecond);
214
215 let table_info = self.table.table_info();
216
217 let insert = RowInsertRequest {
218 table_name: PIPELINE_TABLE_NAME.to_string(),
219 rows: Some(Rows {
220 schema: Self::build_insert_column_schemas(),
221 rows: vec![Row {
222 values: vec![
223 ValueData::StringValue(name.to_string()).into(),
224 ValueData::StringValue(EMPTY_SCHEMA_NAME.to_string()).into(),
225 ValueData::StringValue(content_type.to_string()).into(),
226 ValueData::StringValue(pipeline.to_string()).into(),
227 ValueData::TimestampNanosecondValue(now.value()).into(),
228 ],
229 }],
230 }),
231 };
232
233 let requests = RowInsertRequests {
234 inserts: vec![insert],
235 };
236
237 let output = self
238 .inserter
239 .handle_row_inserts(
240 requests,
241 Self::query_ctx(&table_info),
242 &self.statement_executor,
243 false,
244 false,
245 )
246 .await
247 .context(InsertPipelineSnafu)?;
248
249 info!(
250 "Insert pipeline success, name: {:?}, table: {:?}, output: {:?}",
251 name,
252 table_info.full_table_name(),
253 output
254 );
255
256 Ok(now)
257 }
258
259 pub async fn get_pipeline(
262 &self,
263 schema: &str,
264 name: &str,
265 input_version: PipelineVersion,
266 ) -> Result<Arc<Pipeline>> {
267 self.cache
268 .get_pipeline_with(schema, name, input_version, async {
269 let pipeline_content = self.get_pipeline_str(schema, name, input_version).await?;
270 Ok(Arc::new(Self::compile_pipeline(&pipeline_content.content)?))
271 })
272 .await
273 }
274
275 pub async fn get_pipeline_str(
278 &self,
279 schema: &str,
280 name: &str,
281 input_version: PipelineVersion,
282 ) -> Result<PipelineContent> {
283 self.cache
284 .get_pipeline_str_with(schema, name, input_version, async {
285 self.load_pipeline_str(schema, name, input_version).await
286 })
287 .await
288 }
289
290 async fn load_pipeline_str(
291 &self,
292 schema: &str,
293 name: &str,
294 input_version: PipelineVersion,
295 ) -> Result<PipelineContent> {
296 let mut pipeline_vec;
297 match self.find_pipeline(name, input_version).await {
298 Ok(p) => {
299 METRIC_PIPELINE_TABLE_FIND_COUNT
300 .with_label_values(&["true"])
301 .inc();
302 pipeline_vec = p;
303 }
304 Err(e) => {
305 match e {
306 Error::CollectRecords { .. } => {
307 METRIC_PIPELINE_TABLE_FIND_COUNT
310 .with_label_values(&["false"])
311 .inc();
312 return self
313 .cache
314 .get_failover_cache(schema, name, input_version)
315 .await?
316 .context(PipelineNotFoundSnafu {
317 name,
318 version: input_version,
319 });
320 }
321 _ => {
322 return Err(e);
324 }
325 }
326 }
327 };
328 ensure!(
329 !pipeline_vec.is_empty(),
330 PipelineNotFoundSnafu {
331 name,
332 version: input_version
333 }
334 );
335
336 if pipeline_vec.len() == 1 {
338 let pipeline_content = pipeline_vec.remove(0);
339
340 self.cache
341 .insert_failover_cache(pipeline_content.clone(), input_version.is_none())
342 .await;
343 return Ok(pipeline_content);
344 }
345
346 let pipeline = pipeline_vec
349 .iter()
350 .position(|v| v.schema == EMPTY_SCHEMA_NAME)
351 .or_else(|| pipeline_vec.iter().position(|v| v.schema == schema))
352 .map(|idx| pipeline_vec.remove(idx));
353
354 let pipeline_content = pipeline.with_context(|| MultiPipelineWithDiffSchemaSnafu {
357 name: name.to_string(),
358 current_schema: schema.to_string(),
359 schemas: pipeline_vec.iter().map(|v| v.schema.clone()).join(","),
360 })?;
361
362 self.cache
363 .insert_failover_cache(pipeline_content.clone(), input_version.is_none())
364 .await;
365 Ok(pipeline_content)
366 }
367
368 pub async fn insert_and_compile(
371 &self,
372 name: &str,
373 content_type: &str,
374 pipeline: &str,
375 ) -> Result<PipelineInfo> {
376 let compiled_pipeline = Arc::new(Self::compile_pipeline(pipeline)?);
377 let version = self
379 .insert_pipeline_to_pipeline_table(name, content_type, pipeline)
380 .await?;
381
382 self.cache
383 .on_pipeline_created(PipelineContent {
384 name: name.to_string(),
385 content: pipeline.to_string(),
386 version: TimestampNanosecond(version),
387 schema: EMPTY_SCHEMA_NAME.to_string(),
388 })
389 .await;
390
391 Ok((version, compiled_pipeline))
392 }
393
394 pub async fn delete_pipeline(
395 &self,
396 name: &str,
397 version: PipelineVersion,
398 ) -> Result<Option<()>> {
399 ensure!(
401 version.is_some(),
402 InvalidPipelineVersionSnafu { version: "None" }
403 );
404
405 let pipeline = self.find_pipeline(name, version).await?;
407 if pipeline.is_empty() {
408 return Ok(None);
409 }
410
411 let dataframe = self
413 .query_engine
414 .read_table(self.table.clone())
415 .context(DataFrameSnafu)?;
416
417 let dataframe = dataframe
418 .filter(prepare_dataframe_conditions(name, version))
419 .context(BuildDfLogicalPlanSnafu)?;
420
421 let table_info = self.table.table_info();
423 let table_name = TableReference::full(
424 table_info.catalog_name.clone(),
425 table_info.schema_name.clone(),
426 table_info.name.clone(),
427 );
428
429 let table_provider = Arc::new(DfTableProviderAdapter::new(self.table.clone()));
430 let table_source = Arc::new(DefaultTableSource::new(table_provider));
431
432 let stmt = DmlStatement::new(
434 table_name,
435 table_source,
436 datafusion_expr::WriteOp::Delete,
437 Arc::new(dataframe.into_parts().1),
438 );
439
440 let plan = LogicalPlan::Dml(stmt);
441
442 let output = self
444 .query_engine
445 .execute(plan, Self::query_ctx(&table_info))
446 .await
447 .context(ExecuteInternalStatementSnafu)?;
448
449 info!(
450 "Delete pipeline success, name: {:?}, version: {:?}, table: {:?}, output: {:?}",
451 name,
452 version,
453 table_info.full_table_name(),
454 output
455 );
456
457 self.cache.invalidate(name, version).await;
458
459 Ok(Some(()))
460 }
461
462 async fn find_pipeline(
466 &self,
467 name: &str,
468 version: PipelineVersion,
469 ) -> Result<Vec<PipelineContent>> {
470 let dataframe = self
472 .query_engine
473 .read_table(self.table.clone())
474 .context(DataFrameSnafu)?;
475
476 let dataframe = dataframe
478 .filter(prepare_dataframe_conditions(name, version))
479 .context(BuildDfLogicalPlanSnafu)?
480 .select_columns(&[
481 PIPELINE_TABLE_PIPELINE_CONTENT_COLUMN_NAME,
482 PIPELINE_TABLE_PIPELINE_SCHEMA_COLUMN_NAME,
483 PIPELINE_TABLE_CREATED_AT_COLUMN_NAME,
484 ])
485 .context(BuildDfLogicalPlanSnafu)?
486 .sort(vec![
487 col(PIPELINE_TABLE_CREATED_AT_COLUMN_NAME).sort(false, true),
488 ])
489 .context(BuildDfLogicalPlanSnafu)?;
490
491 let plan = dataframe.into_parts().1;
492
493 let table_info = self.table.table_info();
494
495 debug!("find_pipeline_by_name: plan: {:?}", plan);
496
497 let output = self
499 .query_engine
500 .execute(plan, Self::query_ctx(&table_info))
501 .await
502 .context(ExecuteInternalStatementSnafu)?;
503 let output = output
504 .map_dictionary_to_values()
505 .context(CollectRecordsSnafu)?;
506 let stream = match output.data {
507 OutputData::Stream(stream) => stream,
508 OutputData::RecordBatches(record_batches) => record_batches.as_stream(),
509 _ => unreachable!(),
510 };
511
512 let records = record_util::collect(stream)
514 .await
515 .context(CollectRecordsSnafu)?;
516
517 if records.is_empty() {
518 return Ok(vec![]);
519 }
520
521 ensure!(
522 !records.is_empty() && records.iter().all(|r| r.num_columns() == 3),
523 PipelineNotFoundSnafu { name, version }
524 );
525
526 let mut re = Vec::with_capacity(records.len());
527 for r in records {
528 let pipeline_content_column = r.column(0);
529 let pipeline_content = pipeline_content_column
530 .as_string_opt::<i32>()
531 .with_context(|| CastTypeSnafu {
532 msg: format!(
533 "can't downcast {:?} array into string vector",
534 pipeline_content_column.data_type()
535 ),
536 })?;
537
538 let pipeline_schema_column = r.column(1);
539 let pipeline_schema =
540 pipeline_schema_column
541 .as_string_opt::<i32>()
542 .with_context(|| CastTypeSnafu {
543 msg: format!(
544 "expecting pipeline schema column of type string, actual: {}",
545 pipeline_schema_column.data_type()
546 ),
547 })?;
548
549 let pipeline_created_at_column = r.column(2);
550 let pipeline_created_at = pipeline_created_at_column
551 .as_primitive_opt::<TimestampNanosecondType>()
552 .with_context(|| CastTypeSnafu {
553 msg: format!(
554 "can't downcast {:?} array into scalar vector",
555 pipeline_created_at_column.data_type()
556 ),
557 })?;
558
559 debug!(
560 "find_pipeline_by_name: pipeline_content: {:?}, pipeline_schema: {:?}, pipeline_created_at: {:?}",
561 pipeline_content, pipeline_schema, pipeline_created_at
562 );
563
564 ensure!(
565 pipeline_content.len() == pipeline_schema.len()
566 && pipeline_schema.len() == pipeline_created_at.len(),
567 RecordBatchLenNotMatchSnafu
568 );
569
570 let len = pipeline_content.len();
571 for i in 0..len {
572 re.push(PipelineContent {
573 name: name.to_string(),
574 content: pipeline_content.value(i).to_string(),
575 version: TimestampNanosecond::new(pipeline_created_at.value(i)),
576 schema: pipeline_schema.value(i).to_string(),
577 });
578 }
579 }
580
581 Ok(re)
582 }
583}