1use std::collections::HashMap;
18use std::sync::Arc;
19
20use api::v1::SemanticType;
21use arrow_schema::DataType;
22use arrow_schema::extension::{EXTENSION_TYPE_NAME_KEY, ExtensionType};
23use common_base::readable_size::ReadableSize;
24use common_query::native_histogram::{
25 is_native_histogram_value_type, native_histogram_list_element_id, native_histogram_subfield_id,
26};
27use datatypes::arrow::datatypes::{
28 DataType as ArrowDataType, Field, FieldRef, Fields, Schema, SchemaRef,
29};
30use datatypes::arrow::record_batch::RecordBatch;
31use datatypes::extension::histogram::HistogramExtensionType;
32use datatypes::prelude::ConcreteDataType;
33use datatypes::timestamp::timestamp_array_to_primitive;
34use serde::{Deserialize, Serialize};
35use store_api::codec::PrimaryKeyEncoding;
36use store_api::metadata::RegionMetadata;
37use store_api::storage::consts::{
38 OP_TYPE_COLUMN_NAME, PRIMARY_KEY_COLUMN_NAME, SEQUENCE_COLUMN_NAME,
39};
40
41use crate::error::{InvalidNativeHistogramFieldIdSnafu, InvalidNativeHistogramSubfieldSnafu};
42use crate::sst::parquet::flat_format::time_index_column_index;
43
44pub mod file;
45pub mod file_purger;
46pub mod file_ref;
47pub mod index;
48pub mod location;
49pub mod parquet;
50pub(crate) mod version;
51
52pub const DEFAULT_WRITE_BUFFER_SIZE: ReadableSize = ReadableSize::mb(8);
54
55pub const DEFAULT_WRITE_CONCURRENCY: usize = 8;
57
58#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::EnumString)]
60#[serde(rename_all = "snake_case")]
61#[strum(serialize_all = "snake_case")]
62pub enum FormatType {
63 #[default]
65 PrimaryKey,
66 Flat,
68}
69
70pub const PARQUET_FIELD_ID_KEY: &str = "PARQUET:field_id";
72
73pub fn with_field_id(mut field: Field, column_id: u32) -> Field {
78 field
79 .metadata_mut()
80 .insert(PARQUET_FIELD_ID_KEY.to_string(), column_id.to_string());
81 field
82}
83
84fn stamp_native_histogram_subfield_ids(field: &mut Field) -> crate::error::Result<()> {
100 if !is_native_histogram_value_type(&ConcreteDataType::from_arrow_type(field.data_type())) {
101 return Ok(());
102 }
103 let column_id = field
108 .metadata()
109 .get(PARQUET_FIELD_ID_KEY)
110 .and_then(|s| s.parse::<i32>().ok())
111 .ok_or_else(|| {
112 InvalidNativeHistogramFieldIdSnafu {
113 field_name: field.name().clone(),
114 }
115 .build()
116 })?;
117 field.metadata_mut().insert(
119 EXTENSION_TYPE_NAME_KEY.to_string(),
120 HistogramExtensionType::NAME.to_string(),
121 );
122 let ArrowDataType::Struct(children) = field.data_type() else {
123 return Ok(());
124 };
125 let new_children: crate::error::Result<Fields> = children
126 .iter()
127 .map(|child| {
128 let mut c = (**child).clone();
129 let id = native_histogram_subfield_id(column_id, c.name()).ok_or_else(|| {
134 InvalidNativeHistogramSubfieldSnafu {
135 column_id,
136 field_name: c.name().clone(),
137 }
138 .build()
139 })?;
140 c.metadata_mut()
142 .insert(PARQUET_FIELD_ID_KEY.to_string(), id.to_string());
143 if let ArrowDataType::List(elem) = c.data_type() {
145 let elem_id =
146 native_histogram_list_element_id(column_id, c.name()).ok_or_else(|| {
147 InvalidNativeHistogramSubfieldSnafu {
148 column_id,
149 field_name: c.name().clone(),
150 }
151 .build()
152 })?;
153 let mut new_elem = (**elem).clone();
154 new_elem
155 .metadata_mut()
156 .insert(PARQUET_FIELD_ID_KEY.to_string(), elem_id.to_string());
157 c.set_data_type(ArrowDataType::List(Arc::new(new_elem)));
158 }
159 Ok(Arc::new(c))
160 })
161 .collect();
162 field.set_data_type(ArrowDataType::Struct(new_children?));
163 Ok(())
164}
165
166pub fn maybe_wrap_schema(schema: &SchemaRef) -> crate::error::Result<SchemaRef> {
176 if !schema
179 .fields()
180 .iter()
181 .any(|f| matches!(f.data_type(), ArrowDataType::Struct(_)))
182 {
183 return Ok(schema.clone());
184 }
185 let new_fields: crate::error::Result<Vec<FieldRef>> = schema
186 .fields()
187 .iter()
188 .map(|f| {
189 let mut field = (**f).clone();
190 stamp_native_histogram_subfield_ids(&mut field)?;
191 Ok(Arc::new(field))
192 })
193 .collect();
194 Ok(Arc::new(Schema::new_with_metadata(
195 Fields::from(new_fields?),
196 schema.metadata().clone(),
197 )))
198}
199
200pub(crate) const INTERNAL_PARQUET_FIELD_ID_BASE: u32 = 1 << 30;
203
204pub(crate) const PRIMARY_KEY_PARQUET_FIELD_ID: u32 = INTERNAL_PARQUET_FIELD_ID_BASE;
206pub(crate) const SEQUENCE_PARQUET_FIELD_ID: u32 = INTERNAL_PARQUET_FIELD_ID_BASE + 1;
208pub(crate) const OP_TYPE_PARQUET_FIELD_ID: u32 = INTERNAL_PARQUET_FIELD_ID_BASE + 2;
210
211pub fn to_sst_arrow_schema(metadata: &RegionMetadata) -> SchemaRef {
213 let fields = Fields::from_iter(
214 metadata
215 .schema
216 .arrow_schema()
217 .fields()
218 .iter()
219 .zip(&metadata.column_metadatas)
220 .filter_map(|(field, column_meta)| {
221 if column_meta.semantic_type == SemanticType::Field {
222 Some(Arc::new(with_field_id(
223 (**field).clone(),
224 column_meta.column_id,
225 )))
226 } else {
227 None
229 }
230 })
231 .chain([Arc::new(with_field_id(
232 (*metadata.time_index_field()).clone(),
233 metadata.time_index_column().column_id,
234 ))])
235 .chain(internal_fields()),
236 );
237
238 Arc::new(Schema::new(fields))
239}
240
241pub struct FlatSchemaOptions {
243 pub raw_pk_columns: bool,
245 pub string_pk_use_dict: bool,
249 pub concretized_json_types: HashMap<String, DataType>,
252}
253
254impl Default for FlatSchemaOptions {
255 fn default() -> Self {
256 Self {
257 raw_pk_columns: true,
258 string_pk_use_dict: true,
259 concretized_json_types: HashMap::new(),
260 }
261 }
262}
263
264impl FlatSchemaOptions {
265 pub fn from_encoding(encoding: PrimaryKeyEncoding) -> Self {
267 if encoding == PrimaryKeyEncoding::Dense {
268 Self::default()
269 } else {
270 Self {
271 raw_pk_columns: false,
272 string_pk_use_dict: false,
273 concretized_json_types: HashMap::new(),
274 }
275 }
276 }
277}
278
279pub fn to_flat_sst_arrow_schema(
289 metadata: &RegionMetadata,
290 options: &FlatSchemaOptions,
291) -> SchemaRef {
292 let num_fields = flat_sst_arrow_schema_column_num(metadata, options);
293 let mut fields = Vec::with_capacity(num_fields);
294 let schema = metadata.schema.arrow_schema();
295 if options.raw_pk_columns {
296 for pk_id in &metadata.primary_key {
297 let pk_index = metadata.column_index_by_id(*pk_id).unwrap();
298 let column_id = metadata.column_metadatas[pk_index].column_id;
299 if options.string_pk_use_dict {
300 let old_field = &schema.fields[pk_index];
301 let new_field = tag_maybe_to_dictionary_field(
302 &metadata.column_metadatas[pk_index].column_schema.data_type,
303 old_field,
304 );
305 let new_field = concretize_json_type(new_field, options);
306 fields.push(Arc::new(with_field_id((*new_field).clone(), column_id)));
307 }
308 }
309 }
310 let remaining_fields = schema
311 .fields()
312 .iter()
313 .zip(&metadata.column_metadatas)
314 .filter_map(|(field, column_meta)| {
315 if column_meta.semantic_type == SemanticType::Field {
316 let field = concretize_json_type(field.clone(), options);
317 Some(Arc::new(with_field_id(
318 Arc::unwrap_or_clone(field),
319 column_meta.column_id,
320 )))
321 } else {
322 None
323 }
324 })
325 .chain([Arc::new(with_field_id(
326 (*metadata.time_index_field()).clone(),
327 metadata.time_index_column().column_id,
328 ))])
329 .chain(internal_fields());
330 for field in remaining_fields {
331 fields.push(field);
332 }
333
334 Arc::new(Schema::new(fields))
335}
336
337fn concretize_json_type(field: Arc<Field>, options: &FlatSchemaOptions) -> Arc<Field> {
338 if let Some(data_type) = options.concretized_json_types.get(field.name()) {
339 let mut field = Arc::unwrap_or_clone(field);
340 field.set_data_type(data_type.clone());
341 Arc::new(field)
342 } else {
343 field
344 }
345}
346
347pub fn flat_sst_arrow_schema_column_num(
349 metadata: &RegionMetadata,
350 options: &FlatSchemaOptions,
351) -> usize {
352 if options.raw_pk_columns {
353 metadata.column_metadatas.len() + 3
354 } else {
355 metadata.column_metadatas.len() + 3 - metadata.primary_key.len()
356 }
357}
358
359fn to_dictionary_field(field: &Field) -> Field {
361 let mut new_field = Field::new_dictionary(
362 field.name(),
363 datatypes::arrow::datatypes::DataType::UInt32,
364 field.data_type().clone(),
365 field.is_nullable(),
366 );
367
368 if let Some(field_id) = field.metadata().get(PARQUET_FIELD_ID_KEY) {
370 new_field
371 .metadata_mut()
372 .insert(PARQUET_FIELD_ID_KEY.to_string(), field_id.clone());
373 }
374
375 new_field
376}
377
378pub(crate) fn tag_maybe_to_dictionary_field(
380 data_type: &ConcreteDataType,
381 field: &Arc<Field>,
382) -> Arc<Field> {
383 if data_type.is_string() {
384 Arc::new(to_dictionary_field(field))
385 } else {
386 field.clone()
387 }
388}
389
390pub(crate) fn internal_fields() -> [FieldRef; 3] {
392 [
394 Arc::new(with_field_id(
395 Field::new_dictionary(
396 PRIMARY_KEY_COLUMN_NAME,
397 ArrowDataType::UInt32,
398 ArrowDataType::Binary,
399 false,
400 ),
401 PRIMARY_KEY_PARQUET_FIELD_ID,
402 )),
403 Arc::new(with_field_id(
404 Field::new(SEQUENCE_COLUMN_NAME, ArrowDataType::UInt64, false),
405 SEQUENCE_PARQUET_FIELD_ID,
406 )),
407 Arc::new(with_field_id(
408 Field::new(OP_TYPE_COLUMN_NAME, ArrowDataType::UInt8, false),
409 OP_TYPE_PARQUET_FIELD_ID,
410 )),
411 ]
412}
413
414pub(crate) fn override_pk_field_to_binary(schema: &SchemaRef) -> SchemaRef {
416 let new_fields = schema
417 .fields()
418 .iter()
419 .map(|field| {
420 if field.name() == PRIMARY_KEY_COLUMN_NAME {
421 let mut new_field = Field::new(
422 PRIMARY_KEY_COLUMN_NAME,
423 ArrowDataType::Binary,
424 field.is_nullable(),
425 );
426 if let Some(field_id) = field.metadata().get(PARQUET_FIELD_ID_KEY) {
429 new_field
430 .metadata_mut()
431 .insert(PARQUET_FIELD_ID_KEY.to_string(), field_id.clone());
432 }
433 Arc::new(new_field)
434 } else {
435 field.clone()
436 }
437 })
438 .collect::<Vec<_>>();
439 Arc::new(Schema::new(new_fields))
440}
441
442#[derive(Default)]
447pub(crate) struct SeriesEstimator {
448 last_timestamp: Option<i64>,
450 series_count: u64,
452}
453
454impl SeriesEstimator {
455 pub(crate) fn update_flat(&mut self, record_batch: &RecordBatch) {
459 let batch_rows = record_batch.num_rows();
460 if batch_rows == 0 {
461 return;
462 }
463
464 let time_index_pos = time_index_column_index(record_batch.num_columns());
465 let timestamps = record_batch.column(time_index_pos);
466 let Some((ts_values, _unit)) = timestamp_array_to_primitive(timestamps) else {
467 return;
468 };
469 let values = ts_values.values();
470
471 if let Some(last_ts) = self.last_timestamp {
473 if values[0] <= last_ts {
474 self.series_count += 1;
475 }
476 } else {
477 self.series_count = 1;
479 }
480
481 for i in 0..batch_rows - 1 {
483 if values[i] >= values[i + 1] {
486 self.series_count += 1;
487 }
488 }
489
490 self.last_timestamp = Some(values[batch_rows - 1]);
492 }
493
494 pub(crate) fn finish(&mut self) -> u64 {
496 self.last_timestamp = None;
497 let count = self.series_count;
498 self.series_count = 0;
499
500 count
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use std::sync::Arc;
507
508 use common_query::prelude::greptime_native_histogram;
509 use datatypes::arrow::array::{
510 BinaryArray, DictionaryArray, TimestampMillisecondArray, UInt8Array, UInt32Array,
511 UInt64Array,
512 };
513 use datatypes::arrow::datatypes::{DataType as ArrowDataType, Field, Schema, TimeUnit};
514 use datatypes::arrow::record_batch::RecordBatch;
515
516 use super::*;
517
518 fn new_flat_record_batch(timestamps: &[i64]) -> RecordBatch {
519 let num_cols = 4; let time_index_pos = time_index_column_index(num_cols);
522 assert_eq!(time_index_pos, 0); let time_array = Arc::new(TimestampMillisecondArray::from(timestamps.to_vec()));
525 let pk_array = Arc::new(DictionaryArray::new(
526 UInt32Array::from(vec![0; timestamps.len()]),
527 Arc::new(BinaryArray::from(vec![b"test".as_slice()])),
528 ));
529 let seq_array = Arc::new(UInt64Array::from(vec![1; timestamps.len()]));
530 let op_array = Arc::new(UInt8Array::from(vec![1; timestamps.len()]));
531
532 let schema = Arc::new(Schema::new(vec![
533 Field::new(
534 "time",
535 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
536 false,
537 ),
538 Field::new_dictionary(
539 "__primary_key",
540 ArrowDataType::UInt32,
541 ArrowDataType::Binary,
542 false,
543 ),
544 Field::new("__sequence", ArrowDataType::UInt64, false),
545 Field::new("__op_type", ArrowDataType::UInt8, false),
546 ]));
547
548 RecordBatch::try_new(schema, vec![time_array, pk_array, seq_array, op_array]).unwrap()
549 }
550
551 #[test]
552 fn test_series_estimator_flat_empty_batch() {
553 let mut estimator = SeriesEstimator::default();
554 let record_batch = new_flat_record_batch(&[]);
555 estimator.update_flat(&record_batch);
556 assert_eq!(0, estimator.finish());
557 }
558
559 #[test]
560 fn test_series_estimator_flat_single_batch() {
561 let mut estimator = SeriesEstimator::default();
562 let record_batch = new_flat_record_batch(&[1, 2, 3]);
563 estimator.update_flat(&record_batch);
564 assert_eq!(1, estimator.finish());
565 }
566
567 #[test]
568 fn test_series_estimator_flat_series_boundary_within_batch() {
569 let mut estimator = SeriesEstimator::default();
570 let record_batch = new_flat_record_batch(&[1, 2, 3, 2, 4, 5]);
572 estimator.update_flat(&record_batch);
573 assert_eq!(2, estimator.finish());
575 }
576
577 #[test]
578 fn test_series_estimator_flat_multiple_boundaries_within_batch() {
579 let mut estimator = SeriesEstimator::default();
580 let record_batch = new_flat_record_batch(&[1, 2, 5, 4, 6, 3, 7]);
582 estimator.update_flat(&record_batch);
583 assert_eq!(3, estimator.finish());
584 }
585
586 #[test]
587 fn test_series_estimator_flat_equal_timestamps() {
588 let mut estimator = SeriesEstimator::default();
589 let record_batch = new_flat_record_batch(&[1, 2, 2, 3, 3, 3, 4]);
591 estimator.update_flat(&record_batch);
592 assert_eq!(4, estimator.finish());
594 }
595
596 #[test]
597 fn test_series_estimator_flat_multiple_batches_continuation() {
598 let mut estimator = SeriesEstimator::default();
599
600 let batch1 = new_flat_record_batch(&[1, 2, 3]);
602 estimator.update_flat(&batch1);
603
604 let batch2 = new_flat_record_batch(&[4, 5, 6]);
606 estimator.update_flat(&batch2);
607
608 assert_eq!(1, estimator.finish());
609 }
610
611 #[test]
612 fn test_series_estimator_flat_multiple_batches_new_series() {
613 let mut estimator = SeriesEstimator::default();
614
615 let batch1 = new_flat_record_batch(&[1, 2, 3]);
617 estimator.update_flat(&batch1);
618
619 let batch2 = new_flat_record_batch(&[2, 3, 4]);
621 estimator.update_flat(&batch2);
622
623 assert_eq!(2, estimator.finish());
624 }
625
626 #[test]
627 fn test_series_estimator_flat_boundary_at_batch_edge_equal() {
628 let mut estimator = SeriesEstimator::default();
629
630 let batch1 = new_flat_record_batch(&[1, 2, 5]);
632 estimator.update_flat(&batch1);
633
634 let batch2 = new_flat_record_batch(&[5, 6, 7]);
636 estimator.update_flat(&batch2);
637
638 assert_eq!(2, estimator.finish());
639 }
640
641 #[test]
642 fn test_series_estimator_flat_mixed_batches() {
643 let mut estimator = SeriesEstimator::default();
644
645 let batch1 = new_flat_record_batch(&[10, 20, 30]);
647 estimator.update_flat(&batch1);
648
649 let batch2 = new_flat_record_batch(&[5, 15, 10, 25]);
651 estimator.update_flat(&batch2);
652
653 let batch3 = new_flat_record_batch(&[30, 35]);
655 estimator.update_flat(&batch3);
656
657 assert_eq!(3, estimator.finish());
659 }
660
661 #[test]
662 fn test_series_estimator_flat_descending_timestamps() {
663 let mut estimator = SeriesEstimator::default();
664 let record_batch = new_flat_record_batch(&[10, 9, 8, 7, 6]);
666 estimator.update_flat(&record_batch);
667 assert_eq!(5, estimator.finish());
669 }
670
671 #[test]
672 fn test_series_estimator_flat_finish_resets_state() {
673 let mut estimator = SeriesEstimator::default();
674
675 let batch1 = new_flat_record_batch(&[1, 2, 3]);
676 estimator.update_flat(&batch1);
677
678 assert_eq!(1, estimator.finish());
679
680 let batch2 = new_flat_record_batch(&[4, 5, 6]);
682 estimator.update_flat(&batch2);
683
684 assert_eq!(1, estimator.finish());
685 }
686
687 fn histogram_field(name: &str, column_id: u32) -> Field {
690 use common_query::native_histogram::native_histogram_value_type;
691 use datatypes::data_type::DataType;
692 with_field_id(
693 Field::new(name, native_histogram_value_type().as_arrow_type(), true),
694 column_id,
695 )
696 }
697
698 fn assert_histogram_stamped(field: &Field, column_id: i32) {
702 use arrow_schema::extension::ExtensionType;
703 use common_query::native_histogram::{
704 native_histogram_list_element_id, native_histogram_subfield_id,
705 };
706 use datatypes::extension::histogram::HistogramExtensionType;
707
708 assert_eq!(
709 field
710 .metadata()
711 .get(arrow_schema::extension::EXTENSION_TYPE_NAME_KEY)
712 .map(|s| s.as_str()),
713 Some(HistogramExtensionType::NAME),
714 "histogram field must carry the greptime.histogram extension"
715 );
716 let ArrowDataType::Struct(children) = field.data_type() else {
717 panic!("expected a struct, got {:?}", field.data_type());
718 };
719 for child in children {
720 let expected = native_histogram_subfield_id(column_id, child.name())
721 .unwrap_or_else(|| panic!("no id for sub-field {}", child.name()));
722 let got: i32 = child
723 .metadata()
724 .get(PARQUET_FIELD_ID_KEY)
725 .unwrap_or_else(|| panic!("sub-field {} missing field id", child.name()))
726 .parse()
727 .unwrap();
728 assert_eq!(got, expected, "sub-field {} id", child.name());
729 if let ArrowDataType::List(elem) = child.data_type() {
730 let elem_expected =
731 native_histogram_list_element_id(column_id, child.name()).unwrap();
732 let elem_got: i32 = elem
733 .metadata()
734 .get(PARQUET_FIELD_ID_KEY)
735 .unwrap_or_else(|| panic!("list element of {} missing id", child.name()))
736 .parse()
737 .unwrap();
738 assert_eq!(
739 elem_got,
740 elem_expected,
741 "list element id of {}",
742 child.name()
743 );
744 }
745 }
746 }
747
748 #[test]
749 fn test_maybe_wrap_schema_native_histogram() {
750 let schema = Arc::new(Schema::new(vec![
751 Field::new(
752 "greptime_timestamp",
753 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
754 false,
755 ),
756 histogram_field(greptime_native_histogram(), 1),
757 ]));
758
759 let wrapped = maybe_wrap_schema(&schema).unwrap();
760 let hist = wrapped
761 .field_with_name(greptime_native_histogram())
762 .expect("histogram field present");
763 let ArrowDataType::Struct(children) = hist.data_type() else {
765 unreachable!()
766 };
767 assert_eq!(children.len(), 18);
768 assert_histogram_stamped(hist, 1);
769 }
770
771 #[test]
772 fn test_maybe_wrap_schema_multiple_histograms_disjoint_ids() {
773 use common_query::native_histogram::native_histogram_subfield_id;
777
778 let schema = Arc::new(Schema::new(vec![
779 histogram_field(greptime_native_histogram(), 1),
780 histogram_field(greptime_native_histogram(), 7),
781 ]));
782 let wrapped = maybe_wrap_schema(&schema).unwrap();
783 let h1 = &wrapped.fields()[0];
784 let h2 = &wrapped.fields()[1];
785 assert_histogram_stamped(h1, 1);
786 assert_histogram_stamped(h2, 7);
787 assert_ne!(
789 native_histogram_subfield_id(1, "sum"),
790 native_histogram_subfield_id(7, "sum")
791 );
792 }
793
794 #[test]
795 fn test_maybe_wrap_schema_recognizes_histogram_by_type() {
796 let schema = Arc::new(Schema::new(vec![histogram_field("custom_histogram", 5)]));
797
798 let wrapped = maybe_wrap_schema(&schema).unwrap();
799 let hist = wrapped.field_with_name("custom_histogram").unwrap();
800 assert_histogram_stamped(hist, 5);
801 }
802
803 #[test]
804 fn test_maybe_wrap_schema_plain_struct_not_stamped() {
805 use arrow_schema::extension::EXTENSION_TYPE_NAME_KEY;
806
807 let plain = ArrowDataType::Struct(
808 vec![
809 Arc::new(Field::new("a", ArrowDataType::Int32, true)),
810 Arc::new(Field::new("b", ArrowDataType::Utf8, true)),
811 ]
812 .into(),
813 );
814 let schema = Arc::new(Schema::new(vec![
815 Field::new(
816 "ts",
817 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
818 false,
819 ),
820 Field::new("data", plain, true),
821 ]));
822
823 let wrapped = maybe_wrap_schema(&schema).unwrap();
824 let data = wrapped.field_with_name("data").unwrap();
825 assert!(
826 data.metadata().get(EXTENSION_TYPE_NAME_KEY).is_none(),
827 "non-histogram struct must not get the extension"
828 );
829 if let ArrowDataType::Struct(children) = data.data_type() {
830 for child in children {
831 assert!(
832 child.metadata().get(PARQUET_FIELD_ID_KEY).is_none(),
833 "non-histogram sub-field {} must not get a field id",
834 child.name()
835 );
836 }
837 }
838 }
839
840 #[test]
841 fn test_maybe_wrap_schema_no_struct_unchanged() {
842 let schema: Arc<Schema> = Arc::new(Schema::new(vec![
843 Field::new(
844 "ts",
845 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
846 false,
847 ),
848 Field::new("v", ArrowDataType::Float64, true),
849 ]));
850 let wrapped = maybe_wrap_schema(&schema).unwrap();
851 assert!(
852 Arc::ptr_eq(&wrapped, &schema),
853 "a schema without any struct column must be returned unchanged"
854 );
855 }
856
857 fn parquet_footer_arrow_schema(schema: &SchemaRef) -> SchemaRef {
868 use ::parquet::arrow::ArrowWriter;
869 use ::parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
870 use ::parquet::file::properties::WriterProperties;
871 use bytes::Bytes;
872
873 let wrapped = maybe_wrap_schema(schema).unwrap();
874 let mut bytes = Vec::new();
875 let props = WriterProperties::builder().build();
876 let mut writer = ArrowWriter::try_new(&mut bytes, wrapped.clone(), Some(props)).unwrap();
877 writer
878 .write(&RecordBatch::new_empty(wrapped.clone()))
879 .unwrap();
880 writer.close().unwrap();
881
882 ParquetRecordBatchReaderBuilder::try_new(Bytes::from(bytes))
883 .unwrap()
884 .schema()
885 .clone()
886 }
887
888 #[test]
889 fn test_maybe_wrap_schema_survives_parquet_roundtrip() {
890 let schema = Arc::new(Schema::new(vec![
894 Field::new(
895 "greptime_timestamp",
896 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
897 false,
898 ),
899 histogram_field(greptime_native_histogram(), 3),
900 ]));
901
902 let on_disk = parquet_footer_arrow_schema(&schema);
903 let hist = on_disk
904 .field_with_name(greptime_native_histogram())
905 .expect("histogram field present");
906 assert_histogram_stamped(hist, 3);
907 }
908
909 #[test]
910 fn test_parquet_roundtrip_recognizes_histogram_by_type() {
911 let schema = Arc::new(Schema::new(vec![
914 Field::new(
915 "ts",
916 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
917 false,
918 ),
919 histogram_field("custom_histogram", 9),
920 ]));
921
922 let on_disk = parquet_footer_arrow_schema(&schema);
923 let hist = on_disk.field_with_name("custom_histogram").unwrap();
924 assert_histogram_stamped(hist, 9);
925 }
926
927 #[test]
928 fn test_maybe_wrap_schema_overflows_return_error() {
929 let schema = Arc::new(Schema::new(vec![
934 Field::new(
935 "greptime_timestamp",
936 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
937 false,
938 ),
939 histogram_field(greptime_native_histogram(), 12_582_912),
940 ]));
941 let err = maybe_wrap_schema(&schema).unwrap_err();
942 assert!(
943 matches!(
944 err,
945 crate::error::Error::InvalidNativeHistogramSubfield { .. }
946 ),
947 "expected InvalidNativeHistogramSubfield, got {:?}",
948 err
949 );
950 }
951
952 #[test]
953 fn test_maybe_wrap_schema_missing_field_id_returns_error() {
954 use common_query::native_histogram::native_histogram_value_type;
960 use datatypes::data_type::DataType;
961
962 let field = Field::new(
963 greptime_native_histogram(),
964 native_histogram_value_type().as_arrow_type(),
965 true,
966 );
967 assert!(
968 field.metadata().get(PARQUET_FIELD_ID_KEY).is_none(),
969 "fixture must not carry a field id"
970 );
971 let schema = Arc::new(Schema::new(vec![field]));
972 let err = maybe_wrap_schema(&schema).unwrap_err();
973 assert!(
974 matches!(
975 err,
976 crate::error::Error::InvalidNativeHistogramFieldId { .. }
977 ),
978 "expected InvalidNativeHistogramFieldId, got {:?}",
979 err
980 );
981 }
982
983 #[test]
984 fn test_maybe_wrap_schema_field_id_above_i32_max_returns_error() {
985 let schema = Arc::new(Schema::new(vec![
990 Field::new(
991 "greptime_timestamp",
992 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
993 false,
994 ),
995 histogram_field(greptime_native_histogram(), u32::MAX),
996 ]));
997 let err = maybe_wrap_schema(&schema).unwrap_err();
998 assert!(
999 matches!(
1000 err,
1001 crate::error::Error::InvalidNativeHistogramFieldId { .. }
1002 ),
1003 "expected InvalidNativeHistogramFieldId, got {:?}",
1004 err
1005 );
1006 }
1007}