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 mod range_index;
51pub(crate) mod version;
52
53pub const DEFAULT_WRITE_BUFFER_SIZE: ReadableSize = ReadableSize::mb(8);
55
56pub const DEFAULT_WRITE_CONCURRENCY: usize = 8;
58
59#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::EnumString)]
61#[serde(rename_all = "snake_case")]
62#[strum(serialize_all = "snake_case")]
63pub enum FormatType {
64 #[default]
66 PrimaryKey,
67 Flat,
69}
70
71pub const PARQUET_FIELD_ID_KEY: &str = "PARQUET:field_id";
73
74pub fn with_field_id(mut field: Field, column_id: u32) -> Field {
79 field
80 .metadata_mut()
81 .insert(PARQUET_FIELD_ID_KEY.to_string(), column_id.to_string());
82 field
83}
84
85fn stamp_native_histogram_subfield_ids(field: &mut Field) -> crate::error::Result<()> {
101 if !is_native_histogram_value_type(&ConcreteDataType::from_arrow_type(field.data_type())) {
102 return Ok(());
103 }
104 let column_id = field
109 .metadata()
110 .get(PARQUET_FIELD_ID_KEY)
111 .and_then(|s| s.parse::<i32>().ok())
112 .ok_or_else(|| {
113 InvalidNativeHistogramFieldIdSnafu {
114 field_name: field.name().clone(),
115 }
116 .build()
117 })?;
118 field.metadata_mut().insert(
120 EXTENSION_TYPE_NAME_KEY.to_string(),
121 HistogramExtensionType::NAME.to_string(),
122 );
123 let ArrowDataType::Struct(children) = field.data_type() else {
124 return Ok(());
125 };
126 let new_children: crate::error::Result<Fields> = children
127 .iter()
128 .map(|child| {
129 let mut c = (**child).clone();
130 let id = native_histogram_subfield_id(column_id, c.name()).ok_or_else(|| {
135 InvalidNativeHistogramSubfieldSnafu {
136 column_id,
137 field_name: c.name().clone(),
138 }
139 .build()
140 })?;
141 c.metadata_mut()
143 .insert(PARQUET_FIELD_ID_KEY.to_string(), id.to_string());
144 if let ArrowDataType::List(elem) = c.data_type() {
146 let elem_id =
147 native_histogram_list_element_id(column_id, c.name()).ok_or_else(|| {
148 InvalidNativeHistogramSubfieldSnafu {
149 column_id,
150 field_name: c.name().clone(),
151 }
152 .build()
153 })?;
154 let mut new_elem = (**elem).clone();
155 new_elem
156 .metadata_mut()
157 .insert(PARQUET_FIELD_ID_KEY.to_string(), elem_id.to_string());
158 c.set_data_type(ArrowDataType::List(Arc::new(new_elem)));
159 }
160 Ok(Arc::new(c))
161 })
162 .collect();
163 field.set_data_type(ArrowDataType::Struct(new_children?));
164 Ok(())
165}
166
167pub fn maybe_wrap_schema(schema: &SchemaRef) -> crate::error::Result<SchemaRef> {
177 if !schema
180 .fields()
181 .iter()
182 .any(|f| matches!(f.data_type(), ArrowDataType::Struct(_)))
183 {
184 return Ok(schema.clone());
185 }
186 let new_fields: crate::error::Result<Vec<FieldRef>> = schema
187 .fields()
188 .iter()
189 .map(|f| {
190 let mut field = (**f).clone();
191 stamp_native_histogram_subfield_ids(&mut field)?;
192 Ok(Arc::new(field))
193 })
194 .collect();
195 Ok(Arc::new(Schema::new_with_metadata(
196 Fields::from(new_fields?),
197 schema.metadata().clone(),
198 )))
199}
200
201pub(crate) const INTERNAL_PARQUET_FIELD_ID_BASE: u32 = 1 << 30;
204
205pub(crate) const PRIMARY_KEY_PARQUET_FIELD_ID: u32 = INTERNAL_PARQUET_FIELD_ID_BASE;
207pub(crate) const SEQUENCE_PARQUET_FIELD_ID: u32 = INTERNAL_PARQUET_FIELD_ID_BASE + 1;
209pub(crate) const OP_TYPE_PARQUET_FIELD_ID: u32 = INTERNAL_PARQUET_FIELD_ID_BASE + 2;
211
212pub fn to_sst_arrow_schema(metadata: &RegionMetadata) -> SchemaRef {
214 let fields = Fields::from_iter(
215 metadata
216 .schema
217 .arrow_schema()
218 .fields()
219 .iter()
220 .zip(&metadata.column_metadatas)
221 .filter_map(|(field, column_meta)| {
222 if column_meta.semantic_type == SemanticType::Field {
223 Some(Arc::new(with_field_id(
224 (**field).clone(),
225 column_meta.column_id,
226 )))
227 } else {
228 None
230 }
231 })
232 .chain([Arc::new(with_field_id(
233 (*metadata.time_index_field()).clone(),
234 metadata.time_index_column().column_id,
235 ))])
236 .chain(internal_fields()),
237 );
238
239 Arc::new(Schema::new(fields))
240}
241
242pub struct FlatSchemaOptions {
244 pub raw_pk_columns: bool,
246 pub string_pk_use_dict: bool,
250 pub concretized_json_types: HashMap<String, DataType>,
253}
254
255impl Default for FlatSchemaOptions {
256 fn default() -> Self {
257 Self {
258 raw_pk_columns: true,
259 string_pk_use_dict: true,
260 concretized_json_types: HashMap::new(),
261 }
262 }
263}
264
265impl FlatSchemaOptions {
266 pub fn from_encoding(encoding: PrimaryKeyEncoding) -> Self {
268 if encoding == PrimaryKeyEncoding::Dense {
269 Self::default()
270 } else {
271 Self {
272 raw_pk_columns: false,
273 string_pk_use_dict: false,
274 concretized_json_types: HashMap::new(),
275 }
276 }
277 }
278}
279
280pub fn to_flat_sst_arrow_schema(
290 metadata: &RegionMetadata,
291 options: &FlatSchemaOptions,
292) -> SchemaRef {
293 let num_fields = flat_sst_arrow_schema_column_num(metadata, options);
294 let mut fields = Vec::with_capacity(num_fields);
295 let schema = metadata.schema.arrow_schema();
296 if options.raw_pk_columns {
297 for pk_id in &metadata.primary_key {
298 let pk_index = metadata.column_index_by_id(*pk_id).unwrap();
299 let column_id = metadata.column_metadatas[pk_index].column_id;
300 if options.string_pk_use_dict {
301 let old_field = &schema.fields[pk_index];
302 let new_field = tag_maybe_to_dictionary_field(
303 &metadata.column_metadatas[pk_index].column_schema.data_type,
304 old_field,
305 );
306 let new_field = concretize_json_type(new_field, options);
307 fields.push(Arc::new(with_field_id((*new_field).clone(), column_id)));
308 }
309 }
310 }
311 let remaining_fields = schema
312 .fields()
313 .iter()
314 .zip(&metadata.column_metadatas)
315 .filter_map(|(field, column_meta)| {
316 if column_meta.semantic_type == SemanticType::Field {
317 let field = concretize_json_type(field.clone(), options);
318 Some(Arc::new(with_field_id(
319 Arc::unwrap_or_clone(field),
320 column_meta.column_id,
321 )))
322 } else {
323 None
324 }
325 })
326 .chain([Arc::new(with_field_id(
327 (*metadata.time_index_field()).clone(),
328 metadata.time_index_column().column_id,
329 ))])
330 .chain(internal_fields());
331 for field in remaining_fields {
332 fields.push(field);
333 }
334
335 Arc::new(Schema::new(fields))
336}
337
338fn concretize_json_type(field: Arc<Field>, options: &FlatSchemaOptions) -> Arc<Field> {
339 if let Some(data_type) = options.concretized_json_types.get(field.name()) {
340 let mut field = Arc::unwrap_or_clone(field);
341 field.set_data_type(data_type.clone());
342 Arc::new(field)
343 } else {
344 field
345 }
346}
347
348pub fn flat_sst_arrow_schema_column_num(
350 metadata: &RegionMetadata,
351 options: &FlatSchemaOptions,
352) -> usize {
353 if options.raw_pk_columns {
354 metadata.column_metadatas.len() + 3
355 } else {
356 metadata.column_metadatas.len() + 3 - metadata.primary_key.len()
357 }
358}
359
360fn to_dictionary_field(field: &Field) -> Field {
362 let mut new_field = Field::new_dictionary(
363 field.name(),
364 datatypes::arrow::datatypes::DataType::UInt32,
365 field.data_type().clone(),
366 field.is_nullable(),
367 );
368
369 if let Some(field_id) = field.metadata().get(PARQUET_FIELD_ID_KEY) {
371 new_field
372 .metadata_mut()
373 .insert(PARQUET_FIELD_ID_KEY.to_string(), field_id.clone());
374 }
375
376 new_field
377}
378
379pub(crate) fn tag_maybe_to_dictionary_field(
381 data_type: &ConcreteDataType,
382 field: &Arc<Field>,
383) -> Arc<Field> {
384 if data_type.is_string() {
385 Arc::new(to_dictionary_field(field))
386 } else {
387 field.clone()
388 }
389}
390
391pub(crate) fn internal_fields() -> [FieldRef; 3] {
393 [
395 Arc::new(with_field_id(
396 Field::new_dictionary(
397 PRIMARY_KEY_COLUMN_NAME,
398 ArrowDataType::UInt32,
399 ArrowDataType::Binary,
400 false,
401 ),
402 PRIMARY_KEY_PARQUET_FIELD_ID,
403 )),
404 Arc::new(with_field_id(
405 Field::new(SEQUENCE_COLUMN_NAME, ArrowDataType::UInt64, false),
406 SEQUENCE_PARQUET_FIELD_ID,
407 )),
408 Arc::new(with_field_id(
409 Field::new(OP_TYPE_COLUMN_NAME, ArrowDataType::UInt8, false),
410 OP_TYPE_PARQUET_FIELD_ID,
411 )),
412 ]
413}
414
415pub(crate) fn override_pk_field_to_binary(schema: &SchemaRef) -> SchemaRef {
417 let new_fields = schema
418 .fields()
419 .iter()
420 .map(|field| {
421 if field.name() == PRIMARY_KEY_COLUMN_NAME {
422 let mut new_field = Field::new(
423 PRIMARY_KEY_COLUMN_NAME,
424 ArrowDataType::Binary,
425 field.is_nullable(),
426 );
427 if let Some(field_id) = field.metadata().get(PARQUET_FIELD_ID_KEY) {
430 new_field
431 .metadata_mut()
432 .insert(PARQUET_FIELD_ID_KEY.to_string(), field_id.clone());
433 }
434 Arc::new(new_field)
435 } else {
436 field.clone()
437 }
438 })
439 .collect::<Vec<_>>();
440 Arc::new(Schema::new(new_fields))
441}
442
443#[derive(Default)]
448pub(crate) struct SeriesEstimator {
449 last_timestamp: Option<i64>,
451 series_count: u64,
453}
454
455impl SeriesEstimator {
456 pub(crate) fn update_flat(&mut self, record_batch: &RecordBatch) {
460 let batch_rows = record_batch.num_rows();
461 if batch_rows == 0 {
462 return;
463 }
464
465 let time_index_pos = time_index_column_index(record_batch.num_columns());
466 let timestamps = record_batch.column(time_index_pos);
467 let Some((ts_values, _unit)) = timestamp_array_to_primitive(timestamps) else {
468 return;
469 };
470 let values = ts_values.values();
471
472 if let Some(last_ts) = self.last_timestamp {
474 if values[0] <= last_ts {
475 self.series_count += 1;
476 }
477 } else {
478 self.series_count = 1;
480 }
481
482 for i in 0..batch_rows - 1 {
484 if values[i] >= values[i + 1] {
487 self.series_count += 1;
488 }
489 }
490
491 self.last_timestamp = Some(values[batch_rows - 1]);
493 }
494
495 pub(crate) fn finish(&mut self) -> u64 {
497 self.last_timestamp = None;
498 let count = self.series_count;
499 self.series_count = 0;
500
501 count
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use std::sync::Arc;
508
509 use ::parquet::arrow::AsyncArrowWriter;
510 use ::parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
511 use ::parquet::basic::LogicalType;
512 use ::parquet::variant::{VariantArray, VariantType, json_to_variant};
513 use common_query::prelude::greptime_native_histogram;
514 use datatypes::arrow::array::{
515 ArrayRef, BinaryArray, DictionaryArray, Int64Array, StringArray, StructArray,
516 TimestampMillisecondArray, UInt8Array, UInt32Array, UInt64Array,
517 };
518 use datatypes::arrow::datatypes::{DataType as ArrowDataType, Field, Schema, TimeUnit};
519 use datatypes::arrow::record_batch::RecordBatch;
520 use datatypes::extension::json::{Json2ExtensionType, Json2PhysicalLayout};
521 use datatypes::vectors::json::array::JsonArray;
522 use serde_json::json;
523
524 use super::*;
525
526 fn new_flat_record_batch(timestamps: &[i64]) -> RecordBatch {
527 let num_cols = 4; let time_index_pos = time_index_column_index(num_cols);
530 assert_eq!(time_index_pos, 0); let time_array = Arc::new(TimestampMillisecondArray::from(timestamps.to_vec()));
533 let pk_array = Arc::new(DictionaryArray::new(
534 UInt32Array::from(vec![0; timestamps.len()]),
535 Arc::new(BinaryArray::from(vec![b"test".as_slice()])),
536 ));
537 let seq_array = Arc::new(UInt64Array::from(vec![1; timestamps.len()]));
538 let op_array = Arc::new(UInt8Array::from(vec![1; timestamps.len()]));
539
540 let schema = Arc::new(Schema::new(vec![
541 Field::new(
542 "time",
543 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
544 false,
545 ),
546 Field::new_dictionary(
547 "__primary_key",
548 ArrowDataType::UInt32,
549 ArrowDataType::Binary,
550 false,
551 ),
552 Field::new("__sequence", ArrowDataType::UInt64, false),
553 Field::new("__op_type", ArrowDataType::UInt8, false),
554 ]));
555
556 RecordBatch::try_new(schema, vec![time_array, pk_array, seq_array, op_array]).unwrap()
557 }
558
559 #[test]
560 fn test_series_estimator_flat_empty_batch() {
561 let mut estimator = SeriesEstimator::default();
562 let record_batch = new_flat_record_batch(&[]);
563 estimator.update_flat(&record_batch);
564 assert_eq!(0, estimator.finish());
565 }
566
567 #[test]
568 fn test_series_estimator_flat_single_batch() {
569 let mut estimator = SeriesEstimator::default();
570 let record_batch = new_flat_record_batch(&[1, 2, 3]);
571 estimator.update_flat(&record_batch);
572 assert_eq!(1, estimator.finish());
573 }
574
575 #[test]
576 fn test_series_estimator_flat_series_boundary_within_batch() {
577 let mut estimator = SeriesEstimator::default();
578 let record_batch = new_flat_record_batch(&[1, 2, 3, 2, 4, 5]);
580 estimator.update_flat(&record_batch);
581 assert_eq!(2, estimator.finish());
583 }
584
585 #[test]
586 fn test_series_estimator_flat_multiple_boundaries_within_batch() {
587 let mut estimator = SeriesEstimator::default();
588 let record_batch = new_flat_record_batch(&[1, 2, 5, 4, 6, 3, 7]);
590 estimator.update_flat(&record_batch);
591 assert_eq!(3, estimator.finish());
592 }
593
594 #[test]
595 fn test_series_estimator_flat_equal_timestamps() {
596 let mut estimator = SeriesEstimator::default();
597 let record_batch = new_flat_record_batch(&[1, 2, 2, 3, 3, 3, 4]);
599 estimator.update_flat(&record_batch);
600 assert_eq!(4, estimator.finish());
602 }
603
604 #[test]
605 fn test_series_estimator_flat_multiple_batches_continuation() {
606 let mut estimator = SeriesEstimator::default();
607
608 let batch1 = new_flat_record_batch(&[1, 2, 3]);
610 estimator.update_flat(&batch1);
611
612 let batch2 = new_flat_record_batch(&[4, 5, 6]);
614 estimator.update_flat(&batch2);
615
616 assert_eq!(1, estimator.finish());
617 }
618
619 #[test]
620 fn test_series_estimator_flat_multiple_batches_new_series() {
621 let mut estimator = SeriesEstimator::default();
622
623 let batch1 = new_flat_record_batch(&[1, 2, 3]);
625 estimator.update_flat(&batch1);
626
627 let batch2 = new_flat_record_batch(&[2, 3, 4]);
629 estimator.update_flat(&batch2);
630
631 assert_eq!(2, estimator.finish());
632 }
633
634 #[test]
635 fn test_series_estimator_flat_boundary_at_batch_edge_equal() {
636 let mut estimator = SeriesEstimator::default();
637
638 let batch1 = new_flat_record_batch(&[1, 2, 5]);
640 estimator.update_flat(&batch1);
641
642 let batch2 = new_flat_record_batch(&[5, 6, 7]);
644 estimator.update_flat(&batch2);
645
646 assert_eq!(2, estimator.finish());
647 }
648
649 #[test]
650 fn test_series_estimator_flat_mixed_batches() {
651 let mut estimator = SeriesEstimator::default();
652
653 let batch1 = new_flat_record_batch(&[10, 20, 30]);
655 estimator.update_flat(&batch1);
656
657 let batch2 = new_flat_record_batch(&[5, 15, 10, 25]);
659 estimator.update_flat(&batch2);
660
661 let batch3 = new_flat_record_batch(&[30, 35]);
663 estimator.update_flat(&batch3);
664
665 assert_eq!(3, estimator.finish());
667 }
668
669 #[test]
670 fn test_series_estimator_flat_descending_timestamps() {
671 let mut estimator = SeriesEstimator::default();
672 let record_batch = new_flat_record_batch(&[10, 9, 8, 7, 6]);
674 estimator.update_flat(&record_batch);
675 assert_eq!(5, estimator.finish());
677 }
678
679 #[test]
680 fn test_series_estimator_flat_finish_resets_state() {
681 let mut estimator = SeriesEstimator::default();
682
683 let batch1 = new_flat_record_batch(&[1, 2, 3]);
684 estimator.update_flat(&batch1);
685
686 assert_eq!(1, estimator.finish());
687
688 let batch2 = new_flat_record_batch(&[4, 5, 6]);
690 estimator.update_flat(&batch2);
691
692 assert_eq!(1, estimator.finish());
693 }
694
695 fn histogram_field(name: &str, column_id: u32) -> Field {
698 use common_query::native_histogram::native_histogram_value_type;
699 use datatypes::data_type::DataType;
700 with_field_id(
701 Field::new(name, native_histogram_value_type().as_arrow_type(), true),
702 column_id,
703 )
704 }
705
706 fn assert_histogram_stamped(field: &Field, column_id: i32) {
710 use arrow_schema::extension::ExtensionType;
711 use common_query::native_histogram::{
712 native_histogram_list_element_id, native_histogram_subfield_id,
713 };
714 use datatypes::extension::histogram::HistogramExtensionType;
715
716 assert_eq!(
717 field
718 .metadata()
719 .get(arrow_schema::extension::EXTENSION_TYPE_NAME_KEY)
720 .map(|s| s.as_str()),
721 Some(HistogramExtensionType::NAME),
722 "histogram field must carry the greptime.histogram extension"
723 );
724 let ArrowDataType::Struct(children) = field.data_type() else {
725 panic!("expected a struct, got {:?}", field.data_type());
726 };
727 for child in children {
728 let expected = native_histogram_subfield_id(column_id, child.name())
729 .unwrap_or_else(|| panic!("no id for sub-field {}", child.name()));
730 let got: i32 = child
731 .metadata()
732 .get(PARQUET_FIELD_ID_KEY)
733 .unwrap_or_else(|| panic!("sub-field {} missing field id", child.name()))
734 .parse()
735 .unwrap();
736 assert_eq!(got, expected, "sub-field {} id", child.name());
737 if let ArrowDataType::List(elem) = child.data_type() {
738 let elem_expected =
739 native_histogram_list_element_id(column_id, child.name()).unwrap();
740 let elem_got: i32 = elem
741 .metadata()
742 .get(PARQUET_FIELD_ID_KEY)
743 .unwrap_or_else(|| panic!("list element of {} missing id", child.name()))
744 .parse()
745 .unwrap();
746 assert_eq!(
747 elem_got,
748 elem_expected,
749 "list element id of {}",
750 child.name()
751 );
752 }
753 }
754 }
755
756 #[test]
757 fn test_maybe_wrap_schema_native_histogram() {
758 let schema = Arc::new(Schema::new(vec![
759 Field::new(
760 "greptime_timestamp",
761 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
762 false,
763 ),
764 histogram_field(greptime_native_histogram(), 1),
765 ]));
766
767 let wrapped = maybe_wrap_schema(&schema).unwrap();
768 let hist = wrapped
769 .field_with_name(greptime_native_histogram())
770 .expect("histogram field present");
771 let ArrowDataType::Struct(children) = hist.data_type() else {
773 unreachable!()
774 };
775 assert_eq!(children.len(), 18);
776 assert_histogram_stamped(hist, 1);
777 }
778
779 #[test]
780 fn test_maybe_wrap_schema_multiple_histograms_disjoint_ids() {
781 use common_query::native_histogram::native_histogram_subfield_id;
785
786 let schema = Arc::new(Schema::new(vec![
787 histogram_field(greptime_native_histogram(), 1),
788 histogram_field(greptime_native_histogram(), 7),
789 ]));
790 let wrapped = maybe_wrap_schema(&schema).unwrap();
791 let h1 = &wrapped.fields()[0];
792 let h2 = &wrapped.fields()[1];
793 assert_histogram_stamped(h1, 1);
794 assert_histogram_stamped(h2, 7);
795 assert_ne!(
797 native_histogram_subfield_id(1, "sum"),
798 native_histogram_subfield_id(7, "sum")
799 );
800 }
801
802 #[test]
803 fn test_maybe_wrap_schema_recognizes_histogram_by_type() {
804 let schema = Arc::new(Schema::new(vec![histogram_field("custom_histogram", 5)]));
805
806 let wrapped = maybe_wrap_schema(&schema).unwrap();
807 let hist = wrapped.field_with_name("custom_histogram").unwrap();
808 assert_histogram_stamped(hist, 5);
809 }
810
811 #[test]
812 fn test_maybe_wrap_schema_plain_struct_not_stamped() {
813 use arrow_schema::extension::EXTENSION_TYPE_NAME_KEY;
814
815 let plain = ArrowDataType::Struct(
816 vec![
817 Arc::new(Field::new("a", ArrowDataType::Int32, true)),
818 Arc::new(Field::new("b", ArrowDataType::Utf8, true)),
819 ]
820 .into(),
821 );
822 let schema = Arc::new(Schema::new(vec![
823 Field::new(
824 "ts",
825 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
826 false,
827 ),
828 Field::new("data", plain, true),
829 ]));
830
831 let wrapped = maybe_wrap_schema(&schema).unwrap();
832 let data = wrapped.field_with_name("data").unwrap();
833 assert!(
834 data.metadata().get(EXTENSION_TYPE_NAME_KEY).is_none(),
835 "non-histogram struct must not get the extension"
836 );
837 if let ArrowDataType::Struct(children) = data.data_type() {
838 for child in children {
839 assert!(
840 child.metadata().get(PARQUET_FIELD_ID_KEY).is_none(),
841 "non-histogram sub-field {} must not get a field id",
842 child.name()
843 );
844 }
845 }
846 }
847
848 #[test]
849 fn test_maybe_wrap_schema_no_struct_unchanged() {
850 let schema: Arc<Schema> = Arc::new(Schema::new(vec![
851 Field::new(
852 "ts",
853 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
854 false,
855 ),
856 Field::new("v", ArrowDataType::Float64, true),
857 ]));
858 let wrapped = maybe_wrap_schema(&schema).unwrap();
859 assert!(
860 Arc::ptr_eq(&wrapped, &schema),
861 "a schema without any struct column must be returned unchanged"
862 );
863 }
864
865 fn parquet_footer_arrow_schema(schema: &SchemaRef) -> SchemaRef {
876 use ::parquet::arrow::ArrowWriter;
877 use ::parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
878 use ::parquet::file::properties::WriterProperties;
879 use bytes::Bytes;
880
881 let wrapped = maybe_wrap_schema(schema).unwrap();
882 let mut bytes = Vec::new();
883 let props = WriterProperties::builder().build();
884 let mut writer = ArrowWriter::try_new(&mut bytes, wrapped.clone(), Some(props)).unwrap();
885 writer
886 .write(&RecordBatch::new_empty(wrapped.clone()))
887 .unwrap();
888 writer.close().unwrap();
889
890 ParquetRecordBatchReaderBuilder::try_new(Bytes::from(bytes))
891 .unwrap()
892 .schema()
893 .clone()
894 }
895
896 #[test]
897 fn test_maybe_wrap_schema_survives_parquet_roundtrip() {
898 let schema = Arc::new(Schema::new(vec![
902 Field::new(
903 "greptime_timestamp",
904 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
905 false,
906 ),
907 histogram_field(greptime_native_histogram(), 3),
908 ]));
909
910 let on_disk = parquet_footer_arrow_schema(&schema);
911 let hist = on_disk
912 .field_with_name(greptime_native_histogram())
913 .expect("histogram field present");
914 assert_histogram_stamped(hist, 3);
915 }
916
917 #[test]
918 fn test_parquet_roundtrip_recognizes_histogram_by_type() {
919 let schema = Arc::new(Schema::new(vec![
922 Field::new(
923 "ts",
924 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
925 false,
926 ),
927 histogram_field("custom_histogram", 9),
928 ]));
929
930 let on_disk = parquet_footer_arrow_schema(&schema);
931 let hist = on_disk.field_with_name("custom_histogram").unwrap();
932 assert_histogram_stamped(hist, 9);
933 }
934
935 #[test]
936 fn test_maybe_wrap_schema_overflows_return_error() {
937 let schema = Arc::new(Schema::new(vec![
942 Field::new(
943 "greptime_timestamp",
944 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
945 false,
946 ),
947 histogram_field(greptime_native_histogram(), 12_582_912),
948 ]));
949 let err = maybe_wrap_schema(&schema).unwrap_err();
950 assert!(
951 matches!(
952 err,
953 crate::error::Error::InvalidNativeHistogramSubfield { .. }
954 ),
955 "expected InvalidNativeHistogramSubfield, got {:?}",
956 err
957 );
958 }
959
960 #[test]
961 fn test_maybe_wrap_schema_missing_field_id_returns_error() {
962 use common_query::native_histogram::native_histogram_value_type;
968 use datatypes::data_type::DataType;
969
970 let field = Field::new(
971 greptime_native_histogram(),
972 native_histogram_value_type().as_arrow_type(),
973 true,
974 );
975 assert!(
976 field.metadata().get(PARQUET_FIELD_ID_KEY).is_none(),
977 "fixture must not carry a field id"
978 );
979 let schema = Arc::new(Schema::new(vec![field]));
980 let err = maybe_wrap_schema(&schema).unwrap_err();
981 assert!(
982 matches!(
983 err,
984 crate::error::Error::InvalidNativeHistogramFieldId { .. }
985 ),
986 "expected InvalidNativeHistogramFieldId, got {:?}",
987 err
988 );
989 }
990
991 #[test]
992 fn test_maybe_wrap_schema_field_id_above_i32_max_returns_error() {
993 let schema = Arc::new(Schema::new(vec![
998 Field::new(
999 "greptime_timestamp",
1000 ArrowDataType::Timestamp(TimeUnit::Millisecond, None),
1001 false,
1002 ),
1003 histogram_field(greptime_native_histogram(), u32::MAX),
1004 ]));
1005 let err = maybe_wrap_schema(&schema).unwrap_err();
1006 assert!(
1007 matches!(
1008 err,
1009 crate::error::Error::InvalidNativeHistogramFieldId { .. }
1010 ),
1011 "expected InvalidNativeHistogramFieldId, got {:?}",
1012 err
1013 );
1014 }
1015
1016 fn json2_v2_test_type() -> ArrowDataType {
1017 ArrowDataType::Struct(
1018 vec![
1019 Arc::new(Field::new("active", ArrowDataType::Boolean, true)),
1020 Arc::new(Field::new("hot", ArrowDataType::Int64, true)),
1021 Arc::new(Field::new("name", ArrowDataType::Utf8, true)),
1022 ]
1023 .into(),
1024 )
1025 }
1026
1027 #[tokio::test]
1035 async fn test_nested_variant_survives_sst_writer_schema_roundtrip()
1036 -> Result<(), Box<dyn std::error::Error>> {
1037 let json: ArrayRef = Arc::new(StringArray::from(vec![
1038 Some(r#"{}"#),
1039 Some(r#"{"name":"Alice","active":true}"#),
1040 Some(r#"{"nested":{"count":42},"items":[1,"two",null]}"#),
1041 Some(r#"{"\u5b57\u6bb5":"\u503c"}"#),
1042 None,
1043 ]));
1044 let remainder = json_to_variant(&json)?;
1045 let remainder_field = remainder.field("!__remainder__!");
1046 let remainder_array = ArrayRef::from(remainder);
1047 let hot_field = Field::new("hot", ArrowDataType::Int64, true);
1048 let data_array = Arc::new(StructArray::new(
1049 vec![remainder_field.clone(), hot_field.clone()].into(),
1050 vec![
1051 remainder_array,
1052 Arc::new(Int64Array::from(vec![
1053 Some(1),
1054 Some(2),
1055 Some(3),
1056 Some(4),
1057 None,
1058 ])),
1059 ],
1060 None,
1061 ));
1062 let data_field = Field::new(
1063 "data",
1064 ArrowDataType::Struct(vec![remainder_field, hot_field].into()),
1065 true,
1066 )
1067 .with_extension_type(Json2ExtensionType::default());
1068 let schema = Arc::new(Schema::new(vec![data_field]));
1069 let source = RecordBatch::try_new(schema.clone(), vec![data_array])?;
1070
1071 let wrapped = maybe_wrap_schema(&schema)?;
1072 let mut buffer = Vec::new();
1073 let mut writer = AsyncArrowWriter::try_new(&mut buffer, wrapped, None)?;
1074 writer.write(&source).await?;
1075 writer.close().await?;
1076
1077 let builder = ParquetRecordBatchReaderBuilder::try_new(bytes::Bytes::from(buffer))?;
1078 let parquet_remainder =
1079 &builder.parquet_schema().root_schema().get_fields()[0].get_fields()[0];
1080 assert_eq!(
1081 parquet_remainder.get_basic_info().logical_type_ref(),
1082 Some(&LogicalType::Variant {
1083 specification_version: None,
1084 })
1085 );
1086
1087 let ArrowDataType::Struct(children) = builder.schema().field_with_name("data")?.data_type()
1088 else {
1089 unreachable!();
1090 };
1091 assert!(children[0].has_valid_extension_type::<VariantType>());
1092
1093 let mut reader = builder.build()?;
1094 let result = reader.next().unwrap()?;
1095 assert_eq!(source, result);
1096 let result_field = result.schema().field(0).clone();
1097 let result = result
1098 .column(0)
1099 .as_any()
1100 .downcast_ref::<StructArray>()
1101 .unwrap();
1102 VariantArray::try_new(result.column(0))?;
1103 let result: ArrayRef = Arc::new(result.clone());
1104 let result =
1105 JsonArray::from(&result).project_to_v2(&result_field, &json2_v2_test_type())?;
1106 assert_eq!(
1107 json!({"active": true, "hot": 2, "name": "Alice"}),
1108 JsonArray::from(&result).try_get_value(1)?
1109 );
1110 Ok(())
1111 }
1112
1113 #[test]
1115 fn test_read_json2_v2_fixture() -> Result<(), Box<dyn std::error::Error>> {
1116 let bytes = bytes::Bytes::from_static(include_bytes!("../test-data/json2-v2.parquet"));
1117 let builder = ParquetRecordBatchReaderBuilder::try_new(bytes)?;
1118 let field = builder.schema().field(0).clone();
1119 assert!(Json2PhysicalLayout::try_from_root(&field)?.is_version_2());
1120
1121 let batch = builder.build()?.next().unwrap()?;
1122 let data = batch
1123 .column(0)
1124 .as_any()
1125 .downcast_ref::<StructArray>()
1126 .unwrap();
1127 VariantArray::try_new(data.column(0))?;
1128 let data: ArrayRef = Arc::new(data.clone());
1129 let data = JsonArray::from(&data).project_to_v2(&field, &json2_v2_test_type())?;
1130 assert_eq!(
1131 json!({"active": true, "hot": 2, "name": "Alice"}),
1132 JsonArray::from(&data).try_get_value(1)?
1133 );
1134 Ok(())
1135 }
1136}