diff --git a/src/servers/src/http/prometheus.rs b/src/servers/src/http/prometheus.rs index 529f4e952a..32042b5409 100644 --- a/src/servers/src/http/prometheus.rs +++ b/src/servers/src/http/prometheus.rs @@ -1002,30 +1002,11 @@ pub async fn label_values_query( if label_name == METRIC_NAME_LABEL { let catalog_manager = handler.catalog_manager(); - let mut tables_stream = catalog_manager.tables(&catalog, &schema, Some(&query_ctx)); - let mut table_names = Vec::new(); - while let Some(table) = tables_stream.next().await { - // filter out physical tables - match table { - Ok(table) => { - if table - .table_info() - .meta - .options - .extra_options - .contains_key(PHYSICAL_TABLE_METADATA_KEY) - { - continue; - } - table_names.push(table.table_info().name.clone()); - } - Err(e) => { - return PrometheusJsonResponse::error(e.status_code(), e.output_msg()); - } - } - } - table_names.sort_unstable(); + let mut table_names = try_call_return_response!( + retrieve_table_names(&query_ctx, catalog_manager, params.matches.0).await + ); + truncate_results(&mut table_names, params.limit); return PrometheusJsonResponse::success(PrometheusResponse::LabelValues(table_names)); } else if label_name == FIELD_NAME_LABEL { @@ -1040,17 +1021,11 @@ pub async fn label_values_query( } else if label_name == SCHEMA_LABEL || label_name == DATABASE_LABEL { let catalog_manager = handler.catalog_manager(); - match retrieve_schema_names(&query_ctx, catalog_manager, params.matches.0).await { - Ok(mut schema_names) => { - truncate_results(&mut schema_names, params.limit); - return PrometheusJsonResponse::success(PrometheusResponse::LabelValues( - schema_names, - )); - } - Err(e) => { - return PrometheusJsonResponse::error(e.status_code(), e.output_msg()); - } - } + let mut schema_names = try_call_return_response!( + retrieve_schema_names(&query_ctx, catalog_manager, params.matches.0).await + ); + truncate_results(&mut schema_names, params.limit); + return PrometheusJsonResponse::success(PrometheusResponse::LabelValues(schema_names)); } let queries = params.matches.0; @@ -1136,6 +1111,77 @@ fn take_metric_name(selector: &mut VectorSelector) -> Option { Some(name) } +async fn retrieve_table_names( + query_ctx: &QueryContext, + catalog_manager: CatalogManagerRef, + matches: Vec, +) -> Result> { + let catalog = query_ctx.current_catalog(); + let schema = query_ctx.current_schema(); + + let mut tables_stream = catalog_manager.tables(catalog, &schema, Some(query_ctx)); + let mut table_names = Vec::new(); + + // we only provide very limited support for matcher against __name__ + let name_matcher = matches + .first() + .and_then(|matcher| promql_parser::parser::parse(matcher).ok()) + .and_then(|expr| { + if let PromqlExpr::VectorSelector(vector_selector) = expr { + let matchers = vector_selector.matchers.matchers; + for matcher in matchers { + if matcher.name == METRIC_NAME_LABEL { + return Some(matcher); + } + } + + None + } else { + None + } + }); + + while let Some(table) = tables_stream.next().await { + let table = table.context(CatalogSnafu)?; + if table + .table_info() + .meta + .options + .extra_options + .contains_key(PHYSICAL_TABLE_METADATA_KEY) + { + continue; + } + + let table_name = &table.table_info().name; + + if let Some(matcher) = &name_matcher { + match &matcher.op { + MatchOp::Equal => { + if table_name == &matcher.value { + table_names.push(table_name.clone()); + } + } + MatchOp::Re(reg) => { + if reg.is_match(table_name) { + table_names.push(table_name.clone()); + } + } + _ => { + // != and !~ are not supported: + // vector must contains at least one non-empty matcher + table_names.push(table_name.clone()); + } + } + } else { + table_names.push(table_name.clone()); + } + } + + table_names.sort_unstable(); + Ok(table_names) +} + async fn retrieve_field_names( query_ctx: &QueryContext, manager: CatalogManagerRef, diff --git a/tests-integration/tests/http.rs b/tests-integration/tests/http.rs index dde7ca23e3..39e199b81e 100644 --- a/tests-integration/tests/http.rs +++ b/tests-integration/tests/http.rs @@ -1104,15 +1104,51 @@ pub async fn test_prom_http_api(store_type: StorageType) { assert!(prom_resp.error_type.is_none()); assert_eq!( prom_resp.data, - PrometheusResponse::Labels(vec![ - "demo".to_string(), - "demo_metrics".to_string(), - "demo_metrics_with_nanos".to_string(), - "logic_table".to_string(), - "mito".to_string(), - "multi_labels".to_string(), - "numbers".to_string() - ]) + serde_json::from_value::(json!([ + "demo", + "demo_metrics", + "demo_metrics_with_nanos", + "logic_table", + "mito", + "multi_labels", + "numbers" + ])) + .unwrap() + ); + + // query `__name__` + let res = client + .get("/v1/prometheus/api/v1/label/__name__/values?match[]={__name__=\"demo\"}") + .send() + .await; + assert_eq!(res.status(), StatusCode::OK); + let prom_resp = res.json::().await; + assert_eq!(prom_resp.status, "success"); + assert!(prom_resp.error.is_none()); + assert!(prom_resp.error_type.is_none()); + assert_eq!( + prom_resp.data, + serde_json::from_value::(json!(["demo",])).unwrap() + ); + + // query `__name__` + let res = client + .get("/v1/prometheus/api/v1/label/__name__/values?match[]={__name__=~\".*demo.*\"}") + .send() + .await; + assert_eq!(res.status(), StatusCode::OK); + let prom_resp = res.json::().await; + assert_eq!(prom_resp.status, "success"); + assert!(prom_resp.error.is_none()); + assert!(prom_resp.error_type.is_none()); + assert_eq!( + prom_resp.data, + serde_json::from_value::(json!([ + "demo", + "demo_metrics", + "demo_metrics_with_nanos", + ])) + .unwrap() ); // buildinfo