1use 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#[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#[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#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
107pub struct PromNativeHistogram {
108 pub count: String,
110 pub sum: String,
112 pub buckets: Vec<(u8, String, String, String)>,
118}
119
120#[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#[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#[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 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 }
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#[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(¶ms.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) = ¶ms.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(), ¶ms).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
341macro_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 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 if let Some(db) = ¶ms.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
486async 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 if let Some(db) = ¶ms.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 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
616async 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
647impl<'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
679macro_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 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(¶ms.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 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 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 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
829async 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 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
931async 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 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
988struct RowWriter {
995 template: HashMap<Column, Option<String>>,
998 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 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
1254fn 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 value_columns.iter().all(|c| c.is_null(row_index)) {
1288 continue;
1289 }
1290
1291 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
1314pub(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
1327pub(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 if metric_names.len() == 1 {
1363 metric_names.into_iter().next()
1364 } else {
1365 None
1366 }
1367}
1368
1369fn 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 | token::T_LOR | token::T_LUNLESS ) {
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
1462fn 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 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
1610fn 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(¶ms.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 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
1874fn 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 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 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 _ => 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) = ¶ms.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 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 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 if let Some(db) = ¶ms.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 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 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 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 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 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 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 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 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 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}