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