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};
21
22use api::prom_store::remote::label_matcher::Type as MatcherType;
23use api::prom_store::remote::{Label, Query, Sample, TimeSeries, WriteRequest};
24use api::v1::RowInsertRequests;
25use arrow::array::{
26    Array, AsArray, DictionaryArray, LargeStringArray, StringArray, StringViewArray,
27};
28use arrow::datatypes::{Float64Type, TimestampMillisecondType, UInt32Type};
29use common_grpc::precision::Precision;
30use common_query::prelude::{greptime_timestamp, greptime_value};
31use common_recordbatch::{RecordBatch, RecordBatches};
32use common_telemetry::{tracing, warn};
33use datafusion::dataframe::DataFrame;
34use datafusion::prelude::{Expr, col, lit, regexp_match};
35use datafusion_common::ScalarValue;
36use datafusion_expr::LogicalPlan;
37use openmetrics_parser::{MetricsExposition, PrometheusType, PrometheusValue};
38use snafu::{OptionExt, ResultExt, ensure};
39use snap::raw::{Decoder, Encoder};
40
41use crate::error::{self, Result};
42use crate::row_writer::{self, MultiTableData};
43
44pub const METRIC_NAME_LABEL: &str = "__name__";
45pub const METRIC_NAME_LABEL_BYTES: &[u8] = b"__name__";
46
47/// special label for selecting database name on remote write
48pub const DATABASE_LABEL: &str = "x_greptime_database";
49pub const DATABASE_LABEL_BYTES: &[u8] = b"x_greptime_database";
50pub const DATABASE_LABEL_ALT: &str = "__database__";
51pub const DATABASE_LABEL_ALT_BYTES: &[u8] = b"__database__";
52
53/// deprecated, use DATABASE_LABEL instead
54#[deprecated(note = "use DATABASE_LABEL instead")]
55pub const SCHEMA_LABEL: &str = "__schema__";
56#[deprecated(note = "use DATABASE_LABEL_BYTES instead")]
57pub const SCHEMA_LABEL_BYTES: &[u8] = b"__schema__";
58
59/// special label for selecting physical table name on remote write
60pub const PHYSICAL_TABLE_LABEL: &str = "x_greptime_physical_table";
61pub const PHYSICAL_TABLE_LABEL_BYTES: &[u8] = b"x_greptime_physical_table";
62pub const PHYSICAL_TABLE_LABEL_ALT: &str = "__physical_table__";
63pub const PHYSICAL_TABLE_LABEL_ALT_BYTES: &[u8] = b"__physical_table__";
64
65/// The same as `FIELD_COLUMN_MATCHER` in `promql` crate
66pub const FIELD_NAME_LABEL: &str = "__field__";
67
68/// Check if given label is a special label for remote write
69#[allow(deprecated)]
70pub fn is_remote_write_special_label(label: &str) -> bool {
71    label == DATABASE_LABEL
72        || label == DATABASE_LABEL_ALT
73        || label == PHYSICAL_TABLE_LABEL
74        || label == PHYSICAL_TABLE_LABEL_ALT
75        || label == SCHEMA_LABEL
76}
77
78#[allow(deprecated)]
79pub fn is_remote_read_special_label(label: &str) -> bool {
80    label == METRIC_NAME_LABEL
81        || label == DATABASE_LABEL
82        || label == DATABASE_LABEL_ALT
83        || label == SCHEMA_LABEL
84}
85
86/// Check if given label is a database selection label
87#[allow(deprecated)]
88pub fn is_database_selection_label(label: &str) -> bool {
89    label == DATABASE_LABEL || label == DATABASE_LABEL_ALT || label == SCHEMA_LABEL
90}
91
92/// Check if given label is a physical table selection label
93pub fn is_physical_table_selection_label(label: &str) -> bool {
94    label == PHYSICAL_TABLE_LABEL || label == PHYSICAL_TABLE_LABEL_ALT
95}
96
97/// Metrics for push gateway protocol
98pub struct Metrics {
99    pub exposition: MetricsExposition<PrometheusType, PrometheusValue>,
100}
101
102/// Get table name from remote query
103pub fn table_name(q: &Query) -> Result<String> {
104    let mut matchers = q
105        .matchers
106        .iter()
107        .filter(|matcher| matcher.name == METRIC_NAME_LABEL);
108    let matcher = matchers
109        .next()
110        .context(error::InvalidPromRemoteRequestSnafu {
111            msg: "missing '__name__' label in timeseries",
112        })?;
113
114    if matcher.r#type != MatcherType::Eq as i32
115        || matcher.value.is_empty()
116        || matchers.next().is_some()
117    {
118        return Err(error::InvalidPromRemoteRequestSnafu {
119            msg: "expected exactly one non-empty equality matcher for '__name__'".to_string(),
120        }
121        .build());
122    }
123
124    Ok(matcher.value.clone())
125}
126
127/// Extract database selector from a remote read query.
128pub fn extract_schema_from_query(query: &Query) -> Option<String> {
129    query
130        .matchers
131        .iter()
132        .find(|matcher| {
133            is_database_selection_label(&matcher.name) && matcher.r#type == MatcherType::Eq as i32
134        })
135        .map(|matcher| matcher.value.clone())
136}
137
138/// Create a DataFrame from a remote Query
139#[tracing::instrument(skip_all)]
140pub fn query_to_plan(dataframe: DataFrame, q: &Query) -> Result<LogicalPlan> {
141    let start_timestamp_ms = q.start_timestamp_ms;
142    let end_timestamp_ms = q.end_timestamp_ms;
143
144    let label_matches = &q.matchers;
145
146    let mut conditions = Vec::with_capacity(label_matches.len() + 1);
147
148    conditions.push(col(greptime_timestamp()).gt_eq(lit_timestamp_millisecond(start_timestamp_ms)));
149    conditions.push(col(greptime_timestamp()).lt_eq(lit_timestamp_millisecond(end_timestamp_ms)));
150
151    for m in label_matches {
152        let name = &m.name;
153
154        if is_remote_read_special_label(name) {
155            continue;
156        }
157
158        let value = &m.value;
159        let m_type = MatcherType::try_from(m.r#type).map_err(|e| {
160            error::InvalidPromRemoteRequestSnafu {
161                msg: format!("invalid LabelMatcher type, decode error: {e}",),
162            }
163            .build()
164        })?;
165
166        match m_type {
167            MatcherType::Eq => {
168                conditions.push(col(name).eq(lit(value)));
169            }
170            MatcherType::Neq => {
171                conditions.push(col(name).not_eq(lit(value)));
172            }
173            // Case sensitive regexp match
174            MatcherType::Re => {
175                conditions.push(regexp_match(col(name), lit(value), None).is_not_null());
176            }
177            // Case sensitive regexp not match
178            MatcherType::Nre => {
179                conditions.push(regexp_match(col(name), lit(value), None).is_null());
180            }
181        }
182    }
183
184    // Safety: conditions MUST not be empty, reduce always return Some(expr).
185    let conditions = conditions.into_iter().reduce(Expr::and).unwrap();
186
187    let dataframe = dataframe
188        .filter(conditions)
189        .context(error::DataFrameSnafu)?;
190
191    Ok(dataframe.into_parts().1)
192}
193
194#[inline]
195fn new_label(name: String, value: String) -> Label {
196    Label { name, value }
197}
198
199fn lit_timestamp_millisecond(ts: i64) -> Expr {
200    Expr::Literal(ScalarValue::TimestampMillisecond(Some(ts), None), None)
201}
202
203/// Sort timeseries by labels, matching the former `BTreeMap` order.
204fn compare_timeseries_labels(left: &[Label], right: &[Label]) -> Ordering {
205    let ordering = left.len().cmp(&right.len());
206    if ordering != Ordering::Equal {
207        return ordering;
208    }
209
210    for (left, right) in left.iter().zip(right) {
211        let ordering = left.name.cmp(&right.name);
212        if ordering != Ordering::Equal {
213            return ordering;
214        }
215
216        let ordering = left.value.cmp(&right.value);
217        if ordering != Ordering::Equal {
218            return ordering;
219        }
220    }
221
222    Ordering::Equal
223}
224
225enum LabelValues<'a> {
226    Utf8(&'a StringArray),
227    LargeUtf8(&'a LargeStringArray),
228    Utf8View(&'a StringViewArray),
229    DictionaryUtf8 {
230        dictionary: &'a DictionaryArray<UInt32Type>,
231        values: &'a StringArray,
232    },
233    Other(Vec<Option<String>>),
234}
235
236impl LabelValues<'_> {
237    fn value(&self, row: usize) -> Option<&str> {
238        match self {
239            Self::Utf8(values) => values.is_valid(row).then(|| values.value(row)),
240            Self::LargeUtf8(values) => values.is_valid(row).then(|| values.value(row)),
241            Self::Utf8View(values) => values.is_valid(row).then(|| values.value(row)),
242            Self::DictionaryUtf8 { dictionary, values } => dictionary
243                .key(row)
244                .and_then(|key| values.is_valid(key).then(|| values.value(key))),
245            Self::Other(values) => values.get(row).and_then(Option::as_deref),
246        }
247    }
248}
249
250fn row_labels<'a>(
251    columns: &'a [LabelColumn<'a>],
252    row: usize,
253) -> impl Iterator<Item = (&'a str, &'a str)> {
254    columns
255        .iter()
256        .filter_map(move |column| column.values.value(row).map(|value| (column.name, value)))
257}
258
259struct LabelColumn<'a> {
260    name: &'a str,
261    values: LabelValues<'a>,
262}
263
264fn label_columns(recordbatch: &RecordBatch) -> Result<Vec<LabelColumn<'_>>> {
265    recordbatch
266        .schema
267        .column_schemas()
268        .iter()
269        .enumerate()
270        .filter(|(_, column_schema)| {
271            column_schema.name != greptime_timestamp() && column_schema.name != greptime_value()
272        })
273        .map(|(index, column_schema)| {
274            let array = recordbatch.column(index);
275            let values = match array.data_type() {
276                arrow::datatypes::DataType::Utf8 => LabelValues::Utf8(array.as_string::<i32>()),
277                arrow::datatypes::DataType::LargeUtf8 => {
278                    LabelValues::LargeUtf8(array.as_string::<i64>())
279                }
280                arrow::datatypes::DataType::Utf8View => {
281                    LabelValues::Utf8View(array.as_string_view())
282                }
283                arrow::datatypes::DataType::Dictionary(key, value)
284                    if key.as_ref() == &arrow::datatypes::DataType::UInt32
285                        && value.as_ref() == &arrow::datatypes::DataType::Utf8 =>
286                {
287                    let dictionary = array.as_dictionary::<UInt32Type>();
288                    LabelValues::DictionaryUtf8 {
289                        dictionary,
290                        values: dictionary.values().as_string::<i32>(),
291                    }
292                }
293                _ => {
294                    let values = recordbatch.iter_column_as_string(index).collect::<Vec<_>>();
295                    ensure!(
296                        values.len() == recordbatch.num_rows(),
297                        error::InvalidPromRemoteReadQueryResultSnafu {
298                            msg: format!(
299                                "Cannot convert label column '{}' of datatype {:?} to string",
300                                column_schema.name,
301                                array.data_type()
302                            ),
303                        }
304                    );
305                    LabelValues::Other(values)
306                }
307            };
308            Ok(LabelColumn {
309                name: &column_schema.name,
310                values,
311            })
312        })
313        .collect()
314}
315
316fn hash_timeseries(columns: &[LabelColumn<'_>], row: usize) -> u64 {
317    let mut hasher = DefaultHasher::new();
318
319    for (name, value) in row_labels(columns, row) {
320        name.hash(&mut hasher);
321        value.hash(&mut hasher);
322    }
323
324    hasher.finish()
325}
326
327fn matches_timeseries(labels: &[Label], columns: &[LabelColumn<'_>], row: usize) -> bool {
328    let mut labels = labels.iter().skip(1);
329    for (name, value) in row_labels(columns, row) {
330        let Some(label) = labels.next() else {
331            return false;
332        };
333        if label.name != name || label.value != value {
334            return false;
335        }
336    }
337
338    labels.next().is_none()
339}
340
341fn new_timeseries(table: &str, columns: &[LabelColumn<'_>], row: usize) -> TimeSeries {
342    let mut labels = Vec::with_capacity(columns.len() + 1);
343    labels.push(new_label(METRIC_NAME_LABEL.to_string(), table.to_string()));
344
345    for (name, value) in row_labels(columns, row) {
346        labels.push(new_label(name.to_string(), value.to_string()));
347    }
348
349    TimeSeries {
350        labels,
351        ..Default::default()
352    }
353}
354
355pub fn recordbatches_to_timeseries(
356    table_name: &str,
357    recordbatches: RecordBatches,
358) -> Result<Vec<TimeSeries>> {
359    Ok(recordbatches
360        .take()
361        .into_iter()
362        .map(|x| recordbatch_to_timeseries(table_name, x))
363        .collect::<Result<Vec<_>>>()?
364        .into_iter()
365        .flatten()
366        .collect())
367}
368
369fn recordbatch_to_timeseries(table: &str, recordbatch: RecordBatch) -> Result<Vec<TimeSeries>> {
370    let ts_column = recordbatch.column_by_name(greptime_timestamp()).context(
371        error::InvalidPromRemoteReadQueryResultSnafu {
372            msg: "missing greptime_timestamp column in query result",
373        },
374    )?;
375    let ts_column = ts_column
376        .as_primitive_opt::<TimestampMillisecondType>()
377        .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
378            msg: format!(
379                "Expect timestamp column of datatype Timestamp(Millisecond), actual {:?}",
380                ts_column.data_type()
381            ),
382        })?;
383
384    let field_column = recordbatch.column_by_name(greptime_value()).context(
385        error::InvalidPromRemoteReadQueryResultSnafu {
386            msg: "missing greptime_value column in query result",
387        },
388    )?;
389    let field_column = field_column
390        .as_primitive_opt::<Float64Type>()
391        .with_context(|| error::InvalidPromRemoteReadQueryResultSnafu {
392            msg: format!(
393                "Expect value column of datatype Float64, actual {:?}",
394                field_column.data_type()
395            ),
396        })?;
397
398    let columns = label_columns(&recordbatch)?;
399    let mut timeseries: Vec<TimeSeries> = Vec::new();
400    let mut timeseries_by_hash: HashMap<u64, Vec<usize>> = HashMap::new();
401    let mut previous_timeseries: Option<usize> = None;
402
403    for row in 0..recordbatch.num_rows() {
404        let timeseries_index = match previous_timeseries {
405            Some(index) if matches_timeseries(&timeseries[index].labels, &columns, row) => index,
406            _ => {
407                let hash = hash_timeseries(&columns, row);
408                let candidates = timeseries_by_hash.entry(hash).or_default();
409                match candidates
410                    .iter()
411                    .copied()
412                    .find(|index| matches_timeseries(&timeseries[*index].labels, &columns, row))
413                {
414                    Some(index) => index,
415                    None => {
416                        let index = timeseries.len();
417                        timeseries.push(new_timeseries(table, &columns, row));
418                        candidates.push(index);
419                        index
420                    }
421                }
422            }
423        };
424        previous_timeseries = Some(timeseries_index);
425
426        if ts_column.is_null(row) || field_column.is_null(row) {
427            continue;
428        }
429
430        let value = field_column.value(row);
431        let timestamp = ts_column.value(row);
432        let sample = Sample { value, timestamp };
433
434        timeseries[timeseries_index].samples.push(sample);
435    }
436
437    timeseries
438        .sort_unstable_by(|left, right| compare_timeseries_labels(&left.labels, &right.labels));
439    Ok(timeseries)
440}
441
442pub fn to_grpc_row_insert_requests(request: &WriteRequest) -> Result<(RowInsertRequests, usize)> {
443    let _timer = crate::metrics::METRIC_HTTP_PROM_STORE_CONVERT_ELAPSED.start_timer();
444
445    let mut multi_table_data = MultiTableData::new();
446
447    for series in &request.timeseries {
448        let table_name = &series
449            .labels
450            .iter()
451            .find(|label| {
452                // The metric name is a special label
453                label.name == METRIC_NAME_LABEL
454            })
455            .context(error::InvalidPromRemoteRequestSnafu {
456                msg: "missing '__name__' label in time-series",
457            })?
458            .value;
459
460        // The metric name is a special label,
461        // num_columns = labels.len() - 1 + 1 (value) + 1 (timestamp)
462        let num_columns = series.labels.len() + 1;
463
464        let table_data = multi_table_data.get_or_default_table_data(
465            table_name,
466            num_columns,
467            series.samples.len(),
468        );
469
470        // labels
471        let kvs = series.labels.iter().filter_map(|label| {
472            if label.name == METRIC_NAME_LABEL {
473                None
474            } else {
475                Some((label.name.clone(), label.value.clone()))
476            }
477        });
478
479        if series.samples.len() == 1 {
480            let mut one_row = table_data.alloc_one_row();
481
482            row_writer::write_tags(table_data, kvs, &mut one_row)?;
483            // value
484            row_writer::write_f64(
485                table_data,
486                greptime_value(),
487                series.samples[0].value,
488                &mut one_row,
489            )?;
490            // timestamp
491            row_writer::write_ts_to_millis(
492                table_data,
493                greptime_timestamp(),
494                Some(series.samples[0].timestamp),
495                Precision::Millisecond,
496                &mut one_row,
497            )?;
498
499            table_data.add_row(one_row);
500        } else {
501            for Sample { value, timestamp } in &series.samples {
502                let mut one_row = table_data.alloc_one_row();
503
504                // labels
505                let kvs = kvs.clone();
506                row_writer::write_tags(table_data, kvs, &mut one_row)?;
507                // value
508                row_writer::write_f64(table_data, greptime_value(), *value, &mut one_row)?;
509                // timestamp
510                row_writer::write_ts_to_millis(
511                    table_data,
512                    greptime_timestamp(),
513                    Some(*timestamp),
514                    Precision::Millisecond,
515                    &mut one_row,
516                )?;
517
518                table_data.add_row(one_row);
519            }
520        }
521
522        if !series.histograms.is_empty() {
523            warn!("Native histograms are not supported yet, data ignored");
524        }
525    }
526
527    Ok(multi_table_data.into_row_insert_requests())
528}
529
530#[inline]
531pub fn snappy_decompress(buf: &[u8]) -> Result<Vec<u8>> {
532    let mut decoder = Decoder::new();
533    decoder
534        .decompress_vec(buf)
535        .context(error::DecompressSnappyPromRemoteRequestSnafu)
536}
537
538#[inline]
539pub fn snappy_compress(buf: &[u8]) -> Result<Vec<u8>> {
540    let mut encoder = Encoder::new();
541    encoder
542        .compress_vec(buf)
543        .context(error::CompressPromRemoteRequestSnafu)
544}
545
546#[inline]
547pub fn zstd_decompress(buf: &[u8]) -> Result<Vec<u8>> {
548    zstd::stream::decode_all(buf).context(error::DecompressZstdPromRemoteRequestSnafu)
549}
550
551/// Mock timeseries for test, it is both used in servers and frontend crate
552/// So we present it here
553pub fn mock_timeseries() -> Vec<TimeSeries> {
554    vec![
555        TimeSeries {
556            labels: vec![
557                new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
558                new_label("job".to_string(), "spark".to_string()),
559            ],
560            samples: vec![
561                Sample {
562                    value: 1.0f64,
563                    timestamp: 1000,
564                },
565                Sample {
566                    value: 2.0f64,
567                    timestamp: 2000,
568                },
569            ],
570            ..Default::default()
571        },
572        TimeSeries {
573            labels: vec![
574                new_label(METRIC_NAME_LABEL.to_string(), "metric2".to_string()),
575                new_label("instance".to_string(), "test_host1".to_string()),
576                new_label("idc".to_string(), "z001".to_string()),
577            ],
578            samples: vec![
579                Sample {
580                    value: 3.0f64,
581                    timestamp: 1000,
582                },
583                Sample {
584                    value: 4.0f64,
585                    timestamp: 2000,
586                },
587            ],
588            ..Default::default()
589        },
590        TimeSeries {
591            labels: vec![
592                new_label(METRIC_NAME_LABEL.to_string(), "metric3".to_string()),
593                new_label("idc".to_string(), "z002".to_string()),
594                new_label("app".to_string(), "biz".to_string()),
595            ],
596            samples: vec![
597                Sample {
598                    value: 5.0f64,
599                    timestamp: 1000,
600                },
601                Sample {
602                    value: 6.0f64,
603                    timestamp: 2000,
604                },
605                Sample {
606                    value: 7.0f64,
607                    timestamp: 3000,
608                },
609            ],
610            ..Default::default()
611        },
612    ]
613}
614
615/// Add new labels to the mock timeseries.
616pub fn mock_timeseries_new_label() -> Vec<TimeSeries> {
617    let ts_demo_metrics = TimeSeries {
618        labels: vec![
619            new_label(METRIC_NAME_LABEL.to_string(), "demo_metrics".to_string()),
620            new_label("idc".to_string(), "idc3".to_string()),
621            new_label("new_label1".to_string(), "foo".to_string()),
622        ],
623        samples: vec![Sample {
624            value: 42.0,
625            timestamp: 3000,
626        }],
627        ..Default::default()
628    };
629    let ts_multi_labels = TimeSeries {
630        labels: vec![
631            new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
632            new_label("idc".to_string(), "idc4".to_string()),
633            new_label("env".to_string(), "prod".to_string()),
634            new_label("host".to_string(), "host9".to_string()),
635            new_label("new_label2".to_string(), "bar".to_string()),
636        ],
637        samples: vec![Sample {
638            value: 99.0,
639            timestamp: 4000,
640        }],
641        ..Default::default()
642    };
643
644    vec![ts_demo_metrics, ts_multi_labels]
645}
646
647/// Add new labels to the mock timeseries.
648pub fn mock_timeseries_special_labels() -> Vec<TimeSeries> {
649    let idc3_schema = TimeSeries {
650        labels: vec![
651            new_label(METRIC_NAME_LABEL.to_string(), "idc3_lo_table".to_string()),
652            new_label(DATABASE_LABEL.to_string(), "idc3".to_string()),
653            new_label(PHYSICAL_TABLE_LABEL.to_string(), "f1".to_string()),
654        ],
655        samples: vec![Sample {
656            value: 42.0,
657            timestamp: 3000,
658        }],
659        ..Default::default()
660    };
661    let idc4_schema = TimeSeries {
662        labels: vec![
663            new_label(
664                METRIC_NAME_LABEL.to_string(),
665                "idc4_local_table".to_string(),
666            ),
667            new_label(DATABASE_LABEL.to_string(), "idc4".to_string()),
668            new_label(PHYSICAL_TABLE_LABEL.to_string(), "f2".to_string()),
669        ],
670        samples: vec![Sample {
671            value: 99.0,
672            timestamp: 4000,
673        }],
674        ..Default::default()
675    };
676
677    vec![idc3_schema, idc4_schema]
678}
679
680#[cfg(test)]
681mod tests {
682    use std::sync::Arc;
683
684    use api::prom_store::remote::LabelMatcher;
685    use api::v1::{ColumnDataType, Row, SemanticType};
686    use arrow::array::{
687        DictionaryArray, Float64Array, StringArray, TimestampMillisecondArray, UInt32Array,
688    };
689    use arrow::datatypes::{DataType as ArrowDataType, Field, Schema as ArrowSchema, UInt32Type};
690    use common_recordbatch::DfRecordBatch;
691    use datafusion::prelude::SessionContext;
692    use datatypes::data_type::ConcreteDataType;
693    use datatypes::schema::{ColumnSchema, Schema};
694    use datatypes::vectors::{
695        Float64Vector, Int32Vector, StringVector, TimestampMillisecondVector,
696    };
697    use table::table::adapter::DfTableProviderAdapter;
698    use table::test_util::MemTable;
699
700    use super::*;
701
702    const EQ_TYPE: i32 = MatcherType::Eq as i32;
703    const NEQ_TYPE: i32 = MatcherType::Neq as i32;
704    const RE_TYPE: i32 = MatcherType::Re as i32;
705
706    #[test]
707    fn test_table_name() {
708        let q = Query {
709            start_timestamp_ms: 1000,
710            end_timestamp_ms: 2000,
711            matchers: vec![],
712            ..Default::default()
713        };
714        let err = table_name(&q).unwrap_err();
715        assert!(matches!(err, error::Error::InvalidPromRemoteRequest { .. }));
716
717        let q = Query {
718            start_timestamp_ms: 1000,
719            end_timestamp_ms: 2000,
720            matchers: vec![LabelMatcher {
721                name: METRIC_NAME_LABEL.to_string(),
722                value: "test".to_string(),
723                r#type: EQ_TYPE,
724            }],
725            ..Default::default()
726        };
727        assert_eq!("test", table_name(&q).unwrap());
728
729        for matchers in [
730            vec![LabelMatcher {
731                name: METRIC_NAME_LABEL.to_string(),
732                value: "test.*".to_string(),
733                r#type: RE_TYPE,
734            }],
735            vec![LabelMatcher {
736                name: METRIC_NAME_LABEL.to_string(),
737                value: String::new(),
738                r#type: EQ_TYPE,
739            }],
740            vec![
741                LabelMatcher {
742                    name: METRIC_NAME_LABEL.to_string(),
743                    value: "test".to_string(),
744                    r#type: EQ_TYPE,
745                },
746                LabelMatcher {
747                    name: METRIC_NAME_LABEL.to_string(),
748                    value: "other".to_string(),
749                    r#type: EQ_TYPE,
750                },
751            ],
752        ] {
753            let q = Query {
754                matchers,
755                ..Default::default()
756            };
757            assert!(matches!(
758                table_name(&q),
759                Err(error::Error::InvalidPromRemoteRequest { .. })
760            ));
761        }
762    }
763
764    #[test]
765    #[allow(deprecated)]
766    fn test_extract_schema_from_query() {
767        let query = Query::default();
768        assert_eq!(None, extract_schema_from_query(&query));
769
770        for label in [DATABASE_LABEL, DATABASE_LABEL_ALT, SCHEMA_LABEL] {
771            let query = Query {
772                matchers: vec![LabelMatcher {
773                    name: label.to_string(),
774                    value: "selected_schema".to_string(),
775                    r#type: EQ_TYPE,
776                }],
777                ..Default::default()
778            };
779            assert_eq!(
780                Some("selected_schema".to_string()),
781                extract_schema_from_query(&query)
782            );
783        }
784
785        let query = Query {
786            matchers: vec![LabelMatcher {
787                name: DATABASE_LABEL.to_string(),
788                value: "selected_schema".to_string(),
789                r#type: NEQ_TYPE,
790            }],
791            ..Default::default()
792        };
793        assert_eq!(None, extract_schema_from_query(&query));
794    }
795
796    #[test]
797    fn test_query_to_plan() {
798        let q = Query {
799            start_timestamp_ms: 1000,
800            end_timestamp_ms: 2000,
801            matchers: vec![LabelMatcher {
802                name: METRIC_NAME_LABEL.to_string(),
803                value: "test".to_string(),
804                r#type: EQ_TYPE,
805            }],
806            ..Default::default()
807        };
808
809        let schema = Arc::new(Schema::new(vec![
810            ColumnSchema::new(
811                greptime_timestamp(),
812                ConcreteDataType::timestamp_millisecond_datatype(),
813                true,
814            ),
815            ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
816            ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
817            ColumnSchema::new("job", ConcreteDataType::string_datatype(), true),
818        ]));
819        let recordbatch = RecordBatch::new(
820            schema,
821            vec![
822                Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
823                Arc::new(Float64Vector::from_vec(vec![3.0])) as _,
824                Arc::new(StringVector::from(vec!["host1"])) as _,
825                Arc::new(StringVector::from(vec!["job"])) as _,
826            ],
827        )
828        .unwrap();
829
830        let ctx = SessionContext::new();
831        let table = MemTable::table("test", recordbatch);
832        let table_provider = Arc::new(DfTableProviderAdapter::new(table));
833
834        let dataframe = ctx.read_table(table_provider.clone()).unwrap();
835        let plan = query_to_plan(dataframe, &q).unwrap();
836        let display_string = format!("{}", plan.display_indent());
837
838        let ts_col = greptime_timestamp();
839        let expected = format!(
840            "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None)\n  TableScan: ?table?",
841            ts_col, ts_col
842        );
843        assert_eq!(expected, display_string);
844
845        let q = Query {
846            start_timestamp_ms: 1000,
847            end_timestamp_ms: 2000,
848            matchers: vec![
849                LabelMatcher {
850                    name: METRIC_NAME_LABEL.to_string(),
851                    value: "test".to_string(),
852                    r#type: EQ_TYPE,
853                },
854                LabelMatcher {
855                    name: "job".to_string(),
856                    value: "*prom*".to_string(),
857                    r#type: RE_TYPE,
858                },
859                LabelMatcher {
860                    name: "instance".to_string(),
861                    value: "localhost".to_string(),
862                    r#type: NEQ_TYPE,
863                },
864            ],
865            ..Default::default()
866        };
867
868        let dataframe = ctx.read_table(table_provider).unwrap();
869        let plan = query_to_plan(dataframe, &q).unwrap();
870        let display_string = format!("{}", plan.display_indent());
871
872        let ts_col = greptime_timestamp();
873        let expected = format!(
874            "Filter: ?table?.{} >= TimestampMillisecond(1000, None) AND ?table?.{} <= TimestampMillisecond(2000, None) AND regexp_match(?table?.job, Utf8(\"*prom*\")) IS NOT NULL AND ?table?.instance != Utf8(\"localhost\")\n  TableScan: ?table?",
875            ts_col, ts_col
876        );
877        assert_eq!(expected, display_string);
878    }
879
880    fn column_schemas_with(
881        mut kts_iter: Vec<(&str, ColumnDataType, SemanticType)>,
882    ) -> Vec<api::v1::ColumnSchema> {
883        kts_iter.push((
884            greptime_value(),
885            ColumnDataType::Float64,
886            SemanticType::Field,
887        ));
888        kts_iter.push((
889            greptime_timestamp(),
890            ColumnDataType::TimestampMillisecond,
891            SemanticType::Timestamp,
892        ));
893
894        kts_iter
895            .into_iter()
896            .map(|(k, t, s)| api::v1::ColumnSchema {
897                column_name: k.to_string(),
898                datatype: t as i32,
899                semantic_type: s as i32,
900                ..Default::default()
901            })
902            .collect()
903    }
904
905    fn make_row_with_label(l1: &str, value: f64, timestamp: i64) -> Row {
906        Row {
907            values: vec![
908                api::v1::Value {
909                    value_data: Some(api::v1::value::ValueData::StringValue(l1.to_string())),
910                },
911                api::v1::Value {
912                    value_data: Some(api::v1::value::ValueData::F64Value(value)),
913                },
914                api::v1::Value {
915                    value_data: Some(api::v1::value::ValueData::TimestampMillisecondValue(
916                        timestamp,
917                    )),
918                },
919            ],
920        }
921    }
922
923    fn make_row_with_2_labels(l1: &str, l2: &str, value: f64, timestamp: i64) -> Row {
924        Row {
925            values: vec![
926                api::v1::Value {
927                    value_data: Some(api::v1::value::ValueData::StringValue(l1.to_string())),
928                },
929                api::v1::Value {
930                    value_data: Some(api::v1::value::ValueData::StringValue(l2.to_string())),
931                },
932                api::v1::Value {
933                    value_data: Some(api::v1::value::ValueData::F64Value(value)),
934                },
935                api::v1::Value {
936                    value_data: Some(api::v1::value::ValueData::TimestampMillisecondValue(
937                        timestamp,
938                    )),
939                },
940            ],
941        }
942    }
943
944    #[test]
945    fn test_write_request_to_row_insert_exprs() {
946        let write_request = WriteRequest {
947            timeseries: mock_timeseries(),
948            ..Default::default()
949        };
950
951        let mut exprs = to_grpc_row_insert_requests(&write_request)
952            .unwrap()
953            .0
954            .inserts;
955        exprs.sort_unstable_by(|l, r| l.table_name.cmp(&r.table_name));
956        assert_eq!(3, exprs.len());
957        assert_eq!("metric1", exprs[0].table_name);
958        assert_eq!("metric2", exprs[1].table_name);
959        assert_eq!("metric3", exprs[2].table_name);
960
961        let rows = exprs[0].rows.as_ref().unwrap();
962        let schema = &rows.schema;
963        let rows = &rows.rows;
964        assert_eq!(2, rows.len());
965        assert_eq!(3, schema.len());
966        assert_eq!(
967            column_schemas_with(vec![("job", ColumnDataType::String, SemanticType::Tag)]),
968            *schema
969        );
970        assert_eq!(
971            &vec![
972                make_row_with_label("spark", 1.0, 1000),
973                make_row_with_label("spark", 2.0, 2000),
974            ],
975            rows
976        );
977
978        let rows = exprs[1].rows.as_ref().unwrap();
979        let schema = &rows.schema;
980        let rows = &rows.rows;
981        assert_eq!(2, rows.len());
982        assert_eq!(4, schema.len());
983        assert_eq!(
984            column_schemas_with(vec![
985                ("instance", ColumnDataType::String, SemanticType::Tag),
986                ("idc", ColumnDataType::String, SemanticType::Tag)
987            ]),
988            *schema
989        );
990        assert_eq!(
991            &vec![
992                make_row_with_2_labels("test_host1", "z001", 3.0, 1000),
993                make_row_with_2_labels("test_host1", "z001", 4.0, 2000),
994            ],
995            rows
996        );
997
998        let rows = exprs[2].rows.as_ref().unwrap();
999        let schema = &rows.schema;
1000        let rows = &rows.rows;
1001        assert_eq!(3, rows.len());
1002        assert_eq!(4, schema.len());
1003        assert_eq!(
1004            column_schemas_with(vec![
1005                ("idc", ColumnDataType::String, SemanticType::Tag),
1006                ("app", ColumnDataType::String, SemanticType::Tag)
1007            ]),
1008            *schema
1009        );
1010        assert_eq!(
1011            &vec![
1012                make_row_with_2_labels("z002", "biz", 5.0, 1000),
1013                make_row_with_2_labels("z002", "biz", 6.0, 2000),
1014                make_row_with_2_labels("z002", "biz", 7.0, 3000),
1015            ],
1016            rows
1017        );
1018    }
1019
1020    #[test]
1021    fn test_recordbatches_to_timeseries() {
1022        let schema = Arc::new(Schema::new(vec![
1023            ColumnSchema::new(
1024                greptime_timestamp(),
1025                ConcreteDataType::timestamp_millisecond_datatype(),
1026                true,
1027            ),
1028            ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1029            ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1030        ]));
1031
1032        let recordbatches = RecordBatches::try_new(
1033            schema.clone(),
1034            vec![
1035                RecordBatch::new(
1036                    schema.clone(),
1037                    vec![
1038                        Arc::new(TimestampMillisecondVector::from_vec(vec![1000])) as _,
1039                        Arc::new(Float64Vector::from_vec(vec![3.0])) as _,
1040                        Arc::new(StringVector::from(vec!["host1"])) as _,
1041                    ],
1042                )
1043                .unwrap(),
1044                RecordBatch::new(
1045                    schema,
1046                    vec![
1047                        Arc::new(TimestampMillisecondVector::from_vec(vec![2000])) as _,
1048                        Arc::new(Float64Vector::from_vec(vec![7.0])) as _,
1049                        Arc::new(StringVector::from(vec!["host2"])) as _,
1050                    ],
1051                )
1052                .unwrap(),
1053            ],
1054        )
1055        .unwrap();
1056
1057        let timeseries = recordbatches_to_timeseries("metric1", recordbatches).unwrap();
1058        assert_eq!(2, timeseries.len());
1059
1060        assert_eq!(
1061            vec![
1062                Label {
1063                    name: METRIC_NAME_LABEL.to_string(),
1064                    value: "metric1".to_string(),
1065                },
1066                Label {
1067                    name: "instance".to_string(),
1068                    value: "host1".to_string(),
1069                },
1070            ],
1071            timeseries[0].labels
1072        );
1073
1074        assert_eq!(
1075            timeseries[0].samples,
1076            vec![Sample {
1077                value: 3.0,
1078                timestamp: 1000,
1079            }]
1080        );
1081
1082        assert_eq!(
1083            vec![
1084                Label {
1085                    name: METRIC_NAME_LABEL.to_string(),
1086                    value: "metric1".to_string(),
1087                },
1088                Label {
1089                    name: "instance".to_string(),
1090                    value: "host2".to_string(),
1091                },
1092            ],
1093            timeseries[1].labels
1094        );
1095        assert_eq!(
1096            timeseries[1].samples,
1097            vec![Sample {
1098                value: 7.0,
1099                timestamp: 2000,
1100            }]
1101        );
1102    }
1103
1104    #[test]
1105    fn test_recordbatches_to_timeseries_borrows_and_groups_dictionary_labels() {
1106        let arrow_schema = Arc::new(ArrowSchema::new(vec![
1107            Field::new(
1108                greptime_timestamp(),
1109                ArrowDataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None),
1110                false,
1111            ),
1112            Field::new(greptime_value(), ArrowDataType::Float64, false),
1113            Field::new_dictionary("instance", ArrowDataType::UInt32, ArrowDataType::Utf8, true),
1114        ]));
1115        let schema = Arc::new(Schema::try_from(arrow_schema.clone()).unwrap());
1116        let instance = DictionaryArray::<UInt32Type>::new(
1117            UInt32Array::from(vec![Some(0), None, Some(1), Some(0), Some(2)]),
1118            Arc::new(StringArray::from(vec![Some("host2"), Some("host1"), None])),
1119        );
1120        let batch = DfRecordBatch::try_new(
1121            arrow_schema,
1122            vec![
1123                Arc::new(TimestampMillisecondArray::from(vec![
1124                    1000, 2000, 3000, 4000, 5000,
1125                ])),
1126                Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0, 5.0])),
1127                Arc::new(instance),
1128            ],
1129        )
1130        .unwrap();
1131        let recordbatch = RecordBatch::from_df_record_batch(schema.clone(), batch);
1132        let columns = label_columns(&recordbatch).unwrap();
1133        assert!(matches!(
1134            columns[0].values,
1135            LabelValues::DictionaryUtf8 { .. }
1136        ));
1137        drop(columns);
1138        let recordbatches = RecordBatches::try_new(schema, vec![recordbatch]).unwrap();
1139
1140        let timeseries = recordbatches_to_timeseries("metric1", recordbatches).unwrap();
1141
1142        assert_eq!(3, timeseries.len());
1143        assert_eq!(
1144            vec![Label {
1145                name: METRIC_NAME_LABEL.to_string(),
1146                value: "metric1".to_string(),
1147            }],
1148            timeseries[0].labels
1149        );
1150        assert_eq!(
1151            vec![
1152                Sample {
1153                    value: 2.0,
1154                    timestamp: 2000,
1155                },
1156                Sample {
1157                    value: 5.0,
1158                    timestamp: 5000,
1159                },
1160            ],
1161            timeseries[0].samples
1162        );
1163        assert_eq!("host1", timeseries[1].labels[1].value);
1164        assert_eq!(
1165            vec![Sample {
1166                value: 3.0,
1167                timestamp: 3000,
1168            }],
1169            timeseries[1].samples
1170        );
1171        assert_eq!("host2", timeseries[2].labels[1].value);
1172        assert_eq!(
1173            vec![
1174                Sample {
1175                    value: 1.0,
1176                    timestamp: 1000,
1177                },
1178                Sample {
1179                    value: 4.0,
1180                    timestamp: 4000,
1181                },
1182            ],
1183            timeseries[2].samples
1184        );
1185    }
1186
1187    #[test]
1188    fn test_recordbatch_to_timeseries_groups_non_contiguous_series() {
1189        let schema = Arc::new(Schema::new(vec![
1190            ColumnSchema::new(
1191                greptime_timestamp(),
1192                ConcreteDataType::timestamp_millisecond_datatype(),
1193                true,
1194            ),
1195            ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1196            ColumnSchema::new("instance", ConcreteDataType::string_datatype(), true),
1197        ]));
1198        let recordbatch = RecordBatch::new(
1199            schema,
1200            vec![
1201                Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as _,
1202                Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as _,
1203                Arc::new(StringVector::from(vec!["host2", "host1", "host2"])) as _,
1204            ],
1205        )
1206        .unwrap();
1207
1208        let timeseries = recordbatch_to_timeseries("metric1", recordbatch).unwrap();
1209
1210        // The result stays sorted by labels as it was with the previous BTreeMap.
1211        assert_eq!("host1", timeseries[0].labels[1].value);
1212        assert_eq!("host2", timeseries[1].labels[1].value);
1213        assert_eq!(
1214            vec![
1215                Sample {
1216                    value: 1.0,
1217                    timestamp: 1000,
1218                },
1219                Sample {
1220                    value: 3.0,
1221                    timestamp: 3000,
1222                },
1223            ],
1224            timeseries[1].samples
1225        );
1226    }
1227
1228    #[test]
1229    fn test_recordbatch_to_timeseries_arrow_label_types_and_nulls() {
1230        let schema = Arc::new(Schema::new(vec![
1231            ColumnSchema::new(
1232                greptime_timestamp(),
1233                ConcreteDataType::timestamp_millisecond_datatype(),
1234                true,
1235            ),
1236            ColumnSchema::new(greptime_value(), ConcreteDataType::float64_datatype(), true),
1237            ColumnSchema::new("instance", ConcreteDataType::large_string_datatype(), true),
1238            ColumnSchema::new("zone", ConcreteDataType::utf8_view_datatype(), true),
1239            ColumnSchema::new("shard", ConcreteDataType::int32_datatype(), true),
1240        ]));
1241        let recordbatch = RecordBatch::new(
1242            schema,
1243            vec![
1244                Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as _,
1245                Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as _,
1246                Arc::new(StringVector::from(LargeStringArray::from(vec![
1247                    "host2", "host1", "host2",
1248                ]))) as _,
1249                Arc::new(StringVector::from(StringViewArray::from(vec![
1250                    Some("west"),
1251                    None,
1252                    Some("west"),
1253                ]))) as _,
1254                Arc::new(Int32Vector::from_vec(vec![2, 1, 2])) as _,
1255            ],
1256        )
1257        .unwrap();
1258
1259        let timeseries = recordbatch_to_timeseries("metric1", recordbatch).unwrap();
1260
1261        assert_eq!(2, timeseries.len());
1262        assert_eq!(
1263            vec![
1264                new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
1265                new_label("instance".to_string(), "host1".to_string()),
1266                new_label("shard".to_string(), "1".to_string()),
1267            ],
1268            timeseries[0].labels
1269        );
1270        assert_eq!(
1271            vec![
1272                new_label(METRIC_NAME_LABEL.to_string(), "metric1".to_string()),
1273                new_label("instance".to_string(), "host2".to_string()),
1274                new_label("zone".to_string(), "west".to_string()),
1275                new_label("shard".to_string(), "2".to_string()),
1276            ],
1277            timeseries[1].labels
1278        );
1279        assert_eq!(
1280            vec![
1281                Sample {
1282                    value: 1.0,
1283                    timestamp: 1000,
1284                },
1285                Sample {
1286                    value: 3.0,
1287                    timestamp: 3000,
1288                },
1289            ],
1290            timeseries[1].samples
1291        );
1292    }
1293}