Skip to main content

pipeline/etl/transform/transformer/
greptime.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
15pub 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
61/// Row with potentially designated table suffix.
62pub type RowWithTableSuffix = (Row, Option<String>);
63
64/// fields not in the columns will be discarded
65/// to prevent automatic column creation in GreptimeDB
66#[derive(Debug, Clone)]
67pub struct GreptimeTransformer {
68    transforms: Transforms,
69    schema: Vec<ColumnSchema>,
70}
71
72/// Parameters that can be used to configure the greptime pipelines.
73#[derive(Debug, Default)]
74pub struct GreptimePipelineParams {
75    /// The original options for configuring the greptime pipelines.
76    /// This should not be used directly, instead, use the parsed shortcut option values.
77    options: HashMap<String, String>,
78
79    /// Whether to skip error when processing the pipeline.
80    pub skip_error: OnceCell<bool>,
81    /// Max nested levels when flattening JSON object. Defaults to
82    /// `DEFAULT_MAX_NESTED_LEVELS_FOR_JSON_FLATTENING` when not provided.
83    pub max_nested_levels: OnceCell<usize>,
84}
85
86impl GreptimePipelineParams {
87    /// Create a `GreptimePipelineParams` from params string which is from the http header with key `x-greptime-pipeline-params`
88    /// The params is in the format of `key1=value1&key2=value2`,for example:
89    /// x-greptime-pipeline-params: max_nested_levels=5
90    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    /// Whether to skip error when processing the pipeline.
125    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    /// Max nested levels for JSON flattening. If not provided or invalid,
132    /// falls back to `DEFAULT_MAX_NESTED_LEVELS_FOR_JSON_FLATTENING`.
133    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    /// Add a default timestamp column to the transforms
146    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    /// Generate the schema for the GreptimeTransformer
163    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        // empty check is done in the caller
175        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                    //Safety unwrap is fine here because we have checked the length of real_fields
198                    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                // compatible with v1, add a default timestamp column
212                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                // let keep us `get` here to be compatible with v1
237                match pipeline_map.get(column_name) {
238                    Some(v) => {
239                        let value_data = coerce_value(v, transform)?;
240                        // every transform fields has only one output field
241                        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                    // remove the column from the pipeline_map
263                    // so that the auto-transform can use the rest fields
264                    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/// This is used to record the current state schema information and a sequential cache of field names.
343/// As you traverse the user input JSON, this will change.
344/// It will record a superset of all user input schemas.
345#[derive(Default)]
346pub struct SchemaInfo {
347    /// schema info
348    pub schema: Vec<ColumnMetadata>,
349    /// index of the column name
350    pub index: HashMap<String, usize>,
351    /// The pipeline's corresponding table (if already created). Useful to retrieve column schemas.
352    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
481/// Converts VRL values to Greptime rows grouped by their ContextOpt.
482/// # Returns
483/// A HashMap where keys are `ContextOpt` and values are vectors of (row, table_suffix) pairs.
484/// Single object input produces one ContextOpt group with one row.
485/// Array input groups rows by their per-element ContextOpt values.
486///
487/// # Errors
488/// - `ArrayElementMustBeObject` if an array element is not an object
489pub(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        // Single object: extract ContextOpt and table_suffix
500        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        // Extract ContextOpt and table_suffix for this element
531        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
548/// `need_calc_ts` happens in two cases:
549/// 1. full greptime_identity
550/// 2. auto-transform without transformer
551///
552/// if transform is present in custom pipeline in v2 mode
553/// we dont need to calc ts again, nor do we need to check ts column name
554pub(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        // calculate timestamp value based on the channel
567        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    // skip ts column
574    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            // safe unwrap after type matched
618            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            // safe unwrap after type matched
630            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                // TODO(LFC): Default to JSON2 for auto-created tables.
697                .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    // set time index column schema first
795    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
853/// Identity pipeline for Greptime
854/// This pipeline will convert the input JSON array to Greptime Rows
855/// params table is used to set the semantic type of the row key column to Tag
856/// 1. The pipeline will add a default timestamp column to the schema
857/// 2. The pipeline not resolve NULL value
858/// 3. The pipeline assumes that the json format is fixed
859/// 4. The pipeline will return an error if the same column datatype is mismatched
860/// 5. The pipeline will analyze the schema of each json record and merge them to get the final schema.
861pub 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
893/// Consumes the JSON object and consumes it into a single-level object.
894///
895/// The `max_nested_levels` parameter is used to limit how deep to flatten nested JSON objects.
896/// When the maximum level is reached, the remaining nested structure is serialized to a JSON
897/// string and stored at the current flattened key.
898pub 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        // it will use recursion to flatten the object.
904        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                    // Reached the maximum level; stringify the remaining object.
951                    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            // Arrays are stringified to ensure no JSON column types in the result.
967            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            // Other leaf types are inserted as-is.
973            _ => {
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            &params,
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            // Basic case.
1165            (
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            // Test the case where the object has more than 3 nested levels.
1202            (
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, &params, 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        // Case 1: skip_error=true, mixed valid/invalid elements
1283        {
1284            let schema_info = &mut SchemaInfo::default();
1285            let input_array = vec![
1286                // Valid object
1287                serde_json::json!({"name": "Alice", "age": 25}).into(),
1288                // Invalid element (string)
1289                VrlValue::Bytes("invalid_string".into()),
1290                // Valid object
1291                serde_json::json!({"name": "Bob", "age": 30}).into(),
1292                // Invalid element (number)
1293                VrlValue::Integer(42),
1294                // Valid object
1295                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                &params,
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            // Should succeed and only process valid objects
1319            assert!(result.is_ok());
1320            let rows_by_context = result.unwrap();
1321            // Count total rows across all ContextOpt groups
1322            let total_rows: usize = rows_by_context.values().map(|v| v.len()).sum();
1323            assert_eq!(total_rows, 3); // Only 3 valid objects
1324        }
1325
1326        // Case 2: skip_error=false, invalid elements present
1327        {
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()), // This should cause error
1332            ];
1333
1334            let params = GreptimePipelineParams::default(); // skip_error = false
1335
1336            let pipeline_ctx = PipelineContext::new(
1337                &PipelineDefinition::GreptimeIdentityPipeline(None),
1338                &params,
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            // Should fail with ArrayElementMustBeObject error
1352            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 that values_to_rows correctly groups rows by per-element ContextOpt
1359    #[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        // Create array with elements having different TTL values (ContextOpt)
1365        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            &params,
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        // Should have 2 different ContextOpt groups (1h TTL and 24h TTL)
1391        assert_eq!(rows_by_context.len(), 2);
1392
1393        // Count rows per group
1394        let total_rows: usize = rows_by_context.values().map(|v| v.len()).sum();
1395        assert_eq!(total_rows, 3);
1396
1397        // Verify that rows are correctly grouped by TTL
1398        let mut ttl_1h_count = 0;
1399        let mut ttl_24h_count = 0;
1400        for rows in rows_by_context.values() {
1401            // ContextOpt doesn't expose ttl directly, but we can count by group size
1402            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); // Alice and Bob with 1h TTL
1409        assert_eq!(ttl_24h_count, 1); // Charlie with 24h TTL
1410    }
1411}