Skip to main content

servers/prom_remote_write/
v2.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::hash_map::Entry;
16
17use ahash::{HashMap, HashMapExt, HashSet, HashSetExt};
18#[cfg(test)]
19use api::greptime_proto::io::prometheus::write::v2::BucketSpan;
20#[cfg(test)]
21use api::greptime_proto::io::prometheus::write::v2::histogram::{Count, ZeroCount};
22use api::greptime_proto::io::prometheus::write::v2::{
23    Exemplar, Histogram, Metadata, Sample, metadata,
24};
25#[cfg(test)]
26use api::greptime_proto::io::prometheus::write::v2::{Request, TimeSeries};
27#[cfg(test)]
28use api::v1::ColumnSchema;
29use api::v1::value::ValueData;
30use api::v1::{ColumnDataType, RowInsertRequest, Rows, SemanticType, Value};
31use bytes::{Buf, Bytes};
32use common_grpc::precision::Precision;
33#[cfg(test)]
34use common_query::native_histogram::*;
35use common_query::native_histogram::{
36    NATIVE_HISTOGRAM_FIELD, encode_native_histogram, native_histogram_column_schema,
37};
38use common_query::prelude::{greptime_native_histogram, greptime_timestamp, greptime_value};
39use pipeline::{ContextOpt, ContextReq};
40use prost::encoding::{
41    DecodeContext, WireType, decode_key, decode_varint, message, skip_field, uint32,
42};
43use prost::{DecodeError, Message};
44use snafu::{OptionExt, ResultExt, ensure};
45use table::requests::{
46    METADATA_QUALITY_DECLARED, SEMANTIC_METRIC_METADATA_QUALITY, SEMANTIC_METRIC_TYPE,
47    SEMANTIC_METRIC_UNIT,
48};
49
50use crate::error::{self, Result};
51use crate::prom_remote_write::row_builder::PromCtx;
52use crate::prom_remote_write::validation::validate_label_name;
53use crate::prom_remote_write::{REMOTE_WRITE_V2_VERSION, try_decompress};
54#[allow(deprecated)]
55use crate::prom_store::{
56    DATABASE_LABEL, DATABASE_LABEL_ALT, METRIC_NAME_LABEL, PHYSICAL_TABLE_LABEL,
57    PHYSICAL_TABLE_LABEL_ALT, SCHEMA_LABEL,
58};
59use crate::row_writer::{self, TableData};
60use crate::semantic::{
61    METRIC_TYPE_COUNTER, METRIC_TYPE_GAUGE, METRIC_TYPE_GAUGE_HISTOGRAM, METRIC_TYPE_HISTOGRAM,
62    METRIC_TYPE_INFO, METRIC_TYPE_STATESET, METRIC_TYPE_SUMMARY, SemanticIndexes,
63    openmetrics_unit_to_ucum,
64};
65
66type PromTags<'a> = Vec<(&'a str, String)>;
67type ResolvedSeriesLabels<'a> = (PromCtx, String, PromTags<'a>);
68const TIME_SERIES_LABELS_REFS_TAG: u32 = 1;
69const TIME_SERIES_SAMPLES_TAG: u32 = 2;
70const TIME_SERIES_HISTOGRAMS_TAG: u32 = 3;
71const TIME_SERIES_EXEMPLARS_TAG: u32 = 4;
72const TIME_SERIES_METADATA_TAG: u32 = 5;
73
74struct BorrowedRequest<'a> {
75    symbols: Vec<&'a str>,
76    timeseries: Vec<&'a [u8]>,
77}
78
79impl<'a> BorrowedRequest<'a> {
80    fn decode(mut buf: &'a [u8]) -> std::result::Result<Self, DecodeError> {
81        let mut symbols = Vec::new();
82        let mut timeseries = Vec::new();
83
84        while buf.has_remaining() {
85            let (tag, wire_type) = decode_key(&mut buf)?;
86            match tag {
87                4 => {
88                    let value =
89                        take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
90                            error.push("Request", "symbols");
91                            error
92                        })?;
93                    let symbol = std::str::from_utf8(value).map_err(|_| {
94                        let mut error =
95                            DecodeError::new("invalid string value: data is not UTF-8 encoded");
96                        error.push("Request", "symbols");
97                        error
98                    })?;
99                    symbols.push(symbol);
100                }
101                5 => {
102                    let series =
103                        take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
104                            error.push("Request", "timeseries");
105                            error
106                        })?;
107                    timeseries.push(series);
108                }
109                _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())?,
110            }
111        }
112
113        Ok(Self {
114            symbols,
115            timeseries,
116        })
117    }
118}
119
120pub(crate) struct RemoteWriteV2WriteRequests {
121    pub samples: ContextReq,
122    pub histograms: ContextReq,
123    pub sample_count: u64,
124    pub histogram_count: u64,
125    /// Per-table semantic metadata from the series' inline `Metadata`, folded
126    /// into table options at auto-create time.
127    pub semantic_index: SemanticIndexes,
128}
129
130pub(crate) fn decode_remote_write_v2(
131    is_zstd: bool,
132    body: Bytes,
133    native_histograms_enabled: bool,
134) -> Result<RemoteWriteV2WriteRequests> {
135    let decode_timer = crate::metrics::METRIC_HTTP_PROM_STORE_CODEC_ELAPSED
136        .with_label_values(&["decode", REMOTE_WRITE_V2_VERSION])
137        .start_timer();
138
139    // Match the v1 decoder's VictoriaMetrics fallback: some clients may send a
140    // mismatched content-encoding header, so try the other compression on failure.
141    let buf = if let Ok(buf) = try_decompress(is_zstd, &body[..]) {
142        buf
143    } else {
144        try_decompress(!is_zstd, &body[..])?
145    };
146    let request = BorrowedRequest::decode(&buf).context(error::DecodePromRemoteRequestSnafu)?;
147    drop(decode_timer);
148
149    let _convert_timer = crate::metrics::METRIC_HTTP_PROM_STORE_CODEC_ELAPSED
150        .with_label_values(&["convert", REMOTE_WRITE_V2_VERSION])
151        .start_timer();
152    convert_remote_write_v2(request, native_histograms_enabled)
153}
154
155fn convert_remote_write_v2(
156    request: BorrowedRequest<'_>,
157    native_histograms_enabled: bool,
158) -> Result<RemoteWriteV2WriteRequests> {
159    ensure!(
160        request.symbols.first().copied() == Some(""),
161        error::InvalidPromRemoteRequestSnafu {
162            msg: "remote write v2 symbols must start with an empty string".to_string(),
163        }
164    );
165
166    let mut sample_tables = HashMap::<PromCtx, HashMap<String, TableData>>::new();
167    let mut histogram_tables = HashMap::<PromCtx, HashMap<String, TableData>>::new();
168    let mut label_names = HashSet::new();
169    let mut sample_count_total = 0;
170    let mut histogram_count_total = 0;
171    let mut labels_refs = Vec::new();
172    let mut metadata = Metadata::default();
173    let mut scratch = LeafScratch::default();
174    let mut semantic_index = SemanticIndexes::default();
175
176    for series in request.timeseries {
177        let counts = scan_series(series, &mut labels_refs, &mut metadata)
178            .context(error::DecodePromRemoteRequestSnafu)?;
179
180        ensure!(
181            native_histograms_enabled || counts.histograms == 0,
182            error::InvalidPromRemoteRequestSnafu {
183                msg: "prometheus remote write v2 native histogram ingestion is experimental; set prom_store.experimental_enable_prometheus_native_histogram = true to enable it"
184                    .to_string(),
185            }
186        );
187
188        if counts.samples == 0 && counts.histograms == 0 {
189            decode_series_leaves(series, None, Vec::new(), 0, &mut scratch)?;
190            continue;
191        }
192
193        let (prom_ctx, table_name, tags) =
194            resolve_series_labels(&request.symbols, &labels_refs, &mut label_names)?;
195        ensure_no_internal_histogram_labels(&tags)?;
196        record_series_metadata(
197            &mut semantic_index,
198            &request.symbols,
199            &metadata,
200            &prom_ctx,
201            &table_name,
202        )?;
203        let has_other_value_type = if counts.samples > 0 {
204            counts.histograms > 0
205                || histogram_tables
206                    .get(&prom_ctx)
207                    .is_some_and(|tables| tables.contains_key(&table_name))
208        } else {
209            sample_tables
210                .get(&prom_ctx)
211                .is_some_and(|tables| tables.contains_key(&table_name))
212        };
213        ensure!(
214            !has_other_value_type,
215            error::InvalidPromRemoteRequestSnafu {
216                msg: format!(
217                    "remote write v2 metric `{table_name}` contains both samples and native histograms"
218                ),
219            }
220        );
221
222        let column_count =
223            tags.len()
224                .checked_add(2)
225                .with_context(|| error::InvalidPromRemoteRequestSnafu {
226                    msg: "remote write v2 series has too many labels".to_string(),
227                })?;
228        let (writer, row_count) = if counts.samples > 0 {
229            (
230                SeriesWriter::Samples(get_or_create_table_data(
231                    &mut sample_tables,
232                    prom_ctx,
233                    table_name,
234                    column_count,
235                    counts.samples,
236                )),
237                counts.samples,
238            )
239        } else {
240            (
241                SeriesWriter::Histograms(get_or_create_table_data(
242                    &mut histogram_tables,
243                    prom_ctx,
244                    table_name,
245                    column_count,
246                    counts.histograms,
247                )),
248                counts.histograms,
249            )
250        };
251
252        decode_series_leaves(series, Some(writer), tags, row_count, &mut scratch)?;
253        sample_count_total = checked_total(sample_count_total, counts.samples, "sample")?;
254        histogram_count_total =
255            checked_total(histogram_count_total, counts.histograms, "histogram")?;
256    }
257
258    Ok(RemoteWriteV2WriteRequests {
259        samples: into_context_req(sample_tables),
260        histograms: into_context_req(histogram_tables),
261        sample_count: sample_count_total,
262        histogram_count: histogram_count_total,
263        semantic_index,
264    })
265}
266
267/// Stamps the series' inline metadata for the written table's auto-create: an
268/// explicit metric type upgrades the table's metadata quality to `declared`,
269/// `UNSPECIFIED` series keep the request-level `inferred` stamp, and units are
270/// canonicalised from OpenMetrics words to UCUM. Type and unit stamp
271/// independently, as OpenMetrics defines them. Help text is not persisted.
272///
273/// Every non-zero symbol reference is validated up front, independent of what
274/// ends up persisted: the spec requires all references to point into the
275/// symbol table.
276fn record_series_metadata(
277    index: &mut SemanticIndexes,
278    symbols: &[&str],
279    series_metadata: &Metadata,
280    prom_ctx: &PromCtx,
281    table_name: &str,
282) -> Result<()> {
283    // Symbol 0 is the mandatory empty string: no help / no unit.
284    if series_metadata.help_ref != 0 {
285        symbol_ref(symbols, series_metadata.help_ref, "metadata help")?;
286    }
287    let unit = if series_metadata.unit_ref != 0 {
288        Some(symbol_ref(
289            symbols,
290            series_metadata.unit_ref,
291            "metadata unit",
292        )?)
293    } else {
294        None
295    };
296    let metric_type = metric_type_value(series_metadata.r#type);
297    let ucum = unit.and_then(|unit| openmetrics_unit_to_ucum(unit.trim()));
298    if metric_type.is_none() && ucum.is_none() {
299        return Ok(());
300    }
301
302    let index = index.index_for(prom_ctx.schema.as_deref());
303    if let Some(metric_type) = metric_type {
304        index.record_scalar(table_name, SEMANTIC_METRIC_TYPE, metric_type);
305        index.record_scalar(
306            table_name,
307            SEMANTIC_METRIC_METADATA_QUALITY,
308            METADATA_QUALITY_DECLARED,
309        );
310    }
311    if let Some(ucum) = ucum {
312        index.record_scalar(table_name, SEMANTIC_METRIC_UNIT, ucum);
313    }
314    Ok(())
315}
316
317/// The `greptime.semantic.metric.type` value for a wire metric type; `None`
318/// for `UNSPECIFIED` (nothing was declared) and out-of-range values.
319fn metric_type_value(wire_type: i32) -> Option<&'static str> {
320    match metadata::MetricType::try_from(wire_type).ok()? {
321        metadata::MetricType::Unspecified => None,
322        metadata::MetricType::Counter => Some(METRIC_TYPE_COUNTER),
323        metadata::MetricType::Gauge => Some(METRIC_TYPE_GAUGE),
324        metadata::MetricType::Histogram => Some(METRIC_TYPE_HISTOGRAM),
325        metadata::MetricType::Gaugehistogram => Some(METRIC_TYPE_GAUGE_HISTOGRAM),
326        metadata::MetricType::Summary => Some(METRIC_TYPE_SUMMARY),
327        metadata::MetricType::Info => Some(METRIC_TYPE_INFO),
328        metadata::MetricType::Stateset => Some(METRIC_TYPE_STATESET),
329    }
330}
331
332#[derive(Default)]
333struct SeriesCounts {
334    samples: usize,
335    histograms: usize,
336}
337
338fn scan_series(
339    mut buf: &[u8],
340    labels_refs: &mut Vec<u32>,
341    metadata: &mut Metadata,
342) -> std::result::Result<SeriesCounts, DecodeError> {
343    labels_refs.clear();
344    metadata.clear();
345    let mut counts = SeriesCounts::default();
346
347    while buf.has_remaining() {
348        let (tag, wire_type) = decode_key(&mut buf)?;
349        match tag {
350            TIME_SERIES_LABELS_REFS_TAG => {
351                uint32::merge_repeated(wire_type, labels_refs, &mut buf, DecodeContext::default())
352                    .map_err(|mut error| {
353                    error.push("TimeSeries", "labels_refs");
354                    error
355                })?
356            }
357            TIME_SERIES_SAMPLES_TAG => {
358                take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
359                    error.push("TimeSeries", "samples");
360                    error
361                })?;
362                counts.samples = counts.samples.checked_add(1).ok_or_else(|| {
363                    DecodeError::new("remote write v2 sample count overflows usize")
364                })?;
365            }
366            TIME_SERIES_HISTOGRAMS_TAG => {
367                take_length_delimited(wire_type, &mut buf).map_err(|mut error| {
368                    error.push("TimeSeries", "histograms");
369                    error
370                })?;
371                counts.histograms = counts.histograms.checked_add(1).ok_or_else(|| {
372                    DecodeError::new("remote write v2 histogram count overflows usize")
373                })?;
374            }
375            TIME_SERIES_EXEMPLARS_TAG => {
376                take_length_delimited(wire_type, &mut buf)?;
377            }
378            TIME_SERIES_METADATA_TAG => {
379                message::merge(wire_type, metadata, &mut buf, DecodeContext::default()).map_err(
380                    |mut error| {
381                        error.push("TimeSeries", "metadata");
382                        error
383                    },
384                )?
385            }
386            _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())?,
387        }
388    }
389
390    Ok(counts)
391}
392
393#[derive(Default)]
394struct LeafScratch {
395    sample: Sample,
396    histogram: Histogram,
397    exemplar: Exemplar,
398}
399
400enum SeriesWriter<'a> {
401    Samples(&'a mut TableData),
402    Histograms(&'a mut TableData),
403}
404
405fn decode_series_leaves(
406    mut buf: &[u8],
407    mut writer: Option<SeriesWriter<'_>>,
408    mut tags: PromTags<'_>,
409    mut rows_remaining: usize,
410    scratch: &mut LeafScratch,
411) -> Result<()> {
412    let mut sample_row_template = None;
413
414    while buf.has_remaining() {
415        let (tag, wire_type) = decode_key(&mut buf).context(error::DecodePromRemoteRequestSnafu)?;
416        match tag {
417            TIME_SERIES_LABELS_REFS_TAG => {
418                skip_field(wire_type, tag, &mut buf, DecodeContext::default())
419                    .context(error::DecodePromRemoteRequestSnafu)?
420            }
421            TIME_SERIES_SAMPLES_TAG => {
422                scratch.sample.clear();
423                message::merge(
424                    wire_type,
425                    &mut scratch.sample,
426                    &mut buf,
427                    DecodeContext::default(),
428                )
429                .map_err(|mut error| {
430                    error.push("TimeSeries", "samples");
431                    error
432                })
433                .context(error::DecodePromRemoteRequestSnafu)?;
434                rows_remaining = rows_remaining.checked_sub(1).with_context(|| {
435                    error::InvalidPromRemoteRequestSnafu {
436                        msg: "remote write v2 sample count changed between scans".to_string(),
437                    }
438                })?;
439                if let Some(SeriesWriter::Samples(table_data)) = &mut writer {
440                    if sample_row_template.is_none() {
441                        let timestamp_index = table_data.ensure_column_by_name(
442                            greptime_timestamp(),
443                            ColumnDataType::TimestampMillisecond,
444                            SemanticType::Timestamp,
445                        )?;
446                        let value_index = table_data.ensure_column_by_name(
447                            greptime_value(),
448                            ColumnDataType::Float64,
449                            SemanticType::Field,
450                        )?;
451                        let mut row = table_data.alloc_one_row();
452                        row_writer::write_tags(
453                            table_data,
454                            std::mem::take(&mut tags).into_iter(),
455                            &mut row,
456                        )?;
457                        sample_row_template = Some((row, timestamp_index, value_index));
458                    }
459
460                    if let Some((row, timestamp_index, value_index)) = sample_row_template.as_mut()
461                    {
462                        row[*timestamp_index].value_data = Some(
463                            ValueData::TimestampMillisecondValue(scratch.sample.timestamp),
464                        );
465                        row[*value_index].value_data =
466                            Some(ValueData::F64Value(scratch.sample.value));
467                        let row = if rows_remaining == 0 {
468                            std::mem::take(row)
469                        } else {
470                            row.clone()
471                        };
472                        table_data.add_row(row);
473                    }
474                }
475            }
476            TIME_SERIES_HISTOGRAMS_TAG => {
477                scratch.histogram.clear();
478                message::merge(
479                    wire_type,
480                    &mut scratch.histogram,
481                    &mut buf,
482                    DecodeContext::default(),
483                )
484                .map_err(|mut error| {
485                    error.push("TimeSeries", "histograms");
486                    error
487                })
488                .context(error::DecodePromRemoteRequestSnafu)?;
489                rows_remaining = rows_remaining.checked_sub(1).with_context(|| {
490                    error::InvalidPromRemoteRequestSnafu {
491                        msg: "remote write v2 histogram count changed between scans".to_string(),
492                    }
493                })?;
494                if let Some(SeriesWriter::Histograms(table_data)) = &mut writer {
495                    if rows_remaining == 0 {
496                        write_native_histogram(
497                            table_data,
498                            &scratch.histogram,
499                            std::mem::take(&mut tags).into_iter(),
500                        )?;
501                    } else {
502                        write_native_histogram(
503                            table_data,
504                            &scratch.histogram,
505                            tags.iter().cloned(),
506                        )?;
507                    }
508                }
509            }
510            TIME_SERIES_EXEMPLARS_TAG => {
511                scratch.exemplar.clear();
512                message::merge(
513                    wire_type,
514                    &mut scratch.exemplar,
515                    &mut buf,
516                    DecodeContext::default(),
517                )
518                .map_err(|mut error| {
519                    error.push("TimeSeries", "exemplars");
520                    error
521                })
522                .context(error::DecodePromRemoteRequestSnafu)?;
523            }
524            TIME_SERIES_METADATA_TAG => {
525                take_length_delimited(wire_type, &mut buf)
526                    .map_err(|mut error| {
527                        error.push("TimeSeries", "metadata");
528                        error
529                    })
530                    .context(error::DecodePromRemoteRequestSnafu)?;
531            }
532            _ => skip_field(wire_type, tag, &mut buf, DecodeContext::default())
533                .context(error::DecodePromRemoteRequestSnafu)?,
534        }
535    }
536
537    ensure!(
538        rows_remaining == 0,
539        error::InvalidPromRemoteRequestSnafu {
540            msg: "remote write v2 row count changed between scans".to_string(),
541        }
542    );
543
544    Ok(())
545}
546
547fn take_length_delimited<'a>(
548    wire_type: WireType,
549    buf: &mut &'a [u8],
550) -> std::result::Result<&'a [u8], DecodeError> {
551    if wire_type != WireType::LengthDelimited {
552        return Err(DecodeError::new(format!(
553            "invalid wire type: {wire_type:?} (expected LengthDelimited)"
554        )));
555    }
556
557    let len = decode_varint(buf)?;
558    let len =
559        usize::try_from(len).map_err(|_| DecodeError::new("length delimiter exceeds usize"))?;
560    if len > buf.len() {
561        return Err(DecodeError::new("buffer underflow"));
562    }
563    let (value, remaining) = buf.split_at(len);
564    *buf = remaining;
565    Ok(value)
566}
567
568fn checked_total(total: u64, count: usize, name: &str) -> Result<u64> {
569    let count =
570        u64::try_from(count)
571            .ok()
572            .with_context(|| error::InvalidPromRemoteRequestSnafu {
573                msg: format!("remote write v2 {name} count exceeds u64"),
574            })?;
575    total
576        .checked_add(count)
577        .with_context(|| error::InvalidPromRemoteRequestSnafu {
578            msg: format!("remote write v2 {name} count overflows u64"),
579        })
580}
581
582fn get_or_create_table_data(
583    tables: &mut HashMap<PromCtx, HashMap<String, TableData>>,
584    prom_ctx: PromCtx,
585    table_name: String,
586    column_count: usize,
587    row_count: usize,
588) -> &mut TableData {
589    match tables.entry(prom_ctx).or_default().entry(table_name) {
590        Entry::Occupied(entry) => {
591            let table_data = entry.into_mut();
592            table_data.reserve_rows(row_count);
593            table_data
594        }
595        Entry::Vacant(entry) => entry.insert(TableData::new(column_count, row_count)),
596    }
597}
598
599fn write_native_histogram<'a>(
600    table_data: &mut TableData,
601    histogram: &Histogram,
602    tags: impl Iterator<Item = (&'a str, String)>,
603) -> Result<()> {
604    let value = encode_native_histogram(histogram).map_err(|error| {
605        error::InvalidPromRemoteRequestSnafu {
606            msg: format!("remote write v2 {error}"),
607        }
608        .build()
609    })?;
610    let column_schema = native_histogram_column_schema().map_err(|error| {
611        error::InvalidPromRemoteRequestSnafu {
612            msg: format!("remote write v2 {error}"),
613        }
614        .build()
615    })?;
616
617    // Persist both int and float families into the logical table schema. Only one
618    // family is populated per row; the other is written as NULL so PromQL can
619    // infer the original histogram flavor without a separate type column.
620    let mut row = table_data.alloc_one_row();
621    row_writer::write_ts_to_millis(
622        table_data,
623        greptime_timestamp(),
624        Some(histogram.timestamp),
625        Precision::Millisecond,
626        &mut row,
627    )?;
628    row_writer::write_by_schema(
629        table_data,
630        std::iter::once((column_schema, Some(value))),
631        &mut row,
632    )?;
633
634    row_writer::write_tags(table_data, tags, &mut row)?;
635    table_data.add_row(row);
636
637    Ok(())
638}
639
640fn ensure_no_internal_histogram_labels(tags: &PromTags<'_>) -> Result<()> {
641    // The histogram field column is generated from the protobuf payload.
642    for (name, _) in tags {
643        ensure!(
644            *name != greptime_native_histogram() && *name != NATIVE_HISTOGRAM_FIELD,
645            error::InvalidPromRemoteRequestSnafu {
646                msg: format!(
647                    "remote write v2 label `{name}` conflicts with an internal native histogram label"
648                ),
649            }
650        );
651    }
652
653    Ok(())
654}
655
656fn resolve_series_labels<'a>(
657    symbols: &'a [&str],
658    labels_refs: &[u32],
659    label_names: &mut HashSet<&'a str>,
660) -> Result<ResolvedSeriesLabels<'a>> {
661    ensure!(
662        labels_refs.len().is_multiple_of(2),
663        error::InvalidPromRemoteRequestSnafu {
664            msg: "remote write v2 labels_refs must contain name/value pairs".to_string(),
665        }
666    );
667
668    let mut prom_ctx = PromCtx::default();
669    let mut table_name = None;
670    let mut tags = Vec::with_capacity(labels_refs.len() / 2);
671    label_names.clear();
672
673    for pair in labels_refs.chunks_exact(2) {
674        let name = symbol_ref(symbols, pair[0], "label name")?;
675        let value = symbol_ref(symbols, pair[1], "label value")?;
676        validate_label(name)?;
677        ensure!(
678            label_names.insert(name),
679            error::InvalidPromRemoteRequestSnafu {
680                msg: format!("remote write v2 label name `{name}` is repeated"),
681            }
682        );
683
684        if name == METRIC_NAME_LABEL {
685            table_name = Some(value.to_string());
686            continue;
687        }
688        if apply_remote_write_special_label(name, value, &mut prom_ctx) {
689            continue;
690        }
691
692        tags.push((name, value.to_string()));
693    }
694
695    let table_name = table_name.with_context(|| error::InvalidPromRemoteRequestSnafu {
696        msg: "missing '__name__' label in time-series".to_string(),
697    })?;
698    ensure!(
699        !table_name.is_empty(),
700        error::InvalidPromRemoteRequestSnafu {
701            msg: "remote write v2 label `__name__` value must not be empty".to_string(),
702        }
703    );
704
705    Ok((prom_ctx, table_name, tags))
706}
707
708fn validate_label(name: &str) -> Result<()> {
709    ensure!(
710        validate_label_name(name.as_bytes()),
711        error::InvalidPromRemoteRequestSnafu {
712            msg: format!("remote write v2 invalid label name `{name}`"),
713        }
714    );
715
716    Ok(())
717}
718
719fn symbol_ref<'a>(symbols: &'a [&str], idx: u32, field: &str) -> Result<&'a str> {
720    let idx = usize::try_from(idx)
721        .ok()
722        .with_context(|| error::InvalidPromRemoteRequestSnafu {
723            msg: format!("remote write v2 {field} symbol reference exceeds usize"),
724        })?;
725    symbols
726        .get(idx)
727        .copied()
728        .with_context(|| error::InvalidPromRemoteRequestSnafu {
729            msg: format!(
730                "remote write v2 {field} symbol reference {idx} is out of range, symbols len: {}",
731                symbols.len()
732            ),
733        })
734}
735
736#[allow(deprecated)]
737fn apply_remote_write_special_label(name: &str, value: &str, prom_ctx: &mut PromCtx) -> bool {
738    match name {
739        SCHEMA_LABEL => {
740            prom_ctx.schema = Some(value.to_string());
741            true
742        }
743        DATABASE_LABEL | DATABASE_LABEL_ALT => {
744            if prom_ctx.schema.is_none() {
745                prom_ctx.schema = Some(value.to_string());
746            }
747            true
748        }
749        PHYSICAL_TABLE_LABEL | PHYSICAL_TABLE_LABEL_ALT => {
750            prom_ctx.physical_table = Some(value.to_string());
751            true
752        }
753        _ => false,
754    }
755}
756
757fn into_context_req(tables: HashMap<PromCtx, HashMap<String, TableData>>) -> ContextReq {
758    let mut ctx_req = ContextReq::default();
759    for (prom_ctx, tables) in tables {
760        let mut opt = ContextOpt::default();
761        if let Some(schema) = prom_ctx.schema {
762            opt.set_schema(schema);
763        }
764        if let Some(physical_table) = prom_ctx.physical_table {
765            opt.set_physical_table(physical_table);
766        }
767
768        ctx_req.add_rows(
769            opt,
770            tables.into_iter().map(|(table_name, table_data)| {
771                table_data_to_row_insert_request(table_name, table_data)
772            }),
773        );
774    }
775    ctx_req
776}
777
778fn table_data_to_row_insert_request(table_name: String, table_data: TableData) -> RowInsertRequest {
779    let num_columns = table_data.num_columns();
780    let (schema, mut rows) = table_data.into_schema_and_rows();
781    for row in &mut rows {
782        if num_columns > row.values.len() {
783            row.values.resize(num_columns, Value { value_data: None });
784        }
785    }
786
787    RowInsertRequest {
788        table_name,
789        rows: Some(Rows { schema, rows }),
790    }
791}
792
793#[cfg(any(test, feature = "testing"))]
794pub mod test_util {
795    use api::greptime_proto::io::prometheus::write::v2::{Histogram, Request, Sample, TimeSeries};
796    use api::v1::RowInsertRequest;
797    use bytes::Bytes;
798    use prost::Message;
799    use snafu::ResultExt;
800
801    use crate::error::{self, Result};
802    use crate::prom_remote_write::try_decompress;
803    use crate::prom_store::snappy_compress;
804
805    pub fn request_with_labels_and_samples(
806        labels: Vec<(&str, &str)>,
807        samples: Vec<Sample>,
808    ) -> Request {
809        request_with_labels(labels, samples, Vec::new())
810    }
811
812    pub fn request_with_labels_and_histograms(
813        labels: Vec<(&str, &str)>,
814        histograms: Vec<Histogram>,
815    ) -> Request {
816        request_with_labels(labels, Vec::new(), histograms)
817    }
818
819    pub fn decode_request(is_zstd: bool, body: Bytes) -> Result<Request> {
820        let buf = if let Ok(buf) = try_decompress(is_zstd, &body[..]) {
821            buf
822        } else {
823            try_decompress(!is_zstd, &body[..])?
824        };
825        Request::decode(&buf[..]).context(error::DecodePromRemoteRequestSnafu)
826    }
827
828    pub fn write_requests(
829        request: Request,
830    ) -> Result<(Vec<RowInsertRequest>, Vec<RowInsertRequest>, u64, u64)> {
831        let body = Bytes::from(snappy_compress(&request.encode_to_vec())?);
832        decode_write_requests(false, body, true)
833    }
834
835    pub fn decode_write_requests(
836        is_zstd: bool,
837        body: Bytes,
838        native_histograms_enabled: bool,
839    ) -> Result<(Vec<RowInsertRequest>, Vec<RowInsertRequest>, u64, u64)> {
840        let requests = super::decode_remote_write_v2(is_zstd, body, native_histograms_enabled)?;
841        Ok((
842            requests.samples.all_req().collect(),
843            requests.histograms.all_req().collect(),
844            requests.sample_count,
845            requests.histogram_count,
846        ))
847    }
848
849    pub fn decode_uncompressed_write_requests(
850        body: &[u8],
851        native_histograms_enabled: bool,
852    ) -> Result<(Vec<RowInsertRequest>, Vec<RowInsertRequest>, u64, u64)> {
853        let request =
854            super::BorrowedRequest::decode(body).context(error::DecodePromRemoteRequestSnafu)?;
855        let requests = super::convert_remote_write_v2(request, native_histograms_enabled)?;
856        Ok((
857            requests.samples.all_req().collect(),
858            requests.histograms.all_req().collect(),
859            requests.sample_count,
860            requests.histogram_count,
861        ))
862    }
863
864    pub fn histogram(timestamp: i64) -> Histogram {
865        Histogram {
866            timestamp,
867            ..Default::default()
868        }
869    }
870
871    fn request_with_labels(
872        labels: Vec<(&str, &str)>,
873        samples: Vec<Sample>,
874        histograms: Vec<Histogram>,
875    ) -> Request {
876        let mut symbols = vec!["".to_string()];
877        let mut labels_refs = Vec::with_capacity(labels.len() * 2);
878        for (name, value) in labels {
879            labels_refs.push(push_symbol(&mut symbols, name));
880            labels_refs.push(push_symbol(&mut symbols, value));
881        }
882
883        Request {
884            symbols,
885            timeseries: vec![TimeSeries {
886                labels_refs,
887                samples,
888                histograms,
889                exemplars: Vec::new(),
890                metadata: None,
891            }],
892        }
893    }
894
895    fn push_symbol(symbols: &mut Vec<String>, symbol: &str) -> u32 {
896        if let Some(idx) = symbols.iter().position(|s| s == symbol) {
897            return idx as u32;
898        }
899
900        let idx = symbols.len();
901        symbols.push(symbol.to_string());
902        idx as u32
903    }
904}
905
906#[cfg(test)]
907mod tests {
908    use std::sync::Arc;
909
910    use api::v1::value::ValueData;
911    use common_query::prelude::{greptime_timestamp, greptime_value, set_default_prefix};
912    use session::context::QueryContext;
913
914    use super::*;
915    use crate::error;
916    use crate::http::prom_store::PHYSICAL_TABLE_PARAM;
917    use crate::prom_store::{DATABASE_LABEL, PHYSICAL_TABLE_LABEL};
918
919    #[test]
920    fn test_decode_remote_write_v2_request() {
921        let request = Request {
922            symbols: vec![
923                "".to_string(),
924                "__name__".to_string(),
925                "http_requests_total".to_string(),
926            ],
927            timeseries: vec![TimeSeries {
928                labels_refs: vec![1, 2],
929                samples: vec![Sample {
930                    value: 42.0,
931                    timestamp: 1000,
932                    start_timestamp: 0,
933                }],
934                histograms: Vec::new(),
935                exemplars: Vec::new(),
936                metadata: Some(Metadata {
937                    r#type: metadata::MetricType::Counter as i32,
938                    help_ref: 0,
939                    unit_ref: 0,
940                }),
941            }],
942        };
943        let body =
944            Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
945
946        let decoded = test_util::decode_request(false, body.clone()).unwrap();
947
948        assert_eq!(decoded.symbols, request.symbols);
949        assert_eq!(decoded.timeseries.len(), 1);
950        assert_eq!(decoded.timeseries[0].labels_refs, vec![1, 2]);
951        assert_eq!(decoded.timeseries[0].samples.len(), 1);
952        assert_eq!(decoded.timeseries[0].samples[0].value, 42.0);
953        assert_eq!(decoded.timeseries[0].metadata.as_ref().unwrap().r#type, 1);
954        assert_eq!(
955            decode_remote_write_v2(true, body, true)
956                .unwrap()
957                .sample_count,
958            1
959        );
960    }
961
962    #[test]
963    fn test_fused_decoder_accepts_arbitrary_field_order_and_split_labels() {
964        let mut first_sample = Sample {
965            value: 42.0,
966            timestamp: 1000,
967            start_timestamp: 500,
968        }
969        .encode_to_vec();
970        first_sample.extend(varint_field(90, 1));
971        let second_sample = Sample {
972            value: 43.0,
973            timestamp: 2000,
974            start_timestamp: 1000,
975        }
976        .encode_to_vec();
977
978        let mut series = encoded_message_field(2, &first_sample);
979        series.extend(packed_u32_field(1, &[1]));
980        series.extend(varint_field(90, 1));
981        series.extend(encoded_message_field(2, &second_sample));
982        series.extend(varint_field(1, 2));
983
984        let mut wire = encoded_message_field(5, &series);
985        wire.extend(string_field(4, b""));
986        wire.extend(varint_field(90, 1));
987        wire.extend(string_field(4, METRIC_NAME_LABEL.as_bytes()));
988        wire.extend(encoded_message_field(5, &packed_u32_field(1, &[99])));
989        wire.extend(string_field(4, b"http_requests_total"));
990
991        let requests = decode_wire(&wire, true).unwrap();
992        assert_eq!(requests.sample_count, 2);
993        assert_eq!(requests.histogram_count, 0);
994        let rows = requests.samples.all_req().next().unwrap().rows.unwrap();
995        assert_eq!(rows.rows.len(), 2);
996        assert_eq!(
997            rows.schema
998                .iter()
999                .map(|column| column.column_name.as_str())
1000                .collect::<Vec<_>>(),
1001            vec![greptime_timestamp(), greptime_value()]
1002        );
1003        assert_eq!(
1004            rows.rows[0].values[1].value_data,
1005            Some(ValueData::F64Value(42.0))
1006        );
1007        assert_eq!(
1008            rows.rows[1].values[1].value_data,
1009            Some(ValueData::F64Value(43.0))
1010        );
1011    }
1012
1013    #[test]
1014    fn test_fused_decoder_accepts_histogram_before_labels_and_unknown_fields() {
1015        let mut histogram = Histogram {
1016            count: Some(Count::CountInt(0)),
1017            zero_count: Some(ZeroCount::ZeroCountInt(0)),
1018            timestamp: 2000,
1019            start_timestamp: 1000,
1020            ..Default::default()
1021        }
1022        .encode_to_vec();
1023        histogram.extend(varint_field(90, 1));
1024
1025        let mut series = encoded_message_field(3, &histogram);
1026        series.extend(packed_u32_field(1, &[1, 2]));
1027        let wire = request_wire(&["", METRIC_NAME_LABEL, "metric"], &[series]);
1028
1029        let requests = decode_wire(&wire, true).unwrap();
1030        assert_eq!(requests.histogram_count, 1);
1031        let rows = requests.histograms.all_req().next().unwrap().rows.unwrap();
1032        assert_eq!(
1033            histogram_field_value(&rows, 0, START_TIMESTAMP_FIELD),
1034            Some(ValueData::TimestampMillisecondValue(1000))
1035        );
1036    }
1037
1038    #[test]
1039    fn test_fused_decoder_rejects_malformed_wire() {
1040        let mut wrong_request_wire = varint_field(4, 0);
1041        let invalid_utf8 = string_field(4, &[0xff]);
1042
1043        let mut truncated_request = Vec::new();
1044        prost::encoding::encode_key(5, WireType::LengthDelimited, &mut truncated_request);
1045        prost::encoding::encode_varint(2, &mut truncated_request);
1046        truncated_request.push(0);
1047
1048        let mut oversized_request = Vec::new();
1049        prost::encoding::encode_key(4, WireType::LengthDelimited, &mut oversized_request);
1050        prost::encoding::encode_varint(u64::MAX, &mut oversized_request);
1051
1052        let malformed_varint = vec![0x80; 10];
1053
1054        let mut wrong_labels_wire = Vec::new();
1055        prost::encoding::encode_key(1, WireType::SixtyFourBit, &mut wrong_labels_wire);
1056        wrong_labels_wire.extend([0; 8]);
1057        wrong_labels_wire = request_wire(&[""], &[std::mem::take(&mut wrong_labels_wire)]);
1058
1059        let wrong_series_wire = request_wire(&[""], &[varint_field(2, 0)]);
1060
1061        let mut truncated_series = Vec::new();
1062        prost::encoding::encode_key(2, WireType::LengthDelimited, &mut truncated_series);
1063        prost::encoding::encode_varint(2, &mut truncated_series);
1064        truncated_series.push(0x08);
1065        let truncated_series = request_wire(&[""], &[truncated_series]);
1066
1067        let mut oversized_series = Vec::new();
1068        prost::encoding::encode_key(3, WireType::LengthDelimited, &mut oversized_series);
1069        prost::encoding::encode_varint(u64::MAX, &mut oversized_series);
1070        let oversized_series = request_wire(&[""], &[oversized_series]);
1071
1072        let invalid_sample = series_wire(&[1, 2], 2, &varint_field(1, 1));
1073        let invalid_sample = request_wire(&["", METRIC_NAME_LABEL, "metric"], &[invalid_sample]);
1074        let invalid_histogram = series_wire(&[1, 2], 3, &varint_field(3, 1));
1075        let invalid_histogram =
1076            request_wire(&["", METRIC_NAME_LABEL, "metric"], &[invalid_histogram]);
1077
1078        for (name, wire) in [
1079            (
1080                "wrong request wire type",
1081                std::mem::take(&mut wrong_request_wire),
1082            ),
1083            ("invalid utf8", invalid_utf8),
1084            ("truncated request", truncated_request),
1085            ("oversized request length", oversized_request),
1086            ("malformed varint", malformed_varint),
1087            ("wrong labels wire type", wrong_labels_wire),
1088            ("wrong series wire type", wrong_series_wire),
1089            ("truncated series", truncated_series),
1090            ("oversized series length", oversized_series),
1091            ("invalid sample", invalid_sample),
1092            ("invalid histogram", invalid_histogram),
1093        ] {
1094            let error = decode_wire_error(&wire, true, name);
1095            assert!(
1096                matches!(error, error::Error::DecodePromRemoteRequest { .. }),
1097                "{name}: {error}"
1098            );
1099        }
1100    }
1101
1102    #[test]
1103    fn test_fused_decoder_rejects_malformed_ignored_messages() {
1104        for tag in [4, 5] {
1105            let mut series = series_wire(&[1, 2], 2, &Sample::default().encode_to_vec());
1106            series.extend(encoded_message_field(tag, &[0x08]));
1107            let wire = request_wire(&["", METRIC_NAME_LABEL, "metric"], &[series]);
1108
1109            let error = decode_wire_error(&wire, true, "malformed ignored message");
1110            assert!(matches!(
1111                error,
1112                error::Error::DecodePromRemoteRequest { .. }
1113            ));
1114        }
1115    }
1116
1117    #[test]
1118    fn test_fused_decoder_ignores_exemplar_symbol_refs() {
1119        let request = Request {
1120            symbols: vec![
1121                String::new(),
1122                METRIC_NAME_LABEL.to_string(),
1123                "metric".to_string(),
1124            ],
1125            timeseries: vec![TimeSeries {
1126                labels_refs: vec![1, 2],
1127                samples: vec![Sample::default()],
1128                exemplars: vec![Exemplar {
1129                    labels_refs: vec![99],
1130                    ..Default::default()
1131                }],
1132                ..Default::default()
1133            }],
1134        };
1135
1136        assert_eq!(decode_test_request(request).unwrap().sample_count, 1);
1137    }
1138
1139    #[test]
1140    fn test_fused_decoder_preserves_empty_request_and_series_behavior() {
1141        let request = Request {
1142            symbols: vec![
1143                String::new(),
1144                METRIC_NAME_LABEL.to_string(),
1145                "metric".to_string(),
1146                "job".to_string(),
1147            ],
1148            timeseries: vec![
1149                TimeSeries {
1150                    labels_refs: vec![99, 99],
1151                    ..Default::default()
1152                },
1153                TimeSeries {
1154                    labels_refs: vec![1],
1155                    ..Default::default()
1156                },
1157                TimeSeries {
1158                    labels_refs: vec![3, 2, 3, 2],
1159                    ..Default::default()
1160                },
1161                TimeSeries::default(),
1162            ],
1163        };
1164
1165        let requests = decode_test_request(request).unwrap();
1166        assert_eq!(requests.sample_count, 0);
1167        assert_eq!(requests.histogram_count, 0);
1168
1169        let requests = decode_test_request(Request {
1170            symbols: vec![String::new()],
1171            timeseries: Vec::new(),
1172        })
1173        .unwrap();
1174        assert_eq!(requests.sample_count, 0);
1175        assert!(decode_wire(&[], true).is_err());
1176        assert!(decode_wire(&[0x0a, 0x00], true).is_err());
1177    }
1178
1179    #[test]
1180    fn test_fused_decoder_rejects_duplicate_resolved_label_names() {
1181        let request = Request {
1182            symbols: vec![
1183                String::new(),
1184                METRIC_NAME_LABEL.to_string(),
1185                "metric".to_string(),
1186                "job".to_string(),
1187                "job".to_string(),
1188                "api".to_string(),
1189                "worker".to_string(),
1190            ],
1191            timeseries: vec![TimeSeries {
1192                labels_refs: vec![1, 2, 3, 5, 4, 6],
1193                samples: vec![Sample::default()],
1194                ..Default::default()
1195            }],
1196        };
1197
1198        assert_invalid(
1199            "duplicate resolved labels",
1200            request,
1201            "label name `job` is repeated",
1202        );
1203    }
1204
1205    #[test]
1206    fn test_fused_decoder_pins_experimental_error_precedence() {
1207        let histogram = series_wire(&[1, 2], 3, &Histogram::default().encode_to_vec());
1208        let malformed_sample = series_wire(&[1, 2], 2, &[0x08]);
1209
1210        let wire = request_wire(
1211            &["", METRIC_NAME_LABEL, "metric"],
1212            &[histogram.clone(), malformed_sample.clone()],
1213        );
1214        let error = decode_wire_error(&wire, false, "histogram before malformed series");
1215        assert!(error.to_string().contains("ingestion is experimental"));
1216
1217        let wire = request_wire(
1218            &["", METRIC_NAME_LABEL, "metric"],
1219            &[malformed_sample, histogram.clone()],
1220        );
1221        let error = decode_wire_error(&wire, false, "malformed series before histogram");
1222        assert!(matches!(
1223            error,
1224            error::Error::DecodePromRemoteRequest { .. }
1225        ));
1226
1227        let missing_name = series_wire(&[3, 4], 2, &Sample::default().encode_to_vec());
1228        let wire = request_wire(
1229            &["", METRIC_NAME_LABEL, "metric", "job", "api"],
1230            &[missing_name, histogram],
1231        );
1232        let error = decode_wire_error(&wire, false, "conversion error before histogram");
1233        assert!(error.to_string().contains("missing '__name__'"));
1234    }
1235
1236    #[test]
1237    fn test_into_context_req_samples() {
1238        let ctx_req = decode_test_request(test_util::request_with_labels_and_samples(
1239            vec![
1240                (METRIC_NAME_LABEL, "http_requests_total"),
1241                ("job", "api"),
1242                ("instance", "localhost:9090"),
1243            ],
1244            vec![
1245                Sample {
1246                    value: 42.0,
1247                    timestamp: 1000,
1248                    start_timestamp: 0,
1249                },
1250                Sample {
1251                    value: 43.0,
1252                    timestamp: 2000,
1253                    start_timestamp: 0,
1254                },
1255            ],
1256        ))
1257        .unwrap();
1258
1259        assert_eq!(ctx_req.sample_count, 2);
1260        assert_eq!(ctx_req.histogram_count, 0);
1261        assert_eq!(ctx_req.histograms.all_req().count(), 0);
1262        let mut inserts = ctx_req.samples.all_req().collect::<Vec<_>>();
1263        assert_eq!(inserts.len(), 1);
1264
1265        let request = inserts.pop().unwrap();
1266        assert_eq!(request.table_name, "http_requests_total");
1267        let rows = request.rows.unwrap();
1268        assert_eq!(rows.rows.len(), 2);
1269        assert_eq!(
1270            rows.schema
1271                .iter()
1272                .map(|col| col.column_name.as_str())
1273                .collect::<Vec<_>>(),
1274            vec![greptime_timestamp(), greptime_value(), "job", "instance"]
1275        );
1276        assert_eq!(
1277            rows.rows[0].values[0].value_data,
1278            Some(ValueData::TimestampMillisecondValue(1000))
1279        );
1280        assert_eq!(
1281            rows.rows[0].values[1].value_data,
1282            Some(ValueData::F64Value(42.0))
1283        );
1284        assert_eq!(
1285            rows.rows[0].values[2].value_data,
1286            Some(ValueData::StringValue("api".to_string()))
1287        );
1288        assert_eq!(
1289            rows.rows[0].values[3].value_data,
1290            Some(ValueData::StringValue("localhost:9090".to_string()))
1291        );
1292        assert_eq!(
1293            rows.rows[1].values[0].value_data,
1294            Some(ValueData::TimestampMillisecondValue(2000))
1295        );
1296        assert_eq!(
1297            rows.rows[1].values[1].value_data,
1298            Some(ValueData::F64Value(43.0))
1299        );
1300        assert_eq!(
1301            rows.rows[1].values[2].value_data,
1302            Some(ValueData::StringValue("api".to_string()))
1303        );
1304        assert_eq!(
1305            rows.rows[1].values[3].value_data,
1306            Some(ValueData::StringValue("localhost:9090".to_string()))
1307        );
1308    }
1309
1310    #[test]
1311    fn test_into_context_req_special_labels() {
1312        let ctx_req = decode_test_request(test_util::request_with_labels_and_samples(
1313            vec![
1314                (METRIC_NAME_LABEL, "cpu_usage"),
1315                (DATABASE_LABEL, "tenant_a"),
1316                (PHYSICAL_TABLE_LABEL, "metrics_physical"),
1317                ("job", "api"),
1318            ],
1319            vec![Sample {
1320                value: 1.0,
1321                timestamp: 1000,
1322                start_timestamp: 0,
1323            }],
1324        ))
1325        .unwrap();
1326
1327        let mut iter = ctx_req
1328            .samples
1329            .as_req_iter(Arc::new(QueryContext::with("greptime", "public")));
1330        let (ctx, reqs) = iter.next().unwrap();
1331        assert!(iter.next().is_none());
1332
1333        assert_eq!(ctx.current_schema(), "tenant_a");
1334        assert_eq!(
1335            ctx.extension(PHYSICAL_TABLE_PARAM),
1336            Some("metrics_physical")
1337        );
1338        assert_eq!(reqs.inserts.len(), 1);
1339
1340        let rows = reqs.inserts[0].rows.as_ref().unwrap();
1341        assert_eq!(
1342            rows.schema
1343                .iter()
1344                .map(|col| col.column_name.as_str())
1345                .collect::<Vec<_>>(),
1346            vec![greptime_timestamp(), greptime_value(), "job"]
1347        );
1348    }
1349
1350    #[test]
1351    fn test_into_context_req_rejects_invalid_requests() {
1352        let mut cases = Vec::new();
1353
1354        cases.push((
1355            "missing metric name",
1356            request_with_sample(vec![("job", "api")]),
1357            "missing '__name__'",
1358        ));
1359
1360        let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
1361        request.timeseries[0].labels_refs.push(1);
1362        cases.push((
1363            "odd label refs",
1364            request,
1365            "labels_refs must contain name/value pairs",
1366        ));
1367
1368        let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
1369        request.timeseries[0].labels_refs[1] = 99;
1370        cases.push((
1371            "out of range symbol ref",
1372            request,
1373            "symbol reference 99 is out of range",
1374        ));
1375
1376        let mut request = request_with_sample(vec![(METRIC_NAME_LABEL, "metric")]);
1377        request.symbols[0] = "not-empty".to_string();
1378        cases.push((
1379            "non-empty first symbol",
1380            request,
1381            "symbols must start with an empty string",
1382        ));
1383
1384        cases.push((
1385            "repeated label name",
1386            request_with_sample(vec![
1387                (METRIC_NAME_LABEL, "metric"),
1388                ("job", "api"),
1389                ("job", "worker"),
1390            ]),
1391            "label name `job` is repeated",
1392        ));
1393
1394        cases.push((
1395            "empty label name",
1396            request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("", "api")]),
1397            "invalid label name",
1398        ));
1399
1400        cases.push((
1401            "invalid label name",
1402            request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("has-dash", "api")]),
1403            "invalid label name",
1404        ));
1405
1406        cases.push((
1407            "dotted label name",
1408            request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("service.name", "api")]),
1409            "invalid label name",
1410        ));
1411
1412        cases.push((
1413            "non-ascii label name",
1414            request_with_sample(vec![(METRIC_NAME_LABEL, "metric"), ("区域", "api")]),
1415            "invalid label name",
1416        ));
1417
1418        cases.push((
1419            "empty metric name",
1420            request_with_sample(vec![(METRIC_NAME_LABEL, "")]),
1421            "label `__name__` value must not be empty",
1422        ));
1423
1424        cases.push((
1425            "internal histogram label on samples",
1426            request_with_sample(vec![
1427                (METRIC_NAME_LABEL, "metric"),
1428                (greptime_native_histogram(), "user_value"),
1429            ]),
1430            "conflicts with an internal native histogram label",
1431        ));
1432
1433        cases.push((
1434            "int count with float zero count",
1435            request_with_histogram(Histogram {
1436                count: Some(Count::CountInt(1)),
1437                zero_count: Some(ZeroCount::ZeroCountFloat(0.5)),
1438                ..Default::default()
1439            }),
1440            "count and zero_count must use the same integer or float family",
1441        ));
1442
1443        cases.push((
1444            "float count with int zero count",
1445            request_with_histogram(Histogram {
1446                count: Some(Count::CountFloat(1.0)),
1447                zero_count: Some(ZeroCount::ZeroCountInt(1)),
1448                ..Default::default()
1449            }),
1450            "count and zero_count must use the same integer or float family",
1451        ));
1452
1453        cases.push((
1454            "reducible schema",
1455            request_with_histogram(Histogram {
1456                schema: 9,
1457                ..Default::default()
1458            }),
1459            "schema 9 must be reduced before ingestion",
1460        ));
1461
1462        cases.push((
1463            "unsupported schema",
1464            request_with_histogram(Histogram {
1465                schema: 53,
1466                ..Default::default()
1467            }),
1468            "schema 53 is unsupported",
1469        ));
1470
1471        cases.push((
1472            "standard schema with custom values",
1473            request_with_histogram(Histogram {
1474                schema: 1,
1475                custom_values: vec![1.0],
1476                ..Default::default()
1477            }),
1478            "standard native histogram must not use custom_values",
1479        ));
1480
1481        cases.push((
1482            "custom values with inf",
1483            request_with_histogram(Histogram {
1484                schema: CUSTOM_BUCKETS_SCHEMA,
1485                custom_values: vec![f64::INFINITY],
1486                ..Default::default()
1487            }),
1488            "custom_values must not contain +Inf or NaN",
1489        ));
1490
1491        cases.push((
1492            "custom values not sorted",
1493            request_with_histogram(Histogram {
1494                schema: CUSTOM_BUCKETS_SCHEMA,
1495                custom_values: vec![2.0, 1.0],
1496                ..Default::default()
1497            }),
1498            "custom_values must be sorted",
1499        ));
1500
1501        cases.push((
1502            "custom schema with zero bucket",
1503            request_with_histogram(Histogram {
1504                schema: CUSTOM_BUCKETS_SCHEMA,
1505                zero_count: Some(ZeroCount::ZeroCountInt(1)),
1506                ..Default::default()
1507            }),
1508            "custom native histogram must not use a zero bucket",
1509        ));
1510
1511        cases.push((
1512            "custom schema with negative buckets",
1513            request_with_histogram(Histogram {
1514                schema: CUSTOM_BUCKETS_SCHEMA,
1515                negative_spans: vec![BucketSpan {
1516                    offset: -1,
1517                    length: 1,
1518                }],
1519                negative_deltas: vec![1],
1520                ..Default::default()
1521            }),
1522            "custom native histogram must not use negative buckets",
1523        ));
1524
1525        cases.push((
1526            "span count mismatch",
1527            request_with_histogram(Histogram {
1528                positive_spans: vec![BucketSpan {
1529                    offset: 0,
1530                    length: 2,
1531                }],
1532                positive_deltas: vec![1],
1533                ..Default::default()
1534            }),
1535            "positive spans describe 2 buckets, found 1",
1536        ));
1537
1538        cases.push((
1539            "negative offset after first span",
1540            request_with_histogram(Histogram {
1541                count: Some(Count::CountInt(2)),
1542                positive_spans: vec![
1543                    BucketSpan {
1544                        offset: 0,
1545                        length: 1,
1546                    },
1547                    BucketSpan {
1548                        offset: -1,
1549                        length: 1,
1550                    },
1551                ],
1552                positive_deltas: vec![1, 0],
1553                ..Default::default()
1554            }),
1555            "positive span 2 has negative offset -1",
1556        ));
1557
1558        cases.push((
1559            "negative custom span offset",
1560            request_with_histogram(Histogram {
1561                count: Some(Count::CountInt(1)),
1562                schema: CUSTOM_BUCKETS_SCHEMA,
1563                custom_values: vec![1.0],
1564                positive_spans: vec![BucketSpan {
1565                    offset: -1,
1566                    length: 1,
1567                }],
1568                positive_deltas: vec![1],
1569                ..Default::default()
1570            }),
1571            "positive span 1 has negative offset -1",
1572        ));
1573
1574        cases.push((
1575            "integer bucket total mismatch",
1576            request_with_histogram(Histogram {
1577                count: Some(Count::CountInt(0)),
1578                positive_spans: vec![BucketSpan {
1579                    offset: 0,
1580                    length: 1,
1581                }],
1582                positive_deltas: vec![1],
1583                ..Default::default()
1584            }),
1585            "has 1 observations in buckets, count is 0",
1586        ));
1587
1588        cases.push((
1589            "negative float count",
1590            request_with_histogram(Histogram {
1591                count: Some(Count::CountFloat(-1.0)),
1592                ..Default::default()
1593            }),
1594            "float count must not be negative",
1595        ));
1596
1597        cases.push((
1598            "negative float zero count",
1599            request_with_histogram(Histogram {
1600                count: Some(Count::CountFloat(0.0)),
1601                zero_count: Some(ZeroCount::ZeroCountFloat(-1.0)),
1602                ..Default::default()
1603            }),
1604            "float zero_count must not be negative",
1605        ));
1606
1607        cases.push((
1608            "negative float bucket count",
1609            request_with_histogram(Histogram {
1610                count: Some(Count::CountFloat(0.0)),
1611                positive_spans: vec![BucketSpan {
1612                    offset: 0,
1613                    length: 1,
1614                }],
1615                positive_counts: vec![-1.0],
1616                ..Default::default()
1617            }),
1618            "positive bucket 1 count must not be negative",
1619        ));
1620
1621        cases.push((
1622            "custom span index out of range",
1623            request_with_histogram(Histogram {
1624                schema: CUSTOM_BUCKETS_SCHEMA,
1625                custom_values: vec![1.0],
1626                positive_spans: vec![BucketSpan {
1627                    offset: 2,
1628                    length: 1,
1629                }],
1630                positive_deltas: vec![1],
1631                ..Default::default()
1632            }),
1633            "positive bucket index 2 is out of range",
1634        ));
1635
1636        for (name, request, expected) in cases {
1637            assert_invalid(name, request, expected);
1638        }
1639    }
1640
1641    #[test]
1642    fn test_into_context_req_allows_nan_observations_outside_buckets() {
1643        decode_test_request(request_with_histogram(Histogram {
1644            count: Some(Count::CountInt(2)),
1645            sum: f64::NAN,
1646            positive_spans: vec![BucketSpan {
1647                offset: 0,
1648                length: 1,
1649            }],
1650            positive_deltas: vec![1],
1651            ..Default::default()
1652        }))
1653        .unwrap();
1654    }
1655
1656    #[test]
1657    fn test_into_context_req_allows_empty_label_values() {
1658        let ctx_req = decode_test_request(test_util::request_with_labels_and_samples(
1659            vec![(METRIC_NAME_LABEL, "metric"), ("job", "")],
1660            vec![Sample {
1661                value: 1.0,
1662                timestamp: 1000,
1663                start_timestamp: 0,
1664            }],
1665        ))
1666        .unwrap();
1667
1668        let rows = ctx_req.samples.all_req().next().unwrap().rows.unwrap();
1669        let job_idx = column_index(&rows.schema, "job");
1670        assert_eq!(
1671            rows.rows[0].values[job_idx].value_data,
1672            Some(ValueData::StringValue(String::new()))
1673        );
1674    }
1675
1676    #[test]
1677    fn test_into_context_req_rejects_same_metric_samples_and_histograms() {
1678        let mut request = test_util::request_with_labels_and_samples(
1679            vec![(METRIC_NAME_LABEL, "metric")],
1680            vec![Sample {
1681                value: 1.0,
1682                timestamp: 1000,
1683                start_timestamp: 0,
1684            }],
1685        );
1686        request.timeseries[0].histograms.push(Histogram::default());
1687
1688        assert_invalid(
1689            "same metric samples and histograms",
1690            request,
1691            "contains both samples and native histograms",
1692        );
1693
1694        let mut request = test_util::request_with_labels_and_samples(
1695            vec![(METRIC_NAME_LABEL, "metric")],
1696            vec![Sample {
1697                value: 1.0,
1698                timestamp: 1000,
1699                start_timestamp: 0,
1700            }],
1701        );
1702        request.timeseries.push(TimeSeries {
1703            labels_refs: request.timeseries[0].labels_refs.clone(),
1704            histograms: vec![Histogram::default()],
1705            ..Default::default()
1706        });
1707
1708        assert_invalid(
1709            "same metric samples and histograms across series",
1710            request,
1711            "contains both samples and native histograms",
1712        );
1713    }
1714
1715    #[test]
1716    fn test_into_context_req_rejects_metric_kind_conflict_across_label_sets() {
1717        let request = Request {
1718            symbols: vec![
1719                "".to_string(),
1720                METRIC_NAME_LABEL.to_string(),
1721                "metric".to_string(),
1722                "job".to_string(),
1723                "api".to_string(),
1724                "worker".to_string(),
1725            ],
1726            timeseries: vec![
1727                TimeSeries {
1728                    labels_refs: vec![1, 2, 3, 4],
1729                    samples: vec![Sample {
1730                        value: 1.0,
1731                        timestamp: 1000,
1732                        start_timestamp: 0,
1733                    }],
1734                    ..Default::default()
1735                },
1736                TimeSeries {
1737                    labels_refs: vec![1, 2, 3, 5],
1738                    histograms: vec![Histogram::default()],
1739                    ..Default::default()
1740                },
1741            ],
1742        };
1743
1744        assert_invalid(
1745            "same metric kind conflict across label sets",
1746            request,
1747            "contains both samples and native histograms",
1748        );
1749    }
1750
1751    #[test]
1752    fn test_into_context_req_validates_exponential_overflow_bucket_index() {
1753        for schema in [-4, 0, 8] {
1754            let max_index = exponential_overflow_bucket_index(schema).unwrap();
1755            for positive in [true, false] {
1756                let mut histogram = Histogram {
1757                    schema,
1758                    count: Some(Count::CountInt(1)),
1759                    ..Default::default()
1760                };
1761                if positive {
1762                    histogram.positive_spans = vec![BucketSpan {
1763                        offset: max_index,
1764                        length: 1,
1765                    }];
1766                    histogram.positive_deltas = vec![1];
1767                } else {
1768                    histogram.negative_spans = vec![BucketSpan {
1769                        offset: max_index,
1770                        length: 1,
1771                    }];
1772                    histogram.negative_deltas = vec![1];
1773                }
1774                decode_test_request(request_with_histogram(histogram.clone())).unwrap();
1775
1776                let beyond = max_index + 1;
1777                if positive {
1778                    histogram.positive_spans[0].offset = beyond;
1779                } else {
1780                    histogram.negative_spans[0].offset = beyond;
1781                }
1782                assert_invalid(
1783                    "exponential overflow bucket index",
1784                    request_with_histogram(histogram),
1785                    &format!("bucket index {beyond} is out of range"),
1786                );
1787            }
1788        }
1789    }
1790
1791    #[test]
1792    fn test_into_context_req_converts_histograms_and_ignores_exemplars() {
1793        let request = Request {
1794            symbols: vec![
1795                "".to_string(),
1796                METRIC_NAME_LABEL.to_string(),
1797                "sample_metric".to_string(),
1798                "histogram_metric".to_string(),
1799            ],
1800            timeseries: vec![
1801                TimeSeries {
1802                    labels_refs: vec![1, 2],
1803                    samples: vec![Sample {
1804                        value: 1.0,
1805                        timestamp: 1000,
1806                        start_timestamp: 0,
1807                    }],
1808                    ..Default::default()
1809                },
1810                TimeSeries {
1811                    labels_refs: vec![1, 3],
1812                    histograms: vec![Histogram::default()],
1813                    exemplars: vec![Exemplar::default()],
1814                    ..Default::default()
1815                },
1816            ],
1817        };
1818
1819        let ctx_req = decode_test_request(request).unwrap();
1820
1821        assert_eq!(ctx_req.sample_count, 1);
1822        assert_eq!(ctx_req.histogram_count, 1);
1823        assert_eq!(ctx_req.samples.all_req().count(), 1);
1824        assert_eq!(ctx_req.histograms.all_req().count(), 1);
1825    }
1826
1827    #[test]
1828    fn test_into_context_req_converts_histogram_only_series() {
1829        let mut request =
1830            test_util::request_with_labels_and_samples(vec![(METRIC_NAME_LABEL, "metric")], vec![]);
1831        request.timeseries[0].histograms.push(Histogram::default());
1832
1833        let ctx_req = decode_test_request(request).unwrap();
1834
1835        assert_eq!(ctx_req.sample_count, 0);
1836        assert_eq!(ctx_req.histogram_count, 1);
1837        assert_eq!(ctx_req.samples.all_req().count(), 0);
1838        let mut inserts = ctx_req.histograms.all_req().collect::<Vec<_>>();
1839        assert_eq!(inserts.len(), 1);
1840
1841        let request = inserts.pop().unwrap();
1842        assert_eq!(request.table_name, "metric");
1843        let rows = request.rows.unwrap();
1844        assert_eq!(rows.rows.len(), 1);
1845        assert_eq!(
1846            rows.schema
1847                .iter()
1848                .map(|col| col.column_name.as_str())
1849                .collect::<Vec<_>>(),
1850            vec![greptime_timestamp(), greptime_native_histogram()]
1851        );
1852        assert_eq!(
1853            rows.rows[0].values[0].value_data,
1854            Some(ValueData::TimestampMillisecondValue(0))
1855        );
1856        assert_eq!(
1857            histogram_field_value(&rows, 0, SCHEMA_FIELD),
1858            Some(ValueData::I32Value(0))
1859        );
1860        assert_eq!(
1861            histogram_field_value(&rows, 0, COUNT_I64_FIELD),
1862            Some(ValueData::I64Value(0))
1863        );
1864        assert_eq!(histogram_field_value(&rows, 0, COUNT_F64_FIELD), None);
1865    }
1866
1867    #[test]
1868    fn test_into_context_req_preserves_histogram_start_timestamp() {
1869        let ctx_req = decode_test_request(test_util::request_with_labels_and_histograms(
1870            vec![(METRIC_NAME_LABEL, "metric")],
1871            vec![Histogram {
1872                timestamp: 2000,
1873                start_timestamp: 1000,
1874                ..Default::default()
1875            }],
1876        ))
1877        .unwrap();
1878
1879        let mut inserts = ctx_req.histograms.all_req().collect::<Vec<_>>();
1880        let rows = inserts.pop().unwrap().rows.unwrap();
1881
1882        assert_eq!(
1883            histogram_field_value(&rows, 0, START_TIMESTAMP_FIELD),
1884            Some(ValueData::TimestampMillisecondValue(1000))
1885        );
1886    }
1887
1888    #[test]
1889    fn test_into_context_req_preserves_exponential_zero_threshold() {
1890        for zero_threshold in [-1.0, f64::NAN] {
1891            let ctx_req = decode_test_request(request_with_histogram(Histogram {
1892                zero_threshold,
1893                ..Default::default()
1894            }))
1895            .unwrap();
1896            let rows = ctx_req.histograms.all_req().next().unwrap().rows.unwrap();
1897            let Some(ValueData::F64Value(actual)) =
1898                histogram_field_value(&rows, 0, ZERO_THRESHOLD_FIELD)
1899            else {
1900                panic!("expected zero threshold");
1901            };
1902
1903            assert_eq!(zero_threshold.to_bits(), actual.to_bits());
1904        }
1905    }
1906
1907    #[test]
1908    fn test_into_context_req_rejects_internal_histogram_labels() {
1909        let mut request = test_util::request_with_labels_and_samples(
1910            vec![
1911                (METRIC_NAME_LABEL, "metric"),
1912                (greptime_native_histogram(), "user_value"),
1913            ],
1914            vec![],
1915        );
1916        request.timeseries[0].histograms.push(Histogram::default());
1917
1918        let err = match decode_test_request(request) {
1919            Ok(_) => panic!("expected invalid request error"),
1920            Err(err) => err,
1921        };
1922        assert_eq!(
1923            err.to_string(),
1924            "Invalid prometheus remote request, msg: remote write v2 label `greptime_native_histogram` conflicts with an internal native histogram label"
1925        );
1926    }
1927
1928    #[test]
1929    fn test_rejects_legacy_histogram_label_after_prefix_change() {
1930        set_default_prefix(Some("custom")).unwrap();
1931        assert_eq!(greptime_native_histogram(), "custom_native_histogram");
1932
1933        let err = ensure_no_internal_histogram_labels(&vec![(
1934            NATIVE_HISTOGRAM_FIELD,
1935            "user_value".to_string(),
1936        )])
1937        .unwrap_err();
1938        assert!(
1939            err.to_string()
1940                .contains("conflicts with an internal native histogram label")
1941        );
1942    }
1943
1944    #[test]
1945    fn test_into_context_req_converts_int_and_float_histograms_to_one_schema() {
1946        let float_histogram = Histogram {
1947            count: Some(api::greptime_proto::io::prometheus::write::v2::histogram::Count::CountFloat(6.0)),
1948            zero_count: Some(
1949                api::greptime_proto::io::prometheus::write::v2::histogram::ZeroCount::ZeroCountFloat(
1950                    0.5,
1951                ),
1952            ),
1953            positive_counts: vec![2.0, 3.5],
1954            positive_spans: vec![api::greptime_proto::io::prometheus::write::v2::BucketSpan {
1955                offset: 3,
1956                length: 2,
1957            }],
1958            timestamp: 2000,
1959            ..Default::default()
1960        };
1961        let request = Request {
1962            symbols: vec![
1963                "".to_string(),
1964                METRIC_NAME_LABEL.to_string(),
1965                "metric".to_string(),
1966            ],
1967            timeseries: vec![
1968                TimeSeries {
1969                    labels_refs: vec![1, 2],
1970                    histograms: vec![test_util::histogram(1000)],
1971                    ..Default::default()
1972                },
1973                TimeSeries {
1974                    labels_refs: vec![1, 2],
1975                    histograms: vec![float_histogram],
1976                    ..Default::default()
1977                },
1978            ],
1979        };
1980
1981        let ctx_req = decode_test_request(request).unwrap();
1982
1983        assert_eq!(ctx_req.histogram_count, 2);
1984        let mut inserts = ctx_req.histograms.all_req().collect::<Vec<_>>();
1985        assert_eq!(inserts.len(), 1);
1986        let rows = inserts.pop().unwrap().rows.unwrap();
1987        assert_eq!(rows.rows.len(), 2);
1988        assert_eq!(
1989            rows.schema
1990                .iter()
1991                .map(|col| col.column_name.as_str())
1992                .collect::<Vec<_>>(),
1993            vec![greptime_timestamp(), greptime_native_histogram()]
1994        );
1995
1996        assert_eq!(
1997            histogram_field_value(&rows, 0, COUNT_I64_FIELD),
1998            Some(ValueData::I64Value(0))
1999        );
2000        assert_eq!(histogram_field_value(&rows, 0, COUNT_F64_FIELD), None);
2001        assert!(matches!(
2002            histogram_field_value(&rows, 0, POSITIVE_BUCKETS_I64_FIELD),
2003            Some(ValueData::ListValue(_))
2004        ));
2005        assert!(is_empty_list(histogram_field_value(
2006            &rows,
2007            0,
2008            POSITIVE_BUCKETS_F64_FIELD
2009        )));
2010
2011        assert_eq!(histogram_field_value(&rows, 1, COUNT_I64_FIELD), None);
2012        assert_eq!(
2013            histogram_field_value(&rows, 1, COUNT_F64_FIELD),
2014            Some(ValueData::F64Value(6.0))
2015        );
2016        assert!(is_empty_list(histogram_field_value(
2017            &rows,
2018            1,
2019            POSITIVE_BUCKETS_I64_FIELD
2020        )));
2021        assert!(matches!(
2022            histogram_field_value(&rows, 1, POSITIVE_BUCKETS_F64_FIELD),
2023            Some(ValueData::ListValue(_))
2024        ));
2025    }
2026
2027    fn decode_wire(
2028        wire: &[u8],
2029        native_histograms_enabled: bool,
2030    ) -> Result<RemoteWriteV2WriteRequests> {
2031        let body = Bytes::from(crate::prom_store::snappy_compress(wire).unwrap());
2032        decode_remote_write_v2(false, body, native_histograms_enabled)
2033    }
2034
2035    fn decode_wire_error(wire: &[u8], native_histograms_enabled: bool, name: &str) -> error::Error {
2036        match decode_wire(wire, native_histograms_enabled) {
2037            Ok(_) => panic!("{name}: expected decoder error"),
2038            Err(error) => error,
2039        }
2040    }
2041
2042    fn request_wire(symbols: &[&str], series: &[Vec<u8>]) -> Vec<u8> {
2043        let mut wire = Vec::new();
2044        for symbol in symbols {
2045            wire.extend(string_field(4, symbol.as_bytes()));
2046        }
2047        for series in series {
2048            wire.extend(encoded_message_field(5, series));
2049        }
2050        wire
2051    }
2052
2053    fn series_wire(labels_refs: &[u32], leaf_tag: u32, leaf: &[u8]) -> Vec<u8> {
2054        let mut series = packed_u32_field(1, labels_refs);
2055        series.extend(encoded_message_field(leaf_tag, leaf));
2056        series
2057    }
2058
2059    fn string_field(tag: u32, value: &[u8]) -> Vec<u8> {
2060        encoded_message_field(tag, value)
2061    }
2062
2063    fn encoded_message_field(tag: u32, value: &[u8]) -> Vec<u8> {
2064        let mut field = Vec::new();
2065        prost::encoding::encode_key(tag, WireType::LengthDelimited, &mut field);
2066        prost::encoding::encode_varint(u64::try_from(value.len()).unwrap(), &mut field);
2067        field.extend_from_slice(value);
2068        field
2069    }
2070
2071    fn varint_field(tag: u32, value: u64) -> Vec<u8> {
2072        let mut field = Vec::new();
2073        prost::encoding::encode_key(tag, WireType::Varint, &mut field);
2074        prost::encoding::encode_varint(value, &mut field);
2075        field
2076    }
2077
2078    fn packed_u32_field(tag: u32, values: &[u32]) -> Vec<u8> {
2079        let mut packed = Vec::new();
2080        for value in values {
2081            prost::encoding::encode_varint(u64::from(*value), &mut packed);
2082        }
2083        encoded_message_field(tag, &packed)
2084    }
2085
2086    fn request_with_sample(labels: Vec<(&str, &str)>) -> Request {
2087        test_util::request_with_labels_and_samples(
2088            labels,
2089            vec![Sample {
2090                value: 1.0,
2091                timestamp: 1000,
2092                start_timestamp: 0,
2093            }],
2094        )
2095    }
2096
2097    fn request_with_histogram(histogram: Histogram) -> Request {
2098        test_util::request_with_labels_and_histograms(
2099            vec![(METRIC_NAME_LABEL, "metric")],
2100            vec![histogram],
2101        )
2102    }
2103
2104    fn decode_test_request(request: Request) -> Result<RemoteWriteV2WriteRequests> {
2105        decode_test_request_with_histograms(request, true)
2106    }
2107
2108    fn decode_test_request_with_histograms(
2109        request: Request,
2110        native_histograms_enabled: bool,
2111    ) -> Result<RemoteWriteV2WriteRequests> {
2112        let body =
2113            Bytes::from(crate::prom_store::snappy_compress(&request.encode_to_vec()).unwrap());
2114        decode_remote_write_v2(false, body, native_histograms_enabled)
2115    }
2116
2117    fn assert_invalid(name: &str, request: Request, expected: &str) {
2118        let err = match decode_test_request(request) {
2119            Ok(_) => panic!("{name}: expected invalid request error"),
2120            Err(err) => err,
2121        };
2122        assert!(
2123            matches!(err, error::Error::InvalidPromRemoteRequest { .. }),
2124            "{name}: expected invalid request error, got {err}"
2125        );
2126        assert!(
2127            err.to_string().contains(expected),
2128            "{name}: expected error containing {expected:?}, got {err}"
2129        );
2130    }
2131
2132    fn column_index(schema: &[ColumnSchema], column_name: &str) -> usize {
2133        schema
2134            .iter()
2135            .position(|column| column.column_name == column_name)
2136            .unwrap()
2137    }
2138
2139    fn histogram_field_value(rows: &Rows, row_idx: usize, field_name: &str) -> Option<ValueData> {
2140        let histogram_idx = column_index(&rows.schema, greptime_native_histogram());
2141        let Some(ValueData::StructValue(histogram)) =
2142            &rows.rows[row_idx].values[histogram_idx].value_data
2143        else {
2144            panic!("expected native histogram struct value");
2145        };
2146        let field_idx = NATIVE_HISTOGRAM_FIELD_NAMES
2147            .iter()
2148            .position(|name| *name == field_name)
2149            .unwrap();
2150        histogram.items[field_idx].value_data.clone()
2151    }
2152
2153    fn is_empty_list(value: Option<ValueData>) -> bool {
2154        matches!(value, Some(ValueData::ListValue(list)) if list.items.is_empty())
2155    }
2156
2157    fn push_test_symbol(symbols: &mut Vec<String>, symbol: &str) -> u32 {
2158        if let Some(idx) = symbols.iter().position(|s| s == symbol) {
2159            return idx as u32;
2160        }
2161        let idx = symbols.len();
2162        symbols.push(symbol.to_string());
2163        idx as u32
2164    }
2165
2166    /// One sample-carrying test series: `(labels, metric_type, unit)`.
2167    type MetadataSeries<'a> = (Vec<(&'a str, &'a str)>, i32, Option<&'a str>);
2168
2169    fn metadata_request(series: Vec<MetadataSeries<'_>>) -> Request {
2170        let mut symbols = vec!["".to_string()];
2171        let timeseries = series
2172            .into_iter()
2173            .map(|(labels, metric_type, unit)| {
2174                let mut labels_refs = Vec::with_capacity(labels.len() * 2);
2175                for (name, value) in labels {
2176                    labels_refs.push(push_test_symbol(&mut symbols, name));
2177                    labels_refs.push(push_test_symbol(&mut symbols, value));
2178                }
2179                let unit_ref = unit.map_or(0, |unit| push_test_symbol(&mut symbols, unit));
2180                TimeSeries {
2181                    labels_refs,
2182                    samples: vec![Sample {
2183                        value: 1.0,
2184                        timestamp: 1000,
2185                        start_timestamp: 0,
2186                    }],
2187                    histograms: Vec::new(),
2188                    exemplars: Vec::new(),
2189                    metadata: Some(Metadata {
2190                        r#type: metric_type,
2191                        help_ref: 0,
2192                        unit_ref,
2193                    }),
2194                }
2195            })
2196            .collect();
2197        Request {
2198            symbols,
2199            timeseries,
2200        }
2201    }
2202
2203    type DecodedIndex = std::collections::BTreeMap<
2204        String,
2205        std::collections::BTreeMap<String, std::collections::BTreeMap<String, String>>,
2206    >;
2207
2208    fn decoded_index(request: Request) -> DecodedIndex {
2209        let encoded = decode_test_request(request)
2210            .unwrap()
2211            .semantic_index
2212            .encode("public")
2213            .expect("non-empty semantic index");
2214        serde_json::from_str(&encoded).unwrap()
2215    }
2216
2217    #[test]
2218    fn test_metadata_stamps_semantic_index() {
2219        use table::requests::METADATA_QUALITY_DECLARED;
2220
2221        let index = decoded_index(metadata_request(vec![
2222            (
2223                vec![(METRIC_NAME_LABEL, "http_requests_total")],
2224                metadata::MetricType::Counter as i32,
2225                Some("seconds"),
2226            ),
2227            (
2228                vec![(METRIC_NAME_LABEL, "queue_depth")],
2229                metadata::MetricType::Gauge as i32,
2230                // Outside the OpenMetrics base set: dropped, not passed through.
2231                Some("requests"),
2232            ),
2233        ]));
2234
2235        let tables = &index["public"];
2236        let typed = &tables["http_requests_total"];
2237        assert_eq!(typed[SEMANTIC_METRIC_TYPE], "counter");
2238        assert_eq!(
2239            typed[SEMANTIC_METRIC_METADATA_QUALITY],
2240            METADATA_QUALITY_DECLARED
2241        );
2242        assert_eq!(typed[SEMANTIC_METRIC_UNIT], "s");
2243
2244        let unitless = &tables["queue_depth"];
2245        assert_eq!(unitless[SEMANTIC_METRIC_TYPE], "gauge");
2246        assert!(!unitless.contains_key(SEMANTIC_METRIC_UNIT));
2247    }
2248
2249    #[test]
2250    fn test_metadata_unspecified_stamps_unit_but_not_type() {
2251        let index = decoded_index(metadata_request(vec![(
2252            vec![(METRIC_NAME_LABEL, "untyped_total")],
2253            metadata::MetricType::Unspecified as i32,
2254            Some("seconds"),
2255        )]));
2256        let untyped = &index["public"]["untyped_total"];
2257        assert_eq!(untyped[SEMANTIC_METRIC_UNIT], "s");
2258        assert!(!untyped.contains_key(SEMANTIC_METRIC_TYPE));
2259        assert!(!untyped.contains_key(SEMANTIC_METRIC_METADATA_QUALITY));
2260
2261        let requests = decode_test_request(metadata_request(vec![(
2262            vec![(METRIC_NAME_LABEL, "untyped_unitless_total")],
2263            metadata::MetricType::Unspecified as i32,
2264            None,
2265        )]))
2266        .unwrap();
2267        assert!(requests.semantic_index.is_empty());
2268
2269        // No metadata at all behaves the same.
2270        let requests = decode_test_request(test_util::request_with_labels_and_samples(
2271            vec![(METRIC_NAME_LABEL, "bare_total")],
2272            vec![Sample {
2273                value: 1.0,
2274                timestamp: 1000,
2275                start_timestamp: 0,
2276            }],
2277        ))
2278        .unwrap();
2279        assert!(requests.semantic_index.is_empty());
2280    }
2281
2282    #[test]
2283    fn test_metadata_type_conflict_collapses_to_mixed() {
2284        let index = decoded_index(metadata_request(vec![
2285            (
2286                vec![(METRIC_NAME_LABEL, "flappy_metric"), ("job", "a")],
2287                metadata::MetricType::Counter as i32,
2288                None,
2289            ),
2290            (
2291                vec![(METRIC_NAME_LABEL, "flappy_metric"), ("job", "b")],
2292                metadata::MetricType::Gauge as i32,
2293                None,
2294            ),
2295        ]));
2296        assert_eq!(
2297            index["public"]["flappy_metric"][SEMANTIC_METRIC_TYPE],
2298            "mixed"
2299        );
2300    }
2301
2302    #[test]
2303    fn test_metadata_schema_overrides_stay_apart() {
2304        // The same metric name written into two schemas by one request must not
2305        // collapse each other's metadata.
2306        let index = decoded_index(metadata_request(vec![
2307            (
2308                vec![(METRIC_NAME_LABEL, "cpu_usage")],
2309                metadata::MetricType::Counter as i32,
2310                None,
2311            ),
2312            (
2313                vec![
2314                    (METRIC_NAME_LABEL, "cpu_usage"),
2315                    (DATABASE_LABEL, "tenant_b"),
2316                ],
2317                metadata::MetricType::Gauge as i32,
2318                None,
2319            ),
2320        ]));
2321        assert_eq!(
2322            index["public"]["cpu_usage"][SEMANTIC_METRIC_TYPE],
2323            "counter"
2324        );
2325        assert_eq!(
2326            index["tenant_b"]["cpu_usage"][SEMANTIC_METRIC_TYPE],
2327            "gauge"
2328        );
2329    }
2330
2331    #[test]
2332    fn test_metadata_out_of_range_refs_are_rejected() {
2333        let mut request = metadata_request(vec![(
2334            vec![(METRIC_NAME_LABEL, "broken_total")],
2335            metadata::MetricType::Counter as i32,
2336            None,
2337        )]);
2338        request.timeseries[0].metadata.as_mut().unwrap().unit_ref = 999;
2339        let err = decode_test_request(request).err().unwrap();
2340        assert!(err.to_string().contains("out of range"), "{err}");
2341
2342        // unit_ref must be validated even when the type is UNSPECIFIED
2343        // (which persists nothing).
2344        let mut request = metadata_request(vec![(
2345            vec![(METRIC_NAME_LABEL, "broken_total")],
2346            metadata::MetricType::Unspecified as i32,
2347            None,
2348        )]);
2349        request.timeseries[0].metadata.as_mut().unwrap().unit_ref = 999;
2350        let err = decode_test_request(request).err().unwrap();
2351        assert!(err.to_string().contains("out of range"), "{err}");
2352
2353        // help_ref is validated although help is never persisted.
2354        let mut request = metadata_request(vec![(
2355            vec![(METRIC_NAME_LABEL, "broken_total")],
2356            metadata::MetricType::Counter as i32,
2357            None,
2358        )]);
2359        request.timeseries[0].metadata.as_mut().unwrap().help_ref = 999;
2360        let err = decode_test_request(request).err().unwrap();
2361        assert!(err.to_string().contains("out of range"), "{err}");
2362    }
2363}