Skip to main content

mito2/memtable/
bulk.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Memtable implementation for bulk load
16
17pub(crate) mod chunk_reader;
18pub mod context;
19pub mod part;
20pub mod part_reader;
21mod row_group_reader;
22
23use std::collections::{BTreeMap, HashSet};
24use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, AtomicUsize, Ordering};
25use std::sync::{Arc, LazyLock, Mutex, RwLock};
26use std::time::Instant;
27
28/// Reads an environment variable as usize, returning default if not set or invalid.
29fn env_usize(name: &str, default: usize) -> usize {
30    std::env::var(name)
31        .ok()
32        .and_then(|v| v.parse().ok())
33        .unwrap_or(default)
34}
35
36use common_time::Timestamp;
37use datatypes::arrow::datatypes::SchemaRef;
38use mito_codec::key_values::KeyValue;
39use rayon::prelude::*;
40use serde::{Deserialize, Serialize};
41use serde_with::{DisplayFromStr, serde_as};
42use store_api::metadata::RegionMetadataRef;
43use store_api::mito_engine_options::FloatFieldEncoding;
44use store_api::storage::{ColumnId, FileId, RegionId, SequenceNumber, SequenceRange};
45use tokio::sync::Semaphore;
46
47use crate::compaction::{
48    Json2RewritePlans, collect_json2_rewrite_plans, rewrite_json2_batch, rewrite_json2_schema,
49};
50use crate::error::{Result, UnsupportedOperationSnafu};
51use crate::flush::WriteBufferManagerRef;
52use crate::memtable::bulk::context::BulkIterContext;
53use crate::memtable::bulk::part::{
54    BulkPart, BulkPartEncodeMetrics, BulkPartEncoder, MultiBulkPart, UnorderedPart,
55    should_prune_bulk_part,
56};
57use crate::memtable::bulk::part_reader::BulkPartBatchIter;
58use crate::memtable::stats::WriteMetrics;
59use crate::memtable::{
60    AllocTracker, BoxedBatchIterator, BoxedRecordBatchIterator, EncodedBulkPart, EncodedRange,
61    IterBuilder, KeyValues, MemScanMetrics, Memtable, MemtableBuilder, MemtableId, MemtableRange,
62    MemtableRangeContext, MemtableRanges, MemtableRef, MemtableStats, RangesOptions,
63    group_by_sequence,
64};
65use crate::read::flat_dedup::{FlatDedupIterator, FlatLastNonNull, FlatLastRow};
66use crate::read::flat_merge::FlatMergeIterator;
67use crate::region::options::MergeMode;
68use crate::sst::parquet::DEFAULT_ROW_GROUP_SIZE;
69use crate::sst::parquet::flat_format::field_column_start;
70
71/// Default merge threshold for triggering compaction.
72const DEFAULT_MERGE_THRESHOLD: usize = 16;
73
74/// Threshold for triggering merge of parts. Configurable via `GREPTIME_BULK_MERGE_THRESHOLD`.
75static MERGE_THRESHOLD: LazyLock<usize> =
76    LazyLock::new(|| env_usize("GREPTIME_BULK_MERGE_THRESHOLD", DEFAULT_MERGE_THRESHOLD));
77
78/// Default maximum number of groups for parallel merging.
79const DEFAULT_MAX_MERGE_GROUPS: usize = 32;
80
81/// Maximum merge groups. Configurable via `GREPTIME_BULK_MAX_MERGE_GROUPS`.
82static MAX_MERGE_GROUPS: LazyLock<usize> =
83    LazyLock::new(|| env_usize("GREPTIME_BULK_MAX_MERGE_GROUPS", DEFAULT_MAX_MERGE_GROUPS));
84
85/// Row threshold for encoding parts. Configurable via `GREPTIME_BULK_ENCODE_ROW_THRESHOLD`.
86/// When estimated rows exceed this threshold, parts are encoded as EncodedBulkPart.
87pub(crate) static ENCODE_ROW_THRESHOLD: LazyLock<usize> = LazyLock::new(|| {
88    env_usize(
89        "GREPTIME_BULK_ENCODE_ROW_THRESHOLD",
90        10 * DEFAULT_ROW_GROUP_SIZE,
91    )
92});
93
94/// Default bytes threshold for encoding.
95const DEFAULT_ENCODE_BYTES_THRESHOLD: usize = 64 * 1024 * 1024;
96
97/// Maximum bytes threshold for encoding when adapting to the write buffer size.
98const MAX_ENCODE_BYTES_THRESHOLD: usize = 512 * 1024 * 1024;
99
100/// Divisor to derive the encode bytes threshold from the global write buffer size.
101const ENCODE_BYTES_THRESHOLD_DIVISOR: usize = 32;
102
103/// Optional bytes threshold override from `GREPTIME_BULK_ENCODE_BYTES_THRESHOLD`.
104static ENCODE_BYTES_THRESHOLD_OVERRIDE: LazyLock<Option<usize>> = LazyLock::new(|| {
105    std::env::var("GREPTIME_BULK_ENCODE_BYTES_THRESHOLD")
106        .ok()
107        .and_then(|v| v.parse().ok())
108});
109
110/// Computes the encode bytes threshold adapted to the global write buffer size:
111/// `max(64 MiB, min(global_write_buffer_size / 32, 512 MiB))`.
112fn adaptive_encode_bytes_threshold(global_write_buffer_bytes: usize) -> usize {
113    (global_write_buffer_bytes / ENCODE_BYTES_THRESHOLD_DIVISOR)
114        .clamp(DEFAULT_ENCODE_BYTES_THRESHOLD, MAX_ENCODE_BYTES_THRESHOLD)
115}
116
117/// Returns the default bytes threshold for encoding parts.
118fn default_encode_bytes_threshold() -> usize {
119    ENCODE_BYTES_THRESHOLD_OVERRIDE.unwrap_or(DEFAULT_ENCODE_BYTES_THRESHOLD)
120}
121
122/// Configuration for bulk memtable.
123#[serde_as]
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
125#[serde(default)]
126pub struct BulkMemtableConfig {
127    /// Threshold for triggering merge of parts.
128    #[serde_as(as = "DisplayFromStr")]
129    pub merge_threshold: usize,
130    /// Row threshold for encoding parts.
131    #[serde_as(as = "DisplayFromStr")]
132    pub encode_row_threshold: usize,
133    /// Bytes threshold for encoding parts.
134    #[serde_as(as = "DisplayFromStr")]
135    pub encode_bytes_threshold: usize,
136    /// Maximum number of groups for parallel merging.
137    #[serde_as(as = "DisplayFromStr")]
138    pub max_merge_groups: usize,
139}
140
141impl Default for BulkMemtableConfig {
142    fn default() -> Self {
143        Self {
144            merge_threshold: *MERGE_THRESHOLD,
145            encode_row_threshold: *ENCODE_ROW_THRESHOLD,
146            encode_bytes_threshold: default_encode_bytes_threshold(),
147            max_merge_groups: *MAX_MERGE_GROUPS,
148        }
149        .sanitize()
150    }
151}
152
153impl BulkMemtableConfig {
154    /// Returns the default config adapted to `global_write_buffer_bytes`.
155    pub(crate) fn default_for_write_buffer_size(global_write_buffer_bytes: usize) -> Self {
156        Self {
157            encode_bytes_threshold: ENCODE_BYTES_THRESHOLD_OVERRIDE
158                .unwrap_or_else(|| adaptive_encode_bytes_threshold(global_write_buffer_bytes)),
159            ..Default::default()
160        }
161    }
162
163    fn sanitize(mut self) -> Self {
164        if self.merge_threshold == 0 {
165            self.merge_threshold = DEFAULT_MERGE_THRESHOLD;
166        }
167        self
168    }
169}
170
171/// Result of merging parts - either a MultiBulkPart or an EncodedBulkPart
172enum MergedPart {
173    /// Merged part stored as MultiBulkPart (when rows < DEFAULT_ROW_GROUP_SIZE)
174    Multi(MultiBulkPart),
175    /// Merged part stored as EncodedBulkPart (when rows >= DEFAULT_ROW_GROUP_SIZE)
176    Encoded(EncodedBulkPart),
177}
178
179/// Result of collecting parts to merge
180struct CollectedParts {
181    /// Consecutive groups of parts ready for merging.
182    groups: Vec<Vec<PartToMerge>>,
183}
184
185/// All parts in a bulk memtable.
186#[derive(Default)]
187struct BulkParts {
188    /// Unordered small parts.
189    unordered_part: UnorderedPart,
190    /// All parts (raw and encoded).
191    parts: Vec<BulkPartWrapper>,
192}
193
194impl BulkParts {
195    /// Total number of parts (including unordered).
196    fn num_parts(&self) -> usize {
197        let unordered_count = if self.unordered_part.is_empty() { 0 } else { 1 };
198        self.parts.len() + unordered_count
199    }
200
201    /// Returns true if there is no part.
202    fn is_empty(&self) -> bool {
203        self.unordered_part.is_empty() && self.parts.is_empty()
204    }
205
206    /// Returns a conservative scheduling hint without sorting or allocating.
207    /// The picker checks sequence barriers and sizes when compaction runs.
208    fn may_merge_parts(&self, merge_threshold: usize) -> bool {
209        let merge_threshold = merge_threshold.max(2);
210        self.parts.len() >= merge_threshold
211            && self
212                .parts
213                .iter()
214                .filter(|wrapper| !wrapper.merging)
215                .take(merge_threshold)
216                .count()
217                == merge_threshold
218    }
219
220    fn is_merge_candidate_by_size(
221        is_encoded: bool,
222        estimated_size: usize,
223        encode_bytes_threshold: usize,
224    ) -> bool {
225        !is_encoded || estimated_size <= encode_bytes_threshold
226    }
227
228    /// Returns true if the unordered_part should be compacted into a BulkPart.
229    fn should_compact_unordered_part(&self, encode_bytes_threshold: usize) -> bool {
230        self.unordered_part.should_compact()
231            || self.unordered_part.estimated_bytes() > encode_bytes_threshold
232    }
233
234    /// Converts buffered small writes into a sorted part once a threshold is reached.
235    fn seal_unordered(&mut self, metadata: &RegionMetadataRef) -> Result<()> {
236        if let Some(part) = self.unordered_part.to_bulk_part(metadata)? {
237            self.parts.push(BulkPartWrapper {
238                part: PartToMerge::Bulk {
239                    part,
240                    file_id: FileId::random(),
241                },
242                merging: false,
243            });
244            self.unordered_part.clear();
245        }
246        Ok(())
247    }
248
249    /// Selects consecutive sequence components without crossing unordered, busy,
250    /// or oversized parts. Overlapping components are indivisible, even above the size limit.
251    fn pick_merge_groups(
252        &self,
253        merge_threshold: usize,
254        max_merge_groups: usize,
255        encode_bytes_threshold: usize,
256    ) -> Vec<Vec<usize>> {
257        if self.parts.len() < merge_threshold.max(2) || max_merge_groups == 0 {
258            return Vec::new();
259        }
260        // The unordered interval participates in overlap grouping as a barrier,
261        // so neither overlapping parts nor parts on opposite sides can be merged.
262        let intervals = self
263            .parts
264            .iter()
265            .enumerate()
266            .map(|(index, wrapper)| {
267                let part = &wrapper.part;
268                (Some(index), (part.min_sequence(), part.max_sequence()))
269            })
270            .chain(
271                self.unordered_part
272                    .sequence_bounds()
273                    .map(|bounds| (None, bounds)),
274            )
275            .collect();
276        let components = group_by_sequence(intervals, |(_, bounds)| *bounds);
277        let mut groups = Vec::new();
278        let mut pending = Vec::new();
279        for component in components {
280            if groups.len() >= max_merge_groups {
281                break;
282            }
283            let Some(component) = component
284                .into_iter()
285                .map(|(index, _)| index)
286                .collect::<Option<Vec<_>>>()
287            else {
288                pending.clear();
289                continue;
290            };
291            let busy = component.iter().any(|&i| self.parts[i].merging);
292            let oversized = component.len() == 1 && {
293                let part = &self.parts[component[0]].part;
294                !Self::is_merge_candidate_by_size(
295                    part.is_encoded(),
296                    part.estimated_size(),
297                    encode_bytes_threshold,
298                )
299            };
300            if busy || oversized {
301                pending.clear();
302                continue;
303            }
304            pending.extend(component);
305            if pending.len() >= merge_threshold.max(2) {
306                groups.push(std::mem::take(&mut pending));
307            }
308        }
309        groups
310    }
311
312    fn collect_parts_to_merge(
313        &mut self,
314        merge_threshold: usize,
315        max_merge_groups: usize,
316        encode_bytes_threshold: usize,
317    ) -> CollectedParts {
318        let groups = self
319            .pick_merge_groups(merge_threshold, max_merge_groups, encode_bytes_threshold)
320            .into_iter()
321            .map(|group| {
322                group
323                    .into_iter()
324                    .map(|i| {
325                        let wrapper = &mut self.parts[i];
326                        wrapper.merging = true;
327                        wrapper.part.clone()
328                    })
329                    .collect()
330            })
331            .collect();
332        CollectedParts { groups }
333    }
334
335    /// Installs merged parts and removes the original parts by file ids.
336    /// Returns the total number of rows in the merged parts.
337    fn install_merged_parts<I>(
338        &mut self,
339        merged_parts: I,
340        merged_file_ids: &HashSet<FileId>,
341    ) -> usize
342    where
343        I: IntoIterator<Item = MergedPart>,
344    {
345        let mut total_output_rows = 0;
346
347        for merged_part in merged_parts {
348            match merged_part {
349                MergedPart::Encoded(encoded_part) => {
350                    total_output_rows += encoded_part.metadata().num_rows;
351                    self.parts.push(BulkPartWrapper {
352                        part: PartToMerge::Encoded {
353                            part: encoded_part,
354                            file_id: FileId::random(),
355                        },
356                        merging: false,
357                    });
358                }
359                MergedPart::Multi(multi_part) => {
360                    total_output_rows += multi_part.num_rows();
361                    self.parts.push(BulkPartWrapper {
362                        part: PartToMerge::Multi {
363                            part: multi_part,
364                            file_id: FileId::random(),
365                        },
366                        merging: false,
367                    });
368                }
369            }
370        }
371
372        self.parts
373            .retain(|wrapper| !merged_file_ids.contains(&wrapper.file_id()));
374
375        total_output_rows
376    }
377
378    /// Resets merging flag for parts with the given file ids.
379    /// Used when merging fails or is cancelled.
380    fn reset_merging_flags(&mut self, file_ids: &HashSet<FileId>) {
381        for wrapper in &mut self.parts {
382            if file_ids.contains(&wrapper.file_id()) {
383                wrapper.merging = false;
384            }
385        }
386    }
387}
388
389/// RAII guard for managing merging flags.
390/// Automatically resets merging flags when dropped if the merge operation wasn't successful.
391struct MergingFlagsGuard<'a> {
392    bulk_parts: &'a RwLock<BulkParts>,
393    file_ids: &'a HashSet<FileId>,
394    success: bool,
395}
396
397impl<'a> MergingFlagsGuard<'a> {
398    /// Creates a new guard for the given file ids.
399    fn new(bulk_parts: &'a RwLock<BulkParts>, file_ids: &'a HashSet<FileId>) -> Self {
400        Self {
401            bulk_parts,
402            file_ids,
403            success: false,
404        }
405    }
406
407    /// Marks the merge operation as successful.
408    /// When this is called, the guard will not reset the flags on drop.
409    fn mark_success(&mut self) {
410        self.success = true;
411    }
412}
413
414impl<'a> Drop for MergingFlagsGuard<'a> {
415    fn drop(&mut self) {
416        if !self.success
417            && let Ok(mut parts) = self.bulk_parts.write()
418        {
419            parts.reset_merging_flags(self.file_ids);
420        }
421    }
422}
423
424/// Memtable that ingests and scans parts directly.
425pub struct BulkMemtable {
426    id: MemtableId,
427    /// Configuration for the bulk memtable.
428    config: BulkMemtableConfig,
429    parts: Arc<RwLock<BulkParts>>,
430    metadata: RegionMetadataRef,
431    alloc_tracker: AllocTracker,
432    max_timestamp: AtomicI64,
433    min_timestamp: AtomicI64,
434    max_sequence: AtomicU64,
435    min_sequence: AtomicU64,
436    num_rows: AtomicUsize,
437    /// Compactor for merging bulk parts
438    compactor: Arc<Mutex<MemtableCompactor>>,
439    /// Dispatcher for scheduling compaction tasks
440    compact_dispatcher: Option<Arc<CompactDispatcher>>,
441    /// Whether a write-triggered compaction is queued or running.
442    compacting: Arc<AtomicBool>,
443    /// Whether the append mode is enabled
444    append_mode: bool,
445    /// Mode to handle duplicate rows while merging
446    merge_mode: MergeMode,
447    /// Max number of rows in a parquet row group for encoded parts.
448    row_group_size: usize,
449    float_field_encoding: FloatFieldEncoding,
450}
451
452impl std::fmt::Debug for BulkMemtable {
453    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454        f.debug_struct("BulkMemtable")
455            .field("id", &self.id)
456            .field("num_rows", &self.num_rows.load(Ordering::Relaxed))
457            .field("min_timestamp", &self.min_timestamp.load(Ordering::Relaxed))
458            .field("max_timestamp", &self.max_timestamp.load(Ordering::Relaxed))
459            .field("max_sequence", &self.max_sequence.load(Ordering::Relaxed))
460            .finish()
461    }
462}
463
464impl Memtable for BulkMemtable {
465    fn id(&self) -> MemtableId {
466        self.id
467    }
468
469    fn write(&self, _kvs: &KeyValues) -> Result<()> {
470        UnsupportedOperationSnafu {
471            err_msg: "write() is not supported for bulk memtable",
472        }
473        .fail()
474    }
475
476    fn write_one(&self, _key_value: KeyValue) -> Result<()> {
477        UnsupportedOperationSnafu {
478            err_msg: "write_one() is not supported for bulk memtable",
479        }
480        .fail()
481    }
482
483    fn write_bulk(&self, fragment: BulkPart) -> Result<()> {
484        let local_metrics = WriteMetrics {
485            key_bytes: 0,
486            value_bytes: fragment.estimated_size(),
487            min_ts: fragment.min_timestamp,
488            max_ts: fragment.max_timestamp,
489            num_rows: fragment.num_rows(),
490            max_sequence: fragment.sequence,
491            min_sequence: fragment.min_sequence,
492        };
493
494        {
495            let mut bulk_parts = self.parts.write().unwrap();
496
497            let fragment_size = fragment.estimated_size();
498            // Routes small parts to unordered_part based on row and byte thresholds.
499            if bulk_parts.unordered_part.should_accept(fragment.num_rows())
500                && fragment_size <= self.config.encode_bytes_threshold
501            {
502                bulk_parts.unordered_part.push(fragment);
503
504                // Compacts unordered_part if the row or byte threshold is exceeded.
505                if bulk_parts.should_compact_unordered_part(self.config.encode_bytes_threshold) {
506                    bulk_parts.seal_unordered(&self.metadata)?;
507                }
508            } else {
509                bulk_parts.parts.push(BulkPartWrapper {
510                    part: PartToMerge::Bulk {
511                        part: fragment,
512                        file_id: FileId::random(),
513                    },
514                    merging: false,
515                });
516            }
517
518            // Since this operation should be fast, we do it in parts lock scope.
519            // This ensure the statistics in `ranges()` are correct. What's more,
520            // it guarantees no rows are out of the time range so we don't need to
521            // prune rows by time range again in the iterator of the MemtableRange.
522            self.update_stats(local_metrics);
523        }
524
525        self.schedule_compact();
526
527        Ok(())
528    }
529
530    fn ranges(
531        &self,
532        projection: Option<&[ColumnId]>,
533        options: RangesOptions,
534    ) -> Result<MemtableRanges> {
535        let predicate = options.predicate;
536        let sequence = options.sequence;
537        let mut ranges = BTreeMap::new();
538        let mut range_id = 0;
539
540        // TODO(yingwen): Filter ranges by sequence.
541        let context = Arc::new(BulkIterContext::new_with_pre_filter_mode(
542            self.metadata.clone(),
543            projection,
544            predicate.predicate().cloned(),
545            options.for_flush,
546            options.pre_filter_mode,
547            options.batch_size,
548        )?);
549
550        // Adds ranges for regular parts and encoded parts
551        {
552            let bulk_parts = self.parts.read().unwrap();
553
554            // Adds range for unordered part if not empty
555            if !bulk_parts.unordered_part.is_empty()
556                && let Some(unordered_bulk_part) =
557                    bulk_parts.unordered_part.to_bulk_part(&self.metadata)?
558            {
559                let part_stats = unordered_bulk_part.to_memtable_stats(&self.metadata);
560                let range = MemtableRange::new(
561                    Arc::new(MemtableRangeContext::new(
562                        self.id,
563                        Box::new(BulkRangeIterBuilder {
564                            part: unordered_bulk_part,
565                            context: context.clone(),
566                            sequence,
567                        }),
568                        predicate.clone(),
569                    )),
570                    part_stats,
571                );
572                ranges.insert(range_id, range);
573                range_id += 1;
574            }
575
576            // Adds ranges for all parts
577            for part_wrapper in bulk_parts.parts.iter() {
578                // Skips empty parts
579                if part_wrapper.part.num_rows() == 0 {
580                    continue;
581                }
582
583                let part_stats = part_wrapper.part.to_memtable_stats(&self.metadata);
584                let iter_builder: Box<dyn IterBuilder> = match &part_wrapper.part {
585                    PartToMerge::Bulk { part, .. } => Box::new(BulkRangeIterBuilder {
586                        part: part.clone(),
587                        context: context.clone(),
588                        sequence,
589                    }),
590                    PartToMerge::Multi { part, .. } => Box::new(MultiBulkRangeIterBuilder {
591                        part: part.clone(),
592                        context: context.clone(),
593                        sequence,
594                    }),
595                    PartToMerge::Encoded { part, file_id } => {
596                        Box::new(EncodedBulkRangeIterBuilder {
597                            file_id: *file_id,
598                            part: part.clone(),
599                            context: context.clone(),
600                            sequence,
601                        })
602                    }
603                };
604
605                let range = MemtableRange::new(
606                    Arc::new(MemtableRangeContext::new(
607                        self.id,
608                        iter_builder,
609                        predicate.clone(),
610                    )),
611                    part_stats,
612                );
613                ranges.insert(range_id, range);
614                range_id += 1;
615            }
616        }
617
618        Ok(MemtableRanges { ranges })
619    }
620
621    fn is_empty(&self) -> bool {
622        let bulk_parts = self.parts.read().unwrap();
623        bulk_parts.is_empty()
624    }
625
626    fn freeze(&self) -> Result<()> {
627        self.alloc_tracker.done_allocating();
628        Ok(())
629    }
630
631    fn stats(&self) -> MemtableStats {
632        let estimated_bytes = self.alloc_tracker.bytes_allocated();
633
634        if estimated_bytes == 0 || self.num_rows.load(Ordering::Relaxed) == 0 {
635            return MemtableStats {
636                estimated_bytes,
637                time_range: None,
638                num_rows: 0,
639                num_ranges: 0,
640                max_sequence: 0,
641                min_sequence: 0,
642                series_count: 0,
643            };
644        }
645
646        let ts_type = self
647            .metadata
648            .time_index_column()
649            .column_schema
650            .data_type
651            .clone()
652            .as_timestamp()
653            .expect("Timestamp column must have timestamp type");
654        let max_timestamp = ts_type.create_timestamp(self.max_timestamp.load(Ordering::Relaxed));
655        let min_timestamp = ts_type.create_timestamp(self.min_timestamp.load(Ordering::Relaxed));
656
657        let num_ranges = self.parts.read().unwrap().num_parts();
658
659        MemtableStats {
660            estimated_bytes,
661            time_range: Some((min_timestamp, max_timestamp)),
662            num_rows: self.num_rows.load(Ordering::Relaxed),
663            num_ranges,
664            max_sequence: self.max_sequence.load(Ordering::Relaxed),
665            min_sequence: self.min_sequence.load(Ordering::Relaxed),
666            series_count: self.estimated_series_count(),
667        }
668    }
669
670    fn min_sequence(&self) -> SequenceNumber {
671        if self.alloc_tracker.bytes_allocated() == 0 || self.num_rows.load(Ordering::Relaxed) == 0 {
672            return 0;
673        }
674        self.min_sequence.load(Ordering::Relaxed)
675    }
676
677    fn fork(&self, id: MemtableId, metadata: &RegionMetadataRef) -> MemtableRef {
678        self.fork_inner(id, metadata)
679    }
680
681    fn compact(&self, for_flush: bool) -> Result<()> {
682        let mut compactor = self.compactor.lock().unwrap();
683
684        if for_flush {
685            return Ok(());
686        }
687
688        compactor.merge_parts(
689            &self.parts,
690            &self.metadata,
691            !self.append_mode,
692            self.merge_mode,
693        )
694    }
695}
696
697impl BulkMemtable {
698    fn fork_inner(&self, id: MemtableId, metadata: &RegionMetadataRef) -> Arc<BulkMemtable> {
699        Arc::new(Self {
700            id,
701            config: self.config.clone(),
702            parts: Arc::new(RwLock::new(BulkParts::default())),
703            metadata: metadata.clone(),
704            alloc_tracker: AllocTracker::new(self.alloc_tracker.write_buffer_manager()),
705            max_timestamp: AtomicI64::new(i64::MIN),
706            min_timestamp: AtomicI64::new(i64::MAX),
707            max_sequence: AtomicU64::new(0),
708            min_sequence: AtomicU64::new(u64::MAX),
709            num_rows: AtomicUsize::new(0),
710            compactor: Arc::new(Mutex::new(MemtableCompactor::new(
711                metadata.region_id,
712                id,
713                self.config.clone(),
714                self.row_group_size,
715                self.float_field_encoding,
716            ))),
717            compact_dispatcher: self.compact_dispatcher.clone(),
718            compacting: Arc::new(AtomicBool::new(false)),
719            append_mode: self.append_mode,
720            merge_mode: self.merge_mode,
721            row_group_size: self.row_group_size,
722            float_field_encoding: self.float_field_encoding,
723        })
724    }
725
726    /// Creates a new BulkMemtable with the default row group size.
727    pub fn new(
728        id: MemtableId,
729        config: BulkMemtableConfig,
730        metadata: RegionMetadataRef,
731        write_buffer_manager: Option<WriteBufferManagerRef>,
732        compact_dispatcher: Option<Arc<CompactDispatcher>>,
733        append_mode: bool,
734        merge_mode: MergeMode,
735    ) -> Self {
736        Self::new_with_row_group_size(
737            id,
738            config,
739            metadata,
740            write_buffer_manager,
741            compact_dispatcher,
742            append_mode,
743            merge_mode,
744            DEFAULT_ROW_GROUP_SIZE,
745        )
746    }
747
748    /// Creates a new BulkMemtable with the given `row_group_size`.
749    #[allow(clippy::too_many_arguments)]
750    pub fn new_with_row_group_size(
751        id: MemtableId,
752        config: BulkMemtableConfig,
753        metadata: RegionMetadataRef,
754        write_buffer_manager: Option<WriteBufferManagerRef>,
755        compact_dispatcher: Option<Arc<CompactDispatcher>>,
756        append_mode: bool,
757        merge_mode: MergeMode,
758        row_group_size: usize,
759    ) -> Self {
760        Self::new_with_row_group_size_and_encoding(
761            id,
762            config,
763            metadata,
764            write_buffer_manager,
765            compact_dispatcher,
766            append_mode,
767            merge_mode,
768            row_group_size,
769            FloatFieldEncoding::default(),
770        )
771    }
772
773    /// Creates a new BulkMemtable with the given row group size and float encoding.
774    #[allow(clippy::too_many_arguments)]
775    fn new_with_row_group_size_and_encoding(
776        id: MemtableId,
777        config: BulkMemtableConfig,
778        metadata: RegionMetadataRef,
779        write_buffer_manager: Option<WriteBufferManagerRef>,
780        compact_dispatcher: Option<Arc<CompactDispatcher>>,
781        append_mode: bool,
782        merge_mode: MergeMode,
783        row_group_size: usize,
784        float_field_encoding: FloatFieldEncoding,
785    ) -> Self {
786        let config = config.sanitize();
787        let region_id = metadata.region_id;
788        Self {
789            id,
790            config: config.clone(),
791            parts: Arc::new(RwLock::new(BulkParts::default())),
792            metadata,
793            alloc_tracker: AllocTracker::new(write_buffer_manager),
794            max_timestamp: AtomicI64::new(i64::MIN),
795            min_timestamp: AtomicI64::new(i64::MAX),
796            max_sequence: AtomicU64::new(0),
797            min_sequence: AtomicU64::new(u64::MAX),
798            num_rows: AtomicUsize::new(0),
799            compactor: Arc::new(Mutex::new(MemtableCompactor::new(
800                region_id,
801                id,
802                config,
803                row_group_size,
804                float_field_encoding,
805            ))),
806            compact_dispatcher,
807            compacting: Arc::new(AtomicBool::new(false)),
808            append_mode,
809            merge_mode,
810            row_group_size,
811            float_field_encoding,
812        }
813    }
814
815    /// Sets the unordered part threshold (for testing).
816    #[cfg(test)]
817    pub fn set_unordered_part_threshold(&self, threshold: usize) {
818        self.parts
819            .write()
820            .unwrap()
821            .unordered_part
822            .set_threshold(threshold);
823    }
824
825    /// Sets the unordered part compact threshold (for testing).
826    #[cfg(test)]
827    pub fn set_unordered_part_compact_threshold(&self, compact_threshold: usize) {
828        self.parts
829            .write()
830            .unwrap()
831            .unordered_part
832            .set_compact_threshold(compact_threshold);
833    }
834
835    /// Updates memtable stats.
836    ///
837    /// Please update this inside the write lock scope.
838    fn update_stats(&self, stats: WriteMetrics) {
839        self.alloc_tracker
840            .on_allocation(stats.key_bytes + stats.value_bytes);
841
842        self.max_timestamp
843            .fetch_max(stats.max_ts, Ordering::Relaxed);
844        self.min_timestamp
845            .fetch_min(stats.min_ts, Ordering::Relaxed);
846        self.max_sequence
847            .fetch_max(stats.max_sequence, Ordering::Relaxed);
848        self.min_sequence
849            .fetch_min(stats.min_sequence, Ordering::Relaxed);
850        self.num_rows.fetch_add(stats.num_rows, Ordering::Relaxed);
851    }
852
853    /// Returns the estimated time series count.
854    fn estimated_series_count(&self) -> usize {
855        let bulk_parts = self.parts.read().unwrap();
856        bulk_parts
857            .parts
858            .iter()
859            .map(|part_wrapper| part_wrapper.part.series_count())
860            .sum()
861    }
862
863    /// Schedules at most one write-triggered compaction at a time.
864    fn schedule_compact(&self) {
865        if self.config.max_merge_groups == 0 || self.compacting.load(Ordering::Relaxed) {
866            return;
867        }
868        if !self
869            .parts
870            .read()
871            .unwrap()
872            .may_merge_parts(self.config.merge_threshold)
873        {
874            return;
875        }
876        let Some(guard) = CompactTaskGuard::try_new(self.compacting.clone()) else {
877            return;
878        };
879        if let Some(dispatcher) = &self.compact_dispatcher {
880            let task = MemCompactTask {
881                metadata: self.metadata.clone(),
882                parts: self.parts.clone(),
883                _guard: guard,
884                compactor: self.compactor.clone(),
885                append_mode: self.append_mode,
886                merge_mode: self.merge_mode,
887            };
888
889            dispatcher.dispatch_compact(task);
890        } else {
891            // Uses synchronous compaction if no dispatcher is available.
892            if let Err(e) = self.compact(false) {
893                common_telemetry::error!(e; "Failed to compact table");
894            }
895        }
896    }
897}
898
899/// Iterator builder for bulk range
900pub struct BulkRangeIterBuilder {
901    pub part: BulkPart,
902    pub context: Arc<BulkIterContext>,
903    pub sequence: Option<SequenceRange>,
904}
905
906/// Iterator builder for multi bulk range
907struct MultiBulkRangeIterBuilder {
908    part: MultiBulkPart,
909    context: Arc<BulkIterContext>,
910    sequence: Option<SequenceRange>,
911}
912
913impl IterBuilder for BulkRangeIterBuilder {
914    fn build(&self, _metrics: Option<MemScanMetrics>) -> Result<BoxedBatchIterator> {
915        UnsupportedOperationSnafu {
916            err_msg: "BatchIterator is not supported for bulk memtable",
917        }
918        .fail()
919    }
920
921    fn is_record_batch(&self) -> bool {
922        true
923    }
924
925    fn build_record_batch(
926        &self,
927        _time_range: Option<(Timestamp, Timestamp)>,
928        metrics: Option<MemScanMetrics>,
929    ) -> Result<BoxedRecordBatchIterator> {
930        let metadata = self.context.read_format().metadata();
931        if should_prune_bulk_part(&self.part.batch, &self.context, metadata) {
932            return Ok(Box::new(std::iter::empty()));
933        }
934
935        let series_count = self.part.estimated_series_count();
936        let iter = BulkPartBatchIter::from_single(
937            self.part.batch.clone(),
938            self.context.clone(),
939            self.sequence,
940            series_count,
941            metrics,
942        );
943
944        Ok(Box::new(iter))
945    }
946
947    fn record_batch_schema_hint(&self) -> Option<SchemaRef> {
948        Some(self.part.schema())
949    }
950
951    fn encoded_range(&self) -> Option<EncodedRange> {
952        None
953    }
954}
955
956impl IterBuilder for MultiBulkRangeIterBuilder {
957    fn build(&self, _metrics: Option<MemScanMetrics>) -> Result<BoxedBatchIterator> {
958        UnsupportedOperationSnafu {
959            err_msg: "BatchIterator is not supported for multi bulk memtable",
960        }
961        .fail()
962    }
963
964    fn is_record_batch(&self) -> bool {
965        true
966    }
967
968    fn build_record_batch(
969        &self,
970        _time_range: Option<(Timestamp, Timestamp)>,
971        metrics: Option<MemScanMetrics>,
972    ) -> Result<BoxedRecordBatchIterator> {
973        match self
974            .part
975            .read(self.context.clone(), self.sequence, metrics)?
976        {
977            Some(iter) => Ok(iter),
978            // All batches were pruned by the predicate. Return an empty iterator.
979            None => Ok(Box::new(std::iter::empty())),
980        }
981    }
982
983    fn record_batch_schema_hint(&self) -> Option<SchemaRef> {
984        self.part.schemas().next()
985    }
986
987    fn encoded_range(&self) -> Option<EncodedRange> {
988        None
989    }
990}
991
992/// Iterator builder for encoded bulk range
993struct EncodedBulkRangeIterBuilder {
994    file_id: FileId,
995    part: EncodedBulkPart,
996    context: Arc<BulkIterContext>,
997    sequence: Option<SequenceRange>,
998}
999
1000impl IterBuilder for EncodedBulkRangeIterBuilder {
1001    fn build(&self, _metrics: Option<MemScanMetrics>) -> Result<BoxedBatchIterator> {
1002        UnsupportedOperationSnafu {
1003            err_msg: "BatchIterator is not supported for encoded bulk memtable",
1004        }
1005        .fail()
1006    }
1007
1008    fn is_record_batch(&self) -> bool {
1009        true
1010    }
1011
1012    fn build_record_batch(
1013        &self,
1014        _time_range: Option<(Timestamp, Timestamp)>,
1015        metrics: Option<MemScanMetrics>,
1016    ) -> Result<BoxedRecordBatchIterator> {
1017        if let Some(iter) = self
1018            .part
1019            .read(self.context.clone(), self.sequence, metrics)?
1020        {
1021            Ok(iter)
1022        } else {
1023            // Return an empty iterator if no data to read
1024            Ok(Box::new(std::iter::empty()))
1025        }
1026    }
1027
1028    fn record_batch_schema_hint(&self) -> Option<SchemaRef> {
1029        Some(self.part.schema())
1030    }
1031
1032    fn encoded_range(&self) -> Option<EncodedRange> {
1033        Some(EncodedRange {
1034            data: self.part.data().clone(),
1035            sst_info: self.part.to_sst_info(self.file_id),
1036        })
1037    }
1038}
1039
1040struct BulkPartWrapper {
1041    /// The part to store. It already contains the file id.
1042    part: PartToMerge,
1043    /// Whether this part is currently being merged.
1044    merging: bool,
1045}
1046
1047impl BulkPartWrapper {
1048    /// Returns the file id of this part.
1049    fn file_id(&self) -> FileId {
1050        self.part.file_id()
1051    }
1052}
1053
1054/// Enum to wrap different types of parts for unified merging.
1055#[derive(Clone)]
1056enum PartToMerge {
1057    /// Raw bulk part.
1058    Bulk { part: BulkPart, file_id: FileId },
1059    /// Multiple bulk parts.
1060    Multi {
1061        part: MultiBulkPart,
1062        file_id: FileId,
1063    },
1064    /// Encoded bulk part.
1065    Encoded {
1066        part: EncodedBulkPart,
1067        file_id: FileId,
1068    },
1069}
1070
1071impl PartToMerge {
1072    /// Gets the file ID of this part.
1073    fn file_id(&self) -> FileId {
1074        match self {
1075            PartToMerge::Bulk { file_id, .. } => *file_id,
1076            PartToMerge::Multi { file_id, .. } => *file_id,
1077            PartToMerge::Encoded { file_id, .. } => *file_id,
1078        }
1079    }
1080
1081    /// Gets the minimum timestamp of this part.
1082    fn min_timestamp(&self) -> i64 {
1083        match self {
1084            PartToMerge::Bulk { part, .. } => part.min_timestamp,
1085            PartToMerge::Multi { part, .. } => part.min_timestamp(),
1086            PartToMerge::Encoded { part, .. } => part.metadata().min_timestamp,
1087        }
1088    }
1089
1090    /// Gets the maximum timestamp of this part.
1091    fn max_timestamp(&self) -> i64 {
1092        match self {
1093            PartToMerge::Bulk { part, .. } => part.max_timestamp,
1094            PartToMerge::Multi { part, .. } => part.max_timestamp(),
1095            PartToMerge::Encoded { part, .. } => part.metadata().max_timestamp,
1096        }
1097    }
1098
1099    /// Gets the number of rows in this part.
1100    fn num_rows(&self) -> usize {
1101        match self {
1102            PartToMerge::Bulk { part, .. } => part.num_rows(),
1103            PartToMerge::Multi { part, .. } => part.num_rows(),
1104            PartToMerge::Encoded { part, .. } => part.metadata().num_rows,
1105        }
1106    }
1107
1108    /// Returns the lower bound of input sequences.
1109    fn min_sequence(&self) -> SequenceNumber {
1110        match self {
1111            PartToMerge::Bulk { part, .. } => part.min_sequence,
1112            PartToMerge::Multi { part, .. } => part.min_sequence(),
1113            PartToMerge::Encoded { part, .. } => part.metadata().min_sequence,
1114        }
1115    }
1116
1117    /// Gets the maximum sequence number of this part.
1118    fn max_sequence(&self) -> u64 {
1119        match self {
1120            PartToMerge::Bulk { part, .. } => part.sequence,
1121            PartToMerge::Multi { part, .. } => part.max_sequence(),
1122            PartToMerge::Encoded { part, .. } => part.metadata().max_sequence,
1123        }
1124    }
1125
1126    /// Gets the estimated series count in this part.
1127    fn series_count(&self) -> usize {
1128        match self {
1129            PartToMerge::Bulk { part, .. } => part.estimated_series_count(),
1130            PartToMerge::Multi { part, .. } => part.series_count(),
1131            PartToMerge::Encoded { part, .. } => part.metadata().num_series as usize,
1132        }
1133    }
1134
1135    /// Returns true if this is an encoded part.
1136    fn is_encoded(&self) -> bool {
1137        matches!(self, PartToMerge::Encoded { .. })
1138    }
1139
1140    /// Gets the estimated size in bytes of this part.
1141    fn estimated_size(&self) -> usize {
1142        match self {
1143            PartToMerge::Bulk { part, .. } => part.estimated_size(),
1144            PartToMerge::Multi { part, .. } => part.estimated_size(),
1145            PartToMerge::Encoded { part, .. } => part.size_bytes(),
1146        }
1147    }
1148
1149    /// Returns `(num_rows, estimated_decoded_bytes)` for batch sizing.
1150    fn batch_size_statistic(&self) -> Option<(u64, u64)> {
1151        match self {
1152            PartToMerge::Bulk { part, .. } => {
1153                Some((part.num_rows() as u64, part.estimated_size() as u64))
1154            }
1155            PartToMerge::Multi { part, .. } => {
1156                Some((part.num_rows() as u64, part.estimated_size() as u64))
1157            }
1158            PartToMerge::Encoded { part, .. } => {
1159                crate::batch_size::largest_row_group_statistic(&part.metadata().parquet_metadata)
1160            }
1161        }
1162    }
1163
1164    /// Converts this part to `MemtableStats`.
1165    fn to_memtable_stats(&self, region_metadata: &RegionMetadataRef) -> MemtableStats {
1166        match self {
1167            PartToMerge::Bulk { part, .. } => part.to_memtable_stats(region_metadata),
1168            PartToMerge::Multi { part, .. } => part.to_memtable_stats(region_metadata),
1169            PartToMerge::Encoded { part, .. } => part.to_memtable_stats(),
1170        }
1171    }
1172
1173    /// Returns the Arrow schema of the record batches contained in this [`PartToMerge`].
1174    fn arrow_schema(&self) -> SchemaRef {
1175        match self {
1176            PartToMerge::Bulk { part, .. } => part.schema(),
1177            // A MultiBulkPart is built from batches that have already been aligned, so
1178            // all contained batches are expected to share the same arrow schema.
1179            PartToMerge::Multi { part, .. } => part
1180                .schemas()
1181                .next()
1182                .expect("MultiBulkPart must contain at least one record batch"),
1183            PartToMerge::Encoded { part, .. } => part.schema(),
1184        }
1185    }
1186
1187    /// Creates a record batch iterator for this part.
1188    fn create_iterator(
1189        self,
1190        context: Arc<BulkIterContext>,
1191        plans: Arc<Json2RewritePlans>,
1192    ) -> Result<Option<BoxedRecordBatchIterator>> {
1193        let iter = match self {
1194            PartToMerge::Bulk { part, .. } => {
1195                let series_count = part.estimated_series_count();
1196                let iter = BulkPartBatchIter::from_single(
1197                    part.batch,
1198                    context,
1199                    None, // No sequence filter for merging
1200                    series_count,
1201                    None, // No metrics for merging
1202                );
1203                Some(Box::new(iter) as BoxedRecordBatchIterator)
1204            }
1205            PartToMerge::Multi { part, .. } => part.read(context, None, None)?,
1206            PartToMerge::Encoded { part, .. } => part.read(context, None, None)?,
1207        };
1208        if plans.is_empty() {
1209            Ok(iter)
1210        } else {
1211            Ok(iter.map(|x| {
1212                Box::new(x.map(move |batch| rewrite_json2_batch(batch?, &plans)))
1213                    as BoxedRecordBatchIterator
1214            }))
1215        }
1216    }
1217}
1218
1219struct MemtableCompactor {
1220    region_id: RegionId,
1221    memtable_id: MemtableId,
1222    /// Configuration for the bulk memtable.
1223    config: BulkMemtableConfig,
1224    /// Max number of rows in a parquet row group for encoded parts.
1225    row_group_size: usize,
1226    float_field_encoding: FloatFieldEncoding,
1227}
1228
1229impl MemtableCompactor {
1230    /// Creates a new MemtableCompactor.
1231    fn new(
1232        region_id: RegionId,
1233        memtable_id: MemtableId,
1234        config: BulkMemtableConfig,
1235        row_group_size: usize,
1236        float_field_encoding: FloatFieldEncoding,
1237    ) -> Self {
1238        Self {
1239            region_id,
1240            memtable_id,
1241            config,
1242            row_group_size,
1243            float_field_encoding,
1244        }
1245    }
1246
1247    /// Merges parts (bulk and encoded) and then encodes the result.
1248    fn merge_parts(
1249        &mut self,
1250        bulk_parts: &RwLock<BulkParts>,
1251        metadata: &RegionMetadataRef,
1252        dedup: bool,
1253        merge_mode: MergeMode,
1254    ) -> Result<()> {
1255        let start = Instant::now();
1256
1257        let collected = {
1258            let mut parts = bulk_parts.write().unwrap();
1259            parts.collect_parts_to_merge(
1260                self.config.merge_threshold,
1261                self.config.max_merge_groups,
1262                self.config.encode_bytes_threshold,
1263            )
1264        };
1265
1266        if collected.groups.is_empty() {
1267            return Ok(());
1268        }
1269
1270        // Collect all file IDs for tracking
1271        let merged_file_ids: HashSet<FileId> = collected
1272            .groups
1273            .iter()
1274            .flatten()
1275            .map(|part| part.file_id())
1276            .collect();
1277        let mut guard = MergingFlagsGuard::new(bulk_parts, &merged_file_ids);
1278
1279        let num_groups = collected.groups.len();
1280        let num_parts: usize = collected.groups.iter().map(|g| g.len()).sum();
1281
1282        let encode_row_threshold = self.config.encode_row_threshold;
1283        let encode_bytes_threshold = self.config.encode_bytes_threshold;
1284        let row_group_size = self.row_group_size;
1285        let float_field_encoding = self.float_field_encoding;
1286
1287        // Merge all groups in parallel
1288        let merged_parts = collected
1289            .groups
1290            .into_par_iter()
1291            .map(|group| {
1292                Self::merge_parts_group(
1293                    group,
1294                    metadata,
1295                    dedup,
1296                    merge_mode,
1297                    encode_row_threshold,
1298                    encode_bytes_threshold,
1299                    row_group_size,
1300                    float_field_encoding,
1301                )
1302            })
1303            .collect::<Result<Vec<Option<MergedPart>>>>()?;
1304
1305        // Install all merged parts
1306        let total_output_rows = {
1307            let mut parts = bulk_parts.write().unwrap();
1308            parts.install_merged_parts(merged_parts.into_iter().flatten(), &merged_file_ids)
1309        };
1310
1311        guard.mark_success();
1312
1313        common_telemetry::debug!(
1314            "BulkMemtable {} {} concurrent compact {} groups, {} parts, {} rows, cost: {:?}",
1315            self.region_id,
1316            self.memtable_id,
1317            num_groups,
1318            num_parts,
1319            total_output_rows,
1320            start.elapsed()
1321        );
1322
1323        Ok(())
1324    }
1325
1326    /// Merges a group of parts into a single part (either MultiBulkPart or EncodedBulkPart).
1327    #[allow(clippy::too_many_arguments)]
1328    fn merge_parts_group(
1329        parts_to_merge: Vec<PartToMerge>,
1330        metadata: &RegionMetadataRef,
1331        dedup: bool,
1332        merge_mode: MergeMode,
1333        encode_row_threshold: usize,
1334        encode_bytes_threshold: usize,
1335        row_group_size: usize,
1336        float_field_encoding: FloatFieldEncoding,
1337    ) -> Result<Option<MergedPart>> {
1338        if parts_to_merge.is_empty() {
1339            return Ok(None);
1340        }
1341
1342        // Calculates timestamp bounds and statistics for merged data
1343        let min_timestamp = parts_to_merge
1344            .iter()
1345            .map(|p| p.min_timestamp())
1346            .min()
1347            .unwrap_or(i64::MAX);
1348        let max_timestamp = parts_to_merge
1349            .iter()
1350            .map(|p| p.max_timestamp())
1351            .max()
1352            .unwrap_or(i64::MIN);
1353        let min_sequence = parts_to_merge
1354            .iter()
1355            .map(PartToMerge::min_sequence)
1356            .min()
1357            .unwrap_or(0);
1358        let max_sequence = parts_to_merge
1359            .iter()
1360            .map(|p| p.max_sequence())
1361            .max()
1362            .unwrap_or(0);
1363
1364        // Collects statistics from parts before creating iterators
1365        let estimated_total_rows: usize = parts_to_merge.iter().map(|p| p.num_rows()).sum();
1366        let estimated_total_bytes: usize = parts_to_merge.iter().map(|p| p.estimated_size()).sum();
1367        let estimated_series_count = parts_to_merge
1368            .iter()
1369            .map(|p| p.series_count())
1370            .max()
1371            .unwrap_or(0);
1372
1373        let batch_size = crate::batch_size::estimate_batch_size(
1374            parts_to_merge
1375                .iter()
1376                .filter_map(PartToMerge::batch_size_statistic),
1377        );
1378        let context = Arc::new(BulkIterContext::new(
1379            metadata.clone(),
1380            None, // No column projection for merging
1381            None, // No predicate for merging
1382            true,
1383            batch_size,
1384        )?);
1385
1386        let schemas = parts_to_merge
1387            .iter()
1388            .map(|part| (part.arrow_schema(), part.num_rows() as u64))
1389            .collect::<Vec<_>>();
1390        let plans = Arc::new(collect_json2_rewrite_plans(metadata, &schemas)?);
1391
1392        debug_assert!(parts_to_merge.windows(2).all(|w| rewrite_json2_schema(
1393            &w[0].arrow_schema(),
1394            &plans
1395        ) == rewrite_json2_schema(
1396            &w[1].arrow_schema(),
1397            &plans
1398        )));
1399        // Parts in one merge group may differ only in their JSON2 physical layouts. So every source
1400        // schema is therefore a valid template for producing the final target schema that has
1401        // the union JSON2 types (rewritten).
1402        let schema = rewrite_json2_schema(&parts_to_merge[0].arrow_schema(), &plans);
1403
1404        let iterators: Vec<BoxedRecordBatchIterator> = parts_to_merge
1405            .into_iter()
1406            .map(|part| part.create_iterator(context.clone(), plans.clone()))
1407            .collect::<Result<Vec<_>>>()?
1408            .into_iter()
1409            .flatten()
1410            .collect();
1411
1412        if iterators.is_empty() {
1413            return Ok(None);
1414        }
1415
1416        let merged_iter = FlatMergeIterator::new(schema.clone(), iterators, batch_size)?;
1417
1418        let boxed_iter: BoxedRecordBatchIterator = if dedup {
1419            match merge_mode {
1420                MergeMode::LastRow => {
1421                    let dedup_iter = FlatDedupIterator::new(merged_iter, FlatLastRow::new(false));
1422                    Box::new(dedup_iter)
1423                }
1424                MergeMode::LastNonNull => {
1425                    let field_column_start = field_column_start(metadata, schema.fields().len());
1426
1427                    let dedup_iter = FlatDedupIterator::new(
1428                        merged_iter,
1429                        FlatLastNonNull::new(field_column_start, false),
1430                    );
1431                    Box::new(dedup_iter)
1432                }
1433            }
1434        } else {
1435            Box::new(merged_iter)
1436        };
1437
1438        // Encode as EncodedBulkPart if rows exceed row threshold OR bytes exceed bytes threshold
1439        if estimated_total_rows > encode_row_threshold
1440            || estimated_total_bytes > encode_bytes_threshold
1441        {
1442            let encoder = BulkPartEncoder::new_with_float_field_encoding(
1443                metadata.clone(),
1444                row_group_size,
1445                float_field_encoding,
1446            )?;
1447            let mut metrics = BulkPartEncodeMetrics::default();
1448            let encoded_part = encoder.encode_record_batch_iter(
1449                boxed_iter,
1450                schema,
1451                min_timestamp,
1452                max_timestamp,
1453                min_sequence..=max_sequence,
1454                &mut metrics,
1455            )?;
1456
1457            common_telemetry::trace!("merge_parts_group metrics: {:?}", metrics);
1458
1459            Ok(encoded_part.map(MergedPart::Encoded))
1460        } else {
1461            // Otherwise, collect into MultiBulkPart
1462            let mut batches = Vec::new();
1463            let mut actual_total_rows = 0;
1464
1465            for batch_result in boxed_iter {
1466                let batch = batch_result?;
1467                actual_total_rows += batch.num_rows();
1468                batches.push(batch);
1469            }
1470
1471            if actual_total_rows == 0 {
1472                return Ok(None);
1473            }
1474
1475            let multi_part = MultiBulkPart::new(
1476                batches,
1477                min_timestamp,
1478                max_timestamp,
1479                min_sequence,
1480                max_sequence,
1481                estimated_series_count,
1482                metadata,
1483            );
1484
1485            common_telemetry::trace!(
1486                "merge_parts_group created MultiBulkPart: rows={}, batches={}",
1487                actual_total_rows,
1488                multi_part.num_batches()
1489            );
1490
1491            Ok(Some(MergedPart::Multi(multi_part)))
1492        }
1493    }
1494}
1495
1496/// Keeps the compaction flag set while queued or running and clears it on drop,
1497/// including failures and cancellation while waiting for a dispatcher permit.
1498struct CompactTaskGuard(Arc<AtomicBool>);
1499
1500impl CompactTaskGuard {
1501    fn try_new(compacting: Arc<AtomicBool>) -> Option<Self> {
1502        // The compactor mutex protects merging; this flag only avoids duplicate tasks.
1503        compacting
1504            .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
1505            .ok()
1506            .map(|_| Self(compacting))
1507    }
1508}
1509
1510impl Drop for CompactTaskGuard {
1511    fn drop(&mut self) {
1512        self.0.store(false, Ordering::Relaxed);
1513    }
1514}
1515
1516/// A memtable compact task to run in background.
1517struct MemCompactTask {
1518    metadata: RegionMetadataRef,
1519    parts: Arc<RwLock<BulkParts>>,
1520    _guard: CompactTaskGuard,
1521    /// Compactor for merging bulk parts
1522    compactor: Arc<Mutex<MemtableCompactor>>,
1523    /// Whether the append mode is enabled
1524    append_mode: bool,
1525    /// Mode to handle duplicate rows while merging
1526    merge_mode: MergeMode,
1527}
1528
1529impl MemCompactTask {
1530    fn compact(&self) -> Result<()> {
1531        self.compactor.lock().unwrap().merge_parts(
1532            &self.parts,
1533            &self.metadata,
1534            !self.append_mode,
1535            self.merge_mode,
1536        )
1537    }
1538}
1539
1540/// Scheduler to run compact tasks in background.
1541#[derive(Debug)]
1542pub struct CompactDispatcher {
1543    semaphore: Arc<Semaphore>,
1544}
1545
1546impl CompactDispatcher {
1547    /// Creates a new dispatcher with the given number of max concurrent tasks.
1548    pub fn new(permits: usize) -> Self {
1549        Self {
1550            semaphore: Arc::new(Semaphore::new(permits)),
1551        }
1552    }
1553
1554    /// Dispatches a compact task to run in background.
1555    fn dispatch_compact(&self, task: MemCompactTask) {
1556        let semaphore = self.semaphore.clone();
1557        common_runtime::spawn_global(async move {
1558            let Ok(permit) = semaphore.acquire_owned().await else {
1559                return;
1560            };
1561
1562            common_runtime::spawn_blocking_global(move || {
1563                // The async task above returns as soon as the blocking task is
1564                // submitted, so the permit has to travel with it to bound the
1565                // compaction that actually runs.
1566                let _permit = permit;
1567                if let Err(e) = task.compact() {
1568                    common_telemetry::error!(e; "Failed to compact memtable, region: {}", task.metadata.region_id);
1569                }
1570            });
1571        });
1572    }
1573}
1574
1575/// Builder to build a [BulkMemtable].
1576#[derive(Debug)]
1577pub struct BulkMemtableBuilder {
1578    /// Configuration for the bulk memtable.
1579    config: BulkMemtableConfig,
1580    write_buffer_manager: Option<WriteBufferManagerRef>,
1581    compact_dispatcher: Option<Arc<CompactDispatcher>>,
1582    append_mode: bool,
1583    merge_mode: MergeMode,
1584    /// Max number of rows in a parquet row group for encoded parts.
1585    row_group_size: usize,
1586    float_field_encoding: FloatFieldEncoding,
1587}
1588
1589impl Default for BulkMemtableBuilder {
1590    fn default() -> Self {
1591        Self {
1592            config: BulkMemtableConfig::default(),
1593            write_buffer_manager: None,
1594            compact_dispatcher: None,
1595            append_mode: false,
1596            merge_mode: MergeMode::default(),
1597            row_group_size: DEFAULT_ROW_GROUP_SIZE,
1598            float_field_encoding: FloatFieldEncoding::default(),
1599        }
1600    }
1601}
1602
1603impl BulkMemtableBuilder {
1604    /// Creates a new builder with specific `write_buffer_manager`.
1605    pub fn new(
1606        write_buffer_manager: Option<WriteBufferManagerRef>,
1607        append_mode: bool,
1608        merge_mode: MergeMode,
1609    ) -> Self {
1610        Self {
1611            write_buffer_manager,
1612            append_mode,
1613            merge_mode,
1614            ..Default::default()
1615        }
1616    }
1617
1618    /// Sets the bulk memtable config.
1619    pub fn with_config(mut self, config: BulkMemtableConfig) -> Self {
1620        self.config = config;
1621        self
1622    }
1623
1624    /// Sets the max number of rows in a parquet row group for encoded parts.
1625    pub fn with_row_group_size(mut self, row_group_size: usize) -> Self {
1626        self.row_group_size = row_group_size;
1627        self
1628    }
1629
1630    pub(super) fn with_float_field_encoding(mut self, encoding: FloatFieldEncoding) -> Self {
1631        self.float_field_encoding = encoding;
1632        self
1633    }
1634
1635    /// Sets the compact dispatcher.
1636    pub fn with_compact_dispatcher(mut self, compact_dispatcher: Arc<CompactDispatcher>) -> Self {
1637        self.compact_dispatcher = Some(compact_dispatcher);
1638        self
1639    }
1640
1641    #[cfg(test)]
1642    pub(crate) fn config(&self) -> &BulkMemtableConfig {
1643        &self.config
1644    }
1645}
1646
1647impl MemtableBuilder for BulkMemtableBuilder {
1648    fn build(&self, id: MemtableId, metadata: &RegionMetadataRef) -> MemtableRef {
1649        Arc::new(BulkMemtable::new_with_row_group_size_and_encoding(
1650            id,
1651            self.config.clone(),
1652            metadata.clone(),
1653            self.write_buffer_manager.clone(),
1654            self.compact_dispatcher.clone(),
1655            self.append_mode,
1656            self.merge_mode,
1657            self.row_group_size,
1658            self.float_field_encoding,
1659        ))
1660    }
1661
1662    fn use_bulk_insert(&self, _metadata: &RegionMetadataRef) -> bool {
1663        true
1664    }
1665}
1666
1667#[cfg(test)]
1668mod tests {
1669    use api::helper::encode_json_value;
1670    use api::v1::value::ValueData;
1671    use api::v1::{Mutation, Row, Rows, SemanticType};
1672    use common_error::ext::WhateverResult;
1673    use datatypes::arrow::array::AsArray;
1674    use datatypes::arrow::datatypes::{DataType as ArrowDataType, UInt64Type};
1675    use datatypes::data_type::ConcreteDataType;
1676    use datatypes::extension::json::{
1677        JSON2_REMAINDER_FIELD_NAME, Json2ExtensionType, Json2PhysicalLayout, JsonMetadata,
1678    };
1679    use datatypes::json::value::JsonValue;
1680    use datatypes::json::{JsonSettings, JsonTypeHint};
1681    use datatypes::schema::ColumnSchema;
1682    use datatypes::types::json_type::{JsonNativeType, JsonObjectType};
1683    use mito_codec::row_converter::build_primary_key_codec;
1684    use parquet::basic::{Compression, Encoding, Type as PhysicalType};
1685    use serde_json::json;
1686    use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder, RegionMetadataRef};
1687    use store_api::storage::consts::{OP_TYPE_COLUMN_NAME, SEQUENCE_COLUMN_NAME};
1688
1689    use super::*;
1690    use crate::memtable::bulk::part::BulkPartConverter;
1691    use crate::read::scan_region::PredicateGroup;
1692    use crate::sst::parquet::flat_format::sequence_column_index;
1693    use crate::sst::{FlatSchemaOptions, to_flat_sst_arrow_schema};
1694    use crate::test_util::memtable_util::{
1695        build_key_values_with_ts_seq_values, metadata_for_test, region_metadata_to_row_schema,
1696    };
1697
1698    fn create_bulk_part_with_converter(
1699        k0: &str,
1700        k1: u32,
1701        timestamps: Vec<i64>,
1702        values: Vec<Option<f64>>,
1703        sequence: u64,
1704    ) -> Result<BulkPart> {
1705        let metadata = metadata_for_test();
1706        let capacity = 100;
1707        let primary_key_codec = build_primary_key_codec(&metadata);
1708        let schema = to_flat_sst_arrow_schema(
1709            &metadata,
1710            &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
1711        );
1712
1713        let mut converter =
1714            BulkPartConverter::new(&metadata, schema, capacity, primary_key_codec, true);
1715
1716        let key_values = build_key_values_with_ts_seq_values(
1717            &metadata,
1718            k0.to_string(),
1719            k1,
1720            timestamps.into_iter(),
1721            values.into_iter(),
1722            sequence,
1723        );
1724
1725        converter.append_key_values(&key_values)?;
1726        converter.convert()
1727    }
1728
1729    #[test]
1730    fn test_adaptive_encode_bytes_threshold() {
1731        // Below the lower bound: clamped to the default 64 MiB.
1732        assert_eq!(
1733            DEFAULT_ENCODE_BYTES_THRESHOLD,
1734            adaptive_encode_bytes_threshold(1024 * 1024 * 1024) // 1 GiB / 32 = 32 MiB
1735        );
1736        // In range: global_write_buffer_size / 32.
1737        assert_eq!(
1738            256 * 1024 * 1024,
1739            adaptive_encode_bytes_threshold(8 * 1024 * 1024 * 1024) // 8 GiB / 32 = 256 MiB
1740        );
1741        // Above the upper bound: clamped to 512 MiB.
1742        assert_eq!(
1743            MAX_ENCODE_BYTES_THRESHOLD,
1744            adaptive_encode_bytes_threshold(64 * 1024 * 1024 * 1024) // 64 GiB / 32 = 2 GiB
1745        );
1746    }
1747
1748    #[test]
1749    fn test_bulk_memtable_sanitizes_zero_merge_threshold() {
1750        let metadata = metadata_for_test();
1751        let config = BulkMemtableConfig {
1752            merge_threshold: 0,
1753            ..Default::default()
1754        };
1755
1756        let memtable =
1757            BulkMemtable::new(999, config, metadata, None, None, false, MergeMode::LastRow);
1758
1759        assert_eq!(DEFAULT_MERGE_THRESHOLD, memtable.config.merge_threshold);
1760        assert_eq!(
1761            DEFAULT_MERGE_THRESHOLD,
1762            memtable.compactor.lock().unwrap().config.merge_threshold
1763        );
1764    }
1765
1766    #[test]
1767    fn test_min_sequence_survives_mixed_parts_compaction_and_fork() {
1768        let metadata = metadata_for_test();
1769        let config = BulkMemtableConfig {
1770            merge_threshold: 3,
1771            encode_row_threshold: 1,
1772            encode_bytes_threshold: 1,
1773            ..Default::default()
1774        };
1775        let memtable = BulkMemtable::new(
1776            1,
1777            config,
1778            metadata.clone(),
1779            None,
1780            None,
1781            false,
1782            MergeMode::LastNonNull,
1783        );
1784        memtable.set_unordered_part_threshold(0);
1785        for (sequence, expected_min, expected_ranges) in [(100, 100, 1), (10, 10, 2), (200, 10, 1)]
1786        {
1787            let part = create_bulk_part_with_converter(
1788                "a",
1789                1,
1790                vec![2000, 1000],
1791                vec![Some(1.0), Some(2.0)],
1792                sequence,
1793            )
1794            .unwrap();
1795            // Regular row writes produce heterogeneous parts, sorted by timestamp
1796            // rather than sequence. Neither the scalar nor the first row is a minimum.
1797            assert_eq!(sequence + 1, part.sequence);
1798            assert_eq!(sequence, part.min_sequence);
1799            let sequences = part
1800                .batch
1801                .column(sequence_column_index(part.batch.num_columns()))
1802                .as_primitive::<UInt64Type>();
1803            assert_eq!(&[sequence + 1, sequence], sequences.values().as_ref());
1804            memtable.write_bulk(part).unwrap();
1805            assert_eq!(expected_min, memtable.stats().min_sequence);
1806            assert_eq!(expected_min, memtable.min_sequence());
1807            assert_eq!(expected_ranges, memtable.stats().num_ranges);
1808            assert!(!memtable.compacting.load(Ordering::Relaxed));
1809        }
1810        // The third write synchronously merges all parts without a dispatcher.
1811        // Dedup can remove the oldest rows, but their lower bound remains safe.
1812        assert_eq!(10, memtable.stats().min_sequence);
1813        assert_eq!(201, memtable.stats().max_sequence);
1814        let ranges = memtable.ranges(None, RangesOptions::default()).unwrap();
1815        let merged = ranges.ranges.values().next().unwrap();
1816        assert!(merged.encoded().is_some());
1817        assert_eq!(10, merged.stats().min_sequence);
1818        let fork = memtable.fork(2, &metadata);
1819        assert!(fork.is_empty());
1820        assert_eq!(0, fork.min_sequence());
1821        fork.write_bulk(
1822            create_bulk_part_with_converter("a", 1, vec![1000], vec![Some(1.0)], 50).unwrap(),
1823        )
1824        .unwrap();
1825        assert_eq!(50, fork.stats().min_sequence);
1826        assert_eq!(50, fork.min_sequence());
1827    }
1828
1829    #[test]
1830    fn test_bulk_memtable_write_read() {
1831        let metadata = metadata_for_test();
1832        let memtable = BulkMemtable::new(
1833            999,
1834            BulkMemtableConfig::default(),
1835            metadata.clone(),
1836            None,
1837            None,
1838            false,
1839            MergeMode::LastRow,
1840        );
1841        // Disable unordered_part for this test
1842        memtable.set_unordered_part_threshold(0);
1843
1844        let test_data = [
1845            (
1846                "key_a",
1847                1u32,
1848                vec![1000i64, 2000i64],
1849                vec![Some(10.5), Some(20.5)],
1850                100u64,
1851            ),
1852            (
1853                "key_b",
1854                2u32,
1855                vec![1500i64, 2500i64],
1856                vec![Some(15.5), Some(25.5)],
1857                200u64,
1858            ),
1859            ("key_c", 3u32, vec![3000i64], vec![Some(30.5)], 300u64),
1860        ];
1861
1862        for (k0, k1, timestamps, values, seq) in test_data.iter() {
1863            let part =
1864                create_bulk_part_with_converter(k0, *k1, timestamps.clone(), values.clone(), *seq)
1865                    .unwrap();
1866            memtable.write_bulk(part).unwrap();
1867        }
1868
1869        let stats = memtable.stats();
1870        assert_eq!(5, stats.num_rows);
1871        assert_eq!(3, stats.num_ranges);
1872        assert_eq!(300, stats.max_sequence);
1873        assert_eq!(100, stats.min_sequence);
1874
1875        let (min_ts, max_ts) = stats.time_range.unwrap();
1876        assert_eq!(1000, min_ts.value());
1877        assert_eq!(3000, max_ts.value());
1878
1879        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
1880        let ranges = memtable
1881            .ranges(
1882                None,
1883                RangesOptions::default().with_predicate(predicate_group),
1884            )
1885            .unwrap();
1886
1887        assert_eq!(3, ranges.ranges.len());
1888        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
1889        assert_eq!(5, total_rows);
1890
1891        for range in ranges.ranges.values() {
1892            assert!(range.num_rows() > 0);
1893            assert!(range.is_record_batch());
1894
1895            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
1896
1897            let mut total_rows = 0;
1898            for batch_result in record_batch_iter {
1899                let batch = batch_result.unwrap();
1900                total_rows += batch.num_rows();
1901                assert!(batch.num_rows() > 0);
1902                assert_eq!(8, batch.num_columns());
1903            }
1904            assert_eq!(total_rows, range.num_rows());
1905        }
1906    }
1907
1908    #[test]
1909    fn test_bulk_memtable_compact_parts_with_json2() {
1910        let metadata = mock_metadata_with_json2(JsonSettings::default());
1911
1912        let config = BulkMemtableConfig {
1913            merge_threshold: 2,
1914            encode_row_threshold: 1,
1915            encode_bytes_threshold: 1,
1916            ..Default::default()
1917        };
1918        let memtable = BulkMemtable::new(
1919            999,
1920            config,
1921            metadata.clone(),
1922            None,
1923            None,
1924            true,
1925            MergeMode::LastRow,
1926        );
1927        memtable.set_unordered_part_threshold(0);
1928
1929        let part1 = mock_bulk_part_with_json2(&metadata, vec![1000, 2000], 100).unwrap();
1930        let part2 = mock_bulk_part_with_json2(&metadata, vec![3000, 4000], 200).unwrap();
1931
1932        memtable.write_bulk(part1).unwrap();
1933        memtable.write_bulk(part2).unwrap();
1934        memtable.compact(false).unwrap();
1935
1936        let stats = memtable.stats();
1937        assert_eq!(4, stats.num_rows);
1938        assert_eq!(201, stats.max_sequence);
1939
1940        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
1941        let opts = RangesOptions::default().with_predicate(predicate_group);
1942        let ranges = memtable.ranges(None, opts).unwrap();
1943
1944        assert_eq!(1, ranges.ranges.len());
1945        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
1946        assert_eq!(4, total_rows);
1947    }
1948
1949    #[test]
1950    fn test_bulk_memtable_merge_bounds_json2_paths() -> WhateverResult<()> {
1951        let metadata = mock_metadata_with_json2(JsonSettings::try_new(vec![], Some(1))?);
1952        let first = mock_bulk_part_with_json2_values(
1953            &metadata,
1954            vec![1000, 2000],
1955            vec![json!({"a": 1}), json!({"a": 2})],
1956            100,
1957        )?;
1958        let second = mock_bulk_part_with_json2_values(
1959            &metadata,
1960            vec![3000, 4000],
1961            vec![json!({"a": 3, "b": 3}), json!({"a": 4, "b": 4})],
1962            200,
1963        )?;
1964        let parts = vec![
1965            PartToMerge::Bulk {
1966                part: first,
1967                file_id: FileId::random(),
1968            },
1969            PartToMerge::Bulk {
1970                part: second,
1971                file_id: FileId::random(),
1972            },
1973        ];
1974
1975        let merged = MemtableCompactor::merge_parts_group(
1976            parts,
1977            &metadata,
1978            false,
1979            MergeMode::LastRow,
1980            usize::MAX,
1981            usize::MAX,
1982            DEFAULT_ROW_GROUP_SIZE,
1983            FloatFieldEncoding::default(),
1984        )?
1985        .unwrap();
1986        let MergedPart::Multi(part) = merged else {
1987            unreachable!()
1988        };
1989        let schema = part.schemas().next().unwrap();
1990        let ArrowDataType::Struct(fields) = schema.field(0).data_type() else {
1991            unreachable!()
1992        };
1993        assert_eq!(
1994            vec![JSON2_REMAINDER_FIELD_NAME, "a"],
1995            fields.iter().map(|x| x.name().as_str()).collect::<Vec<_>>()
1996        );
1997        Ok(())
1998    }
1999
2000    #[test]
2001    fn test_unordered_parts_align_json2_layouts() -> WhateverResult<()> {
2002        let metadata = mock_metadata_with_json2(JsonSettings::try_new(vec![], Some(2))?);
2003        let memtable = BulkMemtable::new(
2004            42,
2005            BulkMemtableConfig::default(),
2006            metadata.clone(),
2007            None,
2008            None,
2009            false,
2010            MergeMode::LastRow,
2011        );
2012        memtable.write_bulk(mock_bulk_part_with_json2_values(
2013            &metadata,
2014            vec![1000, 2000],
2015            vec![json!({"a": 1}), json!({"a": 2})],
2016            100,
2017        )?)?;
2018        memtable.write_bulk(mock_bulk_part_with_json2_values(
2019            &metadata,
2020            vec![3000, 4000],
2021            vec![json!({"b": 3}), json!({"b": 4})],
2022            200,
2023        )?)?;
2024
2025        let predicate = PredicateGroup::new(&metadata, &[])?;
2026        let ranges = memtable.ranges(None, RangesOptions::default().with_predicate(predicate))?;
2027        let range = ranges.ranges.values().next().unwrap();
2028        let batch = range.build_record_batch_iter(None, None)?.next().unwrap()?;
2029        let schema = batch.schema();
2030        let ArrowDataType::Struct(fields) = schema.field(0).data_type() else {
2031            unreachable!()
2032        };
2033        assert_eq!(
2034            vec![JSON2_REMAINDER_FIELD_NAME],
2035            fields.iter().map(|x| x.name().as_str()).collect::<Vec<_>>()
2036        );
2037        // Neither path is explicit in every source; keep both opaque without losing values.
2038        assert_eq!(4, batch.num_rows());
2039        let json = datatypes::vectors::json::array::JsonArray::from(batch.column(0));
2040        let restored = json.project_to_v2(schema.field(0), &ArrowDataType::Binary)?;
2041        let restored = datatypes::vectors::json::array::JsonArray::from(&restored);
2042        assert_eq!(json!({"a": 1}), restored.try_get_value(0)?);
2043        assert_eq!(json!({"a": 2}), restored.try_get_value(1)?);
2044        assert_eq!(json!({"b": 3}), restored.try_get_value(2)?);
2045        assert_eq!(json!({"b": 4}), restored.try_get_value(3)?);
2046        Ok(())
2047    }
2048
2049    #[test]
2050    fn test_bulk_part_converter_uses_json2_v2_layout() -> WhateverResult<()> {
2051        let settings = JsonSettings::try_new(
2052            vec![JsonTypeHint {
2053                path: vec!["id".to_string()],
2054                data_type: ConcreteDataType::int64_datatype(),
2055                inverted_index: false,
2056            }],
2057            Some(0),
2058        )?;
2059        let metadata = mock_metadata_with_json2(settings);
2060        let part = mock_bulk_part_with_json2(&metadata, vec![1000, 2000], 100)?;
2061        let schema = part.batch.schema();
2062        let field = schema.field(0);
2063        let layout = Json2PhysicalLayout::try_from_root(field)?;
2064
2065        assert!(layout.is_version_2());
2066        let ArrowDataType::Struct(fields) = field.data_type() else {
2067            unreachable!()
2068        };
2069        assert_eq!(
2070            vec![JSON2_REMAINDER_FIELD_NAME, "id"],
2071            fields.iter().map(|x| x.name().as_str()).collect::<Vec<_>>()
2072        );
2073        Ok(())
2074    }
2075
2076    fn mock_metadata_with_json2(settings: JsonSettings) -> RegionMetadataRef {
2077        let col_meta_1 = ColumnMetadata {
2078            column_schema: ColumnSchema::new(
2079                "ts",
2080                ConcreteDataType::timestamp_millisecond_datatype(),
2081                false,
2082            ),
2083            semantic_type: SemanticType::Timestamp,
2084            column_id: 0,
2085        };
2086
2087        let data_type = ConcreteDataType::json2(JsonNativeType::Object(JsonObjectType::new()));
2088        let mut col_schema = ColumnSchema::new("data", data_type, true);
2089        col_schema.with_extension_type(&Json2ExtensionType::new(Arc::new(JsonMetadata::new(
2090            settings,
2091        ))));
2092
2093        let col_meta_2 = ColumnMetadata {
2094            column_schema: col_schema,
2095            semantic_type: SemanticType::Field,
2096            column_id: 1,
2097        };
2098        let mut builder = RegionMetadataBuilder::new(RegionId::new(123, 789));
2099        builder
2100            .push_column_metadata(col_meta_1)
2101            .push_column_metadata(col_meta_2)
2102            .primary_key(vec![]);
2103        Arc::new(builder.build().unwrap())
2104    }
2105
2106    fn mock_bulk_part_with_json2(
2107        metadata: &RegionMetadataRef,
2108        timestamps: Vec<i64>,
2109        sequence: u64,
2110    ) -> Result<BulkPart> {
2111        let values = timestamps
2112            .iter()
2113            .map(|ts| {
2114                json!({
2115                    "id": ts,
2116                    "payload": {
2117                        "message": format!("row-{ts}"),
2118                    },
2119                })
2120            })
2121            .collect();
2122        mock_bulk_part_with_json2_values(metadata, timestamps, values, sequence)
2123    }
2124
2125    fn mock_bulk_part_with_json2_values(
2126        metadata: &RegionMetadataRef,
2127        timestamps: Vec<i64>,
2128        values: Vec<serde_json::Value>,
2129        sequence: u64,
2130    ) -> Result<BulkPart> {
2131        let capacity = timestamps.len();
2132        debug_assert_eq!(capacity, values.len());
2133        let primary_key_codec = build_primary_key_codec(metadata);
2134        let json_type = JsonNativeType::Object(JsonObjectType::from([
2135            ("id".to_string(), JsonNativeType::i64()),
2136            (
2137                "payload".to_string(),
2138                JsonNativeType::Object(JsonObjectType::from([(
2139                    "message".to_string(),
2140                    JsonNativeType::String,
2141                )])),
2142            ),
2143        ]));
2144        let mut options = FlatSchemaOptions::from_encoding(metadata.primary_key_encoding);
2145        options
2146            .concretized_json_types
2147            .insert("data".to_string(), json_type.as_arrow_type());
2148        let schema = to_flat_sst_arrow_schema(metadata, &options);
2149
2150        let mut converter =
2151            BulkPartConverter::new(metadata, schema, capacity, primary_key_codec, true);
2152
2153        let rows = timestamps
2154            .into_iter()
2155            .zip(values)
2156            .map(|(ts, value)| {
2157                let val1 = api::v1::Value {
2158                    value_data: Some(ValueData::TimestampMillisecondValue(ts)),
2159                };
2160                let value_data = ValueData::JsonValue(encode_json_value(JsonValue::from(value)));
2161                let val2 = api::v1::Value {
2162                    value_data: Some(value_data),
2163                };
2164                Row {
2165                    values: vec![val1, val2],
2166                }
2167            })
2168            .collect();
2169
2170        let mutation = Mutation {
2171            op_type: 1,
2172            sequence,
2173            rows: Some(Rows {
2174                schema: region_metadata_to_row_schema(metadata),
2175                rows,
2176            }),
2177            write_hint: None,
2178        };
2179        let key_values = KeyValues::new(metadata.as_ref(), mutation).unwrap();
2180
2181        converter.append_key_values(&key_values)?;
2182        converter.convert()
2183    }
2184
2185    #[test]
2186    fn test_bulk_memtable_ranges_with_projection() {
2187        let metadata = metadata_for_test();
2188        let memtable = BulkMemtable::new(
2189            111,
2190            BulkMemtableConfig::default(),
2191            metadata.clone(),
2192            None,
2193            None,
2194            false,
2195            MergeMode::LastRow,
2196        );
2197
2198        let bulk_part = create_bulk_part_with_converter(
2199            "projection_test",
2200            5,
2201            vec![5000, 6000, 7000],
2202            vec![Some(50.0), Some(60.0), Some(70.0)],
2203            500,
2204        )
2205        .unwrap();
2206
2207        memtable.write_bulk(bulk_part).unwrap();
2208
2209        let projection = vec![4u32];
2210        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2211        let ranges = memtable
2212            .ranges(
2213                Some(&projection),
2214                RangesOptions::default().with_predicate(predicate_group),
2215            )
2216            .unwrap();
2217
2218        assert_eq!(1, ranges.ranges.len());
2219        let range = ranges.ranges.get(&0).unwrap();
2220
2221        assert!(range.is_record_batch());
2222        let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2223
2224        let mut total_rows = 0;
2225        for batch_result in record_batch_iter {
2226            let batch = batch_result.unwrap();
2227            assert!(batch.num_rows() > 0);
2228            assert_eq!(5, batch.num_columns());
2229            total_rows += batch.num_rows();
2230        }
2231        assert_eq!(3, total_rows);
2232    }
2233
2234    #[test]
2235    fn test_bulk_memtable_unsupported_operations() {
2236        let metadata = metadata_for_test();
2237        let memtable = BulkMemtable::new(
2238            111,
2239            BulkMemtableConfig::default(),
2240            metadata.clone(),
2241            None,
2242            None,
2243            false,
2244            MergeMode::LastRow,
2245        );
2246
2247        let key_values = build_key_values_with_ts_seq_values(
2248            &metadata,
2249            "test".to_string(),
2250            1,
2251            vec![1000].into_iter(),
2252            vec![Some(1.0)].into_iter(),
2253            1,
2254        );
2255
2256        let err = memtable.write(&key_values).unwrap_err();
2257        assert!(err.to_string().contains("not supported"));
2258
2259        let kv = key_values.iter().next().unwrap();
2260        let err = memtable.write_one(kv).unwrap_err();
2261        assert!(err.to_string().contains("not supported"));
2262    }
2263
2264    #[test]
2265    fn test_bulk_memtable_freeze() {
2266        let metadata = metadata_for_test();
2267        let memtable = BulkMemtable::new(
2268            222,
2269            BulkMemtableConfig::default(),
2270            metadata.clone(),
2271            None,
2272            None,
2273            false,
2274            MergeMode::LastRow,
2275        );
2276
2277        let bulk_part = create_bulk_part_with_converter(
2278            "freeze_test",
2279            10,
2280            vec![10000],
2281            vec![Some(100.0)],
2282            1000,
2283        )
2284        .unwrap();
2285
2286        memtable.write_bulk(bulk_part).unwrap();
2287        memtable.freeze().unwrap();
2288
2289        let stats_after_freeze = memtable.stats();
2290        assert_eq!(1, stats_after_freeze.num_rows);
2291    }
2292
2293    #[test]
2294    fn test_bulk_memtable_fork() {
2295        let metadata = metadata_for_test();
2296        let original_memtable = BulkMemtable::new_with_row_group_size_and_encoding(
2297            333,
2298            BulkMemtableConfig::default(),
2299            metadata.clone(),
2300            None,
2301            None,
2302            false,
2303            MergeMode::LastRow,
2304            DEFAULT_ROW_GROUP_SIZE,
2305            FloatFieldEncoding::ByteStreamSplit,
2306        );
2307
2308        let bulk_part =
2309            create_bulk_part_with_converter("fork_test", 15, vec![15000], vec![Some(150.0)], 1500)
2310                .unwrap();
2311
2312        original_memtable.write_bulk(bulk_part).unwrap();
2313
2314        let forked_memtable = original_memtable.fork_inner(444, &metadata);
2315
2316        assert_eq!(forked_memtable.id(), 444);
2317        assert_eq!(
2318            FloatFieldEncoding::ByteStreamSplit,
2319            original_memtable.float_field_encoding
2320        );
2321        assert_eq!(
2322            original_memtable.float_field_encoding,
2323            forked_memtable.float_field_encoding
2324        );
2325        assert_eq!(
2326            original_memtable.float_field_encoding,
2327            forked_memtable
2328                .compactor
2329                .lock()
2330                .unwrap()
2331                .float_field_encoding
2332        );
2333        assert!(forked_memtable.is_empty());
2334        assert_eq!(0, forked_memtable.stats().num_rows);
2335
2336        assert!(!original_memtable.is_empty());
2337        assert_eq!(1, original_memtable.stats().num_rows);
2338    }
2339
2340    #[test]
2341    fn test_bulk_memtable_ranges_multiple_parts() {
2342        let metadata = metadata_for_test();
2343        let memtable = BulkMemtable::new(
2344            777,
2345            BulkMemtableConfig::default(),
2346            metadata.clone(),
2347            None,
2348            None,
2349            false,
2350            MergeMode::LastRow,
2351        );
2352        // Disable unordered_part for this test
2353        memtable.set_unordered_part_threshold(0);
2354
2355        let parts_data = vec![
2356            (
2357                "part1",
2358                1u32,
2359                vec![1000i64, 1100i64],
2360                vec![Some(10.0), Some(11.0)],
2361                100u64,
2362            ),
2363            (
2364                "part2",
2365                2u32,
2366                vec![2000i64, 2100i64],
2367                vec![Some(20.0), Some(21.0)],
2368                200u64,
2369            ),
2370            ("part3", 3u32, vec![3000i64], vec![Some(30.0)], 300u64),
2371        ];
2372
2373        for (k0, k1, timestamps, values, seq) in parts_data {
2374            let part = create_bulk_part_with_converter(k0, k1, timestamps, values, seq).unwrap();
2375            memtable.write_bulk(part).unwrap();
2376        }
2377
2378        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2379        let ranges = memtable
2380            .ranges(
2381                None,
2382                RangesOptions::default().with_predicate(predicate_group),
2383            )
2384            .unwrap();
2385
2386        assert_eq!(3, ranges.ranges.len());
2387        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2388        assert_eq!(5, total_rows);
2389        assert_eq!(3, ranges.ranges.len());
2390
2391        for (range_id, range) in ranges.ranges.iter() {
2392            assert!(*range_id < 3);
2393            assert!(range.num_rows() > 0);
2394            assert!(range.is_record_batch());
2395        }
2396    }
2397
2398    #[test]
2399    fn test_bulk_memtable_ranges_with_sequence_filter() {
2400        let metadata = metadata_for_test();
2401        let memtable = BulkMemtable::new(
2402            888,
2403            BulkMemtableConfig::default(),
2404            metadata.clone(),
2405            None,
2406            None,
2407            false,
2408            MergeMode::LastRow,
2409        );
2410
2411        let part = create_bulk_part_with_converter(
2412            "seq_test",
2413            1,
2414            vec![1000, 2000, 3000],
2415            vec![Some(10.0), Some(20.0), Some(30.0)],
2416            500,
2417        )
2418        .unwrap();
2419
2420        memtable.write_bulk(part).unwrap();
2421
2422        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2423        let sequence_filter = Some(SequenceRange::LtEq { max: 400 }); // Filters out rows with sequence > 400
2424        let ranges = memtable
2425            .ranges(
2426                None,
2427                RangesOptions::default()
2428                    .with_predicate(predicate_group)
2429                    .with_sequence(sequence_filter),
2430            )
2431            .unwrap();
2432
2433        assert_eq!(1, ranges.ranges.len());
2434        let range = ranges.ranges.get(&0).unwrap();
2435
2436        let mut record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2437        assert!(record_batch_iter.next().is_none());
2438    }
2439
2440    #[test]
2441    fn test_bulk_memtable_ranges_with_encoded_parts() {
2442        let metadata = metadata_for_test();
2443        let config = BulkMemtableConfig {
2444            merge_threshold: 8,
2445            ..Default::default()
2446        };
2447        let memtable = BulkMemtable::new(
2448            999,
2449            config,
2450            metadata.clone(),
2451            None,
2452            None,
2453            false,
2454            MergeMode::LastRow,
2455        );
2456        // Disable unordered_part for this test
2457        memtable.set_unordered_part_threshold(0);
2458
2459        // Adds enough bulk parts to trigger encoding
2460        for i in 0..10 {
2461            let part = create_bulk_part_with_converter(
2462                &format!("key_{}", i),
2463                i,
2464                vec![1000 + i as i64 * 100],
2465                vec![Some(i as f64 * 10.0)],
2466                100 + i as u64,
2467            )
2468            .unwrap();
2469            memtable.write_bulk(part).unwrap();
2470        }
2471
2472        memtable.compact(false).unwrap();
2473
2474        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2475        let ranges = memtable
2476            .ranges(
2477                None,
2478                RangesOptions::default().with_predicate(predicate_group),
2479            )
2480            .unwrap();
2481
2482        // Should have ranges for both bulk parts and encoded parts
2483        assert_eq!(3, ranges.ranges.len());
2484        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2485        assert_eq!(10, total_rows);
2486
2487        for range in ranges.ranges.values() {
2488            assert!(range.num_rows() > 0);
2489            assert!(range.is_record_batch());
2490
2491            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2492            let mut total_rows = 0;
2493            for batch_result in record_batch_iter {
2494                let batch = batch_result.unwrap();
2495                total_rows += batch.num_rows();
2496                assert!(batch.num_rows() > 0);
2497            }
2498            assert_eq!(total_rows, range.num_rows());
2499        }
2500    }
2501
2502    #[test]
2503    fn test_bulk_memtable_unordered_part() {
2504        let metadata = metadata_for_test();
2505        let memtable = BulkMemtable::new(
2506            1001,
2507            BulkMemtableConfig::default(),
2508            metadata.clone(),
2509            None,
2510            None,
2511            false,
2512            MergeMode::LastRow,
2513        );
2514
2515        // Set smaller thresholds for testing with smaller inputs
2516        // Accept parts with < 5 rows into unordered_part
2517        memtable.set_unordered_part_threshold(5);
2518        // Compact when total rows >= 10
2519        memtable.set_unordered_part_compact_threshold(10);
2520
2521        // Write 3 small parts (each has 2 rows), should be collected in unordered_part
2522        for i in 0..3 {
2523            let part = create_bulk_part_with_converter(
2524                &format!("key_{}", i),
2525                i,
2526                vec![1000 + i as i64 * 100, 1100 + i as i64 * 100],
2527                vec![Some(i as f64 * 10.0), Some(i as f64 * 10.0 + 1.0)],
2528                100 + i as u64,
2529            )
2530            .unwrap();
2531            assert_eq!(2, part.num_rows());
2532            memtable.write_bulk(part).unwrap();
2533        }
2534
2535        // Total rows = 6, not yet reaching compact threshold
2536        let stats = memtable.stats();
2537        assert_eq!(6, stats.num_rows);
2538
2539        // Write 2 more small parts (each has 2 rows)
2540        // This should trigger compaction when total >= 10
2541        for i in 3..5 {
2542            let part = create_bulk_part_with_converter(
2543                &format!("key_{}", i),
2544                i,
2545                vec![1000 + i as i64 * 100, 1100 + i as i64 * 100],
2546                vec![Some(i as f64 * 10.0), Some(i as f64 * 10.0 + 1.0)],
2547                100 + i as u64,
2548            )
2549            .unwrap();
2550            memtable.write_bulk(part).unwrap();
2551        }
2552
2553        // Total rows = 10, should have compacted unordered_part into a regular part
2554        let stats = memtable.stats();
2555        assert_eq!(10, stats.num_rows);
2556
2557        // Verify we can read all data correctly
2558        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2559        let ranges = memtable
2560            .ranges(
2561                None,
2562                RangesOptions::default().with_predicate(predicate_group),
2563            )
2564            .unwrap();
2565
2566        // Should have at least 1 range (the compacted part)
2567        assert!(!ranges.ranges.is_empty());
2568        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2569        assert_eq!(10, total_rows);
2570
2571        // Read all data and verify
2572        let mut total_rows_read = 0;
2573        for range in ranges.ranges.values() {
2574            assert!(range.is_record_batch());
2575            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2576
2577            for batch_result in record_batch_iter {
2578                let batch = batch_result.unwrap();
2579                total_rows_read += batch.num_rows();
2580            }
2581        }
2582        assert_eq!(10, total_rows_read);
2583    }
2584
2585    #[test]
2586    fn test_bulk_memtable_unordered_part_mixed_sizes() {
2587        let metadata = metadata_for_test();
2588        let memtable = BulkMemtable::new(
2589            1002,
2590            BulkMemtableConfig::default(),
2591            metadata.clone(),
2592            None,
2593            None,
2594            false,
2595            MergeMode::LastRow,
2596        );
2597
2598        // Set threshold to 4 rows - parts with < 4 rows go to unordered_part
2599        memtable.set_unordered_part_threshold(4);
2600        memtable.set_unordered_part_compact_threshold(8);
2601
2602        // Write small parts (3 rows each) - should go to unordered_part
2603        for i in 0..2 {
2604            let part = create_bulk_part_with_converter(
2605                &format!("small_{}", i),
2606                i,
2607                vec![1000 + i as i64, 2000 + i as i64, 3000 + i as i64],
2608                vec![Some(i as f64), Some(i as f64 + 1.0), Some(i as f64 + 2.0)],
2609                10 + i as u64,
2610            )
2611            .unwrap();
2612            assert_eq!(3, part.num_rows());
2613            memtable.write_bulk(part).unwrap();
2614        }
2615
2616        // Write a large part (5 rows) - should go directly to regular parts
2617        let large_part = create_bulk_part_with_converter(
2618            "large_key",
2619            100,
2620            vec![5000, 6000, 7000, 8000, 9000],
2621            vec![
2622                Some(100.0),
2623                Some(101.0),
2624                Some(102.0),
2625                Some(103.0),
2626                Some(104.0),
2627            ],
2628            50,
2629        )
2630        .unwrap();
2631        assert_eq!(5, large_part.num_rows());
2632        memtable.write_bulk(large_part).unwrap();
2633
2634        // A later small write fills the unordered part to its compact threshold.
2635        let part = create_bulk_part_with_converter(
2636            "small_2",
2637            2,
2638            vec![4000, 4100],
2639            vec![Some(20.0), Some(21.0)],
2640            30,
2641        )
2642        .unwrap();
2643        memtable.write_bulk(part).unwrap();
2644
2645        let stats = memtable.stats();
2646        assert_eq!(13, stats.num_rows); // 3 + 3 + 5 + 2 = 13
2647
2648        // Verify all data can be read
2649        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2650        let ranges = memtable
2651            .ranges(
2652                None,
2653                RangesOptions::default().with_predicate(predicate_group),
2654            )
2655            .unwrap();
2656
2657        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2658        assert_eq!(13, total_rows);
2659
2660        let mut total_rows_read = 0;
2661        for range in ranges.ranges.values() {
2662            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2663            for batch_result in record_batch_iter {
2664                let batch = batch_result.unwrap();
2665                total_rows_read += batch.num_rows();
2666            }
2667        }
2668        assert_eq!(13, total_rows_read);
2669    }
2670
2671    #[test]
2672    fn test_bulk_memtable_unordered_part_byte_threshold() {
2673        let metadata = metadata_for_test();
2674        let first =
2675            create_bulk_part_with_converter("first", 1, vec![1000], vec![Some(1.0)], 1).unwrap();
2676        let second =
2677            create_bulk_part_with_converter("second", 2, vec![2000], vec![Some(2.0)], 2).unwrap();
2678        let threshold = first.estimated_size().max(second.estimated_size());
2679        let memtable = BulkMemtable::new(
2680            1004,
2681            BulkMemtableConfig {
2682                encode_bytes_threshold: threshold,
2683                ..Default::default()
2684            },
2685            metadata.clone(),
2686            None,
2687            None,
2688            false,
2689            MergeMode::LastRow,
2690        );
2691        memtable.set_unordered_part_compact_threshold(usize::MAX);
2692
2693        // A part exactly at or below the threshold is accepted.
2694        memtable.write_bulk(first).unwrap();
2695        assert_eq!(1, memtable.parts.read().unwrap().unordered_part.num_parts());
2696
2697        // Compact after the accumulated size becomes larger than the threshold.
2698        memtable.write_bulk(second).unwrap();
2699        let parts = memtable.parts.read().unwrap();
2700        assert!(parts.unordered_part.is_empty());
2701        assert_eq!(1, parts.parts.len());
2702        drop(parts);
2703
2704        let oversized =
2705            create_bulk_part_with_converter("oversized", 3, vec![3000], vec![Some(3.0)], 3)
2706                .unwrap();
2707        let oversized_memtable = BulkMemtable::new(
2708            1005,
2709            BulkMemtableConfig {
2710                encode_bytes_threshold: oversized.estimated_size() - 1,
2711                ..Default::default()
2712            },
2713            metadata,
2714            None,
2715            None,
2716            false,
2717            MergeMode::LastRow,
2718        );
2719        oversized_memtable.write_bulk(oversized).unwrap();
2720        let parts = oversized_memtable.parts.read().unwrap();
2721        assert!(parts.unordered_part.is_empty());
2722        assert_eq!(1, parts.parts.len());
2723    }
2724
2725    #[test]
2726    fn test_bulk_memtable_unordered_part_with_ranges() {
2727        let metadata = metadata_for_test();
2728        let memtable = BulkMemtable::new(
2729            1003,
2730            BulkMemtableConfig::default(),
2731            metadata.clone(),
2732            None,
2733            None,
2734            false,
2735            MergeMode::LastRow,
2736        );
2737
2738        // Set small thresholds
2739        memtable.set_unordered_part_threshold(3);
2740        memtable.set_unordered_part_compact_threshold(100); // High threshold to prevent auto-compaction
2741
2742        // Write several small parts that stay in unordered_part
2743        for i in 0..3 {
2744            let part = create_bulk_part_with_converter(
2745                &format!("key_{}", i),
2746                i,
2747                vec![1000 + i as i64 * 100],
2748                vec![Some(i as f64 * 10.0)],
2749                100 + i as u64,
2750            )
2751            .unwrap();
2752            assert_eq!(1, part.num_rows());
2753            memtable.write_bulk(part).unwrap();
2754        }
2755
2756        let stats = memtable.stats();
2757        assert_eq!(3, stats.num_rows);
2758
2759        // Test that ranges() can correctly read from unordered_part
2760        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2761        let ranges = memtable
2762            .ranges(
2763                None,
2764                RangesOptions::default().with_predicate(predicate_group),
2765            )
2766            .unwrap();
2767
2768        // Should have 1 range for the unordered_part
2769        assert_eq!(1, ranges.ranges.len());
2770        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2771        assert_eq!(3, total_rows);
2772
2773        // Verify data is sorted correctly in the range
2774        let range = ranges.ranges.get(&0).unwrap();
2775        let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2776
2777        let mut total_rows = 0;
2778        for batch_result in record_batch_iter {
2779            let batch = batch_result.unwrap();
2780            total_rows += batch.num_rows();
2781            // Verify data is properly sorted by primary key
2782            assert!(batch.num_rows() > 0);
2783        }
2784        assert_eq!(3, total_rows);
2785    }
2786
2787    /// Helper to create a BulkPartWrapper from a BulkPart.
2788    fn create_bulk_part_wrapper(part: BulkPart) -> BulkPartWrapper {
2789        BulkPartWrapper {
2790            part: PartToMerge::Bulk {
2791                part,
2792                file_id: FileId::random(),
2793            },
2794            merging: false,
2795        }
2796    }
2797
2798    #[test]
2799    fn test_may_merge_parts_below_threshold() {
2800        let mut bulk_parts = BulkParts::default();
2801
2802        // Add one fewer part than the merge threshold.
2803        for i in 0..DEFAULT_MERGE_THRESHOLD - 1 {
2804            let part = create_bulk_part_with_converter(
2805                &format!("key_{}", i),
2806                i as u32,
2807                vec![1000 + i as i64 * 100],
2808                vec![Some(i as f64 * 10.0)],
2809                100 + i as u64,
2810            )
2811            .unwrap();
2812            bulk_parts.parts.push(create_bulk_part_wrapper(part));
2813            if i == 0 {
2814                assert!(!bulk_parts.may_merge_parts(0));
2815                assert!(!bulk_parts.may_merge_parts(1));
2816            }
2817        }
2818
2819        // There are not enough parts to merge.
2820        assert!(!bulk_parts.may_merge_parts(DEFAULT_MERGE_THRESHOLD));
2821    }
2822
2823    #[test]
2824    fn test_may_merge_parts_at_threshold() {
2825        let mut bulk_parts = BulkParts::default();
2826        let merge_threshold = 8;
2827
2828        // Add 8 bulk parts (at merge_threshold)
2829        for i in 0..merge_threshold {
2830            let part = create_bulk_part_with_converter(
2831                &format!("key_{}", i),
2832                i as u32,
2833                vec![1000 + i as i64 * 100],
2834                vec![Some(i as f64 * 10.0)],
2835                100 + i as u64,
2836            )
2837            .unwrap();
2838            bulk_parts.parts.push(create_bulk_part_wrapper(part));
2839        }
2840
2841        // Should trigger merge since we have 8 parts
2842        assert!(bulk_parts.may_merge_parts(merge_threshold));
2843    }
2844
2845    #[test]
2846    fn test_raw_parts_do_not_use_encoded_merge_limit() {
2847        let mut bulk_parts = BulkParts::default();
2848        let merge_threshold = 4;
2849        for i in 0..merge_threshold {
2850            let part = create_bulk_part_with_converter(
2851                &format!("key_{}", i),
2852                i as u32,
2853                vec![1000 + i as i64],
2854                vec![Some(i as f64)],
2855                100 + i as u64,
2856            )
2857            .unwrap();
2858            bulk_parts.parts.push(create_bulk_part_wrapper(part));
2859        }
2860
2861        let max_size = bulk_parts.parts[0].part.estimated_size();
2862        assert!(bulk_parts.may_merge_parts(merge_threshold));
2863        assert_eq!(
2864            1,
2865            bulk_parts
2866                .collect_parts_to_merge(merge_threshold, 1, max_size)
2867                .groups
2868                .len()
2869        );
2870    }
2871
2872    #[test]
2873    fn test_may_merge_parts_with_merging_flag() {
2874        let mut bulk_parts = BulkParts::default();
2875        let merge_threshold = 8;
2876
2877        // Add 10 bulk parts
2878        for i in 0..10 {
2879            let part = create_bulk_part_with_converter(
2880                &format!("key_{}", i),
2881                i as u32,
2882                vec![1000 + i as i64 * 100],
2883                vec![Some(i as f64 * 10.0)],
2884                100 + i as u64,
2885            )
2886            .unwrap();
2887            bulk_parts.parts.push(create_bulk_part_wrapper(part));
2888        }
2889
2890        // Should trigger merge since we have 10 parts
2891        assert!(bulk_parts.may_merge_parts(merge_threshold));
2892
2893        // Mark first 3 parts as merging
2894        for wrapper in bulk_parts.parts.iter_mut().take(3) {
2895            wrapper.merging = true;
2896        }
2897
2898        // Now only 7 parts are available for merging, should not trigger
2899        assert!(!bulk_parts.may_merge_parts(merge_threshold));
2900    }
2901
2902    #[test]
2903    fn test_compact_task_guard() {
2904        let compacting = Arc::new(AtomicBool::new(false));
2905        let guard = CompactTaskGuard::try_new(compacting.clone()).unwrap();
2906        assert!(compacting.load(Ordering::Relaxed));
2907        assert!(CompactTaskGuard::try_new(compacting.clone()).is_none());
2908        drop(guard);
2909        assert!(!compacting.load(Ordering::Relaxed));
2910        assert!(CompactTaskGuard::try_new(compacting.clone()).is_some());
2911        assert!(!compacting.load(Ordering::Relaxed));
2912    }
2913
2914    #[test]
2915    fn test_compact_scheduling_disabled() {
2916        let memtable = BulkMemtable::new(
2917            0,
2918            BulkMemtableConfig {
2919                merge_threshold: 2,
2920                max_merge_groups: 0,
2921                ..Default::default()
2922            },
2923            metadata_for_test(),
2924            None,
2925            Some(Arc::new(CompactDispatcher::new(0))),
2926            false,
2927            MergeMode::LastRow,
2928        );
2929        let part =
2930            create_bulk_part_with_converter("key", 0, vec![1000], vec![Some(1.0)], 0).unwrap();
2931        memtable.parts.write().unwrap().parts = vec![
2932            create_bulk_part_wrapper(part.clone()),
2933            create_bulk_part_wrapper(part),
2934        ];
2935        memtable.schedule_compact();
2936        assert!(!memtable.compacting.load(Ordering::Relaxed));
2937    }
2938
2939    #[test]
2940    fn test_collect_parts_to_merge_sequence_components() {
2941        let make_part = |min, max| {
2942            let mut part =
2943                create_bulk_part_with_converter("key", 0, vec![1000], vec![Some(1.0)], max)
2944                    .unwrap();
2945            part.min_sequence = min;
2946            part
2947        };
2948        let metadata = metadata_for_test();
2949        let encoder = BulkPartEncoder::new(metadata.clone(), DEFAULT_ROW_GROUP_SIZE).unwrap();
2950        // Deliberately use installation order rather than sequence order.
2951        let mut parts = BulkParts {
2952            parts: vec![
2953                create_bulk_part_wrapper(make_part(70, 70)),
2954                create_bulk_part_wrapper(make_part(10, 30)),
2955                BulkPartWrapper {
2956                    part: PartToMerge::Multi {
2957                        part: MultiBulkPart::from_bulk_part(make_part(35, 50), &metadata),
2958                        file_id: FileId::random(),
2959                    },
2960                    merging: false,
2961                },
2962                create_bulk_part_wrapper(make_part(60, 60)),
2963                BulkPartWrapper {
2964                    part: PartToMerge::Encoded {
2965                        part: encoder.encode_part(&make_part(20, 40)).unwrap().unwrap(),
2966                        file_id: FileId::random(),
2967                    },
2968                    merging: false,
2969                },
2970            ],
2971            ..Default::default()
2972        };
2973        // The oversized encoded part connects three intervals transitively;
2974        // neither the merge threshold nor its size may split that component.
2975        assert!(parts.may_merge_parts(parts.parts.len()));
2976        assert_eq!(
2977            vec![vec![1, 4, 2], vec![3, 0]],
2978            parts.pick_merge_groups(2, 2, 1)
2979        );
2980        // The hint may schedule a merge even when unordered writes block all parts.
2981        parts.unordered_part.push(make_part(0, 100));
2982        assert!(parts.may_merge_parts(2));
2983        assert!(parts.pick_merge_groups(2, 2, 1).is_empty());
2984        parts.unordered_part.clear();
2985
2986        // Touching the unordered interval blocks the entire transitive component.
2987        parts.unordered_part.push(make_part(50, 55));
2988        assert_eq!(vec![vec![3, 0]], parts.pick_merge_groups(2, 2, 1));
2989        parts.unordered_part.clear();
2990
2991        // Even without overlap, a group cannot straddle pending small writes.
2992        parts.unordered_part.push(make_part(65, 65));
2993        assert_eq!(vec![vec![1, 4, 2]], parts.pick_merge_groups(2, 2, 1));
2994        parts.unordered_part.clear();
2995
2996        parts.parts[4].merging = true;
2997        let selected = parts.collect_parts_to_merge(2, 2, 1);
2998        assert_eq!(1, selected.groups.len());
2999        assert_eq!(
3000            vec![60, 70],
3001            selected.groups[0]
3002                .iter()
3003                .map(PartToMerge::max_sequence)
3004                .collect::<Vec<_>>()
3005        );
3006        assert!(!parts.parts[1].merging);
3007        assert!(!parts.parts[2].merging);
3008    }
3009
3010    #[test]
3011    fn test_encoded_merge_candidate_size_limit() {
3012        assert!(BulkParts::is_merge_candidate_by_size(false, usize::MAX, 8));
3013        assert!(BulkParts::is_merge_candidate_by_size(true, 8, 8));
3014        assert!(!BulkParts::is_merge_candidate_by_size(true, 9, 8));
3015    }
3016
3017    #[test]
3018    fn test_bulk_part_encoder_preserves_byte_stream_split() -> WhateverResult<()> {
3019        let metadata = metadata_for_test();
3020        let bulk_part = create_bulk_part_with_converter(
3021            "byte_stream_split",
3022            0,
3023            vec![1000, 2000],
3024            vec![Some(10.0), Some(20.0)],
3025            100,
3026        )?;
3027        let encoder = BulkPartEncoder::new_with_float_field_encoding(
3028            metadata,
3029            DEFAULT_ROW_GROUP_SIZE,
3030            FloatFieldEncoding::ByteStreamSplit,
3031        )?;
3032        let encoded_part = encoder.encode_part(&bulk_part)?.unwrap();
3033        let field_column = encoded_part
3034            .metadata()
3035            .parquet_metadata
3036            .row_groups()
3037            .iter()
3038            .flat_map(|row_group| row_group.columns())
3039            .find(|column| {
3040                column.column_path().string() == "v1"
3041                    && column.column_type() == PhysicalType::DOUBLE
3042            })
3043            .expect("Float64 field should be present in encoded bulk part");
3044
3045        assert!(
3046            field_column
3047                .encodings()
3048                .any(|encoding| encoding == Encoding::BYTE_STREAM_SPLIT)
3049        );
3050        Ok(())
3051    }
3052
3053    #[test]
3054    fn test_bulk_part_encoder_matches_sst_writer_internal_encodings() -> WhateverResult<()> {
3055        let metadata = metadata_for_test();
3056        let bulk_part = create_bulk_part_with_converter(
3057            "key",
3058            0,
3059            vec![1000, 2000, 3000],
3060            vec![Some(1.0), Some(2.0), Some(3.0)],
3061            100,
3062        )?;
3063        let encoder = BulkPartEncoder::new(metadata, DEFAULT_ROW_GROUP_SIZE)?;
3064        let encoded_part = encoder.encode_part(&bulk_part)?.unwrap();
3065        let columns = encoded_part
3066            .metadata()
3067            .parquet_metadata
3068            .row_group(0)
3069            .columns();
3070
3071        for name in ["ts", SEQUENCE_COLUMN_NAME] {
3072            let column = columns
3073                .iter()
3074                .find(|column| column.column_path().string() == name)
3075                .unwrap();
3076            assert!(
3077                column
3078                    .encodings()
3079                    .any(|encoding| encoding == Encoding::DELTA_BINARY_PACKED),
3080                "{name}"
3081            );
3082            assert!(column.dictionary_page_offset().is_none(), "{name}");
3083        }
3084        let op_type = columns
3085            .iter()
3086            .find(|column| column.column_path().string() == OP_TYPE_COLUMN_NAME)
3087            .unwrap();
3088        assert_eq!(Compression::UNCOMPRESSED, op_type.compression());
3089        Ok(())
3090    }
3091
3092    #[test]
3093    fn test_encoded_part_batch_size_uses_largest_uncompressed_row_group() {
3094        const NUM_ROWS: usize = 14;
3095        const ROW_GROUP_SIZE: usize = 4;
3096
3097        let metadata = metadata_for_test();
3098        let timestamps = (0..NUM_ROWS as i64).collect::<Vec<_>>();
3099        let field_values = (0..NUM_ROWS)
3100            .map(|value| Some(value as f64))
3101            .collect::<Vec<_>>();
3102        let bulk_part =
3103            create_bulk_part_with_converter("key", 0, timestamps, field_values, 0).unwrap();
3104        let encoder = BulkPartEncoder::new(metadata, ROW_GROUP_SIZE).unwrap();
3105        let encoded_part = encoder.encode_part(&bulk_part).unwrap().unwrap();
3106        let max_row_group = encoded_part
3107            .metadata()
3108            .parquet_metadata
3109            .row_groups()
3110            .iter()
3111            .max_by_key(|row_group| {
3112                row_group
3113                    .columns()
3114                    .iter()
3115                    .map(|column| column.uncompressed_size() as u64)
3116                    .sum::<u64>()
3117            })
3118            .unwrap();
3119        let max_uncompressed_size = max_row_group
3120            .columns()
3121            .iter()
3122            .map(|column| column.uncompressed_size() as u64)
3123            .sum();
3124        let expected = (max_row_group.num_rows() as u64, max_uncompressed_size);
3125        let part = PartToMerge::Encoded {
3126            part: encoded_part,
3127            file_id: FileId::random(),
3128        };
3129
3130        assert_eq!(Some(expected), part.batch_size_statistic());
3131    }
3132
3133    #[test]
3134    fn test_bulk_memtable_ranges_with_multi_bulk_part() {
3135        let metadata = metadata_for_test();
3136        let merge_threshold = 8;
3137        let config = BulkMemtableConfig {
3138            merge_threshold,
3139            ..Default::default()
3140        };
3141        let memtable = BulkMemtable::new(
3142            2005,
3143            config,
3144            metadata.clone(),
3145            None,
3146            None,
3147            false,
3148            MergeMode::LastRow,
3149        );
3150        // Disable unordered_part for this test
3151        memtable.set_unordered_part_threshold(0);
3152
3153        // Write enough bulk parts to trigger merge (merge_threshold = 8)
3154        // Each part has small number of rows so total < DEFAULT_ROW_GROUP_SIZE
3155        // This will result in MultiBulkPart after compaction
3156        for i in 0..merge_threshold {
3157            let part = create_bulk_part_with_converter(
3158                &format!("key_{}", i),
3159                i as u32,
3160                vec![1000 + i as i64 * 100, 2000 + i as i64 * 100],
3161                vec![Some(i as f64 * 10.0), Some(i as f64 * 10.0 + 1.0)],
3162                100 + i as u64,
3163            )
3164            .unwrap();
3165            memtable.write_bulk(part).unwrap();
3166        }
3167
3168        // Compact to trigger MultiBulkPart creation (since total rows < DEFAULT_ROW_GROUP_SIZE)
3169        memtable.compact(false).unwrap();
3170
3171        // Verify we can read from the memtable
3172        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
3173        let ranges = memtable
3174            .ranges(
3175                None,
3176                RangesOptions::default().with_predicate(predicate_group),
3177            )
3178            .unwrap();
3179
3180        assert_eq!(1, ranges.ranges.len());
3181        let expected_rows = merge_threshold * 2; // Each part has 2 rows
3182        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
3183        assert_eq!(expected_rows, total_rows);
3184
3185        // Read all data
3186        let mut total_rows_read = 0;
3187        for range in ranges.ranges.values() {
3188            assert!(range.is_record_batch());
3189            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
3190
3191            for batch_result in record_batch_iter {
3192                let batch = batch_result.unwrap();
3193                total_rows_read += batch.num_rows();
3194            }
3195        }
3196        assert_eq!(expected_rows, total_rows_read);
3197    }
3198
3199    #[test]
3200    fn test_multi_bulk_range_iter_builder_all_pruned() {
3201        let metadata = metadata_for_test();
3202        let merge_threshold = 8;
3203        let config = BulkMemtableConfig {
3204            merge_threshold,
3205            ..Default::default()
3206        };
3207        let memtable = BulkMemtable::new(
3208            2006,
3209            config,
3210            metadata.clone(),
3211            None,
3212            None,
3213            false,
3214            MergeMode::LastRow,
3215        );
3216        memtable.set_unordered_part_threshold(0);
3217
3218        // Write enough bulk parts to trigger merge into MultiBulkPart.
3219        for i in 0..merge_threshold {
3220            let part = create_bulk_part_with_converter(
3221                &format!("key_{}", i),
3222                i as u32,
3223                vec![1000 + i as i64 * 100, 2000 + i as i64 * 100],
3224                vec![Some(i as f64 * 10.0), Some(i as f64 * 10.0 + 1.0)],
3225                100 + i as u64,
3226            )
3227            .unwrap();
3228            memtable.write_bulk(part).unwrap();
3229        }
3230        memtable.compact(false).unwrap();
3231
3232        // Use a predicate that matches no rows so all batches are pruned.
3233        let filter = datafusion_expr::col("k0").eq(datafusion_expr::lit("nonexistent"));
3234        let predicate_group = PredicateGroup::new(&metadata, &[filter]).unwrap();
3235        let ranges = memtable
3236            .ranges(
3237                None,
3238                RangesOptions::default().with_predicate(predicate_group),
3239            )
3240            .unwrap();
3241
3242        // Should return ranges but each range should produce an empty iterator
3243        // instead of an error.
3244        for range in ranges.ranges.values() {
3245            assert!(range.is_record_batch());
3246            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
3247            let total_rows: usize = record_batch_iter.map(|r| r.unwrap().num_rows()).sum();
3248            assert_eq!(0, total_rows);
3249        }
3250    }
3251}