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, .. } => part
1159                .metadata()
1160                .parquet_metadata
1161                .row_groups()
1162                .iter()
1163                .map(|row_group| {
1164                    let uncompressed_bytes = row_group
1165                        .columns()
1166                        .iter()
1167                        .map(|column| column.uncompressed_size() as u64)
1168                        .sum();
1169                    (row_group.num_rows() as u64, uncompressed_bytes)
1170                })
1171                .max_by_key(|(_, uncompressed_bytes)| *uncompressed_bytes),
1172        }
1173    }
1174
1175    /// Converts this part to `MemtableStats`.
1176    fn to_memtable_stats(&self, region_metadata: &RegionMetadataRef) -> MemtableStats {
1177        match self {
1178            PartToMerge::Bulk { part, .. } => part.to_memtable_stats(region_metadata),
1179            PartToMerge::Multi { part, .. } => part.to_memtable_stats(region_metadata),
1180            PartToMerge::Encoded { part, .. } => part.to_memtable_stats(),
1181        }
1182    }
1183
1184    /// Returns the Arrow schema of the record batches contained in this [`PartToMerge`].
1185    fn arrow_schema(&self) -> SchemaRef {
1186        match self {
1187            PartToMerge::Bulk { part, .. } => part.schema(),
1188            // A MultiBulkPart is built from batches that have already been aligned, so
1189            // all contained batches are expected to share the same arrow schema.
1190            PartToMerge::Multi { part, .. } => part
1191                .schemas()
1192                .next()
1193                .expect("MultiBulkPart must contain at least one record batch"),
1194            PartToMerge::Encoded { part, .. } => part.schema(),
1195        }
1196    }
1197
1198    /// Creates a record batch iterator for this part.
1199    fn create_iterator(
1200        self,
1201        context: Arc<BulkIterContext>,
1202        plans: Arc<Json2RewritePlans>,
1203    ) -> Result<Option<BoxedRecordBatchIterator>> {
1204        let iter = match self {
1205            PartToMerge::Bulk { part, .. } => {
1206                let series_count = part.estimated_series_count();
1207                let iter = BulkPartBatchIter::from_single(
1208                    part.batch,
1209                    context,
1210                    None, // No sequence filter for merging
1211                    series_count,
1212                    None, // No metrics for merging
1213                );
1214                Some(Box::new(iter) as BoxedRecordBatchIterator)
1215            }
1216            PartToMerge::Multi { part, .. } => part.read(context, None, None)?,
1217            PartToMerge::Encoded { part, .. } => part.read(context, None, None)?,
1218        };
1219        if plans.is_empty() {
1220            Ok(iter)
1221        } else {
1222            Ok(iter.map(|x| {
1223                Box::new(x.map(move |batch| rewrite_json2_batch(batch?, &plans)))
1224                    as BoxedRecordBatchIterator
1225            }))
1226        }
1227    }
1228}
1229
1230struct MemtableCompactor {
1231    region_id: RegionId,
1232    memtable_id: MemtableId,
1233    /// Configuration for the bulk memtable.
1234    config: BulkMemtableConfig,
1235    /// Max number of rows in a parquet row group for encoded parts.
1236    row_group_size: usize,
1237    float_field_encoding: FloatFieldEncoding,
1238}
1239
1240impl MemtableCompactor {
1241    /// Creates a new MemtableCompactor.
1242    fn new(
1243        region_id: RegionId,
1244        memtable_id: MemtableId,
1245        config: BulkMemtableConfig,
1246        row_group_size: usize,
1247        float_field_encoding: FloatFieldEncoding,
1248    ) -> Self {
1249        Self {
1250            region_id,
1251            memtable_id,
1252            config,
1253            row_group_size,
1254            float_field_encoding,
1255        }
1256    }
1257
1258    /// Merges parts (bulk and encoded) and then encodes the result.
1259    fn merge_parts(
1260        &mut self,
1261        bulk_parts: &RwLock<BulkParts>,
1262        metadata: &RegionMetadataRef,
1263        dedup: bool,
1264        merge_mode: MergeMode,
1265    ) -> Result<()> {
1266        let start = Instant::now();
1267
1268        let collected = {
1269            let mut parts = bulk_parts.write().unwrap();
1270            parts.collect_parts_to_merge(
1271                self.config.merge_threshold,
1272                self.config.max_merge_groups,
1273                self.config.encode_bytes_threshold,
1274            )
1275        };
1276
1277        if collected.groups.is_empty() {
1278            return Ok(());
1279        }
1280
1281        // Collect all file IDs for tracking
1282        let merged_file_ids: HashSet<FileId> = collected
1283            .groups
1284            .iter()
1285            .flatten()
1286            .map(|part| part.file_id())
1287            .collect();
1288        let mut guard = MergingFlagsGuard::new(bulk_parts, &merged_file_ids);
1289
1290        let num_groups = collected.groups.len();
1291        let num_parts: usize = collected.groups.iter().map(|g| g.len()).sum();
1292
1293        let encode_row_threshold = self.config.encode_row_threshold;
1294        let encode_bytes_threshold = self.config.encode_bytes_threshold;
1295        let row_group_size = self.row_group_size;
1296        let float_field_encoding = self.float_field_encoding;
1297
1298        // Merge all groups in parallel
1299        let merged_parts = collected
1300            .groups
1301            .into_par_iter()
1302            .map(|group| {
1303                Self::merge_parts_group(
1304                    group,
1305                    metadata,
1306                    dedup,
1307                    merge_mode,
1308                    encode_row_threshold,
1309                    encode_bytes_threshold,
1310                    row_group_size,
1311                    float_field_encoding,
1312                )
1313            })
1314            .collect::<Result<Vec<Option<MergedPart>>>>()?;
1315
1316        // Install all merged parts
1317        let total_output_rows = {
1318            let mut parts = bulk_parts.write().unwrap();
1319            parts.install_merged_parts(merged_parts.into_iter().flatten(), &merged_file_ids)
1320        };
1321
1322        guard.mark_success();
1323
1324        common_telemetry::debug!(
1325            "BulkMemtable {} {} concurrent compact {} groups, {} parts, {} rows, cost: {:?}",
1326            self.region_id,
1327            self.memtable_id,
1328            num_groups,
1329            num_parts,
1330            total_output_rows,
1331            start.elapsed()
1332        );
1333
1334        Ok(())
1335    }
1336
1337    /// Merges a group of parts into a single part (either MultiBulkPart or EncodedBulkPart).
1338    #[allow(clippy::too_many_arguments)]
1339    fn merge_parts_group(
1340        parts_to_merge: Vec<PartToMerge>,
1341        metadata: &RegionMetadataRef,
1342        dedup: bool,
1343        merge_mode: MergeMode,
1344        encode_row_threshold: usize,
1345        encode_bytes_threshold: usize,
1346        row_group_size: usize,
1347        float_field_encoding: FloatFieldEncoding,
1348    ) -> Result<Option<MergedPart>> {
1349        if parts_to_merge.is_empty() {
1350            return Ok(None);
1351        }
1352
1353        // Calculates timestamp bounds and statistics for merged data
1354        let min_timestamp = parts_to_merge
1355            .iter()
1356            .map(|p| p.min_timestamp())
1357            .min()
1358            .unwrap_or(i64::MAX);
1359        let max_timestamp = parts_to_merge
1360            .iter()
1361            .map(|p| p.max_timestamp())
1362            .max()
1363            .unwrap_or(i64::MIN);
1364        let min_sequence = parts_to_merge
1365            .iter()
1366            .map(PartToMerge::min_sequence)
1367            .min()
1368            .unwrap_or(0);
1369        let max_sequence = parts_to_merge
1370            .iter()
1371            .map(|p| p.max_sequence())
1372            .max()
1373            .unwrap_or(0);
1374
1375        // Collects statistics from parts before creating iterators
1376        let estimated_total_rows: usize = parts_to_merge.iter().map(|p| p.num_rows()).sum();
1377        let estimated_total_bytes: usize = parts_to_merge.iter().map(|p| p.estimated_size()).sum();
1378        let estimated_series_count = parts_to_merge
1379            .iter()
1380            .map(|p| p.series_count())
1381            .max()
1382            .unwrap_or(0);
1383
1384        let batch_size = crate::batch_size::estimate_batch_size(
1385            parts_to_merge
1386                .iter()
1387                .filter_map(PartToMerge::batch_size_statistic),
1388        );
1389        let context = Arc::new(BulkIterContext::new(
1390            metadata.clone(),
1391            None, // No column projection for merging
1392            None, // No predicate for merging
1393            true,
1394            batch_size,
1395        )?);
1396
1397        let schemas = parts_to_merge
1398            .iter()
1399            .map(|part| (part.arrow_schema(), part.num_rows() as u64))
1400            .collect::<Vec<_>>();
1401        let plans = Arc::new(collect_json2_rewrite_plans(metadata, &schemas)?);
1402
1403        debug_assert!(parts_to_merge.windows(2).all(|w| rewrite_json2_schema(
1404            &w[0].arrow_schema(),
1405            &plans
1406        ) == rewrite_json2_schema(
1407            &w[1].arrow_schema(),
1408            &plans
1409        )));
1410        // Parts in one merge group may differ only in their JSON2 physical layouts. So every source
1411        // schema is therefore a valid template for producing the final target schema that has
1412        // the union JSON2 types (rewritten).
1413        let schema = rewrite_json2_schema(&parts_to_merge[0].arrow_schema(), &plans);
1414
1415        let iterators: Vec<BoxedRecordBatchIterator> = parts_to_merge
1416            .into_iter()
1417            .map(|part| part.create_iterator(context.clone(), plans.clone()))
1418            .collect::<Result<Vec<_>>>()?
1419            .into_iter()
1420            .flatten()
1421            .collect();
1422
1423        if iterators.is_empty() {
1424            return Ok(None);
1425        }
1426
1427        let merged_iter = FlatMergeIterator::new(schema.clone(), iterators, batch_size)?;
1428
1429        let boxed_iter: BoxedRecordBatchIterator = if dedup {
1430            match merge_mode {
1431                MergeMode::LastRow => {
1432                    let dedup_iter = FlatDedupIterator::new(merged_iter, FlatLastRow::new(false));
1433                    Box::new(dedup_iter)
1434                }
1435                MergeMode::LastNonNull => {
1436                    let field_column_start = field_column_start(metadata, schema.fields().len());
1437
1438                    let dedup_iter = FlatDedupIterator::new(
1439                        merged_iter,
1440                        FlatLastNonNull::new(field_column_start, false),
1441                    );
1442                    Box::new(dedup_iter)
1443                }
1444            }
1445        } else {
1446            Box::new(merged_iter)
1447        };
1448
1449        // Encode as EncodedBulkPart if rows exceed row threshold OR bytes exceed bytes threshold
1450        if estimated_total_rows > encode_row_threshold
1451            || estimated_total_bytes > encode_bytes_threshold
1452        {
1453            let encoder = BulkPartEncoder::new_with_float_field_encoding(
1454                metadata.clone(),
1455                row_group_size,
1456                float_field_encoding,
1457            )?;
1458            let mut metrics = BulkPartEncodeMetrics::default();
1459            let encoded_part = encoder.encode_record_batch_iter(
1460                boxed_iter,
1461                schema,
1462                min_timestamp,
1463                max_timestamp,
1464                min_sequence..=max_sequence,
1465                &mut metrics,
1466            )?;
1467
1468            common_telemetry::trace!("merge_parts_group metrics: {:?}", metrics);
1469
1470            Ok(encoded_part.map(MergedPart::Encoded))
1471        } else {
1472            // Otherwise, collect into MultiBulkPart
1473            let mut batches = Vec::new();
1474            let mut actual_total_rows = 0;
1475
1476            for batch_result in boxed_iter {
1477                let batch = batch_result?;
1478                actual_total_rows += batch.num_rows();
1479                batches.push(batch);
1480            }
1481
1482            if actual_total_rows == 0 {
1483                return Ok(None);
1484            }
1485
1486            let multi_part = MultiBulkPart::new(
1487                batches,
1488                min_timestamp,
1489                max_timestamp,
1490                min_sequence,
1491                max_sequence,
1492                estimated_series_count,
1493                metadata,
1494            );
1495
1496            common_telemetry::trace!(
1497                "merge_parts_group created MultiBulkPart: rows={}, batches={}",
1498                actual_total_rows,
1499                multi_part.num_batches()
1500            );
1501
1502            Ok(Some(MergedPart::Multi(multi_part)))
1503        }
1504    }
1505}
1506
1507/// Keeps the compaction flag set while queued or running and clears it on drop,
1508/// including failures and cancellation while waiting for a dispatcher permit.
1509struct CompactTaskGuard(Arc<AtomicBool>);
1510
1511impl CompactTaskGuard {
1512    fn try_new(compacting: Arc<AtomicBool>) -> Option<Self> {
1513        // The compactor mutex protects merging; this flag only avoids duplicate tasks.
1514        compacting
1515            .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
1516            .ok()
1517            .map(|_| Self(compacting))
1518    }
1519}
1520
1521impl Drop for CompactTaskGuard {
1522    fn drop(&mut self) {
1523        self.0.store(false, Ordering::Relaxed);
1524    }
1525}
1526
1527/// A memtable compact task to run in background.
1528struct MemCompactTask {
1529    metadata: RegionMetadataRef,
1530    parts: Arc<RwLock<BulkParts>>,
1531    _guard: CompactTaskGuard,
1532    /// Compactor for merging bulk parts
1533    compactor: Arc<Mutex<MemtableCompactor>>,
1534    /// Whether the append mode is enabled
1535    append_mode: bool,
1536    /// Mode to handle duplicate rows while merging
1537    merge_mode: MergeMode,
1538}
1539
1540impl MemCompactTask {
1541    fn compact(&self) -> Result<()> {
1542        self.compactor.lock().unwrap().merge_parts(
1543            &self.parts,
1544            &self.metadata,
1545            !self.append_mode,
1546            self.merge_mode,
1547        )
1548    }
1549}
1550
1551/// Scheduler to run compact tasks in background.
1552#[derive(Debug)]
1553pub struct CompactDispatcher {
1554    semaphore: Arc<Semaphore>,
1555}
1556
1557impl CompactDispatcher {
1558    /// Creates a new dispatcher with the given number of max concurrent tasks.
1559    pub fn new(permits: usize) -> Self {
1560        Self {
1561            semaphore: Arc::new(Semaphore::new(permits)),
1562        }
1563    }
1564
1565    /// Dispatches a compact task to run in background.
1566    fn dispatch_compact(&self, task: MemCompactTask) {
1567        let semaphore = self.semaphore.clone();
1568        common_runtime::spawn_global(async move {
1569            let Ok(permit) = semaphore.acquire_owned().await else {
1570                return;
1571            };
1572
1573            common_runtime::spawn_blocking_global(move || {
1574                // The async task above returns as soon as the blocking task is
1575                // submitted, so the permit has to travel with it to bound the
1576                // compaction that actually runs.
1577                let _permit = permit;
1578                if let Err(e) = task.compact() {
1579                    common_telemetry::error!(e; "Failed to compact memtable, region: {}", task.metadata.region_id);
1580                }
1581            });
1582        });
1583    }
1584}
1585
1586/// Builder to build a [BulkMemtable].
1587#[derive(Debug)]
1588pub struct BulkMemtableBuilder {
1589    /// Configuration for the bulk memtable.
1590    config: BulkMemtableConfig,
1591    write_buffer_manager: Option<WriteBufferManagerRef>,
1592    compact_dispatcher: Option<Arc<CompactDispatcher>>,
1593    append_mode: bool,
1594    merge_mode: MergeMode,
1595    /// Max number of rows in a parquet row group for encoded parts.
1596    row_group_size: usize,
1597    float_field_encoding: FloatFieldEncoding,
1598}
1599
1600impl Default for BulkMemtableBuilder {
1601    fn default() -> Self {
1602        Self {
1603            config: BulkMemtableConfig::default(),
1604            write_buffer_manager: None,
1605            compact_dispatcher: None,
1606            append_mode: false,
1607            merge_mode: MergeMode::default(),
1608            row_group_size: DEFAULT_ROW_GROUP_SIZE,
1609            float_field_encoding: FloatFieldEncoding::default(),
1610        }
1611    }
1612}
1613
1614impl BulkMemtableBuilder {
1615    /// Creates a new builder with specific `write_buffer_manager`.
1616    pub fn new(
1617        write_buffer_manager: Option<WriteBufferManagerRef>,
1618        append_mode: bool,
1619        merge_mode: MergeMode,
1620    ) -> Self {
1621        Self {
1622            write_buffer_manager,
1623            append_mode,
1624            merge_mode,
1625            ..Default::default()
1626        }
1627    }
1628
1629    /// Sets the bulk memtable config.
1630    pub fn with_config(mut self, config: BulkMemtableConfig) -> Self {
1631        self.config = config;
1632        self
1633    }
1634
1635    /// Sets the max number of rows in a parquet row group for encoded parts.
1636    pub fn with_row_group_size(mut self, row_group_size: usize) -> Self {
1637        self.row_group_size = row_group_size;
1638        self
1639    }
1640
1641    pub(super) fn with_float_field_encoding(mut self, encoding: FloatFieldEncoding) -> Self {
1642        self.float_field_encoding = encoding;
1643        self
1644    }
1645
1646    /// Sets the compact dispatcher.
1647    pub fn with_compact_dispatcher(mut self, compact_dispatcher: Arc<CompactDispatcher>) -> Self {
1648        self.compact_dispatcher = Some(compact_dispatcher);
1649        self
1650    }
1651
1652    #[cfg(test)]
1653    pub(crate) fn config(&self) -> &BulkMemtableConfig {
1654        &self.config
1655    }
1656}
1657
1658impl MemtableBuilder for BulkMemtableBuilder {
1659    fn build(&self, id: MemtableId, metadata: &RegionMetadataRef) -> MemtableRef {
1660        Arc::new(BulkMemtable::new_with_row_group_size_and_encoding(
1661            id,
1662            self.config.clone(),
1663            metadata.clone(),
1664            self.write_buffer_manager.clone(),
1665            self.compact_dispatcher.clone(),
1666            self.append_mode,
1667            self.merge_mode,
1668            self.row_group_size,
1669            self.float_field_encoding,
1670        ))
1671    }
1672
1673    fn use_bulk_insert(&self, _metadata: &RegionMetadataRef) -> bool {
1674        true
1675    }
1676}
1677
1678#[cfg(test)]
1679mod tests {
1680    use api::helper::encode_json_value;
1681    use api::v1::value::ValueData;
1682    use api::v1::{Mutation, Row, Rows, SemanticType};
1683    use common_error::ext::WhateverResult;
1684    use datatypes::arrow::array::AsArray;
1685    use datatypes::arrow::datatypes::{DataType as ArrowDataType, UInt64Type};
1686    use datatypes::data_type::ConcreteDataType;
1687    use datatypes::extension::json::{
1688        JSON2_REMAINDER_FIELD_NAME, Json2ExtensionType, Json2PhysicalLayout, JsonMetadata,
1689    };
1690    use datatypes::json::value::JsonValue;
1691    use datatypes::json::{JsonSettings, JsonTypeHint};
1692    use datatypes::schema::ColumnSchema;
1693    use datatypes::types::json_type::{JsonNativeType, JsonObjectType};
1694    use mito_codec::row_converter::build_primary_key_codec;
1695    use parquet::basic::{Encoding, Type as PhysicalType};
1696    use serde_json::json;
1697    use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder, RegionMetadataRef};
1698
1699    use super::*;
1700    use crate::memtable::bulk::part::BulkPartConverter;
1701    use crate::read::scan_region::PredicateGroup;
1702    use crate::sst::parquet::flat_format::sequence_column_index;
1703    use crate::sst::{FlatSchemaOptions, to_flat_sst_arrow_schema};
1704    use crate::test_util::memtable_util::{
1705        build_key_values_with_ts_seq_values, metadata_for_test, region_metadata_to_row_schema,
1706    };
1707
1708    fn create_bulk_part_with_converter(
1709        k0: &str,
1710        k1: u32,
1711        timestamps: Vec<i64>,
1712        values: Vec<Option<f64>>,
1713        sequence: u64,
1714    ) -> Result<BulkPart> {
1715        let metadata = metadata_for_test();
1716        let capacity = 100;
1717        let primary_key_codec = build_primary_key_codec(&metadata);
1718        let schema = to_flat_sst_arrow_schema(
1719            &metadata,
1720            &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
1721        );
1722
1723        let mut converter =
1724            BulkPartConverter::new(&metadata, schema, capacity, primary_key_codec, true);
1725
1726        let key_values = build_key_values_with_ts_seq_values(
1727            &metadata,
1728            k0.to_string(),
1729            k1,
1730            timestamps.into_iter(),
1731            values.into_iter(),
1732            sequence,
1733        );
1734
1735        converter.append_key_values(&key_values)?;
1736        converter.convert()
1737    }
1738
1739    #[test]
1740    fn test_adaptive_encode_bytes_threshold() {
1741        // Below the lower bound: clamped to the default 64 MiB.
1742        assert_eq!(
1743            DEFAULT_ENCODE_BYTES_THRESHOLD,
1744            adaptive_encode_bytes_threshold(1024 * 1024 * 1024) // 1 GiB / 32 = 32 MiB
1745        );
1746        // In range: global_write_buffer_size / 32.
1747        assert_eq!(
1748            256 * 1024 * 1024,
1749            adaptive_encode_bytes_threshold(8 * 1024 * 1024 * 1024) // 8 GiB / 32 = 256 MiB
1750        );
1751        // Above the upper bound: clamped to 512 MiB.
1752        assert_eq!(
1753            MAX_ENCODE_BYTES_THRESHOLD,
1754            adaptive_encode_bytes_threshold(64 * 1024 * 1024 * 1024) // 64 GiB / 32 = 2 GiB
1755        );
1756    }
1757
1758    #[test]
1759    fn test_bulk_memtable_sanitizes_zero_merge_threshold() {
1760        let metadata = metadata_for_test();
1761        let config = BulkMemtableConfig {
1762            merge_threshold: 0,
1763            ..Default::default()
1764        };
1765
1766        let memtable =
1767            BulkMemtable::new(999, config, metadata, None, None, false, MergeMode::LastRow);
1768
1769        assert_eq!(DEFAULT_MERGE_THRESHOLD, memtable.config.merge_threshold);
1770        assert_eq!(
1771            DEFAULT_MERGE_THRESHOLD,
1772            memtable.compactor.lock().unwrap().config.merge_threshold
1773        );
1774    }
1775
1776    #[test]
1777    fn test_min_sequence_survives_mixed_parts_compaction_and_fork() {
1778        let metadata = metadata_for_test();
1779        let config = BulkMemtableConfig {
1780            merge_threshold: 3,
1781            encode_row_threshold: 1,
1782            encode_bytes_threshold: 1,
1783            ..Default::default()
1784        };
1785        let memtable = BulkMemtable::new(
1786            1,
1787            config,
1788            metadata.clone(),
1789            None,
1790            None,
1791            false,
1792            MergeMode::LastNonNull,
1793        );
1794        memtable.set_unordered_part_threshold(0);
1795        for (sequence, expected_min, expected_ranges) in [(100, 100, 1), (10, 10, 2), (200, 10, 1)]
1796        {
1797            let part = create_bulk_part_with_converter(
1798                "a",
1799                1,
1800                vec![2000, 1000],
1801                vec![Some(1.0), Some(2.0)],
1802                sequence,
1803            )
1804            .unwrap();
1805            // Regular row writes produce heterogeneous parts, sorted by timestamp
1806            // rather than sequence. Neither the scalar nor the first row is a minimum.
1807            assert_eq!(sequence + 1, part.sequence);
1808            assert_eq!(sequence, part.min_sequence);
1809            let sequences = part
1810                .batch
1811                .column(sequence_column_index(part.batch.num_columns()))
1812                .as_primitive::<UInt64Type>();
1813            assert_eq!(&[sequence + 1, sequence], sequences.values().as_ref());
1814            memtable.write_bulk(part).unwrap();
1815            assert_eq!(expected_min, memtable.stats().min_sequence);
1816            assert_eq!(expected_min, memtable.min_sequence());
1817            assert_eq!(expected_ranges, memtable.stats().num_ranges);
1818            assert!(!memtable.compacting.load(Ordering::Relaxed));
1819        }
1820        // The third write synchronously merges all parts without a dispatcher.
1821        // Dedup can remove the oldest rows, but their lower bound remains safe.
1822        assert_eq!(10, memtable.stats().min_sequence);
1823        assert_eq!(201, memtable.stats().max_sequence);
1824        let ranges = memtable.ranges(None, RangesOptions::default()).unwrap();
1825        let merged = ranges.ranges.values().next().unwrap();
1826        assert!(merged.encoded().is_some());
1827        assert_eq!(10, merged.stats().min_sequence);
1828        let fork = memtable.fork(2, &metadata);
1829        assert!(fork.is_empty());
1830        assert_eq!(0, fork.min_sequence());
1831        fork.write_bulk(
1832            create_bulk_part_with_converter("a", 1, vec![1000], vec![Some(1.0)], 50).unwrap(),
1833        )
1834        .unwrap();
1835        assert_eq!(50, fork.stats().min_sequence);
1836        assert_eq!(50, fork.min_sequence());
1837    }
1838
1839    #[test]
1840    fn test_bulk_memtable_write_read() {
1841        let metadata = metadata_for_test();
1842        let memtable = BulkMemtable::new(
1843            999,
1844            BulkMemtableConfig::default(),
1845            metadata.clone(),
1846            None,
1847            None,
1848            false,
1849            MergeMode::LastRow,
1850        );
1851        // Disable unordered_part for this test
1852        memtable.set_unordered_part_threshold(0);
1853
1854        let test_data = [
1855            (
1856                "key_a",
1857                1u32,
1858                vec![1000i64, 2000i64],
1859                vec![Some(10.5), Some(20.5)],
1860                100u64,
1861            ),
1862            (
1863                "key_b",
1864                2u32,
1865                vec![1500i64, 2500i64],
1866                vec![Some(15.5), Some(25.5)],
1867                200u64,
1868            ),
1869            ("key_c", 3u32, vec![3000i64], vec![Some(30.5)], 300u64),
1870        ];
1871
1872        for (k0, k1, timestamps, values, seq) in test_data.iter() {
1873            let part =
1874                create_bulk_part_with_converter(k0, *k1, timestamps.clone(), values.clone(), *seq)
1875                    .unwrap();
1876            memtable.write_bulk(part).unwrap();
1877        }
1878
1879        let stats = memtable.stats();
1880        assert_eq!(5, stats.num_rows);
1881        assert_eq!(3, stats.num_ranges);
1882        assert_eq!(300, stats.max_sequence);
1883        assert_eq!(100, stats.min_sequence);
1884
1885        let (min_ts, max_ts) = stats.time_range.unwrap();
1886        assert_eq!(1000, min_ts.value());
1887        assert_eq!(3000, max_ts.value());
1888
1889        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
1890        let ranges = memtable
1891            .ranges(
1892                None,
1893                RangesOptions::default().with_predicate(predicate_group),
1894            )
1895            .unwrap();
1896
1897        assert_eq!(3, ranges.ranges.len());
1898        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
1899        assert_eq!(5, total_rows);
1900
1901        for range in ranges.ranges.values() {
1902            assert!(range.num_rows() > 0);
1903            assert!(range.is_record_batch());
1904
1905            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
1906
1907            let mut total_rows = 0;
1908            for batch_result in record_batch_iter {
1909                let batch = batch_result.unwrap();
1910                total_rows += batch.num_rows();
1911                assert!(batch.num_rows() > 0);
1912                assert_eq!(8, batch.num_columns());
1913            }
1914            assert_eq!(total_rows, range.num_rows());
1915        }
1916    }
1917
1918    #[test]
1919    fn test_bulk_memtable_compact_parts_with_json2() {
1920        let metadata = mock_metadata_with_json2(JsonSettings::default());
1921
1922        let config = BulkMemtableConfig {
1923            merge_threshold: 2,
1924            encode_row_threshold: 1,
1925            encode_bytes_threshold: 1,
1926            ..Default::default()
1927        };
1928        let memtable = BulkMemtable::new(
1929            999,
1930            config,
1931            metadata.clone(),
1932            None,
1933            None,
1934            true,
1935            MergeMode::LastRow,
1936        );
1937        memtable.set_unordered_part_threshold(0);
1938
1939        let part1 = mock_bulk_part_with_json2(&metadata, vec![1000, 2000], 100).unwrap();
1940        let part2 = mock_bulk_part_with_json2(&metadata, vec![3000, 4000], 200).unwrap();
1941
1942        memtable.write_bulk(part1).unwrap();
1943        memtable.write_bulk(part2).unwrap();
1944        memtable.compact(false).unwrap();
1945
1946        let stats = memtable.stats();
1947        assert_eq!(4, stats.num_rows);
1948        assert_eq!(201, stats.max_sequence);
1949
1950        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
1951        let opts = RangesOptions::default().with_predicate(predicate_group);
1952        let ranges = memtable.ranges(None, opts).unwrap();
1953
1954        assert_eq!(1, ranges.ranges.len());
1955        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
1956        assert_eq!(4, total_rows);
1957    }
1958
1959    #[test]
1960    fn test_bulk_memtable_merge_bounds_json2_paths() -> WhateverResult<()> {
1961        let metadata = mock_metadata_with_json2(JsonSettings::try_new(vec![], Some(1))?);
1962        let first = mock_bulk_part_with_json2_values(
1963            &metadata,
1964            vec![1000, 2000],
1965            vec![json!({"a": 1}), json!({"a": 2})],
1966            100,
1967        )?;
1968        let second = mock_bulk_part_with_json2_values(
1969            &metadata,
1970            vec![3000, 4000],
1971            vec![json!({"a": 3, "b": 3}), json!({"a": 4, "b": 4})],
1972            200,
1973        )?;
1974        let parts = vec![
1975            PartToMerge::Bulk {
1976                part: first,
1977                file_id: FileId::random(),
1978            },
1979            PartToMerge::Bulk {
1980                part: second,
1981                file_id: FileId::random(),
1982            },
1983        ];
1984
1985        let merged = MemtableCompactor::merge_parts_group(
1986            parts,
1987            &metadata,
1988            false,
1989            MergeMode::LastRow,
1990            usize::MAX,
1991            usize::MAX,
1992            DEFAULT_ROW_GROUP_SIZE,
1993            FloatFieldEncoding::default(),
1994        )?
1995        .unwrap();
1996        let MergedPart::Multi(part) = merged else {
1997            unreachable!()
1998        };
1999        let schema = part.schemas().next().unwrap();
2000        let ArrowDataType::Struct(fields) = schema.field(0).data_type() else {
2001            unreachable!()
2002        };
2003        assert_eq!(
2004            vec![JSON2_REMAINDER_FIELD_NAME, "a"],
2005            fields.iter().map(|x| x.name().as_str()).collect::<Vec<_>>()
2006        );
2007        Ok(())
2008    }
2009
2010    #[test]
2011    fn test_unordered_parts_align_json2_layouts() -> WhateverResult<()> {
2012        let metadata = mock_metadata_with_json2(JsonSettings::try_new(vec![], Some(2))?);
2013        let memtable = BulkMemtable::new(
2014            42,
2015            BulkMemtableConfig::default(),
2016            metadata.clone(),
2017            None,
2018            None,
2019            false,
2020            MergeMode::LastRow,
2021        );
2022        memtable.write_bulk(mock_bulk_part_with_json2_values(
2023            &metadata,
2024            vec![1000, 2000],
2025            vec![json!({"a": 1}), json!({"a": 2})],
2026            100,
2027        )?)?;
2028        memtable.write_bulk(mock_bulk_part_with_json2_values(
2029            &metadata,
2030            vec![3000, 4000],
2031            vec![json!({"b": 3}), json!({"b": 4})],
2032            200,
2033        )?)?;
2034
2035        let predicate = PredicateGroup::new(&metadata, &[])?;
2036        let ranges = memtable.ranges(None, RangesOptions::default().with_predicate(predicate))?;
2037        let range = ranges.ranges.values().next().unwrap();
2038        let batch = range.build_record_batch_iter(None, None)?.next().unwrap()?;
2039        let schema = batch.schema();
2040        let ArrowDataType::Struct(fields) = schema.field(0).data_type() else {
2041            unreachable!()
2042        };
2043        assert_eq!(
2044            vec![JSON2_REMAINDER_FIELD_NAME],
2045            fields.iter().map(|x| x.name().as_str()).collect::<Vec<_>>()
2046        );
2047        // Neither path is explicit in every source; keep both opaque without losing values.
2048        assert_eq!(4, batch.num_rows());
2049        let json = datatypes::vectors::json::array::JsonArray::from(batch.column(0));
2050        let restored = json.project_to_v2(schema.field(0), &ArrowDataType::Binary)?;
2051        let restored = datatypes::vectors::json::array::JsonArray::from(&restored);
2052        assert_eq!(json!({"a": 1}), restored.try_get_value(0)?);
2053        assert_eq!(json!({"a": 2}), restored.try_get_value(1)?);
2054        assert_eq!(json!({"b": 3}), restored.try_get_value(2)?);
2055        assert_eq!(json!({"b": 4}), restored.try_get_value(3)?);
2056        Ok(())
2057    }
2058
2059    #[test]
2060    fn test_bulk_part_converter_uses_json2_v2_layout() -> WhateverResult<()> {
2061        let settings = JsonSettings::try_new(
2062            vec![JsonTypeHint {
2063                path: vec!["id".to_string()],
2064                data_type: ConcreteDataType::int64_datatype(),
2065                inverted_index: false,
2066            }],
2067            Some(0),
2068        )?;
2069        let metadata = mock_metadata_with_json2(settings);
2070        let part = mock_bulk_part_with_json2(&metadata, vec![1000, 2000], 100)?;
2071        let schema = part.batch.schema();
2072        let field = schema.field(0);
2073        let layout = Json2PhysicalLayout::try_from_root(field)?;
2074
2075        assert!(layout.is_version_2());
2076        let ArrowDataType::Struct(fields) = field.data_type() else {
2077            unreachable!()
2078        };
2079        assert_eq!(
2080            vec![JSON2_REMAINDER_FIELD_NAME, "id"],
2081            fields.iter().map(|x| x.name().as_str()).collect::<Vec<_>>()
2082        );
2083        Ok(())
2084    }
2085
2086    fn mock_metadata_with_json2(settings: JsonSettings) -> RegionMetadataRef {
2087        let col_meta_1 = ColumnMetadata {
2088            column_schema: ColumnSchema::new(
2089                "ts",
2090                ConcreteDataType::timestamp_millisecond_datatype(),
2091                false,
2092            ),
2093            semantic_type: SemanticType::Timestamp,
2094            column_id: 0,
2095        };
2096
2097        let data_type = ConcreteDataType::json2(JsonNativeType::Object(JsonObjectType::new()));
2098        let mut col_schema = ColumnSchema::new("data", data_type, true);
2099        col_schema.with_extension_type(&Json2ExtensionType::new(Arc::new(JsonMetadata::new(
2100            settings,
2101        ))));
2102
2103        let col_meta_2 = ColumnMetadata {
2104            column_schema: col_schema,
2105            semantic_type: SemanticType::Field,
2106            column_id: 1,
2107        };
2108        let mut builder = RegionMetadataBuilder::new(RegionId::new(123, 789));
2109        builder
2110            .push_column_metadata(col_meta_1)
2111            .push_column_metadata(col_meta_2)
2112            .primary_key(vec![]);
2113        Arc::new(builder.build().unwrap())
2114    }
2115
2116    fn mock_bulk_part_with_json2(
2117        metadata: &RegionMetadataRef,
2118        timestamps: Vec<i64>,
2119        sequence: u64,
2120    ) -> Result<BulkPart> {
2121        let values = timestamps
2122            .iter()
2123            .map(|ts| {
2124                json!({
2125                    "id": ts,
2126                    "payload": {
2127                        "message": format!("row-{ts}"),
2128                    },
2129                })
2130            })
2131            .collect();
2132        mock_bulk_part_with_json2_values(metadata, timestamps, values, sequence)
2133    }
2134
2135    fn mock_bulk_part_with_json2_values(
2136        metadata: &RegionMetadataRef,
2137        timestamps: Vec<i64>,
2138        values: Vec<serde_json::Value>,
2139        sequence: u64,
2140    ) -> Result<BulkPart> {
2141        let capacity = timestamps.len();
2142        debug_assert_eq!(capacity, values.len());
2143        let primary_key_codec = build_primary_key_codec(metadata);
2144        let json_type = JsonNativeType::Object(JsonObjectType::from([
2145            ("id".to_string(), JsonNativeType::i64()),
2146            (
2147                "payload".to_string(),
2148                JsonNativeType::Object(JsonObjectType::from([(
2149                    "message".to_string(),
2150                    JsonNativeType::String,
2151                )])),
2152            ),
2153        ]));
2154        let mut options = FlatSchemaOptions::from_encoding(metadata.primary_key_encoding);
2155        options
2156            .concretized_json_types
2157            .insert("data".to_string(), json_type.as_arrow_type());
2158        let schema = to_flat_sst_arrow_schema(metadata, &options);
2159
2160        let mut converter =
2161            BulkPartConverter::new(metadata, schema, capacity, primary_key_codec, true);
2162
2163        let rows = timestamps
2164            .into_iter()
2165            .zip(values)
2166            .map(|(ts, value)| {
2167                let val1 = api::v1::Value {
2168                    value_data: Some(ValueData::TimestampMillisecondValue(ts)),
2169                };
2170                let value_data = ValueData::JsonValue(encode_json_value(JsonValue::from(value)));
2171                let val2 = api::v1::Value {
2172                    value_data: Some(value_data),
2173                };
2174                Row {
2175                    values: vec![val1, val2],
2176                }
2177            })
2178            .collect();
2179
2180        let mutation = Mutation {
2181            op_type: 1,
2182            sequence,
2183            rows: Some(Rows {
2184                schema: region_metadata_to_row_schema(metadata),
2185                rows,
2186            }),
2187            write_hint: None,
2188        };
2189        let key_values = KeyValues::new(metadata.as_ref(), mutation).unwrap();
2190
2191        converter.append_key_values(&key_values)?;
2192        converter.convert()
2193    }
2194
2195    #[test]
2196    fn test_bulk_memtable_ranges_with_projection() {
2197        let metadata = metadata_for_test();
2198        let memtable = BulkMemtable::new(
2199            111,
2200            BulkMemtableConfig::default(),
2201            metadata.clone(),
2202            None,
2203            None,
2204            false,
2205            MergeMode::LastRow,
2206        );
2207
2208        let bulk_part = create_bulk_part_with_converter(
2209            "projection_test",
2210            5,
2211            vec![5000, 6000, 7000],
2212            vec![Some(50.0), Some(60.0), Some(70.0)],
2213            500,
2214        )
2215        .unwrap();
2216
2217        memtable.write_bulk(bulk_part).unwrap();
2218
2219        let projection = vec![4u32];
2220        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2221        let ranges = memtable
2222            .ranges(
2223                Some(&projection),
2224                RangesOptions::default().with_predicate(predicate_group),
2225            )
2226            .unwrap();
2227
2228        assert_eq!(1, ranges.ranges.len());
2229        let range = ranges.ranges.get(&0).unwrap();
2230
2231        assert!(range.is_record_batch());
2232        let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2233
2234        let mut total_rows = 0;
2235        for batch_result in record_batch_iter {
2236            let batch = batch_result.unwrap();
2237            assert!(batch.num_rows() > 0);
2238            assert_eq!(5, batch.num_columns());
2239            total_rows += batch.num_rows();
2240        }
2241        assert_eq!(3, total_rows);
2242    }
2243
2244    #[test]
2245    fn test_bulk_memtable_unsupported_operations() {
2246        let metadata = metadata_for_test();
2247        let memtable = BulkMemtable::new(
2248            111,
2249            BulkMemtableConfig::default(),
2250            metadata.clone(),
2251            None,
2252            None,
2253            false,
2254            MergeMode::LastRow,
2255        );
2256
2257        let key_values = build_key_values_with_ts_seq_values(
2258            &metadata,
2259            "test".to_string(),
2260            1,
2261            vec![1000].into_iter(),
2262            vec![Some(1.0)].into_iter(),
2263            1,
2264        );
2265
2266        let err = memtable.write(&key_values).unwrap_err();
2267        assert!(err.to_string().contains("not supported"));
2268
2269        let kv = key_values.iter().next().unwrap();
2270        let err = memtable.write_one(kv).unwrap_err();
2271        assert!(err.to_string().contains("not supported"));
2272    }
2273
2274    #[test]
2275    fn test_bulk_memtable_freeze() {
2276        let metadata = metadata_for_test();
2277        let memtable = BulkMemtable::new(
2278            222,
2279            BulkMemtableConfig::default(),
2280            metadata.clone(),
2281            None,
2282            None,
2283            false,
2284            MergeMode::LastRow,
2285        );
2286
2287        let bulk_part = create_bulk_part_with_converter(
2288            "freeze_test",
2289            10,
2290            vec![10000],
2291            vec![Some(100.0)],
2292            1000,
2293        )
2294        .unwrap();
2295
2296        memtable.write_bulk(bulk_part).unwrap();
2297        memtable.freeze().unwrap();
2298
2299        let stats_after_freeze = memtable.stats();
2300        assert_eq!(1, stats_after_freeze.num_rows);
2301    }
2302
2303    #[test]
2304    fn test_bulk_memtable_fork() {
2305        let metadata = metadata_for_test();
2306        let original_memtable = BulkMemtable::new_with_row_group_size_and_encoding(
2307            333,
2308            BulkMemtableConfig::default(),
2309            metadata.clone(),
2310            None,
2311            None,
2312            false,
2313            MergeMode::LastRow,
2314            DEFAULT_ROW_GROUP_SIZE,
2315            FloatFieldEncoding::ByteStreamSplit,
2316        );
2317
2318        let bulk_part =
2319            create_bulk_part_with_converter("fork_test", 15, vec![15000], vec![Some(150.0)], 1500)
2320                .unwrap();
2321
2322        original_memtable.write_bulk(bulk_part).unwrap();
2323
2324        let forked_memtable = original_memtable.fork_inner(444, &metadata);
2325
2326        assert_eq!(forked_memtable.id(), 444);
2327        assert_eq!(
2328            FloatFieldEncoding::ByteStreamSplit,
2329            original_memtable.float_field_encoding
2330        );
2331        assert_eq!(
2332            original_memtable.float_field_encoding,
2333            forked_memtable.float_field_encoding
2334        );
2335        assert_eq!(
2336            original_memtable.float_field_encoding,
2337            forked_memtable
2338                .compactor
2339                .lock()
2340                .unwrap()
2341                .float_field_encoding
2342        );
2343        assert!(forked_memtable.is_empty());
2344        assert_eq!(0, forked_memtable.stats().num_rows);
2345
2346        assert!(!original_memtable.is_empty());
2347        assert_eq!(1, original_memtable.stats().num_rows);
2348    }
2349
2350    #[test]
2351    fn test_bulk_memtable_ranges_multiple_parts() {
2352        let metadata = metadata_for_test();
2353        let memtable = BulkMemtable::new(
2354            777,
2355            BulkMemtableConfig::default(),
2356            metadata.clone(),
2357            None,
2358            None,
2359            false,
2360            MergeMode::LastRow,
2361        );
2362        // Disable unordered_part for this test
2363        memtable.set_unordered_part_threshold(0);
2364
2365        let parts_data = vec![
2366            (
2367                "part1",
2368                1u32,
2369                vec![1000i64, 1100i64],
2370                vec![Some(10.0), Some(11.0)],
2371                100u64,
2372            ),
2373            (
2374                "part2",
2375                2u32,
2376                vec![2000i64, 2100i64],
2377                vec![Some(20.0), Some(21.0)],
2378                200u64,
2379            ),
2380            ("part3", 3u32, vec![3000i64], vec![Some(30.0)], 300u64),
2381        ];
2382
2383        for (k0, k1, timestamps, values, seq) in parts_data {
2384            let part = create_bulk_part_with_converter(k0, k1, timestamps, values, seq).unwrap();
2385            memtable.write_bulk(part).unwrap();
2386        }
2387
2388        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2389        let ranges = memtable
2390            .ranges(
2391                None,
2392                RangesOptions::default().with_predicate(predicate_group),
2393            )
2394            .unwrap();
2395
2396        assert_eq!(3, ranges.ranges.len());
2397        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2398        assert_eq!(5, total_rows);
2399        assert_eq!(3, ranges.ranges.len());
2400
2401        for (range_id, range) in ranges.ranges.iter() {
2402            assert!(*range_id < 3);
2403            assert!(range.num_rows() > 0);
2404            assert!(range.is_record_batch());
2405        }
2406    }
2407
2408    #[test]
2409    fn test_bulk_memtable_ranges_with_sequence_filter() {
2410        let metadata = metadata_for_test();
2411        let memtable = BulkMemtable::new(
2412            888,
2413            BulkMemtableConfig::default(),
2414            metadata.clone(),
2415            None,
2416            None,
2417            false,
2418            MergeMode::LastRow,
2419        );
2420
2421        let part = create_bulk_part_with_converter(
2422            "seq_test",
2423            1,
2424            vec![1000, 2000, 3000],
2425            vec![Some(10.0), Some(20.0), Some(30.0)],
2426            500,
2427        )
2428        .unwrap();
2429
2430        memtable.write_bulk(part).unwrap();
2431
2432        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2433        let sequence_filter = Some(SequenceRange::LtEq { max: 400 }); // Filters out rows with sequence > 400
2434        let ranges = memtable
2435            .ranges(
2436                None,
2437                RangesOptions::default()
2438                    .with_predicate(predicate_group)
2439                    .with_sequence(sequence_filter),
2440            )
2441            .unwrap();
2442
2443        assert_eq!(1, ranges.ranges.len());
2444        let range = ranges.ranges.get(&0).unwrap();
2445
2446        let mut record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2447        assert!(record_batch_iter.next().is_none());
2448    }
2449
2450    #[test]
2451    fn test_bulk_memtable_ranges_with_encoded_parts() {
2452        let metadata = metadata_for_test();
2453        let config = BulkMemtableConfig {
2454            merge_threshold: 8,
2455            ..Default::default()
2456        };
2457        let memtable = BulkMemtable::new(
2458            999,
2459            config,
2460            metadata.clone(),
2461            None,
2462            None,
2463            false,
2464            MergeMode::LastRow,
2465        );
2466        // Disable unordered_part for this test
2467        memtable.set_unordered_part_threshold(0);
2468
2469        // Adds enough bulk parts to trigger encoding
2470        for i in 0..10 {
2471            let part = create_bulk_part_with_converter(
2472                &format!("key_{}", i),
2473                i,
2474                vec![1000 + i as i64 * 100],
2475                vec![Some(i as f64 * 10.0)],
2476                100 + i as u64,
2477            )
2478            .unwrap();
2479            memtable.write_bulk(part).unwrap();
2480        }
2481
2482        memtable.compact(false).unwrap();
2483
2484        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2485        let ranges = memtable
2486            .ranges(
2487                None,
2488                RangesOptions::default().with_predicate(predicate_group),
2489            )
2490            .unwrap();
2491
2492        // Should have ranges for both bulk parts and encoded parts
2493        assert_eq!(3, ranges.ranges.len());
2494        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2495        assert_eq!(10, total_rows);
2496
2497        for range in ranges.ranges.values() {
2498            assert!(range.num_rows() > 0);
2499            assert!(range.is_record_batch());
2500
2501            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2502            let mut total_rows = 0;
2503            for batch_result in record_batch_iter {
2504                let batch = batch_result.unwrap();
2505                total_rows += batch.num_rows();
2506                assert!(batch.num_rows() > 0);
2507            }
2508            assert_eq!(total_rows, range.num_rows());
2509        }
2510    }
2511
2512    #[test]
2513    fn test_bulk_memtable_unordered_part() {
2514        let metadata = metadata_for_test();
2515        let memtable = BulkMemtable::new(
2516            1001,
2517            BulkMemtableConfig::default(),
2518            metadata.clone(),
2519            None,
2520            None,
2521            false,
2522            MergeMode::LastRow,
2523        );
2524
2525        // Set smaller thresholds for testing with smaller inputs
2526        // Accept parts with < 5 rows into unordered_part
2527        memtable.set_unordered_part_threshold(5);
2528        // Compact when total rows >= 10
2529        memtable.set_unordered_part_compact_threshold(10);
2530
2531        // Write 3 small parts (each has 2 rows), should be collected in unordered_part
2532        for i in 0..3 {
2533            let part = create_bulk_part_with_converter(
2534                &format!("key_{}", i),
2535                i,
2536                vec![1000 + i as i64 * 100, 1100 + i as i64 * 100],
2537                vec![Some(i as f64 * 10.0), Some(i as f64 * 10.0 + 1.0)],
2538                100 + i as u64,
2539            )
2540            .unwrap();
2541            assert_eq!(2, part.num_rows());
2542            memtable.write_bulk(part).unwrap();
2543        }
2544
2545        // Total rows = 6, not yet reaching compact threshold
2546        let stats = memtable.stats();
2547        assert_eq!(6, stats.num_rows);
2548
2549        // Write 2 more small parts (each has 2 rows)
2550        // This should trigger compaction when total >= 10
2551        for i in 3..5 {
2552            let part = create_bulk_part_with_converter(
2553                &format!("key_{}", i),
2554                i,
2555                vec![1000 + i as i64 * 100, 1100 + i as i64 * 100],
2556                vec![Some(i as f64 * 10.0), Some(i as f64 * 10.0 + 1.0)],
2557                100 + i as u64,
2558            )
2559            .unwrap();
2560            memtable.write_bulk(part).unwrap();
2561        }
2562
2563        // Total rows = 10, should have compacted unordered_part into a regular part
2564        let stats = memtable.stats();
2565        assert_eq!(10, stats.num_rows);
2566
2567        // Verify we can read all data correctly
2568        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2569        let ranges = memtable
2570            .ranges(
2571                None,
2572                RangesOptions::default().with_predicate(predicate_group),
2573            )
2574            .unwrap();
2575
2576        // Should have at least 1 range (the compacted part)
2577        assert!(!ranges.ranges.is_empty());
2578        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2579        assert_eq!(10, total_rows);
2580
2581        // Read all data and verify
2582        let mut total_rows_read = 0;
2583        for range in ranges.ranges.values() {
2584            assert!(range.is_record_batch());
2585            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2586
2587            for batch_result in record_batch_iter {
2588                let batch = batch_result.unwrap();
2589                total_rows_read += batch.num_rows();
2590            }
2591        }
2592        assert_eq!(10, total_rows_read);
2593    }
2594
2595    #[test]
2596    fn test_bulk_memtable_unordered_part_mixed_sizes() {
2597        let metadata = metadata_for_test();
2598        let memtable = BulkMemtable::new(
2599            1002,
2600            BulkMemtableConfig::default(),
2601            metadata.clone(),
2602            None,
2603            None,
2604            false,
2605            MergeMode::LastRow,
2606        );
2607
2608        // Set threshold to 4 rows - parts with < 4 rows go to unordered_part
2609        memtable.set_unordered_part_threshold(4);
2610        memtable.set_unordered_part_compact_threshold(8);
2611
2612        // Write small parts (3 rows each) - should go to unordered_part
2613        for i in 0..2 {
2614            let part = create_bulk_part_with_converter(
2615                &format!("small_{}", i),
2616                i,
2617                vec![1000 + i as i64, 2000 + i as i64, 3000 + i as i64],
2618                vec![Some(i as f64), Some(i as f64 + 1.0), Some(i as f64 + 2.0)],
2619                10 + i as u64,
2620            )
2621            .unwrap();
2622            assert_eq!(3, part.num_rows());
2623            memtable.write_bulk(part).unwrap();
2624        }
2625
2626        // Write a large part (5 rows) - should go directly to regular parts
2627        let large_part = create_bulk_part_with_converter(
2628            "large_key",
2629            100,
2630            vec![5000, 6000, 7000, 8000, 9000],
2631            vec![
2632                Some(100.0),
2633                Some(101.0),
2634                Some(102.0),
2635                Some(103.0),
2636                Some(104.0),
2637            ],
2638            50,
2639        )
2640        .unwrap();
2641        assert_eq!(5, large_part.num_rows());
2642        memtable.write_bulk(large_part).unwrap();
2643
2644        // A later small write fills the unordered part to its compact threshold.
2645        let part = create_bulk_part_with_converter(
2646            "small_2",
2647            2,
2648            vec![4000, 4100],
2649            vec![Some(20.0), Some(21.0)],
2650            30,
2651        )
2652        .unwrap();
2653        memtable.write_bulk(part).unwrap();
2654
2655        let stats = memtable.stats();
2656        assert_eq!(13, stats.num_rows); // 3 + 3 + 5 + 2 = 13
2657
2658        // Verify all data can be read
2659        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2660        let ranges = memtable
2661            .ranges(
2662                None,
2663                RangesOptions::default().with_predicate(predicate_group),
2664            )
2665            .unwrap();
2666
2667        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2668        assert_eq!(13, total_rows);
2669
2670        let mut total_rows_read = 0;
2671        for range in ranges.ranges.values() {
2672            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2673            for batch_result in record_batch_iter {
2674                let batch = batch_result.unwrap();
2675                total_rows_read += batch.num_rows();
2676            }
2677        }
2678        assert_eq!(13, total_rows_read);
2679    }
2680
2681    #[test]
2682    fn test_bulk_memtable_unordered_part_byte_threshold() {
2683        let metadata = metadata_for_test();
2684        let first =
2685            create_bulk_part_with_converter("first", 1, vec![1000], vec![Some(1.0)], 1).unwrap();
2686        let second =
2687            create_bulk_part_with_converter("second", 2, vec![2000], vec![Some(2.0)], 2).unwrap();
2688        let threshold = first.estimated_size().max(second.estimated_size());
2689        let memtable = BulkMemtable::new(
2690            1004,
2691            BulkMemtableConfig {
2692                encode_bytes_threshold: threshold,
2693                ..Default::default()
2694            },
2695            metadata.clone(),
2696            None,
2697            None,
2698            false,
2699            MergeMode::LastRow,
2700        );
2701        memtable.set_unordered_part_compact_threshold(usize::MAX);
2702
2703        // A part exactly at or below the threshold is accepted.
2704        memtable.write_bulk(first).unwrap();
2705        assert_eq!(1, memtable.parts.read().unwrap().unordered_part.num_parts());
2706
2707        // Compact after the accumulated size becomes larger than the threshold.
2708        memtable.write_bulk(second).unwrap();
2709        let parts = memtable.parts.read().unwrap();
2710        assert!(parts.unordered_part.is_empty());
2711        assert_eq!(1, parts.parts.len());
2712        drop(parts);
2713
2714        let oversized =
2715            create_bulk_part_with_converter("oversized", 3, vec![3000], vec![Some(3.0)], 3)
2716                .unwrap();
2717        let oversized_memtable = BulkMemtable::new(
2718            1005,
2719            BulkMemtableConfig {
2720                encode_bytes_threshold: oversized.estimated_size() - 1,
2721                ..Default::default()
2722            },
2723            metadata,
2724            None,
2725            None,
2726            false,
2727            MergeMode::LastRow,
2728        );
2729        oversized_memtable.write_bulk(oversized).unwrap();
2730        let parts = oversized_memtable.parts.read().unwrap();
2731        assert!(parts.unordered_part.is_empty());
2732        assert_eq!(1, parts.parts.len());
2733    }
2734
2735    #[test]
2736    fn test_bulk_memtable_unordered_part_with_ranges() {
2737        let metadata = metadata_for_test();
2738        let memtable = BulkMemtable::new(
2739            1003,
2740            BulkMemtableConfig::default(),
2741            metadata.clone(),
2742            None,
2743            None,
2744            false,
2745            MergeMode::LastRow,
2746        );
2747
2748        // Set small thresholds
2749        memtable.set_unordered_part_threshold(3);
2750        memtable.set_unordered_part_compact_threshold(100); // High threshold to prevent auto-compaction
2751
2752        // Write several small parts that stay in unordered_part
2753        for i in 0..3 {
2754            let part = create_bulk_part_with_converter(
2755                &format!("key_{}", i),
2756                i,
2757                vec![1000 + i as i64 * 100],
2758                vec![Some(i as f64 * 10.0)],
2759                100 + i as u64,
2760            )
2761            .unwrap();
2762            assert_eq!(1, part.num_rows());
2763            memtable.write_bulk(part).unwrap();
2764        }
2765
2766        let stats = memtable.stats();
2767        assert_eq!(3, stats.num_rows);
2768
2769        // Test that ranges() can correctly read from unordered_part
2770        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
2771        let ranges = memtable
2772            .ranges(
2773                None,
2774                RangesOptions::default().with_predicate(predicate_group),
2775            )
2776            .unwrap();
2777
2778        // Should have 1 range for the unordered_part
2779        assert_eq!(1, ranges.ranges.len());
2780        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
2781        assert_eq!(3, total_rows);
2782
2783        // Verify data is sorted correctly in the range
2784        let range = ranges.ranges.get(&0).unwrap();
2785        let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
2786
2787        let mut total_rows = 0;
2788        for batch_result in record_batch_iter {
2789            let batch = batch_result.unwrap();
2790            total_rows += batch.num_rows();
2791            // Verify data is properly sorted by primary key
2792            assert!(batch.num_rows() > 0);
2793        }
2794        assert_eq!(3, total_rows);
2795    }
2796
2797    /// Helper to create a BulkPartWrapper from a BulkPart.
2798    fn create_bulk_part_wrapper(part: BulkPart) -> BulkPartWrapper {
2799        BulkPartWrapper {
2800            part: PartToMerge::Bulk {
2801                part,
2802                file_id: FileId::random(),
2803            },
2804            merging: false,
2805        }
2806    }
2807
2808    #[test]
2809    fn test_may_merge_parts_below_threshold() {
2810        let mut bulk_parts = BulkParts::default();
2811
2812        // Add one fewer part than the merge threshold.
2813        for i in 0..DEFAULT_MERGE_THRESHOLD - 1 {
2814            let part = create_bulk_part_with_converter(
2815                &format!("key_{}", i),
2816                i as u32,
2817                vec![1000 + i as i64 * 100],
2818                vec![Some(i as f64 * 10.0)],
2819                100 + i as u64,
2820            )
2821            .unwrap();
2822            bulk_parts.parts.push(create_bulk_part_wrapper(part));
2823            if i == 0 {
2824                assert!(!bulk_parts.may_merge_parts(0));
2825                assert!(!bulk_parts.may_merge_parts(1));
2826            }
2827        }
2828
2829        // There are not enough parts to merge.
2830        assert!(!bulk_parts.may_merge_parts(DEFAULT_MERGE_THRESHOLD));
2831    }
2832
2833    #[test]
2834    fn test_may_merge_parts_at_threshold() {
2835        let mut bulk_parts = BulkParts::default();
2836        let merge_threshold = 8;
2837
2838        // Add 8 bulk parts (at merge_threshold)
2839        for i in 0..merge_threshold {
2840            let part = create_bulk_part_with_converter(
2841                &format!("key_{}", i),
2842                i as u32,
2843                vec![1000 + i as i64 * 100],
2844                vec![Some(i as f64 * 10.0)],
2845                100 + i as u64,
2846            )
2847            .unwrap();
2848            bulk_parts.parts.push(create_bulk_part_wrapper(part));
2849        }
2850
2851        // Should trigger merge since we have 8 parts
2852        assert!(bulk_parts.may_merge_parts(merge_threshold));
2853    }
2854
2855    #[test]
2856    fn test_raw_parts_do_not_use_encoded_merge_limit() {
2857        let mut bulk_parts = BulkParts::default();
2858        let merge_threshold = 4;
2859        for i in 0..merge_threshold {
2860            let part = create_bulk_part_with_converter(
2861                &format!("key_{}", i),
2862                i as u32,
2863                vec![1000 + i as i64],
2864                vec![Some(i as f64)],
2865                100 + i as u64,
2866            )
2867            .unwrap();
2868            bulk_parts.parts.push(create_bulk_part_wrapper(part));
2869        }
2870
2871        let max_size = bulk_parts.parts[0].part.estimated_size();
2872        assert!(bulk_parts.may_merge_parts(merge_threshold));
2873        assert_eq!(
2874            1,
2875            bulk_parts
2876                .collect_parts_to_merge(merge_threshold, 1, max_size)
2877                .groups
2878                .len()
2879        );
2880    }
2881
2882    #[test]
2883    fn test_may_merge_parts_with_merging_flag() {
2884        let mut bulk_parts = BulkParts::default();
2885        let merge_threshold = 8;
2886
2887        // Add 10 bulk parts
2888        for i in 0..10 {
2889            let part = create_bulk_part_with_converter(
2890                &format!("key_{}", i),
2891                i as u32,
2892                vec![1000 + i as i64 * 100],
2893                vec![Some(i as f64 * 10.0)],
2894                100 + i as u64,
2895            )
2896            .unwrap();
2897            bulk_parts.parts.push(create_bulk_part_wrapper(part));
2898        }
2899
2900        // Should trigger merge since we have 10 parts
2901        assert!(bulk_parts.may_merge_parts(merge_threshold));
2902
2903        // Mark first 3 parts as merging
2904        for wrapper in bulk_parts.parts.iter_mut().take(3) {
2905            wrapper.merging = true;
2906        }
2907
2908        // Now only 7 parts are available for merging, should not trigger
2909        assert!(!bulk_parts.may_merge_parts(merge_threshold));
2910    }
2911
2912    #[test]
2913    fn test_compact_task_guard() {
2914        let compacting = Arc::new(AtomicBool::new(false));
2915        let guard = CompactTaskGuard::try_new(compacting.clone()).unwrap();
2916        assert!(compacting.load(Ordering::Relaxed));
2917        assert!(CompactTaskGuard::try_new(compacting.clone()).is_none());
2918        drop(guard);
2919        assert!(!compacting.load(Ordering::Relaxed));
2920        assert!(CompactTaskGuard::try_new(compacting.clone()).is_some());
2921        assert!(!compacting.load(Ordering::Relaxed));
2922    }
2923
2924    #[test]
2925    fn test_compact_scheduling_disabled() {
2926        let memtable = BulkMemtable::new(
2927            0,
2928            BulkMemtableConfig {
2929                merge_threshold: 2,
2930                max_merge_groups: 0,
2931                ..Default::default()
2932            },
2933            metadata_for_test(),
2934            None,
2935            Some(Arc::new(CompactDispatcher::new(0))),
2936            false,
2937            MergeMode::LastRow,
2938        );
2939        let part =
2940            create_bulk_part_with_converter("key", 0, vec![1000], vec![Some(1.0)], 0).unwrap();
2941        memtable.parts.write().unwrap().parts = vec![
2942            create_bulk_part_wrapper(part.clone()),
2943            create_bulk_part_wrapper(part),
2944        ];
2945        memtable.schedule_compact();
2946        assert!(!memtable.compacting.load(Ordering::Relaxed));
2947    }
2948
2949    #[test]
2950    fn test_collect_parts_to_merge_sequence_components() {
2951        let make_part = |min, max| {
2952            let mut part =
2953                create_bulk_part_with_converter("key", 0, vec![1000], vec![Some(1.0)], max)
2954                    .unwrap();
2955            part.min_sequence = min;
2956            part
2957        };
2958        let metadata = metadata_for_test();
2959        let encoder = BulkPartEncoder::new(metadata.clone(), DEFAULT_ROW_GROUP_SIZE).unwrap();
2960        // Deliberately use installation order rather than sequence order.
2961        let mut parts = BulkParts {
2962            parts: vec![
2963                create_bulk_part_wrapper(make_part(70, 70)),
2964                create_bulk_part_wrapper(make_part(10, 30)),
2965                BulkPartWrapper {
2966                    part: PartToMerge::Multi {
2967                        part: MultiBulkPart::from_bulk_part(make_part(35, 50), &metadata),
2968                        file_id: FileId::random(),
2969                    },
2970                    merging: false,
2971                },
2972                create_bulk_part_wrapper(make_part(60, 60)),
2973                BulkPartWrapper {
2974                    part: PartToMerge::Encoded {
2975                        part: encoder.encode_part(&make_part(20, 40)).unwrap().unwrap(),
2976                        file_id: FileId::random(),
2977                    },
2978                    merging: false,
2979                },
2980            ],
2981            ..Default::default()
2982        };
2983        // The oversized encoded part connects three intervals transitively;
2984        // neither the merge threshold nor its size may split that component.
2985        assert!(parts.may_merge_parts(parts.parts.len()));
2986        assert_eq!(
2987            vec![vec![1, 4, 2], vec![3, 0]],
2988            parts.pick_merge_groups(2, 2, 1)
2989        );
2990        // The hint may schedule a merge even when unordered writes block all parts.
2991        parts.unordered_part.push(make_part(0, 100));
2992        assert!(parts.may_merge_parts(2));
2993        assert!(parts.pick_merge_groups(2, 2, 1).is_empty());
2994        parts.unordered_part.clear();
2995
2996        // Touching the unordered interval blocks the entire transitive component.
2997        parts.unordered_part.push(make_part(50, 55));
2998        assert_eq!(vec![vec![3, 0]], parts.pick_merge_groups(2, 2, 1));
2999        parts.unordered_part.clear();
3000
3001        // Even without overlap, a group cannot straddle pending small writes.
3002        parts.unordered_part.push(make_part(65, 65));
3003        assert_eq!(vec![vec![1, 4, 2]], parts.pick_merge_groups(2, 2, 1));
3004        parts.unordered_part.clear();
3005
3006        parts.parts[4].merging = true;
3007        let selected = parts.collect_parts_to_merge(2, 2, 1);
3008        assert_eq!(1, selected.groups.len());
3009        assert_eq!(
3010            vec![60, 70],
3011            selected.groups[0]
3012                .iter()
3013                .map(PartToMerge::max_sequence)
3014                .collect::<Vec<_>>()
3015        );
3016        assert!(!parts.parts[1].merging);
3017        assert!(!parts.parts[2].merging);
3018    }
3019
3020    #[test]
3021    fn test_encoded_merge_candidate_size_limit() {
3022        assert!(BulkParts::is_merge_candidate_by_size(false, usize::MAX, 8));
3023        assert!(BulkParts::is_merge_candidate_by_size(true, 8, 8));
3024        assert!(!BulkParts::is_merge_candidate_by_size(true, 9, 8));
3025    }
3026
3027    #[test]
3028    fn test_bulk_part_encoder_preserves_byte_stream_split() -> WhateverResult<()> {
3029        let metadata = metadata_for_test();
3030        let bulk_part = create_bulk_part_with_converter(
3031            "byte_stream_split",
3032            0,
3033            vec![1000, 2000],
3034            vec![Some(10.0), Some(20.0)],
3035            100,
3036        )?;
3037        let encoder = BulkPartEncoder::new_with_float_field_encoding(
3038            metadata,
3039            DEFAULT_ROW_GROUP_SIZE,
3040            FloatFieldEncoding::ByteStreamSplit,
3041        )?;
3042        let encoded_part = encoder.encode_part(&bulk_part)?.unwrap();
3043        let field_column = encoded_part
3044            .metadata()
3045            .parquet_metadata
3046            .row_groups()
3047            .iter()
3048            .flat_map(|row_group| row_group.columns())
3049            .find(|column| {
3050                column.column_path().string() == "v1"
3051                    && column.column_type() == PhysicalType::DOUBLE
3052            })
3053            .expect("Float64 field should be present in encoded bulk part");
3054
3055        assert!(
3056            field_column
3057                .encodings()
3058                .any(|encoding| encoding == Encoding::BYTE_STREAM_SPLIT)
3059        );
3060        Ok(())
3061    }
3062
3063    #[test]
3064    fn test_encoded_part_batch_size_uses_largest_uncompressed_row_group() {
3065        const NUM_ROWS: usize = 14;
3066        const ROW_GROUP_SIZE: usize = 4;
3067
3068        let metadata = metadata_for_test();
3069        let timestamps = (0..NUM_ROWS as i64).collect::<Vec<_>>();
3070        let field_values = (0..NUM_ROWS)
3071            .map(|value| Some(value as f64))
3072            .collect::<Vec<_>>();
3073        let bulk_part =
3074            create_bulk_part_with_converter("key", 0, timestamps, field_values, 0).unwrap();
3075        let encoder = BulkPartEncoder::new(metadata, ROW_GROUP_SIZE).unwrap();
3076        let encoded_part = encoder.encode_part(&bulk_part).unwrap().unwrap();
3077        let max_row_group = encoded_part
3078            .metadata()
3079            .parquet_metadata
3080            .row_groups()
3081            .iter()
3082            .max_by_key(|row_group| {
3083                row_group
3084                    .columns()
3085                    .iter()
3086                    .map(|column| column.uncompressed_size() as u64)
3087                    .sum::<u64>()
3088            })
3089            .unwrap();
3090        let max_uncompressed_size = max_row_group
3091            .columns()
3092            .iter()
3093            .map(|column| column.uncompressed_size() as u64)
3094            .sum();
3095        let expected = (max_row_group.num_rows() as u64, max_uncompressed_size);
3096        let part = PartToMerge::Encoded {
3097            part: encoded_part,
3098            file_id: FileId::random(),
3099        };
3100
3101        assert_eq!(Some(expected), part.batch_size_statistic());
3102    }
3103
3104    #[test]
3105    fn test_bulk_memtable_ranges_with_multi_bulk_part() {
3106        let metadata = metadata_for_test();
3107        let merge_threshold = 8;
3108        let config = BulkMemtableConfig {
3109            merge_threshold,
3110            ..Default::default()
3111        };
3112        let memtable = BulkMemtable::new(
3113            2005,
3114            config,
3115            metadata.clone(),
3116            None,
3117            None,
3118            false,
3119            MergeMode::LastRow,
3120        );
3121        // Disable unordered_part for this test
3122        memtable.set_unordered_part_threshold(0);
3123
3124        // Write enough bulk parts to trigger merge (merge_threshold = 8)
3125        // Each part has small number of rows so total < DEFAULT_ROW_GROUP_SIZE
3126        // This will result in MultiBulkPart after compaction
3127        for i in 0..merge_threshold {
3128            let part = create_bulk_part_with_converter(
3129                &format!("key_{}", i),
3130                i as u32,
3131                vec![1000 + i as i64 * 100, 2000 + i as i64 * 100],
3132                vec![Some(i as f64 * 10.0), Some(i as f64 * 10.0 + 1.0)],
3133                100 + i as u64,
3134            )
3135            .unwrap();
3136            memtable.write_bulk(part).unwrap();
3137        }
3138
3139        // Compact to trigger MultiBulkPart creation (since total rows < DEFAULT_ROW_GROUP_SIZE)
3140        memtable.compact(false).unwrap();
3141
3142        // Verify we can read from the memtable
3143        let predicate_group = PredicateGroup::new(&metadata, &[]).unwrap();
3144        let ranges = memtable
3145            .ranges(
3146                None,
3147                RangesOptions::default().with_predicate(predicate_group),
3148            )
3149            .unwrap();
3150
3151        assert_eq!(1, ranges.ranges.len());
3152        let expected_rows = merge_threshold * 2; // Each part has 2 rows
3153        let total_rows: usize = ranges.ranges.values().map(|r| r.stats().num_rows()).sum();
3154        assert_eq!(expected_rows, total_rows);
3155
3156        // Read all data
3157        let mut total_rows_read = 0;
3158        for range in ranges.ranges.values() {
3159            assert!(range.is_record_batch());
3160            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
3161
3162            for batch_result in record_batch_iter {
3163                let batch = batch_result.unwrap();
3164                total_rows_read += batch.num_rows();
3165            }
3166        }
3167        assert_eq!(expected_rows, total_rows_read);
3168    }
3169
3170    #[test]
3171    fn test_multi_bulk_range_iter_builder_all_pruned() {
3172        let metadata = metadata_for_test();
3173        let merge_threshold = 8;
3174        let config = BulkMemtableConfig {
3175            merge_threshold,
3176            ..Default::default()
3177        };
3178        let memtable = BulkMemtable::new(
3179            2006,
3180            config,
3181            metadata.clone(),
3182            None,
3183            None,
3184            false,
3185            MergeMode::LastRow,
3186        );
3187        memtable.set_unordered_part_threshold(0);
3188
3189        // Write enough bulk parts to trigger merge into MultiBulkPart.
3190        for i in 0..merge_threshold {
3191            let part = create_bulk_part_with_converter(
3192                &format!("key_{}", i),
3193                i as u32,
3194                vec![1000 + i as i64 * 100, 2000 + i as i64 * 100],
3195                vec![Some(i as f64 * 10.0), Some(i as f64 * 10.0 + 1.0)],
3196                100 + i as u64,
3197            )
3198            .unwrap();
3199            memtable.write_bulk(part).unwrap();
3200        }
3201        memtable.compact(false).unwrap();
3202
3203        // Use a predicate that matches no rows so all batches are pruned.
3204        let filter = datafusion_expr::col("k0").eq(datafusion_expr::lit("nonexistent"));
3205        let predicate_group = PredicateGroup::new(&metadata, &[filter]).unwrap();
3206        let ranges = memtable
3207            .ranges(
3208                None,
3209                RangesOptions::default().with_predicate(predicate_group),
3210            )
3211            .unwrap();
3212
3213        // Should return ranges but each range should produce an empty iterator
3214        // instead of an error.
3215        for range in ranges.ranges.values() {
3216            assert!(range.is_record_batch());
3217            let record_batch_iter = range.build_record_batch_iter(None, None).unwrap();
3218            let total_rows: usize = record_batch_iter.map(|r| r.unwrap().num_rows()).sum();
3219            assert_eq!(0, total_rows);
3220        }
3221    }
3222}