1use 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
46pub 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 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 for (opt, rows_with_suffix) in rows_by_context {
190 let rows_by_suffix = transformed_map.entry(opt).or_default();
191 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 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 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 for (dispatched_to, coll) in dispatched {
238 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 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}