1use std::borrow::Borrow;
32use std::collections::HashMap;
33use std::sync::Arc;
34
35use api::v1::SemanticType;
36use datatypes::arrow::array::{
37 Array, ArrayRef, BinaryArray, DictionaryArray, UInt32Array, UInt64Array,
38};
39use datatypes::arrow::compute::kernels::take::take;
40use datatypes::arrow::datatypes::{Schema, SchemaRef};
41use datatypes::arrow::record_batch::RecordBatch;
42use datatypes::prelude::{ConcreteDataType, DataType};
43use mito_codec::row_converter::{CompositeValues, PrimaryKeyCodec, build_primary_key_codec};
44use parquet::file::metadata::RowGroupMetaData;
45use snafu::{OptionExt, ResultExt, ensure};
46use store_api::codec::PrimaryKeyEncoding;
47use store_api::metadata::{RegionMetadata, RegionMetadataRef};
48use store_api::storage::{ColumnId, SequenceNumber};
49
50use crate::error::{
51 ComputeArrowSnafu, DecodeSnafu, InvalidParquetSnafu, InvalidRecordBatchSnafu,
52 NewRecordBatchSnafu, Result,
53};
54use crate::read::read_columns::{JsonTargetTypes, ReadColumns};
55use crate::sst::parquet::format::{
56 FIXED_POS_COLUMN_NUM, FormatProjection, INTERNAL_COLUMN_NUM, PrimaryKeyArray,
57 PrimaryKeyReadFormat, StatValues, column_null_counts, column_values,
58};
59use crate::sst::parquet::read_columns::ParquetReadColumns;
60use crate::sst::{
61 FlatSchemaOptions, flat_sst_arrow_schema_column_num, tag_maybe_to_dictionary_field,
62 to_flat_sst_arrow_schema, with_field_id,
63};
64
65pub(crate) struct FlatWriteFormat {
67 arrow_schema: SchemaRef,
69 override_sequence: Option<SequenceNumber>,
70}
71
72impl FlatWriteFormat {
73 pub(crate) fn new(metadata: RegionMetadataRef, options: &FlatSchemaOptions) -> FlatWriteFormat {
75 let arrow_schema = to_flat_sst_arrow_schema(&metadata, options);
76 FlatWriteFormat {
77 arrow_schema,
78 override_sequence: None,
79 }
80 }
81
82 pub(crate) fn with_override_sequence(
84 mut self,
85 override_sequence: Option<SequenceNumber>,
86 ) -> Self {
87 self.override_sequence = override_sequence;
88 self
89 }
90
91 #[cfg(test)]
93 pub(crate) fn arrow_schema(&self) -> &SchemaRef {
94 &self.arrow_schema
95 }
96
97 pub(crate) fn convert_batch(&self, batch: &RecordBatch) -> Result<RecordBatch> {
99 debug_assert_eq!(batch.num_columns(), self.arrow_schema.fields().len());
100
101 let Some(override_sequence) = self.override_sequence else {
102 return Ok(batch.clone());
103 };
104
105 let mut columns = batch.columns().to_vec();
106 let sequence_array = Arc::new(UInt64Array::from(vec![override_sequence; batch.num_rows()]));
107 columns[sequence_column_index(batch.num_columns())] = sequence_array;
108
109 RecordBatch::try_new(batch.schema(), columns).context(NewRecordBatchSnafu)
110 }
111}
112
113pub(crate) fn sequence_column_index(num_columns: usize) -> usize {
115 num_columns - 2
116}
117
118pub(crate) fn time_index_column_index(num_columns: usize) -> usize {
120 num_columns - 4
121}
122
123pub(crate) fn primary_key_column_index(num_columns: usize) -> usize {
125 num_columns - 3
126}
127
128pub(crate) fn wrap_pk_binary_to_dict(
130 record_batch: RecordBatch,
131 dict_schema: &SchemaRef,
132) -> Result<RecordBatch> {
133 let pk_idx = primary_key_column_index(record_batch.num_columns());
134 let pk_column = record_batch.column(pk_idx);
135 let binary_array = pk_column
136 .as_any()
137 .downcast_ref::<BinaryArray>()
138 .with_context(|| InvalidRecordBatchSnafu {
139 reason: format!(
140 "expected BinaryArray for __primary_key, got {:?}",
141 pk_column.data_type()
142 ),
143 })?;
144 let n = binary_array.len();
145 let keys = UInt32Array::from_iter_values(0..n as u32);
146 let dict_array: ArrayRef = Arc::new(DictionaryArray::new(keys, pk_column.clone()));
147
148 let mut columns = record_batch.columns().to_vec();
149 columns[pk_idx] = dict_array;
150
151 RecordBatch::try_new(dict_schema.clone(), columns).context(NewRecordBatchSnafu)
152}
153
154pub(crate) fn op_type_column_index(num_columns: usize) -> usize {
156 num_columns - 1
157}
158
159pub(crate) fn field_column_start(metadata: &RegionMetadata, num_columns: usize) -> usize {
168 let field_column_count = metadata.column_metadatas.len() - 1 - metadata.primary_key.len();
171 num_columns - FIXED_POS_COLUMN_NUM - field_column_count
172}
173
174pub struct FlatReadFormat {
181 override_sequence: Option<SequenceNumber>,
183 read_cols: ReadColumns,
185 parquet_adapter: ParquetAdapter,
187 pk_dict_wrap_schema: Option<SchemaRef>,
189}
190
191impl FlatReadFormat {
192 pub fn new(
196 metadata: RegionMetadataRef,
197 read_cols: ReadColumns,
198 file_schema: Option<SchemaRef>,
199 file_path: &str,
200 skip_auto_convert: bool,
201 ) -> Result<FlatReadFormat> {
202 let num_columns = file_schema.as_ref().map(|x| x.fields().len());
203 let is_legacy = match num_columns {
204 Some(num) => Self::is_legacy_format(&metadata, num, file_path)?,
205 None => metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse,
206 };
207
208 let parquet_adapter = if is_legacy {
209 if metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse {
211 ParquetAdapter::PrimaryKeyToFlat(ParquetPrimaryKeyToFlat::new(
213 metadata,
214 read_cols.clone(),
215 skip_auto_convert,
216 ))
217 } else {
218 ParquetAdapter::PrimaryKeyToFlat(ParquetPrimaryKeyToFlat::new(
219 metadata,
220 read_cols.clone(),
221 false,
222 ))
223 }
224 } else {
225 let file_schema = file_schema
226 .unwrap_or_else(|| to_flat_sst_arrow_schema(&metadata, &Default::default()));
227 ParquetAdapter::Flat(ParquetFlat::new(metadata, read_cols.clone(), file_schema))
228 };
229
230 Ok(FlatReadFormat {
231 override_sequence: None,
232 read_cols,
233 parquet_adapter,
234 pk_dict_wrap_schema: None,
235 })
236 }
237
238 pub(crate) fn set_override_sequence(&mut self, sequence: Option<SequenceNumber>) {
240 self.override_sequence = sequence;
241 }
242
243 pub(crate) fn set_pk_as_binary(&mut self, output_schema: SchemaRef) {
245 self.pk_dict_wrap_schema = Some(output_schema);
246 }
247
248 pub fn projected_index_by_id(&self, column_id: ColumnId) -> Option<usize> {
250 self.format_projection()
251 .column_id_to_projected_index
252 .get(&column_id)
253 .copied()
254 }
255
256 pub fn min_values(
258 &self,
259 row_groups: &[impl Borrow<RowGroupMetaData>],
260 column_id: ColumnId,
261 ) -> StatValues {
262 match &self.parquet_adapter {
263 ParquetAdapter::Flat(p) => p.min_values(row_groups, column_id),
264 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.min_values(row_groups, column_id),
265 }
266 }
267
268 pub fn max_values(
270 &self,
271 row_groups: &[impl Borrow<RowGroupMetaData>],
272 column_id: ColumnId,
273 ) -> StatValues {
274 match &self.parquet_adapter {
275 ParquetAdapter::Flat(p) => p.max_values(row_groups, column_id),
276 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.max_values(row_groups, column_id),
277 }
278 }
279
280 pub fn null_counts(
282 &self,
283 row_groups: &[impl Borrow<RowGroupMetaData>],
284 column_id: ColumnId,
285 ) -> StatValues {
286 match &self.parquet_adapter {
287 ParquetAdapter::Flat(p) => p.null_counts(row_groups, column_id),
288 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.null_counts(row_groups, column_id),
289 }
290 }
291
292 pub(crate) fn arrow_schema(&self) -> &SchemaRef {
294 match &self.parquet_adapter {
295 ParquetAdapter::Flat(p) => &p.arrow_schema,
296 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.arrow_schema(),
297 }
298 }
299
300 pub(crate) fn output_arrow_schema(&self) -> Result<SchemaRef> {
302 let projection = self.parquet_read_columns().root_indices();
303 let mut schema = self
304 .arrow_schema()
305 .project(projection)
306 .context(ComputeArrowSnafu)?;
307 let mut fields = schema.fields().iter().cloned().collect::<Vec<_>>();
308 for (column_id, target) in self.json_target_types().iter() {
309 let Some(index) = self.parquet_projected_index_by_id(*column_id) else {
310 continue;
311 };
312 let Some(field) = schema.fields().get(index) else {
313 continue;
314 };
315 let mut field = field.as_ref().clone();
316 field.set_data_type(ConcreteDataType::json2(target.clone()).as_arrow_type());
317 fields[index] = Arc::new(field);
318 }
319 schema.fields = fields.into();
320 Ok(Arc::new(schema))
321 }
322
323 pub(crate) fn parquet_projected_index_by_id(&self, column_id: ColumnId) -> Option<usize> {
326 match &self.parquet_adapter {
327 ParquetAdapter::Flat(p) => p
328 .format_projection
329 .column_id_to_projected_index
330 .get(&column_id)
331 .copied(),
332 ParquetAdapter::PrimaryKeyToFlat(p) => p
335 .format
336 .field_id_to_projected_index()
337 .get(&column_id)
338 .copied(),
339 }
340 }
341
342 pub(crate) fn metadata(&self) -> &RegionMetadataRef {
344 match &self.parquet_adapter {
345 ParquetAdapter::Flat(p) => &p.metadata,
346 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.metadata(),
347 }
348 }
349
350 pub(crate) fn parquet_read_columns(&self) -> &ParquetReadColumns {
352 match &self.parquet_adapter {
353 ParquetAdapter::Flat(p) => &p.format_projection.parquet_read_cols,
354 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.parquet_read_columns(),
355 }
356 }
357
358 pub(crate) fn json_target_types(&self) -> &JsonTargetTypes {
360 self.read_cols.json_target_types()
361 }
362
363 pub(crate) fn format_projection(&self) -> &FormatProjection {
368 match &self.parquet_adapter {
369 ParquetAdapter::Flat(p) => &p.format_projection,
370 ParquetAdapter::PrimaryKeyToFlat(p) => &p.format_projection,
371 }
372 }
373
374 pub(crate) fn batch_has_raw_pk_columns(&self) -> bool {
378 matches!(&self.parquet_adapter, ParquetAdapter::Flat(_))
379 }
380
381 pub(crate) fn new_override_sequence_array(&self, length: usize) -> Option<ArrayRef> {
383 self.override_sequence
384 .map(|seq| Arc::new(UInt64Array::from_value(seq, length)) as ArrayRef)
385 }
386
387 pub(crate) fn convert_batch(
392 &self,
393 record_batch: RecordBatch,
394 override_sequence_array: Option<&ArrayRef>,
395 ) -> Result<RecordBatch> {
396 let record_batch = if let Some(dict_schema) = &self.pk_dict_wrap_schema {
397 wrap_pk_binary_to_dict(record_batch, dict_schema)?
398 } else {
399 record_batch
400 };
401
402 let batch = match &self.parquet_adapter {
404 ParquetAdapter::Flat(_) => record_batch,
405 ParquetAdapter::PrimaryKeyToFlat(p) => p.convert_batch(record_batch)?,
406 };
407
408 let Some(override_array) = override_sequence_array else {
410 return Ok(batch);
411 };
412
413 let mut columns = batch.columns().to_vec();
414 let sequence_column_idx = sequence_column_index(batch.num_columns());
415
416 let sequence_array = if override_array.len() > batch.num_rows() {
418 override_array.slice(0, batch.num_rows())
419 } else {
420 override_array.clone()
421 };
422
423 columns[sequence_column_idx] = sequence_array;
424
425 RecordBatch::try_new(batch.schema(), columns).context(NewRecordBatchSnafu)
426 }
427
428 pub(crate) fn is_legacy_format(
434 metadata: &RegionMetadata,
435 num_columns: usize,
436 file_path: &str,
437 ) -> Result<bool> {
438 if metadata.primary_key.is_empty() {
439 return Ok(false);
440 }
441
442 let expected_columns = metadata.column_metadatas.len() + INTERNAL_COLUMN_NUM;
445
446 if expected_columns == num_columns {
447 Ok(false)
449 } else {
450 ensure!(
451 expected_columns >= num_columns,
452 InvalidParquetSnafu {
453 file: file_path,
454 reason: format!(
455 "Expected columns {} should be >= actual columns {}",
456 expected_columns, num_columns
457 )
458 }
459 );
460
461 let column_diff = expected_columns - num_columns;
463
464 ensure!(
465 column_diff == metadata.primary_key.len(),
466 InvalidParquetSnafu {
467 file: file_path,
468 reason: format!(
469 "Column number difference {} does not match primary key count {}",
470 column_diff,
471 metadata.primary_key.len()
472 )
473 }
474 );
475
476 Ok(true)
477 }
478 }
479}
480
481enum ParquetAdapter {
483 Flat(ParquetFlat),
484 PrimaryKeyToFlat(ParquetPrimaryKeyToFlat),
485}
486
487struct ParquetPrimaryKeyToFlat {
489 format: PrimaryKeyReadFormat,
491 convert_format: Option<FlatConvertFormat>,
493 format_projection: FormatProjection,
495}
496
497impl ParquetPrimaryKeyToFlat {
498 fn new(
500 metadata: RegionMetadataRef,
501 read_cols: ReadColumns,
502 skip_auto_convert: bool,
503 ) -> ParquetPrimaryKeyToFlat {
504 assert!(if skip_auto_convert {
505 metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse
506 } else {
507 true
508 });
509
510 let id_to_index = sst_column_id_indices(&metadata);
512 let sst_column_num =
513 flat_sst_arrow_schema_column_num(&metadata, &FlatSchemaOptions::default());
514
515 let codec = build_primary_key_codec(&metadata);
516 let format = PrimaryKeyReadFormat::new(metadata.clone(), read_cols.clone());
517 let (convert_format, format_projection) = if skip_auto_convert {
518 (
519 None,
520 FormatProjection {
521 parquet_read_cols: format.parquet_read_columns().clone(),
522 column_id_to_projected_index: format.field_id_to_projected_index().clone(),
523 },
524 )
525 } else {
526 let format_projection = FormatProjection::compute_format_projection(
528 &metadata,
529 &id_to_index,
530 sst_column_num,
531 read_cols.clone(),
532 );
533 (
534 FlatConvertFormat::new(Arc::clone(&metadata), &format_projection, codec),
535 format_projection,
536 )
537 };
538
539 Self {
540 format,
541 convert_format,
542 format_projection,
543 }
544 }
545
546 fn convert_batch(&self, record_batch: RecordBatch) -> Result<RecordBatch> {
547 if let Some(convert_format) = &self.convert_format {
548 convert_format.convert(record_batch)
549 } else {
550 Ok(record_batch)
551 }
552 }
553}
554
555struct ParquetFlat {
557 metadata: RegionMetadataRef,
559 arrow_schema: SchemaRef,
561 format_projection: FormatProjection,
563 column_id_to_sst_index: HashMap<ColumnId, usize>,
565}
566
567impl ParquetFlat {
568 fn new(
570 metadata: RegionMetadataRef,
571 read_cols: ReadColumns,
572 arrow_schema: SchemaRef,
573 ) -> ParquetFlat {
574 let id_to_index = sst_column_id_indices(&metadata);
576 let sst_column_num =
577 flat_sst_arrow_schema_column_num(&metadata, &FlatSchemaOptions::default());
578 let format_projection = FormatProjection::compute_format_projection(
579 &metadata,
580 &id_to_index,
581 sst_column_num,
582 read_cols,
583 );
584
585 Self {
586 metadata,
587 arrow_schema,
588 format_projection,
589 column_id_to_sst_index: id_to_index,
590 }
591 }
592
593 fn min_values(
595 &self,
596 row_groups: &[impl Borrow<RowGroupMetaData>],
597 column_id: ColumnId,
598 ) -> StatValues {
599 self.get_stat_values(row_groups, column_id, true)
600 }
601
602 fn max_values(
604 &self,
605 row_groups: &[impl Borrow<RowGroupMetaData>],
606 column_id: ColumnId,
607 ) -> StatValues {
608 self.get_stat_values(row_groups, column_id, false)
609 }
610
611 fn null_counts(
613 &self,
614 row_groups: &[impl Borrow<RowGroupMetaData>],
615 column_id: ColumnId,
616 ) -> StatValues {
617 let Some(index) = self.column_id_to_sst_index.get(&column_id) else {
618 return StatValues::NoColumn;
620 };
621
622 let stats = column_null_counts(row_groups, *index);
623 StatValues::from_stats_opt(stats)
624 }
625
626 fn get_stat_values(
627 &self,
628 row_groups: &[impl Borrow<RowGroupMetaData>],
629 column_id: ColumnId,
630 is_min: bool,
631 ) -> StatValues {
632 let Some(column) = self.metadata.column_by_id(column_id) else {
633 return StatValues::NoColumn;
635 };
636 let index = self.column_id_to_sst_index.get(&column_id).unwrap();
638
639 let stats = column_values(row_groups, column, *index, is_min);
640 StatValues::from_stats_opt(stats)
641 }
642}
643
644pub(crate) fn sst_column_id_indices(metadata: &RegionMetadata) -> HashMap<ColumnId, usize> {
648 let mut id_to_index = HashMap::with_capacity(metadata.column_metadatas.len());
649 let mut column_index = 0;
650 for pk_id in &metadata.primary_key {
652 id_to_index.insert(*pk_id, column_index);
653 column_index += 1;
654 }
655 for column in &metadata.column_metadatas {
657 if column.semantic_type == SemanticType::Field {
658 id_to_index.insert(column.column_id, column_index);
659 column_index += 1;
660 }
661 }
662 id_to_index.insert(metadata.time_index_column().column_id, column_index);
664
665 id_to_index
666}
667
668pub(crate) fn decode_primary_keys(
672 codec: &dyn PrimaryKeyCodec,
673 batch: &RecordBatch,
674) -> Result<DecodedPrimaryKeys> {
675 let primary_key_index = primary_key_column_index(batch.num_columns());
676 let pk_dict_array = batch
677 .column(primary_key_index)
678 .as_any()
679 .downcast_ref::<PrimaryKeyArray>()
680 .with_context(|| InvalidRecordBatchSnafu {
681 reason: "Primary key column is not a dictionary array".to_string(),
682 })?;
683 let pk_values_array = pk_dict_array
684 .values()
685 .as_any()
686 .downcast_ref::<BinaryArray>()
687 .with_context(|| InvalidRecordBatchSnafu {
688 reason: "Primary key values are not binary array".to_string(),
689 })?;
690
691 let keys = pk_dict_array.keys();
692
693 let mut key_to_decoded_index = Vec::with_capacity(keys.len());
696 let mut decoded_pk_values = Vec::new();
697 let mut prev_key: Option<u32> = None;
698
699 let pk_indices = keys.values();
702 for ¤t_key in pk_indices.iter().take(keys.len()) {
703 if let Some(prev) = prev_key
705 && prev == current_key
706 {
707 key_to_decoded_index.push((decoded_pk_values.len() - 1) as u32);
709 continue;
710 }
711
712 let pk_bytes = pk_values_array.value(current_key as usize);
714 let decoded_value = codec.decode(pk_bytes).context(DecodeSnafu)?;
715
716 decoded_pk_values.push(decoded_value);
717 key_to_decoded_index.push((decoded_pk_values.len() - 1) as u32);
718 prev_key = Some(current_key);
719 }
720
721 let keys_array = UInt32Array::from(key_to_decoded_index);
723
724 Ok(DecodedPrimaryKeys {
725 decoded_pk_values,
726 keys_array,
727 })
728}
729
730pub(crate) struct DecodedPrimaryKeys {
732 decoded_pk_values: Vec<CompositeValues>,
734 keys_array: UInt32Array,
736}
737
738impl DecodedPrimaryKeys {
739 pub(crate) fn get_tag_column(
744 &self,
745 column_id: ColumnId,
746 pk_index: Option<usize>,
747 column_type: &ConcreteDataType,
748 ) -> Result<ArrayRef> {
749 let mut builder = column_type.create_mutable_vector(self.decoded_pk_values.len());
751 for decoded in &self.decoded_pk_values {
752 match decoded {
753 CompositeValues::Dense(dense) => {
754 let pk_idx = pk_index.expect("pk_index required for dense encoding");
755 if pk_idx < dense.len() {
756 builder.push_value_ref(&dense[pk_idx].1.as_value_ref());
757 } else {
758 builder.push_null();
759 }
760 }
761 CompositeValues::Sparse(sparse) => {
762 let value = sparse.get_or_null(column_id);
763 builder.push_value_ref(&value.as_value_ref());
764 }
765 };
766 }
767
768 let values_vector = builder.to_vector();
769 let values_array = values_vector.to_arrow_array();
770
771 if column_type.is_string() {
773 let dict_array = DictionaryArray::new(self.keys_array.clone(), values_array);
776 Ok(Arc::new(dict_array))
777 } else {
778 let taken_array =
780 take(&values_array, &self.keys_array, None).context(ComputeArrowSnafu)?;
781 Ok(taken_array)
782 }
783 }
784}
785
786pub(crate) struct FlatConvertFormat {
789 metadata: RegionMetadataRef,
791 codec: Arc<dyn PrimaryKeyCodec>,
793 projected_primary_keys: Vec<(ColumnId, usize, usize)>,
795}
796
797impl FlatConvertFormat {
798 pub(crate) fn new(
805 metadata: RegionMetadataRef,
806 format_projection: &FormatProjection,
807 codec: Arc<dyn PrimaryKeyCodec>,
808 ) -> Option<Self> {
809 if metadata.primary_key.is_empty() {
810 return None;
811 }
812
813 let mut projected_primary_keys = Vec::new();
815 for (pk_index, &column_id) in metadata.primary_key.iter().enumerate() {
816 if format_projection
817 .column_id_to_projected_index
818 .contains_key(&column_id)
819 {
820 let column_index = metadata.column_index_by_id(column_id).unwrap();
822 projected_primary_keys.push((column_id, pk_index, column_index));
823 }
824 }
825
826 Some(Self {
827 metadata,
828 codec,
829 projected_primary_keys,
830 })
831 }
832
833 pub(crate) fn convert(&self, batch: RecordBatch) -> Result<RecordBatch> {
837 if self.projected_primary_keys.is_empty() {
838 return Ok(batch);
839 }
840
841 let decoded_pks = decode_primary_keys(self.codec.as_ref(), &batch)?;
842
843 let mut decoded_columns = Vec::new();
845 for (column_id, pk_index, column_index) in &self.projected_primary_keys {
846 let column_metadata = &self.metadata.column_metadatas[*column_index];
847 let tag_column = decoded_pks.get_tag_column(
848 *column_id,
849 Some(*pk_index),
850 &column_metadata.column_schema.data_type,
851 )?;
852 decoded_columns.push(tag_column);
853 }
854
855 let mut new_columns = Vec::with_capacity(batch.num_columns() + decoded_columns.len());
857 new_columns.extend(decoded_columns);
858 new_columns.extend_from_slice(batch.columns());
859
860 let mut new_fields =
862 Vec::with_capacity(batch.schema().fields().len() + self.projected_primary_keys.len());
863 for (column_id, _, column_index) in &self.projected_primary_keys {
864 let column_metadata = &self.metadata.column_metadatas[*column_index];
865 let old_field = &self.metadata.schema.arrow_schema().fields()[*column_index];
866 let field =
867 tag_maybe_to_dictionary_field(&column_metadata.column_schema.data_type, old_field);
868 new_fields.push(Arc::new(with_field_id((*field).clone(), *column_id)));
869 }
870 new_fields.extend(batch.schema().fields().iter().cloned());
871
872 let new_schema = Arc::new(Schema::new(new_fields));
873 RecordBatch::try_new(new_schema, new_columns).context(NewRecordBatchSnafu)
874 }
875}
876
877#[cfg(test)]
878impl FlatReadFormat {
879 pub fn new_with_all_columns(metadata: RegionMetadataRef) -> FlatReadFormat {
881 Self::new(
882 Arc::clone(&metadata),
883 ReadColumns::new(metadata.column_metadatas.iter().map(|c| c.column_id)),
884 None,
885 "test",
886 false,
887 )
888 .unwrap()
889 }
890}
891
892#[cfg(test)]
893mod tests {
894 use std::sync::Arc;
895
896 use api::v1::SemanticType;
897 use datatypes::arrow::array::{
898 ArrayRef, BinaryArray, TimestampMillisecondArray, UInt8Array, UInt32Array, UInt64Array,
899 };
900 use datatypes::arrow::datatypes::DataType as ArrowDataType;
901 use datatypes::arrow::record_batch::RecordBatch;
902 use datatypes::prelude::ConcreteDataType;
903 use datatypes::schema::ColumnSchema;
904 use store_api::codec::PrimaryKeyEncoding;
905 use store_api::metadata::{ColumnMetadata, RegionMetadata, RegionMetadataBuilder};
906 use store_api::storage::RegionId;
907 use store_api::storage::consts::PRIMARY_KEY_COLUMN_NAME;
908
909 use super::*;
910 use crate::read::read_columns::ReadColumns;
911 use crate::sst::{
912 FlatSchemaOptions, PARQUET_FIELD_ID_KEY, PRIMARY_KEY_PARQUET_FIELD_ID,
913 flat_sst_arrow_schema_column_num, override_pk_field_to_binary, to_flat_sst_arrow_schema,
914 };
915
916 fn build_metadata(
918 num_tags: usize,
919 num_fields: usize,
920 encoding: PrimaryKeyEncoding,
921 ) -> RegionMetadata {
922 let mut builder = RegionMetadataBuilder::new(RegionId::new(0, 0));
923 let mut col_id = 0u32;
924
925 for i in 0..num_tags {
926 builder.push_column_metadata(ColumnMetadata {
927 column_schema: ColumnSchema::new(
928 format!("tag_{i}"),
929 ConcreteDataType::string_datatype(),
930 true,
931 ),
932 semantic_type: SemanticType::Tag,
933 column_id: col_id,
934 });
935 col_id += 1;
936 }
937
938 for i in 0..num_fields {
939 builder.push_column_metadata(ColumnMetadata {
940 column_schema: ColumnSchema::new(
941 format!("field_{i}"),
942 ConcreteDataType::uint64_datatype(),
943 true,
944 ),
945 semantic_type: SemanticType::Field,
946 column_id: col_id,
947 });
948 col_id += 1;
949 }
950
951 builder.push_column_metadata(ColumnMetadata {
952 column_schema: ColumnSchema::new(
953 "ts".to_string(),
954 ConcreteDataType::timestamp_millisecond_datatype(),
955 false,
956 ),
957 semantic_type: SemanticType::Timestamp,
958 column_id: col_id,
959 });
960
961 let primary_key: Vec<u32> = (0..num_tags as u32).collect();
962 builder.primary_key(primary_key);
963 builder.primary_key_encoding(encoding);
964 builder.build().unwrap()
965 }
966
967 #[test]
968 fn test_field_column_start() {
969 let cases = [
971 (1, 1, PrimaryKeyEncoding::Dense, 1),
972 (2, 2, PrimaryKeyEncoding::Dense, 2),
973 (0, 2, PrimaryKeyEncoding::Dense, 0),
974 (2, 2, PrimaryKeyEncoding::Sparse, 0),
975 ];
976
977 for (num_tags, num_fields, encoding, expected) in cases {
978 let metadata = build_metadata(num_tags, num_fields, encoding);
979 let options = FlatSchemaOptions::from_encoding(encoding);
980 let num_columns = flat_sst_arrow_schema_column_num(&metadata, &options);
981 let result = field_column_start(&metadata, num_columns);
982 assert_eq!(
983 result, expected,
984 "num_tags={num_tags}, num_fields={num_fields}, encoding={encoding:?}"
985 );
986 }
987 }
988
989 #[test]
990 fn test_convert_batch_wraps_binary_pk_to_dict() {
991 use datatypes::arrow::array::{Array, DictionaryArray, StringArray};
992 use datatypes::arrow::datatypes::UInt32Type;
993
994 let metadata = Arc::new(build_metadata(1, 1, PrimaryKeyEncoding::Dense));
998 let column_ids: Vec<u32> = metadata
999 .column_metadatas
1000 .iter()
1001 .map(|c| c.column_id)
1002 .collect();
1003 let mut read_format = FlatReadFormat::new(
1004 metadata.clone(),
1005 ReadColumns::new(column_ids),
1006 None,
1007 "test",
1008 false,
1009 )
1010 .unwrap();
1011 let output_schema = read_format.output_arrow_schema().unwrap();
1012 read_format.set_pk_as_binary(output_schema.clone());
1013 let binary_schema = override_pk_field_to_binary(&output_schema);
1014
1015 let pk_field = binary_schema
1018 .field_with_name(PRIMARY_KEY_COLUMN_NAME)
1019 .unwrap();
1020 assert_eq!(
1021 pk_field.metadata().get(PARQUET_FIELD_ID_KEY),
1022 Some(&PRIMARY_KEY_PARQUET_FIELD_ID.to_string()),
1023 "__primary_key field must retain its PARQUET:field_id after override_pk_field_to_binary"
1024 );
1025
1026 let tag_keys = UInt32Array::from(vec![0u32, 1, 1]);
1028 let tag_values = Arc::new(StringArray::from(vec!["t0", "t1"]));
1029 let tag_array: ArrayRef =
1030 Arc::new(DictionaryArray::<UInt32Type>::new(tag_keys, tag_values));
1031 let field_array: ArrayRef = Arc::new(UInt64Array::from(vec![10u64, 11, 12]));
1032 let ts_array: ArrayRef = Arc::new(TimestampMillisecondArray::from(vec![1i64, 2, 3]));
1033 let pk_array: ArrayRef = Arc::new(BinaryArray::from_iter_values(
1034 [b"alpha".as_ref(), b"beta", b"beta"].iter().copied(),
1035 ));
1036 let seq_array: ArrayRef = Arc::new(UInt64Array::from(vec![100u64, 101, 102]));
1037 let op_array: ArrayRef = Arc::new(UInt8Array::from(vec![1u8, 1, 1]));
1038
1039 let batch = RecordBatch::try_new(
1040 binary_schema,
1041 vec![
1042 tag_array,
1043 field_array,
1044 ts_array,
1045 pk_array,
1046 seq_array,
1047 op_array,
1048 ],
1049 )
1050 .unwrap();
1051
1052 let wrapped = read_format.convert_batch(batch, None).unwrap();
1053 assert_eq!(wrapped.schema(), output_schema);
1054
1055 let pk_idx = primary_key_column_index(wrapped.num_columns());
1056 let pk_col = wrapped.column(pk_idx);
1057 assert_eq!(
1058 pk_col.data_type(),
1059 &ArrowDataType::Dictionary(
1060 Box::new(ArrowDataType::UInt32),
1061 Box::new(ArrowDataType::Binary)
1062 )
1063 );
1064 let dict = pk_col
1065 .as_any()
1066 .downcast_ref::<DictionaryArray<UInt32Type>>()
1067 .unwrap();
1068 assert_eq!(dict.keys().values(), &[0, 1, 2]);
1069 let values = dict
1070 .values()
1071 .as_any()
1072 .downcast_ref::<BinaryArray>()
1073 .unwrap();
1074 assert_eq!(values.value(0), b"alpha");
1075 assert_eq!(values.value(1), b"beta");
1076 assert_eq!(values.value(2), b"beta");
1077 }
1078
1079 #[test]
1080 fn test_output_arrow_schema_uses_projection() {
1081 let metadata = Arc::new(build_metadata(1, 2, PrimaryKeyEncoding::Dense));
1082 let read_format = FlatReadFormat::new(
1083 metadata.clone(),
1084 ReadColumns::new([0_u32, 2_u32]),
1085 None,
1086 "test",
1087 false,
1088 )
1089 .unwrap();
1090
1091 let output_schema = read_format.output_arrow_schema().unwrap();
1092 let projection = read_format.parquet_read_columns().root_indices();
1093 let expected = Arc::new(
1094 to_flat_sst_arrow_schema(&metadata, &FlatSchemaOptions::default())
1095 .project(projection)
1096 .unwrap(),
1097 );
1098
1099 assert_eq!(expected, output_schema);
1100 }
1101}