1use std::collections::HashMap;
16use std::sync::Arc;
17
18use datatypes::arrow::datatypes::{DataType as ArrowDataType, Schema, SchemaRef};
19use datatypes::arrow::record_batch::RecordBatch;
20use datatypes::data_type::DataType;
21use datatypes::extension::json::is_structured_json_field;
22use datatypes::types::JsonType;
23use datatypes::vectors::json::array::JsonArray;
24use snafu::{OptionExt, ResultExt};
25
26use crate::error::{
27 ConvertValueSnafu, DataTypeMismatchSnafu, NewRecordBatchSnafu, Result, UnexpectedSnafu,
28};
29use crate::memtable::BoxedRecordBatchIterator;
30
31#[derive(Clone)]
37pub(crate) struct Json2Aligner {
38 schema: SchemaRef,
40 json_columns: Vec<(usize, ArrowDataType)>,
42}
43
44impl Json2Aligner {
45 pub(crate) fn try_new<I>(input_schemas: I) -> Result<Self>
49 where
50 I: IntoIterator<Item = SchemaRef>,
51 {
52 let mut input_schemas = input_schemas.into_iter();
53
54 let base_schema = input_schemas.next().context(UnexpectedSnafu {
56 reason: "Json2Aligner requires at least one input schema",
57 })?;
58
59 let mut merged_types: HashMap<usize, JsonType> = base_schema
61 .fields()
62 .iter()
63 .enumerate()
64 .filter(|&(_idx, field)| is_structured_json_field(field))
65 .map(|(idx, field)| (idx, JsonType::from(field.data_type())))
66 .collect();
67
68 if merged_types.is_empty() {
70 return Ok(Self {
71 schema: base_schema,
72 json_columns: Vec::new(),
73 });
74 }
75
76 for schema in input_schemas {
78 #[cfg(debug_assertions)]
80 assert_columns_match_except_json2(&base_schema, &schema);
81
82 for (idx, merged) in &mut merged_types {
83 if *idx >= schema.fields().len() {
84 continue;
85 }
86 merged
87 .merge(&JsonType::from(schema.field(*idx).data_type()))
88 .context(DataTypeMismatchSnafu)?;
89 }
90 }
91
92 let mut json_columns = Vec::with_capacity(merged_types.len());
94 let fields: Vec<_> = base_schema
95 .fields()
96 .iter()
97 .enumerate()
98 .map(|(idx, field)| {
99 if let Some(merged) = merged_types.get(&idx) {
100 let data_type = merged.as_arrow_type();
101 json_columns.push((idx, data_type.clone()));
102 let mut field = (**field).clone();
103 field.set_data_type(data_type);
104 Arc::new(field)
105 } else {
106 field.clone()
107 }
108 })
109 .collect();
110
111 let schema = Arc::new(Schema::new_with_metadata(
112 fields,
113 base_schema.metadata().clone(),
114 ));
115
116 Ok(Self {
117 schema,
118 json_columns,
119 })
120 }
121
122 pub(crate) fn schema(&self) -> &SchemaRef {
124 &self.schema
125 }
126
127 pub(crate) fn align_batch(&self, batch: RecordBatch) -> Result<RecordBatch> {
129 if self.json_columns.is_empty() {
130 return Ok(batch);
131 }
132 let mut cols = batch.columns().to_vec();
133 for (idx, expected_type) in &self.json_columns {
134 if batch.schema_ref().field(*idx).data_type() != expected_type {
135 cols[*idx] = JsonArray::from(batch.column(*idx))
136 .try_align(expected_type)
137 .context(ConvertValueSnafu)?;
138 }
139 }
140 RecordBatch::try_new(self.schema.clone(), cols).context(NewRecordBatchSnafu)
141 }
142
143 pub(crate) fn align_batches<I>(&self, batches: I) -> Result<Vec<RecordBatch>>
145 where
146 I: IntoIterator<Item = RecordBatch>,
147 {
148 batches
149 .into_iter()
150 .map(|batch| self.align_batch(batch))
151 .collect()
152 }
153
154 pub(crate) fn wrap_iter(&self, iter: BoxedRecordBatchIterator) -> BoxedRecordBatchIterator {
156 let aligner = self.clone();
157 Box::new(iter.map(move |batch| aligner.align_batch(batch?)))
158 }
159}
160
161#[cfg(debug_assertions)]
162fn assert_columns_match_except_json2(base_schema: &Schema, schema: &Schema) {
163 debug_assert_eq!(
164 base_schema.fields().len(),
165 schema.fields().len(),
166 "input schemas for Json2Aligner must have the same column count"
167 );
168 for (idx, (base_field, field)) in base_schema.fields().iter().zip(schema.fields()).enumerate() {
169 let base_is_json2 = is_structured_json_field(base_field);
170 let is_json2 = is_structured_json_field(field);
171 debug_assert_eq!(
172 base_is_json2, is_json2,
173 "column {idx} must be JSON2 in all input schemas or none"
174 );
175 if !base_is_json2 && !is_json2 {
176 debug_assert_eq!(
177 base_field, field,
178 "non-JSON2 column {idx} must be identical across input schemas"
179 );
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use std::sync::Arc;
187
188 use datatypes::arrow::array::{
189 Array, ArrayRef, Int64Array, StringViewArray, StructArray, UInt64Array,
190 };
191 use datatypes::arrow::datatypes::{DataType, Field, Fields, Schema};
192 use datatypes::extension::json::{JsonExtensionType, JsonMetadata};
193
194 use super::*;
195
196 #[test]
197 fn test_try_new_rejects_empty_input() {
198 let err = match Json2Aligner::try_new([]) {
199 Ok(_) => panic!("expected empty input to fail"),
200 Err(err) => err,
201 };
202 assert!(
203 err.to_string()
204 .contains("Json2Aligner requires at least one input schema")
205 );
206 }
207
208 #[test]
209 fn test_try_new_keeps_non_json_schema_unchanged() {
210 let schema = Arc::new(Schema::new(vec![
211 Arc::new(Field::new("ts", DataType::Int64, false)),
212 Arc::new(Field::new("value", DataType::UInt64, true)),
213 ]));
214 let batch = RecordBatch::try_new(
215 schema.clone(),
216 vec![
217 Arc::new(Int64Array::from_iter_values([1, 2])) as ArrayRef,
218 Arc::new(UInt64Array::from(vec![Some(10), None])) as ArrayRef,
219 ],
220 )
221 .unwrap();
222
223 let aligner = Json2Aligner::try_new([schema.clone()]).unwrap();
224 assert!(Arc::ptr_eq(aligner.schema(), &schema));
225
226 let aligned = aligner.align_batch(batch).unwrap();
227 assert!(Arc::ptr_eq(aligned.schema_ref(), &schema));
228 }
229
230 #[test]
231 fn test_try_new_ignores_legacy_jsonb_extension_field() {
232 let legacy_jsonb_field = Arc::new(
233 Field::new("data", DataType::Binary, true)
234 .with_extension_type(JsonExtensionType::new(Arc::new(JsonMetadata::default()))),
235 );
236 let schema = Arc::new(Schema::new(vec![
237 Arc::new(Field::new("ts", DataType::Int64, false)),
238 legacy_jsonb_field,
239 ]));
240
241 let aligner = Json2Aligner::try_new([schema.clone()]).unwrap();
242
243 assert!(Arc::ptr_eq(aligner.schema(), &schema));
244 assert!(aligner.json_columns.is_empty());
245 }
246
247 #[test]
248 fn test_try_new_merges_json2_object_fields() {
249 let id_fields = Fields::from(vec![id_field()]);
250 let name_fields = Fields::from(vec![name_field()]);
251 let schema_with_id = schema_with_json_field(json_field("data", id_fields));
252 let schema_with_name = schema_with_json_field(json_field("data", name_fields));
253
254 let aligner = Json2Aligner::try_new([schema_with_id, schema_with_name]).unwrap();
255 let data_field = aligner.schema().field(1);
256 let DataType::Struct(fields) = data_field.data_type() else {
257 panic!("expected JSON2 field to be a struct");
258 };
259
260 assert_eq!(2, fields.len());
261 assert_eq!("id", fields[0].name());
262 assert_eq!(&DataType::Int64, fields[0].data_type());
263 assert_eq!("name", fields[1].name());
264 assert_eq!(&DataType::Utf8View, fields[1].data_type());
265 assert!(is_structured_json_field(&aligner.schema().fields()[1]));
266 }
267
268 #[test]
269 fn test_align_batch_fills_missing_json2_fields() {
270 let id_fields = Fields::from(vec![id_field()]);
271 let name_fields = Fields::from(vec![name_field()]);
272 let schema_with_id = schema_with_json_field(json_field("data", id_fields.clone()));
273 let schema_with_name = schema_with_json_field(json_field("data", name_fields.clone()));
274
275 let batch_with_id = RecordBatch::try_new(
276 schema_with_id.clone(),
277 vec![
278 Arc::new(Int64Array::from_iter_values([1, 2])) as ArrayRef,
279 struct_array(
280 id_fields,
281 vec![Arc::new(Int64Array::from_iter_values([10, 20])) as ArrayRef],
282 ),
283 ],
284 )
285 .unwrap();
286 let batch_with_name = RecordBatch::try_new(
287 schema_with_name.clone(),
288 vec![
289 Arc::new(Int64Array::from_iter_values([3, 4])) as ArrayRef,
290 struct_array(
291 name_fields,
292 vec![
293 Arc::new(StringViewArray::from(vec![Some("alice"), Some("bob")]))
294 as ArrayRef,
295 ],
296 ),
297 ],
298 )
299 .unwrap();
300
301 let aligner = Json2Aligner::try_new([schema_with_id, schema_with_name]).unwrap();
302 let aligned_with_id = aligner.align_batch(batch_with_id).unwrap();
303 let aligned_with_name = aligner.align_batch(batch_with_name).unwrap();
304
305 let data_with_id = aligned_with_id
306 .column(1)
307 .as_any()
308 .downcast_ref::<StructArray>()
309 .unwrap();
310 let id_values = data_with_id
311 .column(0)
312 .as_any()
313 .downcast_ref::<Int64Array>()
314 .unwrap();
315 let missing_names = data_with_id.column(1);
316 assert_eq!(10, id_values.value(0));
317 assert_eq!(20, id_values.value(1));
318 assert!(missing_names.is_null(0));
319 assert!(missing_names.is_null(1));
320
321 let data_with_name = aligned_with_name
322 .column(1)
323 .as_any()
324 .downcast_ref::<StructArray>()
325 .unwrap();
326 let missing_ids = data_with_name.column(0);
327 let name_values = data_with_name
328 .column(1)
329 .as_any()
330 .downcast_ref::<StringViewArray>()
331 .unwrap();
332 assert!(missing_ids.is_null(0));
333 assert!(missing_ids.is_null(1));
334 assert_eq!("alice", name_values.value(0));
335 assert_eq!("bob", name_values.value(1));
336 }
337
338 #[test]
339 fn test_wrap_iter_aligns_each_batch() {
340 let id_fields = Fields::from(vec![id_field()]);
341 let name_fields = Fields::from(vec![name_field()]);
342 let schema_with_id = schema_with_json_field(json_field("data", id_fields.clone()));
343 let schema_with_name = schema_with_json_field(json_field("data", name_fields.clone()));
344
345 let batch_with_id = RecordBatch::try_new(
346 schema_with_id.clone(),
347 vec![
348 Arc::new(Int64Array::from_iter_values([1])) as ArrayRef,
349 struct_array(
350 id_fields,
351 vec![Arc::new(Int64Array::from_iter_values([10])) as ArrayRef],
352 ),
353 ],
354 )
355 .unwrap();
356 let batch_with_name = RecordBatch::try_new(
357 schema_with_name.clone(),
358 vec![
359 Arc::new(Int64Array::from_iter_values([2])) as ArrayRef,
360 struct_array(
361 name_fields,
362 vec![Arc::new(StringViewArray::from(vec![Some("alice")])) as ArrayRef],
363 ),
364 ],
365 )
366 .unwrap();
367
368 let aligner = Json2Aligner::try_new([schema_with_id, schema_with_name]).unwrap();
369 let iter: BoxedRecordBatchIterator =
370 Box::new(vec![Ok(batch_with_id), Ok(batch_with_name)].into_iter());
371 let aligned = aligner.wrap_iter(iter).collect::<Result<Vec<_>>>().unwrap();
372
373 assert_eq!(2, aligned.len());
374 assert!(Arc::ptr_eq(aligned[0].schema_ref(), aligner.schema()));
375 assert!(Arc::ptr_eq(aligned[1].schema_ref(), aligner.schema()));
376 }
377
378 fn json_field(name: &str, fields: Fields) -> Arc<Field> {
379 Arc::new(
380 Field::new(name, DataType::Struct(fields), true)
381 .with_extension_type(JsonExtensionType::new(Arc::new(JsonMetadata::default()))),
382 )
383 }
384
385 fn schema_with_json_field(json_field: Arc<Field>) -> SchemaRef {
386 Arc::new(Schema::new(vec![
387 Arc::new(Field::new("ts", DataType::Int64, false)),
388 json_field,
389 ]))
390 }
391
392 fn id_field() -> Arc<Field> {
393 Arc::new(Field::new("id", DataType::Int64, true))
394 }
395
396 fn name_field() -> Arc<Field> {
397 Arc::new(Field::new("name", DataType::Utf8View, true))
398 }
399
400 fn struct_array(fields: Fields, columns: Vec<ArrayRef>) -> ArrayRef {
401 Arc::new(StructArray::new(fields, columns, None))
402 }
403}