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