fix: guard structured JSON alignment paths against legacy JSONB columns (#8323)

* Initial plan

* fix: guard structured json alignment to fix Clippy CI failure

- Add `is_structured_json_field` function that only returns true for
  fields with both JSON extension type AND Struct Arrow data type
- Replace all usages of `is_json_extension_type` / `has_json_extension_field`
  with `is_structured_json_field` to prevent legacy JSONB binary columns
  from entering structured JSON alignment paths
- Fix logic in `FlatProjectionMapper::new_with_read_columns` to guard
  JSON type hint concretization for JSON2 columns only
- Fix `create_column` in show_create_table.rs to only emit JSON structure
  settings for JSON2 columns
- Move `mod tests` to end of flat_projection.rs to fix clippy::items_after_test_module
- Add tests for legacy JSON behavior

* fix: guard narrow_read_columns_by_json_type_hint with is_structured_json_field check

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot
2026-06-18 05:27:56 +00:00
committed by GitHub
parent a3461caf9d
commit 58c85dd1ce
10 changed files with 215 additions and 45 deletions
+18 -4
View File
@@ -21,7 +21,7 @@ use datafusion_common::arrow::array::ArrayRef;
use datafusion_common::arrow::compute;
use datafusion_common::arrow::datatypes::{DataType as ArrowDataType, SchemaRef as ArrowSchemaRef};
use datatypes::arrow::array::{Array, AsArray, RecordBatchOptions};
use datatypes::extension::json::is_json_extension_type;
use datatypes::extension::json::is_structured_json_field;
use datatypes::prelude::DataType;
use datatypes::schema::SchemaRef;
use datatypes::vectors::json::array::JsonArray;
@@ -359,13 +359,13 @@ fn maybe_align_json_array_with_schema(
schema: &ArrowSchemaRef,
arrays: Vec<ArrayRef>,
) -> Result<Vec<ArrayRef>> {
if schema.fields().iter().all(|f| !is_json_extension_type(f)) {
if schema.fields().iter().all(|f| !is_structured_json_field(f)) {
return Ok(arrays);
}
let mut aligned = Vec::with_capacity(arrays.len());
for (field, array) in schema.fields().iter().zip(arrays) {
if !is_json_extension_type(field) {
if !is_structured_json_field(field) {
aligned.push(array);
continue;
}
@@ -382,10 +382,11 @@ fn maybe_align_json_array_with_schema(
mod tests {
use std::sync::Arc;
use datatypes::arrow::array::{AsArray, UInt32Array};
use datatypes::arrow::array::{AsArray, BinaryArray, UInt32Array};
use datatypes::arrow::datatypes::{DataType, Field, Schema as ArrowSchema, UInt32Type};
use datatypes::arrow_array::StringArray;
use datatypes::data_type::ConcreteDataType;
use datatypes::extension::json::{JsonExtensionType, JsonMetadata};
use datatypes::schema::{ColumnSchema, Schema};
use datatypes::vectors::{StringVector, UInt32Vector};
@@ -507,4 +508,17 @@ mod tests {
.expect("merge recordbatch");
assert_eq!(merged.num_rows(), 8);
}
#[test]
fn test_legacy_json_with_extension_does_not_align_as_structured_json() {
let field = Field::new("j", DataType::Binary, true)
.with_extension_type(JsonExtensionType::new(Arc::new(JsonMetadata::default())));
let arrow_schema = Arc::new(ArrowSchema::new(vec![field]));
let schema = Arc::new(Schema::try_from(arrow_schema).unwrap());
let arrays =
vec![Arc::new(BinaryArray::from(vec![Some(br#"{"a":1}"#.as_slice())])) as ArrayRef];
let aligned = maybe_align_json_array_with_schema(schema.arrow_schema(), arrays).unwrap();
assert_eq!(aligned[0].data_type(), &DataType::Binary);
}
}