Skip to main content

servers/
prom_store.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
15//! prometheus protocol supportings
16//! handles prometheus remote_write, remote_read logic
17use std::cmp::Ordering;
18use std::collections::HashMap;
19use std::collections::hash_map::DefaultHasher;
20use std::hash::{Hash, Hasher};
21use std::sync::Arc;
22
23use api::prom_store::remote::label_matcher::Type as MatcherType;
24use api::prom_store::remote::{Label, LabelMatcher, Query, Sample, TimeSeries, WriteRequest};
25use api::v1::RowInsertRequests;
26use arrow::array::{
27    Array, ArrayRef, AsArray, DictionaryArray, LargeStringArray, StringArray, StringViewArray,
28};
29use arrow::compute::kernels::cast as casts;
30use arrow::datatypes::{
31    DataType, Float64Type, TimeUnit, TimestampMicrosecondType, TimestampMillisecondType,
32    TimestampNanosecondType, UInt32Type,
33};
34use common_grpc::precision::Precision;
35use common_memory_manager::MemoryGuard;
36use common_query::native_histogram;
37use common_query::prelude::{greptime_timestamp, greptime_value};
38use common_recordbatch::{RecordBatch, RecordBatches};
39use common_telemetry::{tracing, warn};
40use datafusion::dataframe::DataFrame;
41use datafusion::prelude::{Expr, ident, lit, regexp_match};
42use datafusion_common::ScalarValue;
43use datafusion_expr::LogicalPlan;
44use snafu::{OptionExt, ResultExt, ensure};
45use snap::raw::{Decoder, Encoder};
46
47use crate::error::{self, Result};
48use crate::prom_remote_write::REMOTE_WRITE_V1_VERSION;
49use crate::request_memory_limiter::ServerMemoryLimiter;
50use crate::request_memory_metrics::RequestMemoryMetrics;
51use crate::row_writer::{self, MultiTableData};
52
53pub const METRIC_NAME_LABEL: &str = "__name__";
54pub const METRIC_NAME_LABEL_BYTES: &[u8] = b"__name__";
55
56/// special label for selecting database name on remote write
57pub const DATABASE_LABEL: &str = "x_greptime_database";
58pub const DATABASE_LABEL_BYTES: &[u8] = b"x_greptime_database";
59pub const DATABASE_LABEL_ALT: &str = "__database__";
60pub const DATABASE_LABEL_ALT_BYTES: &[u8] = b"__database__";
61
62/// deprecated, use DATABASE_LABEL instead
63#[deprecated(note = "use DATABASE_LABEL instead")]
64pub const SCHEMA_LABEL: &str = "__schema__";
65#[deprecated(note = "use DATABASE_LABEL_BYTES instead")]
66pub const SCHEMA_LABEL_BYTES: &[u8] = b"__schema__";
67
68/// special label for selecting physical table name on remote write
69pub const PHYSICAL_TABLE_LABEL: &str = "x_greptime_physical_table";
70pub const PHYSICAL_TABLE_LABEL_BYTES: &[u8] = b"x_greptime_physical_table";
71pub const PHYSICAL_TABLE_LABEL_ALT: &str = "__physical_table__";
72pub const PHYSICAL_TABLE_LABEL_ALT_BYTES: &[u8] = b"__physical_table__";
73
74/// The same as `FIELD_COLUMN_MATCHER` in `promql` crate
75pub const FIELD_NAME_LABEL: &str = "__field__";
76
77/// Check if given label is a special label for remote write
78#[allow(deprecated)]
79pub fn is_remote_write_special_label(label: &str) -> bool {
80    label == DATABASE_LABEL
81        || label == DATABASE_LABEL_ALT
82        || label == PHYSICAL_TABLE_LABEL
83        || label == PHYSICAL_TABLE_LABEL_ALT
84        || label == SCHEMA_LABEL
85}
86
87#[allow(deprecated)]
88pub fn is_remote_read_special_label(label: &str) -> bool {
89    label == METRIC_NAME_LABEL
90        || label == DATABASE_LABEL
91        || label == DATABASE_LABEL_ALT
92        || label == SCHEMA_LABEL
93}
94
95/// Check if given label is a database selection label
96#[allow(deprecated)]
97pub fn is_database_selection_label(label: &str) -> bool {
98    label == DATABASE_LABEL || label == DATABASE_LABEL_ALT || label == SCHEMA_LABEL
99}
100
101/// Check if given label is a physical table selection label
102pub fn is_physical_table_selection_label(label: &str) -> bool {
103    label == PHYSICAL_TABLE_LABEL || label == PHYSICAL_TABLE_LABEL_ALT
104}
105
106/// Get table name from remote query
107pub fn table_name(q: &Query) -> Result<String> {
108    let mut matchers = q
109        .matchers
110        .iter()
111        .filter(|matcher| matcher.name == METRIC_NAME_LABEL);
112    let matcher = matchers
113        .next()
114        .context(error::InvalidPromRemoteRequestSnafu {
115            msg: "missing '__name__' label in timeseries",
116        })?;
117
118    if matcher.r#type != MatcherType::Eq as i32
119        || matcher.value.is_empty()
120        || matchers.next().is_some()
121    {
122        return Err(error::InvalidPromRemoteRequestSnafu {
123            msg: "expected exactly one non-empty equality matcher for '__name__'".to_string(),
124        }
125        .build());
126    }
127
128    Ok(matcher.value.clone())
129}
130
131/// Extract database selector from a remote read query.
132pub fn extract_schema_from_query(query: &Query) -> Option<String> {
133    query
134        .matchers
135        .iter()
136        .find(|matcher| {
137            is_database_selection_label(&matcher.name) && matcher.r#type == MatcherType::Eq as i32
138        })
139        .map(|matcher| matcher.value.clone())
140}
141
142/// Validate matcher syntax independently of whether the metric exists.
143pub fn validate_matchers(matchers: &[LabelMatcher]) -> Result<()> {
144    for matcher in matchers {
145        let matcher_type = MatcherType::try_from(matcher.r#type).map_err(|e| {
146            error::InvalidPromRemoteRequestSnafu {
147                msg: format!("invalid LabelMatcher type, decode error: {e}"),
148            }
149            .build()
150        })?;
151        if matches!(matcher_type, MatcherType::Re | MatcherType::Nre) {
152            // Like Prometheus, validate the raw regex first, then the anchored pattern.
153            // We use the same regex engine as query execution, whose syntax differs
154            // from Prometheus's: e.g. `(?x) a` is accepted here but rejected by Prometheus.
155            regex::Regex::new(&matcher.value)
156                .and_then(|_| regex::Regex::new(&anchored_label_regex(&matcher.value)))
157                .map_err(|e| {
158                    error::InvalidPromRemoteRequestSnafu {
159                        msg: format!("invalid regex for label '{}': {e}", matcher.name),
160                    }
161                    .build()
162                })?;
163        }
164    }
165    Ok(())
166}
167
168/// Create a DataFrame from a remote Query
169#[tracing::instrument(skip_all)]
170pub fn query_to_plan(
171    dataframe: DataFrame,
172    q: &Query,
173    timestamp_column_name: &str,
174) -> Result<LogicalPlan> {
175    let (start_timestamp_ms, end_timestamp_ms) = remote_read_time_range(q);
176
177    let label_matches = &q.matchers;
178
179    let mut conditions = Vec::with_capacity(label_matches.len() + 1);
180
181    conditions
182        .push(ident(timestamp_column_name).gt_eq(lit_timestamp_millisecond(start_timestamp_ms)));
183    conditions
184        .push(ident(timestamp_column_name).lt_eq(lit_timestamp_millisecond(end_timestamp_ms)));
185
186    for m in label_matches {
187        let name = &m.name;
188
189        if is_remote_read_special_label(name) {
190            continue;
191        }
192
193        let value = &m.value;
194        let m_type = MatcherType::try_from(m.r#type).map_err(|e| {
195            error::InvalidPromRemoteRequestSnafu {
196                msg: format!("invalid LabelMatcher type, decode error: {e}",),
197            }
198            .build()
199        })?;
200
201        // Prometheus treats an absent label as "".
202        let label = if dataframe.schema().has_column_with_unqualified_name(name) {
203            ident(name)
204        } else {
205            lit("")
206        };
207
208        // Like PromQL, include NULL when the matcher accepts the empty string.
209        // Keep EQ/NEQ on the original column for index recognition.
210        let condition = match m_type {
211            MatcherType::Eq => {
212                if value.is_empty() {
213                    label.clone().eq(lit(value)).or(label.is_null())
214                } else {
215                    label.eq(lit(value))
216                }
217            }
218            MatcherType::Neq => {
219                if value.is_empty() {
220                    label.not_eq(lit(value))
221                } else {
222                    label.clone().not_eq(lit(value)).or(label.is_null())
223                }
224            }
225            MatcherType::Re | MatcherType::Nre => {
226                let pattern = anchored_label_regex(value);
227                let matches_empty = regex::Regex::new(&pattern)
228                    .map_err(|e| {
229                        error::InvalidPromRemoteRequestSnafu {
230                            msg: format!("invalid regex for label '{name}': {e}"),
231                        }
232                        .build()
233                    })?
234                    .is_match("");
235                let matched = regexp_match(label.clone(), lit(pattern), None);
236                if m_type == MatcherType::Re {
237                    let condition = matched.is_not_null();
238                    if matches_empty {
239                        condition.or(label.is_null())
240                    } else {
241                        condition
242                    }
243                } else {
244                    let condition = matched.is_null();
245                    // A NULL input also produces NULL. Keep it only when the
246                    // negative matcher accepts the empty string.
247                    if matches_empty {
248                        condition.and(label.is_not_null())
249                    } else {
250                        condition
251                    }
252                }
253            }
254        };
255        conditions.push(condition);
256    }
257
258    // Safety: conditions MUST not be empty, reduce always return Some(expr).
259    let conditions = conditions.into_iter().reduce(Expr::and).unwrap();
260
261    let dataframe = dataframe
262        .filter(conditions)
263        .context(error::DataFrameSnafu)?;
264
265    Ok(dataframe.into_parts().1)
266}
267
268/// Uses selector time hints without expanding the client's remote-storage range.
269/// Nonpositive or reversed hints retain the outer range: proto3 scalar defaults
270/// cannot distinguish missing endpoints from explicit zero timestamps. Valid
271/// disjoint ranges produce contradictory time predicates and therefore no rows.
272fn remote_read_time_range(query: &Query) -> (i64, i64) {
273    let outer = (query.start_timestamp_ms, query.end_timestamp_ms);
274    let Some(hints) = &query.hints else {
275        return outer;
276    };
277    if hints.start_ms <= 0 || hints.end_ms <= 0 || hints.start_ms > hints.end_ms {
278        return outer;
279    }
280
281    (outer.0.max(hints.start_ms), outer.1.min(hints.end_ms))
282}
283
284/// Prometheus's NewFastRegexMatcher also wraps patterns in ^(?s:...)$:
285/// https://github.com/prometheus/prometheus/blob/main/model/labels/regexp.go
286fn anchored_label_regex(value: &str) -> String {
287    format!("^(?s:{value})$")
288}
289
290#[inline]
291fn new_label(name: String, value: String) -> Label {
292    Label { name, value }
293}
294
295fn lit_timestamp_millisecond(ts: i64) -> Expr {
296    Expr::Literal(ScalarValue::TimestampMillisecond(Some(ts), None), None)
297}
298
299/// Sort timeseries by labels, matching the former `BTreeMap` order.
300fn compare_timeseries_labels(left: &[Label], right: &[Label]) -> Ordering {
301    let ordering = left.len().cmp(&right.len());
302    if ordering != Ordering::Equal {
303        return ordering;
304    }
305
306    for (left, right) in left.iter().zip(right) {
307        let ordering = left.name.cmp(&right.name);
308        if ordering != Ordering::Equal {
309            return ordering;
310        }
311
312        let ordering = left.value.cmp(&right.value);
313        if ordering != Ordering::Equal {
314            return ordering;
315        }
316    }
317
318    Ordering::Equal
319}
320
321enum LabelValues<'a> {
322    Utf8(&'a StringArray),
323    LargeUtf8(&'a LargeStringArray),
324    Utf8View(&'a StringViewArray),
325    DictionaryUtf8 {
326        dictionary: &'a DictionaryArray<UInt32Type>,
327        values: &'a StringArray,
328    },
329    Other(Vec<Option<String>>),
330}
331
332impl LabelValues<'_> {
333    fn value(&self, row: usize) -> Option<&str> {
334        match self {
335            Self::Utf8(values) => values.is_valid(row).then(|| values.value(row)),
336            Self::LargeUtf8(values) => values.is_valid(row).then(|| values.value(row)),
337            Self::Utf8View(values) => values.is_valid(row).then(|| values.value(row)),
338            Self::DictionaryUtf8 { dictionary, values } => dictionary
339                .key(row)
340                .and_then(|key| values.is_valid(key).then(|| values.value(key))),
341            Self::Other(values) => values.get(row).and_then(Option::as_deref),
342        }
343    }
344}
345
346fn row_labels<'a>(
347    columns: &'a [LabelColumn<'a>],
348    row: usize,
349) -> impl Iterator<Item = (&'a str, &'a str)> {
350    columns
351        .iter()
352        .filter_map(move |column| column.values.value(row).map(|value| (column.name, value)))
353}
354
355struct LabelColumn<'a> {
356    name: &'a str,
357    values: LabelValues<'a>,
358}
359
360fn label_columns<'a>(
361    recordbatch: &'a RecordBatch,
362    timestamp_column_name: &str,
363    value_column_name: &str,
364) -> Result<Vec<LabelColumn<'a>>> {
365    recordbatch
366        .schema
367        .column_schemas()
368        .iter()
369        .enumerate()
370        .filter(|(_, column_schema)| {
371            column_schema.name != timestamp_column_name && column_schema.name != value_column_name
372        })
373        .map(|(index, column_schema)| {
374            let array = recordbatch.column(index);
375            let values = match array.data_type() {
376                arrow::datatypes::DataType::Utf8 => LabelValues::Utf8(array.as_string::<i32>()),
377                arrow::datatypes::DataType::LargeUtf8 => {
378                    LabelValues::LargeUtf8(array.as_string::<i64>())
379                }
380                arrow::datatypes::DataType::Utf8View => {
381                    LabelValues::Utf8View(array.as_string_view())
382                }
383                arrow::datatypes::DataType::Dictionary(key, value)
384                    if key.as_ref() == &arrow::datatypes::DataType::UInt32
385                        && value.as_ref() == &arrow::datatypes::DataType::Utf8 =>
386                {
387                    let dictionary = array.as_dictionary::<UInt32Type>();
388                    LabelValues::DictionaryUtf8 {
389                        dictionary,
390                        values: dictionary.values().as_string::<i32>(),
391                    }
392                }
393                _ => {
394                    let values = recordbatch.iter_column_as_string(index).collect::<Vec<_>>();
395                    ensure!(
396                        values.len() == recordbatch.num_rows(),
397                        error::InvalidPromRemoteReadQueryResultSnafu {
398                            msg: format!(
399                                "Cannot convert label column '{}' of datatype {:?} to string",
400                                column_schema.name,
401                                array.data_type()
402                            ),
403                        }
404                    );
405                    LabelValues::Other(values)
406                }
407            };
408            Ok(LabelColumn {
409                name: &column_schema.name,
410                values,
411            })
412        })
413        .collect()
414}
415
416fn hash_timeseries(columns: &[LabelColumn<'_>], row: usize) -> u64 {
417    let mut hasher = DefaultHasher::new();
418
419    for (name, value) in row_labels(columns, row) {
420        name.hash(&mut hasher);
421        value.hash(&mut hasher);
422    }
423
424    hasher.finish()
425}
426
427fn matches_timeseries(labels: &[Label], columns: &[LabelColumn<'_>], row: usize) -> bool {
428    let mut labels = labels.iter().skip(1);
429    for (name, value) in row_labels(columns, row) {
430        let Some(label) = labels.next() else {
431            return false;
432        };
433        if label.name != name || label.value != value {
434            return false;
435        }
436    }
437
438    labels.next().is_none()
439}
440
441fn new_timeseries(table: &str, columns: &[LabelColumn<'_>], row: usize) -> TimeSeries {
442    let mut labels = Vec::with_capacity(columns.len() + 1);
443    labels.push(new_label(METRIC_NAME_LABEL.to_string(), table.to_string()));
444
445    for (name, value) in row_labels(columns, row) {
446        labels.push(new_label(name.to_string(), value.to_string()));
447    }
448
449    TimeSeries {
450        labels,
451        ..Default::default()
452    }
453}
454
455pub fn recordbatches_to_timeseries(
456    table_name: &str,
457    timestamp_column_name: &str,
458    value_column_name: &str,
459    recordbatches: RecordBatches,
460) -> Result<Vec<TimeSeries>> {
461    let mut timeseries: Vec<TimeSeries> = Vec::new();
462    let mut timeseries_by_hash: HashMap<u64, Vec<usize>> = HashMap::new();
463
464    for recordbatch in recordbatches.take() {
465        append_recordbatch_to_timeseries(
466            table_name,
467            timestamp_column_name,
468            value_column_name,
469            recordbatch,
470            &mut timeseries,
471            &mut timeseries_by_hash,
472        )?;
473    }
474
475    for ts in &mut timeseries {
476        ts.samples.sort_unstable_by_key(|s| s.timestamp);
477        ts.histograms.sort_unstable_by_key(|h| h.timestamp);
478    }
479
480    timeseries
481        .sort_unstable_by(|left, right| compare_timeseries_labels(&left.labels, &right.labels));
482    Ok(timeseries)
483}
484
485fn append_recordbatch_to_timeseries(
486    table: &str,
487    timestamp_column_name: &str,
488    value_column_name: &str,
489    recordbatch: RecordBatch,
490    timeseries: &mut Vec<TimeSeries>,
491    timeseries_by_hash: &mut HashMap<u64, Vec<usize>>,
492) -> Result<()> {
493    let ts_column = recordbatch
494        .column_by_name(timestamp_column_name)
495        .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
496            msg: format!("missing timestamp column '{timestamp_column_name}' in query result"),
497        })?;
498    // The Prometheus remote read wire format carries millisecond timestamps,
499    // while the table's time index can use any time unit (e.g. a metric
500    // physical table created with TIMESTAMP(6)). Narrowing floors towards
501    // negative infinity, consistent with `Timestamp::convert_to` on the
502    // ingestion path; arrow's cast would truncate towards zero, so a
503    // pre-epoch sample (e.g. -1001us) would round to -1ms instead of -2ms
504    // and disagree with what ingesting the same instant into a millisecond
505    // table would have stored.
506    let ts_column: ArrayRef = match ts_column.data_type() {
507        DataType::Timestamp(TimeUnit::Millisecond, _) => ts_column.clone(),
508        // Second -> millisecond is a widening (exact) conversion.
509        DataType::Timestamp(TimeUnit::Second, _) => casts::cast(
510            ts_column,
511            &DataType::Timestamp(TimeUnit::Millisecond, None),
512        )
513        .map_err(|e| {
514            error::InvalidPromRemoteReadQueryResultSnafu {
515                msg: format!(
516                    "failed to cast timestamp column '{timestamp_column_name}' of datatype {:?} to millisecond: {e}",
517                    ts_column.data_type()
518                ),
519            }
520            .build()
521        })?,
522        DataType::Timestamp(TimeUnit::Microsecond, _) => Arc::new(
523            ts_column
524                .as_primitive::<TimestampMicrosecondType>()
525                .unary::<_, TimestampMillisecondType>(|v| v.div_euclid(1_000)),
526        ),
527        DataType::Timestamp(TimeUnit::Nanosecond, _) => Arc::new(
528            ts_column
529                .as_primitive::<TimestampNanosecondType>()
530                .unary::<_, TimestampMillisecondType>(|v| v.div_euclid(1_000_000)),
531        ),
532        _ => {
533            return error::InvalidPromRemoteReadQueryResultSnafu {
534                msg: format!(
535                    "Expect timestamp column of datatype Timestamp(Millisecond), actual {:?}",
536                    ts_column.data_type()
537                ),
538            }
539            .fail();
540        }
541    };
542    let ts_column = ts_column
543        .as_primitive_opt::<TimestampMillisecondType>()
544        .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
545            msg: format!(
546                "Expect timestamp column of datatype Timestamp(Millisecond), actual {:?}",
547                ts_column.data_type()
548            ),
549        })?;
550
551    let field_column = recordbatch
552        .column_by_name(value_column_name)
553        .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
554            msg: format!("missing value column '{value_column_name}' in query result"),
555        })?;
556    let scalar_column = field_column.as_primitive_opt::<Float64Type>();
557    let histogram_column = recordbatch
558        .schema
559        .column_schema_by_name(value_column_name)
560        .filter(|column| native_histogram::is_native_histogram_value_type(&column.data_type))
561        .and_then(|_| field_column.as_struct_opt());
562    ensure!(
563        scalar_column.is_some() || histogram_column.is_some(),
564        error::InvalidPromRemoteReadQueryResultSnafu {
565            msg: format!(
566                "Expect value column of datatype Float64 or canonical native histogram Struct, actual {:?}",
567                field_column.data_type()
568            ),
569        }
570    );
571
572    let columns = label_columns(&recordbatch, timestamp_column_name, value_column_name)?;
573    let mut previous_timeseries: Option<usize> = None;
574
575    for row in 0..recordbatch.num_rows() {
576        let timeseries_index = match previous_timeseries {
577            Some(index) if matches_timeseries(&timeseries[index].labels, &columns, row) => index,
578            _ => {
579                let hash = hash_timeseries(&columns, row);
580                let candidates = timeseries_by_hash.entry(hash).or_default();
581                match candidates
582                    .iter()
583                    .copied()
584                    .find(|index| matches_timeseries(&timeseries[*index].labels, &columns, row))
585                {
586                    Some(index) => index,
587                    None => {
588                        let index = timeseries.len();
589                        timeseries.push(new_timeseries(table, &columns, row));
590                        candidates.push(index);
591                        index
592                    }
593                }
594            }
595        };
596        previous_timeseries = Some(timeseries_index);
597
598        if ts_column.is_null(row) || field_column.is_null(row) {
599            continue;
600        }
601
602        let timestamp = ts_column.value(row);
603        if let Some(column) = scalar_column {
604            timeseries[timeseries_index].samples.push(Sample {
605                value: column.value(row),
606                timestamp,
607            });
608        } else if let Some(column) = histogram_column {
609            let histogram =
610                native_histogram::decode_native_histogram_for_remote_read(column, row, timestamp)
611                    .map_err(|error| {
612                    error::InvalidPromRemoteReadQueryResultSnafu {
613                        msg: error.to_string(),
614                    }
615                    .build()
616                })?;
617            if let Some(histogram) = histogram {
618                timeseries[timeseries_index].histograms.push(histogram);
619            }
620        }
621    }
622
623    Ok(())
624}
625
626pub fn to_grpc_row_insert_requests(request: &WriteRequest) -> Result<(RowInsertRequests, usize)> {
627    let _timer = crate::metrics::METRIC_HTTP_PROM_STORE_CODEC_ELAPSED
628        .with_label_values(&["convert", REMOTE_WRITE_V1_VERSION])
629        .start_timer();
630
631    let mut multi_table_data = MultiTableData::new();
632
633    for series in &request.timeseries {
634        let table_name = &series
635            .labels
636            .iter()
637            .find(|label| {
638                // The metric name is a special label
639                label.name == METRIC_NAME_LABEL
640            })
641            .context(error::InvalidPromRemoteRequestSnafu {
642                msg: "missing '__name__' label in time-series",
643            })?
644            .value;
645
646        // The metric name is a special label,
647        // num_columns = labels.len() - 1 + 1 (value) + 1 (timestamp)
648        let num_columns = series.labels.len() + 1;
649
650        let table_data = multi_table_data.get_or_default_table_data(
651            table_name,
652            num_columns,
653            series.samples.len(),
654        );
655
656        // labels
657        let kvs = series.labels.iter().filter_map(|label| {
658            if label.name == METRIC_NAME_LABEL {
659                None
660            } else {
661                Some((label.name.clone(), label.value.clone()))
662            }
663        });
664
665        if series.samples.len() == 1 {
666            let mut one_row = table_data.alloc_one_row();
667
668            row_writer::write_tags(table_data, kvs, &mut one_row)?;
669            // value
670            row_writer::write_f64(
671                table_data,
672                greptime_value(),
673                series.samples[0].value,
674                &mut one_row,
675            )?;
676            // timestamp
677            row_writer::write_ts_to_millis(
678                table_data,
679                greptime_timestamp(),
680                Some(series.samples[0].timestamp),
681                Precision::Millisecond,
682                &mut one_row,
683            )?;
684
685            table_data.add_row(one_row);
686        } else {
687            for Sample { value, timestamp } in &series.samples {
688                let mut one_row = table_data.alloc_one_row();
689
690                // labels
691                let kvs = kvs.clone();
692                row_writer::write_tags(table_data, kvs, &mut one_row)?;
693                // value
694                row_writer::write_f64(table_data, greptime_value(), *value, &mut one_row)?;
695                // timestamp
696                row_writer::write_ts_to_millis(
697                    table_data,
698                    greptime_timestamp(),
699                    Some(*timestamp),
700                    Precision::Millisecond,
701                    &mut one_row,
702                )?;
703
704                table_data.add_row(one_row);
705            }
706        }
707
708        if !series.histograms.is_empty() {
709            warn!("Native histograms are not supported yet, data ignored");
710        }
711    }
712
713    Ok(multi_table_data.into_row_insert_requests())
714}
715
716/// Hard cap on the decompressed size of a handler-decompressed request body
717/// (Prometheus remote read/write, Loki push).
718///
719/// The HTTP body limit only bounds the *encoded* bytes on the wire. Without
720/// this cap a tiny compressed payload (e.g. a five-byte raw-Snappy varint or a
721/// few KiB of zstd RLE blocks) could declare/expand to multiple GiB of heap
722/// before any protobuf validation runs, so the decompressed size must be
723/// bounded on its own.
724pub const MAX_DECOMPRESSED_REQUEST_SIZE: usize = 512 * 1024 * 1024;
725
726/// Size of the chunks a zstd body is decompressed and charged in.
727const ZSTD_DECOMPRESS_CHUNK: usize = 256 * 1024;
728
729/// Decompressed bytes together with the memory permits charged for them.
730/// The permits are released when the buffer is dropped, so the aggregate
731/// memory accounting covers the whole lifetime of the decompressed data.
732pub(crate) struct ChargedBuffer {
733    pub data: Vec<u8>,
734    guards: Vec<MemoryGuard<RequestMemoryMetrics>>,
735}
736
737impl std::ops::Deref for ChargedBuffer {
738    type Target = [u8];
739
740    fn deref(&self) -> &Self::Target {
741        &self.data
742    }
743}
744
745impl std::fmt::Debug for ChargedBuffer {
746    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
747        f.debug_struct("ChargedBuffer")
748            .field("data_len", &self.data.len())
749            .field("guards", &self.guards.len())
750            .finish()
751    }
752}
753
754impl ChargedBuffer {
755    pub(crate) fn reserved_bytes(&self) -> u64 {
756        self.guards.iter().map(MemoryGuard::granted_bytes).sum()
757    }
758
759    /// Creates a buffer with the permits charged for it.
760    pub(crate) fn new(data: Vec<u8>, guards: Vec<MemoryGuard<RequestMemoryMetrics>>) -> Self {
761        Self { data, guards }
762    }
763
764    /// Splits the buffer into the raw bytes and the memory permits charged for
765    /// them. Hold the permits as long as the bytes are kept.
766    pub(crate) fn into_parts(self) -> (Vec<u8>, Vec<MemoryGuard<RequestMemoryMetrics>>) {
767        (self.data, self.guards)
768    }
769}
770
771/// Decompresses a raw Snappy body after enforcing a hard cap on the decoded
772/// size and charging the aggregate request-memory limiter.
773///
774/// The raw Snappy format prefixes the stream with a varint of the decoded
775/// length, so the output size is known (and validated) before the output
776/// buffer is allocated by `decompress_vec`.
777pub(crate) async fn snappy_decompress_limited(
778    buf: &[u8],
779    max_decompressed: usize,
780    limiter: &ServerMemoryLimiter,
781    held_bytes: u64,
782) -> Result<ChargedBuffer> {
783    let decoded_len =
784        snap::raw::decompress_len(buf).context(error::DecompressSnappyPromRemoteRequestSnafu)?;
785    ensure!(
786        decoded_len <= max_decompressed,
787        error::DecompressedBodyTooLargeSnafu {
788            size: decoded_len as u64,
789            limit: max_decompressed as u64,
790        }
791    );
792    // Reserve the declared size before the allocation happens so concurrent
793    // decompressions cannot exceed the aggregate quota.
794    limiter.check_request_fits(decoded_len as u64, held_bytes)?;
795    let guard = limiter.acquire(decoded_len as u64).await?;
796    let mut decoder = Decoder::new();
797    let data = decoder
798        .decompress_vec(buf)
799        .context(error::DecompressSnappyPromRemoteRequestSnafu)?;
800    Ok(ChargedBuffer {
801        data,
802        guards: vec![guard],
803    })
804}
805
806/// Decompresses a zstd body with a hard cap on the decoded size, charging the
807/// aggregate request-memory limiter incrementally while the output is
808/// materialized (zstd streams don't declare their decoded size up front).
809pub(crate) async fn zstd_decompress_limited(
810    buf: &[u8],
811    max_decompressed: usize,
812    limiter: &ServerMemoryLimiter,
813    mut held_bytes: u64,
814) -> Result<ChargedBuffer> {
815    use std::io::Read;
816
817    let decoder = zstd::stream::read::Decoder::new(buf)
818        .context(error::DecompressZstdPromRemoteRequestSnafu)?;
819    // Allow reading one byte past the limit so oversized streams are detected
820    // instead of being silently truncated.
821    let mut limited = decoder.take(max_decompressed as u64 + 1);
822    let mut data = Vec::new();
823    let mut guards = Vec::new();
824    let mut chunk = vec![0u8; ZSTD_DECOMPRESS_CHUNK];
825    loop {
826        let n = limited
827            .read(&mut chunk)
828            .context(error::DecompressZstdPromRemoteRequestSnafu)?;
829        if n == 0 {
830            break;
831        }
832        ensure!(
833            data.len() + n <= max_decompressed,
834            error::DecompressedBodyTooLargeSnafu {
835                size: (data.len() + n) as u64,
836                limit: max_decompressed as u64,
837            }
838        );
839        // Charge each chunk before it is appended so the aggregate quota
840        // bounds the memory while it is being materialized.
841        limiter.check_request_fits(n as u64, held_bytes)?;
842        let guard = limiter.acquire(n as u64).await?;
843        held_bytes += guard.granted_bytes();
844        guards.push(guard);
845        data.extend_from_slice(&chunk[..n]);
846    }
847    Ok(ChargedBuffer { data, guards })
848}
849
850/// Decompresses a raw Snappy body with the hard decoded-size cap enforced,
851/// without charging the aggregate request-memory limiter.
852///
853/// Used for response bodies (remote read replies) and tests; request bodies
854/// must use [`snappy_decompress_limited`] instead so their decoded size is
855/// also charged to the aggregate quota.
856#[inline]
857pub fn snappy_decompress(buf: &[u8]) -> Result<Vec<u8>> {
858    let decoded_len =
859        snap::raw::decompress_len(buf).context(error::DecompressSnappyPromRemoteRequestSnafu)?;
860    ensure!(
861        decoded_len <= MAX_DECOMPRESSED_REQUEST_SIZE,
862        error::DecompressedBodyTooLargeSnafu {
863            size: decoded_len as u64,
864            limit: MAX_DECOMPRESSED_REQUEST_SIZE as u64,
865        }
866    );
867    let mut decoder = Decoder::new();
868    decoder
869        .decompress_vec(buf)
870        .context(error::DecompressSnappyPromRemoteRequestSnafu)
871}
872
873#[inline]
874pub fn snappy_compress(buf: &[u8]) -> Result<Vec<u8>> {
875    let mut encoder = Encoder::new();
876    encoder
877        .compress_vec(buf)
878        .context(error::CompressPromRemoteRequestSnafu)
879}
880
881/// Mock timeseries for test, it is both used in servers and frontend crate
882/// So we present it here
883pub fn mock_timeseries() -> Vec<TimeSeries> {
884    vec![
885        TimeSeries {
886            labels: vec![
887                new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
888                new_label("job".to_string(), "spark".to_string()),
889            ],
890            samples: vec![
891                Sample {
892                    value: 1.0f64,
893                    timestamp: 1000,
894                },
895                Sample {
896                    value: 2.0f64,
897                    timestamp: 2000,
898                },
899            ],
900            ..Default::default()
901        },
902        TimeSeries {
903            labels: vec![
904                new_label(METRIC_NAME_LABEL.to_string(), "metric2".to_string()),
905                new_label("instance".to_string(), "test_host1".to_string()),
906                new_label("idc".to_string(), "z001".to_string()),
907            ],
908            samples: vec![
909                Sample {
910                    value: 3.0f64,
911                    timestamp: 1000,
912                },
913                Sample {
914                    value: 4.0f64,
915                    timestamp: 2000,
916                },
917            ],
918            ..Default::default()
919        },
920        TimeSeries {
921            labels: vec![
922                new_label(METRIC_NAME_LABEL.to_string(), "metric3".to_string()),
923                new_label("idc".to_string(), "z002".to_string()),
924                new_label("app".to_string(), "biz".to_string()),
925            ],
926            samples: vec![
927                Sample {
928                    value: 5.0f64,
929                    timestamp: 1000,
930                },
931                Sample {
932                    value: 6.0f64,
933                    timestamp: 2000,
934                },
935                Sample {
936                    value: 7.0f64,
937                    timestamp: 3000,
938                },
939            ],
940            ..Default::default()
941        },
942    ]
943}
944
945/// Add new labels to the mock timeseries.
946pub fn mock_timeseries_new_label() -> Vec<TimeSeries> {
947    let ts_demo_metrics = TimeSeries {
948        labels: vec![
949            new_label(METRIC_NAME_LABEL.to_string(), "demo_metrics".to_string()),
950            new_label("idc".to_string(), "idc3".to_string()),
951            new_label("new_label1".to_string(), "foo".to_string()),
952        ],
953        samples: vec![Sample {
954            value: 42.0,
955            timestamp: 3000,
956        }],
957        ..Default::default()
958    };
959    let ts_multi_labels = TimeSeries {
960        labels: vec![
961            new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
962            new_label("idc".to_string(), "idc4".to_string()),
963            new_label("env".to_string(), "prod".to_string()),
964            new_label("host".to_string(), "host9".to_string()),
965            new_label("new_label2".to_string(), "bar".to_string()),
966        ],
967        samples: vec![Sample {
968            value: 99.0,
969            timestamp: 4000,
970        }],
971        ..Default::default()
972    };
973
974    vec![ts_demo_metrics, ts_multi_labels]
975}
976
977/// Add new labels to the mock timeseries.
978pub fn mock_timeseries_special_labels() -> Vec<TimeSeries> {
979    let idc3_schema = TimeSeries {
980        labels: vec![
981            new_label(METRIC_NAME_LABEL.to_string(), "idc3_lo_table".to_string()),
982            new_label(DATABASE_LABEL.to_string(), "idc3".to_string()),
983            new_label(PHYSICAL_TABLE_LABEL.to_string(), "f1".to_string()),
984        ],
985        samples: vec![Sample {
986            value: 42.0,
987            timestamp: 3000,
988        }],
989        ..Default::default()
990    };
991    let idc4_schema = TimeSeries {
992        labels: vec![
993            new_label(
994                METRIC_NAME_LABEL.to_string(),
995                "idc4_local_table".to_string(),
996            ),
997            new_label(DATABASE_LABEL.to_string(), "idc4".to_string()),
998            new_label(PHYSICAL_TABLE_LABEL.to_string(), "f2".to_string()),
999        ],
1000        samples: vec![Sample {
1001            value: 99.0,
1002            timestamp: 4000,
1003        }],
1004        ..Default::default()
1005    };
1006
1007    vec![idc3_schema, idc4_schema]
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012    use std::sync::Arc;
1013
1014    use api::prom_store::remote::LabelMatcher;
1015    use api::v1::{ColumnDataType, Row, SemanticType};
1016    use arrow::array::{
1017        DictionaryArray, Float64Array, StringArray, TimestampMillisecondArray, UInt32Array,
1018    };
1019    use arrow::datatypes::{DataType as ArrowDataType, Field, Schema as ArrowSchema, UInt32Type};
1020    use common_memory_manager::OnExhaustedPolicy;
1021    use common_recordbatch::DfRecordBatch;
1022    use datafusion::prelude::SessionContext;
1023    use datatypes::data_type::ConcreteDataType;
1024    use datatypes::schema::{ColumnSchema, Schema};
1025    use datatypes::vectors::{
1026        Float64Vector, Int32Vector, StringVector, TimestampMillisecondVector,
1027    };
1028    use table::table::adapter::DfTableProviderAdapter;
1029    use table::test_util::MemTable;
1030
1031    use super::*;
1032
1033    const EQ_TYPE: i32 = MatcherType::Eq as i32;
1034    const NEQ_TYPE: i32 = MatcherType::Neq as i32;
1035    const RE_TYPE: i32 = MatcherType::Re as i32;
1036
1037    #[test]
1038    fn test_validate_matchers_regex_syntax() {
1039        for op in [MatcherType::Re, MatcherType::Nre] {
1040            for (value, valid) in [
1041                ("api|web", true),
1042                ("(?s)api.web", true),
1043                ("(?x) api", true),
1044                ("(?x) api # comment", false),
1045                ("[", false),
1046                // Wrapping must not make an invalid original pattern valid.
1047                ("a)|(?:b", false),
1048            ] {
1049                let matcher = LabelMatcher {
1050                    name: "job".into(),
1051                    value: value.into(),
1052                    r#type: op as i32,
1053                };
1054                assert_eq!(
1055                    valid,
1056                    validate_matchers(&[matcher]).is_ok(),
1057                    "{op:?}: {value}"
1058                );
1059            }
1060        }
1061    }
1062
1063    #[test]
1064    fn test_table_name() {
1065        let q = Query {
1066            start_timestamp_ms: 1000,
1067            end_timestamp_ms: 2000,
1068            matchers: vec![],
1069            ..Default::default()
1070        };
1071        let err = table_name(&q).unwrap_err();
1072        assert!(matches!(err, error::Error::InvalidPromRemoteRequest { .. }));
1073
1074        let q = Query {
1075            start_timestamp_ms: 1000,
1076            end_timestamp_ms: 2000,
1077            matchers: vec![LabelMatcher {
1078                name: METRIC_NAME_LABEL.to_string(),
1079                value: "test".to_string(),
1080                r#type: EQ_TYPE,
1081            }],
1082            ..Default::default()
1083        };
1084        assert_eq!("test", table_name(&q).unwrap());
1085
1086        for matchers in [
1087            vec![LabelMatcher {
1088                name: METRIC_NAME_LABEL.to_string(),
1089                value: "test.*".to_string(),
1090                r#type: RE_TYPE,
1091            }],
1092            vec![LabelMatcher {
1093                name: METRIC_NAME_LABEL.to_string(),
1094                value: String::new(),
1095                r#type: EQ_TYPE,
1096            }],
1097            vec![
1098                LabelMatcher {
1099                    name: METRIC_NAME_LABEL.to_string(),
1100                    value: "test".to_string(),
1101                    r#type: EQ_TYPE,
1102                },
1103                LabelMatcher {
1104                    name: METRIC_NAME_LABEL.to_string(),
1105                    value: "other".to_string(),
1106                    r#type: EQ_TYPE,
1107                },
1108            ],
1109        ] {
1110            let q = Query {
1111                matchers,
1112                ..Default::default()
1113            };
1114            assert!(matches!(
1115                table_name(&q),
1116                Err(error::Error::InvalidPromRemoteRequest { .. })
1117            ));
1118        }
1119    }
1120
1121    #[test]
1122    #[allow(deprecated)]
1123    fn test_extract_schema_from_query() {
1124        let query = Query::default();
1125        assert_eq!(None, extract_schema_from_query(&query));
1126
1127        for label in [DATABASE_LABEL, DATABASE_LABEL_ALT, SCHEMA_LABEL] {
1128            let query = Query {
1129                matchers: vec![LabelMatcher {
1130                    name: label.to_string(),
1131                    value: "selected_schema".to_string(),
1132                    r#type: EQ_TYPE,
1133                }],
1134                ..Default::default()
1135            };
1136            assert_eq!(
1137                Some("selected_schema".to_string()),
1138                extract_schema_from_query(&query)
1139            );
1140        }
1141
1142        let query = Query {
1143            matchers: vec![LabelMatcher {
1144                name: DATABASE_LABEL.to_string(),
1145                value: "selected_schema".to_string(),
1146                r#type: NEQ_TYPE,
1147            }],
1148            ..Default::default()
1149        };
1150        assert_eq!(None, extract_schema_from_query(&query));
1151    }
1152
1153    #[test]
1154    fn test_query_to_plan() {
1155        let q = Query {
1156            start_timestamp_ms: 1000,
1157            end_timestamp_ms: 2000,
1158            matchers: vec![LabelMatcher {
1159                name: METRIC_NAME_LABEL.to_string(),
1160                value: "test".to_string(),
1161                r#type: EQ_TYPE,
1162            }],
1163            ..Default::default()
1164        };
1165
1166        let schema = Arc::new(Schema::new(vec![
1167            ColumnSchema::new(
1168                greptime_timestamp(),
1169                ConcreteDataType::timestamp_millisecond_datatype(),
1170                true,
1171            ),
1172            ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1173            ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1174            ColumnSchema::new("job", ConcreteDataType::string_datatype(), true),
1175            ColumnSchema::new("service.name", ConcreteDataType::string_datatype(), true),
1176            ColumnSchema::new("Region", ConcreteDataType::string_datatype(), true),
1177        ]));
1178        let recordbatch = RecordBatch::new(
1179            schema,
1180            vec![
1181                Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
1182                Arc::new(Float64Vector::from_vec(vec![3.0])) as _,
1183                Arc::new(StringVector::from(vec!["host1"])) as _,
1184                Arc::new(StringVector::from(vec!["job"])) as _,
1185                Arc::new(StringVector::from(vec!["api"])) as _,
1186                Arc::new(StringVector::from(vec!["us"])) as _,
1187            ],
1188        )
1189        .unwrap();
1190
1191        let ctx = SessionContext::new();
1192        let table = MemTable::table("test", recordbatch);
1193        let table_provider = Arc::new(DfTableProviderAdapter::new(table));
1194
1195        let dataframe = ctx.read_table(table_provider.clone()).unwrap();
1196        let plan = query_to_plan(dataframe, &q, greptime_timestamp()).unwrap();
1197        let display_string = format!("{}", plan.display_indent());
1198
1199        let ts_col = greptime_timestamp();
1200        let expected = format!(
1201            "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None)\n  TableScan: ?table?",
1202            ts_col, ts_col
1203        );
1204        assert_eq!(expected, display_string);
1205
1206        let q = Query {
1207            start_timestamp_ms: 1000,
1208            end_timestamp_ms: 2000,
1209            matchers: vec![
1210                LabelMatcher {
1211                    name: METRIC_NAME_LABEL.to_string(),
1212                    value: "test".to_string(),
1213                    r#type: EQ_TYPE,
1214                },
1215                LabelMatcher {
1216                    name: "job".to_string(),
1217                    value: ".*prom.*".to_string(),
1218                    r#type: RE_TYPE,
1219                },
1220                LabelMatcher {
1221                    name: "instance".to_string(),
1222                    value: "localhost".to_string(),
1223                    r#type: NEQ_TYPE,
1224                },
1225                LabelMatcher {
1226                    name: "service.name".to_string(),
1227                    value: "api".to_string(),
1228                    r#type: EQ_TYPE,
1229                },
1230                LabelMatcher {
1231                    name: "Region".to_string(),
1232                    value: "us".to_string(),
1233                    r#type: EQ_TYPE,
1234                },
1235            ],
1236            ..Default::default()
1237        };
1238
1239        let dataframe = ctx.read_table(table_provider).unwrap();
1240        let plan = query_to_plan(dataframe, &q, greptime_timestamp()).unwrap();
1241        let display_string = format!("{}", plan.display_indent());
1242
1243        let ts_col = greptime_timestamp();
1244        let expected = format!(
1245            "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None) AND regexp_match(?table?.job, Utf8(\"^(?s:.*prom.*)$\")) IS NOT NULL AND (?table?.instance != Utf8(\"localhost\") OR ?table?.instance IS NULL) AND ?table?.service.name = Utf8(\"api\") AND ?table?.Region = Utf8(\"us\")\n  TableScan: ?table?",
1246            ts_col, ts_col
1247        );
1248        assert_eq!(expected, display_string);
1249    }
1250
1251    #[tokio::test]
1252    async fn test_remote_read_time_hints() {
1253        use api::prom_store::remote::ReadHints;
1254
1255        let ctx = SessionContext::new();
1256        let timestamps = vec![-1, 0, 999, 1000, 1500, 2000, 2001, 3000];
1257        let batch = DfRecordBatch::try_from_iter(vec![(
1258            greptime_timestamp(),
1259            Arc::new(TimestampMillisecondArray::from(timestamps)) as ArrayRef,
1260        )])
1261        .unwrap();
1262        ctx.register_batch("metrics", batch).unwrap();
1263
1264        for (outer, hinted, expected) in [
1265            ((1000, 2000), None, vec![1000, 1500, 2000]),
1266            ((1000, 2000), Some((0, 0)), vec![1000, 1500, 2000]),
1267            ((1000, 2000), Some((0, 1500)), vec![1000, 1500, 2000]),
1268            ((1000, 2000), Some((1500, 0)), vec![1000, 1500, 2000]),
1269            ((1000, 2000), Some((-1, 1500)), vec![1000, 1500, 2000]),
1270            ((1000, 2000), Some((1500, -1)), vec![1000, 1500, 2000]),
1271            ((1000, 2000), Some((2000, 1000)), vec![1000, 1500, 2000]),
1272            ((1000, 2000), Some((1000, 2000)), vec![1000, 1500, 2000]),
1273            ((1000, 2000), Some((1, 3000)), vec![1000, 1500, 2000]),
1274            ((1000, 2000), Some((1001, 1999)), vec![1500]),
1275            ((1000, 2000), Some((1500, 3000)), vec![1500, 2000]),
1276            ((1000, 2000), Some((1, 1500)), vec![1000, 1500]),
1277            ((1000, 2000), Some((1500, 1500)), vec![1500]),
1278            ((1000, 2000), Some((2000, 3000)), vec![2000]),
1279            ((1000, 2000), Some((2001, 3000)), vec![]),
1280            ((1000, 2000), Some((1, 999)), vec![]),
1281            ((2000, 1000), Some((1, 3000)), vec![]),
1282            ((-1, 1000), Some((0, 1000)), vec![-1, 0, 999, 1000]),
1283        ] {
1284            let query = Query {
1285                start_timestamp_ms: outer.0,
1286                end_timestamp_ms: outer.1,
1287                hints: hinted.map(|(start_ms, end_ms)| ReadHints {
1288                    start_ms,
1289                    end_ms,
1290                    // These describe evaluation, not sampling or aggregation instructions.
1291                    step_ms: 10000,
1292                    range_ms: 300000,
1293                    func: "rate".into(),
1294                    grouping: vec!["job".into()],
1295                    by: true,
1296                }),
1297                ..Default::default()
1298            };
1299            let plan = query_to_plan(
1300                ctx.table("metrics").await.unwrap(),
1301                &query,
1302                greptime_timestamp(),
1303            )
1304            .unwrap();
1305            let batches = ctx
1306                .execute_logical_plan(plan)
1307                .await
1308                .unwrap()
1309                .collect()
1310                .await
1311                .unwrap();
1312            let actual: Vec<i64> = batches
1313                .iter()
1314                .flat_map(|batch| {
1315                    batch
1316                        .column(0)
1317                        .as_primitive::<TimestampMillisecondType>()
1318                        .values()
1319                        .to_vec()
1320                })
1321                .collect();
1322            assert_eq!(expected, actual, "outer={outer:?}, hints={hinted:?}");
1323        }
1324    }
1325
1326    #[tokio::test]
1327    async fn test_remote_read_label_matcher_semantics() {
1328        let ctx = SessionContext::new();
1329        let batch = DfRecordBatch::try_from_iter(vec![
1330            (
1331                greptime_timestamp(),
1332                Arc::new(TimestampMillisecondArray::from(vec![1000; 6])) as ArrayRef,
1333            ),
1334            (
1335                "job",
1336                Arc::new(StringArray::from(vec![
1337                    Some("api"),
1338                    Some("my-api-server"),
1339                    Some(""),
1340                    None,
1341                    Some("web"),
1342                    Some("api\nweb"),
1343                ])) as ArrayRef,
1344            ),
1345            (
1346                "id",
1347                Arc::new(UInt32Array::from(vec![0, 1, 2, 3, 4, 5])) as ArrayRef,
1348            ),
1349        ])
1350        .unwrap();
1351        ctx.register_batch("metrics", batch).unwrap();
1352        for (name, op, value, expected) in [
1353            ("job", MatcherType::Eq, "api", vec![0]),
1354            ("job", MatcherType::Neq, "api", vec![1, 2, 3, 4, 5]),
1355            ("job", MatcherType::Eq, "", vec![2, 3]),
1356            ("job", MatcherType::Neq, "", vec![0, 1, 4, 5]),
1357            ("job", MatcherType::Re, "api", vec![0]),
1358            ("job", MatcherType::Nre, "api", vec![1, 2, 3, 4, 5]),
1359            ("job", MatcherType::Re, "api|web", vec![0, 4]),
1360            ("job", MatcherType::Nre, "api|web", vec![1, 2, 3, 5]),
1361            ("job", MatcherType::Re, "", vec![2, 3]),
1362            ("job", MatcherType::Nre, "", vec![0, 1, 4, 5]),
1363            ("job", MatcherType::Re, ".*", vec![0, 1, 2, 3, 4, 5]),
1364            ("job", MatcherType::Nre, ".*", vec![]),
1365            ("job", MatcherType::Re, ".+", vec![0, 1, 4, 5]),
1366            ("job", MatcherType::Nre, ".+", vec![2, 3]),
1367            ("job", MatcherType::Re, "(?m)api", vec![0]),
1368            ("job", MatcherType::Re, "api.web", vec![5]),
1369            ("job", MatcherType::Nre, "api.web", vec![0, 1, 2, 3, 4]),
1370            ("job", MatcherType::Re, "(?-s:api.web)", vec![]),
1371            ("job", MatcherType::Re, "(api)?", vec![0, 2, 3]),
1372            ("missing", MatcherType::Eq, "", vec![0, 1, 2, 3, 4, 5]),
1373            ("missing", MatcherType::Eq, "us", vec![]),
1374            ("missing", MatcherType::Neq, "us", vec![0, 1, 2, 3, 4, 5]),
1375            ("missing", MatcherType::Neq, "", vec![]),
1376            ("missing", MatcherType::Re, ".*", vec![0, 1, 2, 3, 4, 5]),
1377            ("missing", MatcherType::Re, ".+", vec![]),
1378            ("missing", MatcherType::Nre, ".*", vec![]),
1379            ("missing", MatcherType::Nre, ".+", vec![0, 1, 2, 3, 4, 5]),
1380            ("missing", MatcherType::Re, "", vec![0, 1, 2, 3, 4, 5]),
1381            ("missing", MatcherType::Nre, "", vec![]),
1382        ] {
1383            let query = Query {
1384                start_timestamp_ms: 1000,
1385                end_timestamp_ms: 1000,
1386                matchers: vec![LabelMatcher {
1387                    name: name.into(),
1388                    value: value.into(),
1389                    r#type: op as i32,
1390                }],
1391                ..Default::default()
1392            };
1393            let plan = query_to_plan(
1394                ctx.table("metrics").await.unwrap(),
1395                &query,
1396                greptime_timestamp(),
1397            )
1398            .unwrap_or_else(|err| panic!("{name} {op:?} {value:?}: {err}"));
1399            let batches = ctx
1400                .execute_logical_plan(plan)
1401                .await
1402                .unwrap()
1403                .collect()
1404                .await
1405                .unwrap();
1406            let actual: Vec<u32> = batches
1407                .iter()
1408                .flat_map(|batch| {
1409                    batch
1410                        .column(2)
1411                        .as_primitive::<UInt32Type>()
1412                        .values()
1413                        .to_vec()
1414                })
1415                .collect();
1416            assert_eq!(expected, actual, "{name} {op:?} {value:?}");
1417        }
1418    }
1419
1420    #[tokio::test]
1421    async fn test_remote_read_matcher_combinations_and_label_types() {
1422        let labels = StringArray::from(vec![Some("api"), Some("api"), None, Some("api")]);
1423        for label_type in [
1424            DataType::Utf8,
1425            DataType::LargeUtf8,
1426            DataType::Utf8View,
1427            DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Utf8)),
1428        ] {
1429            let ctx = SessionContext::new();
1430            let batch = DfRecordBatch::try_from_iter(vec![
1431                (
1432                    greptime_timestamp(),
1433                    Arc::new(TimestampMillisecondArray::from(vec![999, 1000, 2000, 2001]))
1434                        as ArrayRef,
1435                ),
1436                ("Job.Name", casts::cast(&labels, &label_type).unwrap()),
1437            ])
1438            .unwrap();
1439            ctx.register_batch("metrics", batch).unwrap();
1440            let mut cases = vec![
1441                (vec![("Job.Name", MatcherType::Re, ".*")], vec![1000, 2000]),
1442                (vec![("Job.Name", MatcherType::Nre, ".+")], vec![2000]),
1443            ];
1444            // Exercise combinations once; other representations only need regex/NULL coverage.
1445            if label_type == DataType::Utf8 {
1446                cases.extend([
1447                    (
1448                        vec![
1449                            ("Job.Name", MatcherType::Re, "api|web"),
1450                            ("Job.Name", MatcherType::Neq, "web"),
1451                        ],
1452                        vec![1000],
1453                    ),
1454                    (
1455                        vec![
1456                            ("Job.Name", MatcherType::Eq, ""),
1457                            ("Job.Name", MatcherType::Neq, "api"),
1458                        ],
1459                        vec![2000],
1460                    ),
1461                    (
1462                        vec![
1463                            ("Job.Name", MatcherType::Eq, "api"),
1464                            ("Job.Name", MatcherType::Eq, "web"),
1465                        ],
1466                        vec![],
1467                    ),
1468                    (
1469                        vec![
1470                            ("job.name", MatcherType::Eq, ""),
1471                            ("Job.Name", MatcherType::Re, ".*"),
1472                        ],
1473                        vec![1000, 2000],
1474                    ),
1475                ]);
1476            }
1477            for (matchers, expected) in cases {
1478                let mut query = Query {
1479                    start_timestamp_ms: 1000,
1480                    end_timestamp_ms: 2000,
1481                    matchers: matchers
1482                        .iter()
1483                        .map(|(name, op, value)| LabelMatcher {
1484                            name: (*name).into(),
1485                            value: (*value).into(),
1486                            r#type: *op as i32,
1487                        })
1488                        .collect(),
1489                    ..Default::default()
1490                };
1491                query.matchers.push(LabelMatcher {
1492                    name: METRIC_NAME_LABEL.into(),
1493                    value: "metrics".into(),
1494                    r#type: EQ_TYPE,
1495                });
1496                let plan = query_to_plan(
1497                    ctx.table("metrics").await.unwrap(),
1498                    &query,
1499                    greptime_timestamp(),
1500                )
1501                .unwrap();
1502                let batches = ctx
1503                    .execute_logical_plan(plan)
1504                    .await
1505                    .unwrap()
1506                    .collect()
1507                    .await
1508                    .unwrap();
1509                let actual: Vec<i64> = batches
1510                    .iter()
1511                    .flat_map(|batch| {
1512                        batch
1513                            .column(0)
1514                            .as_primitive::<TimestampMillisecondType>()
1515                            .values()
1516                            .to_vec()
1517                    })
1518                    .collect();
1519                assert_eq!(expected, actual, "{label_type:?}: {matchers:?}");
1520            }
1521        }
1522    }
1523
1524    fn column_schemas_with(
1525        mut kts_iter: Vec<(&str, ColumnDataType, SemanticType)>,
1526    ) -> Vec<api::v1::ColumnSchema> {
1527        kts_iter.push((
1528            greptime_value(),
1529            ColumnDataType::Float64,
1530            SemanticType::Field,
1531        ));
1532        kts_iter.push((
1533            greptime_timestamp(),
1534            ColumnDataType::TimestampMillisecond,
1535            SemanticType::Timestamp,
1536        ));
1537
1538        kts_iter
1539            .into_iter()
1540            .map(|(k, t, s)| api::v1::ColumnSchema {
1541                column_name: k.to_string(),
1542                datatype: t as i32,
1543                semantic_type: s as i32,
1544                ..Default::default()
1545            })
1546            .collect()
1547    }
1548
1549    fn make_row_with_label(l1: &str, value: f64, timestamp: i64) -> Row {
1550        Row {
1551            values: vec![
1552                api::v1::Value {
1553                    value_data: Some(api::v1::value::ValueData::StringValue(l1.to_string())),
1554                },
1555                api::v1::Value {
1556                    value_data: Some(api::v1::value::ValueData::F64Value(value)),
1557                },
1558                api::v1::Value {
1559                    value_data: Some(api::v1::value::ValueData::TimestampMillisecondValue(
1560                        timestamp,
1561                    )),
1562                },
1563            ],
1564        }
1565    }
1566
1567    fn make_row_with_2_labels(l1: &str, l2: &str, value: f64, timestamp: i64) -> Row {
1568        Row {
1569            values: vec![
1570                api::v1::Value {
1571                    value_data: Some(api::v1::value::ValueData::StringValue(l1.to_string())),
1572                },
1573                api::v1::Value {
1574                    value_data: Some(api::v1::value::ValueData::StringValue(l2.to_string())),
1575                },
1576                api::v1::Value {
1577                    value_data: Some(api::v1::value::ValueData::F64Value(value)),
1578                },
1579                api::v1::Value {
1580                    value_data: Some(api::v1::value::ValueData::TimestampMillisecondValue(
1581                        timestamp,
1582                    )),
1583                },
1584            ],
1585        }
1586    }
1587
1588    #[test]
1589    fn test_write_request_to_row_insert_exprs() {
1590        let write_request = WriteRequest {
1591            timeseries: mock_timeseries(),
1592            ..Default::default()
1593        };
1594
1595        let mut exprs = to_grpc_row_insert_requests(&write_request)
1596            .unwrap()
1597            .0
1598            .inserts;
1599        exprs.sort_unstable_by(|l, r| l.table_name.cmp(&r.table_name));
1600        assert_eq!(3, exprs.len());
1601        assert_eq!("metric1", exprs[0].table_name);
1602        assert_eq!("metric2", exprs[1].table_name);
1603        assert_eq!("metric3", exprs[2].table_name);
1604
1605        let rows = exprs[0].rows.as_ref().unwrap();
1606        let schema = &rows.schema;
1607        let rows = &rows.rows;
1608        assert_eq!(2, rows.len());
1609        assert_eq!(3, schema.len());
1610        assert_eq!(
1611            column_schemas_with(vec![("job", ColumnDataType::String, SemanticType::Tag)]),
1612            *schema
1613        );
1614        assert_eq!(
1615            &vec![
1616                make_row_with_label("spark", 1.0, 1000),
1617                make_row_with_label("spark", 2.0, 2000),
1618            ],
1619            rows
1620        );
1621
1622        let rows = exprs[1].rows.as_ref().unwrap();
1623        let schema = &rows.schema;
1624        let rows = &rows.rows;
1625        assert_eq!(2, rows.len());
1626        assert_eq!(4, schema.len());
1627        assert_eq!(
1628            column_schemas_with(vec![
1629                ("instance", ColumnDataType::String, SemanticType::Tag),
1630                ("idc", ColumnDataType::String, SemanticType::Tag)
1631            ]),
1632            *schema
1633        );
1634        assert_eq!(
1635            &vec![
1636                make_row_with_2_labels("test_host1", "z001", 3.0, 1000),
1637                make_row_with_2_labels("test_host1", "z001", 4.0, 2000),
1638            ],
1639            rows
1640        );
1641
1642        let rows = exprs[2].rows.as_ref().unwrap();
1643        let schema = &rows.schema;
1644        let rows = &rows.rows;
1645        assert_eq!(3, rows.len());
1646        assert_eq!(4, schema.len());
1647        assert_eq!(
1648            column_schemas_with(vec![
1649                ("idc", ColumnDataType::String, SemanticType::Tag),
1650                ("app", ColumnDataType::String, SemanticType::Tag)
1651            ]),
1652            *schema
1653        );
1654        assert_eq!(
1655            &vec![
1656                make_row_with_2_labels("z002", "biz", 5.0, 1000),
1657                make_row_with_2_labels("z002", "biz", 6.0, 2000),
1658                make_row_with_2_labels("z002", "biz", 7.0, 3000),
1659            ],
1660            rows
1661        );
1662    }
1663
1664    #[test]
1665    fn test_native_histogram_remote_read_wire_fixture() {
1666        use api::greptime_proto::io::prometheus::write::v2::{
1667            BucketSpan, Histogram,
1668            histogram::{Count, ZeroCount},
1669        };
1670        use api::prom_store::remote::{QueryResult, ReadResponse};
1671        use common_query::native_histogram::testing::histogram_array;
1672        use prost::Message;
1673
1674        let histograms = vec![
1675            Histogram {
1676                count: Some(Count::CountInt((1 << 53) + 13)),
1677                zero_count: Some(ZeroCount::ZeroCountInt((1 << 53) + 1)),
1678                sum: 10.0,
1679                schema: 1,
1680                zero_threshold: 0.001,
1681                reset_hint: 2,
1682                start_timestamp: 123,
1683                positive_spans: vec![
1684                    BucketSpan {
1685                        offset: -2,
1686                        length: 2,
1687                    },
1688                    BucketSpan {
1689                        offset: 3,
1690                        length: 1,
1691                    },
1692                ],
1693                positive_deltas: vec![1, 2, -1],
1694                negative_spans: vec![
1695                    BucketSpan {
1696                        offset: -3,
1697                        length: 1,
1698                    },
1699                    BucketSpan {
1700                        offset: 2,
1701                        length: 2,
1702                    },
1703                ],
1704                negative_deltas: vec![1, 2, -1],
1705                ..Default::default()
1706            },
1707            Histogram {
1708                count: Some(Count::CountFloat(6.0)),
1709                zero_count: Some(ZeroCount::ZeroCountFloat(0.5)),
1710                sum: 20.0,
1711                schema: 2,
1712                zero_threshold: 0.002,
1713                reset_hint: 3,
1714                positive_spans: vec![BucketSpan {
1715                    offset: 3,
1716                    length: 2,
1717                }],
1718                positive_counts: vec![2.0, 3.5],
1719                ..Default::default()
1720            },
1721            Histogram {
1722                count: Some(Count::CountInt(1)),
1723                sum: 1.0,
1724                schema: -53,
1725                custom_values: vec![1.0, 2.0],
1726                positive_spans: vec![BucketSpan {
1727                    offset: 0,
1728                    length: 1,
1729                }],
1730                positive_deltas: vec![1],
1731                ..Default::default()
1732            },
1733            Histogram {
1734                count: Some(Count::CountFloat(3.5)),
1735                sum: 4.0,
1736                schema: -53,
1737                custom_values: vec![1.0, 2.0],
1738                reset_hint: 3,
1739                positive_spans: vec![BucketSpan {
1740                    offset: 0,
1741                    length: 2,
1742                }],
1743                positive_counts: vec![1.0, 2.5],
1744                ..Default::default()
1745            },
1746            Histogram {
1747                count: Some(Count::CountInt(0)),
1748                ..Default::default()
1749            },
1750            Histogram {
1751                count: Some(Count::CountFloat(0.0)),
1752                ..Default::default()
1753            },
1754            Histogram {
1755                count: Some(Count::CountInt(8)),
1756                sum: f64::from_bits(0x7ff8_0000_0000_0123),
1757                positive_spans: vec![BucketSpan {
1758                    offset: 0,
1759                    length: 2,
1760                }],
1761                positive_deltas: vec![3, -1],
1762                ..Default::default()
1763            },
1764            Histogram {
1765                count: Some(Count::CountInt(0)),
1766                sum: f64::from_bits(0x7ff0_0000_0000_0002),
1767                ..Default::default()
1768            },
1769            Histogram {
1770                count: Some(Count::CountInt(i64::MAX as u64)),
1771                zero_count: Some(ZeroCount::ZeroCountInt(i64::MAX as u64)),
1772                ..Default::default()
1773            },
1774        ];
1775        let values = histograms
1776            .iter()
1777            .map(|h| Some(native_histogram::encode_native_histogram(h).unwrap()))
1778            .collect::<Vec<_>>();
1779        let array = histogram_array(&values);
1780        let buckets = array
1781            .column_by_name("positive_buckets_i64")
1782            .unwrap()
1783            .as_list::<i32>()
1784            .value(0);
1785        assert_eq!(
1786            buckets
1787                .as_primitive::<arrow::datatypes::Int64Type>()
1788                .values()
1789                .as_ref(),
1790            &[1, 3, 2]
1791        );
1792        let arrow_schema = Arc::new(ArrowSchema::new(vec![
1793            Field::new(
1794                "ts",
1795                ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
1796                false,
1797            ),
1798            Field::new("histogram", array.data_type().clone(), true),
1799        ]));
1800        let schema = Arc::new(Schema::try_from(arrow_schema.clone()).unwrap());
1801        let batch = DfRecordBatch::try_new(
1802            arrow_schema,
1803            vec![
1804                Arc::new(TimestampMillisecondArray::from(
1805                    (1..=9).map(|i| i * 1000).collect::<Vec<_>>(),
1806                )),
1807                Arc::new(array),
1808            ],
1809        )
1810        .unwrap();
1811        let batches = RecordBatches::try_new(
1812            schema.clone(),
1813            vec![RecordBatch::from_df_record_batch(schema, batch)],
1814        )
1815        .unwrap();
1816        let response = ReadResponse {
1817            results: vec![QueryResult {
1818                timeseries: recordbatches_to_timeseries(
1819                    "native_histogram_remote_read",
1820                    "ts",
1821                    "histogram",
1822                    batches,
1823                )
1824                .unwrap(),
1825            }],
1826        };
1827        let body = snappy_compress(&response.encode_to_vec()).unwrap();
1828        let mut decoded =
1829            ReadResponse::decode(snappy_decompress(&body).unwrap().as_slice()).unwrap();
1830        let mut expected = ReadResponse::decode(
1831            include_bytes!(
1832                "../../../tests/compatibility/wire/native_histogram_remote_read/response.pb"
1833            )
1834            .as_slice(),
1835        )
1836        .unwrap();
1837        let actual_histograms = &mut decoded.results[0].timeseries[0].histograms;
1838        assert_eq!(actual_histograms[0].positive_deltas, vec![1, 2, -1]);
1839        assert_eq!(actual_histograms[0].negative_deltas, vec![1, 2, -1]);
1840        assert_eq!(actual_histograms[6].positive_deltas, vec![3, -1]);
1841        for (actual, expected) in actual_histograms
1842            .iter_mut()
1843            .zip(&mut expected.results[0].timeseries[0].histograms)
1844        {
1845            assert_eq!(actual.sum.to_bits(), expected.sum.to_bits());
1846            actual.sum = 0.0;
1847            expected.sum = 0.0;
1848        }
1849        assert_eq!(decoded, expected);
1850        // The optional output is consumed by the pinned Prometheus iterator check.
1851        if let Some(path) = std::env::var_os("GREPTIME_NATIVE_HISTOGRAM_REMOTE_READ_OUTPUT") {
1852            std::fs::write(path, body).unwrap();
1853        }
1854    }
1855
1856    #[test]
1857    fn test_native_histogram_remote_read_groups_and_sorts_batches() {
1858        use api::greptime_proto::io::prometheus::write::v2::{Histogram, histogram::Count};
1859        use common_query::native_histogram::testing::histogram_array;
1860
1861        let integer = native_histogram::encode_native_histogram(&Histogram {
1862            count: Some(Count::CountInt(0)),
1863            sum: f64::from_bits(0x7ff0_0000_0000_0002),
1864            ..Default::default()
1865        })
1866        .unwrap();
1867        let float = native_histogram::encode_native_histogram(&Histogram {
1868            count: Some(Count::CountFloat(0.0)),
1869            ..Default::default()
1870        })
1871        .unwrap();
1872        let histograms = histogram_array(&[Some(integer), Some(float.clone()), None, Some(float)]);
1873        let arrow_schema = Arc::new(ArrowSchema::new(vec![
1874            Field::new(
1875                "event_time",
1876                ArrowDataType::Timestamp(TimeUnit::Microsecond, None),
1877                true,
1878            ),
1879            Field::new("legacy_histogram", histograms.data_type().clone(), true),
1880            Field::new_dictionary("instance", ArrowDataType::UInt32, ArrowDataType::Utf8, true),
1881        ]));
1882        let schema = Arc::new(Schema::try_from(arrow_schema.clone()).unwrap());
1883        let batch = |timestamps: Vec<Option<i64>>, labels: Vec<Option<u32>>| {
1884            RecordBatch::from_df_record_batch(
1885                schema.clone(),
1886                DfRecordBatch::try_new(
1887                    arrow_schema.clone(),
1888                    vec![
1889                        Arc::new(arrow::array::TimestampMicrosecondArray::from(timestamps)),
1890                        Arc::new(histograms.clone()),
1891                        Arc::new(DictionaryArray::<UInt32Type>::new(
1892                            UInt32Array::from(labels),
1893                            Arc::new(StringArray::from(vec![Some("b"), Some("a"), None])),
1894                        )),
1895                    ],
1896                )
1897                .unwrap(),
1898            )
1899        };
1900        let batches = vec![
1901            batch(
1902                vec![Some(4000), Some(3000), Some(5000), Some(-1001)],
1903                vec![Some(0), None, Some(0), Some(0)],
1904            ),
1905            batch(
1906                vec![Some(2000), Some(1000), Some(6000), None],
1907                vec![Some(0), Some(2), Some(1), Some(0)],
1908            ),
1909        ];
1910        let series = recordbatches_to_timeseries(
1911            "latency",
1912            "event_time",
1913            "legacy_histogram",
1914            RecordBatches::try_new(schema.clone(), batches).unwrap(),
1915        )
1916        .unwrap();
1917        assert_eq!(series.len(), 3);
1918        assert_eq!(
1919            series[0].labels,
1920            vec![new_label(
1921                METRIC_NAME_LABEL.to_string(),
1922                "latency".to_string()
1923            )]
1924        );
1925        assert_eq!(
1926            series[0]
1927                .histograms
1928                .iter()
1929                .map(|h| h.timestamp)
1930                .collect::<Vec<_>>(),
1931            vec![1, 3]
1932        );
1933        assert_eq!(series[1].labels[1].value, "a");
1934        assert!(series[1].histograms.is_empty());
1935        assert_eq!(series[2].labels[1].value, "b");
1936        assert_eq!(
1937            series[2]
1938                .histograms
1939                .iter()
1940                .map(|h| h.timestamp)
1941                .collect::<Vec<_>>(),
1942            vec![-2, 2, 4]
1943        );
1944        assert!(matches!(
1945            series[2].histograms[0].count,
1946            Some(api::prom_store::remote::histogram::Count::CountFloat(0.0))
1947        ));
1948        for histogram in &series[2].histograms[1..] {
1949            assert!(matches!(
1950                histogram.count,
1951                Some(api::prom_store::remote::histogram::Count::CountInt(0))
1952            ));
1953            assert_eq!(histogram.sum.to_bits(), 0x7ff0_0000_0000_0002);
1954        }
1955        assert!(
1956            series
1957                .iter()
1958                .all(|s| s.samples.is_empty()
1959                    && s.labels.iter().all(|l| l.name != "legacy_histogram"))
1960        );
1961    }
1962
1963    #[test]
1964    fn test_recordbatches_to_timeseries() {
1965        let schema = Arc::new(Schema::new(vec![
1966            ColumnSchema::new(
1967                greptime_timestamp(),
1968                ConcreteDataType::timestamp_millisecond_datatype(),
1969                true,
1970            ),
1971            ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1972            ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1973        ]));
1974
1975        let recordbatches = RecordBatches::try_new(
1976            schema.clone(),
1977            vec![
1978                RecordBatch::new(
1979                    schema.clone(),
1980                    vec![
1981                        Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
1982                        Arc::new(Float64Vector::from_vec(vec![3.0])) as _,
1983                        Arc::new(StringVector::from(vec!["host1"])) as _,
1984                    ],
1985                )
1986                .unwrap(),
1987                RecordBatch::new(
1988                    schema,
1989                    vec![
1990                        Arc::new(TimestampMillisecondVector::from_vec(vec![2000])) as _,
1991                        Arc::new(Float64Vector::from_vec(vec![7.0])) as _,
1992                        Arc::new(StringVector::from(vec!["host2"])) as _,
1993                    ],
1994                )
1995                .unwrap(),
1996            ],
1997        )
1998        .unwrap();
1999
2000        let timeseries = recordbatches_to_timeseries(
2001            "metric1",
2002            greptime_timestamp(),
2003            greptime_value(),
2004            recordbatches,
2005        )
2006        .unwrap();
2007        assert_eq!(2, timeseries.len());
2008
2009        assert_eq!(
2010            vec![
2011                Label {
2012                    name: METRIC_NAME_LABEL.to_string(),
2013                    value: "metric1".to_string(),
2014                },
2015                Label {
2016                    name: "instance".to_string(),
2017                    value: "host1".to_string(),
2018                },
2019            ],
2020            timeseries[0].labels
2021        );
2022
2023        assert_eq!(
2024            timeseries[0].samples,
2025            vec![Sample {
2026                value: 3.0,
2027                timestamp: 1000,
2028            }]
2029        );
2030
2031        assert_eq!(
2032            vec![
2033                Label {
2034                    name: METRIC_NAME_LABEL.to_string(),
2035                    value: "metric1".to_string(),
2036                },
2037                Label {
2038                    name: "instance".to_string(),
2039                    value: "host2".to_string(),
2040                },
2041            ],
2042            timeseries[1].labels
2043        );
2044        assert_eq!(
2045            timeseries[1].samples,
2046            vec![Sample {
2047                value: 7.0,
2048                timestamp: 2000,
2049            }]
2050        );
2051    }
2052
2053    #[test]
2054    fn test_recordbatches_to_timeseries_merges_across_batches() {
2055        let schema = Arc::new(Schema::new(vec![
2056            ColumnSchema::new(
2057                greptime_timestamp(),
2058                ConcreteDataType::timestamp_millisecond_datatype(),
2059                true,
2060            ),
2061            ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
2062            ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
2063        ]));
2064
2065        let recordbatches = RecordBatches::try_new(
2066            schema.clone(),
2067            vec![
2068                RecordBatch::new(
2069                    schema.clone(),
2070                    vec![
2071                        Arc::new(TimestampMillisecondVector::from_vec(vec![3000, 1500])) as _,
2072                        Arc::new(Float64Vector::from_vec(vec![30.0, 15.0])) as _,
2073                        Arc::new(StringVector::from(vec!["host1", "host2"])) as _,
2074                    ],
2075                )
2076                .unwrap(),
2077                RecordBatch::new(
2078                    schema.clone(),
2079                    vec![
2080                        Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
2081                        Arc::new(Float64Vector::from_vec(vec![10.0])) as _,
2082                        Arc::new(StringVector::from(vec!["host1"])) as _,
2083                    ],
2084                )
2085                .unwrap(),
2086                RecordBatch::new(
2087                    schema,
2088                    vec![
2089                        Arc::new(TimestampMillisecondVector::from_vec(vec![2000, 2500])) as _,
2090                        Arc::new(Float64Vector::from_vec(vec![20.0, 25.0])) as _,
2091                        Arc::new(StringVector::from(vec!["host1", "host2"])) as _,
2092                    ],
2093                )
2094                .unwrap(),
2095            ],
2096        )
2097        .unwrap();
2098
2099        let timeseries = recordbatches_to_timeseries(
2100            "cpu_usage",
2101            greptime_timestamp(),
2102            greptime_value(),
2103            recordbatches,
2104        )
2105        .unwrap();
2106
2107        assert_eq!(2, timeseries.len());
2108
2109        assert_eq!(
2110            vec![
2111                Label {
2112                    name: METRIC_NAME_LABEL.to_string(),
2113                    value: "cpu_usage".to_string(),
2114                },
2115                Label {
2116                    name: "instance".to_string(),
2117                    value: "host1".to_string(),
2118                },
2119            ],
2120            timeseries[0].labels
2121        );
2122        assert_eq!(
2123            vec![
2124                Sample {
2125                    value: 10.0,
2126                    timestamp: 1000,
2127                },
2128                Sample {
2129                    value: 20.0,
2130                    timestamp: 2000,
2131                },
2132                Sample {
2133                    value: 30.0,
2134                    timestamp: 3000,
2135                },
2136            ],
2137            timeseries[0].samples
2138        );
2139
2140        assert_eq!(
2141            vec![
2142                Label {
2143                    name: METRIC_NAME_LABEL.to_string(),
2144                    value: "cpu_usage".to_string(),
2145                },
2146                Label {
2147                    name: "instance".to_string(),
2148                    value: "host2".to_string(),
2149                },
2150            ],
2151            timeseries[1].labels
2152        );
2153        assert_eq!(
2154            vec![
2155                Sample {
2156                    value: 15.0,
2157                    timestamp: 1500,
2158                },
2159                Sample {
2160                    value: 25.0,
2161                    timestamp: 2500,
2162                },
2163            ],
2164            timeseries[1].samples
2165        );
2166    }
2167
2168    #[test]
2169    fn test_recordbatches_to_timeseries_borrows_and_groups_dictionary_labels() {
2170        let arrow_schema = Arc::new(ArrowSchema::new(vec![
2171            Field::new(
2172                greptime_timestamp(),
2173                ArrowDataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None),
2174                false,
2175            ),
2176            Field::new(greptime_value(), ArrowDataType::Float64, false),
2177            Field::new_dictionary("instance", ArrowDataType::UInt32, ArrowDataType::Utf8, true),
2178        ]));
2179        let schema = Arc::new(Schema::try_from(arrow_schema.clone()).unwrap());
2180        let instance = DictionaryArray::<UInt32Type>::new(
2181            UInt32Array::from(vec![Some(0), None, Some(1), Some(0), Some(2)]),
2182            Arc::new(StringArray::from(vec![Some("host2"), Some("host1"), None])),
2183        );
2184        let batch = DfRecordBatch::try_new(
2185            arrow_schema,
2186            vec![
2187                Arc::new(TimestampMillisecondArray::from(vec![
2188                    1000, 2000, 3000, 4000, 5000,
2189                ])),
2190                Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0, 5.0])),
2191                Arc::new(instance),
2192            ],
2193        )
2194        .unwrap();
2195        let recordbatch = RecordBatch::from_df_record_batch(schema.clone(), batch);
2196        let columns = label_columns(&recordbatch, greptime_timestamp(), greptime_value()).unwrap();
2197        assert!(matches!(
2198            columns[0].values,
2199            LabelValues::DictionaryUtf8 { .. }
2200        ));
2201        drop(columns);
2202        let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
2203
2204        let timeseries = recordbatches_to_timeseries(
2205            "metric1",
2206            greptime_timestamp(),
2207            greptime_value(),
2208            recordbatches,
2209        )
2210        .unwrap();
2211
2212        assert_eq!(3, timeseries.len());
2213        assert_eq!(
2214            vec![Label {
2215                name: METRIC_NAME_LABEL.to_string(),
2216                value: "metric1".to_string(),
2217            }],
2218            timeseries[0].labels
2219        );
2220        assert_eq!(
2221            vec![
2222                Sample {
2223                    value: 2.0,
2224                    timestamp: 2000,
2225                },
2226                Sample {
2227                    value: 5.0,
2228                    timestamp: 5000,
2229                },
2230            ],
2231            timeseries[0].samples
2232        );
2233        assert_eq!("host1", timeseries[1].labels[1].value);
2234        assert_eq!(
2235            vec![Sample {
2236                value: 3.0,
2237                timestamp: 3000,
2238            }],
2239            timeseries[1].samples
2240        );
2241        assert_eq!("host2", timeseries[2].labels[1].value);
2242        assert_eq!(
2243            vec![
2244                Sample {
2245                    value: 1.0,
2246                    timestamp: 1000,
2247                },
2248                Sample {
2249                    value: 4.0,
2250                    timestamp: 4000,
2251                },
2252            ],
2253            timeseries[2].samples
2254        );
2255    }
2256
2257    #[test]
2258    fn test_recordbatches_to_timeseries_groups_non_contiguous_series() {
2259        let schema = Arc::new(Schema::new(vec![
2260            ColumnSchema::new(
2261                greptime_timestamp(),
2262                ConcreteDataType::timestamp_millisecond_datatype(),
2263                true,
2264            ),
2265            ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
2266            ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
2267        ]));
2268        let recordbatch = RecordBatch::new(
2269            schema.clone(),
2270            vec![
2271                Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as _,
2272                Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as _,
2273                Arc::new(StringVector::from(vec!["host2", "host1", "host2"])) as _,
2274            ],
2275        )
2276        .unwrap();
2277
2278        let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
2279        let timeseries = recordbatches_to_timeseries(
2280            "metric1",
2281            greptime_timestamp(),
2282            greptime_value(),
2283            recordbatches,
2284        )
2285        .unwrap();
2286
2287        // The result stays sorted by labels as it was with the previous BTreeMap.
2288        assert_eq!("host1", timeseries[0].labels[1].value);
2289        assert_eq!("host2", timeseries[1].labels[1].value);
2290        assert_eq!(
2291            vec![
2292                Sample {
2293                    value: 1.0,
2294                    timestamp: 1000,
2295                },
2296                Sample {
2297                    value: 3.0,
2298                    timestamp: 3000,
2299                },
2300            ],
2301            timeseries[1].samples
2302        );
2303    }
2304
2305    #[test]
2306    fn test_recordbatches_to_timeseries_arrow_label_types_and_nulls() {
2307        let schema = Arc::new(Schema::new(vec![
2308            ColumnSchema::new(
2309                greptime_timestamp(),
2310                ConcreteDataType::timestamp_millisecond_datatype(),
2311                true,
2312            ),
2313            ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
2314            ColumnSchema::new("instance", ConcreteDataType::large_string_datatype(), true),
2315            ColumnSchema::new("zone", ConcreteDataType::utf8_view_datatype(), true),
2316            ColumnSchema::new("shard", ConcreteDataType::int32_datatype(), true),
2317        ]));
2318        let recordbatch = RecordBatch::new(
2319            schema.clone(),
2320            vec![
2321                Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as _,
2322                Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as _,
2323                Arc::new(StringVector::from(LargeStringArray::from(vec![
2324                    "host2", "host1", "host2",
2325                ]))) as _,
2326                Arc::new(StringVector::from(StringViewArray::from(vec![
2327                    Some("west"),
2328                    None,
2329                    Some("west"),
2330                ]))) as _,
2331                Arc::new(Int32Vector::from_vec(vec![2, 1, 2])) as _,
2332            ],
2333        )
2334        .unwrap();
2335
2336        let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
2337        let timeseries = recordbatches_to_timeseries(
2338            "metric1",
2339            greptime_timestamp(),
2340            greptime_value(),
2341            recordbatches,
2342        )
2343        .unwrap();
2344
2345        assert_eq!(2, timeseries.len());
2346        assert_eq!(
2347            vec![
2348                new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
2349                new_label("instance".to_string(), "host1".to_string()),
2350                new_label("shard".to_string(), "1".to_string()),
2351            ],
2352            timeseries[0].labels
2353        );
2354        assert_eq!(
2355            vec![
2356                new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
2357                new_label("instance".to_string(), "host2".to_string()),
2358                new_label("zone".to_string(), "west".to_string()),
2359                new_label("shard".to_string(), "2".to_string()),
2360            ],
2361            timeseries[1].labels
2362        );
2363        assert_eq!(
2364            vec![
2365                Sample {
2366                    value: 1.0,
2367                    timestamp: 1000,
2368                },
2369                Sample {
2370                    value: 3.0,
2371                    timestamp: 3000,
2372                },
2373            ],
2374            timeseries[1].samples
2375        );
2376    }
2377
2378    #[tokio::test]
2379    async fn test_snappy_varint_bomb_is_rejected_before_allocation() {
2380        // A five-byte raw Snappy header declaring a ~4 GiB decoded length.
2381        // Regression test for the compression-bomb finding: the declared size
2382        // must be rejected by the cap check before any buffer is allocated.
2383        let bomb = [0xffu8, 0xff, 0xff, 0xff, 0x0f];
2384        let limiter = ServerMemoryLimiter::default();
2385
2386        let err = snappy_decompress_limited(&bomb, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2387            .await
2388            .unwrap_err();
2389        assert!(matches!(
2390            err,
2391            crate::error::Error::DecompressedBodyTooLarge { .. }
2392        ));
2393        assert_eq!(0, limiter.used_bytes());
2394    }
2395
2396    #[tokio::test]
2397    async fn test_snappy_decompress_limited_roundtrip() {
2398        let raw = vec![b'a'; 1024 * 1024];
2399        let mut encoder = snap::raw::Encoder::new();
2400        let compressed = encoder.compress_vec(&raw).unwrap();
2401        let limiter = ServerMemoryLimiter::default();
2402
2403        let decoded =
2404            snappy_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2405                .await
2406                .unwrap();
2407        assert_eq!(&decoded[..], &raw[..]);
2408    }
2409
2410    #[tokio::test]
2411    async fn test_snappy_charges_and_releases_quota() {
2412        let raw = vec![b'a'; 64 * 1024];
2413        let mut encoder = snap::raw::Encoder::new();
2414        let compressed = encoder.compress_vec(&raw).unwrap();
2415        let limiter = ServerMemoryLimiter::new(128 * 1024, OnExhaustedPolicy::Fail);
2416
2417        let decoded =
2418            snappy_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2419                .await
2420                .unwrap();
2421        // Rounding to the 1 KiB permit granularity.
2422        assert!(limiter.used_bytes() >= 64 * 1024);
2423        assert!(limiter.used_bytes() <= 65 * 1024);
2424        drop(decoded);
2425        assert_eq!(0, limiter.used_bytes());
2426    }
2427
2428    #[tokio::test]
2429    async fn test_snappy_rejects_when_quota_exhausted() {
2430        let raw = vec![b'a'; 64 * 1024];
2431        let mut encoder = snap::raw::Encoder::new();
2432        let compressed = encoder.compress_vec(&raw).unwrap();
2433        // Quota smaller than the decoded size.
2434        let limiter = ServerMemoryLimiter::new(1024, OnExhaustedPolicy::Fail);
2435
2436        let err =
2437            snappy_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2438                .await
2439                .unwrap_err();
2440        assert!(matches!(
2441            err,
2442            crate::error::Error::MemoryLimitExceeded { .. }
2443        ));
2444        assert_eq!(0, limiter.used_bytes(), "failed charge must be released");
2445    }
2446
2447    #[tokio::test]
2448    async fn test_zstd_at_and_over_the_cap() {
2449        let limiter = ServerMemoryLimiter::default();
2450        let small_cap = 8 * 1024;
2451
2452        // Exactly at the cap: accepted.
2453        let raw = vec![0u8; small_cap];
2454        let compressed = zstd::stream::encode_all(&raw[..], 3).unwrap();
2455        let decoded = zstd_decompress_limited(&compressed, small_cap, &limiter, 0)
2456            .await
2457            .unwrap();
2458        assert_eq!(decoded.len(), small_cap);
2459
2460        // One byte past the cap: rejected.
2461        let raw = vec![0u8; small_cap + 1];
2462        let compressed = zstd::stream::encode_all(&raw[..], 3).unwrap();
2463        let err = zstd_decompress_limited(&compressed, small_cap, &limiter, 0)
2464            .await
2465            .unwrap_err();
2466        assert!(matches!(
2467            err,
2468            crate::error::Error::DecompressedBodyTooLarge { .. }
2469        ));
2470    }
2471
2472    #[tokio::test(start_paused = true)]
2473    async fn test_zstd_charges_incrementally() {
2474        // 64 KiB wire that expands to 2 MiB must be charged chunk by chunk
2475        // against a quota that only fits the expanded size.
2476        let raw = vec![0u8; 2 * 1024 * 1024];
2477        let compressed = zstd::stream::encode_all(&raw[..], 19).unwrap();
2478        assert!(compressed.len() < 16 * 1024);
2479        let limiter = ServerMemoryLimiter::new(4 * 1024 * 1024, OnExhaustedPolicy::Fail);
2480
2481        let decoded =
2482            zstd_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2483                .await
2484                .unwrap();
2485        assert_eq!(decoded.len(), raw.len());
2486        assert!(limiter.used_bytes() >= raw.len() as u64);
2487        drop(decoded);
2488        assert_eq!(0, limiter.used_bytes());
2489        // The next chunk cannot fit alongside this request's earlier chunks.
2490        let limiter = ServerMemoryLimiter::new(
2491            (2 * ZSTD_DECOMPRESS_CHUNK) as u64,
2492            OnExhaustedPolicy::default(),
2493        );
2494        let started = tokio::time::Instant::now();
2495        let error =
2496            zstd_decompress_limited(&compressed, MAX_DECOMPRESSED_REQUEST_SIZE, &limiter, 0)
2497                .await
2498                .unwrap_err();
2499        assert!(matches!(error, error::Error::MemoryLimitExceeded { .. }));
2500        assert_eq!(started.elapsed(), std::time::Duration::ZERO);
2501        assert_eq!(limiter.used_bytes(), 0);
2502    }
2503
2504    #[test]
2505    fn test_snappy_response_decompress_is_capped() {
2506        // The response-side (uncharged) helper must enforce the cap too.
2507        let bomb = [0xffu8, 0xff, 0xff, 0xff, 0x0f];
2508        let err = snappy_decompress(&bomb).unwrap_err();
2509        assert!(matches!(
2510            err,
2511            crate::error::Error::DecompressedBodyTooLarge { .. }
2512        ));
2513    }
2514}