Skip to main content

servers/http/
prometheus.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//! prom supply the prometheus HTTP API Server compliance
16
17use std::borrow::Borrow;
18use std::collections::{BTreeMap, HashMap, HashSet};
19use std::hash::{Hash, Hasher};
20use std::sync::Arc;
21
22use arrow::array::{Array, AsArray};
23use arrow::datatypes::{
24    Date32Type, Date64Type, Decimal128Type, Float32Type, Float64Type, Int8Type, Int16Type,
25    Int32Type, Int64Type, IntervalDayTimeType, IntervalMonthDayNanoType, IntervalYearMonthType,
26    UInt8Type, UInt16Type, UInt32Type, UInt64Type,
27};
28use arrow_schema::{DataType, IntervalUnit};
29use auth::{PermissionTableTarget, PermissionTableTargets};
30use axum::extract::{Path, Query, State};
31use axum::{Extension, Form};
32use catalog::CatalogManagerRef;
33use common_catalog::parse_catalog_and_schema_from_db_string;
34use common_decimal::Decimal128;
35use common_error::ext::ErrorExt;
36use common_error::status_code::StatusCode;
37use common_query::native_histogram::is_native_histogram_value_type;
38use common_query::{Output, OutputData};
39use common_recordbatch::{RecordBatch, RecordBatches};
40use common_telemetry::{debug, tracing};
41use common_time::util::{current_time_rfc3339, yesterday_rfc3339};
42use common_time::{Date, IntervalDayTime, IntervalMonthDayNano, IntervalYearMonth};
43use common_version::OwnedBuildInfo;
44use datafusion_common::ScalarValue;
45use datatypes::prelude::ConcreteDataType;
46use datatypes::schema::{ColumnSchema, SchemaRef};
47use datatypes::types::jsonb_to_string;
48use futures::future::join_all;
49use futures::{StreamExt, TryStreamExt};
50use itertools::Itertools;
51use promql_parser::label::{METRIC_NAME, MatchOp, Matcher, Matchers};
52use promql_parser::parser::token::{self};
53use promql_parser::parser::value::ValueType;
54use promql_parser::parser::{
55    AggregateExpr, BinaryExpr, Call, Expr as PromqlExpr, LabelModifier, MatrixSelector, ParenExpr,
56    SubqueryExpr, UnaryExpr, VectorSelector,
57};
58use query::parser::{DEFAULT_LOOKBACK_STRING, PromQuery, QueryStatement};
59use serde::de::{self, MapAccess, Visitor};
60use serde::{Deserialize, Serialize};
61use serde_json::Value;
62use session::context::{QueryContext, QueryContextRef};
63use snafu::{Location, OptionExt, ResultExt};
64use store_api::metric_engine_consts::{
65    DATA_SCHEMA_TABLE_ID_COLUMN_NAME, DATA_SCHEMA_TSID_COLUMN_NAME, LOGICAL_TABLE_METADATA_KEY,
66};
67use table::TableRef;
68use table::metadata::TableInfo;
69use table::requests::{SEMANTIC_METRIC_TEMPORALITY, SEMANTIC_METRIC_TYPE, SEMANTIC_METRIC_UNIT};
70
71pub use super::result::prometheus_resp::PrometheusJsonResponse;
72use crate::error::{
73    CollectRecordbatchSnafu, ConvertScalarValueSnafu, DataFusionSnafu, Error, InvalidQuerySnafu,
74    NotSupportedSnafu, Result, TableNotFoundSnafu, UnexpectedResultSnafu,
75};
76use crate::http::header::collect_plan_metrics;
77use crate::otlp::metrics::ucum_to_openmetrics_unit;
78use crate::prom_store::{FIELD_NAME_LABEL, METRIC_NAME_LABEL, is_database_selection_label};
79use crate::prometheus_handler::{
80    ParsedPromQuery, PrometheusHandlerRef, resolve_schema_from_matchers,
81};
82
83/// For [ValueType::Vector] result type
84#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
85#[serde(deny_unknown_fields)]
86pub struct PromSeriesVector {
87    pub metric: BTreeMap<String, String>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub value: Option<(f64, String)>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub histogram: Option<(f64, PromNativeHistogram)>,
92}
93
94/// For [ValueType::Matrix] result type
95#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
96#[serde(deny_unknown_fields)]
97pub struct PromSeriesMatrix {
98    pub metric: BTreeMap<String, String>,
99    #[serde(skip_serializing_if = "Vec::is_empty", default)]
100    pub values: Vec<(f64, String)>,
101    #[serde(skip_serializing_if = "Vec::is_empty", default)]
102    pub histograms: Vec<(f64, PromNativeHistogram)>,
103}
104
105/// A native histogram sample in the Prometheus HTTP API JSON format.
106#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
107pub struct PromNativeHistogram {
108    /// Total number of observations, encoded as a string.
109    pub count: String,
110    /// Sum of all observations, encoded as a string.
111    pub sum: String,
112    /// Populated buckets as `(boundary rule, lower bound, upper bound, count)`.
113    ///
114    /// Boundary rules are `0` for lower-open/upper-closed, `1` for
115    /// lower-closed/upper-open, `2` for both bounds open, and `3` for both
116    /// bounds closed. Bounds and counts are encoded as strings.
117    pub buckets: Vec<(u8, String, String, String)>,
118}
119
120/// Variants corresponding to [ValueType]
121#[derive(Debug, Serialize, Deserialize, PartialEq)]
122#[serde(untagged)]
123pub enum PromQueryResult {
124    Matrix(Vec<PromSeriesMatrix>),
125    Vector(Vec<PromSeriesVector>),
126    Scalar(#[serde(skip_serializing_if = "Option::is_none")] Option<(f64, String)>),
127    String(#[serde(skip_serializing_if = "Option::is_none")] Option<(f64, String)>),
128}
129
130impl Default for PromQueryResult {
131    fn default() -> Self {
132        PromQueryResult::Matrix(Default::default())
133    }
134}
135
136#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
137pub struct PromData {
138    #[serde(rename = "resultType")]
139    pub result_type: String,
140    pub result: PromQueryResult,
141}
142
143/// Metadata for a Prometheus metric family.
144#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
145pub struct PromMetadata {
146    #[serde(rename = "type")]
147    pub metric_type: String,
148    pub unit: String,
149    pub help: String,
150}
151
152/// A "holder" for the reference([Arc]) to a column name,
153/// to help avoiding cloning [String]s when used as a [HashMap] key.
154#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
155pub struct Column(Arc<String>);
156
157impl From<&str> for Column {
158    fn from(s: &str) -> Self {
159        Self(Arc::new(s.to_string()))
160    }
161}
162
163#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
164#[serde(untagged)]
165pub enum PrometheusResponse {
166    PromData(PromData),
167    Labels(Vec<String>),
168    Series(Vec<HashMap<Column, String>>),
169    Metadata(BTreeMap<String, Vec<PromMetadata>>),
170    LabelValues(Vec<String>),
171    FormatQuery(String),
172    BuildInfo(OwnedBuildInfo),
173    #[serde(skip_deserializing)]
174    ParseResult(promql_parser::parser::Expr),
175    #[default]
176    None,
177}
178
179impl PrometheusResponse {
180    /// Append the other [`PrometheusResponse]`.
181    /// # NOTE
182    ///   Only append matrix and vector results, otherwise just ignore the other response.
183    pub(super) fn append(&mut self, other: PrometheusResponse) {
184        match (self, other) {
185            (
186                PrometheusResponse::PromData(PromData {
187                    result: PromQueryResult::Matrix(lhs),
188                    ..
189                }),
190                PrometheusResponse::PromData(PromData {
191                    result: PromQueryResult::Matrix(rhs),
192                    ..
193                }),
194            ) => {
195                lhs.extend(rhs);
196            }
197
198            (
199                PrometheusResponse::PromData(PromData {
200                    result: PromQueryResult::Vector(lhs),
201                    ..
202                }),
203                PrometheusResponse::PromData(PromData {
204                    result: PromQueryResult::Vector(rhs),
205                    ..
206                }),
207            ) => {
208                lhs.extend(rhs);
209            }
210            _ => {
211                // TODO(dennis): process other cases?
212            }
213        }
214    }
215
216    pub fn is_none(&self) -> bool {
217        matches!(self, PrometheusResponse::None)
218    }
219}
220
221#[derive(Debug, Default, Serialize, Deserialize)]
222pub struct FormatQuery {
223    query: Option<String>,
224}
225
226/// Query parameters for the Prometheus metric metadata endpoint.
227#[derive(Debug, Default, Serialize, Deserialize)]
228pub struct MetadataQuery {
229    db: Option<String>,
230    limit: Option<usize>,
231    metric: Option<String>,
232}
233
234#[axum_macros::debug_handler]
235#[tracing::instrument(
236    skip_all,
237    fields(protocol = "prometheus", request_type = "format_query")
238)]
239pub async fn format_query(
240    State(_handler): State<PrometheusHandlerRef>,
241    Query(params): Query<InstantQuery>,
242    Extension(_query_ctx): Extension<QueryContext>,
243    Form(form_params): Form<InstantQuery>,
244) -> PrometheusJsonResponse {
245    let query = params.query.or(form_params.query).unwrap_or_default();
246    match promql_parser::parser::parse(&query) {
247        Ok(expr) => {
248            let pretty = expr.prettify();
249            PrometheusJsonResponse::success(PrometheusResponse::FormatQuery(pretty))
250        }
251        Err(reason) => {
252            let err = InvalidQuerySnafu { reason }.build();
253            PrometheusJsonResponse::error(err.status_code(), err.output_msg())
254        }
255    }
256}
257
258#[derive(Debug, Default, Serialize, Deserialize)]
259pub struct BuildInfoQuery {}
260
261#[axum_macros::debug_handler]
262#[tracing::instrument(
263    skip_all,
264    fields(protocol = "prometheus", request_type = "build_info_query")
265)]
266pub async fn build_info_query() -> PrometheusJsonResponse {
267    let build_info = common_version::build_info().clone();
268    PrometheusJsonResponse::success(PrometheusResponse::BuildInfo(build_info.into()))
269}
270
271#[axum_macros::debug_handler]
272#[tracing::instrument(
273    skip_all,
274    fields(protocol = "prometheus", request_type = "metadata_query")
275)]
276pub async fn metadata_query(
277    State(handler): State<PrometheusHandlerRef>,
278    Query(params): Query<MetadataQuery>,
279    Extension(mut query_ctx): Extension<QueryContext>,
280) -> PrometheusJsonResponse {
281    let (catalog, schema) = get_catalog_schema(&params.db, &query_ctx);
282    try_update_catalog_schema(&mut query_ctx, &catalog, &schema);
283    let query_ctx = Arc::new(query_ctx);
284
285    if let Err(err) = handler.check_query_permission(&[], &query_ctx).await {
286        return PrometheusJsonResponse::error(err.status_code(), err.to_string());
287    }
288    if let Some(metric) = &params.metric
289        && let Err(err) = handler
290            .check_query_target_permission(
291                current_schema_metric_targets(&query_ctx, std::slice::from_ref(metric)),
292                &query_ctx,
293            )
294            .await
295    {
296        return PrometheusJsonResponse::error(err.status_code(), err.to_string());
297    }
298
299    let mut metadata =
300        match retrieve_metric_metadata(&query_ctx, handler.catalog_manager(), &params).await {
301            Ok(metadata) => metadata,
302            Err(err) => {
303                return PrometheusJsonResponse::error(
304                    StatusCode::InvalidArguments,
305                    err.to_string(),
306                );
307            }
308        };
309    let allowed_metric_names = match handler
310        .filter_metadata_metric_names(
311            metadata.keys().cloned().collect(),
312            query_ctx.current_schema().as_str(),
313            &query_ctx,
314        )
315        .await
316    {
317        Ok(metric_names) => metric_names,
318        Err(err) => {
319            return PrometheusJsonResponse::error(err.status_code(), err.to_string());
320        }
321    }
322    .into_iter()
323    .collect::<HashSet<_>>();
324    metadata.retain(|metric, _| allowed_metric_names.contains(metric));
325    if let Some(limit) = params.limit {
326        metadata = metadata.into_iter().take(limit).collect();
327    }
328
329    PrometheusJsonResponse::success(PrometheusResponse::Metadata(metadata))
330}
331
332#[derive(Debug, Default, Serialize, Deserialize)]
333pub struct InstantQuery {
334    query: Option<String>,
335    lookback: Option<String>,
336    time: Option<String>,
337    timeout: Option<String>,
338    db: Option<String>,
339}
340
341/// Helper macro which try to evaluate the expression and return its results.
342/// If the evaluation fails, return a `PrometheusJsonResponse` early.
343macro_rules! try_call_return_response {
344    (@output_msg $handle: expr, $status_code: expr) => {
345        match $handle {
346            Ok(res) => res,
347            Err(err) => {
348                let msg = err.output_msg();
349                return PrometheusJsonResponse::error($status_code, msg);
350            }
351        }
352    };
353    ($handle: expr, $status_code: expr) => {
354        match $handle {
355            Ok(res) => res,
356            Err(err) => {
357                let msg = err.to_string();
358                return PrometheusJsonResponse::error($status_code, msg);
359            }
360        }
361    };
362    ($handle: expr) => {
363        match $handle {
364            Ok(res) => res,
365            Err(err) => {
366                let status_code = err.status_code();
367                let msg = err.to_string();
368                return PrometheusJsonResponse::error(status_code, msg);
369            }
370        }
371    };
372}
373
374#[axum_macros::debug_handler]
375#[tracing::instrument(
376    skip_all,
377    fields(protocol = "prometheus", request_type = "instant_query")
378)]
379pub async fn instant_query(
380    State(handler): State<PrometheusHandlerRef>,
381    Query(params): Query<InstantQuery>,
382    Extension(mut query_ctx): Extension<QueryContext>,
383    Form(form_params): Form<InstantQuery>,
384) -> PrometheusJsonResponse {
385    // Extract time from query string, or use current server time if not specified.
386    let time = params
387        .time
388        .or(form_params.time)
389        .unwrap_or_else(current_time_rfc3339);
390    let prom_query = PromQuery {
391        query: params.query.or(form_params.query).unwrap_or_default(),
392        start: time.clone(),
393        end: time,
394        step: "1s".to_string(),
395        lookback: params
396            .lookback
397            .or(form_params.lookback)
398            .unwrap_or_else(|| DEFAULT_LOOKBACK_STRING.to_string()),
399        alias: None,
400    };
401
402    // update catalog and schema in query context if necessary
403    if let Some(db) = &params.db {
404        let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
405        try_update_catalog_schema(&mut query_ctx, &catalog, &schema);
406    }
407    let query_ctx = Arc::new(query_ctx);
408    let _timer = crate::metrics::METRIC_HTTP_PROMETHEUS_PROMQL_ELAPSED
409        .with_label_values(&[query_ctx.get_db_string().as_str(), "instant_query"])
410        .start_timer();
411    let prom_query = try_call_return_response!(
412        @output_msg ParsedPromQuery::parse(prom_query, &query_ctx),
413        StatusCode::InvalidArguments
414    );
415    let promql_expr = prom_query.expr();
416
417    let metric_name_discovery =
418        try_call_return_response!(find_metric_name_not_equal_matchers(promql_expr));
419    if let Some(discovery) = metric_name_discovery {
420        debug!("Find metric name matchers: {:?}", discovery.name_matchers);
421
422        try_call_return_response!(handler.check_query_permission(&[], &query_ctx).await);
423        let (static_targets, unresolved_selectors) =
424            try_call_return_response!(static_promql_targets(promql_expr, &query_ctx));
425        try_call_return_response!(
426            handler
427                .check_query_target_permission(static_targets, &query_ctx,)
428                .await
429        );
430        if unresolved_selectors != 1 {
431            try_call_return_response!(
432                handler
433                    .check_query_target_permission(PermissionTableTargets::Unresolved, &query_ctx,)
434                    .await
435            );
436            return do_instant_query(&handler, prom_query, query_ctx).await;
437        }
438
439        let schema = discovery
440            .schema
441            .unwrap_or_else(|| query_ctx.current_schema());
442        let metric_names = try_call_return_response!(
443            handler
444                .query_metric_names(discovery.name_matchers, &schema, &query_ctx)
445                .await
446        );
447
448        debug!("Find metric names: {:?}", metric_names);
449
450        let prom_queries = expand_metric_name_queries(&prom_query, metric_names);
451        try_call_return_response!(
452            handler
453                .check_query_permission_parsed(&prom_queries, &query_ctx)
454                .await
455        );
456
457        if prom_queries.is_empty() {
458            let result_type = promql_expr.value_type();
459
460            return PrometheusJsonResponse::success(PrometheusResponse::PromData(PromData {
461                result_type: result_type.to_string(),
462                ..Default::default()
463            }));
464        }
465
466        let responses = join_all(prom_queries.into_iter().map(|prom_query| {
467            let query_ctx = query_ctx.clone();
468            let handler = handler.clone();
469
470            async move { do_instant_query(&handler, prom_query, query_ctx).await }
471        }))
472        .await;
473
474        responses
475            .into_iter()
476            .reduce(|mut acc, resp| {
477                acc.append_query_response(resp);
478                acc
479            })
480            .unwrap()
481    } else {
482        do_instant_query(&handler, prom_query, query_ctx).await
483    }
484}
485
486/// Executes a single instant query and returns response
487async fn do_instant_query(
488    handler: &PrometheusHandlerRef,
489    prom_query: ParsedPromQuery,
490    query_ctx: QueryContextRef,
491) -> PrometheusJsonResponse {
492    let (metric_name, result_type) = retrieve_metric_name_and_result_type(prom_query.expr());
493    let query_id = query_ctx.remote_query_id().map(str::to_string);
494    let result = handler.do_query_parsed(prom_query, query_ctx).await;
495    PrometheusJsonResponse::from_query_result(result, metric_name, result_type, query_id.as_deref())
496        .await
497}
498
499#[derive(Debug, Default, Serialize, Deserialize)]
500pub struct RangeQuery {
501    query: Option<String>,
502    start: Option<String>,
503    end: Option<String>,
504    step: Option<String>,
505    lookback: Option<String>,
506    timeout: Option<String>,
507    db: Option<String>,
508}
509
510#[axum_macros::debug_handler]
511#[tracing::instrument(
512    skip_all,
513    fields(protocol = "prometheus", request_type = "range_query")
514)]
515pub async fn range_query(
516    State(handler): State<PrometheusHandlerRef>,
517    Query(params): Query<RangeQuery>,
518    Extension(mut query_ctx): Extension<QueryContext>,
519    Form(form_params): Form<RangeQuery>,
520) -> PrometheusJsonResponse {
521    let prom_query = PromQuery {
522        query: params.query.or(form_params.query).unwrap_or_default(),
523        start: params.start.or(form_params.start).unwrap_or_default(),
524        end: params.end.or(form_params.end).unwrap_or_default(),
525        step: params.step.or(form_params.step).unwrap_or_default(),
526        lookback: params
527            .lookback
528            .or(form_params.lookback)
529            .unwrap_or_else(|| DEFAULT_LOOKBACK_STRING.to_string()),
530        alias: None,
531    };
532
533    // update catalog and schema in query context if necessary
534    if let Some(db) = &params.db {
535        let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
536        try_update_catalog_schema(&mut query_ctx, &catalog, &schema);
537    }
538    let query_ctx = Arc::new(query_ctx);
539    let _timer = crate::metrics::METRIC_HTTP_PROMETHEUS_PROMQL_ELAPSED
540        .with_label_values(&[query_ctx.get_db_string().as_str(), "range_query"])
541        .start_timer();
542    let prom_query = try_call_return_response!(
543        @output_msg ParsedPromQuery::parse(prom_query, &query_ctx),
544        StatusCode::InvalidArguments
545    );
546    let promql_expr = prom_query.expr();
547
548    let metric_name_discovery =
549        try_call_return_response!(find_metric_name_not_equal_matchers(promql_expr));
550    if let Some(discovery) = metric_name_discovery {
551        debug!("Find metric name matchers: {:?}", discovery.name_matchers);
552
553        try_call_return_response!(handler.check_query_permission(&[], &query_ctx).await);
554        let (static_targets, unresolved_selectors) =
555            try_call_return_response!(static_promql_targets(promql_expr, &query_ctx));
556        try_call_return_response!(
557            handler
558                .check_query_target_permission(static_targets, &query_ctx,)
559                .await
560        );
561        if unresolved_selectors != 1 {
562            try_call_return_response!(
563                handler
564                    .check_query_target_permission(PermissionTableTargets::Unresolved, &query_ctx,)
565                    .await
566            );
567            return do_range_query(&handler, prom_query, query_ctx).await;
568        }
569
570        let schema = discovery
571            .schema
572            .unwrap_or_else(|| query_ctx.current_schema());
573        let metric_names = try_call_return_response!(
574            handler
575                .query_metric_names(discovery.name_matchers, &schema, &query_ctx)
576                .await
577        );
578
579        debug!("Find metric names: {:?}", metric_names);
580
581        let prom_queries = expand_metric_name_queries(&prom_query, metric_names);
582        try_call_return_response!(
583            handler
584                .check_query_permission_parsed(&prom_queries, &query_ctx)
585                .await
586        );
587
588        if prom_queries.is_empty() {
589            return PrometheusJsonResponse::success(PrometheusResponse::PromData(PromData {
590                result_type: ValueType::Matrix.to_string(),
591                ..Default::default()
592            }));
593        }
594
595        let responses = join_all(prom_queries.into_iter().map(|prom_query| {
596            let query_ctx = query_ctx.clone();
597            let handler = handler.clone();
598
599            async move { do_range_query(&handler, prom_query, query_ctx).await }
600        }))
601        .await;
602
603        // Safety: at least one responses, checked above
604        responses
605            .into_iter()
606            .reduce(|mut acc, resp| {
607                acc.append_query_response(resp);
608                acc
609            })
610            .unwrap()
611    } else {
612        do_range_query(&handler, prom_query, query_ctx).await
613    }
614}
615
616/// Executes a single range query and returns response
617async fn do_range_query(
618    handler: &PrometheusHandlerRef,
619    prom_query: ParsedPromQuery,
620    query_ctx: QueryContextRef,
621) -> PrometheusJsonResponse {
622    let (metric_name, _) = retrieve_metric_name_and_result_type(prom_query.expr());
623    let query_id = query_ctx.remote_query_id().map(str::to_string);
624    let result = handler.do_query_parsed(prom_query, query_ctx).await;
625    PrometheusJsonResponse::from_query_result(
626        result,
627        metric_name,
628        ValueType::Matrix,
629        query_id.as_deref(),
630    )
631    .await
632}
633
634#[derive(Debug, Default, Serialize)]
635struct Matches(Vec<String>);
636
637#[derive(Debug, Default, Serialize, Deserialize)]
638pub struct LabelsQuery {
639    start: Option<String>,
640    end: Option<String>,
641    lookback: Option<String>,
642    #[serde(flatten)]
643    matches: Matches,
644    db: Option<String>,
645}
646
647// Custom Deserialize method to support parsing repeated match[]
648impl<'de> Deserialize<'de> for Matches {
649    fn deserialize<D>(deserializer: D) -> std::result::Result<Matches, D::Error>
650    where
651        D: de::Deserializer<'de>,
652    {
653        struct MatchesVisitor;
654
655        impl<'d> Visitor<'d> for MatchesVisitor {
656            type Value = Vec<String>;
657
658            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
659                formatter.write_str("a string")
660            }
661
662            fn visit_map<M>(self, mut access: M) -> std::result::Result<Self::Value, M::Error>
663            where
664                M: MapAccess<'d>,
665            {
666                let mut matches = Vec::new();
667                while let Some((key, value)) = access.next_entry::<String, String>()? {
668                    if key == "match[]" {
669                        matches.push(value);
670                    }
671                }
672                Ok(matches)
673            }
674        }
675        Ok(Matches(deserializer.deserialize_map(MatchesVisitor)?))
676    }
677}
678
679/// Handles schema errors, transforming a Result into an Option.
680/// - If the input is `Ok(v)`, returns `Some(v)`
681/// - If the input is `Err(err)` and the error status code is `TableNotFound` or
682///   `TableColumnNotFound`, returns `None` (ignoring these specific errors)
683/// - If the input is `Err(err)` with any other error code, directly returns a
684///   `PrometheusJsonResponse::error`.
685macro_rules! handle_schema_err {
686    ($result:expr) => {
687        match $result {
688            Ok(v) => Some(v),
689            Err(err) => {
690                if err.status_code() == StatusCode::TableNotFound
691                    || err.status_code() == StatusCode::TableColumnNotFound
692                {
693                    // Prometheus won't report error if querying nonexist label and metric
694                    None
695                } else {
696                    return PrometheusJsonResponse::error(err.status_code(), err.output_msg());
697                }
698            }
699        }
700    };
701}
702
703#[axum_macros::debug_handler]
704#[tracing::instrument(
705    skip_all,
706    fields(protocol = "prometheus", request_type = "labels_query")
707)]
708pub async fn labels_query(
709    State(handler): State<PrometheusHandlerRef>,
710    Query(params): Query<LabelsQuery>,
711    Extension(mut query_ctx): Extension<QueryContext>,
712    Form(form_params): Form<LabelsQuery>,
713) -> PrometheusJsonResponse {
714    let (catalog, schema) = get_catalog_schema(&params.db, &query_ctx);
715    try_update_catalog_schema(&mut query_ctx, &catalog, &schema);
716    let query_ctx = Arc::new(query_ctx);
717
718    let mut queries = params.matches.0;
719    if queries.is_empty() {
720        queries = form_params.matches.0;
721    }
722
723    let _timer = crate::metrics::METRIC_HTTP_PROMETHEUS_PROMQL_ELAPSED
724        .with_label_values(&[query_ctx.get_db_string().as_str(), "labels_query"])
725        .start_timer();
726
727    let prom_queries = if queries.is_empty() {
728        try_call_return_response!(handler.check_query_permission(&[], &query_ctx).await);
729        Vec::new()
730    } else {
731        let start = params
732            .start
733            .or(form_params.start)
734            .unwrap_or_else(yesterday_rfc3339);
735        let end = params
736            .end
737            .or(form_params.end)
738            .unwrap_or_else(current_time_rfc3339);
739        let lookback = params
740            .lookback
741            .or(form_params.lookback)
742            .unwrap_or_else(|| DEFAULT_LOOKBACK_STRING.to_string());
743        let prom_queries = queries
744            .into_iter()
745            .map(|query| PromQuery {
746                query,
747                start: start.clone(),
748                end: end.clone(),
749                step: DEFAULT_LOOKBACK_STRING.to_string(),
750                lookback: lookback.clone(),
751                alias: None,
752            })
753            .collect::<Vec<_>>();
754        let prom_queries = try_call_return_response!(
755            prom_queries
756                .into_iter()
757                .map(|query| ParsedPromQuery::parse(query, &query_ctx))
758                .collect::<Result<Vec<_>>>()
759        );
760        try_call_return_response!(
761            handler
762                .check_query_permission_parsed(&prom_queries, &query_ctx)
763                .await
764        );
765        prom_queries
766    };
767
768    // Fetch all columns if no query matcher is provided
769    if prom_queries.is_empty() {
770        let (mut labels, table_names) =
771            match get_all_column_names(&catalog, &schema, &handler.catalog_manager()).await {
772                Ok(result) => result,
773                Err(e) => return PrometheusJsonResponse::error(e.status_code(), e.output_msg()),
774            };
775        try_call_return_response!(
776            handler
777                .check_query_target_permission(
778                    current_schema_metric_targets(&query_ctx, &table_names),
779                    &query_ctx,
780                )
781                .await
782        );
783        let _ = labels.insert(METRIC_NAME.to_string());
784        let mut labels_vec = labels.into_iter().collect::<Vec<_>>();
785        labels_vec.sort_unstable();
786        return PrometheusJsonResponse::success(PrometheusResponse::Labels(labels_vec));
787    }
788
789    // Fetch tag columns only from the tables checked above.
790    let mut labels = match resolved_promql_targets(&prom_queries, &query_ctx) {
791        Some(targets) => {
792            match get_target_column_names(&targets, &handler.catalog_manager(), &query_ctx).await {
793                Ok(labels) => labels,
794                Err(e) => return PrometheusJsonResponse::error(e.status_code(), e.output_msg()),
795            }
796        }
797        None => match get_all_column_names(&catalog, &schema, &handler.catalog_manager()).await {
798            Ok((labels, _)) => labels,
799            Err(e) => return PrometheusJsonResponse::error(e.status_code(), e.output_msg()),
800        },
801    };
802    let _ = labels.insert(METRIC_NAME.to_string());
803
804    let mut fetched_labels = HashSet::new();
805    let _ = fetched_labels.insert(METRIC_NAME.to_string());
806
807    let mut merge_map = HashMap::new();
808    for prom_query in prom_queries {
809        let result = handler.do_query_parsed(prom_query, query_ctx.clone()).await;
810        handle_schema_err!(
811            retrieve_labels_name_from_query_result(result, &mut fetched_labels, &mut merge_map)
812                .await
813        );
814    }
815
816    // intersect `fetched_labels` with `labels` to filter out non-tag columns
817    fetched_labels.retain(|l| labels.contains(l));
818    let mut sorted_labels: Vec<String> = fetched_labels.into_iter().collect();
819    sorted_labels.sort();
820    let merge_map = merge_map
821        .into_iter()
822        .map(|(k, v)| (k, Value::from(v)))
823        .collect();
824    let mut resp = PrometheusJsonResponse::success(PrometheusResponse::Labels(sorted_labels));
825    resp.resp_metrics = merge_map;
826    resp
827}
828
829/// Get all tag column name of the given schema
830async fn get_all_column_names(
831    catalog: &str,
832    schema: &str,
833    manager: &CatalogManagerRef,
834) -> std::result::Result<(HashSet<String>, Vec<String>), catalog::error::Error> {
835    let mut labels = HashSet::new();
836    let mut table_names = Vec::new();
837    let mut tables = manager.tables(catalog, schema, None);
838    while let Some(table) = tables.try_next().await? {
839        if is_internal_physical_metric_table(&table) {
840            continue;
841        }
842        table_names.push(table.table_info().name.clone());
843        extend_tag_column_names(&mut labels, &table);
844    }
845
846    Ok((labels, table_names))
847}
848
849async fn get_target_column_names(
850    targets: &[PermissionTableTarget],
851    manager: &CatalogManagerRef,
852    query_ctx: &QueryContext,
853) -> std::result::Result<HashSet<String>, catalog::error::Error> {
854    let mut labels = HashSet::new();
855    for target in targets {
856        if let Some(table) = manager
857            .table(
858                &target.catalog,
859                &target.schema,
860                &target.table,
861                Some(query_ctx),
862            )
863            .await?
864            && !is_internal_physical_metric_table(&table)
865        {
866            extend_tag_column_names(&mut labels, &table);
867        }
868    }
869    Ok(labels)
870}
871
872fn extend_tag_column_names(labels: &mut HashSet<String>, table: &TableRef) {
873    labels.extend(table.primary_key_columns().filter_map(|column| {
874        (column.name != DATA_SCHEMA_TABLE_ID_COLUMN_NAME
875            && column.name != DATA_SCHEMA_TSID_COLUMN_NAME)
876            .then_some(column.name)
877    }));
878}
879
880async fn retrieve_series_from_query_result(
881    result: Result<Output>,
882    series: &mut Vec<HashMap<Column, String>>,
883    query_ctx: &QueryContext,
884    target: &PermissionTableTarget,
885    manager: &CatalogManagerRef,
886    metrics: &mut HashMap<String, u64>,
887) -> Result<()> {
888    let result = result?;
889
890    // fetch tag list
891    let table = manager
892        .table(
893            &target.catalog,
894            &target.schema,
895            &target.table,
896            Some(query_ctx),
897        )
898        .await?
899        .with_context(|| TableNotFoundSnafu {
900            catalog: &target.catalog,
901            schema: &target.schema,
902            table: &target.table,
903        })?;
904    let tag_columns = table
905        .primary_key_columns()
906        .map(|c| c.name)
907        .collect::<HashSet<_>>();
908
909    match result.data {
910        OutputData::RecordBatches(batches) => {
911            record_batches_to_series(batches, series, &target.table, &tag_columns)
912        }
913        OutputData::Stream(stream) => {
914            let batches = RecordBatches::try_collect(stream)
915                .await
916                .context(CollectRecordbatchSnafu)?;
917            record_batches_to_series(batches, series, &target.table, &tag_columns)
918        }
919        OutputData::AffectedRows(_) => Err(Error::UnexpectedResult {
920            reason: "expected data result, but got affected rows".to_string(),
921            location: Location::default(),
922        }),
923    }?;
924
925    if let Some(ref plan) = result.meta.plan {
926        collect_plan_metrics(plan, &mut [metrics]);
927    }
928    Ok(())
929}
930
931/// Retrieve labels name from query result
932async fn retrieve_labels_name_from_query_result(
933    result: Result<Output>,
934    labels: &mut HashSet<String>,
935    metrics: &mut HashMap<String, u64>,
936) -> Result<()> {
937    let result = result?;
938    match result.data {
939        OutputData::RecordBatches(batches) => record_batches_to_labels_name(batches, labels),
940        OutputData::Stream(stream) => {
941            let batches = RecordBatches::try_collect(stream)
942                .await
943                .context(CollectRecordbatchSnafu)?;
944            record_batches_to_labels_name(batches, labels)
945        }
946        OutputData::AffectedRows(_) => UnexpectedResultSnafu {
947            reason: "expected data result, but got affected rows".to_string(),
948        }
949        .fail(),
950    }?;
951    if let Some(ref plan) = result.meta.plan {
952        collect_plan_metrics(plan, &mut [metrics]);
953    }
954    Ok(())
955}
956
957fn record_batches_to_series(
958    batches: RecordBatches,
959    series: &mut Vec<HashMap<Column, String>>,
960    table_name: &str,
961    tag_columns: &HashSet<String>,
962) -> Result<()> {
963    for batch in batches.iter() {
964        // project record batch to only contains tag columns
965        let projection = batch
966            .schema
967            .column_schemas()
968            .iter()
969            .enumerate()
970            .filter_map(|(idx, col)| {
971                if tag_columns.contains(&col.name) {
972                    Some(idx)
973                } else {
974                    None
975                }
976            })
977            .collect::<Vec<_>>();
978        let batch = batch
979            .try_project(&projection)
980            .context(CollectRecordbatchSnafu)?;
981
982        let mut writer = RowWriter::new(&batch.schema, table_name);
983        writer.write(batch, series)?;
984    }
985    Ok(())
986}
987
988/// Writer from a row in the record batch to a Prometheus time series:
989///
990/// `{__name__="<metric name>", <label name>="<label value>", ...}`
991///
992/// The metrics name is the table name; label names are the column names and
993/// the label values are the corresponding row values (all are converted to strings).
994struct RowWriter {
995    /// The template that is to produce a Prometheus time series. It is pre-filled with metrics name
996    /// and label names, waiting to be filled by row values afterward.
997    template: HashMap<Column, Option<String>>,
998    /// The current filling row.
999    current: Option<HashMap<Column, Option<String>>>,
1000}
1001
1002impl RowWriter {
1003    fn new(schema: &SchemaRef, table: &str) -> Self {
1004        let mut template = schema
1005            .column_schemas()
1006            .iter()
1007            .map(|x| (x.name.as_str().into(), None))
1008            .collect::<HashMap<Column, Option<String>>>();
1009        template.insert("__name__".into(), Some(table.to_string()));
1010        Self {
1011            template,
1012            current: None,
1013        }
1014    }
1015
1016    fn insert(&mut self, column: ColumnRef, value: impl ToString) {
1017        let current = self.current.get_or_insert_with(|| self.template.clone());
1018        match current.get_mut(&column as &dyn AsColumnRef) {
1019            Some(x) => {
1020                let _ = x.insert(value.to_string());
1021            }
1022            None => {
1023                let _ = current.insert(column.0.into(), Some(value.to_string()));
1024            }
1025        }
1026    }
1027
1028    fn insert_bytes(&mut self, column_schema: &ColumnSchema, bytes: &[u8]) -> Result<()> {
1029        let column_name = column_schema.name.as_str().into();
1030
1031        if column_schema.data_type.is_json() {
1032            let s = jsonb_to_string(bytes).context(ConvertScalarValueSnafu)?;
1033            self.insert(column_name, s);
1034        } else {
1035            let hex = bytes
1036                .iter()
1037                .map(|b| format!("{b:02x}"))
1038                .collect::<Vec<String>>()
1039                .join("");
1040            self.insert(column_name, hex);
1041        }
1042        Ok(())
1043    }
1044
1045    fn finish(&mut self) -> HashMap<Column, String> {
1046        let Some(current) = self.current.take() else {
1047            return HashMap::new();
1048        };
1049        current
1050            .into_iter()
1051            .filter_map(|(k, v)| v.map(|v| (k, v)))
1052            .collect()
1053    }
1054
1055    fn write(
1056        &mut self,
1057        record_batch: RecordBatch,
1058        series: &mut Vec<HashMap<Column, String>>,
1059    ) -> Result<()> {
1060        let schema = record_batch.schema.clone();
1061        let record_batch = record_batch.into_df_record_batch();
1062        for i in 0..record_batch.num_rows() {
1063            for (j, array) in record_batch.columns().iter().enumerate() {
1064                let column = schema.column_name_by_index(j).into();
1065
1066                if array.is_null(i) {
1067                    self.insert(column, "Null");
1068                    continue;
1069                }
1070
1071                match array.data_type() {
1072                    DataType::Null => {
1073                        self.insert(column, "Null");
1074                    }
1075                    DataType::Boolean => {
1076                        let array = array.as_boolean();
1077                        let v = array.value(i);
1078                        self.insert(column, v);
1079                    }
1080                    DataType::UInt8 => {
1081                        let array = array.as_primitive::<UInt8Type>();
1082                        let v = array.value(i);
1083                        self.insert(column, v);
1084                    }
1085                    DataType::UInt16 => {
1086                        let array = array.as_primitive::<UInt16Type>();
1087                        let v = array.value(i);
1088                        self.insert(column, v);
1089                    }
1090                    DataType::UInt32 => {
1091                        let array = array.as_primitive::<UInt32Type>();
1092                        let v = array.value(i);
1093                        self.insert(column, v);
1094                    }
1095                    DataType::UInt64 => {
1096                        let array = array.as_primitive::<UInt64Type>();
1097                        let v = array.value(i);
1098                        self.insert(column, v);
1099                    }
1100                    DataType::Int8 => {
1101                        let array = array.as_primitive::<Int8Type>();
1102                        let v = array.value(i);
1103                        self.insert(column, v);
1104                    }
1105                    DataType::Int16 => {
1106                        let array = array.as_primitive::<Int16Type>();
1107                        let v = array.value(i);
1108                        self.insert(column, v);
1109                    }
1110                    DataType::Int32 => {
1111                        let array = array.as_primitive::<Int32Type>();
1112                        let v = array.value(i);
1113                        self.insert(column, v);
1114                    }
1115                    DataType::Int64 => {
1116                        let array = array.as_primitive::<Int64Type>();
1117                        let v = array.value(i);
1118                        self.insert(column, v);
1119                    }
1120                    DataType::Float32 => {
1121                        let array = array.as_primitive::<Float32Type>();
1122                        let v = array.value(i);
1123                        self.insert(column, v);
1124                    }
1125                    DataType::Float64 => {
1126                        let array = array.as_primitive::<Float64Type>();
1127                        let v = array.value(i);
1128                        self.insert(column, v);
1129                    }
1130                    DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View => {
1131                        let v = datatypes::arrow_array::string_array_value(array, i);
1132                        self.insert(column, v);
1133                    }
1134                    DataType::Binary | DataType::LargeBinary | DataType::BinaryView => {
1135                        let v = datatypes::arrow_array::binary_array_value(array, i);
1136                        let column_schema = &schema.column_schemas()[j];
1137                        self.insert_bytes(column_schema, v)?;
1138                    }
1139                    DataType::Date32 => {
1140                        let array = array.as_primitive::<Date32Type>();
1141                        let v = Date::new(array.value(i));
1142                        self.insert(column, v);
1143                    }
1144                    DataType::Date64 => {
1145                        let array = array.as_primitive::<Date64Type>();
1146                        // `Date64` values are milliseconds representation of `Date32` values,
1147                        // according to its specification. So we convert the `Date64` value here to
1148                        // the `Date32` value to process them unified.
1149                        let v = Date::new((array.value(i) / 86_400_000) as i32);
1150                        self.insert(column, v);
1151                    }
1152                    DataType::Timestamp(_, _) => {
1153                        let v = datatypes::arrow_array::timestamp_array_value(array, i);
1154                        self.insert(column, v.to_iso8601_string());
1155                    }
1156                    DataType::Time32(_) | DataType::Time64(_) => {
1157                        let v = datatypes::arrow_array::time_array_value(array, i);
1158                        self.insert(column, v.to_iso8601_string());
1159                    }
1160                    DataType::Interval(interval_unit) => match interval_unit {
1161                        IntervalUnit::YearMonth => {
1162                            let array = array.as_primitive::<IntervalYearMonthType>();
1163                            let v: IntervalYearMonth = array.value(i).into();
1164                            self.insert(column, v.to_iso8601_string());
1165                        }
1166                        IntervalUnit::DayTime => {
1167                            let array = array.as_primitive::<IntervalDayTimeType>();
1168                            let v: IntervalDayTime = array.value(i).into();
1169                            self.insert(column, v.to_iso8601_string());
1170                        }
1171                        IntervalUnit::MonthDayNano => {
1172                            let array = array.as_primitive::<IntervalMonthDayNanoType>();
1173                            let v: IntervalMonthDayNano = array.value(i).into();
1174                            self.insert(column, v.to_iso8601_string());
1175                        }
1176                    },
1177                    DataType::Duration(_) => {
1178                        let d = datatypes::arrow_array::duration_array_value(array, i);
1179                        self.insert(column, d);
1180                    }
1181                    DataType::List(_) => {
1182                        let v = ScalarValue::try_from_array(array, i).context(DataFusionSnafu)?;
1183                        self.insert(column, v);
1184                    }
1185                    DataType::Struct(_) => {
1186                        let v = ScalarValue::try_from_array(array, i).context(DataFusionSnafu)?;
1187                        self.insert(column, v);
1188                    }
1189                    DataType::Decimal128(precision, scale) => {
1190                        let array = array.as_primitive::<Decimal128Type>();
1191                        let v = Decimal128::new(array.value(i), *precision, *scale);
1192                        self.insert(column, v);
1193                    }
1194                    _ => {
1195                        return NotSupportedSnafu {
1196                            feat: format!("convert {} to http value", array.data_type()),
1197                        }
1198                        .fail();
1199                    }
1200                }
1201            }
1202
1203            series.push(self.finish())
1204        }
1205        Ok(())
1206    }
1207}
1208
1209#[derive(Debug, Clone, Copy, PartialEq)]
1210struct ColumnRef<'a>(&'a str);
1211
1212impl<'a> From<&'a str> for ColumnRef<'a> {
1213    fn from(s: &'a str) -> Self {
1214        Self(s)
1215    }
1216}
1217
1218trait AsColumnRef {
1219    fn as_ref(&self) -> ColumnRef<'_>;
1220}
1221
1222impl AsColumnRef for Column {
1223    fn as_ref(&self) -> ColumnRef<'_> {
1224        self.0.as_str().into()
1225    }
1226}
1227
1228impl AsColumnRef for ColumnRef<'_> {
1229    fn as_ref(&self) -> ColumnRef<'_> {
1230        *self
1231    }
1232}
1233
1234impl<'a> PartialEq for dyn AsColumnRef + 'a {
1235    fn eq(&self, other: &Self) -> bool {
1236        self.as_ref() == other.as_ref()
1237    }
1238}
1239
1240impl<'a> Eq for dyn AsColumnRef + 'a {}
1241
1242impl<'a> Hash for dyn AsColumnRef + 'a {
1243    fn hash<H: Hasher>(&self, state: &mut H) {
1244        self.as_ref().0.hash(state);
1245    }
1246}
1247
1248impl<'a> Borrow<dyn AsColumnRef + 'a> for Column {
1249    fn borrow(&self) -> &(dyn AsColumnRef + 'a) {
1250        self
1251    }
1252}
1253
1254/// Retrieve labels name from record batches
1255fn record_batches_to_labels_name(
1256    batches: RecordBatches,
1257    labels: &mut HashSet<String>,
1258) -> Result<()> {
1259    let mut column_indices = Vec::new();
1260    let mut value_column_indices = Vec::new();
1261    for (i, column) in batches.schema().column_schemas().iter().enumerate() {
1262        if is_prometheus_value_column(&column.data_type) {
1263            value_column_indices.push(i);
1264        }
1265        column_indices.push(i);
1266    }
1267
1268    if value_column_indices.is_empty() {
1269        return Err(Error::Internal {
1270            err_msg: "no value column found".to_string(),
1271        });
1272    }
1273
1274    for batch in batches.iter() {
1275        let names = column_indices
1276            .iter()
1277            .map(|c| batches.schema().column_name_by_index(*c).to_string())
1278            .collect::<Vec<_>>();
1279
1280        let value_columns = value_column_indices
1281            .iter()
1282            .map(|i| batch.column(*i))
1283            .collect::<Vec<_>>();
1284
1285        for row_index in 0..batch.num_rows() {
1286            // if all value columns are null, skip this row
1287            if value_columns.iter().all(|c| c.is_null(row_index)) {
1288                continue;
1289            }
1290
1291            // if a value is not null, record the tag name and return
1292            names.iter().for_each(|name| {
1293                let _ = labels.insert(name.clone());
1294            });
1295            return Ok(());
1296        }
1297    }
1298    Ok(())
1299}
1300
1301fn is_prometheus_value_column(data_type: &ConcreteDataType) -> bool {
1302    matches!(data_type, ConcreteDataType::Float64(_)) || is_native_histogram_value_type(data_type)
1303}
1304
1305pub(crate) fn retrieve_metric_name_and_result_type(
1306    promql_expr: &PromqlExpr,
1307) -> (Option<String>, ValueType) {
1308    (
1309        promql_expr_to_metric_name(promql_expr),
1310        promql_expr.value_type(),
1311    )
1312}
1313
1314/// Tries to get catalog and schema from an optional db param. And retrieves
1315/// them from [QueryContext] if they don't present.
1316pub(crate) fn get_catalog_schema(db: &Option<String>, ctx: &QueryContext) -> (String, String) {
1317    if let Some(db) = db {
1318        parse_catalog_and_schema_from_db_string(db)
1319    } else {
1320        (
1321            ctx.current_catalog().to_string(),
1322            ctx.current_schema().clone(),
1323        )
1324    }
1325}
1326
1327/// Update catalog and schema in [QueryContext] if necessary.
1328pub(crate) fn try_update_catalog_schema(ctx: &mut QueryContext, catalog: &str, schema: &str) {
1329    if ctx.current_catalog() != catalog || ctx.current_schema() != schema {
1330        ctx.set_current_catalog(catalog);
1331        ctx.set_current_schema(schema);
1332    }
1333}
1334
1335fn current_schema_metric_targets(
1336    query_ctx: &QueryContext,
1337    metric_names: &[String],
1338) -> PermissionTableTargets {
1339    PermissionTableTargets::resolved(
1340        metric_names
1341            .iter()
1342            .map(|metric| {
1343                PermissionTableTarget::new(
1344                    query_ctx.current_catalog(),
1345                    query_ctx.current_schema(),
1346                    metric,
1347                )
1348            })
1349            .collect(),
1350    )
1351}
1352
1353fn is_internal_physical_metric_table(table: &TableRef) -> bool {
1354    table.table_info().is_physical_table()
1355}
1356
1357fn promql_expr_to_metric_name(expr: &PromqlExpr) -> Option<String> {
1358    let mut metric_names = HashSet::new();
1359    collect_metric_names(expr, &mut metric_names);
1360
1361    // Return the metric name only if there's exactly one unique metric name
1362    if metric_names.len() == 1 {
1363        metric_names.into_iter().next()
1364    } else {
1365        None
1366    }
1367}
1368
1369/// Recursively collect all metric names from a PromQL expression
1370fn collect_metric_names(expr: &PromqlExpr, metric_names: &mut HashSet<String>) {
1371    match expr {
1372        PromqlExpr::Aggregate(AggregateExpr { modifier, expr, .. }) => {
1373            match modifier {
1374                Some(LabelModifier::Include(labels))
1375                    if !labels.labels.contains(&METRIC_NAME.to_string()) =>
1376                {
1377                    metric_names.clear();
1378                    return;
1379                }
1380                Some(LabelModifier::Exclude(labels))
1381                    if labels.labels.contains(&METRIC_NAME.to_string()) =>
1382                {
1383                    metric_names.clear();
1384                    return;
1385                }
1386                _ => {}
1387            }
1388            collect_metric_names(expr, metric_names)
1389        }
1390        PromqlExpr::Unary(UnaryExpr { .. }) => metric_names.clear(),
1391        PromqlExpr::Binary(BinaryExpr { lhs, op, .. }) => {
1392            if matches!(
1393                op.id(),
1394                token::T_LAND // INTERSECT
1395                    | token::T_LOR // UNION
1396                    | token::T_LUNLESS // EXCEPT
1397            ) {
1398                collect_metric_names(lhs, metric_names)
1399            } else {
1400                metric_names.clear()
1401            }
1402        }
1403        PromqlExpr::Paren(ParenExpr { expr }) => collect_metric_names(expr, metric_names),
1404        PromqlExpr::Subquery(SubqueryExpr { expr, .. }) => collect_metric_names(expr, metric_names),
1405        PromqlExpr::VectorSelector(VectorSelector { name, matchers, .. }) => {
1406            if let Some(name) = name {
1407                metric_names.insert(name.clone());
1408            } else if let Some(matcher) = matchers.find_matchers(METRIC_NAME).into_iter().next() {
1409                metric_names.insert(matcher.value);
1410            }
1411        }
1412        PromqlExpr::MatrixSelector(MatrixSelector { vs, .. }) => {
1413            let VectorSelector { name, matchers, .. } = vs;
1414            if let Some(name) = name {
1415                metric_names.insert(name.clone());
1416            } else if let Some(matcher) = matchers.find_matchers(METRIC_NAME).into_iter().next() {
1417                metric_names.insert(matcher.value);
1418            }
1419        }
1420        PromqlExpr::Call(Call { args, .. }) => {
1421            args.args
1422                .iter()
1423                .for_each(|e| collect_metric_names(e, metric_names));
1424        }
1425        PromqlExpr::NumberLiteral(_) | PromqlExpr::StringLiteral(_) | PromqlExpr::Extension(_) => {}
1426    }
1427}
1428
1429fn find_metric_name_and_matchers<E, F>(expr: &PromqlExpr, f: F) -> Option<E>
1430where
1431    F: Fn(&Option<String>, &Matchers) -> Option<E> + Clone,
1432{
1433    match expr {
1434        PromqlExpr::Aggregate(AggregateExpr { expr, .. }) => find_metric_name_and_matchers(expr, f),
1435        PromqlExpr::Unary(UnaryExpr { expr }) => find_metric_name_and_matchers(expr, f),
1436        PromqlExpr::Binary(BinaryExpr { lhs, rhs, .. }) => {
1437            find_metric_name_and_matchers(lhs, f.clone()).or(find_metric_name_and_matchers(rhs, f))
1438        }
1439        PromqlExpr::Paren(ParenExpr { expr }) => find_metric_name_and_matchers(expr, f),
1440        PromqlExpr::Subquery(SubqueryExpr { expr, .. }) => find_metric_name_and_matchers(expr, f),
1441        PromqlExpr::NumberLiteral(_) => None,
1442        PromqlExpr::StringLiteral(_) => None,
1443        PromqlExpr::Extension(_) => None,
1444        PromqlExpr::VectorSelector(VectorSelector { name, matchers, .. }) => f(name, matchers),
1445        PromqlExpr::MatrixSelector(MatrixSelector { vs, .. }) => {
1446            let VectorSelector { name, matchers, .. } = vs;
1447
1448            f(name, matchers)
1449        }
1450        PromqlExpr::Call(Call { args, .. }) => args
1451            .args
1452            .iter()
1453            .find_map(|e| find_metric_name_and_matchers(e, f.clone())),
1454    }
1455}
1456
1457struct MetricNameDiscovery {
1458    name_matchers: Vec<Matcher>,
1459    schema: Option<String>,
1460}
1461
1462/// Finds non-equality `__name__` matchers and their unambiguous schema.
1463fn find_metric_name_not_equal_matchers(expr: &PromqlExpr) -> Result<Option<MetricNameDiscovery>> {
1464    if find_metric_name_and_matchers(expr, |name, matchers| {
1465        (name.is_none() && !matchers.or_matchers.is_empty()).then_some(())
1466    })
1467    .is_some()
1468    {
1469        return Ok(None);
1470    }
1471
1472    find_metric_name_and_matchers(expr, |name, matchers| {
1473        // Has name, ignore the matchers
1474        if name.is_some() {
1475            return None;
1476        }
1477
1478        let name_matchers = matchers.find_matchers(METRIC_NAME);
1479        if name_matchers.len() != 1 || name_matchers[0].op == MatchOp::Equal {
1480            return None;
1481        }
1482
1483        Some(
1484            resolve_schema_from_matchers(&matchers.matchers).map(|schema| MetricNameDiscovery {
1485                name_matchers,
1486                schema,
1487            }),
1488        )
1489    })
1490    .transpose()
1491}
1492
1493fn expand_metric_name_queries(
1494    query: &ParsedPromQuery,
1495    metric_names: Vec<String>,
1496) -> Vec<ParsedPromQuery> {
1497    metric_names
1498        .into_iter()
1499        .map(|metric| {
1500            let mut query = query.clone();
1501            query.update_expr(|expr| update_metric_name_matcher(expr, &metric));
1502            query
1503        })
1504        .collect()
1505}
1506
1507fn static_promql_targets(
1508    expr: &PromqlExpr,
1509    query_ctx: &QueryContextRef,
1510) -> Result<(PermissionTableTargets, usize)> {
1511    let mut targets = Vec::new();
1512    let mut unresolved_selectors = 0;
1513    collect_static_promql_targets(expr, query_ctx, &mut targets, &mut unresolved_selectors)?;
1514    Ok((
1515        PermissionTableTargets::resolved(targets),
1516        unresolved_selectors,
1517    ))
1518}
1519
1520fn resolved_promql_targets(
1521    queries: &[ParsedPromQuery],
1522    query_ctx: &QueryContextRef,
1523) -> Option<Vec<PermissionTableTarget>> {
1524    let mut targets = Vec::new();
1525    for query in queries {
1526        let (PermissionTableTargets::Resolved(query_targets), unresolved_selectors) =
1527            static_promql_targets(query.expr(), query_ctx).ok()?
1528        else {
1529            return None;
1530        };
1531        if unresolved_selectors != 0 {
1532            return None;
1533        }
1534        for target in query_targets {
1535            if !targets.contains(&target) {
1536                targets.push(target);
1537            }
1538        }
1539    }
1540    Some(targets)
1541}
1542
1543fn collect_static_promql_targets(
1544    expr: &PromqlExpr,
1545    query_ctx: &QueryContextRef,
1546    targets: &mut Vec<PermissionTableTarget>,
1547    unresolved_selectors: &mut usize,
1548) -> Result<()> {
1549    match expr {
1550        PromqlExpr::Aggregate(AggregateExpr { expr, .. })
1551        | PromqlExpr::Unary(UnaryExpr { expr })
1552        | PromqlExpr::Paren(ParenExpr { expr })
1553        | PromqlExpr::Subquery(SubqueryExpr { expr, .. }) => {
1554            collect_static_promql_targets(expr, query_ctx, targets, unresolved_selectors)?
1555        }
1556        PromqlExpr::Binary(BinaryExpr { lhs, rhs, .. }) => {
1557            collect_static_promql_targets(lhs, query_ctx, targets, unresolved_selectors)?;
1558            collect_static_promql_targets(rhs, query_ctx, targets, unresolved_selectors)?;
1559        }
1560        PromqlExpr::VectorSelector(selector) => {
1561            collect_static_vector_target(selector, query_ctx, targets, unresolved_selectors)?
1562        }
1563        PromqlExpr::MatrixSelector(MatrixSelector { vs, .. }) => {
1564            collect_static_vector_target(vs, query_ctx, targets, unresolved_selectors)?
1565        }
1566        PromqlExpr::Call(Call { args, .. }) => {
1567            for expr in &args.args {
1568                collect_static_promql_targets(expr, query_ctx, targets, unresolved_selectors)?;
1569            }
1570        }
1571        PromqlExpr::NumberLiteral(_) | PromqlExpr::StringLiteral(_) | PromqlExpr::Extension(_) => {}
1572    }
1573    Ok(())
1574}
1575
1576fn collect_static_vector_target(
1577    selector: &VectorSelector,
1578    query_ctx: &QueryContextRef,
1579    targets: &mut Vec<PermissionTableTarget>,
1580    unresolved_selectors: &mut usize,
1581) -> Result<()> {
1582    if selector.name.is_none() && !selector.matchers.or_matchers.is_empty() {
1583        *unresolved_selectors += 1;
1584        return Ok(());
1585    }
1586
1587    let metric = selector.name.clone().or_else(|| {
1588        let mut matchers = selector.matchers.find_matchers(METRIC_NAME);
1589        if matchers.len() == 1 && matchers[0].op == MatchOp::Equal {
1590            matchers.pop().map(|matcher| matcher.value)
1591        } else {
1592            None
1593        }
1594    });
1595    let Some(metric) = metric else {
1596        *unresolved_selectors += 1;
1597        return Ok(());
1598    };
1599
1600    let schema = resolve_schema_from_matchers(&selector.matchers.matchers)?
1601        .unwrap_or_else(|| query_ctx.current_schema());
1602    targets.push(PermissionTableTarget::new(
1603        query_ctx.current_catalog(),
1604        schema,
1605        metric,
1606    ));
1607    Ok(())
1608}
1609
1610/// Update the `__name__` matchers in expression into special value
1611/// Returns the updated expression.
1612fn update_metric_name_matcher(expr: &mut PromqlExpr, metric_name: &str) {
1613    match expr {
1614        PromqlExpr::Aggregate(AggregateExpr { expr, .. }) => {
1615            update_metric_name_matcher(expr, metric_name)
1616        }
1617        PromqlExpr::Unary(UnaryExpr { expr }) => update_metric_name_matcher(expr, metric_name),
1618        PromqlExpr::Binary(BinaryExpr { lhs, rhs, .. }) => {
1619            update_metric_name_matcher(lhs, metric_name);
1620            update_metric_name_matcher(rhs, metric_name);
1621        }
1622        PromqlExpr::Paren(ParenExpr { expr }) => update_metric_name_matcher(expr, metric_name),
1623        PromqlExpr::Subquery(SubqueryExpr { expr, .. }) => {
1624            update_metric_name_matcher(expr, metric_name)
1625        }
1626        PromqlExpr::VectorSelector(VectorSelector { name, matchers, .. }) => {
1627            if name.is_some() {
1628                return;
1629            }
1630
1631            for m in &mut matchers.matchers {
1632                if m.name == METRIC_NAME && m.op != MatchOp::Equal {
1633                    m.op = MatchOp::Equal;
1634                    m.value = metric_name.to_string();
1635                }
1636            }
1637        }
1638        PromqlExpr::MatrixSelector(MatrixSelector { vs, .. }) => {
1639            let VectorSelector { name, matchers, .. } = vs;
1640            if name.is_some() {
1641                return;
1642            }
1643
1644            for m in &mut matchers.matchers {
1645                if m.name == METRIC_NAME && m.op != MatchOp::Equal {
1646                    m.op = MatchOp::Equal;
1647                    m.value = metric_name.to_string();
1648                }
1649            }
1650        }
1651        PromqlExpr::Call(Call { args, .. }) => {
1652            args.args.iter_mut().for_each(|e| {
1653                update_metric_name_matcher(e, metric_name);
1654            });
1655        }
1656        PromqlExpr::NumberLiteral(_) | PromqlExpr::StringLiteral(_) | PromqlExpr::Extension(_) => {}
1657    }
1658}
1659
1660#[derive(Debug, Default, Serialize, Deserialize)]
1661pub struct LabelValueQuery {
1662    start: Option<String>,
1663    end: Option<String>,
1664    lookback: Option<String>,
1665    #[serde(flatten)]
1666    matches: Matches,
1667    db: Option<String>,
1668    limit: Option<usize>,
1669}
1670
1671#[axum_macros::debug_handler]
1672#[tracing::instrument(
1673    skip_all,
1674    fields(protocol = "prometheus", request_type = "label_values_query")
1675)]
1676pub async fn label_values_query(
1677    State(handler): State<PrometheusHandlerRef>,
1678    Path(label_name): Path<String>,
1679    Extension(mut query_ctx): Extension<QueryContext>,
1680    Query(params): Query<LabelValueQuery>,
1681) -> PrometheusJsonResponse {
1682    let (catalog, schema) = get_catalog_schema(&params.db, &query_ctx);
1683    try_update_catalog_schema(&mut query_ctx, &catalog, &schema);
1684    let query_ctx = Arc::new(query_ctx);
1685
1686    let _timer = crate::metrics::METRIC_HTTP_PROMETHEUS_PROMQL_ELAPSED
1687        .with_label_values(&[query_ctx.get_db_string().as_str(), "label_values_query"])
1688        .start_timer();
1689
1690    let matches = params.matches.0;
1691    if label_name == METRIC_NAME_LABEL {
1692        try_call_return_response!(handler.check_query_permission(&[], &query_ctx).await);
1693        let exact_metric_names = matches
1694            .iter()
1695            .filter_map(|selector| retrieve_exact_metric_name_from_promql(selector))
1696            .collect::<Vec<_>>();
1697        try_call_return_response!(
1698            handler
1699                .check_query_target_permission(
1700                    current_schema_metric_targets(&query_ctx, &exact_metric_names),
1701                    &query_ctx,
1702                )
1703                .await
1704        );
1705        let catalog_manager = handler.catalog_manager();
1706
1707        let mut table_names = try_call_return_response!(
1708            retrieve_table_names(&query_ctx, catalog_manager, matches).await
1709        );
1710        table_names = try_call_return_response!(
1711            handler
1712                .filter_metadata_metric_names(
1713                    table_names,
1714                    query_ctx.current_schema().as_str(),
1715                    &query_ctx,
1716                )
1717                .await
1718        );
1719
1720        truncate_results(&mut table_names, params.limit);
1721        return PrometheusJsonResponse::success(PrometheusResponse::LabelValues(table_names));
1722    } else if label_name == FIELD_NAME_LABEL {
1723        try_call_return_response!(handler.check_query_permission(&[], &query_ctx).await);
1724        let enumerate_all = matches.is_empty();
1725        let metric_names = matches
1726            .iter()
1727            .map(|selector| retrieve_exact_metric_name_from_promql(selector))
1728            .collect::<Vec<_>>();
1729        if metric_names.iter().any(Option::is_none) {
1730            try_call_return_response!(
1731                handler
1732                    .check_query_target_permission(PermissionTableTargets::Unresolved, &query_ctx,)
1733                    .await
1734            );
1735        }
1736        let metric_names = metric_names.into_iter().flatten().collect::<Vec<_>>();
1737
1738        try_call_return_response!(
1739            handler
1740                .check_query_target_permission(
1741                    current_schema_metric_targets(&query_ctx, &metric_names),
1742                    &query_ctx,
1743                )
1744                .await
1745        );
1746
1747        let (field_columns, table_names) = if !enumerate_all && metric_names.is_empty() {
1748            (HashSet::new(), Vec::new())
1749        } else {
1750            handle_schema_err!(
1751                retrieve_field_names(&query_ctx, handler.catalog_manager(), metric_names).await
1752            )
1753            .unwrap_or_default()
1754        };
1755        try_call_return_response!(
1756            handler
1757                .check_query_target_permission(
1758                    current_schema_metric_targets(&query_ctx, &table_names),
1759                    &query_ctx,
1760                )
1761                .await
1762        );
1763        let mut field_columns = field_columns.into_iter().collect::<Vec<_>>();
1764        field_columns.sort_unstable();
1765        truncate_results(&mut field_columns, params.limit);
1766        return PrometheusJsonResponse::success(PrometheusResponse::LabelValues(field_columns));
1767    } else if is_database_selection_label(&label_name) {
1768        try_call_return_response!(handler.check_query_permission(&[], &query_ctx).await);
1769        let catalog_manager = handler.catalog_manager();
1770
1771        let (mut schema_names, targets) = try_call_return_response!(
1772            retrieve_schema_names(&query_ctx, catalog_manager, matches).await
1773        );
1774        try_call_return_response!(
1775            handler
1776                .check_query_target_permission(targets, &query_ctx)
1777                .await
1778        );
1779        truncate_results(&mut schema_names, params.limit);
1780        return PrometheusJsonResponse::success(PrometheusResponse::LabelValues(schema_names));
1781    }
1782
1783    let queries = matches;
1784    if queries.is_empty() {
1785        return PrometheusJsonResponse::error(
1786            StatusCode::InvalidArguments,
1787            "match[] parameter is required",
1788        );
1789    }
1790
1791    let start = params.start.unwrap_or_else(yesterday_rfc3339);
1792    let end = params.end.unwrap_or_else(current_time_rfc3339);
1793    let lookback = params
1794        .lookback
1795        .unwrap_or_else(|| DEFAULT_LOOKBACK_STRING.to_string());
1796    let prom_queries = queries
1797        .into_iter()
1798        .map(|query| PromQuery {
1799            query,
1800            start: start.clone(),
1801            end: end.clone(),
1802            step: DEFAULT_LOOKBACK_STRING.to_string(),
1803            lookback: lookback.clone(),
1804            alias: None,
1805        })
1806        .collect::<Vec<_>>();
1807    let prom_queries = try_call_return_response!(
1808        prom_queries
1809            .into_iter()
1810            .map(|query| ParsedPromQuery::parse(query, &query_ctx))
1811            .collect::<Result<Vec<_>>>()
1812    );
1813    try_call_return_response!(
1814        handler
1815            .check_query_permission_parsed(&prom_queries, &query_ctx)
1816            .await
1817    );
1818
1819    let mut label_values = HashSet::new();
1820
1821    let Some(first_query) = prom_queries.first() else {
1822        unreachable!("empty match[] is rejected above")
1823    };
1824    let QueryStatement::Promql(eval_stmt, _) = first_query.statement() else {
1825        unreachable!("query is parsed from PromQL")
1826    };
1827    let start = eval_stmt.start;
1828    let end = eval_stmt.end;
1829
1830    for prom_query in prom_queries {
1831        let (_, statement) = prom_query.into_parts();
1832        let QueryStatement::Promql(eval_stmt, _) = statement else {
1833            unreachable!("query is parsed from PromQL")
1834        };
1835        let PromqlExpr::VectorSelector(mut vector_selector) = eval_stmt.expr else {
1836            return PrometheusJsonResponse::error(
1837                StatusCode::InvalidArguments,
1838                "expected vector selector",
1839            );
1840        };
1841        let Some(name) = take_metric_name(&mut vector_selector) else {
1842            return PrometheusJsonResponse::error(
1843                StatusCode::InvalidArguments,
1844                "expected metric name",
1845            );
1846        };
1847        let VectorSelector { matchers, .. } = vector_selector;
1848        // Only use and filter matchers.
1849        let matchers = matchers.matchers;
1850        let result = handler
1851            .query_label_values(name, label_name.clone(), matchers, start, end, &query_ctx)
1852            .await;
1853        if let Some(result) = handle_schema_err!(result) {
1854            label_values.extend(result.into_iter());
1855        }
1856    }
1857
1858    let mut label_values: Vec<_> = label_values.into_iter().collect();
1859    label_values.sort_unstable();
1860    truncate_results(&mut label_values, params.limit);
1861
1862    PrometheusJsonResponse::success(PrometheusResponse::LabelValues(label_values))
1863}
1864
1865fn truncate_results(label_values: &mut Vec<String>, limit: Option<usize>) {
1866    if let Some(limit) = limit
1867        && limit > 0
1868        && label_values.len() >= limit
1869    {
1870        label_values.truncate(limit);
1871    }
1872}
1873
1874/// Take metric name from the [VectorSelector].
1875/// It takes the name in the selector or removes the name matcher.
1876fn take_metric_name(selector: &mut VectorSelector) -> Option<String> {
1877    if let Some(name) = selector.name.take() {
1878        return Some(name);
1879    }
1880
1881    let (pos, matcher) = selector
1882        .matchers
1883        .matchers
1884        .iter()
1885        .find_position(|matcher| matcher.name == "__name__" && matcher.op == MatchOp::Equal)?;
1886    let name = matcher.value.clone();
1887    // We need to remove the name matcher to avoid using it as a filter in query.
1888    selector.matchers.matchers.remove(pos);
1889
1890    Some(name)
1891}
1892
1893async fn retrieve_table_names(
1894    query_ctx: &QueryContext,
1895    catalog_manager: CatalogManagerRef,
1896    matches: Vec<String>,
1897) -> Result<Vec<String>> {
1898    let catalog = query_ctx.current_catalog();
1899    let schema = query_ctx.current_schema();
1900
1901    let mut tables_stream = catalog_manager.tables(catalog, &schema, Some(query_ctx));
1902    let mut table_names = Vec::new();
1903
1904    let name_matchers = matches
1905        .iter()
1906        .map(|selector| {
1907            let expr = promql_parser::parser::parse(selector)
1908                .map_err(|reason| InvalidQuerySnafu { reason }.build())?;
1909            let PromqlExpr::VectorSelector(selector) = expr else {
1910                return InvalidQuerySnafu {
1911                    reason: "expected vector selector".to_string(),
1912                }
1913                .fail();
1914            };
1915            if !selector.matchers.or_matchers.is_empty() {
1916                return Ok(None);
1917            }
1918            if let Some(name) = selector.name {
1919                return Ok(Some(Matcher::new(MatchOp::Equal, METRIC_NAME_LABEL, &name)));
1920            }
1921            Ok(selector
1922                .matchers
1923                .matchers
1924                .into_iter()
1925                .find(|matcher| matcher.name == METRIC_NAME_LABEL))
1926        })
1927        .collect::<Result<Vec<_>>>()?;
1928
1929    while let Some(table) = tables_stream.next().await {
1930        let table = table?;
1931        if !table
1932            .table_info()
1933            .meta
1934            .options
1935            .extra_options
1936            .contains_key(LOGICAL_TABLE_METADATA_KEY)
1937            || is_internal_physical_metric_table(&table)
1938        {
1939            // skip non-prometheus (non-metricengine) tables for __name__ query
1940            continue;
1941        }
1942
1943        let table_name = &table.table_info().name;
1944
1945        if name_matchers.is_empty()
1946            || name_matchers.iter().any(|matcher| match matcher {
1947                None => true,
1948                Some(matcher) => match &matcher.op {
1949                    MatchOp::Equal => table_name == &matcher.value,
1950                    MatchOp::Re(reg) => reg.is_match(table_name),
1951                    // != and !~ are not supported, so include every table and
1952                    // let the caller authorize the complete candidate set.
1953                    _ => true,
1954                },
1955            })
1956        {
1957            table_names.push(table_name.clone());
1958        }
1959    }
1960
1961    table_names.sort_unstable();
1962    Ok(table_names)
1963}
1964
1965async fn retrieve_metric_metadata(
1966    query_ctx: &QueryContext,
1967    manager: CatalogManagerRef,
1968    params: &MetadataQuery,
1969) -> Result<BTreeMap<String, Vec<PromMetadata>>> {
1970    let mut metadata = BTreeMap::new();
1971    if params.limit == Some(0) {
1972        return Ok(metadata);
1973    }
1974
1975    let catalog = query_ctx.current_catalog();
1976    let schema = query_ctx.current_schema();
1977
1978    if let Some(metric) = &params.metric {
1979        let Some(table) = manager
1980            .table(catalog, &schema, metric, Some(query_ctx))
1981            .await?
1982        else {
1983            return Ok(metadata);
1984        };
1985        let table_info = table.table_info();
1986        if is_prometheus_metric_table(table_info.as_ref()) {
1987            metadata.insert(
1988                table_info.name.clone(),
1989                vec![prometheus_metadata_from_table(table_info.as_ref())],
1990            );
1991        }
1992        return Ok(metadata);
1993    }
1994
1995    let mut tables_stream = manager.tables(catalog, &schema, Some(query_ctx));
1996
1997    while let Some(table) = tables_stream.next().await {
1998        let table = table?;
1999        let table_info = table.table_info();
2000        if !is_prometheus_metric_table(table_info.as_ref()) {
2001            continue;
2002        }
2003
2004        metadata.insert(
2005            table_info.name.clone(),
2006            vec![prometheus_metadata_from_table(table_info.as_ref())],
2007        );
2008    }
2009
2010    Ok(metadata)
2011}
2012
2013fn is_prometheus_metric_table(table_info: &TableInfo) -> bool {
2014    table_info
2015        .meta
2016        .options
2017        .extra_options
2018        .contains_key(LOGICAL_TABLE_METADATA_KEY)
2019}
2020
2021fn prometheus_metadata_from_table(table_info: &TableInfo) -> PromMetadata {
2022    let options = &table_info.meta.options.extra_options;
2023    let metric_type = match options.get(SEMANTIC_METRIC_TYPE) {
2024        Some(metric_type)
2025            if options
2026                .get(SEMANTIC_METRIC_TEMPORALITY)
2027                .is_some_and(|temporality| temporality == "delta")
2028                && matches!(
2029                    metric_type.as_str(),
2030                    "counter" | "histogram" | "updown_counter"
2031                ) =>
2032        {
2033            "unknown".to_string()
2034        }
2035        Some(metric_type) => match metric_type.as_str() {
2036            "updown_counter" => "gauge".to_string(),
2037            "gauge_histogram" => "gaugehistogram".to_string(),
2038            "mixed" => "unknown".to_string(),
2039            metric_type => metric_type.to_string(),
2040        },
2041        None if table_has_native_histogram_value(table_info) => "histogram".to_string(),
2042        None => String::new(),
2043    };
2044    let unit = options
2045        .get(SEMANTIC_METRIC_UNIT)
2046        .map(|unit| ucum_to_openmetrics_unit(unit))
2047        .unwrap_or_default();
2048
2049    PromMetadata {
2050        metric_type,
2051        unit,
2052        // TODO: Persist and return Prometheus help text and OTLP metric descriptions.
2053        help: String::new(),
2054    }
2055}
2056
2057fn table_has_native_histogram_value(table_info: &TableInfo) -> bool {
2058    table_info
2059        .meta
2060        .schema
2061        .column_schemas()
2062        .iter()
2063        .any(|column| is_native_histogram_value_type(&column.data_type))
2064}
2065
2066async fn retrieve_field_names(
2067    query_ctx: &QueryContext,
2068    manager: CatalogManagerRef,
2069    matches: Vec<String>,
2070) -> Result<(HashSet<String>, Vec<String>)> {
2071    let mut field_columns = HashSet::new();
2072    let mut table_names = Vec::new();
2073    let catalog = query_ctx.current_catalog();
2074    let schema = query_ctx.current_schema();
2075
2076    if matches.is_empty() {
2077        // query all tables if no matcher is provided
2078        let mut tables = manager.tables(catalog, &schema, Some(query_ctx));
2079        while let Some(table) = tables.next().await {
2080            let table = table?;
2081            if is_internal_physical_metric_table(&table) {
2082                continue;
2083            }
2084            table_names.push(table.table_info().name.clone());
2085            for column in table.field_columns() {
2086                field_columns.insert(column.name);
2087            }
2088        }
2089        return Ok((field_columns, table_names));
2090    }
2091
2092    for table_name in matches {
2093        let table = manager
2094            .table(catalog, &schema, &table_name, Some(query_ctx))
2095            .await?
2096            .with_context(|| TableNotFoundSnafu {
2097                catalog: catalog.to_string(),
2098                schema: schema.clone(),
2099                table: table_name.clone(),
2100            })?;
2101
2102        if is_internal_physical_metric_table(&table) {
2103            continue;
2104        }
2105        table_names.push(table.table_info().name.clone());
2106        for column in table.field_columns() {
2107            field_columns.insert(column.name);
2108        }
2109    }
2110    Ok((field_columns, table_names))
2111}
2112
2113async fn retrieve_schema_names(
2114    query_ctx: &QueryContext,
2115    catalog_manager: CatalogManagerRef,
2116    matches: Vec<String>,
2117) -> Result<(Vec<String>, PermissionTableTargets)> {
2118    let mut schemas = Vec::new();
2119    let mut targets = Vec::new();
2120    let catalog = query_ctx.current_catalog();
2121    let metric_names = matches
2122        .iter()
2123        .map(|match_item| retrieve_exact_metric_name_from_promql(match_item))
2124        .collect::<Vec<_>>();
2125    let unresolved = metric_names.is_empty() || metric_names.iter().any(Option::is_none);
2126
2127    let candidate_schemas = catalog_manager
2128        .schema_names(catalog, Some(query_ctx))
2129        .await?;
2130
2131    for schema in candidate_schemas {
2132        let mut found = true;
2133        for table_name in metric_names.iter().flatten() {
2134            let table = catalog_manager
2135                .table(catalog, &schema, table_name, Some(query_ctx))
2136                .await?;
2137            let Some(table) = table else {
2138                found = false;
2139                continue;
2140            };
2141            targets.push(PermissionTableTarget::new(catalog, &schema, table_name));
2142            if is_internal_physical_metric_table(&table) {
2143                found = false;
2144            }
2145        }
2146
2147        if found {
2148            schemas.push(schema);
2149        }
2150    }
2151
2152    schemas.sort_unstable();
2153
2154    let targets = if unresolved {
2155        PermissionTableTargets::Unresolved
2156    } else {
2157        PermissionTableTargets::resolved(targets)
2158    };
2159    Ok((schemas, targets))
2160}
2161
2162fn retrieve_exact_metric_name_from_promql(query: &str) -> Option<String> {
2163    let PromqlExpr::VectorSelector(selector) = promql_parser::parser::parse(query).ok()? else {
2164        return None;
2165    };
2166    if let Some(name) = selector.name {
2167        return (!name.is_empty()).then_some(name);
2168    }
2169
2170    let mut name_matchers = selector.matchers.find_matchers(METRIC_NAME);
2171    if name_matchers.len() != 1 {
2172        return None;
2173    }
2174    let matcher = name_matchers.pop()?;
2175    (matcher.op == MatchOp::Equal && !matcher.value.is_empty()).then_some(matcher.value)
2176}
2177
2178#[derive(Debug, Default, Serialize, Deserialize)]
2179pub struct SeriesQuery {
2180    start: Option<String>,
2181    end: Option<String>,
2182    lookback: Option<String>,
2183    #[serde(flatten)]
2184    matches: Matches,
2185    db: Option<String>,
2186}
2187
2188#[axum_macros::debug_handler]
2189#[tracing::instrument(
2190    skip_all,
2191    fields(protocol = "prometheus", request_type = "series_query")
2192)]
2193pub async fn series_query(
2194    State(handler): State<PrometheusHandlerRef>,
2195    Query(params): Query<SeriesQuery>,
2196    Extension(mut query_ctx): Extension<QueryContext>,
2197    Form(form_params): Form<SeriesQuery>,
2198) -> PrometheusJsonResponse {
2199    let mut queries: Vec<String> = params.matches.0;
2200    if queries.is_empty() {
2201        queries = form_params.matches.0;
2202    }
2203    if queries.is_empty() {
2204        return PrometheusJsonResponse::error(
2205            StatusCode::Unsupported,
2206            "match[] parameter is required",
2207        );
2208    }
2209    let start = params
2210        .start
2211        .or(form_params.start)
2212        .unwrap_or_else(yesterday_rfc3339);
2213    let end = params
2214        .end
2215        .or(form_params.end)
2216        .unwrap_or_else(current_time_rfc3339);
2217    let lookback = params
2218        .lookback
2219        .or(form_params.lookback)
2220        .unwrap_or_else(|| DEFAULT_LOOKBACK_STRING.to_string());
2221
2222    // update catalog and schema in query context if necessary
2223    if let Some(db) = &params.db {
2224        let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
2225        try_update_catalog_schema(&mut query_ctx, &catalog, &schema);
2226    }
2227    let query_ctx = Arc::new(query_ctx);
2228
2229    let _timer = crate::metrics::METRIC_HTTP_PROMETHEUS_PROMQL_ELAPSED
2230        .with_label_values(&[query_ctx.get_db_string().as_str(), "series_query"])
2231        .start_timer();
2232
2233    let prom_queries = queries
2234        .into_iter()
2235        .map(|query| PromQuery {
2236            query,
2237            start: start.clone(),
2238            end: end.clone(),
2239            // TODO: find a better value for step
2240            step: DEFAULT_LOOKBACK_STRING.to_string(),
2241            lookback: lookback.clone(),
2242            alias: None,
2243        })
2244        .collect::<Vec<_>>();
2245    let mut expanded_queries = Vec::new();
2246    for prom_query in prom_queries {
2247        let prom_query = try_call_return_response!(
2248            @output_msg ParsedPromQuery::parse(prom_query, &query_ctx),
2249            StatusCode::InvalidArguments
2250        );
2251        let promql_expr = prom_query.expr();
2252        let Some(discovery) =
2253            try_call_return_response!(find_metric_name_not_equal_matchers(promql_expr))
2254        else {
2255            expanded_queries.push(prom_query);
2256            continue;
2257        };
2258
2259        try_call_return_response!(handler.check_query_permission(&[], &query_ctx).await);
2260        let (static_targets, unresolved_selectors) =
2261            try_call_return_response!(static_promql_targets(promql_expr, &query_ctx));
2262        try_call_return_response!(
2263            handler
2264                .check_query_target_permission(static_targets, &query_ctx)
2265                .await
2266        );
2267        if unresolved_selectors != 1 {
2268            try_call_return_response!(
2269                handler
2270                    .check_query_target_permission(PermissionTableTargets::Unresolved, &query_ctx)
2271                    .await
2272            );
2273            expanded_queries.push(prom_query);
2274            continue;
2275        }
2276
2277        let schema = discovery
2278            .schema
2279            .unwrap_or_else(|| query_ctx.current_schema());
2280        let metric_names = try_call_return_response!(
2281            handler
2282                .query_metric_names(discovery.name_matchers, &schema, &query_ctx)
2283                .await
2284        );
2285        expanded_queries.extend(expand_metric_name_queries(&prom_query, metric_names));
2286    }
2287    let prom_queries = expanded_queries;
2288    try_call_return_response!(
2289        handler
2290            .check_query_permission_parsed(&prom_queries, &query_ctx)
2291            .await
2292    );
2293
2294    let mut series = Vec::new();
2295    let mut merge_map = HashMap::new();
2296    for prom_query in prom_queries {
2297        let Some(target) = resolved_promql_targets(std::slice::from_ref(&prom_query), &query_ctx)
2298            .and_then(|targets| match targets.as_slice() {
2299                [target] => Some(target.clone()),
2300                _ => None,
2301            })
2302        else {
2303            return PrometheusJsonResponse::error(
2304                StatusCode::InvalidArguments,
2305                "series selector must resolve exactly one metric table",
2306            );
2307        };
2308        let result = handler.do_query_parsed(prom_query, query_ctx.clone()).await;
2309
2310        handle_schema_err!(
2311            retrieve_series_from_query_result(
2312                result,
2313                &mut series,
2314                &query_ctx,
2315                &target,
2316                &handler.catalog_manager(),
2317                &mut merge_map,
2318            )
2319            .await
2320        );
2321    }
2322    let merge_map = merge_map
2323        .into_iter()
2324        .map(|(k, v)| (k, Value::from(v)))
2325        .collect();
2326    let mut resp = PrometheusJsonResponse::success(PrometheusResponse::Series(series));
2327    resp.resp_metrics = merge_map;
2328    resp
2329}
2330
2331#[derive(Debug, Default, Serialize, Deserialize)]
2332pub struct ParseQuery {
2333    query: Option<String>,
2334    db: Option<String>,
2335}
2336
2337#[axum_macros::debug_handler]
2338#[tracing::instrument(
2339    skip_all,
2340    fields(protocol = "prometheus", request_type = "parse_query")
2341)]
2342pub async fn parse_query(
2343    State(_handler): State<PrometheusHandlerRef>,
2344    Query(params): Query<ParseQuery>,
2345    Extension(_query_ctx): Extension<QueryContext>,
2346    Form(form_params): Form<ParseQuery>,
2347) -> PrometheusJsonResponse {
2348    if let Some(query) = params.query.or(form_params.query) {
2349        let ast = try_call_return_response!(
2350            promql_parser::parser::parse(&query),
2351            StatusCode::InvalidArguments
2352        );
2353        PrometheusJsonResponse::success(PrometheusResponse::ParseResult(ast))
2354    } else {
2355        PrometheusJsonResponse::error(StatusCode::InvalidArguments, "query is required")
2356    }
2357}
2358
2359#[cfg(test)]
2360mod tests {
2361    use std::collections::HashSet;
2362    use std::sync::{Arc, Mutex};
2363
2364    use catalog::memory::MemoryCatalogManager;
2365    use catalog::{RegisterSchemaRequest, RegisterTableRequest};
2366    use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
2367    use common_query::native_histogram::{
2368        CounterResetHint, NativeHistogram, build_histogram_array, native_histogram_value_type,
2369    };
2370    use common_query::prelude::greptime_native_histogram;
2371    use datatypes::prelude::ConcreteDataType;
2372    use datatypes::schema::{ColumnSchema, Schema};
2373    use datatypes::vectors::{StringVector, StructVector};
2374    use promql_parser::parser::value::ValueType;
2375    use table::metadata::{TableInfoBuilder, TableMetaBuilder, TableType, TableVersion};
2376    use table::requests::{
2377        SEMANTIC_METRIC_TEMPORALITY, SEMANTIC_METRIC_TYPE, SEMANTIC_METRIC_UNIT, TableOptions,
2378    };
2379    use table::test_util::EmptyTable;
2380    use table::test_util::table_info::test_table_info;
2381
2382    use super::*;
2383    use crate::prometheus_handler::PrometheusHandler;
2384
2385    struct TestCase {
2386        name: &'static str,
2387        promql: &'static str,
2388        expected_metric: Option<&'static str>,
2389        expected_type: ValueType,
2390        should_error: bool,
2391    }
2392
2393    struct TestPrometheusHandler {
2394        catalog_manager: CatalogManagerRef,
2395        deny_operation: bool,
2396        denied_table: Option<&'static str>,
2397        metric_names: Vec<String>,
2398        queries: Mutex<Vec<String>>,
2399    }
2400
2401    #[async_trait::async_trait]
2402    impl PrometheusHandler for TestPrometheusHandler {
2403        async fn do_query(&self, _: &PromQuery, _: QueryContextRef) -> Result<Output> {
2404            unreachable!("HTTP handlers should execute the parsed query")
2405        }
2406
2407        async fn do_query_parsed(
2408            &self,
2409            query: ParsedPromQuery,
2410            _: QueryContextRef,
2411        ) -> Result<Output> {
2412            self.queries
2413                .lock()
2414                .unwrap()
2415                .push(query.query().query.clone());
2416            Ok(Output::new_with_record_batches(RecordBatches::empty()))
2417        }
2418
2419        async fn check_query_permission(&self, _: &[PromQuery], _: &QueryContextRef) -> Result<()> {
2420            if self.deny_operation {
2421                return auth::error::PermissionDeniedSnafu
2422                    .fail()
2423                    .context(crate::error::AuthSnafu);
2424            }
2425            Ok(())
2426        }
2427
2428        async fn check_query_target_permission(
2429            &self,
2430            targets: PermissionTableTargets,
2431            _: &QueryContextRef,
2432        ) -> Result<()> {
2433            if matches!(
2434                targets,
2435                PermissionTableTargets::Resolved(targets)
2436                    if self.denied_table.is_some_and(|denied| {
2437                        targets.iter().any(|target| target.table == denied)
2438                    })
2439            ) {
2440                return auth::error::PermissionDeniedSnafu
2441                    .fail()
2442                    .context(crate::error::AuthSnafu);
2443            }
2444            Ok(())
2445        }
2446
2447        async fn filter_metadata_metric_names(
2448            &self,
2449            metric_names: Vec<String>,
2450            _: &str,
2451            _: &QueryContextRef,
2452        ) -> Result<Vec<String>> {
2453            Ok(metric_names
2454                .into_iter()
2455                .filter(|metric| metric != "denied")
2456                .collect())
2457        }
2458
2459        async fn query_metric_names(
2460            &self,
2461            _: Vec<Matcher>,
2462            _: &str,
2463            _: &QueryContextRef,
2464        ) -> Result<Vec<String>> {
2465            Ok(self.metric_names.clone())
2466        }
2467
2468        async fn query_label_values(
2469            &self,
2470            _: String,
2471            _: String,
2472            _: Vec<Matcher>,
2473            _: std::time::SystemTime,
2474            _: std::time::SystemTime,
2475            _: &QueryContextRef,
2476        ) -> Result<Vec<String>> {
2477            unreachable!()
2478        }
2479
2480        fn catalog_manager(&self) -> CatalogManagerRef {
2481            self.catalog_manager.clone()
2482        }
2483    }
2484
2485    fn permission_denied_response() -> PrometheusJsonResponse {
2486        let result: auth::error::Result<()> = auth::error::PermissionDeniedSnafu.fail();
2487        try_call_return_response!(result);
2488        unreachable!()
2489    }
2490
2491    fn invalid_promql_response() -> PrometheusJsonResponse {
2492        let result = ParsedPromQuery::parse(
2493            PromQuery {
2494                query: "up{".to_string(),
2495                ..Default::default()
2496            },
2497            &QueryContext::arc(),
2498        );
2499        try_call_return_response!(@output_msg result, StatusCode::InvalidArguments);
2500        unreachable!()
2501    }
2502
2503    #[test]
2504    fn test_try_call_return_response_preserves_status() {
2505        use axum::response::IntoResponse;
2506
2507        let response = permission_denied_response();
2508        assert_eq!(Some(StatusCode::PermissionDenied), response.status_code);
2509        assert_eq!(
2510            axum::http::StatusCode::FORBIDDEN,
2511            response.into_response().status()
2512        );
2513    }
2514
2515    #[test]
2516    fn test_try_call_return_response_preserves_error_source() {
2517        let response = invalid_promql_response();
2518        assert_eq!(Some(StatusCode::InvalidArguments), response.status_code);
2519        let error = response.error.unwrap();
2520        assert!(
2521            error.contains("unexpected end of input inside braces"),
2522            "{error}"
2523        );
2524    }
2525
2526    #[tokio::test]
2527    async fn test_promql_timer_records_parse_errors() {
2528        let handler: PrometheusHandlerRef = Arc::new(TestPrometheusHandler {
2529            catalog_manager: MemoryCatalogManager::new(),
2530            deny_operation: false,
2531            denied_table: None,
2532            metric_names: Vec::new(),
2533            queries: Mutex::new(Vec::new()),
2534        });
2535        let query_ctx = QueryContext::with("promql_timer_test", "parse_error");
2536        let db = query_ctx.get_db_string();
2537
2538        let instant_histogram = crate::metrics::METRIC_HTTP_PROMETHEUS_PROMQL_ELAPSED
2539            .with_label_values(&[db.as_str(), "instant_query"]);
2540        let instant_count = instant_histogram.get_sample_count();
2541        let response = instant_query(
2542            State(handler.clone()),
2543            Query(InstantQuery {
2544                query: Some("up{".to_string()),
2545                ..Default::default()
2546            }),
2547            Extension(query_ctx),
2548            Form(InstantQuery::default()),
2549        )
2550        .await;
2551        assert_eq!(Some(StatusCode::InvalidArguments), response.status_code);
2552        assert_eq!(instant_count + 1, instant_histogram.get_sample_count());
2553
2554        let range_histogram = crate::metrics::METRIC_HTTP_PROMETHEUS_PROMQL_ELAPSED
2555            .with_label_values(&[db.as_str(), "range_query"]);
2556        let range_count = range_histogram.get_sample_count();
2557        let response = range_query(
2558            State(handler),
2559            Query(RangeQuery {
2560                query: Some("up{".to_string()),
2561                start: Some("0".to_string()),
2562                end: Some("1".to_string()),
2563                step: Some("1s".to_string()),
2564                ..Default::default()
2565            }),
2566            Extension(QueryContext::with("promql_timer_test", "parse_error")),
2567            Form(RangeQuery::default()),
2568        )
2569        .await;
2570        assert_eq!(Some(StatusCode::InvalidArguments), response.status_code);
2571        assert_eq!(range_count + 1, range_histogram.get_sample_count());
2572    }
2573
2574    #[tokio::test]
2575    async fn test_field_name_enumeration_fails_closed() {
2576        use axum::response::IntoResponse;
2577
2578        let mut allowed = test_table_info(
2579            1024,
2580            "allowed",
2581            DEFAULT_SCHEMA_NAME,
2582            DEFAULT_CATALOG_NAME,
2583            Arc::new(Schema::new(vec![])),
2584        );
2585        allowed.meta.options.extra_options.insert(
2586            LOGICAL_TABLE_METADATA_KEY.to_string(),
2587            "physical_metrics".to_string(),
2588        );
2589        let manager = MemoryCatalogManager::new_with_table(EmptyTable::from_table_info(&allowed));
2590        let mut denied = allowed.clone();
2591        denied.ident.table_id = 1025;
2592        denied.name = "denied".to_string();
2593        manager
2594            .register_table_sync(RegisterTableRequest {
2595                catalog: DEFAULT_CATALOG_NAME.to_string(),
2596                schema: DEFAULT_SCHEMA_NAME.to_string(),
2597                table_name: denied.name.clone(),
2598                table_id: denied.table_id(),
2599                table: EmptyTable::from_table_info(&denied),
2600            })
2601            .unwrap();
2602        let query_ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
2603        assert_eq!(
2604            vec!["allowed".to_string(), "denied".to_string()],
2605            retrieve_table_names(&query_ctx, manager.clone(), Vec::new())
2606                .await
2607                .unwrap()
2608        );
2609
2610        let response = label_values_query(
2611            State(Arc::new(TestPrometheusHandler {
2612                catalog_manager: manager,
2613                deny_operation: false,
2614                denied_table: Some("denied"),
2615                metric_names: Vec::new(),
2616                queries: Mutex::new(Vec::new()),
2617            })),
2618            Path(FIELD_NAME_LABEL.to_string()),
2619            Extension(query_ctx),
2620            Query(LabelValueQuery::default()),
2621        )
2622        .await;
2623
2624        assert_eq!(Some(StatusCode::PermissionDenied), response.status_code);
2625        assert_eq!(
2626            axum::http::StatusCode::FORBIDDEN,
2627            response.into_response().status()
2628        );
2629    }
2630
2631    #[tokio::test]
2632    async fn test_series_query_expands_metric_name_regex() {
2633        let cpu_user = test_table_info(
2634            1024,
2635            "cpu_user",
2636            DEFAULT_SCHEMA_NAME,
2637            DEFAULT_CATALOG_NAME,
2638            Arc::new(Schema::new(vec![])),
2639        );
2640        let manager = MemoryCatalogManager::new_with_table(EmptyTable::from_table_info(&cpu_user));
2641        let mut cpu_system = cpu_user.clone();
2642        cpu_system.ident.table_id = 1025;
2643        cpu_system.name = "cpu_system".to_string();
2644        manager
2645            .register_table_sync(RegisterTableRequest {
2646                catalog: DEFAULT_CATALOG_NAME.to_string(),
2647                schema: DEFAULT_SCHEMA_NAME.to_string(),
2648                table_name: cpu_system.name.clone(),
2649                table_id: cpu_system.table_id(),
2650                table: EmptyTable::from_table_info(&cpu_system),
2651            })
2652            .unwrap();
2653
2654        let handler = Arc::new(TestPrometheusHandler {
2655            catalog_manager: manager,
2656            deny_operation: false,
2657            denied_table: None,
2658            metric_names: vec!["cpu_user".to_string(), "cpu_system".to_string()],
2659            queries: Mutex::new(Vec::new()),
2660        });
2661        let state: PrometheusHandlerRef = handler.clone();
2662        let query_ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
2663        let target_ctx = Arc::new(query_ctx.clone());
2664        let response = series_query(
2665            State(state),
2666            Query(SeriesQuery {
2667                matches: Matches(vec![r#"{__name__=~"cpu_.*"}"#.to_string()]),
2668                ..Default::default()
2669            }),
2670            Extension(query_ctx),
2671            Form(SeriesQuery::default()),
2672        )
2673        .await;
2674
2675        assert!(
2676            response.status_code.is_none(),
2677            "status={:?}, error={:?}",
2678            response.status_code,
2679            response.error
2680        );
2681        let mut targets = handler
2682            .queries
2683            .lock()
2684            .unwrap()
2685            .iter()
2686            .map(|query| {
2687                let query = ParsedPromQuery::parse(
2688                    PromQuery {
2689                        query: query.clone(),
2690                        ..Default::default()
2691                    },
2692                    &target_ctx,
2693                )
2694                .unwrap();
2695                resolved_promql_targets(std::slice::from_ref(&query), &target_ctx)
2696                    .unwrap()
2697                    .pop()
2698                    .unwrap()
2699                    .table
2700            })
2701            .collect_vec();
2702        targets.sort_unstable();
2703        assert_eq!(vec!["cpu_system", "cpu_user"], targets);
2704    }
2705
2706    #[test]
2707    fn test_retrieve_metric_name_and_result_type() {
2708        let test_cases = &[
2709            // Single metric cases
2710            TestCase {
2711                name: "simple metric",
2712                promql: "cpu_usage",
2713                expected_metric: Some("cpu_usage"),
2714                expected_type: ValueType::Vector,
2715                should_error: false,
2716            },
2717            TestCase {
2718                name: "metric with selector",
2719                promql: r#"cpu_usage{instance="localhost"}"#,
2720                expected_metric: Some("cpu_usage"),
2721                expected_type: ValueType::Vector,
2722                should_error: false,
2723            },
2724            TestCase {
2725                name: "metric with range selector",
2726                promql: "cpu_usage[5m]",
2727                expected_metric: Some("cpu_usage"),
2728                expected_type: ValueType::Matrix,
2729                should_error: false,
2730            },
2731            TestCase {
2732                name: "metric with __name__ matcher",
2733                promql: r#"{__name__="cpu_usage"}"#,
2734                expected_metric: Some("cpu_usage"),
2735                expected_type: ValueType::Vector,
2736                should_error: false,
2737            },
2738            TestCase {
2739                name: "metric with unary operator",
2740                promql: "-cpu_usage",
2741                expected_metric: None,
2742                expected_type: ValueType::Vector,
2743                should_error: false,
2744            },
2745            // Aggregation and function cases
2746            TestCase {
2747                name: "metric with aggregation",
2748                promql: "sum(cpu_usage)",
2749                expected_metric: Some("cpu_usage"),
2750                expected_type: ValueType::Vector,
2751                should_error: false,
2752            },
2753            TestCase {
2754                name: "complex aggregation",
2755                promql: r#"sum by (instance) (cpu_usage{job="node"})"#,
2756                expected_metric: None,
2757                expected_type: ValueType::Vector,
2758                should_error: false,
2759            },
2760            TestCase {
2761                name: "complex aggregation",
2762                promql: r#"sum by (__name__) (cpu_usage{job="node"})"#,
2763                expected_metric: Some("cpu_usage"),
2764                expected_type: ValueType::Vector,
2765                should_error: false,
2766            },
2767            TestCase {
2768                name: "complex aggregation",
2769                promql: r#"sum without (instance) (cpu_usage{job="node"})"#,
2770                expected_metric: Some("cpu_usage"),
2771                expected_type: ValueType::Vector,
2772                should_error: false,
2773            },
2774            // Same metric binary operations
2775            TestCase {
2776                name: "same metric addition",
2777                promql: "cpu_usage + cpu_usage",
2778                expected_metric: None,
2779                expected_type: ValueType::Vector,
2780                should_error: false,
2781            },
2782            TestCase {
2783                name: "metric with scalar addition",
2784                promql: r#"sum(rate(cpu_usage{job="node"}[5m])) + 100"#,
2785                expected_metric: None,
2786                expected_type: ValueType::Vector,
2787                should_error: false,
2788            },
2789            // Multiple metrics cases
2790            TestCase {
2791                name: "different metrics addition",
2792                promql: "cpu_usage + memory_usage",
2793                expected_metric: None,
2794                expected_type: ValueType::Vector,
2795                should_error: false,
2796            },
2797            TestCase {
2798                name: "different metrics subtraction",
2799                promql: "network_in - network_out",
2800                expected_metric: None,
2801                expected_type: ValueType::Vector,
2802                should_error: false,
2803            },
2804            // Unless operator cases
2805            TestCase {
2806                name: "unless with different metrics",
2807                promql: "cpu_usage unless memory_usage",
2808                expected_metric: Some("cpu_usage"),
2809                expected_type: ValueType::Vector,
2810                should_error: false,
2811            },
2812            TestCase {
2813                name: "unless with same metric",
2814                promql: "cpu_usage unless cpu_usage",
2815                expected_metric: Some("cpu_usage"),
2816                expected_type: ValueType::Vector,
2817                should_error: false,
2818            },
2819            // Subquery cases
2820            TestCase {
2821                name: "basic subquery",
2822                promql: "cpu_usage[5m:1m]",
2823                expected_metric: Some("cpu_usage"),
2824                expected_type: ValueType::Matrix,
2825                should_error: false,
2826            },
2827            TestCase {
2828                name: "subquery with multiple metrics",
2829                promql: "(cpu_usage + memory_usage)[5m:1m]",
2830                expected_metric: None,
2831                expected_type: ValueType::Matrix,
2832                should_error: false,
2833            },
2834            // Literal values
2835            TestCase {
2836                name: "scalar value",
2837                promql: "42",
2838                expected_metric: None,
2839                expected_type: ValueType::Scalar,
2840                should_error: false,
2841            },
2842            TestCase {
2843                name: "string literal",
2844                promql: r#""hello world""#,
2845                expected_metric: None,
2846                expected_type: ValueType::String,
2847                should_error: false,
2848            },
2849            // Error cases
2850            TestCase {
2851                name: "invalid syntax",
2852                promql: "cpu_usage{invalid=",
2853                expected_metric: None,
2854                expected_type: ValueType::Vector,
2855                should_error: true,
2856            },
2857            TestCase {
2858                name: "empty query",
2859                promql: "",
2860                expected_metric: None,
2861                expected_type: ValueType::Vector,
2862                should_error: true,
2863            },
2864            TestCase {
2865                name: "malformed brackets",
2866                promql: "cpu_usage[5m",
2867                expected_metric: None,
2868                expected_type: ValueType::Vector,
2869                should_error: true,
2870            },
2871        ];
2872
2873        for test_case in test_cases {
2874            let result = promql_parser::parser::parse(test_case.promql)
2875                .map(|expr| retrieve_metric_name_and_result_type(&expr));
2876
2877            if test_case.should_error {
2878                assert!(
2879                    result.is_err(),
2880                    "Test '{}' should have failed but succeeded with: {:?}",
2881                    test_case.name,
2882                    result
2883                );
2884            } else {
2885                let (metric_name, value_type) = result.unwrap_or_else(|e| {
2886                    panic!(
2887                        "Test '{}' should have succeeded but failed with error: {}",
2888                        test_case.name, e
2889                    )
2890                });
2891
2892                let expected_metric_name = test_case.expected_metric.map(|s| s.to_string());
2893                assert_eq!(
2894                    metric_name, expected_metric_name,
2895                    "Test '{}': metric name mismatch. Expected: {:?}, Got: {:?}",
2896                    test_case.name, expected_metric_name, metric_name
2897                );
2898
2899                assert_eq!(
2900                    value_type, test_case.expected_type,
2901                    "Test '{}': value type mismatch. Expected: {:?}, Got: {:?}",
2902                    test_case.name, test_case.expected_type, value_type
2903                );
2904            }
2905        }
2906    }
2907
2908    #[tokio::test]
2909    async fn test_get_all_column_names_uses_tag_columns() {
2910        let schema = Arc::new(Schema::new(vec![
2911            ColumnSchema::new(
2912                "greptime_timestamp",
2913                ConcreteDataType::timestamp_millisecond_datatype(),
2914                false,
2915            )
2916            .with_time_index(true),
2917            ColumnSchema::new("host", ConcreteDataType::string_datatype(), false),
2918            ColumnSchema::new("region", ConcreteDataType::string_datatype(), false),
2919            ColumnSchema::new("value", ConcreteDataType::float64_datatype(), true),
2920            ColumnSchema::new(
2921                DATA_SCHEMA_TSID_COLUMN_NAME,
2922                ConcreteDataType::uint64_datatype(),
2923                true,
2924            ),
2925        ]));
2926        let mut options = TableOptions::default();
2927        options.extra_options.insert(
2928            LOGICAL_TABLE_METADATA_KEY.to_string(),
2929            "physical_metrics".to_string(),
2930        );
2931        let meta = TableMetaBuilder::empty()
2932            .schema(schema)
2933            .primary_key_indices(vec![1, 2, 4])
2934            .engine("metric".to_string())
2935            .next_column_id(5)
2936            .options(options)
2937            .build()
2938            .unwrap();
2939        let table_info = TableInfoBuilder::default()
2940            .table_id(1024)
2941            .table_version(0 as TableVersion)
2942            .name("cpu_usage")
2943            .catalog_name(DEFAULT_CATALOG_NAME)
2944            .schema_name(DEFAULT_SCHEMA_NAME)
2945            .table_type(TableType::Base)
2946            .meta(meta)
2947            .build()
2948            .unwrap();
2949        let manager =
2950            MemoryCatalogManager::new_with_table(EmptyTable::from_table_info(&table_info));
2951
2952        let physical_schema = Arc::new(Schema::new(vec![
2953            ColumnSchema::new(
2954                "greptime_timestamp",
2955                ConcreteDataType::timestamp_millisecond_datatype(),
2956                false,
2957            )
2958            .with_time_index(true),
2959            ColumnSchema::new(
2960                "physical_only_tag",
2961                ConcreteDataType::string_datatype(),
2962                false,
2963            ),
2964            ColumnSchema::new(
2965                "physical_only_value",
2966                ConcreteDataType::float64_datatype(),
2967                true,
2968            ),
2969        ]));
2970        let mut physical_options = TableOptions::default();
2971        physical_options.extra_options.insert(
2972            store_api::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY.to_string(),
2973            String::new(),
2974        );
2975        let physical_meta = TableMetaBuilder::empty()
2976            .schema(physical_schema)
2977            .primary_key_indices(vec![1])
2978            .engine("metric".to_string())
2979            .next_column_id(3)
2980            .options(physical_options)
2981            .build()
2982            .unwrap();
2983        let physical_table_info = TableInfoBuilder::default()
2984            .table_id(1025)
2985            .table_version(0 as TableVersion)
2986            .name("physical_metrics")
2987            .catalog_name(DEFAULT_CATALOG_NAME)
2988            .schema_name(DEFAULT_SCHEMA_NAME)
2989            .table_type(TableType::Base)
2990            .meta(physical_meta)
2991            .build()
2992            .unwrap();
2993        let physical_table = EmptyTable::from_table_info(&physical_table_info);
2994        manager
2995            .register_table_sync(RegisterTableRequest {
2996                catalog: DEFAULT_CATALOG_NAME.to_string(),
2997                schema: DEFAULT_SCHEMA_NAME.to_string(),
2998                table_name: "physical_metrics".to_string(),
2999                table_id: 1025,
3000                table: physical_table,
3001            })
3002            .unwrap();
3003        let manager: CatalogManagerRef = manager;
3004
3005        let (labels, table_names) =
3006            get_all_column_names(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, &manager)
3007                .await
3008                .unwrap();
3009
3010        assert_eq!(
3011            labels,
3012            HashSet::from(["host".to_string(), "region".to_string()])
3013        );
3014        assert_eq!(table_names, vec!["cpu_usage"]);
3015
3016        let query_ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
3017        let (schemas, targets) =
3018            retrieve_schema_names(&query_ctx, manager.clone(), vec!["cpu_usage".to_string()])
3019                .await
3020                .unwrap();
3021        assert_eq!(schemas, vec![DEFAULT_SCHEMA_NAME]);
3022        assert_eq!(
3023            targets,
3024            PermissionTableTargets::Resolved(vec![PermissionTableTarget::new(
3025                DEFAULT_CATALOG_NAME,
3026                DEFAULT_SCHEMA_NAME,
3027                "cpu_usage",
3028            )])
3029        );
3030
3031        let (_, targets) = retrieve_schema_names(
3032            &query_ctx,
3033            manager.clone(),
3034            vec![r#"{__name__=~"cpu.*"}"#.to_string()],
3035        )
3036        .await
3037        .unwrap();
3038        assert_eq!(targets, PermissionTableTargets::Unresolved);
3039
3040        let (schemas, targets) = retrieve_schema_names(&query_ctx, manager.clone(), vec![])
3041            .await
3042            .unwrap();
3043        assert!(schemas.contains(&DEFAULT_SCHEMA_NAME.to_string()));
3044        assert_eq!(targets, PermissionTableTargets::Unresolved);
3045
3046        let (schemas, targets) = retrieve_schema_names(
3047            &query_ctx,
3048            manager.clone(),
3049            vec!["physical_metrics".to_string()],
3050        )
3051        .await
3052        .unwrap();
3053        assert!(schemas.is_empty());
3054        assert_eq!(
3055            targets,
3056            PermissionTableTargets::Resolved(vec![PermissionTableTarget::new(
3057                DEFAULT_CATALOG_NAME,
3058                DEFAULT_SCHEMA_NAME,
3059                "physical_metrics",
3060            )])
3061        );
3062
3063        let (schemas, targets) = retrieve_schema_names(
3064            &query_ctx,
3065            manager.clone(),
3066            vec!["missing".to_string(), "physical_metrics".to_string()],
3067        )
3068        .await
3069        .unwrap();
3070        assert!(schemas.is_empty());
3071        assert_eq!(
3072            targets,
3073            PermissionTableTargets::Resolved(vec![PermissionTableTarget::new(
3074                DEFAULT_CATALOG_NAME,
3075                DEFAULT_SCHEMA_NAME,
3076                "physical_metrics",
3077            )])
3078        );
3079
3080        assert_eq!(
3081            vec!["cpu_usage".to_string()],
3082            retrieve_table_names(
3083                &query_ctx,
3084                manager.clone(),
3085                vec!["missing".to_string(), r#"{__name__=~"cpu.*"}"#.to_string(),],
3086            )
3087            .await
3088            .unwrap()
3089        );
3090        assert!(
3091            retrieve_table_names(
3092                &query_ctx,
3093                manager.clone(),
3094                vec!["cpu_usage".to_string(), "{".to_string()],
3095            )
3096            .await
3097            .is_err()
3098        );
3099
3100        let (fields, table_names) = retrieve_field_names(
3101            &query_ctx,
3102            manager.clone(),
3103            vec!["physical_metrics".to_string()],
3104        )
3105        .await
3106        .unwrap();
3107        assert!(fields.is_empty());
3108        assert!(table_names.is_empty());
3109
3110        let (fields, table_names) = retrieve_field_names(&query_ctx, manager, vec![])
3111            .await
3112            .unwrap();
3113        assert_eq!(fields, HashSet::from(["value".to_string()]));
3114        assert_eq!(table_names, vec!["cpu_usage"]);
3115    }
3116
3117    #[tokio::test]
3118    async fn test_get_target_column_names_uses_selected_schema() {
3119        let manager = MemoryCatalogManager::with_default_setup();
3120        manager
3121            .register_schema_sync(RegisterSchemaRequest {
3122                catalog: DEFAULT_CATALOG_NAME.to_string(),
3123                schema: "private".to_string(),
3124            })
3125            .unwrap();
3126
3127        for (schema_name, tag_name, table_id) in [
3128            (DEFAULT_SCHEMA_NAME, "public_tag", 2048),
3129            ("private", "private_tag", 2049),
3130        ] {
3131            let schema = Arc::new(Schema::new(vec![
3132                ColumnSchema::new(
3133                    "greptime_timestamp",
3134                    ConcreteDataType::timestamp_millisecond_datatype(),
3135                    false,
3136                )
3137                .with_time_index(true),
3138                ColumnSchema::new(tag_name, ConcreteDataType::string_datatype(), false),
3139                ColumnSchema::new("value", ConcreteDataType::float64_datatype(), true),
3140            ]));
3141            let meta = TableMetaBuilder::empty()
3142                .schema(schema)
3143                .primary_key_indices(vec![1])
3144                .engine("metric".to_string())
3145                .next_column_id(3)
3146                .build()
3147                .unwrap();
3148            let table_info = TableInfoBuilder::default()
3149                .table_id(table_id)
3150                .table_version(0 as TableVersion)
3151                .name("cpu")
3152                .catalog_name(DEFAULT_CATALOG_NAME)
3153                .schema_name(schema_name)
3154                .table_type(TableType::Base)
3155                .meta(meta)
3156                .build()
3157                .unwrap();
3158            manager
3159                .register_table_sync(RegisterTableRequest {
3160                    catalog: DEFAULT_CATALOG_NAME.to_string(),
3161                    schema: schema_name.to_string(),
3162                    table_name: "cpu".to_string(),
3163                    table_id,
3164                    table: EmptyTable::from_table_info(&table_info),
3165                })
3166                .unwrap();
3167        }
3168
3169        let query_ctx = Arc::new(QueryContext::with(
3170            DEFAULT_CATALOG_NAME,
3171            DEFAULT_SCHEMA_NAME,
3172        ));
3173        let queries = [ParsedPromQuery::parse(
3174            PromQuery {
3175                query: r#"{__name__="cpu",__schema__="private"}"#.to_string(),
3176                ..Default::default()
3177            },
3178            &query_ctx,
3179        )
3180        .unwrap()];
3181        let targets = resolved_promql_targets(&queries, &query_ctx).unwrap();
3182        let manager: CatalogManagerRef = manager;
3183
3184        assert_eq!(
3185            get_target_column_names(&targets, &manager, &query_ctx)
3186                .await
3187                .unwrap(),
3188            HashSet::from(["private_tag".to_string()])
3189        );
3190    }
3191
3192    #[test]
3193    fn test_metric_name_discovery_uses_selected_schema() {
3194        let expr =
3195            promql_parser::parser::parse(r#"{__name__=~"cpu.*",__database__="private"}"#).unwrap();
3196        let discovery = find_metric_name_not_equal_matchers(&expr).unwrap().unwrap();
3197
3198        assert_eq!(discovery.schema.as_deref(), Some("private"));
3199        assert_eq!(discovery.name_matchers.len(), 1);
3200        assert_eq!(discovery.name_matchers[0].value, "cpu.*");
3201        assert!(matches!(discovery.name_matchers[0].op, MatchOp::Re(_)));
3202
3203        let expr = promql_parser::parser::parse(
3204            r#"{__name__=~"cpu.*",__database__="private",__schema__="public"}"#,
3205        )
3206        .unwrap();
3207        let discovery = find_metric_name_not_equal_matchers(&expr).unwrap().unwrap();
3208        assert_eq!(discovery.schema.as_deref(), Some("public"));
3209    }
3210
3211    #[test]
3212    fn test_metric_name_discovery_rejects_unsafe_selector_shapes() {
3213        for promql in [
3214            r#"{__name__="denied",__name__=~"no_such_.*"}"#,
3215            r#"{__name__=~"cpu.*" or job="api"}"#,
3216        ] {
3217            let expr = promql_parser::parser::parse(promql).unwrap();
3218            assert!(
3219                find_metric_name_not_equal_matchers(&expr)
3220                    .unwrap()
3221                    .is_none(),
3222                "{promql}"
3223            );
3224        }
3225
3226        let expr = promql_parser::parser::parse(r#"{__name__=~"cpu.*" or job="api"}"#).unwrap();
3227        assert_eq!(
3228            (PermissionTableTargets::Resolved(vec![]), 1),
3229            static_promql_targets(&expr, &QueryContext::arc()).unwrap()
3230        );
3231
3232        let expr =
3233            promql_parser::parser::parse(r#"allowed{job="api" or instance="host"}"#).unwrap();
3234        assert_eq!(
3235            (
3236                PermissionTableTargets::Resolved(vec![PermissionTableTarget::new(
3237                    "greptime", "public", "allowed",
3238                )]),
3239                0,
3240            ),
3241            static_promql_targets(&expr, &QueryContext::arc()).unwrap()
3242        );
3243    }
3244
3245    #[test]
3246    fn test_metric_name_discovery_ignores_or_matchers_on_named_selector() {
3247        let expr = promql_parser::parser::parse(
3248            r#"{__name__=~"cpu.*"} + allowed{job="api" or instance="host"}"#,
3249        )
3250        .unwrap();
3251
3252        let discovery = find_metric_name_not_equal_matchers(&expr).unwrap().unwrap();
3253        assert_eq!(discovery.name_matchers[0].value, "cpu.*");
3254    }
3255
3256    #[test]
3257    fn test_retrieve_exact_metric_name_from_selector() {
3258        assert_eq!(
3259            retrieve_exact_metric_name_from_promql(r#"cpu{host="a"}"#).as_deref(),
3260            Some("cpu")
3261        );
3262        assert_eq!(
3263            retrieve_exact_metric_name_from_promql(r#"{__name__="cpu",host="a"}"#).as_deref(),
3264            Some("cpu")
3265        );
3266        assert!(retrieve_exact_metric_name_from_promql(r#"{__name__=~"cpu.*"}"#).is_none());
3267    }
3268
3269    #[test]
3270    fn test_expand_metric_name_queries_preserves_schema_matcher() {
3271        let query = PromQuery {
3272            query: r#"{__name__=~"cpu.*",__database__="private"}"#.to_string(),
3273            ..Default::default()
3274        };
3275        let ctx = QueryContext::arc();
3276        let query = ParsedPromQuery::parse(query, &ctx).unwrap();
3277        let queries = expand_metric_name_queries(
3278            &query,
3279            vec!["cpu_user".to_string(), "cpu_system".to_string()],
3280        );
3281
3282        assert_eq!(queries.len(), 2);
3283        for (query, metric_name) in queries.iter().zip(["cpu_user", "cpu_system"]) {
3284            let PromqlExpr::VectorSelector(selector) = query.expr() else {
3285                panic!("expected vector selector");
3286            };
3287            assert!(selector.matchers.matchers.iter().any(|matcher| {
3288                matcher.name == METRIC_NAME
3289                    && matcher.op == MatchOp::Equal
3290                    && matcher.value == metric_name
3291            }));
3292            assert!(selector.matchers.matchers.iter().any(|matcher| {
3293                matcher.name == "__database__"
3294                    && matcher.op == MatchOp::Equal
3295                    && matcher.value == "private"
3296            }));
3297        }
3298    }
3299
3300    #[test]
3301    fn test_expand_metric_name_queries_preserves_exact_selector() {
3302        let query = PromQuery {
3303            query: r#"denied + {__name__=~"cpu.*"}"#.to_string(),
3304            ..Default::default()
3305        };
3306        let ctx = QueryContext::arc();
3307        let query = ParsedPromQuery::parse(query, &ctx).unwrap();
3308        let queries = expand_metric_name_queries(&query, vec!["cpu_user".to_string()]);
3309
3310        assert_eq!(queries.len(), 1);
3311        let expanded = queries[0].expr();
3312        let ctx = Arc::new(QueryContext::with("greptime", "public"));
3313        let (PermissionTableTargets::Resolved(mut targets), unresolved_selectors) =
3314            static_promql_targets(expanded, &ctx).unwrap()
3315        else {
3316            panic!("expected resolved targets");
3317        };
3318        assert_eq!(unresolved_selectors, 0);
3319        targets.sort_unstable_by(|left, right| left.table.cmp(&right.table));
3320        assert_eq!(
3321            targets,
3322            vec![
3323                PermissionTableTarget::new("greptime", "public", "cpu_user"),
3324                PermissionTableTarget::new("greptime", "public", "denied"),
3325            ]
3326        );
3327    }
3328
3329    #[test]
3330    fn test_static_promql_targets_keep_exact_selector_during_discovery() {
3331        let expr = promql_parser::parser::parse(
3332            r#"denied + {__name__=~"no_match_.*",__schema__="private"}"#,
3333        )
3334        .unwrap();
3335        let ctx = Arc::new(QueryContext::with("greptime", "public"));
3336
3337        assert_eq!(
3338            static_promql_targets(&expr, &ctx).unwrap(),
3339            (
3340                PermissionTableTargets::resolved(vec![PermissionTableTarget::new(
3341                    "greptime", "public", "denied",
3342                )]),
3343                1,
3344            )
3345        );
3346    }
3347
3348    #[test]
3349    fn test_static_promql_targets_count_unresolved_selectors() {
3350        let expr =
3351            promql_parser::parser::parse(r#"{__name__=~"no_such_metric"} or {job="api"}"#).unwrap();
3352        let ctx = Arc::new(QueryContext::with("greptime", "public"));
3353
3354        assert_eq!(
3355            static_promql_targets(&expr, &ctx).unwrap(),
3356            (PermissionTableTargets::resolved(Vec::new()), 2)
3357        );
3358    }
3359
3360    #[test]
3361    fn test_record_batches_to_labels_name_accepts_native_histogram_value() {
3362        let schema = Arc::new(Schema::new(vec![
3363            ColumnSchema::new("host", ConcreteDataType::string_datatype(), false),
3364            ColumnSchema::new(
3365                greptime_native_histogram(),
3366                native_histogram_value_type().clone(),
3367                true,
3368            ),
3369        ]));
3370        let batch = RecordBatch::new_empty(schema.clone());
3371        let batches = RecordBatches::try_new(schema.clone(), vec![batch]).unwrap();
3372        let mut labels = HashSet::new();
3373
3374        record_batches_to_labels_name(batches, &mut labels).unwrap();
3375
3376        assert!(labels.is_empty());
3377
3378        let histogram = NativeHistogram {
3379            schema: 0,
3380            zero_threshold: 0.001,
3381            sum: 1.0,
3382            reset_hint: CounterResetHint::Unknown,
3383            start_timestamp: None,
3384            custom_values: vec![],
3385            positive_spans: vec![],
3386            negative_spans: vec![],
3387            count: 1.0,
3388            zero_count: 1.0,
3389            positive_buckets: vec![],
3390            negative_buckets: vec![],
3391        };
3392        let histogram_array = build_histogram_array(&[Some(histogram)]);
3393        let histogram_array = histogram_array
3394            .as_any()
3395            .downcast_ref::<arrow::array::StructArray>()
3396            .unwrap()
3397            .clone();
3398        let ConcreteDataType::Struct(histogram_type) = native_histogram_value_type().clone() else {
3399            unreachable!("native histogram type must be a struct")
3400        };
3401        let batch = RecordBatch::new(
3402            schema.clone(),
3403            vec![
3404                Arc::new(StringVector::from(vec![Some("localhost")])) as _,
3405                Arc::new(StructVector::try_new(histogram_type, histogram_array).unwrap()) as _,
3406            ],
3407        )
3408        .unwrap();
3409        let batches = RecordBatches::try_new(schema, vec![batch]).unwrap();
3410
3411        record_batches_to_labels_name(batches, &mut labels).unwrap();
3412
3413        assert_eq!(
3414            labels,
3415            HashSet::from(["host".to_string(), greptime_native_histogram().to_string(),])
3416        );
3417    }
3418
3419    fn prometheus_metric_table_info(table_id: u32, name: &str) -> TableInfo {
3420        let schema = Arc::new(Schema::new(vec![
3421            ColumnSchema::new(
3422                "greptime_timestamp",
3423                ConcreteDataType::timestamp_millisecond_datatype(),
3424                false,
3425            )
3426            .with_time_index(true),
3427            ColumnSchema::new("host", ConcreteDataType::string_datatype(), false),
3428            ColumnSchema::new("value", ConcreteDataType::float64_datatype(), true),
3429        ]));
3430        let mut options = TableOptions::default();
3431        options.extra_options.insert(
3432            LOGICAL_TABLE_METADATA_KEY.to_string(),
3433            "greptime_physical_table".to_string(),
3434        );
3435        options
3436            .extra_options
3437            .insert(SEMANTIC_METRIC_TYPE.to_string(), "counter".to_string());
3438        options
3439            .extra_options
3440            .insert(SEMANTIC_METRIC_UNIT.to_string(), "By".to_string());
3441        let meta = TableMetaBuilder::empty()
3442            .schema(schema)
3443            .primary_key_indices(vec![1])
3444            .engine("metric".to_string())
3445            .next_column_id(3)
3446            .options(options)
3447            .build()
3448            .unwrap();
3449
3450        TableInfoBuilder::default()
3451            .table_id(table_id)
3452            .table_version(0 as TableVersion)
3453            .name(name)
3454            .catalog_name(DEFAULT_CATALOG_NAME)
3455            .schema_name(DEFAULT_SCHEMA_NAME)
3456            .table_type(TableType::Base)
3457            .meta(meta)
3458            .build()
3459            .unwrap()
3460    }
3461
3462    #[tokio::test]
3463    async fn test_retrieve_metric_metadata_uses_semantic_options() {
3464        let table_info = prometheus_metric_table_info(1025, "http_requests_total");
3465        let manager: CatalogManagerRef =
3466            MemoryCatalogManager::new_with_table(EmptyTable::from_table_info(&table_info));
3467        let query_ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
3468
3469        let metadata = retrieve_metric_metadata(&query_ctx, manager, &MetadataQuery::default())
3470            .await
3471            .unwrap();
3472
3473        assert_eq!(
3474            metadata.get("http_requests_total"),
3475            Some(&vec![PromMetadata {
3476                metric_type: "counter".to_string(),
3477                unit: "bytes".to_string(),
3478                help: String::new(),
3479            }])
3480        );
3481
3482        for (ucum, openmetrics) in [
3483            ("1", "ratios"),
3484            ("ms", "milliseconds"),
3485            ("m/s", "meters_per_second"),
3486            ("USD", "USD"),
3487        ] {
3488            let mut table_info = table_info.clone();
3489            table_info
3490                .meta
3491                .options
3492                .extra_options
3493                .insert(SEMANTIC_METRIC_UNIT.to_string(), ucum.to_string());
3494            assert_eq!(
3495                prometheus_metadata_from_table(&table_info).unit,
3496                openmetrics
3497            );
3498        }
3499
3500        for (metric_type, temporality, expected) in [
3501            ("updown_counter", None, "gauge"),
3502            ("gauge_histogram", None, "gaugehistogram"),
3503            ("summary", None, "summary"),
3504            ("mixed", None, "unknown"),
3505            ("counter", Some("delta"), "unknown"),
3506            ("histogram", Some("delta"), "unknown"),
3507        ] {
3508            let mut table_info = table_info.clone();
3509            table_info
3510                .meta
3511                .options
3512                .extra_options
3513                .insert(SEMANTIC_METRIC_TYPE.to_string(), metric_type.to_string());
3514            if let Some(temporality) = temporality {
3515                table_info.meta.options.extra_options.insert(
3516                    SEMANTIC_METRIC_TEMPORALITY.to_string(),
3517                    temporality.to_string(),
3518                );
3519            }
3520            assert_eq!(
3521                prometheus_metadata_from_table(&table_info).metric_type,
3522                expected
3523            );
3524        }
3525    }
3526
3527    #[tokio::test]
3528    async fn test_metadata_query_checks_permissions_and_filters_tables() {
3529        let allowed = prometheus_metric_table_info(1025, "allowed");
3530        let denied = prometheus_metric_table_info(1026, "denied");
3531        let manager = MemoryCatalogManager::new_with_table(EmptyTable::from_table_info(&allowed));
3532        manager
3533            .register_table_sync(RegisterTableRequest {
3534                catalog: DEFAULT_CATALOG_NAME.to_string(),
3535                schema: DEFAULT_SCHEMA_NAME.to_string(),
3536                table_name: denied.name.clone(),
3537                table_id: denied.table_id(),
3538                table: EmptyTable::from_table_info(&denied),
3539            })
3540            .unwrap();
3541        let query_ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
3542
3543        let metadata = retrieve_metric_metadata(
3544            &query_ctx,
3545            manager.clone(),
3546            &MetadataQuery {
3547                metric: Some("allowed".to_string()),
3548                ..Default::default()
3549            },
3550        )
3551        .await
3552        .unwrap();
3553        assert_eq!(metadata.len(), 1);
3554        assert!(metadata.contains_key("allowed"));
3555
3556        let response = metadata_query(
3557            State(Arc::new(TestPrometheusHandler {
3558                catalog_manager: manager.clone(),
3559                deny_operation: true,
3560                denied_table: None,
3561                metric_names: Vec::new(),
3562                queries: Mutex::new(Vec::new()),
3563            })),
3564            Query(MetadataQuery::default()),
3565            Extension(query_ctx.clone()),
3566        )
3567        .await;
3568        assert_eq!(Some(StatusCode::PermissionDenied), response.status_code);
3569
3570        let handler: PrometheusHandlerRef = Arc::new(TestPrometheusHandler {
3571            catalog_manager: manager,
3572            deny_operation: false,
3573            denied_table: Some("denied"),
3574            metric_names: Vec::new(),
3575            queries: Mutex::new(Vec::new()),
3576        });
3577        let response = metadata_query(
3578            State(handler.clone()),
3579            Query(MetadataQuery::default()),
3580            Extension(query_ctx.clone()),
3581        )
3582        .await;
3583        assert!(response.status_code.is_none());
3584        let PrometheusResponse::Metadata(metadata) = response.data else {
3585            panic!("expected metadata response");
3586        };
3587        assert_eq!(
3588            metadata.keys().cloned().collect::<Vec<_>>(),
3589            vec!["allowed".to_string()]
3590        );
3591
3592        let response = metadata_query(
3593            State(handler),
3594            Query(MetadataQuery {
3595                metric: Some("denied".to_string()),
3596                ..Default::default()
3597            }),
3598            Extension(query_ctx),
3599        )
3600        .await;
3601        assert_eq!(Some(StatusCode::PermissionDenied), response.status_code);
3602    }
3603}