fix(prom): align remote read label matcher semantics (#9633)

* fix(prom): align remote read label matcher semantics

Signed-off-by: fys <fengys1996@gmail.com>

* chore: optimize comment of validate_matchers

* fix(prom): preserve existing binary label regex handling

Signed-off-by: fys <fengys1996@gmail.com>

* refactor: condition

* test(prom): simplify remote read matcher tests and clarify comments

* fix(prom): return binary remote read labels as UTF-8

* fix(prom): reject binary labels in remote read

* refactor(prom): align remote read null label handling with PromQL

Signed-off-by: fys <fengys1996@gmail.com>

* refactor(prom): simplify null handling in label matcher branches

Signed-off-by: fys <fengys1996@gmail.com>

* fix(prom): restrict remote read labels to string types

Signed-off-by: fys <fengys1996@gmail.com>

* test(prom): use string label in default value column test

Signed-off-by: fys <fengys1996@gmail.com>

* fix: code review

---------

Signed-off-by: fys <fengys1996@gmail.com>
This commit is contained in:
fys
2026-10-19 06:58:40 +00:00
committed by GitHub
parent cca95b2637
commit 4e8bc6df86
5 changed files with 389 additions and 28 deletions
+8
View File
@@ -231,6 +231,13 @@ pub enum Error {
source: servers::error::Error,
},
#[snafu(display("Invalid Prometheus remote read schema: {detail}"))]
InvalidPromRemoteReadSchema {
detail: String,
#[snafu(implicit)]
location: Location,
},
#[snafu(display("Failed to create logical plan for prometheus query"))]
PromStoreRemoteQueryPlan {
#[snafu(implicit)]
@@ -394,6 +401,7 @@ impl ErrorExt for Error {
| Error::SchemaExists { .. }
| Error::ColumnNotFound { .. }
| Error::AmbiguousValueColumn { .. }
| Error::InvalidPromRemoteReadSchema { .. }
| Error::UnsupportedFormat { .. }
| Error::IllegalAuthConfig { .. }
| Error::ColumnNoneDefaultValue { .. }
+2 -2
View File
@@ -3336,7 +3336,7 @@ mod tests {
false,
)
.with_time_index(true),
ColumnSchema::new("extra_field", ConcreteDataType::float64_datatype(), false),
ColumnSchema::new("extra_field", ConcreteDataType::string_datatype(), false),
ColumnSchema::new(
greptime_value(),
ConcreteDataType::float64_datatype(),
@@ -3347,7 +3347,7 @@ mod tests {
schema,
vec![
Arc::new(TimestampMillisecondVector::from_vec(vec![1000, 2000, 3000])) as VectorRef,
Arc::new(Float64Vector::from_vec(vec![99.0, 99.0, 99.0])) as VectorRef,
Arc::new(StringVector::from(vec!["99", "99", "99"])) as VectorRef,
Arc::new(Float64Vector::from_vec(vec![1.0, 2.0, 3.0])) as VectorRef,
],
)
+80 -4
View File
@@ -35,6 +35,8 @@ use common_query::Output;
use common_query::prelude::{GREPTIME_PHYSICAL_TABLE, greptime_value};
use common_recordbatch::RecordBatches;
use common_telemetry::{debug, tracing};
use datatypes::data_type::ConcreteDataType;
use datatypes::schema::Schema;
use operator::insert::{
AutoCreateTableType, InserterRef, PerTableSemanticIndex, apply_per_table_semantic_options,
build_create_table_expr, fill_table_options_for_create, parse_per_table_semantic_index,
@@ -52,7 +54,7 @@ use servers::query_handler::{
PromStoreProtocolHandler, PromStoreProtocolHandlerRef, PromStoreResponse,
};
use session::context::QueryContextRef;
use snafu::{OptionExt, ResultExt};
use snafu::{OptionExt, ResultExt, ensure};
use store_api::metric_engine_consts::{METRIC_ENGINE_NAME, PHYSICAL_TABLE_METADATA_KEY};
use store_api::mito_engine_options::SST_FORMAT_KEY;
use table::TableRef;
@@ -61,7 +63,7 @@ use tracing::instrument;
use crate::error::{
AmbiguousValueColumnSnafu, CatalogSnafu, ColumnNotFoundSnafu, ExecLogicalPlanSnafu,
PromStoreRemoteQueryPlanSnafu, ReadTableSnafu, Result,
InvalidPromRemoteReadSchemaSnafu, PromStoreRemoteQueryPlanSnafu, ReadTableSnafu, Result,
};
use crate::instance::Instance;
@@ -172,7 +174,7 @@ async fn to_query_result(
})
}
fn resolve_column_names(table_name: &str, table: &TableRef) -> Result<String> {
fn resolve_value_column_name(table_name: &str, table: &TableRef) -> Result<String> {
let columns = table
.field_columns()
.map(|column| column.name)
@@ -198,6 +200,29 @@ fn resolve_column_names(table_name: &str, table: &TableRef) -> Result<String> {
}
}
fn ensure_string_label_columns(
schema: &Schema,
timestamp_column_name: &str,
value_column_name: &str,
) -> Result<()> {
for column in schema.column_schemas() {
if column.name == timestamp_column_name || column.name == value_column_name {
continue;
}
// All remaining columns are emitted as labels, including non-key columns.
ensure!(
matches!(column.data_type, ConcreteDataType::String(_)),
InvalidPromRemoteReadSchemaSnafu {
detail: format!(
"Prometheus remote read only supports string labels: column '{}' has type {:?}",
column.name, column.data_type
),
}
);
}
Ok(())
}
impl Instance {
#[tracing::instrument(skip_all)]
async fn handle_remote_query(
@@ -232,7 +257,9 @@ impl Instance {
.name
.clone();
let value_column_name = resolve_column_names(table_name, &table)?;
let value_column_name = resolve_value_column_name(table_name, &table)?;
ensure_string_label_columns(&table.schema(), &timestamp_column_name, &value_column_name)?;
let dataframe = self
.query_engine
@@ -817,10 +844,59 @@ mod tests {
use std::sync::Arc;
use api::prom_store::remote::LabelMatcher;
use common_error::ext::ErrorExt;
use common_error::status_code::StatusCode;
use session::context::QueryContext;
use super::*;
#[test]
fn test_remote_read_label_types() {
use datatypes::schema::ColumnSchema;
for (label_type, supported) in [
(ConcreteDataType::string_datatype(), true),
(ConcreteDataType::large_string_datatype(), true),
(ConcreteDataType::utf8_view_datatype(), true),
(ConcreteDataType::binary_datatype(), false),
(ConcreteDataType::binary_view_datatype(), false),
(ConcreteDataType::json_datatype(), false),
(ConcreteDataType::vector_datatype(3), false),
(ConcreteDataType::int64_datatype(), false),
(ConcreteDataType::boolean_datatype(), false),
] {
let schema = Schema::new(vec![
ColumnSchema::new(
"ts",
ConcreteDataType::timestamp_millisecond_datatype(),
false,
),
ColumnSchema::new("value", label_type.clone(), true),
ColumnSchema::new("label", ConcreteDataType::string_datatype(), true),
]);
// Value columns are validated separately, regardless of their type.
ensure_string_label_columns(&schema, "ts", "value").unwrap();
let result = ensure_string_label_columns(&schema, "ts", "label");
assert_eq!(supported, result.is_ok(), "{label_type:?}");
if let Err(err) = result {
assert!(matches!(
err,
crate::error::Error::InvalidPromRemoteReadSchema { .. }
));
assert_eq!(err.status_code(), StatusCode::InvalidArguments);
assert!(
err.to_string()
.starts_with("Invalid Prometheus remote read schema: ")
);
assert!(
err.to_string()
.contains("only supports string labels: column 'value'")
);
}
}
}
#[test]
fn test_auto_create_table_type_for_prom_remote_write_metric_engine() {
let mut query_ctx = QueryContext::with(