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