fix(metric-engine): validate logical projection indices (#8535)

* fix(metric-engine): validate logical projection indices

Signed-off-by: discord9 <discord9@163.com>

* perf(metric-engine): avoid cloning projection names

Signed-off-by: discord9 <discord9@163.com>

---------

Signed-off-by: discord9 <discord9@163.com>
This commit is contained in:
discord9
2026-07-24 16:25:09 +08:00
committed by GitHub
parent 8ab5e68d48
commit 8afb960dda
+42 -4
View File
@@ -25,7 +25,8 @@ use store_api::storage::{RegionId, ScanRequest, SequenceNumber};
use crate::engine::MetricEngineInner;
use crate::error::{
InvalidMetadataSnafu, LogicalRegionNotFoundSnafu, MitoReadOperationSnafu, Result,
InvalidMetadataSnafu, InvalidRequestSnafu, LogicalRegionNotFoundSnafu, MitoReadOperationSnafu,
Result,
};
use crate::metrics::MITO_OPERATION_ELAPSED;
use crate::utils;
@@ -192,8 +193,16 @@ impl MetricEngineInner {
.await?;
let projected_logical_names = origin_projection
.iter()
.map(|i| all_logical_columns[*i].clone())
.collect::<Vec<_>>();
.map(|&index| {
all_logical_columns
.get(index)
.map(String::as_str)
.with_context(|| InvalidRequestSnafu {
region_id: logical_region_id,
reason: format!("projection index {index} is out of bounds"),
})
})
.collect::<Result<Vec<_>>>()?;
// generate physical projection
let mut physical_projection = Vec::with_capacity(origin_projection.len());
@@ -206,7 +215,7 @@ impl MetricEngineInner {
for name in projected_logical_names {
// Safety: logical columns is a strict subset of physical columns
physical_projection.push(physical_metadata.column_index_by_name(&name).unwrap());
physical_projection.push(physical_metadata.column_index_by_name(name).unwrap());
}
Ok(physical_projection)
@@ -302,6 +311,8 @@ impl MetricEngineInner {
#[cfg(test)]
mod test {
use common_error::ext::ErrorExt;
use common_error::status_code::StatusCode;
use store_api::region_request::RegionRequest;
use super::*;
@@ -309,6 +320,33 @@ mod test {
TestEnv, alter_logical_region_add_tag_columns, create_logical_region_request,
};
#[tokio::test]
async fn test_invalid_logical_projection() {
let env = TestEnv::new().await;
env.init_metric_region().await;
let logical_region_id = env.default_logical_region_id();
let invalid_index = usize::MAX;
let request = ScanRequest {
projection_input: Some(vec![invalid_index].into()),
..Default::default()
};
let error =
match RegionEngine::handle_query(&env.metric(), logical_region_id, request).await {
Ok(_) => panic!("invalid logical projection unexpectedly succeeded"),
Err(error) => error,
};
assert_eq!(error.status_code(), StatusCode::InvalidArguments);
assert!(
error.to_string().contains(&format!(
"projection index {invalid_index} is out of bounds"
)),
"unexpected error: {error}"
);
}
#[tokio::test]
async fn test_transform_scan_req() {
let env = TestEnv::new().await;