Skip to main content

servers/
pipeline.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
15use std::collections::BTreeMap;
16use std::sync::Arc;
17
18use ahash::{HashMap, HashMapExt};
19use api::v1::helper::time_index_column_schema;
20use api::v1::{ColumnDataType, Row, RowInsertRequest, Rows, Value};
21use common_time::timestamp::TimeUnit;
22use pipeline::{
23    ContextOpt, ContextReq, DispatchedTo, GREPTIME_INTERNAL_IDENTITY_PIPELINE_NAME, Pipeline,
24    PipelineContext, PipelineDefinition, PipelineProcessOutput, SchemaInfo, TransformedOutput,
25    TransformerMode, identity_pipeline, unwrap_or_continue_if_err,
26};
27use session::context::{Channel, QueryContextRef};
28use snafu::ResultExt;
29use vrl::value::Value as VrlValue;
30
31use crate::error::{PipelineSnafu, Result};
32use crate::http::event::PipelineIngestRequest;
33use crate::metrics::{
34    METRIC_FAILURE_VALUE, METRIC_HTTP_LOGS_TRANSFORM_ELAPSED, METRIC_SUCCESS_VALUE,
35};
36use crate::query_handler::PipelineHandlerRef;
37
38macro_rules! push_to_map {
39    ($map:expr, $key:expr, $value:expr, $capacity:expr) => {
40        $map.entry($key)
41            .or_insert_with(|| Vec::with_capacity($capacity))
42            .push($value);
43    };
44}
45
46/// Never call this on `GreptimeIdentityPipeline` because it's a real pipeline
47pub async fn get_pipeline(
48    pipeline_def: &PipelineDefinition,
49    handler: &PipelineHandlerRef,
50    query_ctx: &QueryContextRef,
51) -> Result<Arc<Pipeline>> {
52    match pipeline_def {
53        PipelineDefinition::Resolved(pipeline) => Ok(pipeline.clone()),
54        PipelineDefinition::ByNameAndValue((name, version)) => {
55            handler
56                .get_pipeline(name, *version, query_ctx.clone())
57                .await
58        }
59        _ => {
60            unreachable!("Never call get_pipeline on identity.")
61        }
62    }
63}
64
65pub(crate) async fn run_pipeline(
66    handler: &PipelineHandlerRef,
67    pipeline_ctx: &PipelineContext<'_>,
68    pipeline_req: PipelineIngestRequest,
69    query_ctx: &QueryContextRef,
70    is_top_level: bool,
71) -> Result<ContextReq> {
72    if pipeline_ctx.pipeline_definition.is_identity() {
73        run_identity_pipeline(handler, pipeline_ctx, pipeline_req, query_ctx).await
74    } else {
75        run_custom_pipeline(handler, pipeline_ctx, pipeline_req, query_ctx, is_top_level).await
76    }
77}
78
79async fn run_identity_pipeline(
80    handler: &PipelineHandlerRef,
81    pipeline_ctx: &PipelineContext<'_>,
82    pipeline_req: PipelineIngestRequest,
83    query_ctx: &QueryContextRef,
84) -> Result<ContextReq> {
85    let PipelineIngestRequest {
86        table: table_name,
87        values: data_array,
88    } = pipeline_req;
89    let table = if pipeline_ctx.channel == Channel::Prometheus {
90        None
91    } else {
92        handler.get_table(&table_name, query_ctx).await?
93    };
94    identity_pipeline(data_array, table, pipeline_ctx)
95        .map(|opt_map| ContextReq::from_opt_map(opt_map, table_name))
96        .context(PipelineSnafu)
97}
98
99async fn run_custom_pipeline(
100    handler: &PipelineHandlerRef,
101    pipeline_ctx: &PipelineContext<'_>,
102    pipeline_req: PipelineIngestRequest,
103    query_ctx: &QueryContextRef,
104    is_top_level: bool,
105) -> Result<ContextReq> {
106    let skip_error = pipeline_ctx.pipeline_param.skip_error();
107    let db = query_ctx.get_db_string();
108    let pipeline = get_pipeline(pipeline_ctx.pipeline_definition, handler, query_ctx).await?;
109
110    let transform_timer = std::time::Instant::now();
111
112    let PipelineIngestRequest {
113        table: table_name,
114        values: pipeline_maps,
115    } = pipeline_req;
116    let arr_len = pipeline_maps.len();
117    let mut transformed_map: HashMap<ContextOpt, HashMap<String, Vec<Row>>> = HashMap::new();
118    let mut dispatched: BTreeMap<DispatchedTo, Vec<VrlValue>> = BTreeMap::new();
119
120    let mut schema_info = match pipeline.transformer() {
121        TransformerMode::GreptimeTransformer(greptime_transformer) => {
122            SchemaInfo::from_schema_list(greptime_transformer.schemas().clone())
123        }
124        TransformerMode::AutoTransform(ts_name, timeunit) => {
125            let timeunit = match timeunit {
126                TimeUnit::Second => ColumnDataType::TimestampSecond,
127                TimeUnit::Millisecond => ColumnDataType::TimestampMillisecond,
128                TimeUnit::Microsecond => ColumnDataType::TimestampMicrosecond,
129                TimeUnit::Nanosecond => ColumnDataType::TimestampNanosecond,
130            };
131
132            let mut schema_info = SchemaInfo::default();
133            schema_info
134                .schema
135                .push(time_index_column_schema(ts_name, timeunit).into());
136
137            schema_info
138        }
139    };
140
141    let table = handler.get_table(&table_name, query_ctx).await?;
142    schema_info.set_table(table);
143    let needs_json_settings = matches!(
144        pipeline.transformer(),
145        TransformerMode::GreptimeTransformer(transformer) if transformer.has_json_transform()
146    );
147
148    for pipeline_map in pipeline_maps {
149        let result = pipeline
150            .process_mut(pipeline_map)
151            .inspect_err(|_| {
152                METRIC_HTTP_LOGS_TRANSFORM_ELAPSED
153                    .with_label_values(&[db.as_str(), METRIC_FAILURE_VALUE])
154                    .observe(transform_timer.elapsed().as_secs_f64());
155            })
156            .context(PipelineSnafu);
157
158        match unwrap_or_continue_if_err!(result, skip_error) {
159            PipelineProcessOutput::Processed(value) => {
160                if needs_json_settings {
161                    // JSON2 coercion must use settings from the final routed table.
162                    let values = match &value {
163                        VrlValue::Array(values) => values.as_slice(),
164                        value => std::slice::from_ref(value),
165                    };
166                    for value in values.iter().filter(|value| value.is_object()) {
167                        let table_suffix = pipeline.resolve_table_suffix(value).unwrap_or_default();
168                        if !schema_info.has_table_for_suffix(&table_suffix) {
169                            let destination =
170                                table_suffix_to_table_name(&table_name, &table_suffix);
171                            let table = handler.get_table(&destination, query_ctx).await?;
172                            schema_info.set_table_for_suffix(table_suffix, table);
173                        }
174                    }
175                }
176
177                let result = pipeline
178                    .transform_mut(value, pipeline_ctx, &mut schema_info)
179                    .inspect_err(|_| {
180                        METRIC_HTTP_LOGS_TRANSFORM_ELAPSED
181                            .with_label_values(&[db.as_str(), METRIC_FAILURE_VALUE])
182                            .observe(transform_timer.elapsed().as_secs_f64());
183                    })
184                    .context(PipelineSnafu);
185                let TransformedOutput { rows_by_context } =
186                    unwrap_or_continue_if_err!(result, skip_error);
187
188                // Process each ContextOpt group separately
189                for (opt, rows_with_suffix) in rows_by_context {
190                    let rows_by_suffix = transformed_map.entry(opt).or_default();
191                    // Group rows by table suffix within each context.
192                    for (row, table_suffix) in rows_with_suffix {
193                        rows_by_suffix
194                            .entry(table_suffix.unwrap_or_default())
195                            .or_insert_with(|| Vec::with_capacity(arr_len))
196                            .push(row);
197                    }
198                }
199            }
200            PipelineProcessOutput::DispatchedTo(dispatched_to, val) => {
201                push_to_map!(dispatched, dispatched_to, val, arr_len);
202            }
203            PipelineProcessOutput::Filtered => {
204                continue;
205            }
206        }
207    }
208
209    let mut results = ContextReq::default();
210
211    // Process transformed outputs. Rows are grouped by context first, then table suffix.
212    let column_count = schema_info.schema.len();
213    let column_schemas = schema_info.column_schemas()?;
214    for (opt, rows_by_suffix) in transformed_map {
215        let row_requests = rows_by_suffix.into_iter().map(|(table_suffix, mut rows)| {
216            let table_name = table_suffix_to_table_name(&table_name, &table_suffix);
217
218            // Pad rows to match final schema size (schema may have evolved during processing)
219            for row in &mut rows {
220                row.values.resize(column_count, Value { value_data: None });
221            }
222
223            RowInsertRequest {
224                rows: Some(Rows {
225                    rows,
226                    schema: column_schemas.clone(),
227                }),
228                table_name,
229            }
230        });
231
232        results.add_rows(opt, row_requests);
233    }
234
235    // if current pipeline contains dispatcher and has several rules, we may
236    // already accumulated several dispatched rules and rows.
237    for (dispatched_to, coll) in dispatched {
238        // we generate the new table name according to `table_part` and
239        // current custom table name.
240        let table_name = dispatched_to.dispatched_to_table_name(&table_name);
241        let next_pipeline_name = dispatched_to
242            .pipeline
243            .as_deref()
244            .unwrap_or(GREPTIME_INTERNAL_IDENTITY_PIPELINE_NAME);
245
246        // run pipeline recursively.
247        let next_pipeline_def =
248            PipelineDefinition::from_name(next_pipeline_name, None, None).context(PipelineSnafu)?;
249        let next_pipeline_ctx = PipelineContext::new(
250            &next_pipeline_def,
251            pipeline_ctx.pipeline_param,
252            pipeline_ctx.channel,
253        );
254        let requests = Box::pin(run_pipeline(
255            handler,
256            &next_pipeline_ctx,
257            PipelineIngestRequest {
258                table: table_name,
259                values: coll,
260            },
261            query_ctx,
262            false,
263        ))
264        .await?;
265
266        results.merge(requests);
267    }
268
269    if is_top_level {
270        METRIC_HTTP_LOGS_TRANSFORM_ELAPSED
271            .with_label_values(&[db.as_str(), METRIC_SUCCESS_VALUE])
272            .observe(transform_timer.elapsed().as_secs_f64());
273    }
274
275    Ok(results)
276}
277
278#[inline]
279fn table_suffix_to_table_name(table_name: &str, table_suffix: &str) -> String {
280    if table_suffix.is_empty() {
281        table_name.to_string()
282    } else {
283        format!("{}{}", table_name, table_suffix)
284    }
285}