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