diff --git a/crates/s3select-query/Cargo.toml b/crates/s3select-query/Cargo.toml index b786f2197..3fbf7778c 100644 --- a/crates/s3select-query/Cargo.toml +++ b/crates/s3select-query/Cargo.toml @@ -57,6 +57,8 @@ tracing = { workspace = true } [dev-dependencies] rustfs-test-utils = { workspace = true, features = ["put-object-commit-barrier"] } +# Match the streaming inference's field order in Arrow's JSON reference. +serde_json = { workspace = true, features = ["preserve_order"] } [lib] doctest = false diff --git a/crates/s3select-query/src/dispatcher/json_document_schema.rs b/crates/s3select-query/src/dispatcher/json_document_schema.rs index d61d9ff0b..1f8c33c46 100644 --- a/crates/s3select-query/src/dispatcher/json_document_schema.rs +++ b/crates/s3select-query/src/dispatcher/json_document_schema.rs @@ -430,7 +430,7 @@ mod tests { const TEST_SCHEMA_LIMIT: usize = 4 * 1024 * 1024; - fn assert_matches_arrow(input: &[u8]) { + fn assert_matches_arrow(input: &[u8]) -> Schema { let records = input.iter().filter(|byte| **byte == b'\n').count(); let (expected, expected_records) = arrow_infer_json_schema(io::BufReader::new(input), Some(records)) .expect("Arrow should infer the compatibility fixture"); @@ -438,6 +438,33 @@ mod tests { .expect("streaming inference should accept the compatibility fixture"); assert_eq!(actual_records, expected_records); assert_eq!(actual, expected); + actual + } + + #[test] + fn inference_preserves_first_seen_field_order_for_nested_objects() { + let schema = assert_matches_arrow( + br#"{"z":1,"nested":{"z":true,"a":"first"}} +{"a":2,"nested":{"m":3}} +"#, + ); + let expected = Schema::new(vec![ + Field::new("z", DataType::Int64, true), + Field::new( + "nested", + DataType::Struct( + vec![ + Field::new("z", DataType::Boolean, true), + Field::new("a", DataType::Utf8, true), + Field::new("m", DataType::Int64, true), + ] + .into(), + ), + true, + ), + Field::new("a", DataType::Int64, true), + ]); + assert_eq!(schema, expected); } #[test]