1use std::collections::{HashMap, HashSet};
18use std::sync::Arc;
19use std::time::{Duration, Instant};
20
21use api::helper::{ColumnDataTypeWrapper, to_grpc_value};
22use api::v1::bulk_wal_entry::Body;
23use api::v1::{ArrowIpc, BulkWalEntry, Mutation, OpType};
24use bytes::Bytes;
25use common_grpc::flight::{FlightDecoder, FlightEncoder, FlightMessage};
26use common_recordbatch::DfRecordBatch as RecordBatch;
27use common_time::Timestamp;
28use datafusion_common::Column;
29use datafusion_common::pruning::PruningStatistics;
30use datafusion_expr::utils::expr_to_columns;
31use datatypes::arrow;
32use datatypes::arrow::array::{
33 Array, ArrayRef, BinaryArray, BooleanArray, StringDictionaryBuilder, UInt8Array, UInt64Array,
34};
35use datatypes::arrow::compute::{SortColumn, SortOptions, concat_batches};
36use datatypes::arrow::datatypes::{
37 DataType as ArrowDataType, Field, Schema, SchemaRef, UInt32Type,
38};
39use datatypes::data_type::DataType;
40use datatypes::extension::json::is_structured_json_field;
41use datatypes::prelude::{MutableVector, Vector};
42use datatypes::value::ValueRef;
43use datatypes::vectors::Helper;
44use mito_codec::key_values::{KeyValue, KeyValues};
45use mito_codec::row_converter::{PrimaryKeyCodec, SortField, build_primary_key_codec_with_fields};
46use parquet::arrow::ArrowWriter;
47use parquet::basic::{Compression, ZstdLevel};
48use parquet::file::metadata::ParquetMetaData;
49use parquet::file::properties::WriterProperties;
50use smallvec::SmallVec;
51use snafu::{OptionExt, ResultExt};
52use store_api::codec::PrimaryKeyEncoding;
53use store_api::metadata::{RegionMetadata, RegionMetadataRef};
54use store_api::storage::consts::PRIMARY_KEY_COLUMN_NAME;
55use store_api::storage::{ColumnId, FileId, SequenceNumber, SequenceRange};
56
57use crate::error::{
58 self, ColumnNotFoundSnafu, ComputeArrowSnafu, CreateDefaultSnafu, DataTypeMismatchSnafu,
59 EncodeMemtableSnafu, EncodeSnafu, InvalidMetadataSnafu, InvalidRequestSnafu,
60 NewRecordBatchSnafu, Result,
61};
62use crate::memtable::bulk::context::{BulkIterContext, BulkIterContextRef};
63use crate::memtable::bulk::json_align::Json2Aligner;
64use crate::memtable::bulk::part_reader::EncodedBulkPartIter;
65use crate::memtable::time_series::{ValueBuilder, Values};
66use crate::memtable::{BoxedRecordBatchIterator, MemScanMetrics, MemtableStats};
67use crate::sst::SeriesEstimator;
68use crate::sst::index::IndexOutput;
69use crate::sst::parquet::flat_format::primary_key_column_index;
70use crate::sst::parquet::format::{PrimaryKeyArray, PrimaryKeyArrayBuilder};
71use crate::sst::parquet::{PARQUET_METADATA_KEY, SstInfo};
72
73const INIT_DICT_VALUE_CAPACITY: usize = 8;
74
75#[derive(Clone)]
77pub struct BulkPart {
78 pub batch: RecordBatch,
79 pub max_timestamp: i64,
80 pub min_timestamp: i64,
81 pub sequence: u64,
82 pub timestamp_index: usize,
83 pub raw_data: Option<ArrowIpc>,
84}
85
86impl TryFrom<BulkWalEntry> for BulkPart {
87 type Error = error::Error;
88
89 fn try_from(value: BulkWalEntry) -> std::result::Result<Self, Self::Error> {
90 match value.body.expect("Entry payload should be present") {
91 Body::ArrowIpc(ipc) => {
92 let mut decoder = FlightDecoder::try_from_schema_bytes(&ipc.schema)
93 .context(error::ConvertBulkWalEntrySnafu)?;
94 let batch = decoder
95 .try_decode_record_batch(&ipc.data_header, &ipc.payload)
96 .context(error::ConvertBulkWalEntrySnafu)?;
97 Ok(Self {
98 batch,
99 max_timestamp: value.max_ts,
100 min_timestamp: value.min_ts,
101 sequence: value.sequence,
102 timestamp_index: value.timestamp_index as usize,
103 raw_data: Some(ipc),
104 })
105 }
106 }
107 }
108}
109
110impl TryFrom<&BulkPart> for BulkWalEntry {
111 type Error = error::Error;
112
113 fn try_from(value: &BulkPart) -> Result<Self> {
114 if let Some(ipc) = &value.raw_data {
115 Ok(BulkWalEntry {
116 sequence: value.sequence,
117 max_ts: value.max_timestamp,
118 min_ts: value.min_timestamp,
119 timestamp_index: value.timestamp_index as u32,
120 body: Some(Body::ArrowIpc(ipc.clone())),
121 })
122 } else {
123 let mut encoder = FlightEncoder::default();
124 let schema_bytes = encoder
125 .encode_schema(value.batch.schema().as_ref())
126 .data_header;
127 let [rb_data] = encoder
128 .encode(FlightMessage::RecordBatch(value.batch.clone()))
129 .try_into()
130 .map_err(|_| {
131 error::UnsupportedOperationSnafu {
132 err_msg: "create BulkWalEntry from RecordBatch with dictionary arrays",
133 }
134 .build()
135 })?;
136 Ok(BulkWalEntry {
137 sequence: value.sequence,
138 max_ts: value.max_timestamp,
139 min_ts: value.min_timestamp,
140 timestamp_index: value.timestamp_index as u32,
141 body: Some(Body::ArrowIpc(ArrowIpc {
142 schema: schema_bytes,
143 data_header: rb_data.data_header,
144 payload: rb_data.data_body,
145 })),
146 })
147 }
148 }
149}
150
151impl BulkPart {
152 pub(crate) fn schema(&self) -> SchemaRef {
153 self.batch.schema()
154 }
155
156 pub(crate) fn estimated_size(&self) -> usize {
157 record_batch_estimated_size(&self.batch)
158 }
159
160 pub fn estimated_series_count(&self) -> usize {
163 let pk_column_idx = primary_key_column_index(self.batch.num_columns());
164 let pk_column = self.batch.column(pk_column_idx);
165 if let Some(dict_array) = pk_column.as_any().downcast_ref::<PrimaryKeyArray>() {
166 dict_array.values().len()
167 } else {
168 0
169 }
170 }
171
172 pub fn to_memtable_stats(&self, region_metadata: &RegionMetadataRef) -> MemtableStats {
174 let ts_type = region_metadata.time_index_type();
175 let min_ts = ts_type.create_timestamp(self.min_timestamp);
176 let max_ts = ts_type.create_timestamp(self.max_timestamp);
177
178 MemtableStats {
179 estimated_bytes: self.estimated_size(),
180 time_range: Some((min_ts, max_ts)),
181 num_rows: self.num_rows(),
182 num_ranges: 1,
183 max_sequence: self.sequence,
184 series_count: self.estimated_series_count(),
185 }
186 }
187
188 pub fn fill_missing_columns(&mut self, region_metadata: &RegionMetadata) -> Result<()> {
198 let batch_schema = self.batch.schema();
200 let batch_columns: HashSet<_> = batch_schema
201 .fields()
202 .iter()
203 .map(|f| f.name().as_str())
204 .collect();
205
206 let mut columns_to_fill = Vec::new();
208 for column_meta in ®ion_metadata.column_metadatas {
209 if !batch_columns.contains(column_meta.column_schema.name.as_str()) {
212 columns_to_fill.push(column_meta);
213 }
214 }
215
216 if columns_to_fill.is_empty() {
217 return Ok(());
218 }
219
220 let num_rows = self.batch.num_rows();
221
222 let mut new_columns = Vec::new();
223 let mut new_fields = Vec::new();
224
225 new_fields.extend(batch_schema.fields().iter().cloned());
227 new_columns.extend_from_slice(self.batch.columns());
228
229 let region_id = region_metadata.region_id;
230 for column_meta in columns_to_fill {
232 let default_vector = column_meta
233 .column_schema
234 .create_default_vector(num_rows)
235 .context(CreateDefaultSnafu {
236 region_id,
237 column: &column_meta.column_schema.name,
238 })?
239 .with_context(|| InvalidRequestSnafu {
240 region_id,
241 reason: format!(
242 "column {} does not have default value",
243 column_meta.column_schema.name
244 ),
245 })?;
246 let arrow_array = default_vector.to_arrow_array();
247 column_meta.column_schema.data_type.as_arrow_type();
248
249 new_fields.push(Arc::new(Field::new(
250 column_meta.column_schema.name.clone(),
251 column_meta.column_schema.data_type.as_arrow_type(),
252 column_meta.column_schema.is_nullable(),
253 )));
254 new_columns.push(arrow_array);
255 }
256
257 let new_schema = Arc::new(Schema::new(new_fields));
259 let new_batch =
260 RecordBatch::try_new(new_schema, new_columns).context(NewRecordBatchSnafu)?;
261
262 self.batch = new_batch;
264
265 Ok(())
266 }
267
268 pub(crate) fn to_mutation(&self, region_metadata: &RegionMetadataRef) -> Result<Mutation> {
270 let vectors = region_metadata
271 .schema
272 .column_schemas()
273 .iter()
274 .map(|col| match self.batch.column_by_name(&col.name) {
275 None => Ok(None),
276 Some(col) => Helper::try_into_vector(col).map(Some),
277 })
278 .collect::<datatypes::error::Result<Vec<_>>>()
279 .context(error::ComputeVectorSnafu)?;
280
281 let rows = (0..self.num_rows())
282 .map(|row_idx| {
283 let values = (0..self.batch.num_columns())
284 .map(|col_idx| {
285 if let Some(v) = &vectors[col_idx] {
286 to_grpc_value(v.get(row_idx))
287 } else {
288 api::v1::Value { value_data: None }
289 }
290 })
291 .collect::<Vec<_>>();
292 api::v1::Row { values }
293 })
294 .collect::<Vec<_>>();
295
296 let schema = region_metadata
297 .column_metadatas
298 .iter()
299 .map(|c| {
300 let data_type_wrapper =
301 ColumnDataTypeWrapper::try_from(c.column_schema.data_type.clone())?;
302 Ok(api::v1::ColumnSchema {
303 column_name: c.column_schema.name.clone(),
304 datatype: data_type_wrapper.datatype() as i32,
305 semantic_type: c.semantic_type as i32,
306 ..Default::default()
307 })
308 })
309 .collect::<api::error::Result<Vec<_>>>()
310 .context(error::ConvertColumnDataTypeSnafu {
311 reason: "failed to convert region metadata to column schema",
312 })?;
313
314 let rows = api::v1::Rows { schema, rows };
315
316 Ok(Mutation {
317 op_type: OpType::Put as i32,
318 sequence: self.sequence,
319 rows: Some(rows),
320 write_hint: None,
321 })
322 }
323
324 pub fn timestamps(&self) -> &ArrayRef {
325 self.batch.column(self.timestamp_index)
326 }
327
328 pub fn num_rows(&self) -> usize {
329 self.batch.num_rows()
330 }
331}
332
333pub struct UnorderedPart {
336 parts: Vec<BulkPart>,
338 total_rows: usize,
340 total_bytes: usize,
342 min_timestamp: i64,
344 max_timestamp: i64,
346 max_sequence: u64,
348 threshold: usize,
350 compact_threshold: usize,
352}
353
354impl Default for UnorderedPart {
355 fn default() -> Self {
356 Self::new()
357 }
358}
359
360impl UnorderedPart {
361 pub fn new() -> Self {
363 Self {
364 parts: Vec::new(),
365 total_rows: 0,
366 total_bytes: 0,
367 min_timestamp: i64::MAX,
368 max_timestamp: i64::MIN,
369 max_sequence: 0,
370 threshold: 1024,
371 compact_threshold: 4096,
372 }
373 }
374
375 pub fn set_threshold(&mut self, threshold: usize) {
377 self.threshold = threshold;
378 }
379
380 pub fn set_compact_threshold(&mut self, compact_threshold: usize) {
382 self.compact_threshold = compact_threshold;
383 }
384
385 pub fn threshold(&self) -> usize {
387 self.threshold
388 }
389
390 pub fn compact_threshold(&self) -> usize {
392 self.compact_threshold
393 }
394
395 pub fn should_accept(&self, num_rows: usize) -> bool {
397 num_rows < self.threshold
398 }
399
400 pub fn should_compact(&self) -> bool {
402 self.total_rows >= self.compact_threshold
403 }
404
405 pub(super) fn estimated_bytes(&self) -> usize {
407 self.total_bytes
408 }
409
410 pub fn push(&mut self, part: BulkPart) {
412 self.total_rows += part.num_rows();
413 self.total_bytes = self.total_bytes.saturating_add(part.estimated_size());
414 self.min_timestamp = self.min_timestamp.min(part.min_timestamp);
415 self.max_timestamp = self.max_timestamp.max(part.max_timestamp);
416 self.max_sequence = self.max_sequence.max(part.sequence);
417 self.parts.push(part);
418 }
419
420 pub fn num_rows(&self) -> usize {
422 self.total_rows
423 }
424
425 pub fn is_empty(&self) -> bool {
427 self.parts.is_empty()
428 }
429
430 pub fn num_parts(&self) -> usize {
432 self.parts.len()
433 }
434
435 pub fn concat_and_sort(&self) -> Result<Option<RecordBatch>> {
438 if self.parts.is_empty() {
439 return Ok(None);
440 }
441
442 if self.parts.len() == 1 {
443 return Ok(Some(self.parts[0].batch.clone()));
445 }
446
447 let schema = self.parts[0].batch.schema();
449 let concatenated = if schema.fields().iter().any(is_structured_json_field) {
450 let aligner = Json2Aligner::try_new(self.parts.iter().map(|part| part.batch.schema()))?;
451 let aligned_batches =
452 aligner.align_batches(self.parts.iter().map(|part| part.batch.clone()))?;
453 concat_batches(aligner.schema(), &aligned_batches).context(ComputeArrowSnafu)?
454 } else {
455 concat_batches(&schema, self.parts.iter().map(|x| &x.batch))
456 .context(ComputeArrowSnafu)?
457 };
458
459 let sorted_batch = sort_primary_key_record_batch(&concatenated)?;
461
462 Ok(Some(sorted_batch))
463 }
464
465 pub fn to_bulk_part(&self) -> Result<Option<BulkPart>> {
468 let Some(sorted_batch) = self.concat_and_sort()? else {
469 return Ok(None);
470 };
471
472 let timestamp_index = self.parts[0].timestamp_index;
473
474 Ok(Some(BulkPart {
475 batch: sorted_batch,
476 max_timestamp: self.max_timestamp,
477 min_timestamp: self.min_timestamp,
478 sequence: self.max_sequence,
479 timestamp_index,
480 raw_data: None,
481 }))
482 }
483
484 pub fn clear(&mut self) {
486 self.parts.clear();
487 self.total_rows = 0;
488 self.total_bytes = 0;
489 self.min_timestamp = i64::MAX;
490 self.max_timestamp = i64::MIN;
491 self.max_sequence = 0;
492 }
493}
494
495pub fn record_batch_estimated_size(batch: &RecordBatch) -> usize {
497 batch
498 .columns()
499 .iter()
500 .map(|c| c.to_data().get_slice_memory_size().unwrap_or(0))
502 .sum()
503}
504
505enum PrimaryKeyColumnBuilder {
507 StringDict(StringDictionaryBuilder<UInt32Type>),
509 Vector(Box<dyn MutableVector>),
511}
512
513impl PrimaryKeyColumnBuilder {
514 fn push_value_ref(&mut self, value: ValueRef) -> Result<()> {
516 match self {
517 PrimaryKeyColumnBuilder::StringDict(builder) => {
518 if let Some(s) = value.try_into_string().context(DataTypeMismatchSnafu)? {
519 builder.append_value(s);
521 } else {
522 builder.append_null();
523 }
524 }
525 PrimaryKeyColumnBuilder::Vector(builder) => {
526 builder.push_value_ref(&value);
527 }
528 }
529 Ok(())
530 }
531
532 fn into_arrow_array(self) -> ArrayRef {
534 match self {
535 PrimaryKeyColumnBuilder::StringDict(mut builder) => Arc::new(builder.finish()),
536 PrimaryKeyColumnBuilder::Vector(mut builder) => builder.to_vector().to_arrow_array(),
537 }
538 }
539}
540
541pub struct BulkPartConverter {
543 schema: SchemaRef,
545 primary_key_codec: Arc<dyn PrimaryKeyCodec>,
547 key_buf: Vec<u8>,
549 key_array_builder: PrimaryKeyArrayBuilder,
551 value_builder: ValueBuilder,
553 primary_key_column_builders: Vec<PrimaryKeyColumnBuilder>,
556
557 max_ts: i64,
559 min_ts: i64,
561 max_sequence: SequenceNumber,
563}
564
565impl BulkPartConverter {
566 pub fn new(
571 region_metadata: &RegionMetadataRef,
572 schema: SchemaRef,
573 capacity: usize,
574 primary_key_codec: Arc<dyn PrimaryKeyCodec>,
575 store_primary_key_columns: bool,
576 ) -> Self {
577 debug_assert_eq!(
578 region_metadata.primary_key_encoding,
579 primary_key_codec.encoding()
580 );
581
582 let primary_key_column_builders = if store_primary_key_columns
583 && region_metadata.primary_key_encoding != PrimaryKeyEncoding::Sparse
584 {
585 new_primary_key_column_builders(region_metadata, capacity)
586 } else {
587 Vec::new()
588 };
589
590 Self {
591 schema,
592 primary_key_codec,
593 key_buf: Vec::new(),
594 key_array_builder: PrimaryKeyArrayBuilder::new(),
595 value_builder: ValueBuilder::new(region_metadata, capacity),
596 primary_key_column_builders,
597 min_ts: i64::MAX,
598 max_ts: i64::MIN,
599 max_sequence: SequenceNumber::MIN,
600 }
601 }
602
603 pub fn append_key_values(&mut self, key_values: &KeyValues) -> Result<()> {
605 for kv in key_values.iter() {
606 self.append_key_value(&kv)?;
607 }
608
609 Ok(())
610 }
611
612 fn append_key_value(&mut self, kv: &KeyValue) -> Result<()> {
616 if self.primary_key_codec.encoding() == PrimaryKeyEncoding::Sparse {
618 let mut primary_keys = kv.primary_keys();
621 if let Some(encoded) = primary_keys
622 .next()
623 .context(ColumnNotFoundSnafu {
624 column: PRIMARY_KEY_COLUMN_NAME,
625 })?
626 .try_into_binary()
627 .context(DataTypeMismatchSnafu)?
628 {
629 self.key_array_builder
630 .append(encoded)
631 .context(ComputeArrowSnafu)?;
632 } else {
633 self.key_array_builder
634 .append("")
635 .context(ComputeArrowSnafu)?;
636 }
637 } else {
638 self.key_buf.clear();
640 self.primary_key_codec
641 .encode_key_value(kv, &mut self.key_buf)
642 .context(EncodeSnafu)?;
643 self.key_array_builder
644 .append(&self.key_buf)
645 .context(ComputeArrowSnafu)?;
646 };
647
648 if !self.primary_key_column_builders.is_empty() {
650 for (builder, pk_value) in self
651 .primary_key_column_builders
652 .iter_mut()
653 .zip(kv.primary_keys())
654 {
655 builder.push_value_ref(pk_value)?;
656 }
657 }
658
659 self.value_builder.push(
661 kv.timestamp(),
662 kv.sequence(),
663 kv.op_type() as u8,
664 kv.fields(),
665 );
666
667 let ts = kv
670 .timestamp()
671 .try_into_timestamp()
672 .unwrap()
673 .unwrap()
674 .value();
675 self.min_ts = self.min_ts.min(ts);
676 self.max_ts = self.max_ts.max(ts);
677 self.max_sequence = self.max_sequence.max(kv.sequence());
678
679 Ok(())
680 }
681
682 pub fn convert(mut self) -> Result<BulkPart> {
686 let values = Values::from(self.value_builder);
687 let mut columns =
688 Vec::with_capacity(4 + values.fields.len() + self.primary_key_column_builders.len());
689
690 for builder in self.primary_key_column_builders {
692 columns.push(builder.into_arrow_array());
693 }
694 columns.extend(values.fields.iter().map(|field| field.to_arrow_array()));
696 let timestamp_index = columns.len();
698 columns.push(values.timestamp.to_arrow_array());
699 let pk_array = self.key_array_builder.finish();
701 columns.push(Arc::new(pk_array));
702 columns.push(values.sequence.to_arrow_array());
704 columns.push(values.op_type.to_arrow_array());
705
706 let schema = align_schema_with_json_array(self.schema, &columns);
709 let batch = RecordBatch::try_new(schema, columns).context(NewRecordBatchSnafu)?;
710 let batch = sort_primary_key_record_batch(&batch)?;
712
713 Ok(BulkPart {
714 batch,
715 max_timestamp: self.max_ts,
716 min_timestamp: self.min_ts,
717 sequence: self.max_sequence,
718 timestamp_index,
719 raw_data: None,
720 })
721 }
722}
723
724fn align_schema_with_json_array(schema: SchemaRef, columns: &[ArrayRef]) -> SchemaRef {
725 if schema.fields().iter().all(|f| !is_structured_json_field(f)) {
726 return schema;
727 }
728
729 let mut fields = Vec::with_capacity(schema.fields().len());
730 for (field, array) in schema.fields().iter().zip(columns) {
731 if !is_structured_json_field(field) {
732 fields.push(field.clone());
733 continue;
734 }
735
736 let mut field = field.as_ref().clone();
737 field.set_data_type(array.data_type().clone());
738 fields.push(Arc::new(field));
739 }
740
741 Arc::new(Schema::new_with_metadata(fields, schema.metadata().clone()))
742}
743
744fn new_primary_key_column_builders(
745 metadata: &RegionMetadata,
746 capacity: usize,
747) -> Vec<PrimaryKeyColumnBuilder> {
748 metadata
749 .primary_key_columns()
750 .map(|col| {
751 if col.column_schema.data_type.is_string() {
752 PrimaryKeyColumnBuilder::StringDict(StringDictionaryBuilder::with_capacity(
753 capacity,
754 INIT_DICT_VALUE_CAPACITY,
755 capacity,
756 ))
757 } else {
758 PrimaryKeyColumnBuilder::Vector(
759 col.column_schema.data_type.create_mutable_vector(capacity),
760 )
761 }
762 })
763 .collect()
764}
765
766pub fn sort_primary_key_record_batch(batch: &RecordBatch) -> Result<RecordBatch> {
768 let total_columns = batch.num_columns();
769 let sort_columns = vec![
770 SortColumn {
772 values: batch.column(total_columns - 3).clone(),
773 options: Some(SortOptions {
774 descending: false,
775 nulls_first: true,
776 }),
777 },
778 SortColumn {
780 values: batch.column(total_columns - 4).clone(),
781 options: Some(SortOptions {
782 descending: false,
783 nulls_first: true,
784 }),
785 },
786 SortColumn {
788 values: batch.column(total_columns - 2).clone(),
789 options: Some(SortOptions {
790 descending: true,
791 nulls_first: true,
792 }),
793 },
794 ];
795
796 let indices = datatypes::arrow::compute::lexsort_to_indices(&sort_columns, None)
797 .context(ComputeArrowSnafu)?;
798
799 datatypes::arrow::compute::take_record_batch(batch, &indices).context(ComputeArrowSnafu)
800}
801
802pub fn convert_bulk_part(
828 part: BulkPart,
829 region_metadata: &RegionMetadataRef,
830 primary_key_codec: Arc<dyn PrimaryKeyCodec>,
831 schema: SchemaRef,
832 store_primary_key_columns: bool,
833) -> Result<Option<BulkPart>> {
834 if part.num_rows() == 0 {
835 return Ok(None);
836 }
837
838 let num_rows = part.num_rows();
839 let is_sparse = region_metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse;
840
841 let input_schema = part.batch.schema();
843 let column_indices: HashMap<&str, usize> = input_schema
844 .fields()
845 .iter()
846 .enumerate()
847 .map(|(idx, field)| (field.name().as_str(), idx))
848 .collect();
849
850 let mut output_columns = Vec::new();
852
853 let pk_array = if is_sparse {
855 None
858 } else {
859 let pk_vectors: Result<Vec<_>> = region_metadata
861 .primary_key_columns()
862 .map(|col_meta| {
863 let col_idx = column_indices
864 .get(col_meta.column_schema.name.as_str())
865 .context(ColumnNotFoundSnafu {
866 column: &col_meta.column_schema.name,
867 })?;
868 let col = part.batch.column(*col_idx);
869 Helper::try_into_vector(col).context(error::ComputeVectorSnafu)
870 })
871 .collect();
872 let pk_vectors = pk_vectors?;
873
874 let mut key_array_builder = PrimaryKeyArrayBuilder::new();
875 let mut encode_buf = Vec::new();
876
877 for row_idx in 0..num_rows {
878 encode_buf.clear();
879
880 let pk_values_with_ids: Vec<_> = region_metadata
882 .primary_key
883 .iter()
884 .zip(pk_vectors.iter())
885 .map(|(col_id, vector)| (*col_id, vector.get_ref(row_idx)))
886 .collect();
887
888 primary_key_codec
890 .encode_value_refs(&pk_values_with_ids, &mut encode_buf)
891 .context(EncodeSnafu)?;
892
893 key_array_builder
894 .append(&encode_buf)
895 .context(ComputeArrowSnafu)?;
896 }
897
898 Some(key_array_builder.finish())
899 };
900
901 if store_primary_key_columns && !is_sparse {
903 for col_meta in region_metadata.primary_key_columns() {
904 let col_idx = column_indices
905 .get(col_meta.column_schema.name.as_str())
906 .context(ColumnNotFoundSnafu {
907 column: &col_meta.column_schema.name,
908 })?;
909 let col = part.batch.column(*col_idx);
910
911 let col = if col_meta.column_schema.data_type.is_string() {
913 let target_type = ArrowDataType::Dictionary(
914 Box::new(ArrowDataType::UInt32),
915 Box::new(ArrowDataType::Utf8),
916 );
917 arrow::compute::cast(col, &target_type).context(ComputeArrowSnafu)?
918 } else {
919 col.clone()
920 };
921 output_columns.push(col);
922 }
923 }
924
925 for col_meta in region_metadata.field_columns() {
927 let col_idx = column_indices
928 .get(col_meta.column_schema.name.as_str())
929 .context(ColumnNotFoundSnafu {
930 column: &col_meta.column_schema.name,
931 })?;
932 output_columns.push(part.batch.column(*col_idx).clone());
933 }
934
935 let new_timestamp_index = output_columns.len();
937 let ts_col_idx = column_indices
938 .get(
939 region_metadata
940 .time_index_column()
941 .column_schema
942 .name
943 .as_str(),
944 )
945 .context(ColumnNotFoundSnafu {
946 column: ®ion_metadata.time_index_column().column_schema.name,
947 })?;
948 output_columns.push(part.batch.column(*ts_col_idx).clone());
949
950 let pk_dictionary = if let Some(pk_dict_array) = pk_array {
952 Arc::new(pk_dict_array) as ArrayRef
953 } else {
954 let pk_col_idx =
955 column_indices
956 .get(PRIMARY_KEY_COLUMN_NAME)
957 .context(ColumnNotFoundSnafu {
958 column: PRIMARY_KEY_COLUMN_NAME,
959 })?;
960 let col = part.batch.column(*pk_col_idx);
961
962 let target_type = ArrowDataType::Dictionary(
964 Box::new(ArrowDataType::UInt32),
965 Box::new(ArrowDataType::Binary),
966 );
967 arrow::compute::cast(col, &target_type).context(ComputeArrowSnafu)?
968 };
969 output_columns.push(pk_dictionary);
970
971 let sequence_array = UInt64Array::from(vec![part.sequence; num_rows]);
972 output_columns.push(Arc::new(sequence_array) as ArrayRef);
973
974 let op_type_array = UInt8Array::from(vec![OpType::Put as u8; num_rows]);
975 output_columns.push(Arc::new(op_type_array) as ArrayRef);
976
977 let batch = RecordBatch::try_new(schema, output_columns).context(NewRecordBatchSnafu)?;
978
979 let sorted_batch = sort_primary_key_record_batch(&batch)?;
981
982 Ok(Some(BulkPart {
983 batch: sorted_batch,
984 max_timestamp: part.max_timestamp,
985 min_timestamp: part.min_timestamp,
986 sequence: part.sequence,
987 timestamp_index: new_timestamp_index,
988 raw_data: None,
989 }))
990}
991
992#[derive(Debug, Clone)]
993pub struct EncodedBulkPart {
994 data: Bytes,
995 metadata: BulkPartMeta,
996 schema: SchemaRef,
998}
999
1000impl EncodedBulkPart {
1001 pub fn new(data: Bytes, metadata: BulkPartMeta, schema: SchemaRef) -> Self {
1002 Self {
1003 data,
1004 metadata,
1005 schema,
1006 }
1007 }
1008
1009 pub fn metadata(&self) -> &BulkPartMeta {
1010 &self.metadata
1011 }
1012
1013 pub(crate) fn schema(&self) -> SchemaRef {
1014 self.schema.clone()
1015 }
1016
1017 pub(crate) fn size_bytes(&self) -> usize {
1019 self.data.len()
1020 }
1021
1022 pub fn data(&self) -> &Bytes {
1024 &self.data
1025 }
1026
1027 pub fn to_memtable_stats(&self) -> MemtableStats {
1029 let meta = &self.metadata;
1030 let ts_type = meta.region_metadata.time_index_type();
1031 let min_ts = ts_type.create_timestamp(meta.min_timestamp);
1032 let max_ts = ts_type.create_timestamp(meta.max_timestamp);
1033
1034 MemtableStats {
1035 estimated_bytes: self.size_bytes(),
1036 time_range: Some((min_ts, max_ts)),
1037 num_rows: meta.num_rows,
1038 num_ranges: 1,
1039 max_sequence: meta.max_sequence,
1040 series_count: meta.num_series as usize,
1041 }
1042 }
1043
1044 pub(crate) fn to_sst_info(&self, file_id: FileId) -> SstInfo {
1052 let unit = self.metadata.region_metadata.time_index_type().unit();
1053 let max_row_group_uncompressed_size: u64 = self
1054 .metadata
1055 .parquet_metadata
1056 .row_groups()
1057 .iter()
1058 .map(|rg| {
1059 rg.columns()
1060 .iter()
1061 .map(|c| c.uncompressed_size() as u64)
1062 .sum::<u64>()
1063 })
1064 .max()
1065 .unwrap_or(0);
1066 SstInfo {
1067 file_id,
1068 time_range: (
1069 Timestamp::new(self.metadata.min_timestamp, unit),
1070 Timestamp::new(self.metadata.max_timestamp, unit),
1071 ),
1072 file_size: self.data.len() as u64,
1073 max_row_group_uncompressed_size,
1074 num_rows: self.metadata.num_rows,
1075 num_row_groups: self.metadata.parquet_metadata.num_row_groups() as u64,
1076 file_metadata: Some(self.metadata.parquet_metadata.clone()),
1077 index_metadata: IndexOutput::default(),
1078 num_series: self.metadata.num_series,
1079 }
1080 }
1081
1082 pub(crate) fn read(
1083 &self,
1084 context: BulkIterContextRef,
1085 sequence: Option<SequenceRange>,
1086 mem_scan_metrics: Option<MemScanMetrics>,
1087 ) -> Result<Option<BoxedRecordBatchIterator>> {
1088 let skip_fields_for_pruning = context.pre_filter_mode().skip_fields();
1090
1091 let row_groups_to_read =
1093 context.row_groups_to_read(&self.metadata.parquet_metadata, skip_fields_for_pruning);
1094
1095 if row_groups_to_read.is_empty() {
1096 return Ok(None);
1098 }
1099
1100 let iter = EncodedBulkPartIter::try_new(
1101 self,
1102 context,
1103 row_groups_to_read,
1104 sequence,
1105 mem_scan_metrics,
1106 )?;
1107 Ok(Some(Box::new(iter) as BoxedRecordBatchIterator))
1108 }
1109}
1110
1111#[derive(Debug, Clone)]
1113pub struct BulkPartMeta {
1114 pub num_rows: usize,
1116 pub max_timestamp: i64,
1118 pub min_timestamp: i64,
1120 pub parquet_metadata: Arc<ParquetMetaData>,
1122 pub region_metadata: RegionMetadataRef,
1124 pub num_series: u64,
1126 pub max_sequence: u64,
1128}
1129
1130#[derive(Default, Debug)]
1132pub struct BulkPartEncodeMetrics {
1133 pub iter_cost: Duration,
1135 pub write_cost: Duration,
1137 pub raw_size: usize,
1139 pub encoded_size: usize,
1141 pub num_rows: usize,
1143}
1144
1145pub struct BulkPartEncoder {
1146 metadata: RegionMetadataRef,
1147 writer_props: Option<WriterProperties>,
1148}
1149
1150impl BulkPartEncoder {
1151 pub fn new(metadata: RegionMetadataRef, row_group_size: usize) -> Result<BulkPartEncoder> {
1152 let json = metadata.to_json().context(InvalidMetadataSnafu)?;
1154 let key_value_meta =
1155 parquet::file::metadata::KeyValue::new(PARQUET_METADATA_KEY.to_string(), json);
1156
1157 let writer_props = Some(
1159 WriterProperties::builder()
1160 .set_key_value_metadata(Some(vec![key_value_meta]))
1161 .set_write_batch_size(row_group_size)
1162 .set_max_row_group_row_count(Some(row_group_size))
1163 .set_compression(Compression::ZSTD(ZstdLevel::default()))
1164 .set_column_index_truncate_length(None)
1165 .set_statistics_truncate_length(None)
1166 .build(),
1167 );
1168
1169 Ok(Self {
1170 metadata,
1171 writer_props,
1172 })
1173 }
1174}
1175
1176impl BulkPartEncoder {
1177 pub fn encode_record_batch_iter(
1179 &self,
1180 iter: BoxedRecordBatchIterator,
1181 arrow_schema: SchemaRef,
1182 min_timestamp: i64,
1183 max_timestamp: i64,
1184 max_sequence: u64,
1185 metrics: &mut BulkPartEncodeMetrics,
1186 ) -> Result<Option<EncodedBulkPart>> {
1187 let mut buf = Vec::with_capacity(4096);
1188 let mut writer =
1189 ArrowWriter::try_new(&mut buf, arrow_schema.clone(), self.writer_props.clone())
1190 .context(EncodeMemtableSnafu)?;
1191 let mut total_rows = 0;
1192 let mut series_estimator = SeriesEstimator::default();
1193
1194 let mut iter_start = Instant::now();
1196 for batch_result in iter {
1197 metrics.iter_cost += iter_start.elapsed();
1198 let batch = batch_result?;
1199 if batch.num_rows() == 0 {
1200 continue;
1201 }
1202
1203 series_estimator.update_flat(&batch);
1204 metrics.raw_size += record_batch_estimated_size(&batch);
1205 let write_start = Instant::now();
1206 writer.write(&batch).context(EncodeMemtableSnafu)?;
1207 metrics.write_cost += write_start.elapsed();
1208 total_rows += batch.num_rows();
1209 iter_start = Instant::now();
1210 }
1211 metrics.iter_cost += iter_start.elapsed();
1212
1213 if total_rows == 0 {
1214 return Ok(None);
1215 }
1216
1217 let close_start = Instant::now();
1218 let file_metadata = writer.close().context(EncodeMemtableSnafu)?;
1219 metrics.write_cost += close_start.elapsed();
1220 metrics.encoded_size += buf.len();
1221 metrics.num_rows += total_rows;
1222
1223 let buf = Bytes::from(buf);
1224 let parquet_metadata = Arc::new(file_metadata);
1225 let num_series = series_estimator.finish();
1226
1227 Ok(Some(EncodedBulkPart {
1228 data: buf,
1229 metadata: BulkPartMeta {
1230 num_rows: total_rows,
1231 max_timestamp,
1232 min_timestamp,
1233 parquet_metadata,
1234 region_metadata: self.metadata.clone(),
1235 num_series,
1236 max_sequence,
1237 },
1238 schema: arrow_schema,
1239 }))
1240 }
1241
1242 pub fn encode_part(&self, part: &BulkPart) -> Result<Option<EncodedBulkPart>> {
1244 if part.batch.num_rows() == 0 {
1245 return Ok(None);
1246 }
1247
1248 let mut buf = Vec::with_capacity(4096);
1249 let arrow_schema = part.batch.schema();
1250
1251 let file_metadata = {
1252 let mut writer =
1253 ArrowWriter::try_new(&mut buf, arrow_schema.clone(), self.writer_props.clone())
1254 .context(EncodeMemtableSnafu)?;
1255 writer.write(&part.batch).context(EncodeMemtableSnafu)?;
1256 writer.finish().context(EncodeMemtableSnafu)?
1257 };
1258
1259 let buf = Bytes::from(buf);
1260 let parquet_metadata = Arc::new(file_metadata);
1261
1262 Ok(Some(EncodedBulkPart {
1263 data: buf,
1264 metadata: BulkPartMeta {
1265 num_rows: part.batch.num_rows(),
1266 max_timestamp: part.max_timestamp,
1267 min_timestamp: part.min_timestamp,
1268 parquet_metadata,
1269 region_metadata: self.metadata.clone(),
1270 num_series: part.estimated_series_count() as u64,
1271 max_sequence: part.sequence,
1272 },
1273 schema: arrow_schema,
1274 }))
1275 }
1276}
1277
1278#[derive(Debug, Clone)]
1284struct BatchStats {
1285 num_batches: usize,
1287 first_tag_id: ColumnId,
1289 min_values: ArrayRef,
1291 max_values: ArrayRef,
1293}
1294
1295impl BatchStats {
1296 fn compute(batches: &[RecordBatch], metadata: &RegionMetadata) -> Option<Self> {
1301 let first_tag_id = *metadata.primary_key.first()?;
1306 let first_tag_column = metadata.column_by_id(first_tag_id)?;
1307 let data_type = &first_tag_column.column_schema.data_type;
1308
1309 let converter = build_primary_key_codec_with_fields(
1310 metadata.primary_key_encoding,
1311 [(first_tag_id, SortField::new(data_type.clone()))].into_iter(),
1312 );
1313 let pk_index = primary_key_column_index(batches.first()?.num_columns());
1314
1315 let mut min_builder = data_type.create_mutable_vector(batches.len());
1316 let mut max_builder = data_type.create_mutable_vector(batches.len());
1317
1318 for batch in batches {
1319 match Self::extract_first_tag_bounds(batch, pk_index, &*converter) {
1320 Some((min_val, max_val)) => {
1321 min_builder.push_value_ref(&min_val.as_value_ref());
1322 max_builder.push_value_ref(&max_val.as_value_ref());
1323 }
1324 None => {
1325 min_builder.push_null();
1326 max_builder.push_null();
1327 }
1328 }
1329 }
1330
1331 Some(Self {
1332 num_batches: batches.len(),
1333 first_tag_id,
1334 min_values: min_builder.to_vector().to_arrow_array(),
1335 max_values: max_builder.to_vector().to_arrow_array(),
1336 })
1337 }
1338
1339 fn extract_first_tag_bounds(
1341 batch: &RecordBatch,
1342 pk_index: usize,
1343 converter: &dyn PrimaryKeyCodec,
1344 ) -> Option<(datatypes::value::Value, datatypes::value::Value)> {
1345 if batch.num_rows() == 0 {
1346 return None;
1347 }
1348
1349 let pk_dict = batch
1350 .column(pk_index)
1351 .as_any()
1352 .downcast_ref::<PrimaryKeyArray>()?;
1353 let pk_values = pk_dict.values().as_any().downcast_ref::<BinaryArray>()?;
1354
1355 let keys = pk_dict.keys();
1356 let min_key = keys.value(0);
1357 let max_key = keys.value(batch.num_rows() - 1);
1358 let min_bytes = pk_values.value(min_key as usize);
1359 let max_bytes = pk_values.value(max_key as usize);
1360
1361 Some((
1362 converter.decode_leftmost(min_bytes).ok()??,
1363 converter.decode_leftmost(max_bytes).ok()??,
1364 ))
1365 }
1366}
1367
1368struct BatchPruningStats<'a> {
1373 stats: &'a BatchStats,
1374 metadata: &'a RegionMetadataRef,
1375}
1376
1377impl PruningStatistics for BatchPruningStats<'_> {
1378 fn min_values(&self, column: &Column) -> Option<ArrayRef> {
1379 let col = self.metadata.column_by_name(&column.name)?;
1380 if col.column_id == self.stats.first_tag_id {
1381 Some(self.stats.min_values.clone())
1382 } else {
1383 None
1384 }
1385 }
1386
1387 fn max_values(&self, column: &Column) -> Option<ArrayRef> {
1388 let col = self.metadata.column_by_name(&column.name)?;
1389 if col.column_id == self.stats.first_tag_id {
1390 Some(self.stats.max_values.clone())
1391 } else {
1392 None
1393 }
1394 }
1395
1396 fn num_containers(&self) -> usize {
1397 self.stats.num_batches
1398 }
1399
1400 fn null_counts(&self, _column: &Column) -> Option<ArrayRef> {
1401 None
1402 }
1403
1404 fn row_counts(&self, _column: &Column) -> Option<ArrayRef> {
1405 None
1406 }
1407
1408 fn contained(
1409 &self,
1410 _column: &Column,
1411 _values: &std::collections::HashSet<datafusion_common::ScalarValue>,
1412 ) -> Option<BooleanArray> {
1413 None
1414 }
1415}
1416
1417fn predicate_references_column(predicate: &table::predicate::Predicate, column_name: &str) -> bool {
1419 let mut columns = HashSet::new();
1420 for expr in predicate.exprs() {
1421 let _ = expr_to_columns(expr, &mut columns);
1422 }
1423 columns.iter().any(|col| col.name == column_name)
1424}
1425
1426pub(crate) fn should_prune_bulk_part(
1430 batch: &RecordBatch,
1431 context: &BulkIterContext,
1432 metadata: &RegionMetadata,
1433) -> bool {
1434 let predicate = match &context.predicate {
1435 Some(p) => p,
1436 None => return false,
1437 };
1438 let first_tag_id = match metadata.primary_key.first() {
1441 Some(id) => *id,
1442 None => return false,
1443 };
1444 let first_tag_name = &metadata
1446 .column_by_id(first_tag_id)
1447 .unwrap()
1448 .column_schema
1449 .name;
1450 if !predicate_references_column(predicate, first_tag_name) {
1451 return false;
1452 }
1453 let stats = match BatchStats::compute(std::slice::from_ref(batch), metadata) {
1454 Some(s) => s,
1455 None => return false,
1456 };
1457 let region_meta = context.read_format().metadata();
1458 let pruning_stats = BatchPruningStats {
1459 stats: &stats,
1460 metadata: region_meta,
1461 };
1462 let mask = predicate.prune_with_stats(&pruning_stats, region_meta.schema.arrow_schema());
1463 !mask.first().copied().unwrap_or(true)
1464}
1465
1466#[derive(Debug, Clone)]
1472pub struct MultiBulkPart {
1473 batches: SmallVec<[RecordBatch; 4]>,
1475 total_rows: usize,
1477 max_timestamp: i64,
1479 min_timestamp: i64,
1481 max_sequence: SequenceNumber,
1483 series_count: usize,
1485 batch_stats: Option<BatchStats>,
1488}
1489
1490impl MultiBulkPart {
1491 pub fn from_bulk_part(part: BulkPart, metadata: &RegionMetadata) -> Self {
1493 let num_rows = part.num_rows();
1494 let series_count = part.estimated_series_count();
1495 let batch_stats = BatchStats::compute(std::slice::from_ref(&part.batch), metadata);
1496 let mut batches = SmallVec::new();
1497 batches.push(part.batch);
1498
1499 Self {
1500 batches,
1501 total_rows: num_rows,
1502 max_timestamp: part.max_timestamp,
1503 min_timestamp: part.min_timestamp,
1504 max_sequence: part.sequence,
1505 series_count,
1506 batch_stats,
1507 }
1508 }
1509
1510 pub fn new(
1523 batches: Vec<RecordBatch>,
1524 min_timestamp: i64,
1525 max_timestamp: i64,
1526 max_sequence: SequenceNumber,
1527 series_count: usize,
1528 metadata: &RegionMetadata,
1529 ) -> Self {
1530 assert!(!batches.is_empty(), "batches must not be empty");
1531
1532 let total_rows = batches.iter().map(|b| b.num_rows()).sum();
1533 let batch_stats = BatchStats::compute(&batches, metadata);
1534
1535 Self {
1536 batches: SmallVec::from_vec(batches),
1537 total_rows,
1538 max_timestamp,
1539 min_timestamp,
1540 max_sequence,
1541 series_count,
1542 batch_stats,
1543 }
1544 }
1545
1546 pub fn num_rows(&self) -> usize {
1548 self.total_rows
1549 }
1550
1551 pub(crate) fn schemas(&self) -> impl Iterator<Item = SchemaRef> + '_ {
1552 self.batches.iter().map(|batch| batch.schema())
1553 }
1554
1555 pub fn min_timestamp(&self) -> i64 {
1557 self.min_timestamp
1558 }
1559
1560 pub fn max_timestamp(&self) -> i64 {
1562 self.max_timestamp
1563 }
1564
1565 pub fn max_sequence(&self) -> SequenceNumber {
1567 self.max_sequence
1568 }
1569
1570 pub fn series_count(&self) -> usize {
1572 self.series_count
1573 }
1574
1575 pub fn num_batches(&self) -> usize {
1577 self.batches.len()
1578 }
1579
1580 pub(crate) fn estimated_size(&self) -> usize {
1582 self.batches.iter().map(record_batch_estimated_size).sum()
1583 }
1584
1585 pub(crate) fn read(
1591 &self,
1592 context: BulkIterContextRef,
1593 sequence: Option<SequenceRange>,
1594 mem_scan_metrics: Option<MemScanMetrics>,
1595 ) -> Result<Option<BoxedRecordBatchIterator>> {
1596 if self.batches.is_empty() {
1597 return Ok(None);
1598 }
1599
1600 let batches_to_read = self.prune_batches(&context);
1601
1602 if batches_to_read.is_empty() {
1603 return Ok(None);
1604 }
1605
1606 let iter = crate::memtable::bulk::part_reader::BulkPartBatchIter::new(
1607 batches_to_read,
1608 context,
1609 sequence,
1610 self.series_count,
1611 mem_scan_metrics,
1612 );
1613 Ok(Some(Box::new(iter) as BoxedRecordBatchIterator))
1614 }
1615
1616 fn prune_batches(&self, context: &BulkIterContextRef) -> Vec<RecordBatch> {
1619 if let Some(stats) = &self.batch_stats
1620 && let Some(predicate) = &context.predicate
1621 {
1622 let region_meta = context.read_format().metadata();
1623 let pruning_stats = BatchPruningStats {
1624 stats,
1625 metadata: region_meta,
1626 };
1627 let mask =
1628 predicate.prune_with_stats(&pruning_stats, region_meta.schema.arrow_schema());
1629 self.batches
1630 .iter()
1631 .zip(mask.iter())
1632 .filter_map(
1633 |(batch, &selected)| {
1634 if selected { Some(batch.clone()) } else { None }
1635 },
1636 )
1637 .collect()
1638 } else {
1639 self.batches.iter().cloned().collect()
1640 }
1641 }
1642
1643 pub fn to_memtable_stats(&self, region_metadata: &RegionMetadataRef) -> MemtableStats {
1645 let ts_type = region_metadata.time_index_type();
1646 let min_ts = ts_type.create_timestamp(self.min_timestamp);
1647 let max_ts = ts_type.create_timestamp(self.max_timestamp);
1648
1649 MemtableStats {
1650 estimated_bytes: self.estimated_size(),
1651 time_range: Some((min_ts, max_ts)),
1652 num_rows: self.num_rows(),
1653 num_ranges: 1,
1654 max_sequence: self.max_sequence,
1655 series_count: self.series_count,
1656 }
1657 }
1658}
1659
1660#[cfg(test)]
1661mod tests {
1662 use api::v1::{Row, SemanticType, WriteHint};
1663 use datafusion_common::ScalarValue;
1664 use datatypes::arrow::array::{
1665 BinaryArray, DictionaryArray, Float64Array, TimestampMillisecondArray,
1666 };
1667 use datatypes::arrow::datatypes::UInt32Type;
1668 use datatypes::prelude::{ConcreteDataType, Value};
1669 use datatypes::schema::ColumnSchema;
1670 use mito_codec::row_converter::build_primary_key_codec;
1671 use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder};
1672 use store_api::storage::RegionId;
1673 use store_api::storage::consts::ReservedColumnId;
1674 use table::predicate::Predicate;
1675
1676 use super::*;
1677 use crate::memtable::bulk::context::BulkIterContext;
1678 use crate::sst::{FlatSchemaOptions, to_flat_sst_arrow_schema};
1679 use crate::test_util::memtable_util::{build_key_values_with_ts_seq_values, metadata_for_test};
1680
1681 struct MutationInput<'a> {
1682 k0: &'a str,
1683 k1: u32,
1684 timestamps: &'a [i64],
1685 v1: &'a [Option<f64>],
1686 sequence: u64,
1687 }
1688
1689 #[test]
1690 fn test_unordered_part_tracks_estimated_bytes() {
1691 let mut part = UnorderedPart::new();
1692 let bulk_part = BulkPart {
1693 batch: RecordBatch::new_empty(Arc::new(arrow::datatypes::Schema::empty())),
1694 max_timestamp: 0,
1695 min_timestamp: 0,
1696 sequence: 0,
1697 timestamp_index: 0,
1698 raw_data: None,
1699 };
1700 let estimated_size = bulk_part.estimated_size();
1701
1702 part.push(bulk_part);
1703 assert_eq!(estimated_size, part.estimated_bytes());
1704 part.clear();
1705 assert_eq!(0, part.estimated_bytes());
1706 assert!(part.is_empty());
1707 }
1708
1709 #[test]
1710 fn test_unordered_part_should_accept() {
1711 let mut part = UnorderedPart::new();
1712 part.set_threshold(10);
1713 assert!(part.should_accept(9));
1714 assert!(!part.should_accept(10));
1715 }
1716
1717 fn encode(input: &[MutationInput]) -> EncodedBulkPart {
1718 let metadata = metadata_for_test();
1719 let kvs = input
1720 .iter()
1721 .map(|m| {
1722 build_key_values_with_ts_seq_values(
1723 &metadata,
1724 m.k0.to_string(),
1725 m.k1,
1726 m.timestamps.iter().copied(),
1727 m.v1.iter().copied(),
1728 m.sequence,
1729 )
1730 })
1731 .collect::<Vec<_>>();
1732 let schema = to_flat_sst_arrow_schema(&metadata, &FlatSchemaOptions::default());
1733 let primary_key_codec = build_primary_key_codec(&metadata);
1734 let mut converter = BulkPartConverter::new(&metadata, schema, 64, primary_key_codec, true);
1735 for kv in kvs {
1736 converter.append_key_values(&kv).unwrap();
1737 }
1738 let part = converter.convert().unwrap();
1739 let encoder = BulkPartEncoder::new(metadata, 1024).unwrap();
1740 encoder.encode_part(&part).unwrap().unwrap()
1741 }
1742
1743 #[test]
1744 fn test_write_and_read_part_projection() {
1745 let part = encode(&[
1746 MutationInput {
1747 k0: "a",
1748 k1: 0,
1749 timestamps: &[1],
1750 v1: &[Some(0.1)],
1751 sequence: 0,
1752 },
1753 MutationInput {
1754 k0: "b",
1755 k1: 0,
1756 timestamps: &[1],
1757 v1: &[Some(0.0)],
1758 sequence: 0,
1759 },
1760 MutationInput {
1761 k0: "a",
1762 k1: 0,
1763 timestamps: &[2],
1764 v1: &[Some(0.2)],
1765 sequence: 1,
1766 },
1767 ]);
1768
1769 let projection = &[4u32];
1770 let reader = part
1771 .read(
1772 Arc::new(
1773 BulkIterContext::new(
1774 part.metadata.region_metadata.clone(),
1775 Some(projection.as_slice()),
1776 None,
1777 false,
1778 crate::sst::parquet::DEFAULT_READ_BATCH_SIZE,
1779 )
1780 .unwrap(),
1781 ),
1782 None,
1783 None,
1784 )
1785 .unwrap()
1786 .expect("expect at least one row group");
1787
1788 let mut total_rows_read = 0;
1789 let mut field: Vec<f64> = vec![];
1790 for res in reader {
1791 let batch = res.unwrap();
1792 assert_eq!(5, batch.num_columns());
1793 field.extend_from_slice(
1794 batch
1795 .column(0)
1796 .as_any()
1797 .downcast_ref::<Float64Array>()
1798 .unwrap()
1799 .values(),
1800 );
1801 total_rows_read += batch.num_rows();
1802 }
1803 assert_eq!(3, total_rows_read);
1804 assert_eq!(vec![0.1, 0.2, 0.0], field);
1805 }
1806
1807 fn prepare(key_values: Vec<(&str, u32, (i64, i64), u64)>) -> EncodedBulkPart {
1808 let metadata = metadata_for_test();
1809 let kvs = key_values
1810 .into_iter()
1811 .map(|(k0, k1, (start, end), sequence)| {
1812 let ts = start..end;
1813 let v1 = (start..end).map(|_| None);
1814 build_key_values_with_ts_seq_values(&metadata, k0.to_string(), k1, ts, v1, sequence)
1815 })
1816 .collect::<Vec<_>>();
1817 let schema = to_flat_sst_arrow_schema(&metadata, &FlatSchemaOptions::default());
1818 let primary_key_codec = build_primary_key_codec(&metadata);
1819 let mut converter = BulkPartConverter::new(&metadata, schema, 64, primary_key_codec, true);
1820 for kv in kvs {
1821 converter.append_key_values(&kv).unwrap();
1822 }
1823 let part = converter.convert().unwrap();
1824 let encoder = BulkPartEncoder::new(metadata, 1024).unwrap();
1825 encoder.encode_part(&part).unwrap().unwrap()
1826 }
1827
1828 fn check_prune_row_group(
1829 part: &EncodedBulkPart,
1830 predicate: Option<Predicate>,
1831 expected_rows: usize,
1832 ) {
1833 let context = Arc::new(
1834 BulkIterContext::new(
1835 part.metadata.region_metadata.clone(),
1836 None,
1837 predicate,
1838 false,
1839 crate::sst::parquet::DEFAULT_READ_BATCH_SIZE,
1840 )
1841 .unwrap(),
1842 );
1843 let reader = part
1844 .read(context, None, None)
1845 .unwrap()
1846 .expect("expect at least one row group");
1847 let mut total_rows_read = 0;
1848 for res in reader {
1849 let batch = res.unwrap();
1850 total_rows_read += batch.num_rows();
1851 }
1852 assert_eq!(expected_rows, total_rows_read);
1854 }
1855
1856 #[test]
1857 fn test_prune_row_groups() {
1858 let part = prepare(vec![
1859 ("a", 0, (0, 40), 1),
1860 ("a", 1, (0, 60), 1),
1861 ("b", 0, (0, 100), 2),
1862 ("b", 1, (100, 180), 3),
1863 ("b", 1, (180, 210), 4),
1864 ]);
1865
1866 let context = Arc::new(
1867 BulkIterContext::new(
1868 part.metadata.region_metadata.clone(),
1869 None,
1870 Some(Predicate::new(vec![datafusion_expr::col("ts").eq(
1871 datafusion_expr::lit(ScalarValue::TimestampMillisecond(Some(300), None)),
1872 )])),
1873 false,
1874 crate::sst::parquet::DEFAULT_READ_BATCH_SIZE,
1875 )
1876 .unwrap(),
1877 );
1878 assert!(part.read(context, None, None).unwrap().is_none());
1879
1880 check_prune_row_group(&part, None, 310);
1881
1882 check_prune_row_group(
1883 &part,
1884 Some(Predicate::new(vec![
1885 datafusion_expr::col("k0").eq(datafusion_expr::lit("a")),
1886 datafusion_expr::col("k1").eq(datafusion_expr::lit(0u32)),
1887 ])),
1888 40,
1889 );
1890
1891 check_prune_row_group(
1892 &part,
1893 Some(Predicate::new(vec![
1894 datafusion_expr::col("k0").eq(datafusion_expr::lit("a")),
1895 datafusion_expr::col("k1").eq(datafusion_expr::lit(1u32)),
1896 ])),
1897 60,
1898 );
1899
1900 check_prune_row_group(
1901 &part,
1902 Some(Predicate::new(vec![
1903 datafusion_expr::col("k0").eq(datafusion_expr::lit("a")),
1904 ])),
1905 100,
1906 );
1907
1908 check_prune_row_group(
1909 &part,
1910 Some(Predicate::new(vec![
1911 datafusion_expr::col("k0").eq(datafusion_expr::lit("b")),
1912 datafusion_expr::col("k1").eq(datafusion_expr::lit(0u32)),
1913 ])),
1914 100,
1915 );
1916
1917 check_prune_row_group(
1919 &part,
1920 Some(Predicate::new(vec![
1921 datafusion_expr::col("v0").eq(datafusion_expr::lit(150i64)),
1922 ])),
1923 1,
1924 );
1925 }
1926
1927 #[test]
1928 fn test_bulk_part_converter_append_and_convert() {
1929 let metadata = metadata_for_test();
1930 let capacity = 100;
1931 let primary_key_codec = build_primary_key_codec(&metadata);
1932 let schema = to_flat_sst_arrow_schema(
1933 &metadata,
1934 &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
1935 );
1936
1937 let mut converter =
1938 BulkPartConverter::new(&metadata, schema, capacity, primary_key_codec, true);
1939
1940 let key_values1 = build_key_values_with_ts_seq_values(
1941 &metadata,
1942 "key1".to_string(),
1943 1u32,
1944 vec![1000, 2000].into_iter(),
1945 vec![Some(1.0), Some(2.0)].into_iter(),
1946 1,
1947 );
1948
1949 let key_values2 = build_key_values_with_ts_seq_values(
1950 &metadata,
1951 "key2".to_string(),
1952 2u32,
1953 vec![1500].into_iter(),
1954 vec![Some(3.0)].into_iter(),
1955 2,
1956 );
1957
1958 converter.append_key_values(&key_values1).unwrap();
1959 converter.append_key_values(&key_values2).unwrap();
1960
1961 let bulk_part = converter.convert().unwrap();
1962
1963 assert_eq!(bulk_part.num_rows(), 3);
1964 assert_eq!(bulk_part.min_timestamp, 1000);
1965 assert_eq!(bulk_part.max_timestamp, 2000);
1966 assert_eq!(bulk_part.sequence, 2);
1967 assert_eq!(bulk_part.timestamp_index, bulk_part.batch.num_columns() - 4);
1968
1969 let schema = bulk_part.batch.schema();
1972 let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
1973 assert_eq!(
1974 field_names,
1975 vec![
1976 "k0",
1977 "k1",
1978 "v0",
1979 "v1",
1980 "ts",
1981 "__primary_key",
1982 "__sequence",
1983 "__op_type"
1984 ]
1985 );
1986 }
1987
1988 #[test]
1989 fn test_bulk_part_converter_sorting() {
1990 let metadata = metadata_for_test();
1991 let capacity = 100;
1992 let primary_key_codec = build_primary_key_codec(&metadata);
1993 let schema = to_flat_sst_arrow_schema(
1994 &metadata,
1995 &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
1996 );
1997
1998 let mut converter =
1999 BulkPartConverter::new(&metadata, schema, capacity, primary_key_codec, true);
2000
2001 let key_values1 = build_key_values_with_ts_seq_values(
2002 &metadata,
2003 "z_key".to_string(),
2004 3u32,
2005 vec![3000].into_iter(),
2006 vec![Some(3.0)].into_iter(),
2007 3,
2008 );
2009
2010 let key_values2 = build_key_values_with_ts_seq_values(
2011 &metadata,
2012 "a_key".to_string(),
2013 1u32,
2014 vec![1000].into_iter(),
2015 vec![Some(1.0)].into_iter(),
2016 1,
2017 );
2018
2019 let key_values3 = build_key_values_with_ts_seq_values(
2020 &metadata,
2021 "m_key".to_string(),
2022 2u32,
2023 vec![2000].into_iter(),
2024 vec![Some(2.0)].into_iter(),
2025 2,
2026 );
2027
2028 converter.append_key_values(&key_values1).unwrap();
2029 converter.append_key_values(&key_values2).unwrap();
2030 converter.append_key_values(&key_values3).unwrap();
2031
2032 let bulk_part = converter.convert().unwrap();
2033
2034 assert_eq!(bulk_part.num_rows(), 3);
2035
2036 let ts_column = bulk_part.batch.column(bulk_part.timestamp_index);
2037 let seq_column = bulk_part.batch.column(bulk_part.batch.num_columns() - 2);
2038
2039 let ts_array = ts_column
2040 .as_any()
2041 .downcast_ref::<TimestampMillisecondArray>()
2042 .unwrap();
2043 let seq_array = seq_column.as_any().downcast_ref::<UInt64Array>().unwrap();
2044
2045 assert_eq!(ts_array.values(), &[1000, 2000, 3000]);
2046 assert_eq!(seq_array.values(), &[1, 2, 3]);
2047
2048 let schema = bulk_part.batch.schema();
2050 let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
2051 assert_eq!(
2052 field_names,
2053 vec![
2054 "k0",
2055 "k1",
2056 "v0",
2057 "v1",
2058 "ts",
2059 "__primary_key",
2060 "__sequence",
2061 "__op_type"
2062 ]
2063 );
2064 }
2065
2066 #[test]
2067 fn test_bulk_part_converter_empty() {
2068 let metadata = metadata_for_test();
2069 let capacity = 10;
2070 let primary_key_codec = build_primary_key_codec(&metadata);
2071 let schema = to_flat_sst_arrow_schema(
2072 &metadata,
2073 &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
2074 );
2075
2076 let converter =
2077 BulkPartConverter::new(&metadata, schema, capacity, primary_key_codec, true);
2078
2079 let bulk_part = converter.convert().unwrap();
2080
2081 assert_eq!(bulk_part.num_rows(), 0);
2082 assert_eq!(bulk_part.min_timestamp, i64::MAX);
2083 assert_eq!(bulk_part.max_timestamp, i64::MIN);
2084 assert_eq!(bulk_part.sequence, SequenceNumber::MIN);
2085
2086 let schema = bulk_part.batch.schema();
2088 let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
2089 assert_eq!(
2090 field_names,
2091 vec![
2092 "k0",
2093 "k1",
2094 "v0",
2095 "v1",
2096 "ts",
2097 "__primary_key",
2098 "__sequence",
2099 "__op_type"
2100 ]
2101 );
2102 }
2103
2104 #[test]
2105 fn test_bulk_part_converter_without_primary_key_columns() {
2106 let metadata = metadata_for_test();
2107 let primary_key_codec = build_primary_key_codec(&metadata);
2108 let schema = to_flat_sst_arrow_schema(
2109 &metadata,
2110 &FlatSchemaOptions {
2111 raw_pk_columns: false,
2112 string_pk_use_dict: true,
2113 ..Default::default()
2114 },
2115 );
2116
2117 let capacity = 100;
2118 let mut converter =
2119 BulkPartConverter::new(&metadata, schema, capacity, primary_key_codec, false);
2120
2121 let key_values1 = build_key_values_with_ts_seq_values(
2122 &metadata,
2123 "key1".to_string(),
2124 1u32,
2125 vec![1000, 2000].into_iter(),
2126 vec![Some(1.0), Some(2.0)].into_iter(),
2127 1,
2128 );
2129
2130 let key_values2 = build_key_values_with_ts_seq_values(
2131 &metadata,
2132 "key2".to_string(),
2133 2u32,
2134 vec![1500].into_iter(),
2135 vec![Some(3.0)].into_iter(),
2136 2,
2137 );
2138
2139 converter.append_key_values(&key_values1).unwrap();
2140 converter.append_key_values(&key_values2).unwrap();
2141
2142 let bulk_part = converter.convert().unwrap();
2143
2144 assert_eq!(bulk_part.num_rows(), 3);
2145 assert_eq!(bulk_part.min_timestamp, 1000);
2146 assert_eq!(bulk_part.max_timestamp, 2000);
2147 assert_eq!(bulk_part.sequence, 2);
2148 assert_eq!(bulk_part.timestamp_index, bulk_part.batch.num_columns() - 4);
2149
2150 let schema = bulk_part.batch.schema();
2152 let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
2153 assert_eq!(
2154 field_names,
2155 vec!["v0", "v1", "ts", "__primary_key", "__sequence", "__op_type"]
2156 );
2157 }
2158
2159 #[allow(clippy::too_many_arguments)]
2160 fn build_key_values_with_sparse_encoding(
2161 metadata: &RegionMetadataRef,
2162 primary_key_codec: &Arc<dyn PrimaryKeyCodec>,
2163 table_id: u32,
2164 tsid: u64,
2165 k0: String,
2166 k1: String,
2167 timestamps: impl Iterator<Item = i64>,
2168 values: impl Iterator<Item = Option<f64>>,
2169 sequence: SequenceNumber,
2170 ) -> KeyValues {
2171 let pk_values = vec![
2173 (ReservedColumnId::table_id(), Value::UInt32(table_id)),
2174 (ReservedColumnId::tsid(), Value::UInt64(tsid)),
2175 (0, Value::String(k0.clone().into())),
2176 (1, Value::String(k1.clone().into())),
2177 ];
2178 let mut encoded_key = Vec::new();
2179 primary_key_codec
2180 .encode_values(&pk_values, &mut encoded_key)
2181 .unwrap();
2182 assert!(!encoded_key.is_empty());
2183
2184 let column_schema = vec![
2186 api::v1::ColumnSchema {
2187 column_name: PRIMARY_KEY_COLUMN_NAME.to_string(),
2188 datatype: api::helper::ColumnDataTypeWrapper::try_from(
2189 ConcreteDataType::binary_datatype(),
2190 )
2191 .unwrap()
2192 .datatype() as i32,
2193 semantic_type: api::v1::SemanticType::Tag as i32,
2194 ..Default::default()
2195 },
2196 api::v1::ColumnSchema {
2197 column_name: "ts".to_string(),
2198 datatype: api::helper::ColumnDataTypeWrapper::try_from(
2199 ConcreteDataType::timestamp_millisecond_datatype(),
2200 )
2201 .unwrap()
2202 .datatype() as i32,
2203 semantic_type: api::v1::SemanticType::Timestamp as i32,
2204 ..Default::default()
2205 },
2206 api::v1::ColumnSchema {
2207 column_name: "v0".to_string(),
2208 datatype: api::helper::ColumnDataTypeWrapper::try_from(
2209 ConcreteDataType::int64_datatype(),
2210 )
2211 .unwrap()
2212 .datatype() as i32,
2213 semantic_type: api::v1::SemanticType::Field as i32,
2214 ..Default::default()
2215 },
2216 api::v1::ColumnSchema {
2217 column_name: "v1".to_string(),
2218 datatype: api::helper::ColumnDataTypeWrapper::try_from(
2219 ConcreteDataType::float64_datatype(),
2220 )
2221 .unwrap()
2222 .datatype() as i32,
2223 semantic_type: api::v1::SemanticType::Field as i32,
2224 ..Default::default()
2225 },
2226 ];
2227
2228 let rows = timestamps
2229 .zip(values)
2230 .map(|(ts, v)| Row {
2231 values: vec![
2232 api::v1::Value {
2233 value_data: Some(api::v1::value::ValueData::BinaryValue(
2234 encoded_key.clone(),
2235 )),
2236 },
2237 api::v1::Value {
2238 value_data: Some(api::v1::value::ValueData::TimestampMillisecondValue(ts)),
2239 },
2240 api::v1::Value {
2241 value_data: Some(api::v1::value::ValueData::I64Value(ts)),
2242 },
2243 api::v1::Value {
2244 value_data: v.map(api::v1::value::ValueData::F64Value),
2245 },
2246 ],
2247 })
2248 .collect();
2249
2250 let mutation = api::v1::Mutation {
2251 op_type: 1,
2252 sequence,
2253 rows: Some(api::v1::Rows {
2254 schema: column_schema,
2255 rows,
2256 }),
2257 write_hint: Some(WriteHint {
2258 primary_key_encoding: api::v1::PrimaryKeyEncoding::Sparse.into(),
2259 }),
2260 };
2261 KeyValues::new(metadata.as_ref(), mutation).unwrap()
2262 }
2263
2264 #[test]
2265 fn test_bulk_part_converter_sparse_primary_key_encoding() {
2266 use api::v1::SemanticType;
2267 use datatypes::schema::ColumnSchema;
2268 use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder};
2269 use store_api::storage::RegionId;
2270
2271 let mut builder = RegionMetadataBuilder::new(RegionId::new(123, 456));
2272 builder
2273 .push_column_metadata(ColumnMetadata {
2274 column_schema: ColumnSchema::new("k0", ConcreteDataType::string_datatype(), false),
2275 semantic_type: SemanticType::Tag,
2276 column_id: 0,
2277 })
2278 .push_column_metadata(ColumnMetadata {
2279 column_schema: ColumnSchema::new("k1", ConcreteDataType::string_datatype(), false),
2280 semantic_type: SemanticType::Tag,
2281 column_id: 1,
2282 })
2283 .push_column_metadata(ColumnMetadata {
2284 column_schema: ColumnSchema::new(
2285 "ts",
2286 ConcreteDataType::timestamp_millisecond_datatype(),
2287 false,
2288 ),
2289 semantic_type: SemanticType::Timestamp,
2290 column_id: 2,
2291 })
2292 .push_column_metadata(ColumnMetadata {
2293 column_schema: ColumnSchema::new("v0", ConcreteDataType::int64_datatype(), true),
2294 semantic_type: SemanticType::Field,
2295 column_id: 3,
2296 })
2297 .push_column_metadata(ColumnMetadata {
2298 column_schema: ColumnSchema::new("v1", ConcreteDataType::float64_datatype(), true),
2299 semantic_type: SemanticType::Field,
2300 column_id: 4,
2301 })
2302 .primary_key(vec![0, 1])
2303 .primary_key_encoding(PrimaryKeyEncoding::Sparse);
2304 let metadata = Arc::new(builder.build().unwrap());
2305
2306 let primary_key_codec = build_primary_key_codec(&metadata);
2307 let schema = to_flat_sst_arrow_schema(
2308 &metadata,
2309 &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
2310 );
2311
2312 assert_eq!(metadata.primary_key_encoding, PrimaryKeyEncoding::Sparse);
2313 assert_eq!(primary_key_codec.encoding(), PrimaryKeyEncoding::Sparse);
2314
2315 let capacity = 100;
2316 let mut converter =
2317 BulkPartConverter::new(&metadata, schema, capacity, primary_key_codec.clone(), true);
2318
2319 let key_values1 = build_key_values_with_sparse_encoding(
2320 &metadata,
2321 &primary_key_codec,
2322 2048u32, 100u64, "key11".to_string(),
2325 "key21".to_string(),
2326 vec![1000, 2000].into_iter(),
2327 vec![Some(1.0), Some(2.0)].into_iter(),
2328 1,
2329 );
2330
2331 let key_values2 = build_key_values_with_sparse_encoding(
2332 &metadata,
2333 &primary_key_codec,
2334 4096u32, 200u64, "key12".to_string(),
2337 "key22".to_string(),
2338 vec![1500].into_iter(),
2339 vec![Some(3.0)].into_iter(),
2340 2,
2341 );
2342
2343 converter.append_key_values(&key_values1).unwrap();
2344 converter.append_key_values(&key_values2).unwrap();
2345
2346 let bulk_part = converter.convert().unwrap();
2347
2348 assert_eq!(bulk_part.num_rows(), 3);
2349 assert_eq!(bulk_part.min_timestamp, 1000);
2350 assert_eq!(bulk_part.max_timestamp, 2000);
2351 assert_eq!(bulk_part.sequence, 2);
2352 assert_eq!(bulk_part.timestamp_index, bulk_part.batch.num_columns() - 4);
2353
2354 let schema = bulk_part.batch.schema();
2358 let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
2359 assert_eq!(
2360 field_names,
2361 vec!["v0", "v1", "ts", "__primary_key", "__sequence", "__op_type"]
2362 );
2363
2364 let primary_key_column = bulk_part.batch.column_by_name("__primary_key").unwrap();
2366 let dict_array = primary_key_column
2367 .as_any()
2368 .downcast_ref::<DictionaryArray<UInt32Type>>()
2369 .unwrap();
2370
2371 assert!(!dict_array.is_empty());
2373 assert_eq!(dict_array.len(), 3); let values = dict_array
2377 .values()
2378 .as_any()
2379 .downcast_ref::<BinaryArray>()
2380 .unwrap();
2381 for i in 0..values.len() {
2382 assert!(
2383 !values.value(i).is_empty(),
2384 "Encoded primary key should not be empty"
2385 );
2386 }
2387 }
2388
2389 #[test]
2390 fn test_convert_bulk_part_empty() {
2391 let metadata = metadata_for_test();
2392 let schema = to_flat_sst_arrow_schema(
2393 &metadata,
2394 &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
2395 );
2396 let primary_key_codec = build_primary_key_codec(&metadata);
2397
2398 let empty_batch = RecordBatch::new_empty(schema.clone());
2400 let empty_part = BulkPart {
2401 batch: empty_batch,
2402 max_timestamp: 0,
2403 min_timestamp: 0,
2404 sequence: 0,
2405 timestamp_index: 0,
2406 raw_data: None,
2407 };
2408
2409 let result =
2410 convert_bulk_part(empty_part, &metadata, primary_key_codec, schema, true).unwrap();
2411 assert!(result.is_none());
2412 }
2413
2414 #[test]
2415 fn test_convert_bulk_part_dense_with_pk_columns() {
2416 let metadata = metadata_for_test();
2417 let primary_key_codec = build_primary_key_codec(&metadata);
2418
2419 let k0_array = Arc::new(arrow::array::StringArray::from(vec![
2420 "key1", "key2", "key1",
2421 ]));
2422 let k1_array = Arc::new(arrow::array::UInt32Array::from(vec![1, 2, 1]));
2423 let v0_array = Arc::new(arrow::array::Int64Array::from(vec![100, 200, 300]));
2424 let v1_array = Arc::new(arrow::array::Float64Array::from(vec![1.0, 2.0, 3.0]));
2425 let ts_array = Arc::new(TimestampMillisecondArray::from(vec![1000, 2000, 1500]));
2426
2427 let input_schema = Arc::new(Schema::new(vec![
2428 Field::new("k0", ArrowDataType::Utf8, false),
2429 Field::new("k1", ArrowDataType::UInt32, false),
2430 Field::new("v0", ArrowDataType::Int64, true),
2431 Field::new("v1", ArrowDataType::Float64, true),
2432 Field::new(
2433 "ts",
2434 ArrowDataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None),
2435 false,
2436 ),
2437 ]));
2438
2439 let input_batch = RecordBatch::try_new(
2440 input_schema,
2441 vec![k0_array, k1_array, v0_array, v1_array, ts_array],
2442 )
2443 .unwrap();
2444
2445 let part = BulkPart {
2446 batch: input_batch,
2447 max_timestamp: 2000,
2448 min_timestamp: 1000,
2449 sequence: 5,
2450 timestamp_index: 4,
2451 raw_data: None,
2452 };
2453
2454 let output_schema = to_flat_sst_arrow_schema(
2455 &metadata,
2456 &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
2457 );
2458
2459 let result = convert_bulk_part(
2460 part,
2461 &metadata,
2462 primary_key_codec,
2463 output_schema,
2464 true, )
2466 .unwrap();
2467
2468 let converted = result.unwrap();
2469
2470 assert_eq!(converted.num_rows(), 3);
2471 assert_eq!(converted.max_timestamp, 2000);
2472 assert_eq!(converted.min_timestamp, 1000);
2473 assert_eq!(converted.sequence, 5);
2474
2475 let schema = converted.batch.schema();
2476 let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
2477 assert_eq!(
2478 field_names,
2479 vec![
2480 "k0",
2481 "k1",
2482 "v0",
2483 "v1",
2484 "ts",
2485 "__primary_key",
2486 "__sequence",
2487 "__op_type"
2488 ]
2489 );
2490
2491 let k0_col = converted.batch.column_by_name("k0").unwrap();
2492 assert!(matches!(
2493 k0_col.data_type(),
2494 ArrowDataType::Dictionary(_, _)
2495 ));
2496
2497 let pk_col = converted.batch.column_by_name("__primary_key").unwrap();
2498 let dict_array = pk_col
2499 .as_any()
2500 .downcast_ref::<DictionaryArray<UInt32Type>>()
2501 .unwrap();
2502 let keys = dict_array.keys();
2503
2504 assert_eq!(keys.len(), 3);
2505 }
2506
2507 #[test]
2508 fn test_convert_bulk_part_dense_without_pk_columns() {
2509 let metadata = metadata_for_test();
2510 let primary_key_codec = build_primary_key_codec(&metadata);
2511
2512 let k0_array = Arc::new(arrow::array::StringArray::from(vec!["key1", "key2"]));
2514 let k1_array = Arc::new(arrow::array::UInt32Array::from(vec![1, 2]));
2515 let v0_array = Arc::new(arrow::array::Int64Array::from(vec![100, 200]));
2516 let v1_array = Arc::new(arrow::array::Float64Array::from(vec![1.0, 2.0]));
2517 let ts_array = Arc::new(TimestampMillisecondArray::from(vec![1000, 2000]));
2518
2519 let input_schema = Arc::new(Schema::new(vec![
2520 Field::new("k0", ArrowDataType::Utf8, false),
2521 Field::new("k1", ArrowDataType::UInt32, false),
2522 Field::new("v0", ArrowDataType::Int64, true),
2523 Field::new("v1", ArrowDataType::Float64, true),
2524 Field::new(
2525 "ts",
2526 ArrowDataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None),
2527 false,
2528 ),
2529 ]));
2530
2531 let input_batch = RecordBatch::try_new(
2532 input_schema,
2533 vec![k0_array, k1_array, v0_array, v1_array, ts_array],
2534 )
2535 .unwrap();
2536
2537 let part = BulkPart {
2538 batch: input_batch,
2539 max_timestamp: 2000,
2540 min_timestamp: 1000,
2541 sequence: 3,
2542 timestamp_index: 4,
2543 raw_data: None,
2544 };
2545
2546 let output_schema = to_flat_sst_arrow_schema(
2547 &metadata,
2548 &FlatSchemaOptions {
2549 raw_pk_columns: false,
2550 string_pk_use_dict: true,
2551 ..Default::default()
2552 },
2553 );
2554
2555 let result = convert_bulk_part(
2556 part,
2557 &metadata,
2558 primary_key_codec,
2559 output_schema,
2560 false, )
2562 .unwrap();
2563
2564 let converted = result.unwrap();
2565
2566 assert_eq!(converted.num_rows(), 2);
2567 assert_eq!(converted.max_timestamp, 2000);
2568 assert_eq!(converted.min_timestamp, 1000);
2569 assert_eq!(converted.sequence, 3);
2570
2571 let schema = converted.batch.schema();
2573 let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
2574 assert_eq!(
2575 field_names,
2576 vec!["v0", "v1", "ts", "__primary_key", "__sequence", "__op_type"]
2577 );
2578
2579 let pk_col = converted.batch.column_by_name("__primary_key").unwrap();
2581 assert!(matches!(
2582 pk_col.data_type(),
2583 ArrowDataType::Dictionary(_, _)
2584 ));
2585 }
2586
2587 #[test]
2588 fn test_convert_bulk_part_sparse_encoding() {
2589 let mut builder = RegionMetadataBuilder::new(RegionId::new(123, 456));
2590 builder
2591 .push_column_metadata(ColumnMetadata {
2592 column_schema: ColumnSchema::new("k0", ConcreteDataType::string_datatype(), false),
2593 semantic_type: SemanticType::Tag,
2594 column_id: 0,
2595 })
2596 .push_column_metadata(ColumnMetadata {
2597 column_schema: ColumnSchema::new("k1", ConcreteDataType::string_datatype(), false),
2598 semantic_type: SemanticType::Tag,
2599 column_id: 1,
2600 })
2601 .push_column_metadata(ColumnMetadata {
2602 column_schema: ColumnSchema::new(
2603 "ts",
2604 ConcreteDataType::timestamp_millisecond_datatype(),
2605 false,
2606 ),
2607 semantic_type: SemanticType::Timestamp,
2608 column_id: 2,
2609 })
2610 .push_column_metadata(ColumnMetadata {
2611 column_schema: ColumnSchema::new("v0", ConcreteDataType::int64_datatype(), true),
2612 semantic_type: SemanticType::Field,
2613 column_id: 3,
2614 })
2615 .push_column_metadata(ColumnMetadata {
2616 column_schema: ColumnSchema::new("v1", ConcreteDataType::float64_datatype(), true),
2617 semantic_type: SemanticType::Field,
2618 column_id: 4,
2619 })
2620 .primary_key(vec![0, 1])
2621 .primary_key_encoding(PrimaryKeyEncoding::Sparse);
2622 let metadata = Arc::new(builder.build().unwrap());
2623
2624 let primary_key_codec = build_primary_key_codec(&metadata);
2625
2626 let pk_array = Arc::new(arrow::array::BinaryArray::from(vec![
2628 b"encoded_key_1".as_slice(),
2629 b"encoded_key_2".as_slice(),
2630 ]));
2631 let v0_array = Arc::new(arrow::array::Int64Array::from(vec![100, 200]));
2632 let v1_array = Arc::new(arrow::array::Float64Array::from(vec![1.0, 2.0]));
2633 let ts_array = Arc::new(TimestampMillisecondArray::from(vec![1000, 2000]));
2634
2635 let input_schema = Arc::new(Schema::new(vec![
2636 Field::new("__primary_key", ArrowDataType::Binary, false),
2637 Field::new("v0", ArrowDataType::Int64, true),
2638 Field::new("v1", ArrowDataType::Float64, true),
2639 Field::new(
2640 "ts",
2641 ArrowDataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None),
2642 false,
2643 ),
2644 ]));
2645
2646 let input_batch =
2647 RecordBatch::try_new(input_schema, vec![pk_array, v0_array, v1_array, ts_array])
2648 .unwrap();
2649
2650 let part = BulkPart {
2651 batch: input_batch,
2652 max_timestamp: 2000,
2653 min_timestamp: 1000,
2654 sequence: 7,
2655 timestamp_index: 3,
2656 raw_data: None,
2657 };
2658
2659 let output_schema = to_flat_sst_arrow_schema(
2660 &metadata,
2661 &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
2662 );
2663
2664 let result = convert_bulk_part(
2665 part,
2666 &metadata,
2667 primary_key_codec,
2668 output_schema,
2669 true, )
2671 .unwrap();
2672
2673 let converted = result.unwrap();
2674
2675 assert_eq!(converted.num_rows(), 2);
2676 assert_eq!(converted.max_timestamp, 2000);
2677 assert_eq!(converted.min_timestamp, 1000);
2678 assert_eq!(converted.sequence, 7);
2679
2680 let schema = converted.batch.schema();
2682 let field_names: Vec<&str> = schema.fields().iter().map(|f| f.name().as_str()).collect();
2683 assert_eq!(
2684 field_names,
2685 vec!["v0", "v1", "ts", "__primary_key", "__sequence", "__op_type"]
2686 );
2687
2688 let pk_col = converted.batch.column_by_name("__primary_key").unwrap();
2690 assert!(matches!(
2691 pk_col.data_type(),
2692 ArrowDataType::Dictionary(_, _)
2693 ));
2694 }
2695
2696 #[test]
2697 fn test_convert_bulk_part_sorting_with_multiple_series() {
2698 let metadata = metadata_for_test();
2699 let primary_key_codec = build_primary_key_codec(&metadata);
2700
2701 let k0_array = Arc::new(arrow::array::StringArray::from(vec![
2703 "series_b", "series_a", "series_b", "series_a",
2704 ]));
2705 let k1_array = Arc::new(arrow::array::UInt32Array::from(vec![2, 1, 2, 1]));
2706 let v0_array = Arc::new(arrow::array::Int64Array::from(vec![200, 100, 400, 300]));
2707 let v1_array = Arc::new(arrow::array::Float64Array::from(vec![2.0, 1.0, 4.0, 3.0]));
2708 let ts_array = Arc::new(TimestampMillisecondArray::from(vec![
2709 2000, 1000, 4000, 3000,
2710 ]));
2711
2712 let input_schema = Arc::new(Schema::new(vec![
2713 Field::new("k0", ArrowDataType::Utf8, false),
2714 Field::new("k1", ArrowDataType::UInt32, false),
2715 Field::new("v0", ArrowDataType::Int64, true),
2716 Field::new("v1", ArrowDataType::Float64, true),
2717 Field::new(
2718 "ts",
2719 ArrowDataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None),
2720 false,
2721 ),
2722 ]));
2723
2724 let input_batch = RecordBatch::try_new(
2725 input_schema,
2726 vec![k0_array, k1_array, v0_array, v1_array, ts_array],
2727 )
2728 .unwrap();
2729
2730 let part = BulkPart {
2731 batch: input_batch,
2732 max_timestamp: 4000,
2733 min_timestamp: 1000,
2734 sequence: 10,
2735 timestamp_index: 4,
2736 raw_data: None,
2737 };
2738
2739 let output_schema = to_flat_sst_arrow_schema(
2740 &metadata,
2741 &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
2742 );
2743
2744 let result =
2745 convert_bulk_part(part, &metadata, primary_key_codec, output_schema, true).unwrap();
2746
2747 let converted = result.unwrap();
2748
2749 assert_eq!(converted.num_rows(), 4);
2750
2751 let ts_col = converted.batch.column(converted.timestamp_index);
2753 let ts_array = ts_col
2754 .as_any()
2755 .downcast_ref::<TimestampMillisecondArray>()
2756 .unwrap();
2757
2758 let timestamps: Vec<i64> = ts_array.values().to_vec();
2762 assert_eq!(timestamps, vec![1000, 3000, 2000, 4000]);
2763 }
2764
2765 fn build_converted_bulk_part(inputs: &[MutationInput]) -> BulkPart {
2767 let metadata = metadata_for_test();
2768 let kvs = inputs
2769 .iter()
2770 .map(|m| {
2771 build_key_values_with_ts_seq_values(
2772 &metadata,
2773 m.k0.to_string(),
2774 m.k1,
2775 m.timestamps.iter().copied(),
2776 m.v1.iter().copied(),
2777 m.sequence,
2778 )
2779 })
2780 .collect::<Vec<_>>();
2781 let schema = to_flat_sst_arrow_schema(&metadata, &FlatSchemaOptions::default());
2782 let primary_key_codec = build_primary_key_codec(&metadata);
2783 let mut converter = BulkPartConverter::new(&metadata, schema, 64, primary_key_codec, true);
2784 for kv in kvs {
2785 converter.append_key_values(&kv).unwrap();
2786 }
2787 converter.convert().unwrap()
2788 }
2789
2790 fn build_multi_bulk_part(groups: &[&[MutationInput]]) -> (MultiBulkPart, RegionMetadataRef) {
2792 let metadata = metadata_for_test();
2793 let mut all_batches = Vec::new();
2794 let mut min_ts = i64::MAX;
2795 let mut max_ts = i64::MIN;
2796 let mut max_seq = 0u64;
2797
2798 for inputs in groups {
2799 let part = build_converted_bulk_part(inputs);
2800 min_ts = min_ts.min(part.min_timestamp);
2801 max_ts = max_ts.max(part.max_timestamp);
2802 max_seq = max_seq.max(part.sequence);
2803 all_batches.push(part.batch);
2804 }
2805
2806 let multi = MultiBulkPart::new(
2807 all_batches,
2808 min_ts,
2809 max_ts,
2810 max_seq,
2811 groups.len(),
2812 &metadata,
2813 );
2814 (multi, metadata)
2815 }
2816
2817 #[test]
2818 fn test_multi_bulk_part_prune_batches() {
2819 let (multi, metadata) = build_multi_bulk_part(&[
2821 &[MutationInput {
2822 k0: "a",
2823 k1: 0,
2824 timestamps: &[1, 2],
2825 v1: &[Some(1.0), Some(2.0)],
2826 sequence: 0,
2827 }],
2828 &[MutationInput {
2829 k0: "m",
2830 k1: 0,
2831 timestamps: &[3, 4],
2832 v1: &[Some(3.0), Some(4.0)],
2833 sequence: 1,
2834 }],
2835 &[MutationInput {
2836 k0: "z",
2837 k1: 0,
2838 timestamps: &[5, 6],
2839 v1: &[Some(5.0), Some(6.0)],
2840 sequence: 2,
2841 }],
2842 ]);
2843 assert_eq!(multi.num_rows(), 6);
2844 assert_eq!(multi.num_batches(), 3);
2845
2846 let context = Arc::new(
2848 BulkIterContext::new(
2849 metadata.clone(),
2850 None,
2851 Some(Predicate::new(vec![
2852 datafusion_expr::col("k0").eq(datafusion_expr::lit("m")),
2853 ])),
2854 false,
2855 crate::sst::parquet::DEFAULT_READ_BATCH_SIZE,
2856 )
2857 .unwrap(),
2858 );
2859 let reader = multi
2860 .read(context, None, None)
2861 .unwrap()
2862 .expect("should have results");
2863 let total_rows: usize = reader.map(|r| r.unwrap().num_rows()).sum();
2864 assert_eq!(total_rows, 2);
2865
2866 let context = Arc::new(
2868 BulkIterContext::new(
2869 metadata.clone(),
2870 None,
2871 Some(Predicate::new(vec![
2872 datafusion_expr::col("k0").eq(datafusion_expr::lit("nonexistent")),
2873 ])),
2874 false,
2875 crate::sst::parquet::DEFAULT_READ_BATCH_SIZE,
2876 )
2877 .unwrap(),
2878 );
2879 assert!(multi.read(context, None, None).unwrap().is_none());
2880
2881 let context = Arc::new(
2883 BulkIterContext::new(
2884 metadata.clone(),
2885 None,
2886 None,
2887 false,
2888 crate::sst::parquet::DEFAULT_READ_BATCH_SIZE,
2889 )
2890 .unwrap(),
2891 );
2892 let reader = multi
2893 .read(context, None, None)
2894 .unwrap()
2895 .expect("should have results");
2896 let total_rows: usize = reader.map(|r| r.unwrap().num_rows()).sum();
2897 assert_eq!(total_rows, 6);
2898 }
2899}