fix: qualify HistogramFold schema (#8157)

* test: add regression test for binary op on histogram_quantile (#8144)

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: preserve column qualifiers in HistogramFold output schema (#8144)

Signed-off-by: evenyag <realevenyag@gmail.com>

---------

Signed-off-by: evenyag <realevenyag@gmail.com>
This commit is contained in:
Yingwen
2026-05-25 15:40:48 +08:00
committed by GitHub
parent 8f7951c5bd
commit a25152664b
4 changed files with 187 additions and 9 deletions

View File

@@ -322,16 +322,18 @@ impl HistogramFold {
/// Transform the schema
///
/// - `le` will be removed
///
/// Column qualifiers are preserved so downstream plan nodes can keep
/// referencing the columns by their original qualified names.
fn convert_schema(
input_schema: &DFSchemaRef,
le_column: &str,
) -> DataFusionResult<DFSchemaRef> {
let fields = input_schema.fields();
// safety: those fields are checked in `check_schema()`
let mut new_fields = Vec::with_capacity(fields.len() - 1);
for f in fields {
if f.name() != le_column {
new_fields.push((None, f.clone()));
let mut new_fields = Vec::with_capacity(input_schema.fields().len() - 1);
for (qualifier, field) in input_schema.iter() {
if field.name() != le_column {
new_fields.push((qualifier.cloned(), field.clone()));
}
}
Ok(Arc::new(DFSchema::new_with_metadata(

View File

@@ -4023,12 +4023,15 @@ impl PromPlanner {
return Ok(plan);
}
// Preserve column qualifiers so downstream plan nodes can keep referencing
// the columns by their original qualified names.
let project_exprs = schema
.fields()
.iter()
.filter(|field| field.name() != DATA_SCHEMA_TSID_COLUMN_NAME)
.map(|field| Ok(DfExpr::Column(Column::from_name(field.name().clone()))))
.collect::<Result<Vec<_>>>()?;
.filter(|(_, field)| field.name() != DATA_SCHEMA_TSID_COLUMN_NAME)
.map(|(qualifier, field)| {
DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()))
})
.collect::<Vec<_>>();
LogicalPlanBuilder::from(plan)
.project(project_exprs)
@@ -6005,6 +6008,39 @@ mod test {
.unwrap();
}
#[tokio::test]
async fn test_histogram_quantile_binary_op() {
let mut eval_stmt = EvalStmt {
expr: PromExpr::NumberLiteral(NumberLiteral { val: 1.0 }),
start: UNIX_EPOCH,
end: UNIX_EPOCH
.checked_add(Duration::from_secs(100_000))
.unwrap(),
interval: Duration::from_secs(5),
lookback_delta: Duration::from_secs(1),
};
// Arithmetic applied to a histogram_quantile() result. Regression for #8144:
// HistogramFold used to drop the input column qualifiers, so the binary-op
// projection failed to resolve the qualified tag column.
let case = r#"histogram_quantile(0.5, sum by (le, pod) (rate(http_request_duration_seconds_bucket[5m]))) + 0"#;
let prom_expr = parser::parse(case).unwrap();
eval_stmt.expr = prom_expr;
let table_provider = build_test_table_provider_with_fields(
&[(
DEFAULT_SCHEMA_NAME.to_string(),
"http_request_duration_seconds_bucket".to_string(),
)],
&["pod", "le"],
)
.await;
// Should plan without a "No field named ..." error.
let _ = PromPlanner::stmt_to_plan(table_provider, &eval_stmt, &build_query_engine_state())
.await
.unwrap();
}
#[tokio::test]
async fn test_parse_and_operator() {
let mut eval_stmt = EvalStmt {