feat: name label regex matcher in label values api (#6799)

* test: add failing test for #6791

* test: add support for = and =~

* fix: lint

* fix: code merge issue

Signed-off-by: Ning Sun <sunning@greptime.com>

---------

Signed-off-by: Ning Sun <sunning@greptime.com>
This commit is contained in:
Ning Sun
2025-08-25 16:48:53 +08:00
committed by GitHub
parent d5575d3fa4
commit 48572d18a8
2 changed files with 125 additions and 43 deletions

View File

@@ -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<String> {
Some(name)
}
async fn retrieve_table_names(
query_ctx: &QueryContext,
catalog_manager: CatalogManagerRef,
matches: Vec<String>,
) -> Result<Vec<String>> {
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,

View File

@@ -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::<PrometheusResponse>(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::<PrometheusJsonResponse>().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::<PrometheusResponse>(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::<PrometheusJsonResponse>().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::<PrometheusResponse>(json!([
"demo",
"demo_metrics",
"demo_metrics_with_nanos",
]))
.unwrap()
);
// buildinfo