mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-24 04:42:28 +00:00
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:
@@ -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 { .. }
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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(), ×tamp_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(
|
||||
|
||||
Reference in New Issue
Block a user