1use std::sync::Arc;
18
19use api::v1::SemanticType;
20use common_error::ext::BoxedError;
21use common_recordbatch::error::{
22 ArrowComputeSnafu, DataTypesSnafu, ExternalSnafu, NewDfRecordBatchSnafu,
23};
24use common_recordbatch::{DfRecordBatch, RecordBatch};
25use datatypes::arrow::array::{Array, AsArray};
26use datatypes::arrow::datatypes::{DataType as ArrowDataType, Field};
27use datatypes::extension::json::is_json2_extension_type;
28use datatypes::prelude::{ConcreteDataType, DataType};
29use datatypes::schema::{Schema, SchemaRef};
30use datatypes::value::Value;
31use datatypes::vectors::Helper;
32use datatypes::vectors::json::array::JsonArray;
33use datatypes::vectors::json::json2_physical_data_type;
34use snafu::{OptionExt, ResultExt};
35use store_api::metadata::{RegionMetadata, RegionMetadataRef};
36use store_api::storage::ColumnId;
37
38use crate::cache::CacheStrategy;
39use crate::error::{InvalidRequestSnafu, RecordBatchSnafu, Result};
40use crate::read::projection::{read_column_ids_from_projection, repeated_vector_with_cache};
41use crate::read::read_columns::{JsonTargetTypes, ReadColumns};
42use crate::sst::parquet::Json2RewriteTargets;
43use crate::sst::parquet::flat_format::sst_column_id_indices;
44use crate::sst::parquet::format::FormatProjection;
45use crate::sst::{
46 FlatSchemaOptions, internal_fields, tag_maybe_to_dictionary_field, to_flat_sst_arrow_schema,
47 with_field_id,
48};
49
50pub struct FlatProjectionMapper {
55 metadata: RegionMetadataRef,
57 output_schema: SchemaRef,
59 read_cols: ReadColumns,
64 batch_schema: Vec<(ColumnId, ConcreteDataType)>,
69 is_empty_projection: bool,
71 batch_indices: Vec<usize>,
73 input_arrow_schema: datatypes::arrow::datatypes::SchemaRef,
75}
76
77impl FlatProjectionMapper {
78 pub fn new(
83 metadata: &RegionMetadataRef,
84 projection: impl IntoIterator<Item = usize>,
85 ) -> Result<Self> {
86 let projection: Vec<_> = projection.into_iter().collect();
87 let read_column_ids = read_column_ids_from_projection(metadata, &projection)?;
88 let read_cols = ReadColumns::new(read_column_ids);
89 Self::new_with_read_columns(metadata, projection, read_cols)
90 }
91
92 pub fn new_with_read_columns(
94 metadata: &RegionMetadataRef,
95 projection: Vec<usize>,
96 read_cols: ReadColumns,
97 ) -> Result<Self> {
98 Self::new_with_json2_rewrite_targets(
99 metadata,
100 projection,
101 read_cols,
102 &Json2RewriteTargets::default(),
103 )
104 }
105
106 pub(crate) fn new_with_json2_rewrite_targets(
108 metadata: &RegionMetadataRef,
109 projection: Vec<usize>,
110 read_cols: ReadColumns,
111 json2_rewrite_targets: &Json2RewriteTargets,
112 ) -> Result<Self> {
113 let is_empty_projection = projection.is_empty();
115
116 let mut col_schemas = Vec::with_capacity(projection.len());
118 let mut output_col_ids = Vec::with_capacity(projection.len());
120 for idx in &projection {
121 let col = metadata
122 .column_metadatas
123 .get(*idx)
124 .with_context(|| InvalidRequestSnafu {
125 region_id: metadata.region_id,
126 reason: format!("projection index {} is out of bound", idx),
127 })?;
128 output_col_ids.push(col.column_id);
129
130 let mut schema = col.column_schema.clone();
131 if let Some(data_type) = read_cols.json_target_type(col.column_id) {
132 schema.data_type = ConcreteDataType::json2(data_type.clone());
133 }
134 col_schemas.push(schema);
135 }
136
137 let id_to_index = sst_column_id_indices(metadata);
139
140 let format_projection = FormatProjection::compute_format_projection(
142 metadata,
143 &id_to_index,
144 metadata.column_metadatas.len() + 3,
146 read_cols.clone(),
147 );
148
149 let batch_schema =
150 flat_projected_columns(metadata, &format_projection, read_cols.json_target_types());
151
152 let input_arrow_schema =
153 compute_input_arrow_schema(metadata, &batch_schema, &read_cols, json2_rewrite_targets);
154
155 let output_schema = if is_empty_projection {
157 Arc::new(Schema::new(vec![]))
158 } else {
159 Arc::new(Schema::new(col_schemas))
161 };
162
163 let batch_indices = if is_empty_projection {
164 vec![]
165 } else {
166 output_col_ids
167 .iter()
168 .map(|id| {
169 format_projection
171 .column_id_to_projected_index
172 .get(id)
173 .copied()
174 .with_context(|| {
175 let name = metadata
176 .column_by_id(*id)
177 .map(|column| column.column_schema.name.clone())
178 .unwrap_or_else(|| id.to_string());
179 InvalidRequestSnafu {
180 region_id: metadata.region_id,
181 reason: format!(
182 "output column {} is missing in read projection",
183 name
184 ),
185 }
186 })
187 })
188 .collect::<Result<Vec<_>>>()?
189 };
190
191 Ok(FlatProjectionMapper {
192 metadata: metadata.clone(),
193 output_schema,
194 read_cols,
195 batch_schema,
196 is_empty_projection,
197 batch_indices,
198 input_arrow_schema,
199 })
200 }
201
202 pub fn all(metadata: &RegionMetadataRef) -> Result<Self> {
204 FlatProjectionMapper::new(metadata, 0..metadata.column_metadatas.len())
205 }
206
207 pub(crate) fn with_pk_dictionary_encoding(mut self) -> Self {
209 let mut changed = false;
210 let columns = self
211 .output_schema
212 .column_schemas()
213 .iter()
214 .map(|column| {
215 let mut column = column.clone();
216 if column.data_type == ConcreteDataType::string_datatype()
217 && self
218 .metadata
219 .column_by_name(&column.name)
220 .is_some_and(|metadata| metadata.semantic_type == SemanticType::Tag)
221 {
222 changed = true;
223 column.data_type = ConcreteDataType::dictionary_datatype(
224 ConcreteDataType::uint32_datatype(),
225 column.data_type.clone(),
226 );
227 }
228 column
229 })
230 .collect();
231 if changed {
232 self.output_schema = Arc::new(Schema::new_with_version(
233 columns,
234 self.output_schema.version(),
235 ));
236 }
237 self
238 }
239
240 pub(crate) fn metadata(&self) -> &RegionMetadataRef {
242 &self.metadata
243 }
244 pub(crate) fn read_columns(&self) -> &ReadColumns {
246 &self.read_cols
247 }
248
249 pub(crate) fn field_column_start(&self) -> usize {
251 for (idx, column_id) in self
252 .batch_schema
253 .iter()
254 .map(|(column_id, _)| column_id)
255 .enumerate()
256 {
257 if self
259 .metadata
260 .column_by_id(*column_id)
261 .unwrap()
262 .semantic_type
263 == SemanticType::Field
264 {
265 return idx;
266 }
267 }
268
269 self.batch_schema.len()
270 }
271
272 pub(crate) fn batch_schema(&self) -> &[(ColumnId, ConcreteDataType)] {
274 &self.batch_schema
275 }
276
277 pub(crate) fn input_arrow_schema(
281 &self,
282 compaction: bool,
283 ) -> datatypes::arrow::datatypes::SchemaRef {
284 if !compaction {
285 self.input_arrow_schema.clone()
286 } else {
287 let mut options = FlatSchemaOptions::from_encoding(self.metadata.primary_key_encoding);
289 options.concretized_json_types = self
290 .input_arrow_schema
291 .fields()
292 .iter()
293 .filter(|&field| is_json2_extension_type(field))
294 .map(|field| (field.name().clone(), field.data_type().clone()))
295 .collect();
296 to_flat_sst_arrow_schema(&self.metadata, &options)
297 }
298 }
299
300 pub(crate) fn output_schema(&self) -> SchemaRef {
304 self.output_schema.clone()
305 }
306
307 pub(crate) fn convert(
311 &self,
312 batch: &datatypes::arrow::record_batch::RecordBatch,
313 cache_strategy: &CacheStrategy,
314 ) -> common_recordbatch::error::Result<RecordBatch> {
315 if self.is_empty_projection {
316 return RecordBatch::new_with_count(self.output_schema.clone(), batch.num_rows());
317 }
318 let mut arrays = Vec::with_capacity(self.output_schema.num_columns());
321 for (output_idx, index) in self.batch_indices.iter().enumerate() {
322 let mut array = batch.column(*index).clone();
323 if matches!(
324 self.output_schema.arrow_schema().fields()[output_idx].data_type(),
325 ArrowDataType::Dictionary(_, _)
326 ) && let Some(dictionary) = array.as_any_dictionary_opt()
327 && dictionary.values().len() > dictionary.len()
328 {
329 array = arrow_select::dictionary::garbage_collect_any_dictionary(dictionary)
331 .context(ArrowComputeSnafu)?;
332 }
333 if let ArrowDataType::Dictionary(_key_type, value_type) = array.data_type()
335 && !matches!(
336 self.output_schema.arrow_schema().fields()[output_idx].data_type(),
337 ArrowDataType::Dictionary(_, _)
338 )
339 {
340 if let Some(dict_array) = single_value_string_dictionary(
343 &array,
344 &self.output_schema.column_schemas()[output_idx].data_type,
345 value_type.as_ref(),
346 ) {
347 let dict_values = dict_array.values();
348 let value = if dict_values.is_null(0) {
349 Value::Null
350 } else {
351 Value::from(datatypes::arrow_array::string_array_value(dict_values, 0))
352 };
353
354 let repeated = repeated_vector_with_cache(
355 &self.output_schema.column_schemas()[output_idx].data_type,
356 &value,
357 batch.num_rows(),
358 cache_strategy,
359 )?;
360 array = repeated.to_arrow_array();
361 } else {
362 let casted = datatypes::arrow::compute::cast(&array, value_type)
363 .context(ArrowComputeSnafu)?;
364 array = casted;
365 }
366 }
367
368 let field = &self.output_schema.arrow_schema().fields()[output_idx];
369 if is_json2_extension_type(field) && array.data_type() != field.data_type() {
370 array = JsonArray::from(&array)
371 .project_to_v2(batch.schema_ref().field(*index), field.data_type())
372 .context(DataTypesSnafu)?;
373 }
374
375 arrays.push(array);
376 }
377
378 let df_record_batch =
379 DfRecordBatch::try_new(self.output_schema.arrow_schema().clone(), arrays)
380 .context(NewDfRecordBatchSnafu)?;
381 Ok(RecordBatch::from_df_record_batch(
382 self.output_schema.clone(),
383 df_record_batch,
384 ))
385 }
386
387 pub(crate) fn project_vectors(
389 &self,
390 batch: &datatypes::arrow::record_batch::RecordBatch,
391 ) -> common_recordbatch::error::Result<Vec<datatypes::vectors::VectorRef>> {
392 let mut columns = Vec::with_capacity(self.output_schema.num_columns());
393 for index in &self.batch_indices {
394 let mut array = batch.column(*index).clone();
395 if let datatypes::arrow::datatypes::DataType::Dictionary(_key_type, value_type) =
397 array.data_type()
398 {
399 let casted = datatypes::arrow::compute::cast(&array, value_type)
400 .context(ArrowComputeSnafu)?;
401 array = casted;
402 }
403 let vector = Helper::try_into_vector(array)
404 .map_err(BoxedError::new)
405 .context(ExternalSnafu)?;
406 columns.push(vector);
407 }
408 Ok(columns)
409 }
410}
411
412fn single_value_string_dictionary<'a>(
413 array: &'a Arc<dyn Array>,
414 output_type: &ConcreteDataType,
415 value_type: &ArrowDataType,
416) -> Option<&'a datatypes::arrow::array::DictionaryArray<datatypes::arrow::datatypes::UInt32Type>> {
417 if !matches!(
418 value_type,
419 ArrowDataType::Utf8 | ArrowDataType::LargeUtf8 | ArrowDataType::Utf8View
420 ) || !output_type.is_string()
421 {
422 return None;
423 }
424
425 let dict_array = array
426 .as_any()
427 .downcast_ref::<datatypes::arrow::array::DictionaryArray<
428 datatypes::arrow::datatypes::UInt32Type,
429 >>()?;
430
431 (dict_array.values().len() == 1 && dict_array.null_count() == 0).then_some(dict_array)
432}
433
434pub(crate) fn flat_projected_columns(
438 metadata: &RegionMetadata,
439 format_projection: &FormatProjection,
440 json_target_types: &JsonTargetTypes,
441) -> Vec<(ColumnId, ConcreteDataType)> {
442 let time_index = metadata.time_index_column();
443 let num_columns = if format_projection
444 .column_id_to_projected_index
445 .contains_key(&time_index.column_id)
446 {
447 format_projection.column_id_to_projected_index.len()
448 } else {
449 format_projection.column_id_to_projected_index.len() + 1
450 };
451 let mut schema = vec![None; num_columns];
452 for (column_id, index) in &format_projection.column_id_to_projected_index {
453 let data_type = if let Some(json_type) = json_target_types.get(column_id) {
454 ConcreteDataType::json2(json_type.clone())
455 } else {
456 metadata
458 .column_by_id(*column_id)
459 .unwrap()
460 .column_schema
461 .data_type
462 .clone()
463 };
464 schema[*index] = Some((*column_id, data_type));
465 }
466 if num_columns != format_projection.column_id_to_projected_index.len() {
467 schema[num_columns - 1] = Some((
468 time_index.column_id,
469 time_index.column_schema.data_type.clone(),
470 ));
471 }
472
473 schema.into_iter().map(|id_type| id_type.unwrap()).collect()
475}
476
477pub(crate) fn compute_input_arrow_schema(
482 metadata: &RegionMetadata,
483 batch_schema: &[(ColumnId, ConcreteDataType)],
484 read_cols: &ReadColumns,
485 json2_rewrite_targets: &Json2RewriteTargets,
486) -> datatypes::arrow::datatypes::SchemaRef {
487 let mut new_fields = Vec::with_capacity(batch_schema.len() + 3);
488 for (column_id, data_type) in batch_schema {
489 let data_type = json2_rewrite_targets
490 .get(column_id)
491 .map(|x| json2_physical_data_type(&x.target_layout))
492 .or_else(|| {
493 read_cols
494 .json_target_type(*column_id)
495 .map(|x| x.as_arrow_type())
496 })
497 .unwrap_or_else(|| data_type.as_arrow_type());
498
499 let column_metadata = metadata.column_by_id(*column_id).unwrap();
500 let field = Field::new(
501 &column_metadata.column_schema.name,
502 data_type,
503 column_metadata.column_schema.is_nullable(),
504 )
505 .with_metadata(column_metadata.column_schema.metadata().clone());
506 let field = with_field_id(field, *column_id);
507 if column_metadata.semantic_type == SemanticType::Tag {
508 new_fields.push(tag_maybe_to_dictionary_field(
509 &column_metadata.column_schema.data_type,
510 &Arc::new(field),
511 ));
512 } else {
513 new_fields.push(Arc::new(field));
514 }
515 }
516 new_fields.extend_from_slice(&internal_fields());
517
518 Arc::new(datatypes::arrow::datatypes::Schema::new(new_fields))
519}
520
521pub(crate) struct CompactionProjectionMapper {
524 mapper: FlatProjectionMapper,
525 assembler: DfBatchAssembler,
526}
527
528impl CompactionProjectionMapper {
529 pub(crate) fn try_new(metadata: &RegionMetadataRef) -> Result<Self> {
530 let projection = metadata
531 .column_metadatas
532 .iter()
533 .enumerate()
534 .filter_map(|(idx, col)| {
535 if matches!(col.semantic_type, SemanticType::Field) {
536 Some(idx)
537 } else {
538 None
539 }
540 })
541 .chain([metadata.time_index_column_pos()])
542 .collect::<Vec<_>>();
543
544 let read_col_ids = metadata.column_metadatas.iter().map(|col| col.column_id);
545 let read_cols = ReadColumns::new(read_col_ids);
546 let mapper = FlatProjectionMapper::new_with_read_columns(metadata, projection, read_cols)?;
547 let assembler = DfBatchAssembler::new(mapper.output_schema());
548
549 Ok(Self { mapper, assembler })
550 }
551
552 pub(crate) fn project(&self, batch: DfRecordBatch) -> Result<DfRecordBatch> {
556 let columns = self
557 .mapper
558 .project_vectors(&batch)
559 .context(RecordBatchSnafu)?;
560 self.assembler
561 .build_df_record_batch_with_internal(&batch, columns)
562 .context(RecordBatchSnafu)
563 }
564}
565
566pub(crate) struct DfBatchAssembler {
568 output_arrow_schema_with_internal: datatypes::arrow::datatypes::SchemaRef,
569}
570
571impl DfBatchAssembler {
572 pub(crate) fn new(output_schema: SchemaRef) -> Self {
574 let fields = output_schema
575 .arrow_schema()
576 .fields()
577 .into_iter()
578 .chain(internal_fields().iter())
579 .cloned()
580 .collect::<Vec<_>>();
581 let output_arrow_schema_with_internal =
582 Arc::new(datatypes::arrow::datatypes::Schema::new(fields));
583 Self {
584 output_arrow_schema_with_internal,
585 }
586 }
587
588 pub(crate) fn build_df_record_batch_with_internal(
593 &self,
594 batch: &datatypes::arrow::record_batch::RecordBatch,
595 mut columns: Vec<datatypes::vectors::VectorRef>,
596 ) -> common_recordbatch::error::Result<DfRecordBatch> {
597 let num_columns = batch.columns().len();
598 let internal_indices = [num_columns - 3, num_columns - 2, num_columns - 1];
600 for index in internal_indices.iter() {
601 let array = batch.column(*index).clone();
602 let vector = Helper::try_into_vector(array)
603 .map_err(BoxedError::new)
604 .context(ExternalSnafu)?;
605 columns.push(vector);
606 }
607 RecordBatch::to_df_record_batch(self.output_arrow_schema_with_internal.clone(), columns)
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use datatypes::schema::ColumnSchema;
614 use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder};
615 use store_api::storage::RegionId;
616
617 use super::*;
618
619 fn metadata_with_legacy_json() -> RegionMetadataRef {
620 let mut builder = RegionMetadataBuilder::new(RegionId::new(1024, 0));
621 builder
622 .push_column_metadata(ColumnMetadata {
623 column_schema: datatypes::schema::ColumnSchema::new(
624 "j",
625 ConcreteDataType::json_datatype(),
626 true,
627 ),
628 semantic_type: SemanticType::Field,
629 column_id: 0,
630 })
631 .push_column_metadata(ColumnMetadata {
632 column_schema: datatypes::schema::ColumnSchema::new(
633 "ts",
634 ConcreteDataType::timestamp_millisecond_datatype(),
635 false,
636 ),
637 semantic_type: SemanticType::Timestamp,
638 column_id: 1,
639 });
640 Arc::new(builder.build().unwrap())
641 }
642
643 #[test]
644 fn test_tag_projection_preserves_dictionary() {
645 let mut builder = RegionMetadataBuilder::new(RegionId::new(1024, 0));
646 builder
647 .push_column_metadata(ColumnMetadata {
648 column_schema: ColumnSchema::new("tag", ConcreteDataType::string_datatype(), true),
649 semantic_type: SemanticType::Tag,
650 column_id: 0,
651 })
652 .push_column_metadata(ColumnMetadata {
653 column_schema: ColumnSchema::new(
654 "ts",
655 ConcreteDataType::timestamp_millisecond_datatype(),
656 false,
657 ),
658 semantic_type: SemanticType::Timestamp,
659 column_id: 1,
660 })
661 .primary_key(vec![0]);
662 let metadata = Arc::new(builder.build().unwrap());
663
664 let mapper = FlatProjectionMapper::new(&metadata, [0, 1]).unwrap();
665 assert_eq!(
666 &ArrowDataType::Utf8,
667 mapper.output_schema().arrow_schema().field(0).data_type()
668 );
669
670 let mapper = mapper.with_pk_dictionary_encoding();
671 assert_eq!(
672 &ArrowDataType::Dictionary(
673 Box::new(ArrowDataType::UInt32),
674 Box::new(ArrowDataType::Utf8),
675 ),
676 mapper.output_schema().arrow_schema().field(0).data_type()
677 );
678 }
679
680 #[test]
681 fn test_tag_projection_compacts_dictionary_slices() {
682 use datatypes::arrow::array::{
683 DictionaryArray, StringArray, TimestampMillisecondArray, UInt32Array,
684 };
685 use datatypes::arrow::datatypes::UInt32Type;
686
687 let mut builder = RegionMetadataBuilder::new(RegionId::new(1024, 0));
688 builder
689 .push_column_metadata(ColumnMetadata {
690 column_schema: ColumnSchema::new("tag", ConcreteDataType::string_datatype(), true),
691 semantic_type: SemanticType::Tag,
692 column_id: 0,
693 })
694 .push_column_metadata(ColumnMetadata {
695 column_schema: ColumnSchema::new(
696 "ts",
697 ConcreteDataType::timestamp_millisecond_datatype(),
698 false,
699 ),
700 semantic_type: SemanticType::Timestamp,
701 column_id: 1,
702 })
703 .primary_key(vec![0]);
704 let metadata = Arc::new(builder.build().unwrap());
705 let mapper = FlatProjectionMapper::new(&metadata, [0, 1])
706 .unwrap()
707 .with_pk_dictionary_encoding();
708 let values = StringArray::from_iter(
709 (0..1024).map(|i| (i != 1).then(|| format!("{i}{}", "x".repeat(4096)))),
710 );
711 let dictionary = DictionaryArray::<UInt32Type>::try_new(
712 UInt32Array::from(vec![Some(1023), None, Some(1), Some(1023)]),
713 Arc::new(values),
714 )
715 .unwrap();
716 let expected = datatypes::arrow::compute::cast(&dictionary, &ArrowDataType::Utf8).unwrap();
717 let batch = DfRecordBatch::try_from_iter([
718 ("tag", Arc::new(dictionary) as Arc<dyn Array>),
719 (
720 "ts",
721 Arc::new(TimestampMillisecondArray::from(vec![1, 2, 3, 4])),
722 ),
723 ])
724 .unwrap();
725 let output = mapper.convert(&batch, &CacheStrategy::Disabled).unwrap();
726 let actual = output.column(0);
727 assert!(actual.get_array_memory_size() < 64 * 1024);
728 assert_eq!(actual.data_type(), batch.column(0).data_type());
729 assert_eq!(
730 datatypes::arrow::compute::cast(actual.as_ref(), &ArrowDataType::Utf8)
731 .unwrap()
732 .as_ref(),
733 expected.as_ref(),
734 );
735 }
736
737 #[test]
738 fn test_single_value_dictionary_preserves_string_type() {
739 use datatypes::arrow::array::{DictionaryArray, TimestampMillisecondArray, UInt32Array};
740 use datatypes::arrow::datatypes::UInt32Type;
741
742 for data_type in [
743 ConcreteDataType::large_string_datatype(),
744 ConcreteDataType::utf8_view_datatype(),
745 ] {
746 let mut builder = RegionMetadataBuilder::new(RegionId::new(1024, 0));
747 builder
748 .push_column_metadata(ColumnMetadata {
749 column_schema: ColumnSchema::new("tag", data_type.clone(), true),
750 semantic_type: SemanticType::Tag,
751 column_id: 0,
752 })
753 .push_column_metadata(ColumnMetadata {
754 column_schema: ColumnSchema::new(
755 "ts",
756 ConcreteDataType::timestamp_millisecond_datatype(),
757 false,
758 ),
759 semantic_type: SemanticType::Timestamp,
760 column_id: 1,
761 })
762 .primary_key(vec![0]);
763 let metadata = Arc::new(builder.build().unwrap());
764 let mapper = FlatProjectionMapper::new(&metadata, [0, 1]).unwrap();
765
766 for value in [Value::from("greptime"), Value::Null] {
767 let mut values = data_type.create_mutable_vector(1);
768 values.try_push_value_ref(&value.as_value_ref()).unwrap();
769 let dictionary = Arc::new(
770 DictionaryArray::<UInt32Type>::try_new(
771 UInt32Array::from(vec![0, 0, 0]),
772 values.to_vector().to_arrow_array(),
773 )
774 .unwrap(),
775 );
776 let arrays: Vec<Arc<dyn Array>> = mapper
777 .batch_schema()
778 .iter()
779 .map(|(id, _)| match id {
780 0 => dictionary.clone() as Arc<dyn Array>,
781 1 => Arc::new(TimestampMillisecondArray::from(vec![1, 2, 3])),
782 _ => unreachable!(),
783 })
784 .collect();
785 let fields = mapper
786 .batch_schema()
787 .iter()
788 .zip(&arrays)
789 .map(|((id, _), array)| {
790 Field::new(id.to_string(), array.data_type().clone(), true)
791 })
792 .collect::<Vec<_>>();
793 let batch = DfRecordBatch::try_new(
794 Arc::new(datatypes::arrow::datatypes::Schema::new(fields)),
795 arrays,
796 )
797 .unwrap();
798 let output = mapper.convert(&batch, &CacheStrategy::Disabled).unwrap();
799 let vector = Helper::try_into_vector(output.column(0)).unwrap();
800 assert_eq!(data_type, vector.data_type());
801 for row in 0..3 {
802 assert_eq!(value, vector.get(row));
803 }
804 }
805 }
806 }
807
808 #[test]
809 fn test_json_type_hint_does_not_concretize_legacy_json() {
810 let metadata = metadata_with_legacy_json();
811 let mapper = FlatProjectionMapper::new_with_read_columns(
812 &metadata,
813 vec![0, 1],
814 ReadColumns::new([0, 1]),
815 )
816 .unwrap();
817
818 assert_eq!(
819 mapper.batch_schema()[0],
820 (0, ConcreteDataType::json_datatype())
821 );
822 }
823}