query/optimizer/
json_schema_concretize.rs1use std::collections::HashMap;
16use std::sync::Arc;
17
18use datafusion::config::ConfigOptions;
19use datafusion_common::tree_node::Transformed;
20use datafusion_common::{DFSchema, DFSchemaRef, Result};
21use datafusion_expr::{LogicalPlan, UserDefinedLogicalNodeCore};
22use datafusion_optimizer::analyzer::AnalyzerRule;
23use datatypes::extension::json::{
24 Json2ExtensionType, is_json2_extension_type, is_legacy_json2_extension_type,
25};
26use datatypes::types::json_type::JsonNativeType;
27
28use crate::dist_plan::MergeScanLogicalPlan;
29use crate::optimizer::json_type_concretize::deduce_json_types;
30
31#[derive(Debug)]
60pub(crate) struct JsonSchemaConcretizeRule;
61
62impl AnalyzerRule for JsonSchemaConcretizeRule {
63 fn analyze(&self, plan: LogicalPlan, _config: &ConfigOptions) -> Result<LogicalPlan> {
64 let plan = plan.transform_up_with_subqueries(|plan| {
65 let LogicalPlan::Extension(mut extension) = plan else {
66 return Ok(Transformed::no(plan));
67 };
68 let Some(merge_scan) = extension
69 .node
70 .as_any()
71 .downcast_ref::<MergeScanLogicalPlan>()
72 else {
73 return Ok(Transformed::no(LogicalPlan::Extension(extension)));
74 };
75
76 let json_types = deduce_json_types(merge_scan.input())?;
78 if json_types.is_empty() {
79 return Ok(Transformed::no(LogicalPlan::Extension(extension)));
80 }
81 let schema = concretize_json2_schema(merge_scan.schema(), &json_types)?;
82 if schema.as_ref() == merge_scan.schema().as_ref() {
83 return Ok(Transformed::no(LogicalPlan::Extension(extension)));
84 }
85
86 extension.node = Arc::new(merge_scan.clone().with_output_schema(schema));
87 Ok(Transformed::yes(LogicalPlan::Extension(extension)))
88 })?;
89
90 if plan.transformed {
91 plan.data
92 .transform_up_with_subqueries(|plan| {
93 if matches!(plan, LogicalPlan::Extension(_)) || plan.inputs().is_empty() {
94 Ok(Transformed::no(plan))
95 } else {
96 plan.recompute_schema().map(Transformed::yes)
97 }
98 })
99 .map(|x| x.data)
100 } else {
101 Ok(plan.data)
102 }
103 }
104
105 fn name(&self) -> &str {
106 "JsonSchemaConcretizeRule"
107 }
108}
109
110fn concretize_json2_schema(
111 schema: &DFSchemaRef,
112 json_types: &HashMap<String, JsonNativeType>,
113) -> Result<DFSchemaRef> {
114 if !schema
115 .iter()
116 .any(|(_, field)| json_types.contains_key(field.name()) && is_json2_extension_type(field))
117 {
118 return Ok(schema.clone());
119 }
120
121 let mut changed = false;
122 let fields = schema
123 .iter()
124 .map(|(qualifier, field)| {
125 let Some(json_type) = json_types
126 .get(field.name())
127 .filter(|_| is_json2_extension_type(field))
128 else {
129 return (qualifier.cloned(), field.clone());
130 };
131 let data_type = json_type.as_arrow_type();
132 if field.data_type() == &data_type {
133 return (qualifier.cloned(), field.clone());
134 }
135
136 changed = true;
137
138 let legacy = is_legacy_json2_extension_type(field);
144 let mut field = field.as_ref().clone().with_data_type(data_type);
145 if legacy {
146 field = field.with_extension_type(Json2ExtensionType::default());
147 }
148 (qualifier.cloned(), Arc::new(field))
149 })
150 .collect();
151
152 if changed {
153 let schema = DFSchema::new_with_metadata(fields, schema.metadata().clone())?
154 .with_functional_dependencies(schema.functional_dependencies().clone())?;
155 Ok(Arc::new(schema))
156 } else {
157 Ok(schema.clone())
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use arrow_schema::extension::{
164 EXTENSION_TYPE_METADATA_KEY, EXTENSION_TYPE_NAME_KEY, ExtensionType,
165 };
166 use arrow_schema::{DataType, Field, Fields, Schema};
167 use datafusion_common::DFSchema;
168 use datafusion_expr::{LogicalPlanBuilder, col};
169 use datatypes::extension::json::{JsonExtensionType, is_json2_extension_type};
170
171 use super::*;
172
173 #[test]
174 fn test_json_schema_concretize_rule_updates_merge_scan() -> Result<()> {
175 let field = Field::new("j", DataType::Struct(Fields::empty()), true).with_metadata(
176 HashMap::from([
177 (
178 EXTENSION_TYPE_NAME_KEY.to_string(),
179 JsonExtensionType::NAME.to_string(),
180 ),
181 (
182 EXTENSION_TYPE_METADATA_KEY.to_string(),
183 serde_json::json!({
184 "json_structure_settings": { "Structured": null }
185 })
186 .to_string(),
187 ),
188 ]),
189 );
190 let schema = Arc::new(DFSchema::try_from(Schema::new(vec![field]))?);
191 let input = LogicalPlan::EmptyRelation(datafusion_expr::logical_plan::EmptyRelation {
192 produce_one_row: false,
193 schema,
194 });
195 let merge_scan =
196 MergeScanLogicalPlan::new(input, false, Default::default()).into_logical_plan();
197 let plan = LogicalPlanBuilder::from(merge_scan)
198 .project(vec![col("j")])?
199 .build()?;
200
201 let plan = JsonSchemaConcretizeRule.analyze(plan, &ConfigOptions::default())?;
202 let field = plan.schema().field(0);
203 assert_eq!(&DataType::Binary, field.data_type());
204 assert_eq!(Some(Json2ExtensionType::NAME), field.extension_type_name());
205 assert!(is_json2_extension_type(field));
206 Ok(())
207 }
208}