From d4a3f67f65fedc9b4c8bf0733059c8ec3fa6d779 Mon Sep 17 00:00:00 2001 From: discord9 <55937128+discord9@users.noreply.github.com> Date: Thu, 15 Oct 2026 20:37:16 +0800 Subject: [PATCH] fix(datatypes): preserve series frame field names Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --- src/datatypes/src/series/frame.rs | 8 ++-- src/datatypes/src/series/frame/tests.rs | 54 ++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/datatypes/src/series/frame.rs b/src/datatypes/src/series/frame.rs index 2fd242c9b76..d9f9c1ee347 100644 --- a/src/datatypes/src/series/frame.rs +++ b/src/datatypes/src/series/frame.rs @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Finite Arrow batches for raw series chunks. Field names are ordinal (`f0`, `f1`, ...); -//! callers bind those positions to their logical names, which this frame cannot authenticate. +//! Finite Arrow batches for raw series chunks. Field names preserve the logical series schema in order. #[cfg(test)] mod tests; @@ -50,7 +49,7 @@ pub enum RawLabelMode { Explicit, } -/// Builds the typed schema for a raw frame using ordinal domain fields. +/// Builds the typed schema for a raw frame, preserving logical field names and order. pub fn raw_series_frame_schema(schema: &SeriesSchema, mode: RawLabelMode) -> Result { if schema.eval_grid.is_some() { return invalid_series("raw series schema cannot have an evaluation grid"); @@ -88,8 +87,7 @@ pub fn raw_series_frame_schema(schema: &SeriesSchema, mode: RawLabelMode) -> Res let fields: Fields = schema .fields .iter() - .enumerate() - .map(|(i, _)| Field::new(format!("f{i}"), payload.clone(), false)) + .map(|field| Field::new(field.name.as_ref(), payload.clone(), false)) .collect(); let mut columns = vec![ Field::new("read_key", DataType::Binary, false), diff --git a/src/datatypes/src/series/frame/tests.rs b/src/datatypes/src/series/frame/tests.rs index 6247c4924ee..7edc38168b5 100644 --- a/src/datatypes/src/series/frame/tests.rs +++ b/src/datatypes/src/series/frame/tests.rs @@ -339,6 +339,58 @@ fn sliced_histogram_frames_densify_without_duplicating_shared_table() { ); } +#[test] +fn raw_frame_field_names_bind_payloads_to_their_schema() { + let schema = test_schema(&["cpu_usage", "cpu_idle"]); + let fields = SeriesFields::try_from_vec(vec![ + Samples::from_float(Float64Array::from(vec![Some(1.0), Some(2.0)])), + Samples::from_float(Float64Array::from(vec![Some(10.0), Some(20.0)])), + ]) + .unwrap(); + + for mode in [RawLabelMode::Key, RawLabelMode::Explicit] { + let metric = (mode == RawLabelMode::Explicit).then(|| metric(Some("host"), &[])); + let chunk = RawSeriesChunk::try_new( + &schema, + Buffer::from(vec![0xaa_u8]), + metric, + RawTimeAxis::try_new(vec![100, 200].into()).unwrap(), + fields.clone(), + true, + ) + .unwrap(); + let batch = &pack_raw_series_chunk(&schema, mode, &chunk).unwrap()[0]; + let payloads = batch + .column(3) + .as_any() + .downcast_ref::() + .unwrap(); + assert_eq!( + payloads + .fields() + .iter() + .map(|field| field.name().as_str()) + .collect::>(), + ["cpu_usage", "cpu_idle"] + ); + + let decoded = unpack_raw_series_frame(&schema, mode, batch).unwrap(); + assert_eq!(decoded[0].read_key.as_slice(), &[0xaa]); + assert!(decoded[0].is_last); + let RawTimeAxis::Explicit(times) = &decoded[0].time; + assert_eq!(times.as_ref(), &[100, 200]); + assert_float_bits(decoded[0].fields.get(0).unwrap(), &[Some(1.0), Some(2.0)]); + assert_float_bits(decoded[0].fields.get(1).unwrap(), &[Some(10.0), Some(20.0)]); + + assert!( + unpack_raw_series_frame(&test_schema(&["cpu_idle", "cpu_usage"]), mode, batch).is_err() + ); + assert!( + unpack_raw_series_frame(&test_schema(&["cpu_usage", "cpu_busy"]), mode, batch).is_err() + ); + } +} + #[test] fn schema_and_key_explicit_round_trips_preserve_identity_and_bits() { let schema = test_schema(&["value"]); @@ -400,7 +452,7 @@ fn schema_and_key_explicit_round_trips_preserve_identity_and_bits() { ), Field::new( "fields", - DataType::Struct(vec![Field::new("f0", payload, false)].into()), + DataType::Struct(vec![Field::new("value", payload, false)].into()), false, ), ],