1pub mod coerce;
16
17use std::borrow::Cow;
18use std::collections::{BTreeMap, HashSet};
19use std::sync::Arc;
20
21use ahash::{HashMap, HashMapExt};
22use api::helper::{ColumnDataTypeWrapper, encode_json_value};
23use api::v1::column_def::{collect_column_options, options_from_column_schema};
24use api::v1::value::ValueData;
25use api::v1::{ColumnDataType, SemanticType};
26use arrow_schema::extension::ExtensionType;
27use coerce::{coerce_columns, coerce_value};
28use common_query::prelude::{greptime_timestamp, greptime_value};
29use common_telemetry::warn;
30use datatypes::data_type::ConcreteDataType;
31use datatypes::extension::json::{Json2ExtensionType, parse_legacy_json2_settings};
32use datatypes::json::JsonSettings;
33use datatypes::value::Value;
34use greptime_proto::v1::{ColumnSchema, Row, Rows, Value as GreptimeValue};
35use itertools::Itertools;
36use jsonb::Number;
37use once_cell::sync::OnceCell;
38use serde_json as serde_json_crate;
39use session::context::Channel;
40use snafu::OptionExt;
41use table::Table;
42use vrl::prelude::{Bytes, VrlValueConvert};
43use vrl::value::value::StdError;
44use vrl::value::{KeyString, Value as VrlValue};
45
46use crate::error::{
47 ArrayElementMustBeObjectSnafu, CoerceIncompatibleTypesSnafu,
48 IdentifyPipelineColumnTypeMismatchSnafu, InvalidTimestampSnafu, Result,
49 TimeIndexMustBeNonNullSnafu, TransformColumnNameMustBeUniqueSnafu,
50 TransformMultipleTimestampIndexSnafu, TransformTimestampIndexCountSnafu, ValueMustBeMapSnafu,
51};
52use crate::etl::PipelineDocVersion;
53use crate::etl::ctx_req::ContextOpt;
54use crate::etl::field::{Field, Fields};
55use crate::etl::transform::index::Index;
56use crate::etl::transform::{Transform, Transforms};
57use crate::{PipelineContext, truthy, unwrap_or_continue_if_err};
58
59const DEFAULT_MAX_NESTED_LEVELS_FOR_JSON_FLATTENING: usize = 10;
60
61pub type RowWithTableSuffix = (Row, Option<String>);
63
64#[derive(Debug, Clone)]
67pub struct GreptimeTransformer {
68 transforms: Transforms,
69 schema: Vec<ColumnSchema>,
70}
71
72#[derive(Debug, Default)]
74pub struct GreptimePipelineParams {
75 options: HashMap<String, String>,
78
79 pub skip_error: OnceCell<bool>,
81 pub max_nested_levels: OnceCell<usize>,
84}
85
86impl GreptimePipelineParams {
87 pub fn from_params(params: Option<&str>) -> Self {
91 let options = Self::parse_header_str_to_map(params);
92
93 Self {
94 options,
95 skip_error: OnceCell::new(),
96 max_nested_levels: OnceCell::new(),
97 }
98 }
99
100 pub fn from_map(options: HashMap<String, String>) -> Self {
101 Self {
102 options,
103 skip_error: OnceCell::new(),
104 max_nested_levels: OnceCell::new(),
105 }
106 }
107
108 pub fn parse_header_str_to_map(params: Option<&str>) -> HashMap<String, String> {
109 if let Some(params) = params {
110 if params.is_empty() {
111 HashMap::new()
112 } else {
113 params
114 .split('&')
115 .filter_map(|s| s.split_once('='))
116 .map(|(k, v)| (k.to_string(), v.to_string()))
117 .collect::<HashMap<String, String>>()
118 }
119 } else {
120 HashMap::new()
121 }
122 }
123
124 pub fn skip_error(&self) -> bool {
126 *self
127 .skip_error
128 .get_or_init(|| self.options.get("skip_error").map(truthy).unwrap_or(false))
129 }
130
131 pub fn max_nested_levels(&self) -> usize {
134 *self.max_nested_levels.get_or_init(|| {
135 self.options
136 .get("max_nested_levels")
137 .and_then(|s| s.parse::<usize>().ok())
138 .filter(|v| *v > 0)
139 .unwrap_or(DEFAULT_MAX_NESTED_LEVELS_FOR_JSON_FLATTENING)
140 })
141 }
142}
143
144impl GreptimeTransformer {
145 fn add_greptime_timestamp_column(transforms: &mut Transforms) {
147 let type_ = ColumnDataType::TimestampNanosecond;
148 let default = None;
149
150 let transform = Transform {
151 fields: Fields::one(Field::new(greptime_timestamp().to_string(), None)),
152 type_,
153 default,
154 index: Some(Index::Time),
155 index_options: None,
156 on_failure: Some(crate::etl::transform::OnFailure::Default),
157 tag: false,
158 };
159 transforms.push(transform);
160 }
161
162 fn init_schemas(transforms: &Transforms) -> Result<Vec<ColumnSchema>> {
164 let mut schema = vec![];
165 for transform in transforms.iter() {
166 schema.extend(coerce_columns(transform)?);
167 }
168 Ok(schema)
169 }
170}
171
172impl GreptimeTransformer {
173 pub fn new(mut transforms: Transforms, doc_version: &PipelineDocVersion) -> Result<Self> {
174 let mut column_names_set = HashSet::new();
176 let mut timestamp_columns = vec![];
177
178 for transform in transforms.iter() {
179 let target_fields_set = transform
180 .fields
181 .iter()
182 .map(|f| f.target_or_input_field())
183 .collect::<HashSet<_>>();
184
185 let intersections: Vec<_> = column_names_set.intersection(&target_fields_set).collect();
186 if !intersections.is_empty() {
187 let duplicates = intersections.iter().join(",");
188 return TransformColumnNameMustBeUniqueSnafu { duplicates }.fail();
189 }
190
191 column_names_set.extend(target_fields_set);
192
193 if let Some(idx) = transform.index
194 && idx == Index::Time
195 {
196 match transform.fields.len() {
197 1 => timestamp_columns.push(transform.fields.first().unwrap().input_field()),
199 _ => {
200 return TransformMultipleTimestampIndexSnafu {
201 columns: transform.fields.iter().map(|x| x.input_field()).join(", "),
202 }
203 .fail();
204 }
205 }
206 }
207 }
208
209 let schema = match timestamp_columns.len() {
210 0 if doc_version == &PipelineDocVersion::V1 => {
211 GreptimeTransformer::add_greptime_timestamp_column(&mut transforms);
213 GreptimeTransformer::init_schemas(&transforms)?
214 }
215 1 => GreptimeTransformer::init_schemas(&transforms)?,
216 count => {
217 let columns = timestamp_columns.iter().join(", ");
218 return TransformTimestampIndexCountSnafu { count, columns }.fail();
219 }
220 };
221 Ok(GreptimeTransformer { transforms, schema })
222 }
223
224 pub fn transform_mut(
225 &self,
226 pipeline_map: &mut VrlValue,
227 is_v1: bool,
228 ) -> Result<Vec<GreptimeValue>> {
229 let mut values = vec![GreptimeValue { value_data: None }; self.schema.len()];
230 let mut output_index = 0;
231 for transform in self.transforms.iter() {
232 for field in transform.fields.iter() {
233 let column_name = field.input_field();
234
235 let pipeline_map = pipeline_map.as_object_mut().context(ValueMustBeMapSnafu)?;
236 match pipeline_map.get(column_name) {
238 Some(v) => {
239 let value_data = coerce_value(v, transform)?;
240 values[output_index] = GreptimeValue { value_data };
242 }
243 None => {
244 let value_data = match transform.on_failure {
245 Some(crate::etl::transform::OnFailure::Default) => {
246 match transform.get_default() {
247 Some(default) => Some(default.clone()),
248 None => transform.get_default_value_when_data_is_none(),
249 }
250 }
251 Some(crate::etl::transform::OnFailure::Ignore) => None,
252 None => None,
253 };
254 if transform.is_timeindex() && value_data.is_none() {
255 return TimeIndexMustBeNonNullSnafu.fail();
256 }
257 values[output_index] = GreptimeValue { value_data };
258 }
259 }
260 output_index += 1;
261 if !is_v1 {
262 pipeline_map.remove(column_name);
265 }
266 }
267 }
268 Ok(values)
269 }
270
271 pub fn transforms(&self) -> &Transforms {
272 &self.transforms
273 }
274
275 pub fn schemas(&self) -> &Vec<greptime_proto::v1::ColumnSchema> {
276 &self.schema
277 }
278
279 pub fn transforms_mut(&mut self) -> &mut Transforms {
280 &mut self.transforms
281 }
282}
283
284#[derive(Clone)]
285pub struct ColumnMetadata {
286 column_schema: datatypes::schema::ColumnSchema,
287 semantic_type: SemanticType,
288}
289
290impl From<ColumnSchema> for ColumnMetadata {
291 fn from(value: ColumnSchema) -> Self {
292 let datatype = value.datatype();
293 let semantic_type = value.semantic_type();
294 let ColumnSchema {
295 column_name,
296 datatype: _,
297 semantic_type: _,
298 datatype_extension,
299 options,
300 } = value;
301
302 let column_schema = datatypes::schema::ColumnSchema::new(
303 column_name,
304 ColumnDataTypeWrapper::new(datatype, datatype_extension).into(),
305 semantic_type != SemanticType::Timestamp,
306 );
307
308 let metadata = collect_column_options(options.as_ref());
309 let column_schema = column_schema.with_metadata(metadata);
310
311 Self {
312 column_schema,
313 semantic_type,
314 }
315 }
316}
317
318impl TryFrom<ColumnMetadata> for ColumnSchema {
319 type Error = api::error::Error;
320
321 fn try_from(value: ColumnMetadata) -> std::result::Result<Self, Self::Error> {
322 let ColumnMetadata {
323 column_schema,
324 semantic_type,
325 } = value;
326
327 let options = options_from_column_schema(&column_schema);
328
329 let (datatype, datatype_extension) =
330 ColumnDataTypeWrapper::try_from(column_schema.data_type).map(|x| x.into_parts())?;
331
332 Ok(ColumnSchema {
333 column_name: column_schema.name,
334 datatype: datatype as _,
335 semantic_type: semantic_type as _,
336 datatype_extension,
337 options,
338 })
339 }
340}
341
342#[derive(Default)]
346pub struct SchemaInfo {
347 pub schema: Vec<ColumnMetadata>,
349 pub index: HashMap<String, usize>,
351 table: Option<Arc<Table>>,
353}
354
355impl SchemaInfo {
356 pub fn with_capacity(capacity: usize) -> Self {
357 Self {
358 schema: Vec::with_capacity(capacity),
359 index: HashMap::with_capacity(capacity),
360 table: None,
361 }
362 }
363
364 pub fn from_schema_list(schema_list: Vec<ColumnSchema>) -> Self {
365 let mut index = HashMap::new();
366 for (i, schema) in schema_list.iter().enumerate() {
367 index.insert(schema.column_name.clone(), i);
368 }
369 Self {
370 schema: schema_list.into_iter().map(Into::into).collect(),
371 index,
372 table: None,
373 }
374 }
375
376 pub fn set_table(&mut self, table: Option<Arc<Table>>) {
377 self.table = table;
378 }
379
380 fn find_column_schema_in_table(&self, column_name: &str) -> Option<ColumnMetadata> {
381 if let Some(table) = &self.table
382 && let Some(i) = table.schema_ref().column_index_by_name(column_name)
383 {
384 let column_schema = table.schema_ref().column_schemas()[i].clone();
385
386 let semantic_type = if column_schema.is_time_index() {
387 SemanticType::Timestamp
388 } else if table.table_info().meta.primary_key_indices.contains(&i) {
389 SemanticType::Tag
390 } else {
391 SemanticType::Field
392 };
393
394 Some(ColumnMetadata {
395 column_schema,
396 semantic_type,
397 })
398 } else {
399 None
400 }
401 }
402
403 pub fn column_schemas(&self) -> api::error::Result<Vec<ColumnSchema>> {
404 self.schema
405 .iter()
406 .map(|x| x.clone().try_into())
407 .collect::<api::error::Result<Vec<_>>>()
408 }
409}
410
411fn resolve_schema(
412 index: Option<usize>,
413 pipeline_context: &PipelineContext,
414 column: &str,
415 value_type: &ConcreteDataType,
416 schema_info: &mut SchemaInfo,
417) -> Result<()> {
418 if let Some(index) = index {
419 let column_type = &mut schema_info.schema[index].column_schema.data_type;
420 match (column_type, value_type) {
421 (column_type, value_type) if column_type == value_type => Ok(()),
422 (ConcreteDataType::Json(column_type), ConcreteDataType::Json(value_type))
423 if column_type.is_json2() && value_type.is_json2() =>
424 {
425 Ok(())
426 }
427 (column_type, value_type) => IdentifyPipelineColumnTypeMismatchSnafu {
428 column,
429 expected: column_type.to_string(),
430 actual: value_type.to_string(),
431 }
432 .fail(),
433 }
434 } else {
435 let column_schema = schema_info
436 .find_column_schema_in_table(column)
437 .unwrap_or_else(|| {
438 let semantic_type = decide_semantic(pipeline_context, column);
439 let column_schema = datatypes::schema::ColumnSchema::new(
440 column,
441 value_type.clone(),
442 semantic_type != SemanticType::Timestamp,
443 );
444 ColumnMetadata {
445 column_schema,
446 semantic_type,
447 }
448 });
449 let key = column.to_string();
450 schema_info.schema.push(column_schema);
451 schema_info.index.insert(key, schema_info.schema.len() - 1);
452 Ok(())
453 }
454}
455
456fn calc_ts(p_ctx: &PipelineContext, values: &VrlValue) -> Result<Option<ValueData>> {
457 match p_ctx.channel {
458 Channel::Prometheus => {
459 let ts = values
460 .as_object()
461 .and_then(|m| m.get(greptime_timestamp()))
462 .and_then(|ts| ts.try_into_i64().ok())
463 .unwrap_or_default();
464 Ok(Some(ValueData::TimestampMillisecondValue(ts)))
465 }
466 _ => {
467 let custom_ts = p_ctx.pipeline_definition.get_custom_ts();
468 match custom_ts {
469 Some(ts) => {
470 let ts_field = values.as_object().and_then(|m| m.get(ts.get_column_name()));
471 Some(ts.get_timestamp_value(ts_field)).transpose()
472 }
473 None => Ok(Some(ValueData::TimestampNanosecondValue(
474 chrono::Utc::now().timestamp_nanos_opt().unwrap_or_default(),
475 ))),
476 }
477 }
478 }
479}
480
481pub(crate) fn values_to_rows(
490 schema_info: &mut SchemaInfo,
491 mut values: VrlValue,
492 pipeline_ctx: &PipelineContext<'_>,
493 row: Option<Vec<GreptimeValue>>,
494 need_calc_ts: bool,
495 tablesuffix_template: Option<&crate::tablesuffix::TableSuffixTemplate>,
496) -> Result<std::collections::HashMap<ContextOpt, Vec<RowWithTableSuffix>>> {
497 let skip_error = pipeline_ctx.pipeline_param.skip_error();
498 let VrlValue::Array(arr) = values else {
499 let mut result = std::collections::HashMap::new();
501
502 let mut opt = match ContextOpt::from_pipeline_map_to_opt(&mut values) {
503 Ok(r) => r,
504 Err(e) => return if skip_error { Ok(result) } else { Err(e) },
505 };
506
507 let table_suffix = opt.resolve_table_suffix(tablesuffix_template, &values);
508 let row = match values_to_row(schema_info, values, pipeline_ctx, row, need_calc_ts) {
509 Ok(r) => r,
510 Err(e) => return if skip_error { Ok(result) } else { Err(e) },
511 };
512 result.insert(opt, vec![(row, table_suffix)]);
513 return Ok(result);
514 };
515
516 let mut rows_by_context: std::collections::HashMap<ContextOpt, Vec<RowWithTableSuffix>> =
517 std::collections::HashMap::new();
518 for (index, mut value) in arr.into_iter().enumerate() {
519 if !value.is_object() {
520 unwrap_or_continue_if_err!(
521 ArrayElementMustBeObjectSnafu {
522 index,
523 actual_type: value.kind_str().to_string(),
524 }
525 .fail(),
526 skip_error
527 );
528 }
529
530 let mut opt = unwrap_or_continue_if_err!(
532 ContextOpt::from_pipeline_map_to_opt(&mut value),
533 skip_error
534 );
535 let table_suffix = opt.resolve_table_suffix(tablesuffix_template, &value);
536 let transformed_row = unwrap_or_continue_if_err!(
537 values_to_row(schema_info, value, pipeline_ctx, row.clone(), need_calc_ts),
538 skip_error
539 );
540 rows_by_context
541 .entry(opt)
542 .or_default()
543 .push((transformed_row, table_suffix));
544 }
545 Ok(rows_by_context)
546}
547
548pub(crate) fn values_to_row(
555 schema_info: &mut SchemaInfo,
556 values: VrlValue,
557 pipeline_ctx: &PipelineContext<'_>,
558 row: Option<Vec<GreptimeValue>>,
559 need_calc_ts: bool,
560) -> Result<Row> {
561 let mut row: Vec<GreptimeValue> =
562 row.unwrap_or_else(|| Vec::with_capacity(schema_info.schema.len()));
563 let custom_ts = pipeline_ctx.pipeline_definition.get_custom_ts();
564
565 if need_calc_ts {
566 let ts = calc_ts(pipeline_ctx, &values)?;
568 row.push(GreptimeValue { value_data: ts });
569 }
570
571 row.resize(schema_info.schema.len(), GreptimeValue { value_data: None });
572
573 let ts_column_name = custom_ts
575 .as_ref()
576 .map_or(greptime_timestamp(), |ts| ts.get_column_name());
577
578 let values = values.into_object().context(ValueMustBeMapSnafu)?;
579
580 for (column_name, value) in values {
581 if need_calc_ts && column_name.as_str() == ts_column_name {
582 continue;
583 }
584
585 resolve_value(
586 value,
587 column_name.into(),
588 &mut row,
589 schema_info,
590 pipeline_ctx,
591 )?;
592 }
593 Ok(Row { values: row })
594}
595
596fn decide_semantic(p_ctx: &PipelineContext, column_name: &str) -> SemanticType {
597 if p_ctx.channel == Channel::Prometheus && column_name != greptime_value() {
598 SemanticType::Tag
599 } else {
600 SemanticType::Field
601 }
602}
603
604fn resolve_value(
605 value: VrlValue,
606 column_name: String,
607 row: &mut Vec<GreptimeValue>,
608 schema_info: &mut SchemaInfo,
609 p_ctx: &PipelineContext,
610) -> Result<()> {
611 let index = schema_info.index.get(&column_name).copied();
612
613 let value_data = match value {
614 VrlValue::Null => return Ok(()),
615
616 VrlValue::Integer(v) => {
617 resolve_schema(
619 index,
620 p_ctx,
621 &column_name,
622 &ConcreteDataType::int64_datatype(),
623 schema_info,
624 )?;
625 Some(ValueData::I64Value(v))
626 }
627
628 VrlValue::Float(v) => {
629 resolve_schema(
631 index,
632 p_ctx,
633 &column_name,
634 &ConcreteDataType::float64_datatype(),
635 schema_info,
636 )?;
637 Some(ValueData::F64Value(v.into()))
638 }
639
640 VrlValue::Boolean(v) => {
641 resolve_schema(
642 index,
643 p_ctx,
644 &column_name,
645 &ConcreteDataType::boolean_datatype(),
646 schema_info,
647 )?;
648 Some(ValueData::BoolValue(v))
649 }
650
651 VrlValue::Bytes(v) => {
652 resolve_schema(
653 index,
654 p_ctx,
655 &column_name,
656 &ConcreteDataType::string_datatype(),
657 schema_info,
658 )?;
659 Some(ValueData::StringValue(String::from_utf8_lossy_owned(
660 v.to_vec(),
661 )))
662 }
663
664 VrlValue::Regex(v) => {
665 warn!(
666 "Persisting regex value in the table, this should not happen, column_name: {}",
667 column_name
668 );
669 resolve_schema(
670 index,
671 p_ctx,
672 &column_name,
673 &ConcreteDataType::string_datatype(),
674 schema_info,
675 )?;
676 Some(ValueData::StringValue(v.to_string()))
677 }
678
679 VrlValue::Timestamp(ts) => {
680 let ns = ts.timestamp_nanos_opt().context(InvalidTimestampSnafu {
681 input: ts.to_rfc3339(),
682 })?;
683 resolve_schema(
684 index,
685 p_ctx,
686 &column_name,
687 &ConcreteDataType::timestamp_nanosecond_datatype(),
688 schema_info,
689 )?;
690 Some(ValueData::TimestampNanosecondValue(ns))
691 }
692
693 VrlValue::Array(_) | VrlValue::Object(_) => {
694 let is_json2 = schema_info
695 .find_column_schema_in_table(&column_name)
696 .is_some_and(|x| {
698 matches!(
699 &x.column_schema.data_type,
700 ConcreteDataType::Json(column_type) if column_type.is_json2()
701 )
702 });
703
704 let value = if is_json2 {
705 let value: serde_json::Value = value.try_into().map_err(|e: StdError| {
706 CoerceIncompatibleTypesSnafu { msg: e.to_string() }.build()
707 })?;
708 let value =
709 if let Some(column) = schema_info.find_column_schema_in_table(&column_name) {
710 if let Some(extension) = column
711 .column_schema
712 .extension_type::<Json2ExtensionType>()?
713 {
714 extension.metadata().json_settings().encode(value)?
715 } else {
716 parse_legacy_json2_settings(column.column_schema.metadata())?
717 .unwrap_or_default()
718 .encode(value)?
719 }
720 } else {
721 JsonSettings::default().encode(value)?
722 };
723
724 resolve_schema(
725 index,
726 p_ctx,
727 &column_name,
728 &ConcreteDataType::json2(Default::default()),
729 schema_info,
730 )?;
731
732 let Value::Json(value) = value else {
733 unreachable!()
734 };
735 ValueData::JsonValue(encode_json_value(*value))
736 } else {
737 resolve_schema(
738 index,
739 p_ctx,
740 &column_name,
741 &ConcreteDataType::binary_datatype(),
742 schema_info,
743 )?;
744
745 let value = vrl_value_to_jsonb_value(&value);
746 ValueData::BinaryValue(value.to_vec())
747 };
748 Some(value)
749 }
750 };
751
752 let value = GreptimeValue { value_data };
753 if let Some(index) = index {
754 row[index] = value;
755 } else {
756 row.push(value);
757 }
758 Ok(())
759}
760
761fn vrl_value_to_jsonb_value<'a>(value: &'a VrlValue) -> jsonb::Value<'a> {
762 match value {
763 VrlValue::Bytes(bytes) => jsonb::Value::String(String::from_utf8_lossy(bytes)),
764 VrlValue::Regex(value_regex) => jsonb::Value::String(Cow::Borrowed(value_regex.as_str())),
765 VrlValue::Integer(i) => jsonb::Value::Number(Number::Int64(*i)),
766 VrlValue::Float(not_nan) => jsonb::Value::Number(Number::Float64(not_nan.into_inner())),
767 VrlValue::Boolean(b) => jsonb::Value::Bool(*b),
768 VrlValue::Timestamp(date_time) => jsonb::Value::String(Cow::Owned(date_time.to_rfc3339())),
769 VrlValue::Object(btree_map) => jsonb::Value::Object(
770 btree_map
771 .iter()
772 .map(|(key, value)| (key.to_string(), vrl_value_to_jsonb_value(value)))
773 .collect(),
774 ),
775 VrlValue::Array(values) => jsonb::Value::Array(
776 values
777 .iter()
778 .map(|value| vrl_value_to_jsonb_value(value))
779 .collect(),
780 ),
781 VrlValue::Null => jsonb::Value::Null,
782 }
783}
784
785fn identity_pipeline_inner(
786 pipeline_maps: Vec<VrlValue>,
787 pipeline_ctx: &PipelineContext<'_>,
788 max_nested_levels: usize,
789) -> Result<(SchemaInfo, HashMap<ContextOpt, Vec<Row>>)> {
790 let skip_error = pipeline_ctx.pipeline_param.skip_error();
791 let mut schema_info = SchemaInfo::default();
792 let custom_ts = pipeline_ctx.pipeline_definition.get_custom_ts();
793
794 let column_schema = datatypes::schema::ColumnSchema::new(
796 custom_ts
797 .map(|ts| ts.get_column_name().to_string())
798 .unwrap_or_else(|| greptime_timestamp().to_string()),
799 custom_ts
800 .map(|c| ConcreteDataType::from(ColumnDataTypeWrapper::new(c.get_datatype(), None)))
801 .unwrap_or_else(|| {
802 if pipeline_ctx.channel == Channel::Prometheus {
803 ConcreteDataType::timestamp_millisecond_datatype()
804 } else {
805 ConcreteDataType::timestamp_nanosecond_datatype()
806 }
807 }),
808 false,
809 );
810 schema_info.schema.push(ColumnMetadata {
811 column_schema,
812 semantic_type: SemanticType::Timestamp,
813 });
814
815 let mut opt_map = HashMap::new();
816 let len = pipeline_maps.len();
817
818 for pipeline_map in pipeline_maps {
819 let mut pipeline_map =
820 unwrap_or_continue_if_err!(flatten_object(pipeline_map, max_nested_levels), skip_error);
821 let opt = unwrap_or_continue_if_err!(
822 ContextOpt::from_pipeline_map_to_opt(&mut pipeline_map),
823 skip_error
824 );
825 let row = unwrap_or_continue_if_err!(
826 values_to_row(&mut schema_info, pipeline_map, pipeline_ctx, None, true),
827 skip_error
828 );
829
830 opt_map
831 .entry(opt)
832 .or_insert_with(|| Vec::with_capacity(len))
833 .push(row);
834 }
835
836 let column_count = schema_info.schema.len();
837 for (_, row) in opt_map.iter_mut() {
838 for row in row.iter_mut() {
839 assert!(
840 column_count >= row.values.len(),
841 "column_count: {}, row.values.len(): {}",
842 column_count,
843 row.values.len()
844 );
845 row.values
846 .resize(column_count, GreptimeValue { value_data: None });
847 }
848 }
849
850 Ok((schema_info, opt_map))
851}
852
853pub fn identity_pipeline(
862 array: Vec<VrlValue>,
863 table: Option<Arc<table::Table>>,
864 pipeline_ctx: &PipelineContext<'_>,
865) -> Result<HashMap<ContextOpt, Rows>> {
866 let max_nested_levels = pipeline_ctx.pipeline_param.max_nested_levels();
867
868 let (mut schema, opt_map) = identity_pipeline_inner(array, pipeline_ctx, max_nested_levels)?;
869 if let Some(table) = table {
870 let table_info = table.table_info();
871 for tag_name in table_info.meta.row_key_column_names() {
872 if let Some(index) = schema.index.get(tag_name) {
873 schema.schema[*index].semantic_type = SemanticType::Tag;
874 }
875 }
876 }
877
878 let column_schemas = schema.column_schemas()?;
879 Ok(opt_map
880 .into_iter()
881 .map(|(opt, rows)| {
882 (
883 opt,
884 Rows {
885 schema: column_schemas.clone(),
886 rows,
887 },
888 )
889 })
890 .collect::<HashMap<ContextOpt, Rows>>())
891}
892
893pub fn flatten_object(object: VrlValue, max_nested_levels: usize) -> Result<VrlValue> {
899 let mut flattened = BTreeMap::new();
900 let object = object.into_object().context(ValueMustBeMapSnafu)?;
901
902 if !object.is_empty() {
903 do_flatten_object(&mut flattened, None, object, 1, max_nested_levels);
905 }
906
907 Ok(VrlValue::Object(flattened))
908}
909
910fn vrl_value_to_serde_json(value: &VrlValue) -> serde_json_crate::Value {
911 match value {
912 VrlValue::Null => serde_json_crate::Value::Null,
913 VrlValue::Boolean(b) => serde_json_crate::Value::Bool(*b),
914 VrlValue::Integer(i) => serde_json_crate::Value::Number((*i).into()),
915 VrlValue::Float(not_nan) => serde_json_crate::Number::from_f64(not_nan.into_inner())
916 .map(serde_json_crate::Value::Number)
917 .unwrap_or(serde_json_crate::Value::Null),
918 VrlValue::Bytes(bytes) => {
919 serde_json_crate::Value::String(String::from_utf8_lossy(bytes).into_owned())
920 }
921 VrlValue::Regex(re) => serde_json_crate::Value::String(re.as_str().to_string()),
922 VrlValue::Timestamp(ts) => serde_json_crate::Value::String(ts.to_rfc3339()),
923 VrlValue::Array(arr) => {
924 serde_json_crate::Value::Array(arr.iter().map(vrl_value_to_serde_json).collect())
925 }
926 VrlValue::Object(map) => serde_json_crate::Value::Object(
927 map.iter()
928 .map(|(k, v)| (k.to_string(), vrl_value_to_serde_json(v)))
929 .collect(),
930 ),
931 }
932}
933
934fn do_flatten_object(
935 dest: &mut BTreeMap<KeyString, VrlValue>,
936 base: Option<&str>,
937 object: BTreeMap<KeyString, VrlValue>,
938 current_level: usize,
939 max_nested_levels: usize,
940) {
941 for (key, value) in object {
942 let new_key = base.map_or_else(
943 || key.clone(),
944 |base_key| format!("{base_key}.{key}").into(),
945 );
946
947 match value {
948 VrlValue::Object(object) => {
949 if current_level >= max_nested_levels {
950 let json_string = serde_json_crate::to_string(&vrl_value_to_serde_json(
952 &VrlValue::Object(object),
953 ))
954 .unwrap_or_else(|_| String::from("{}"));
955 dest.insert(new_key, VrlValue::Bytes(Bytes::from(json_string)));
956 } else {
957 do_flatten_object(
958 dest,
959 Some(&new_key),
960 object,
961 current_level + 1,
962 max_nested_levels,
963 );
964 }
965 }
966 VrlValue::Array(_) => {
968 let json_string = serde_json_crate::to_string(&vrl_value_to_serde_json(&value))
969 .unwrap_or_else(|_| String::from("[]"));
970 dest.insert(new_key, VrlValue::Bytes(Bytes::from(json_string)));
971 }
972 _ => {
974 dest.insert(new_key, value);
975 }
976 }
977 }
978}
979
980#[cfg(test)]
981mod tests {
982 use api::v1::SemanticType;
983
984 use super::*;
985 use crate::{PipelineDefinition, identity_pipeline};
986
987 #[test]
988 fn test_identify_pipeline() {
989 let params = GreptimePipelineParams::default();
990 let pipeline_ctx = PipelineContext::new(
991 &PipelineDefinition::GreptimeIdentityPipeline(None),
992 ¶ms,
993 Channel::Unknown,
994 );
995 {
996 let array = [
997 serde_json::json!({
998 "woshinull": null,
999 "name": "Alice",
1000 "age": 20,
1001 "is_student": true,
1002 "score": 99.5,
1003 "hobbies": "reading",
1004 "address": "Beijing",
1005 }),
1006 serde_json::json!({
1007 "name": "Bob",
1008 "age": 21,
1009 "is_student": false,
1010 "score": "88.5",
1011 "hobbies": "swimming",
1012 "address": "Shanghai",
1013 "gaga": "gaga"
1014 }),
1015 ];
1016 let array = array.iter().map(|v| v.into()).collect();
1017 let rows = identity_pipeline(array, None, &pipeline_ctx);
1018 assert!(rows.is_err());
1019 assert_eq!(
1020 rows.err().unwrap().to_string(),
1021 "Column datatype mismatch. For column: score, expected datatype: Float64, actual datatype: String".to_string(),
1022 );
1023 }
1024 {
1025 let array = [
1026 serde_json::json!({
1027 "woshinull": null,
1028 "name": "Alice",
1029 "age": 20,
1030 "is_student": true,
1031 "score": 99.5,
1032 "hobbies": "reading",
1033 "address": "Beijing",
1034 }),
1035 serde_json::json!({
1036 "name": "Bob",
1037 "age": 21,
1038 "is_student": false,
1039 "score": 88,
1040 "hobbies": "swimming",
1041 "address": "Shanghai",
1042 "gaga": "gaga"
1043 }),
1044 ];
1045 let array = array.iter().map(|v| v.into()).collect();
1046 let rows = identity_pipeline(array, None, &pipeline_ctx);
1047 assert!(rows.is_err());
1048 assert_eq!(
1049 rows.err().unwrap().to_string(),
1050 "Column datatype mismatch. For column: score, expected datatype: Float64, actual datatype: Int64".to_string(),
1051 );
1052 }
1053 {
1054 let array = [
1055 serde_json::json!({
1056 "woshinull": null,
1057 "name": "Alice",
1058 "age": 20,
1059 "is_student": true,
1060 "score": 99.5,
1061 "hobbies": "reading",
1062 "address": "Beijing",
1063 }),
1064 serde_json::json!({
1065 "name": "Bob",
1066 "age": 21,
1067 "is_student": false,
1068 "score": 88.5,
1069 "hobbies": "swimming",
1070 "address": "Shanghai",
1071 "gaga": "gaga"
1072 }),
1073 ];
1074 let array = array.iter().map(|v| v.into()).collect();
1075 let rows = identity_pipeline(array, None, &pipeline_ctx);
1076 assert!(rows.is_ok());
1077 let mut rows = rows.unwrap();
1078 assert!(rows.len() == 1);
1079 let rows = rows.remove(&ContextOpt::default()).unwrap();
1080 assert_eq!(rows.schema.len(), 8);
1081 assert_eq!(rows.rows.len(), 2);
1082 assert_eq!(8, rows.rows[0].values.len());
1083 assert_eq!(8, rows.rows[1].values.len());
1084 }
1085 {
1086 let array = [
1087 serde_json::json!({
1088 "woshinull": null,
1089 "name": "Alice",
1090 "age": 20,
1091 "is_student": true,
1092 "score": 99.5,
1093 "hobbies": "reading",
1094 "address": "Beijing",
1095 }),
1096 serde_json::json!({
1097 "name": "Bob",
1098 "age": 21,
1099 "is_student": false,
1100 "score": 88.5,
1101 "hobbies": "swimming",
1102 "address": "Shanghai",
1103 "gaga": "gaga"
1104 }),
1105 ];
1106 let tag_column_names = ["name".to_string(), "address".to_string()];
1107
1108 let rows = identity_pipeline_inner(
1109 array.iter().map(|v| v.into()).collect(),
1110 &pipeline_ctx,
1111 pipeline_ctx.pipeline_param.max_nested_levels(),
1112 )
1113 .map(|(mut schema, mut rows)| {
1114 for name in tag_column_names {
1115 if let Some(index) = schema.index.get(&name) {
1116 schema.schema[*index].semantic_type = SemanticType::Tag;
1117 }
1118 }
1119
1120 assert!(rows.len() == 1);
1121 let rows = rows.remove(&ContextOpt::default()).unwrap();
1122
1123 Rows {
1124 schema: schema.column_schemas().unwrap(),
1125 rows,
1126 }
1127 });
1128
1129 assert!(rows.is_ok());
1130 let rows = rows.unwrap();
1131 assert_eq!(rows.schema.len(), 8);
1132 assert_eq!(rows.rows.len(), 2);
1133 assert_eq!(8, rows.rows[0].values.len());
1134 assert_eq!(8, rows.rows[1].values.len());
1135 assert_eq!(
1136 rows.schema
1137 .iter()
1138 .find(|x| x.column_name == "name")
1139 .unwrap()
1140 .semantic_type,
1141 SemanticType::Tag as i32
1142 );
1143 assert_eq!(
1144 rows.schema
1145 .iter()
1146 .find(|x| x.column_name == "address")
1147 .unwrap()
1148 .semantic_type,
1149 SemanticType::Tag as i32
1150 );
1151 assert_eq!(
1152 rows.schema
1153 .iter()
1154 .filter(|x| x.semantic_type == SemanticType::Tag as i32)
1155 .count(),
1156 2
1157 );
1158 }
1159 }
1160
1161 #[test]
1162 fn test_flatten() {
1163 let test_cases = vec![
1164 (
1166 serde_json::json!(
1167 {
1168 "a": {
1169 "b": {
1170 "c": [1, 2, 3]
1171 }
1172 },
1173 "d": [
1174 "foo",
1175 "bar"
1176 ],
1177 "e": {
1178 "f": [7, 8, 9],
1179 "g": {
1180 "h": 123,
1181 "i": "hello",
1182 "j": {
1183 "k": true
1184 }
1185 }
1186 }
1187 }
1188 ),
1189 10,
1190 Some(serde_json::json!(
1191 {
1192 "a.b.c": "[1,2,3]",
1193 "d": "[\"foo\",\"bar\"]",
1194 "e.f": "[7,8,9]",
1195 "e.g.h": 123,
1196 "e.g.i": "hello",
1197 "e.g.j.k": true
1198 }
1199 )),
1200 ),
1201 (
1203 serde_json::json!(
1204 {
1205 "a": {
1206 "b": {
1207 "c": {
1208 "d": [1, 2, 3]
1209 }
1210 }
1211 },
1212 "e": [
1213 "foo",
1214 "bar"
1215 ]
1216 }
1217 ),
1218 3,
1219 Some(serde_json::json!(
1220 {
1221 "a.b.c": "{\"d\":[1,2,3]}",
1222 "e": "[\"foo\",\"bar\"]"
1223 }
1224 )),
1225 ),
1226 ];
1227
1228 for (input, max_depth, expected) in test_cases {
1229 let input = input.into();
1230 let expected = expected.map(|e| e.into());
1231
1232 let flattened_object = flatten_object(input, max_depth).ok();
1233 assert_eq!(flattened_object, expected);
1234 }
1235 }
1236
1237 #[test]
1238 fn test_identity_pipeline_skip_error_flattens_valid_rows() {
1239 let params = GreptimePipelineParams::from_map(ahash::HashMap::from_iter([(
1240 "skip_error".to_string(),
1241 "true".to_string(),
1242 )]));
1243 let pipeline_def = PipelineDefinition::GreptimeIdentityPipeline(None);
1244 let pipeline_ctx = PipelineContext::new(&pipeline_def, ¶ms, Channel::Unknown);
1245 let array = vec![
1246 serde_json::json!({
1247 "service": "frontend",
1248 "nested": {
1249 "status": 200,
1250 "path": "/v1/ingest"
1251 },
1252 "labels": ["pipeline", "identity"]
1253 })
1254 .into(),
1255 VrlValue::Bytes("invalid_string".into()),
1256 serde_json::json!({
1257 "service": "frontend",
1258 "nested": {
1259 "status": 201,
1260 "path": "/v1/ingest"
1261 },
1262 "labels": ["pipeline", "identity"]
1263 })
1264 .into(),
1265 ];
1266
1267 let mut rows_by_opt = identity_pipeline(array, None, &pipeline_ctx).unwrap();
1268 let rows = rows_by_opt.remove(&ContextOpt::default()).unwrap();
1269
1270 assert_eq!(rows.rows.len(), 2);
1271 assert_eq!(rows.schema.len(), rows.rows[0].values.len());
1272 assert!(rows.schema.iter().any(|s| s.column_name == "nested.status"));
1273 assert!(rows.schema.iter().any(|s| s.column_name == "nested.path"));
1274 assert!(rows.schema.iter().any(|s| s.column_name == "labels"));
1275 }
1276
1277 use ahash::HashMap as AHashMap;
1278 #[test]
1279 fn test_values_to_rows_skip_error_handling() {
1280 let table_suffix_template: Option<crate::tablesuffix::TableSuffixTemplate> = None;
1281
1282 {
1284 let schema_info = &mut SchemaInfo::default();
1285 let input_array = vec![
1286 serde_json::json!({"name": "Alice", "age": 25}).into(),
1288 VrlValue::Bytes("invalid_string".into()),
1290 serde_json::json!({"name": "Bob", "age": 30}).into(),
1292 VrlValue::Integer(42),
1294 serde_json::json!({"name": "Charlie", "age": 35}).into(),
1296 ];
1297
1298 let params = GreptimePipelineParams::from_map(AHashMap::from_iter([(
1299 "skip_error".to_string(),
1300 "true".to_string(),
1301 )]));
1302
1303 let pipeline_ctx = PipelineContext::new(
1304 &PipelineDefinition::GreptimeIdentityPipeline(None),
1305 ¶ms,
1306 Channel::Unknown,
1307 );
1308
1309 let result = values_to_rows(
1310 schema_info,
1311 VrlValue::Array(input_array),
1312 &pipeline_ctx,
1313 None,
1314 true,
1315 table_suffix_template.as_ref(),
1316 );
1317
1318 assert!(result.is_ok());
1320 let rows_by_context = result.unwrap();
1321 let total_rows: usize = rows_by_context.values().map(|v| v.len()).sum();
1323 assert_eq!(total_rows, 3); }
1325
1326 {
1328 let schema_info = &mut SchemaInfo::default();
1329 let input_array = vec![
1330 serde_json::json!({"name": "Alice", "age": 25}).into(),
1331 VrlValue::Bytes("invalid_string".into()), ];
1333
1334 let params = GreptimePipelineParams::default(); let pipeline_ctx = PipelineContext::new(
1337 &PipelineDefinition::GreptimeIdentityPipeline(None),
1338 ¶ms,
1339 Channel::Unknown,
1340 );
1341
1342 let result = values_to_rows(
1343 schema_info,
1344 VrlValue::Array(input_array),
1345 &pipeline_ctx,
1346 None,
1347 true,
1348 table_suffix_template.as_ref(),
1349 );
1350
1351 assert!(result.is_err());
1353 let error_msg = result.unwrap_err().to_string();
1354 assert!(error_msg.contains("Array element at index 1 must be an object for one-to-many transformation, got string"));
1355 }
1356 }
1357
1358 #[test]
1360 fn test_values_to_rows_per_element_context_opt() {
1361 let table_suffix_template: Option<crate::tablesuffix::TableSuffixTemplate> = None;
1362 let schema_info = &mut SchemaInfo::default();
1363
1364 let input_array = vec![
1366 serde_json::json!({"name": "Alice", "greptime_ttl": "1h"}).into(),
1367 serde_json::json!({"name": "Bob", "greptime_ttl": "1h"}).into(),
1368 serde_json::json!({"name": "Charlie", "greptime_ttl": "24h"}).into(),
1369 ];
1370
1371 let params = GreptimePipelineParams::default();
1372 let pipeline_ctx = PipelineContext::new(
1373 &PipelineDefinition::GreptimeIdentityPipeline(None),
1374 ¶ms,
1375 Channel::Unknown,
1376 );
1377
1378 let result = values_to_rows(
1379 schema_info,
1380 VrlValue::Array(input_array),
1381 &pipeline_ctx,
1382 None,
1383 true,
1384 table_suffix_template.as_ref(),
1385 );
1386
1387 assert!(result.is_ok());
1388 let rows_by_context = result.unwrap();
1389
1390 assert_eq!(rows_by_context.len(), 2);
1392
1393 let total_rows: usize = rows_by_context.values().map(|v| v.len()).sum();
1395 assert_eq!(total_rows, 3);
1396
1397 let mut ttl_1h_count = 0;
1399 let mut ttl_24h_count = 0;
1400 for rows in rows_by_context.values() {
1401 if rows.len() == 2 {
1403 ttl_1h_count = rows.len();
1404 } else if rows.len() == 1 {
1405 ttl_24h_count = rows.len();
1406 }
1407 }
1408 assert_eq!(ttl_1h_count, 2); assert_eq!(ttl_24h_count, 1); }
1411}