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::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::json_align::align_schema_by_nested_paths;
60use crate::sst::parquet::read_columns::ParquetReadColumns;
61use crate::sst::{
62 FlatSchemaOptions, flat_sst_arrow_schema_column_num, tag_maybe_to_dictionary_field,
63 to_flat_sst_arrow_schema, with_field_id,
64};
65
66pub(crate) struct FlatWriteFormat {
68 arrow_schema: SchemaRef,
70 override_sequence: Option<SequenceNumber>,
71}
72
73impl FlatWriteFormat {
74 pub(crate) fn new(metadata: RegionMetadataRef, options: &FlatSchemaOptions) -> FlatWriteFormat {
76 let arrow_schema = to_flat_sst_arrow_schema(&metadata, options);
77 FlatWriteFormat {
78 arrow_schema,
79 override_sequence: None,
80 }
81 }
82
83 pub(crate) fn with_override_sequence(
85 mut self,
86 override_sequence: Option<SequenceNumber>,
87 ) -> Self {
88 self.override_sequence = override_sequence;
89 self
90 }
91
92 #[cfg(test)]
94 pub(crate) fn arrow_schema(&self) -> &SchemaRef {
95 &self.arrow_schema
96 }
97
98 pub(crate) fn convert_batch(&self, batch: &RecordBatch) -> Result<RecordBatch> {
100 debug_assert_eq!(batch.num_columns(), self.arrow_schema.fields().len());
101
102 let Some(override_sequence) = self.override_sequence else {
103 return Ok(batch.clone());
104 };
105
106 let mut columns = batch.columns().to_vec();
107 let sequence_array = Arc::new(UInt64Array::from(vec![override_sequence; batch.num_rows()]));
108 columns[sequence_column_index(batch.num_columns())] = sequence_array;
109
110 RecordBatch::try_new(batch.schema(), columns).context(NewRecordBatchSnafu)
111 }
112}
113
114pub(crate) fn sequence_column_index(num_columns: usize) -> usize {
116 num_columns - 2
117}
118
119pub(crate) fn time_index_column_index(num_columns: usize) -> usize {
121 num_columns - 4
122}
123
124pub(crate) fn primary_key_column_index(num_columns: usize) -> usize {
126 num_columns - 3
127}
128
129pub(crate) fn wrap_pk_binary_to_dict(
131 record_batch: RecordBatch,
132 dict_schema: &SchemaRef,
133) -> Result<RecordBatch> {
134 let pk_idx = primary_key_column_index(record_batch.num_columns());
135 let pk_column = record_batch.column(pk_idx);
136 let binary_array = pk_column
137 .as_any()
138 .downcast_ref::<BinaryArray>()
139 .with_context(|| InvalidRecordBatchSnafu {
140 reason: format!(
141 "expected BinaryArray for __primary_key, got {:?}",
142 pk_column.data_type()
143 ),
144 })?;
145 let n = binary_array.len();
146 let keys = UInt32Array::from_iter_values(0..n as u32);
147 let dict_array: ArrayRef = Arc::new(DictionaryArray::new(keys, pk_column.clone()));
148
149 let mut columns = record_batch.columns().to_vec();
150 columns[pk_idx] = dict_array;
151
152 RecordBatch::try_new(dict_schema.clone(), columns).context(NewRecordBatchSnafu)
153}
154
155pub(crate) fn op_type_column_index(num_columns: usize) -> usize {
157 num_columns - 1
158}
159
160pub(crate) fn field_column_start(metadata: &RegionMetadata, num_columns: usize) -> usize {
169 let field_column_count = metadata.column_metadatas.len() - 1 - metadata.primary_key.len();
172 num_columns - FIXED_POS_COLUMN_NUM - field_column_count
173}
174
175pub struct FlatReadFormat {
182 override_sequence: Option<SequenceNumber>,
184 parquet_adapter: ParquetAdapter,
186 pk_dict_wrap_schema: Option<SchemaRef>,
188}
189
190impl FlatReadFormat {
191 pub fn new(
195 metadata: RegionMetadataRef,
196 read_cols: ReadColumns,
197 file_schema: Option<SchemaRef>,
198 file_path: &str,
199 skip_auto_convert: bool,
200 ) -> Result<FlatReadFormat> {
201 let num_columns = file_schema.as_ref().map(|x| x.fields().len());
202 let is_legacy = match num_columns {
203 Some(num) => Self::is_legacy_format(&metadata, num, file_path)?,
204 None => metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse,
205 };
206
207 let parquet_adapter = if is_legacy {
208 if metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse {
210 ParquetAdapter::PrimaryKeyToFlat(ParquetPrimaryKeyToFlat::new(
212 metadata,
213 read_cols,
214 skip_auto_convert,
215 ))
216 } else {
217 ParquetAdapter::PrimaryKeyToFlat(ParquetPrimaryKeyToFlat::new(
218 metadata, read_cols, false,
219 ))
220 }
221 } else {
222 let file_schema = file_schema
223 .unwrap_or_else(|| to_flat_sst_arrow_schema(&metadata, &Default::default()));
224 ParquetAdapter::Flat(ParquetFlat::new(metadata, read_cols, file_schema))
225 };
226
227 Ok(FlatReadFormat {
228 override_sequence: None,
229 parquet_adapter,
230 pk_dict_wrap_schema: None,
231 })
232 }
233
234 pub(crate) fn set_override_sequence(&mut self, sequence: Option<SequenceNumber>) {
236 self.override_sequence = sequence;
237 }
238
239 pub(crate) fn set_pk_as_binary(&mut self) -> Result<()> {
241 self.pk_dict_wrap_schema = Some(self.output_arrow_schema()?);
242 Ok(())
243 }
244
245 pub fn projected_index_by_id(&self, column_id: ColumnId) -> Option<usize> {
247 self.format_projection()
248 .column_id_to_projected_index
249 .get(&column_id)
250 .copied()
251 }
252
253 pub fn min_values(
255 &self,
256 row_groups: &[impl Borrow<RowGroupMetaData>],
257 column_id: ColumnId,
258 ) -> StatValues {
259 match &self.parquet_adapter {
260 ParquetAdapter::Flat(p) => p.min_values(row_groups, column_id),
261 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.min_values(row_groups, column_id),
262 }
263 }
264
265 pub fn max_values(
267 &self,
268 row_groups: &[impl Borrow<RowGroupMetaData>],
269 column_id: ColumnId,
270 ) -> StatValues {
271 match &self.parquet_adapter {
272 ParquetAdapter::Flat(p) => p.max_values(row_groups, column_id),
273 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.max_values(row_groups, column_id),
274 }
275 }
276
277 pub fn null_counts(
279 &self,
280 row_groups: &[impl Borrow<RowGroupMetaData>],
281 column_id: ColumnId,
282 ) -> StatValues {
283 match &self.parquet_adapter {
284 ParquetAdapter::Flat(p) => p.null_counts(row_groups, column_id),
285 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.null_counts(row_groups, column_id),
286 }
287 }
288
289 pub(crate) fn arrow_schema(&self) -> &SchemaRef {
291 match &self.parquet_adapter {
292 ParquetAdapter::Flat(p) => &p.arrow_schema,
293 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.arrow_schema(),
294 }
295 }
296
297 pub(crate) fn output_arrow_schema(&self) -> Result<SchemaRef> {
299 let read_columns = self.parquet_read_columns();
300 let projection = read_columns.root_indices();
301 let mut schema = self
302 .arrow_schema()
303 .project(projection)
304 .context(ComputeArrowSnafu)?;
305 if read_columns.has_nested() {
306 debug_assert_eq!(schema.fields().len(), read_columns.columns().len());
307 let nested_paths = read_columns.columns().iter().map(|x| x.nested_paths());
308 align_schema_by_nested_paths(&mut schema, nested_paths);
309 }
310 Ok(Arc::new(schema))
311 }
312
313 pub(crate) fn metadata(&self) -> &RegionMetadataRef {
315 match &self.parquet_adapter {
316 ParquetAdapter::Flat(p) => &p.metadata,
317 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.metadata(),
318 }
319 }
320
321 pub(crate) fn parquet_read_columns(&self) -> &ParquetReadColumns {
323 match &self.parquet_adapter {
324 ParquetAdapter::Flat(p) => &p.format_projection.parquet_read_cols,
325 ParquetAdapter::PrimaryKeyToFlat(p) => p.format.parquet_read_columns(),
326 }
327 }
328
329 pub(crate) fn format_projection(&self) -> &FormatProjection {
334 match &self.parquet_adapter {
335 ParquetAdapter::Flat(p) => &p.format_projection,
336 ParquetAdapter::PrimaryKeyToFlat(p) => &p.format_projection,
337 }
338 }
339
340 pub(crate) fn batch_has_raw_pk_columns(&self) -> bool {
344 matches!(&self.parquet_adapter, ParquetAdapter::Flat(_))
345 }
346
347 pub(crate) fn new_override_sequence_array(&self, length: usize) -> Option<ArrayRef> {
349 self.override_sequence
350 .map(|seq| Arc::new(UInt64Array::from_value(seq, length)) as ArrayRef)
351 }
352
353 pub(crate) fn convert_batch(
358 &self,
359 record_batch: RecordBatch,
360 override_sequence_array: Option<&ArrayRef>,
361 ) -> Result<RecordBatch> {
362 let record_batch = if let Some(dict_schema) = &self.pk_dict_wrap_schema {
363 wrap_pk_binary_to_dict(record_batch, dict_schema)?
364 } else {
365 record_batch
366 };
367
368 let batch = match &self.parquet_adapter {
370 ParquetAdapter::Flat(_) => record_batch,
371 ParquetAdapter::PrimaryKeyToFlat(p) => p.convert_batch(record_batch)?,
372 };
373
374 let Some(override_array) = override_sequence_array else {
376 return Ok(batch);
377 };
378
379 let mut columns = batch.columns().to_vec();
380 let sequence_column_idx = sequence_column_index(batch.num_columns());
381
382 let sequence_array = if override_array.len() > batch.num_rows() {
384 override_array.slice(0, batch.num_rows())
385 } else {
386 override_array.clone()
387 };
388
389 columns[sequence_column_idx] = sequence_array;
390
391 RecordBatch::try_new(batch.schema(), columns).context(NewRecordBatchSnafu)
392 }
393
394 pub(crate) fn is_legacy_format(
400 metadata: &RegionMetadata,
401 num_columns: usize,
402 file_path: &str,
403 ) -> Result<bool> {
404 if metadata.primary_key.is_empty() {
405 return Ok(false);
406 }
407
408 let expected_columns = metadata.column_metadatas.len() + INTERNAL_COLUMN_NUM;
411
412 if expected_columns == num_columns {
413 Ok(false)
415 } else {
416 ensure!(
417 expected_columns >= num_columns,
418 InvalidParquetSnafu {
419 file: file_path,
420 reason: format!(
421 "Expected columns {} should be >= actual columns {}",
422 expected_columns, num_columns
423 )
424 }
425 );
426
427 let column_diff = expected_columns - num_columns;
429
430 ensure!(
431 column_diff == metadata.primary_key.len(),
432 InvalidParquetSnafu {
433 file: file_path,
434 reason: format!(
435 "Column number difference {} does not match primary key count {}",
436 column_diff,
437 metadata.primary_key.len()
438 )
439 }
440 );
441
442 Ok(true)
443 }
444 }
445}
446
447enum ParquetAdapter {
449 Flat(ParquetFlat),
450 PrimaryKeyToFlat(ParquetPrimaryKeyToFlat),
451}
452
453struct ParquetPrimaryKeyToFlat {
455 format: PrimaryKeyReadFormat,
457 convert_format: Option<FlatConvertFormat>,
459 format_projection: FormatProjection,
461}
462
463impl ParquetPrimaryKeyToFlat {
464 fn new(
466 metadata: RegionMetadataRef,
467 read_cols: ReadColumns,
468 skip_auto_convert: bool,
469 ) -> ParquetPrimaryKeyToFlat {
470 assert!(if skip_auto_convert {
471 metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse
472 } else {
473 true
474 });
475
476 let id_to_index = sst_column_id_indices(&metadata);
478 let sst_column_num =
479 flat_sst_arrow_schema_column_num(&metadata, &FlatSchemaOptions::default());
480
481 let codec = build_primary_key_codec(&metadata);
482 let format = PrimaryKeyReadFormat::new(metadata.clone(), read_cols.clone());
483 let (convert_format, format_projection) = if skip_auto_convert {
484 (
485 None,
486 FormatProjection {
487 parquet_read_cols: format.parquet_read_columns().clone(),
488 column_id_to_projected_index: format.field_id_to_projected_index().clone(),
489 },
490 )
491 } else {
492 let format_projection = FormatProjection::compute_format_projection(
494 &id_to_index,
495 sst_column_num,
496 read_cols.clone(),
497 );
498 (
499 FlatConvertFormat::new(Arc::clone(&metadata), &format_projection, codec),
500 format_projection,
501 )
502 };
503
504 Self {
505 format,
506 convert_format,
507 format_projection,
508 }
509 }
510
511 fn convert_batch(&self, record_batch: RecordBatch) -> Result<RecordBatch> {
512 if let Some(convert_format) = &self.convert_format {
513 convert_format.convert(record_batch)
514 } else {
515 Ok(record_batch)
516 }
517 }
518}
519
520struct ParquetFlat {
522 metadata: RegionMetadataRef,
524 arrow_schema: SchemaRef,
526 format_projection: FormatProjection,
528 column_id_to_sst_index: HashMap<ColumnId, usize>,
530}
531
532impl ParquetFlat {
533 fn new(
535 metadata: RegionMetadataRef,
536 read_cols: ReadColumns,
537 arrow_schema: SchemaRef,
538 ) -> ParquetFlat {
539 let id_to_index = sst_column_id_indices(&metadata);
541 let sst_column_num =
542 flat_sst_arrow_schema_column_num(&metadata, &FlatSchemaOptions::default());
543 let format_projection =
544 FormatProjection::compute_format_projection(&id_to_index, sst_column_num, read_cols);
545
546 Self {
547 metadata,
548 arrow_schema,
549 format_projection,
550 column_id_to_sst_index: id_to_index,
551 }
552 }
553
554 fn min_values(
556 &self,
557 row_groups: &[impl Borrow<RowGroupMetaData>],
558 column_id: ColumnId,
559 ) -> StatValues {
560 self.get_stat_values(row_groups, column_id, true)
561 }
562
563 fn max_values(
565 &self,
566 row_groups: &[impl Borrow<RowGroupMetaData>],
567 column_id: ColumnId,
568 ) -> StatValues {
569 self.get_stat_values(row_groups, column_id, false)
570 }
571
572 fn null_counts(
574 &self,
575 row_groups: &[impl Borrow<RowGroupMetaData>],
576 column_id: ColumnId,
577 ) -> StatValues {
578 let Some(index) = self.column_id_to_sst_index.get(&column_id) else {
579 return StatValues::NoColumn;
581 };
582
583 let stats = column_null_counts(row_groups, *index);
584 StatValues::from_stats_opt(stats)
585 }
586
587 fn get_stat_values(
588 &self,
589 row_groups: &[impl Borrow<RowGroupMetaData>],
590 column_id: ColumnId,
591 is_min: bool,
592 ) -> StatValues {
593 let Some(column) = self.metadata.column_by_id(column_id) else {
594 return StatValues::NoColumn;
596 };
597 let index = self.column_id_to_sst_index.get(&column_id).unwrap();
599
600 let stats = column_values(row_groups, column, *index, is_min);
601 StatValues::from_stats_opt(stats)
602 }
603}
604
605pub(crate) fn sst_column_id_indices(metadata: &RegionMetadata) -> HashMap<ColumnId, usize> {
609 let mut id_to_index = HashMap::with_capacity(metadata.column_metadatas.len());
610 let mut column_index = 0;
611 for pk_id in &metadata.primary_key {
613 id_to_index.insert(*pk_id, column_index);
614 column_index += 1;
615 }
616 for column in &metadata.column_metadatas {
618 if column.semantic_type == SemanticType::Field {
619 id_to_index.insert(column.column_id, column_index);
620 column_index += 1;
621 }
622 }
623 id_to_index.insert(metadata.time_index_column().column_id, column_index);
625
626 id_to_index
627}
628
629pub(crate) fn decode_primary_keys(
633 codec: &dyn PrimaryKeyCodec,
634 batch: &RecordBatch,
635) -> Result<DecodedPrimaryKeys> {
636 let primary_key_index = primary_key_column_index(batch.num_columns());
637 let pk_dict_array = batch
638 .column(primary_key_index)
639 .as_any()
640 .downcast_ref::<PrimaryKeyArray>()
641 .with_context(|| InvalidRecordBatchSnafu {
642 reason: "Primary key column is not a dictionary array".to_string(),
643 })?;
644 let pk_values_array = pk_dict_array
645 .values()
646 .as_any()
647 .downcast_ref::<BinaryArray>()
648 .with_context(|| InvalidRecordBatchSnafu {
649 reason: "Primary key values are not binary array".to_string(),
650 })?;
651
652 let keys = pk_dict_array.keys();
653
654 let mut key_to_decoded_index = Vec::with_capacity(keys.len());
657 let mut decoded_pk_values = Vec::new();
658 let mut prev_key: Option<u32> = None;
659
660 let pk_indices = keys.values();
663 for ¤t_key in pk_indices.iter().take(keys.len()) {
664 if let Some(prev) = prev_key
666 && prev == current_key
667 {
668 key_to_decoded_index.push((decoded_pk_values.len() - 1) as u32);
670 continue;
671 }
672
673 let pk_bytes = pk_values_array.value(current_key as usize);
675 let decoded_value = codec.decode(pk_bytes).context(DecodeSnafu)?;
676
677 decoded_pk_values.push(decoded_value);
678 key_to_decoded_index.push((decoded_pk_values.len() - 1) as u32);
679 prev_key = Some(current_key);
680 }
681
682 let keys_array = UInt32Array::from(key_to_decoded_index);
684
685 Ok(DecodedPrimaryKeys {
686 decoded_pk_values,
687 keys_array,
688 })
689}
690
691pub(crate) struct DecodedPrimaryKeys {
693 decoded_pk_values: Vec<CompositeValues>,
695 keys_array: UInt32Array,
697}
698
699impl DecodedPrimaryKeys {
700 pub(crate) fn get_tag_column(
705 &self,
706 column_id: ColumnId,
707 pk_index: Option<usize>,
708 column_type: &ConcreteDataType,
709 ) -> Result<ArrayRef> {
710 let mut builder = column_type.create_mutable_vector(self.decoded_pk_values.len());
712 for decoded in &self.decoded_pk_values {
713 match decoded {
714 CompositeValues::Dense(dense) => {
715 let pk_idx = pk_index.expect("pk_index required for dense encoding");
716 if pk_idx < dense.len() {
717 builder.push_value_ref(&dense[pk_idx].1.as_value_ref());
718 } else {
719 builder.push_null();
720 }
721 }
722 CompositeValues::Sparse(sparse) => {
723 let value = sparse.get_or_null(column_id);
724 builder.push_value_ref(&value.as_value_ref());
725 }
726 };
727 }
728
729 let values_vector = builder.to_vector();
730 let values_array = values_vector.to_arrow_array();
731
732 if column_type.is_string() {
734 let dict_array = DictionaryArray::new(self.keys_array.clone(), values_array);
737 Ok(Arc::new(dict_array))
738 } else {
739 let taken_array =
741 take(&values_array, &self.keys_array, None).context(ComputeArrowSnafu)?;
742 Ok(taken_array)
743 }
744 }
745}
746
747pub(crate) struct FlatConvertFormat {
750 metadata: RegionMetadataRef,
752 codec: Arc<dyn PrimaryKeyCodec>,
754 projected_primary_keys: Vec<(ColumnId, usize, usize)>,
756}
757
758impl FlatConvertFormat {
759 pub(crate) fn new(
766 metadata: RegionMetadataRef,
767 format_projection: &FormatProjection,
768 codec: Arc<dyn PrimaryKeyCodec>,
769 ) -> Option<Self> {
770 if metadata.primary_key.is_empty() {
771 return None;
772 }
773
774 let mut projected_primary_keys = Vec::new();
776 for (pk_index, &column_id) in metadata.primary_key.iter().enumerate() {
777 if format_projection
778 .column_id_to_projected_index
779 .contains_key(&column_id)
780 {
781 let column_index = metadata.column_index_by_id(column_id).unwrap();
783 projected_primary_keys.push((column_id, pk_index, column_index));
784 }
785 }
786
787 Some(Self {
788 metadata,
789 codec,
790 projected_primary_keys,
791 })
792 }
793
794 pub(crate) fn convert(&self, batch: RecordBatch) -> Result<RecordBatch> {
798 if self.projected_primary_keys.is_empty() {
799 return Ok(batch);
800 }
801
802 let decoded_pks = decode_primary_keys(self.codec.as_ref(), &batch)?;
803
804 let mut decoded_columns = Vec::new();
806 for (column_id, pk_index, column_index) in &self.projected_primary_keys {
807 let column_metadata = &self.metadata.column_metadatas[*column_index];
808 let tag_column = decoded_pks.get_tag_column(
809 *column_id,
810 Some(*pk_index),
811 &column_metadata.column_schema.data_type,
812 )?;
813 decoded_columns.push(tag_column);
814 }
815
816 let mut new_columns = Vec::with_capacity(batch.num_columns() + decoded_columns.len());
818 new_columns.extend(decoded_columns);
819 new_columns.extend_from_slice(batch.columns());
820
821 let mut new_fields =
823 Vec::with_capacity(batch.schema().fields().len() + self.projected_primary_keys.len());
824 for (column_id, _, column_index) in &self.projected_primary_keys {
825 let column_metadata = &self.metadata.column_metadatas[*column_index];
826 let old_field = &self.metadata.schema.arrow_schema().fields()[*column_index];
827 let field =
828 tag_maybe_to_dictionary_field(&column_metadata.column_schema.data_type, old_field);
829 new_fields.push(Arc::new(with_field_id((*field).clone(), *column_id)));
830 }
831 new_fields.extend(batch.schema().fields().iter().cloned());
832
833 let new_schema = Arc::new(Schema::new(new_fields));
834 RecordBatch::try_new(new_schema, new_columns).context(NewRecordBatchSnafu)
835 }
836}
837
838#[cfg(test)]
839impl FlatReadFormat {
840 pub fn new_with_all_columns(metadata: RegionMetadataRef) -> FlatReadFormat {
842 Self::new(
843 Arc::clone(&metadata),
844 ReadColumns::from_deduped_column_ids(
845 metadata.column_metadatas.iter().map(|c| c.column_id),
846 ),
847 None,
848 "test",
849 false,
850 )
851 .unwrap()
852 }
853}
854
855#[cfg(test)]
856mod tests {
857 use std::sync::Arc;
858
859 use api::v1::SemanticType;
860 use datatypes::arrow::array::{
861 ArrayRef, BinaryArray, TimestampMillisecondArray, UInt8Array, UInt32Array, UInt64Array,
862 };
863 use datatypes::arrow::datatypes::DataType as ArrowDataType;
864 use datatypes::arrow::record_batch::RecordBatch;
865 use datatypes::prelude::ConcreteDataType;
866 use datatypes::schema::ColumnSchema;
867 use store_api::codec::PrimaryKeyEncoding;
868 use store_api::metadata::{ColumnMetadata, RegionMetadata, RegionMetadataBuilder};
869 use store_api::storage::RegionId;
870 use store_api::storage::consts::PRIMARY_KEY_COLUMN_NAME;
871
872 use super::*;
873 use crate::read::read_columns::ReadColumns;
874 use crate::sst::{
875 FlatSchemaOptions, PARQUET_FIELD_ID_KEY, PRIMARY_KEY_PARQUET_FIELD_ID,
876 flat_sst_arrow_schema_column_num, override_pk_field_to_binary, to_flat_sst_arrow_schema,
877 };
878
879 fn build_metadata(
881 num_tags: usize,
882 num_fields: usize,
883 encoding: PrimaryKeyEncoding,
884 ) -> RegionMetadata {
885 let mut builder = RegionMetadataBuilder::new(RegionId::new(0, 0));
886 let mut col_id = 0u32;
887
888 for i in 0..num_tags {
889 builder.push_column_metadata(ColumnMetadata {
890 column_schema: ColumnSchema::new(
891 format!("tag_{i}"),
892 ConcreteDataType::string_datatype(),
893 true,
894 ),
895 semantic_type: SemanticType::Tag,
896 column_id: col_id,
897 });
898 col_id += 1;
899 }
900
901 for i in 0..num_fields {
902 builder.push_column_metadata(ColumnMetadata {
903 column_schema: ColumnSchema::new(
904 format!("field_{i}"),
905 ConcreteDataType::uint64_datatype(),
906 true,
907 ),
908 semantic_type: SemanticType::Field,
909 column_id: col_id,
910 });
911 col_id += 1;
912 }
913
914 builder.push_column_metadata(ColumnMetadata {
915 column_schema: ColumnSchema::new(
916 "ts".to_string(),
917 ConcreteDataType::timestamp_millisecond_datatype(),
918 false,
919 ),
920 semantic_type: SemanticType::Timestamp,
921 column_id: col_id,
922 });
923
924 let primary_key: Vec<u32> = (0..num_tags as u32).collect();
925 builder.primary_key(primary_key);
926 builder.primary_key_encoding(encoding);
927 builder.build().unwrap()
928 }
929
930 #[test]
931 fn test_field_column_start() {
932 let cases = [
934 (1, 1, PrimaryKeyEncoding::Dense, 1),
935 (2, 2, PrimaryKeyEncoding::Dense, 2),
936 (0, 2, PrimaryKeyEncoding::Dense, 0),
937 (2, 2, PrimaryKeyEncoding::Sparse, 0),
938 ];
939
940 for (num_tags, num_fields, encoding, expected) in cases {
941 let metadata = build_metadata(num_tags, num_fields, encoding);
942 let options = FlatSchemaOptions::from_encoding(encoding);
943 let num_columns = flat_sst_arrow_schema_column_num(&metadata, &options);
944 let result = field_column_start(&metadata, num_columns);
945 assert_eq!(
946 result, expected,
947 "num_tags={num_tags}, num_fields={num_fields}, encoding={encoding:?}"
948 );
949 }
950 }
951
952 #[test]
953 fn test_convert_batch_wraps_binary_pk_to_dict() {
954 use datatypes::arrow::array::{Array, DictionaryArray, StringArray};
955 use datatypes::arrow::datatypes::UInt32Type;
956
957 let metadata = Arc::new(build_metadata(1, 1, PrimaryKeyEncoding::Dense));
961 let column_ids: Vec<u32> = metadata
962 .column_metadatas
963 .iter()
964 .map(|c| c.column_id)
965 .collect();
966 let mut read_format = FlatReadFormat::new(
967 metadata.clone(),
968 ReadColumns::from_deduped_column_ids(column_ids),
969 None,
970 "test",
971 false,
972 )
973 .unwrap();
974 read_format.set_pk_as_binary().unwrap();
975
976 let output_schema = read_format.output_arrow_schema().unwrap();
977 let binary_schema = override_pk_field_to_binary(&output_schema);
978
979 let pk_field = binary_schema
982 .field_with_name(PRIMARY_KEY_COLUMN_NAME)
983 .unwrap();
984 assert_eq!(
985 pk_field.metadata().get(PARQUET_FIELD_ID_KEY),
986 Some(&PRIMARY_KEY_PARQUET_FIELD_ID.to_string()),
987 "__primary_key field must retain its PARQUET:field_id after override_pk_field_to_binary"
988 );
989
990 let tag_keys = UInt32Array::from(vec![0u32, 1, 1]);
992 let tag_values = Arc::new(StringArray::from(vec!["t0", "t1"]));
993 let tag_array: ArrayRef =
994 Arc::new(DictionaryArray::<UInt32Type>::new(tag_keys, tag_values));
995 let field_array: ArrayRef = Arc::new(UInt64Array::from(vec![10u64, 11, 12]));
996 let ts_array: ArrayRef = Arc::new(TimestampMillisecondArray::from(vec![1i64, 2, 3]));
997 let pk_array: ArrayRef = Arc::new(BinaryArray::from_iter_values(
998 [b"alpha".as_ref(), b"beta", b"beta"].iter().copied(),
999 ));
1000 let seq_array: ArrayRef = Arc::new(UInt64Array::from(vec![100u64, 101, 102]));
1001 let op_array: ArrayRef = Arc::new(UInt8Array::from(vec![1u8, 1, 1]));
1002
1003 let batch = RecordBatch::try_new(
1004 binary_schema,
1005 vec![
1006 tag_array,
1007 field_array,
1008 ts_array,
1009 pk_array,
1010 seq_array,
1011 op_array,
1012 ],
1013 )
1014 .unwrap();
1015
1016 let wrapped = read_format.convert_batch(batch, None).unwrap();
1017 assert_eq!(wrapped.schema(), output_schema);
1018
1019 let pk_idx = primary_key_column_index(wrapped.num_columns());
1020 let pk_col = wrapped.column(pk_idx);
1021 assert_eq!(
1022 pk_col.data_type(),
1023 &ArrowDataType::Dictionary(
1024 Box::new(ArrowDataType::UInt32),
1025 Box::new(ArrowDataType::Binary)
1026 )
1027 );
1028 let dict = pk_col
1029 .as_any()
1030 .downcast_ref::<DictionaryArray<UInt32Type>>()
1031 .unwrap();
1032 assert_eq!(dict.keys().values(), &[0, 1, 2]);
1033 let values = dict
1034 .values()
1035 .as_any()
1036 .downcast_ref::<BinaryArray>()
1037 .unwrap();
1038 assert_eq!(values.value(0), b"alpha");
1039 assert_eq!(values.value(1), b"beta");
1040 assert_eq!(values.value(2), b"beta");
1041 }
1042
1043 #[test]
1044 fn test_output_arrow_schema_uses_projection() {
1045 let metadata = Arc::new(build_metadata(1, 2, PrimaryKeyEncoding::Dense));
1046 let read_format = FlatReadFormat::new(
1047 metadata.clone(),
1048 ReadColumns::from_deduped_column_ids([0_u32, 2_u32]),
1049 None,
1050 "test",
1051 false,
1052 )
1053 .unwrap();
1054
1055 let output_schema = read_format.output_arrow_schema().unwrap();
1056 let projection = read_format.parquet_read_columns().root_indices();
1057 let expected = Arc::new(
1058 to_flat_sst_arrow_schema(&metadata, &FlatSchemaOptions::default())
1059 .project(projection)
1060 .unwrap(),
1061 );
1062
1063 assert_eq!(expected, output_schema);
1064 }
1065}