Skip to main content

mito2/read/
flat_merge.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#[cfg(test)]
16use std::cell::Cell;
17use std::cmp::Ordering;
18use std::collections::BinaryHeap;
19use std::fmt;
20use std::ops::Range;
21use std::sync::Arc;
22use std::time::{Duration, Instant};
23
24use async_stream::try_stream;
25use common_telemetry::debug;
26use datatypes::arrow::array::{
27    Array, ArrayRef, AsArray, BinaryBuilder, Int64Array, UInt32Array, UInt64Array,
28};
29use datatypes::arrow::compute::interleave;
30use datatypes::arrow::datatypes::{ArrowNativeType, BinaryType, DataType, SchemaRef, Utf8Type};
31use datatypes::arrow::error::ArrowError;
32use datatypes::arrow::record_batch::RecordBatch;
33use datatypes::arrow_array::BinaryArray;
34use datatypes::timestamp::timestamp_array_to_primitive;
35use futures::{Stream, TryStreamExt};
36use snafu::{OptionExt, ResultExt, ensure};
37use store_api::storage::SequenceNumber;
38use store_api::storage::consts::{PRIMARY_KEY_COLUMN_NAME, SEQUENCE_COLUMN_NAME};
39use winner_tree::WinnerTree;
40
41use crate::error::{ComputeArrowSnafu, InvalidRecordBatchSnafu, Result};
42use crate::memtable::BoxedRecordBatchIterator;
43use crate::metrics::READ_STAGE_ELAPSED;
44use crate::read::BoxedRecordBatchStream;
45use crate::sst::parquet::flat_format::{
46    primary_key_column_index, sequence_column_index, time_index_column_index,
47};
48use crate::sst::parquet::format::{FIXED_POS_COLUMN_NUM, PrimaryKeyArray};
49
50/// Checks whether interleaving the selected rows from byte columns would overflow
51/// i32 offsets. Similar to arrow-rs `interleave_bytes()`, accumulates offsets and
52/// returns an error if the capacity exceeds `i32::MAX`.
53///
54/// TODO(yingwen): Remove this after upgrading to arrow >= 58.1.0, which handles
55/// offset overflow in `interleave_bytes()` natively.
56///
57/// See: <https://github.com/apache/arrow-rs/blob/65ad652f2410fc51ad77da1805e85c0a76d9a7ea/arrow-select/src/interleave.rs#L208-L225>
58fn check_interleave_bytes_overflow<T: datatypes::arrow::datatypes::ByteArrayType>(
59    batches: &[(usize, RecordBatch)],
60    col_idx: usize,
61    indices: &[(usize, usize)],
62) -> std::result::Result<(), ArrowError> {
63    // Quick check: if concatenating all value data won't overflow, interleaving
64    // a subset of rows definitely won't either.
65    let total: usize = batches
66        .iter()
67        .map(|(_, batch)| batch.column(col_idx).as_bytes::<T>().value_data().len())
68        .sum();
69    if T::Offset::from_usize(total).is_some() {
70        return Ok(());
71    }
72    // Total exceeds the offset limit, do the precise per-row check.
73    let mut capacity: usize = 0;
74    for &(a, b) in indices {
75        let array = batches[a].1.column(col_idx).as_bytes::<T>();
76        let o = array.value_offsets();
77        let element_len = o[b + 1].as_usize() - o[b].as_usize();
78        capacity += element_len;
79        T::Offset::from_usize(capacity).ok_or(ArrowError::OffsetOverflowError(capacity))?;
80    }
81    Ok(())
82}
83
84/// Checks whether `interleave()` would overflow i32 offsets for `Utf8` or `Binary` columns.
85fn check_interleave_overflow(
86    batches: &[(usize, RecordBatch)],
87    schema: &SchemaRef,
88    indices: &[(usize, usize)],
89) -> Result<()> {
90    for (col_idx, field) in schema.fields.iter().enumerate() {
91        match field.data_type() {
92            DataType::Utf8 => {
93                check_interleave_bytes_overflow::<Utf8Type>(batches, col_idx, indices)
94                    .context(ComputeArrowSnafu)?;
95            }
96            DataType::Binary => {
97                check_interleave_bytes_overflow::<BinaryType>(batches, col_idx, indices)
98                    .context(ComputeArrowSnafu)?;
99            }
100            _ => continue,
101        }
102    }
103    Ok(())
104}
105
106/// Interleaves the non-null internal primary-key column from globally sorted rows.
107fn interleave_primary_key(
108    arrays: &[&dyn Array],
109    indices: &[(usize, usize)],
110) -> std::result::Result<ArrayRef, ArrowError> {
111    if arrays.is_empty() {
112        return Err(ArrowError::InvalidArgumentError(
113            "interleave requires input of at least one array".to_string(),
114        ));
115    }
116
117    let dictionaries = arrays
118        .iter()
119        .map(|array| {
120            let dictionary = array
121                .as_any()
122                .downcast_ref::<PrimaryKeyArray>()
123                .ok_or_else(|| {
124                    ArrowError::CastError(format!(
125                        "expected Dictionary(UInt32, Binary) primary key, got {}",
126                        array.data_type()
127                    ))
128                })?;
129            let values = dictionary
130                .values()
131                .as_any()
132                .downcast_ref::<BinaryArray>()
133                .ok_or_else(|| {
134                    ArrowError::CastError(format!(
135                        "expected Binary primary-key dictionary values, got {}",
136                        dictionary.values().data_type()
137                    ))
138                })?;
139            Ok((dictionary, values))
140        })
141        .collect::<std::result::Result<Vec<_>, ArrowError>>()?;
142
143    let mut keys = Vec::with_capacity(indices.len());
144    let mut values = BinaryBuilder::with_capacity(indices.len(), 0);
145    let mut previous_primary_key = None;
146    let mut current_key = 0;
147    let mut num_dictionary_values = 0_usize;
148    let mut value_bytes = 0_usize;
149
150    for &(array_idx, row_idx) in indices {
151        let (dictionary, dictionary_values) = dictionaries.get(array_idx).ok_or_else(|| {
152            ArrowError::InvalidArgumentError(format!(
153                "primary-key source index {array_idx} is out of bounds for {} arrays",
154                dictionaries.len()
155            ))
156        })?;
157        if row_idx >= dictionary.len() {
158            return Err(ArrowError::InvalidArgumentError(format!(
159                "primary-key row index {row_idx} is out of bounds for array of length {}",
160                dictionary.len()
161            )));
162        }
163        let source_key = dictionary.key(row_idx).ok_or_else(|| {
164            ArrowError::InvalidArgumentError(
165                "internal primary-key dictionary contains a null key".to_string(),
166            )
167        })?;
168        if dictionary_values.is_null(source_key) {
169            return Err(ArrowError::InvalidArgumentError(
170                "internal primary-key dictionary contains a null dictionary value".to_string(),
171            ));
172        }
173        let primary_key = dictionary_values.value(source_key);
174
175        if previous_primary_key != Some(primary_key) {
176            current_key = u32::try_from(num_dictionary_values)
177                .map_err(|_| ArrowError::DictionaryKeyOverflowError)?;
178            value_bytes = value_bytes.checked_add(primary_key.len()).ok_or_else(|| {
179                ArrowError::ArithmeticOverflow(
180                    "primary-key dictionary value length overflow".to_string(),
181                )
182            })?;
183            if value_bytes > i32::MAX as usize {
184                return Err(ArrowError::OffsetOverflowError(value_bytes));
185            }
186            values.append_value(primary_key);
187            num_dictionary_values += 1;
188            previous_primary_key = Some(primary_key);
189        }
190        keys.push(current_key);
191    }
192
193    let dictionary = PrimaryKeyArray::try_new(UInt32Array::from(keys), Arc::new(values.finish()))?;
194    Ok(Arc::new(dictionary))
195}
196
197/// Keeps track of the current position in a batch
198#[derive(Debug, Copy, Clone, Default)]
199struct BatchCursor {
200    /// The index into BatchBuilder::batches
201    batch_idx: usize,
202    /// The row index within the given batch
203    row_idx: usize,
204}
205
206/// Trait for reporting merge metrics.
207pub trait MergeMetricsReport: Send + Sync {
208    /// Reports and resets the metrics.
209    fn report(&self, metrics: &mut MergeMetrics);
210}
211
212/// Metrics for the merge reader.
213#[derive(Default)]
214pub struct MergeMetrics {
215    /// Cost to initialize the reader.
216    pub(crate) init_cost: Duration,
217    /// Total scan cost of the reader.
218    pub(crate) scan_cost: Duration,
219    /// Number of times to fetch batches.
220    pub(crate) num_fetch_by_batches: usize,
221    /// Number of times to fetch rows.
222    pub(crate) num_fetch_by_rows: usize,
223    /// Cost to fetch batches from sources.
224    pub(crate) fetch_cost: Duration,
225}
226
227impl fmt::Debug for MergeMetrics {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        if self.scan_cost.is_zero() {
230            return write!(f, "{{}}");
231        }
232
233        write!(f, r#"{{"scan_cost":"{:?}""#, self.scan_cost)?;
234
235        if !self.init_cost.is_zero() {
236            write!(f, r#", "init_cost":"{:?}""#, self.init_cost)?;
237        }
238        if self.num_fetch_by_batches > 0 {
239            write!(
240                f,
241                r#", "num_fetch_by_batches":{}"#,
242                self.num_fetch_by_batches
243            )?;
244        }
245        if self.num_fetch_by_rows > 0 {
246            write!(f, r#", "num_fetch_by_rows":{}"#, self.num_fetch_by_rows)?;
247        }
248        if !self.fetch_cost.is_zero() {
249            write!(f, r#", "fetch_cost":"{:?}""#, self.fetch_cost)?;
250        }
251
252        write!(f, "}}")
253    }
254}
255
256impl MergeMetrics {
257    /// Merges metrics from another MergeMetrics instance.
258    pub(crate) fn merge(&mut self, other: &MergeMetrics) {
259        let MergeMetrics {
260            init_cost,
261            scan_cost,
262            num_fetch_by_batches,
263            num_fetch_by_rows,
264            fetch_cost,
265        } = other;
266
267        self.init_cost += *init_cost;
268        self.scan_cost += *scan_cost;
269        self.num_fetch_by_batches += *num_fetch_by_batches;
270        self.num_fetch_by_rows += *num_fetch_by_rows;
271        self.fetch_cost += *fetch_cost;
272    }
273
274    /// Reports the metrics if scan_cost exceeds 10ms and resets them.
275    pub(crate) fn maybe_report(&mut self, reporter: &Option<Arc<dyn MergeMetricsReport>>) {
276        if self.scan_cost.as_millis() > 10
277            && let Some(r) = reporter
278        {
279            r.report(self);
280        }
281    }
282}
283
284/// Provides an API to incrementally build a [`RecordBatch`] from partitioned [`RecordBatch`]
285// Ports from https://github.com/apache/datafusion/blob/49.0.0/datafusion/physical-plan/src/sorts/builder.rs
286// Adds the `take_remaining_rows()` method.
287#[derive(Debug)]
288pub struct BatchBuilder {
289    /// The schema of the RecordBatches yielded by this stream
290    schema: SchemaRef,
291
292    /// Index of the internal primary key column, if present.
293    primary_key_column_idx: Option<usize>,
294
295    /// Maintain a list of [`RecordBatch`] and their corresponding stream
296    batches: Vec<(usize, RecordBatch)>,
297
298    /// The current [`BatchCursor`] for each stream
299    cursors: Vec<BatchCursor>,
300
301    /// The accumulated stream indexes from which to pull rows
302    /// Consists of a tuple of `(batch_idx, row_idx)`
303    indices: Vec<(usize, usize)>,
304}
305
306impl BatchBuilder {
307    /// Create a new [`BatchBuilder`] with the provided `stream_count` and `batch_size`
308    pub fn new(schema: SchemaRef, stream_count: usize, batch_size: usize) -> Self {
309        // A real flat-format schema always has at least 4 columns (time index
310        // plus the 3 internal columns); the `>= 3` check below only keeps
311        // `primary_key_column_index` (`num_columns - 3`) from underflowing on
312        // generic schemas without internal columns.
313        let primary_key_column_idx = (schema.fields.len() >= 3)
314            .then(|| primary_key_column_index(schema.fields.len()))
315            .filter(|&column_idx| schema.field(column_idx).name() == PRIMARY_KEY_COLUMN_NAME);
316        Self {
317            schema,
318            primary_key_column_idx,
319            batches: Vec::with_capacity(stream_count * 2),
320            cursors: vec![BatchCursor::default(); stream_count],
321            indices: Vec::with_capacity(batch_size),
322        }
323    }
324
325    /// Append a new batch in `stream_idx`
326    pub fn push_batch(&mut self, stream_idx: usize, batch: RecordBatch) {
327        let batch_idx = self.batches.len();
328        self.batches.push((stream_idx, batch));
329        self.cursors[stream_idx] = BatchCursor {
330            batch_idx,
331            row_idx: 0,
332        };
333    }
334
335    /// Append the next row from `stream_idx`
336    pub fn push_row(&mut self, stream_idx: usize) {
337        let cursor = &mut self.cursors[stream_idx];
338        let row_idx = cursor.row_idx;
339        cursor.row_idx += 1;
340        self.indices.push((cursor.batch_idx, row_idx));
341    }
342
343    /// Returns the number of in-progress rows in this [`BatchBuilder`]
344    pub fn len(&self) -> usize {
345        self.indices.len()
346    }
347
348    /// Returns `true` if this [`BatchBuilder`] contains no in-progress rows
349    pub fn is_empty(&self) -> bool {
350        self.indices.is_empty()
351    }
352
353    /// Returns the schema of this [`BatchBuilder`]
354    pub fn schema(&self) -> &SchemaRef {
355        &self.schema
356    }
357
358    /// Drains the in_progress row indexes, and builds a new RecordBatch from them
359    ///
360    /// Will then drop any batches for which all rows have been yielded to the output
361    ///
362    /// Returns `None` if no pending rows
363    pub fn build_record_batch(&mut self) -> Result<Option<RecordBatch>> {
364        if self.is_empty() {
365            return Ok(None);
366        }
367
368        check_interleave_overflow(&self.batches, &self.schema, &self.indices)?;
369
370        let columns = (0..self.schema.fields.len())
371            .map(|column_idx| {
372                let arrays: Vec<_> = self
373                    .batches
374                    .iter()
375                    .map(|(_, batch)| batch.column(column_idx).as_ref())
376                    .collect();
377                if Some(column_idx) == self.primary_key_column_idx {
378                    interleave_primary_key(&arrays, &self.indices).context(ComputeArrowSnafu)
379                } else {
380                    interleave(&arrays, &self.indices).context(ComputeArrowSnafu)
381                }
382            })
383            .collect::<Result<Vec<_>>>()?;
384
385        self.indices.clear();
386
387        // New cursors are only created once the previous cursor for the stream
388        // is finished. This means all remaining rows from all but the last batch
389        // for each stream have been yielded to the newly created record batch
390        //
391        // We can therefore drop all but the last batch for each stream
392        self.retain_batches();
393
394        RecordBatch::try_new(Arc::clone(&self.schema), columns)
395            .context(ComputeArrowSnafu)
396            .map(Some)
397    }
398
399    /// Slice and take remaining rows from the last batch of `stream_idx` and push
400    /// the next batch if available.
401    pub fn take_remaining_rows(
402        &mut self,
403        stream_idx: usize,
404        next: Option<RecordBatch>,
405    ) -> RecordBatch {
406        let cursor = &mut self.cursors[stream_idx];
407        let batch = &self.batches[cursor.batch_idx];
408        let output = batch
409            .1
410            .slice(cursor.row_idx, batch.1.num_rows() - cursor.row_idx);
411        cursor.row_idx = batch.1.num_rows();
412
413        if let Some(b) = next {
414            self.push_batch(stream_idx, b);
415            self.retain_batches();
416        }
417
418        output
419    }
420
421    fn retain_batches(&mut self) {
422        let mut batch_idx = 0;
423        let mut retained = 0;
424        self.batches.retain(|(stream_idx, _)| {
425            let stream_cursor = &mut self.cursors[*stream_idx];
426            let retain = stream_cursor.batch_idx == batch_idx;
427            batch_idx += 1;
428
429            if retain {
430                stream_cursor.batch_idx = retained;
431                retained += 1;
432            }
433            retain
434        });
435    }
436}
437
438/// A comparable node of the heap.
439trait NodeCmp: Eq + Ord {
440    /// Returns whether the node still has batch to read.
441    fn is_eof(&self) -> bool;
442
443    /// Returns true if the key range of current batch in `self` is behind (exclusive) current
444    /// batch in `other`.
445    ///
446    /// # Panics
447    /// Panics if either `self` or `other` is EOF.
448    fn is_behind(&self, other: &Self) -> bool;
449}
450
451/// Common algorithm of merging sorted batches from multiple nodes.
452struct MergeAlgo<T: Ord> {
453    /// Holds nodes whose key range of current batch **is** overlapped with the merge window.
454    /// Each node yields batches from a `source`.
455    ///
456    /// Node in this tree **MUST** not be empty. A `merge window` is the (primary key, timestamp)
457    /// range of the **winner node** in the `hot` tree.
458    hot: WinnerTree<T>,
459    /// Holds nodes whose key range of current batch **isn't** overlapped with the merge window.
460    ///
461    /// Nodes in this heap **MUST** not be empty.
462    cold: BinaryHeap<T>,
463}
464
465impl<T: NodeCmp> MergeAlgo<T> {
466    /// Creates a new merge algorithm from `nodes`.
467    ///
468    /// All nodes must be initialized.
469    fn new(mut nodes: Vec<T>) -> Self {
470        // Skips EOF nodes.
471        nodes.retain(|node| !node.is_eof());
472        let hot = WinnerTree::with_capacity(nodes.len());
473        let cold = BinaryHeap::from(nodes);
474
475        let mut algo = MergeAlgo { hot, cold };
476        // Initializes the algorithm.
477        algo.refill_hot();
478
479        algo
480    }
481
482    /// Moves nodes in `cold` heap, whose key range is overlapped with current merge
483    /// window to `hot` tree.
484    fn refill_hot(&mut self) {
485        while !self.cold.is_empty() {
486            if let Some(merge_window) = self.hot.peek() {
487                let warmest = self.cold.peek().unwrap();
488                if warmest.is_behind(merge_window) {
489                    // if the warmest node in the `cold` heap is totally after the
490                    // `merge_window`, then no need to add more nodes into the `hot`
491                    // heap for merge sorting.
492                    break;
493                }
494            }
495
496            let warmest = self.cold.pop().unwrap();
497            self.hot.push(warmest);
498        }
499    }
500
501    /// Returns the hottest node mutably.
502    fn hottest_mut(&mut self) -> Option<&mut T> {
503        self.hot.winner_mut()
504    }
505
506    /// Removes the hottest node before a transition that can fetch a batch.
507    fn pop_hot_for_batch_transition(&mut self) -> Option<T> {
508        self.hot.pop()
509    }
510
511    /// Returns a node to the appropriate heap after a batch transition.
512    fn reheap_after_batch_transition(&mut self, node: T) {
513        if node.is_eof() {
514            self.refill_hot();
515            return;
516        }
517
518        let node_is_cold = self
519            .hot
520            .peek()
521            .is_none_or(|hottest| node.is_behind(hottest));
522        if node_is_cold {
523            self.cold.push(node);
524        } else {
525            self.hot.push(node);
526        }
527        self.refill_hot();
528    }
529
530    /// Repairs the hot tree after mutating its winner and refills the merge window.
531    fn repair_hot_root(&mut self) {
532        if self.hot.peek().is_some_and(NodeCmp::is_eof) {
533            self.hot.pop();
534        } else {
535            let (winner, second_best) = self.hot.winner_and_second_best();
536            let Some(winner) = winner else {
537                self.refill_hot();
538                return;
539            };
540            let root_is_cold = second_best.is_some_and(|best| winner.is_behind(best));
541            // If the winner still wins (tie included), every cached champion on its
542            // path is unchanged and the tree invariant already holds, so the replay
543            // can be skipped.
544            let winner_lost = second_best.is_some_and(|best| winner < best);
545            if root_is_cold {
546                self.cold.push(self.hot.pop().unwrap());
547            } else if winner_lost {
548                self.hot.replay_winner();
549            }
550        }
551
552        self.refill_hot();
553    }
554
555    /// Returns true if there are rows in the hot tree.
556    fn has_rows(&self) -> bool {
557        !self.hot.is_empty()
558    }
559
560    /// Returns true if we can fetch a batch directly instead of a row.
561    fn can_fetch_batch(&self) -> bool {
562        self.hot.len() == 1
563    }
564}
565
566// TODO(yingwen): Further downcast and store arrays in this struct.
567/// Columns to compare for a [RecordBatch].
568struct SortColumns {
569    primary_key: PrimaryKeyArray,
570    primary_key_values: BinaryArray,
571    timestamp: Int64Array,
572    sequence: UInt64Array,
573    #[cfg(test)]
574    primary_key_lookups: Cell<usize>,
575}
576
577impl SortColumns {
578    /// Creates a new [SortColumns] from a [RecordBatch] in the flat format.
579    ///
580    /// Returns an error if the batch doesn't carry the flat-format internal
581    /// columns (time index, `__primary_key`, `__sequence`) of the expected
582    /// types at the fixed trailing positions. Unlike [BatchBuilder], which
583    /// falls back to plain `interleave` on generic schemas, row comparison
584    /// fundamentally requires these columns, so a batch without them is
585    /// rejected with an error instead of a panic.
586    fn try_new(batch: &RecordBatch) -> Result<Self> {
587        let num_columns = batch.num_columns();
588        ensure!(
589            num_columns >= FIXED_POS_COLUMN_NUM,
590            InvalidRecordBatchSnafu {
591                reason: format!(
592                    "flat merge batch only has {num_columns} columns, expect at least {FIXED_POS_COLUMN_NUM}"
593                ),
594            }
595        );
596        let primary_key = batch
597            .column(primary_key_column_index(num_columns))
598            .as_any()
599            .downcast_ref::<PrimaryKeyArray>()
600            .with_context(|| InvalidRecordBatchSnafu {
601                reason: format!(
602                    "expected a {PRIMARY_KEY_COLUMN_NAME} column of type Dictionary(UInt32, Binary) at index {}",
603                    primary_key_column_index(num_columns),
604                ),
605            })?
606            .clone();
607        let primary_key_values = primary_key
608            .values()
609            .as_any()
610            .downcast_ref::<BinaryArray>()
611            .with_context(|| InvalidRecordBatchSnafu {
612                reason: format!(
613                    "expected Binary {PRIMARY_KEY_COLUMN_NAME} dictionary values, got {}",
614                    primary_key.values().data_type()
615                ),
616            })?
617            .clone();
618        let timestamp = batch.column(time_index_column_index(num_columns));
619        let (timestamp, _unit) =
620            timestamp_array_to_primitive(timestamp).with_context(|| InvalidRecordBatchSnafu {
621                reason: format!(
622                    "expected a timestamp time index column at index {}, got {}",
623                    time_index_column_index(num_columns),
624                    batch
625                        .column(time_index_column_index(num_columns))
626                        .data_type(),
627                ),
628            })?;
629        let sequence = batch
630            .column(sequence_column_index(num_columns))
631            .as_any()
632            .downcast_ref::<UInt64Array>()
633            .with_context(|| InvalidRecordBatchSnafu {
634                reason: format!(
635                    "expected a UInt64 {SEQUENCE_COLUMN_NAME} column at index {}",
636                    sequence_column_index(num_columns),
637                ),
638            })?
639            .clone();
640
641        Ok(Self {
642            primary_key,
643            primary_key_values,
644            timestamp,
645            sequence,
646            #[cfg(test)]
647            primary_key_lookups: Cell::new(0),
648        })
649    }
650
651    fn primary_key_at(&self, index: usize) -> &[u8] {
652        let range = self.primary_key_range_at(index);
653        &self.primary_key_values.value_data()[range]
654    }
655
656    fn primary_key_range_at(&self, index: usize) -> Range<usize> {
657        #[cfg(test)]
658        self.primary_key_lookups
659            .set(self.primary_key_lookups.get() + 1);
660        let key = self.primary_key.keys().value(index) as usize;
661        let offsets = self.primary_key_values.value_offsets();
662        offsets[key].as_usize()..offsets[key + 1].as_usize()
663    }
664
665    #[cfg(test)]
666    fn primary_key_lookups(&self) -> usize {
667        self.primary_key_lookups.get()
668    }
669
670    fn timestamp_at(&self, index: usize) -> i64 {
671        self.timestamp.value(index)
672    }
673
674    fn sequence_at(&self, index: usize) -> SequenceNumber {
675        self.sequence.value(index)
676    }
677
678    fn num_rows(&self) -> usize {
679        self.timestamp.len()
680    }
681}
682
683/// Cursor to a row in the [RecordBatch].
684///
685/// It compares batches by rows. During comparison, it ignores op type as sequence is enough to
686/// distinguish different rows.
687struct RowCursor {
688    /// Current row offset.
689    offset: usize,
690    /// Byte range of the current primary key in the dictionary values.
691    primary_key_range: Range<usize>,
692    /// Keys of the batch.
693    columns: SortColumns,
694}
695
696impl RowCursor {
697    fn new(columns: SortColumns) -> Self {
698        debug_assert!(columns.num_rows() > 0);
699        let primary_key_range = columns.primary_key_range_at(0);
700
701        Self {
702            offset: 0,
703            primary_key_range,
704            columns,
705        }
706    }
707
708    fn is_finished(&self) -> bool {
709        self.offset >= self.columns.num_rows()
710    }
711
712    /// Returns whether advancing this cursor will finish the current batch.
713    fn is_last_row(&self) -> bool {
714        self.offset.checked_add(1) == Some(self.columns.num_rows())
715    }
716
717    fn advance(&mut self) {
718        self.offset += 1;
719        if !self.is_finished() {
720            self.primary_key_range = self.columns.primary_key_range_at(self.offset);
721        }
722    }
723
724    fn first_primary_key(&self) -> &[u8] {
725        &self.columns.primary_key_values.value_data()[self.primary_key_range.clone()]
726    }
727
728    fn first_timestamp(&self) -> i64 {
729        self.columns.timestamp_at(self.offset)
730    }
731
732    fn first_sequence(&self) -> SequenceNumber {
733        self.columns.sequence_at(self.offset)
734    }
735
736    fn last_primary_key(&self) -> &[u8] {
737        self.columns.primary_key_at(self.columns.num_rows() - 1)
738    }
739
740    fn last_timestamp(&self) -> i64 {
741        self.columns.timestamp_at(self.columns.num_rows() - 1)
742    }
743}
744
745impl PartialEq for RowCursor {
746    fn eq(&self, other: &Self) -> bool {
747        self.first_primary_key() == other.first_primary_key()
748            && self.first_timestamp() == other.first_timestamp()
749            && self.first_sequence() == other.first_sequence()
750    }
751}
752
753impl Eq for RowCursor {}
754
755impl PartialOrd for RowCursor {
756    fn partial_cmp(&self, other: &RowCursor) -> Option<Ordering> {
757        Some(self.cmp(other))
758    }
759}
760
761impl Ord for RowCursor {
762    /// Compares by primary key, time index, sequence desc.
763    fn cmp(&self, other: &RowCursor) -> Ordering {
764        self.first_primary_key()
765            .cmp(other.first_primary_key())
766            .then_with(|| self.first_timestamp().cmp(&other.first_timestamp()))
767            .then_with(|| other.first_sequence().cmp(&self.first_sequence()))
768    }
769}
770
771/// Iterator to merge multiple sorted iterators into a single sorted iterator.
772///
773/// All iterators must be sorted by primary key, time index, sequence desc.
774///
775/// Input batches must be in the flat format: the last four columns are time
776/// index, `__primary_key`, `__sequence` and `__op_type`. Ordering uses only
777/// (primary key, time index, sequence desc); `__op_type` is required for
778/// downstream flat dedup, but is not part of the ordering key.
779/// The name-based gate in [BatchBuilder] only makes output assembly degrade gracefully on generic schemas, not sorting.
780pub struct FlatMergeIterator {
781    /// The merge algorithm to maintain heaps.
782    algo: MergeAlgo<IterNode>,
783    /// Current buffered rows to output.
784    in_progress: BatchBuilder,
785    /// Non-empty batch to output.
786    output_batch: Option<RecordBatch>,
787    /// Batch size to merge rows.
788    /// This is not a hard limit, the iterator may return smaller batches to avoid concatenating
789    /// rows.
790    batch_size: usize,
791}
792
793impl FlatMergeIterator {
794    /// Creates a new iterator to merge sorted `iters`.
795    pub fn new(
796        schema: SchemaRef,
797        iters: Vec<BoxedRecordBatchIterator>,
798        batch_size: usize,
799    ) -> Result<Self> {
800        let mut in_progress = BatchBuilder::new(schema, iters.len(), batch_size);
801        let mut nodes = Vec::with_capacity(iters.len());
802        // Initialize nodes and the buffer.
803        for (node_index, iter) in iters.into_iter().enumerate() {
804            let mut node = IterNode {
805                node_index,
806                iter,
807                cursor: None,
808            };
809            if let Some(batch) = node.advance_batch()? {
810                in_progress.push_batch(node_index, batch);
811                nodes.push(node);
812            }
813        }
814
815        let algo = MergeAlgo::new(nodes);
816
817        let iter = Self {
818            algo,
819            in_progress,
820            output_batch: None,
821            batch_size,
822        };
823
824        Ok(iter)
825    }
826
827    /// Fetches next sorted batch.
828    pub fn next_batch(&mut self) -> Result<Option<RecordBatch>> {
829        while self.algo.has_rows() && self.output_batch.is_none() {
830            if self.algo.can_fetch_batch() && !self.in_progress.is_empty() {
831                // Only one batch in the hot heap, but we have pending rows, output the pending rows first.
832                self.output_batch = self.in_progress.build_record_batch()?;
833                debug_assert!(self.output_batch.is_some());
834            } else if self.algo.can_fetch_batch() {
835                self.fetch_batch_from_hottest()?;
836            } else {
837                self.fetch_row_from_hottest()?;
838            }
839        }
840
841        Ok(self.output_batch.take())
842    }
843
844    /// Fetches a batch from the hottest node.
845    fn fetch_batch_from_hottest(&mut self) -> Result<()> {
846        debug_assert!(self.in_progress.is_empty());
847
848        // Safety: next_batch() ensures the heap is not empty.
849        let mut hottest = self.algo.pop_hot_for_batch_transition().unwrap();
850        debug_assert!(!hottest.current_cursor().is_finished());
851        let node_index = hottest.node_index;
852        let next = hottest.advance_batch()?;
853        // The node is the heap is not empty, so it must have existing rows in the builder.
854        let batch = self.in_progress.take_remaining_rows(node_index, next);
855        Self::maybe_output_batch(batch, &mut self.output_batch);
856        self.algo.reheap_after_batch_transition(hottest);
857
858        Ok(())
859    }
860
861    /// Fetches a row from the hottest node.
862    fn fetch_row_from_hottest(&mut self) -> Result<()> {
863        let (node_index, at_batch_boundary) = {
864            // Safety: next_batch() ensures the heap has more than 1 element.
865            let hottest = self.algo.hottest_mut().unwrap();
866            debug_assert!(!hottest.current_cursor().is_finished());
867            (hottest.node_index, hottest.current_cursor().is_last_row())
868        };
869        let mut boundary_node =
870            at_batch_boundary.then(|| self.algo.pop_hot_for_batch_transition().unwrap());
871        self.in_progress.push_row(node_index);
872        if self.in_progress.len() >= self.batch_size {
873            // We buffered enough rows.
874            if let Some(output) = self.in_progress.build_record_batch()? {
875                Self::maybe_output_batch(output, &mut self.output_batch);
876            }
877        }
878
879        let next = if let Some(hottest) = &mut boundary_node {
880            hottest.advance_row()?
881        } else {
882            self.algo.hottest_mut().unwrap().advance_row()?
883        };
884        if let Some(next) = next {
885            self.in_progress.push_batch(node_index, next);
886        }
887
888        if let Some(hottest) = boundary_node {
889            self.algo.reheap_after_batch_transition(hottest);
890        } else {
891            self.algo.repair_hot_root();
892        }
893        Ok(())
894    }
895
896    /// Adds the batch to the output batch if it is not empty.
897    fn maybe_output_batch(batch: RecordBatch, output_batch: &mut Option<RecordBatch>) {
898        debug_assert!(output_batch.is_none());
899        if batch.num_rows() > 0 {
900            *output_batch = Some(batch);
901        }
902    }
903}
904
905impl Iterator for FlatMergeIterator {
906    type Item = Result<RecordBatch>;
907
908    fn next(&mut self) -> Option<Self::Item> {
909        self.next_batch().transpose()
910    }
911}
912
913/// Iterator to merge multiple sorted iterators into a single sorted iterator.
914///
915/// All iterators must be sorted by primary key, time index, sequence desc.
916///
917/// Input batches must be in the flat format: the last four columns are time
918/// index, `__primary_key`, `__sequence` and `__op_type`. Row comparison
919/// decodes these internal columns and returns an error on batches that don't
920/// match the flat format; the name-based gate in [BatchBuilder] only makes
921/// output assembly degrade gracefully on generic schemas, not sorting.
922pub struct FlatMergeReader {
923    /// The merge algorithm to maintain heaps.
924    algo: MergeAlgo<StreamNode>,
925    /// Current buffered rows to output.
926    in_progress: BatchBuilder,
927    /// Non-empty batch to output.
928    output_batch: Option<RecordBatch>,
929    /// Batch size to merge rows.
930    /// This is not a hard limit, the iterator may return smaller batches to avoid concatenating
931    /// rows.
932    batch_size: usize,
933    /// Local metrics.
934    metrics: MergeMetrics,
935    /// Optional metrics reporter.
936    metrics_reporter: Option<Arc<dyn MergeMetricsReport>>,
937}
938
939impl FlatMergeReader {
940    /// Creates a new iterator to merge sorted `iters`.
941    pub async fn new(
942        schema: SchemaRef,
943        iters: Vec<BoxedRecordBatchStream>,
944        batch_size: usize,
945        metrics_reporter: Option<Arc<dyn MergeMetricsReport>>,
946    ) -> Result<Self> {
947        let start = Instant::now();
948        let metrics = MergeMetrics::default();
949        let mut in_progress = BatchBuilder::new(schema, iters.len(), batch_size);
950        let mut nodes = Vec::with_capacity(iters.len());
951        // Initialize nodes and the buffer.
952        for (node_index, iter) in iters.into_iter().enumerate() {
953            let mut node = StreamNode {
954                node_index,
955                iter,
956                cursor: None,
957            };
958            if let Some(batch) = node.advance_batch().await? {
959                in_progress.push_batch(node_index, batch);
960                nodes.push(node);
961            }
962        }
963
964        let algo = MergeAlgo::new(nodes);
965
966        let mut reader = Self {
967            algo,
968            in_progress,
969            output_batch: None,
970            batch_size,
971            metrics,
972            metrics_reporter,
973        };
974        let elapsed = start.elapsed();
975        reader.metrics.init_cost += elapsed;
976        reader.metrics.scan_cost += elapsed;
977
978        Ok(reader)
979    }
980
981    /// Fetches next sorted batch.
982    pub async fn next_batch(&mut self) -> Result<Option<RecordBatch>> {
983        let start = Instant::now();
984        while self.algo.has_rows() && self.output_batch.is_none() {
985            if self.algo.can_fetch_batch() && !self.in_progress.is_empty() {
986                // Only one batch in the hot heap, but we have pending rows, output the pending rows first.
987                self.output_batch = self.in_progress.build_record_batch()?;
988                debug_assert!(self.output_batch.is_some());
989            } else if self.algo.can_fetch_batch() {
990                self.fetch_batch_from_hottest().await?;
991                self.metrics.num_fetch_by_batches += 1;
992            } else {
993                self.fetch_row_from_hottest().await?;
994                self.metrics.num_fetch_by_rows += 1;
995            }
996        }
997
998        if let Some(batch) = self.output_batch.take() {
999            self.metrics.scan_cost += start.elapsed();
1000            self.metrics.maybe_report(&self.metrics_reporter);
1001            Ok(Some(batch))
1002        } else {
1003            // No more batches.
1004            self.metrics.scan_cost += start.elapsed();
1005            self.metrics.maybe_report(&self.metrics_reporter);
1006            Ok(None)
1007        }
1008    }
1009
1010    /// Converts the reader into a stream.
1011    pub fn into_stream(mut self) -> impl Stream<Item = Result<RecordBatch>> {
1012        try_stream! {
1013            while let Some(batch) = self.next_batch().await? {
1014                yield batch;
1015            }
1016        }
1017    }
1018
1019    /// Fetches a batch from the hottest node.
1020    async fn fetch_batch_from_hottest(&mut self) -> Result<()> {
1021        debug_assert!(self.in_progress.is_empty());
1022
1023        // Safety: next_batch() ensures the heap is not empty.
1024        let mut hottest = self.algo.pop_hot_for_batch_transition().unwrap();
1025        debug_assert!(!hottest.current_cursor().is_finished());
1026        let node_index = hottest.node_index;
1027        let start = Instant::now();
1028        let next = hottest.advance_batch().await?;
1029        self.metrics.fetch_cost += start.elapsed();
1030        // The node is the heap is not empty, so it must have existing rows in the builder.
1031        let batch = self.in_progress.take_remaining_rows(node_index, next);
1032        Self::maybe_output_batch(batch, &mut self.output_batch);
1033        self.algo.reheap_after_batch_transition(hottest);
1034
1035        Ok(())
1036    }
1037
1038    /// Fetches a row from the hottest node.
1039    async fn fetch_row_from_hottest(&mut self) -> Result<()> {
1040        let (node_index, at_batch_boundary) = {
1041            // Safety: next_batch() ensures the heap has more than 1 element.
1042            let hottest = self.algo.hottest_mut().unwrap();
1043            debug_assert!(!hottest.current_cursor().is_finished());
1044            (hottest.node_index, hottest.current_cursor().is_last_row())
1045        };
1046        let mut boundary_node =
1047            at_batch_boundary.then(|| self.algo.pop_hot_for_batch_transition().unwrap());
1048        self.in_progress.push_row(node_index);
1049        if self.in_progress.len() >= self.batch_size {
1050            // We buffered enough rows.
1051            if let Some(output) = self.in_progress.build_record_batch()? {
1052                Self::maybe_output_batch(output, &mut self.output_batch);
1053            }
1054        }
1055
1056        let start = at_batch_boundary.then(Instant::now);
1057        let next = if let Some(hottest) = &mut boundary_node {
1058            hottest.advance_row().await?
1059        } else {
1060            self.algo.hottest_mut().unwrap().advance_row().await?
1061        };
1062        if let Some(start) = start {
1063            self.metrics.fetch_cost += start.elapsed();
1064        }
1065        if let Some(next) = next {
1066            self.in_progress.push_batch(node_index, next);
1067        }
1068
1069        if let Some(hottest) = boundary_node {
1070            self.algo.reheap_after_batch_transition(hottest);
1071        } else {
1072            self.algo.repair_hot_root();
1073        }
1074        Ok(())
1075    }
1076
1077    /// Adds the batch to the output batch if it is not empty.
1078    fn maybe_output_batch(batch: RecordBatch, output_batch: &mut Option<RecordBatch>) {
1079        debug_assert!(output_batch.is_none());
1080        if batch.num_rows() > 0 {
1081            *output_batch = Some(batch);
1082        }
1083    }
1084}
1085
1086impl Drop for FlatMergeReader {
1087    fn drop(&mut self) {
1088        debug!("Flat merge reader finished, metrics: {:?}", self.metrics);
1089
1090        READ_STAGE_ELAPSED
1091            .with_label_values(&["flat_merge"])
1092            .observe(self.metrics.scan_cost.as_secs_f64());
1093        READ_STAGE_ELAPSED
1094            .with_label_values(&["flat_merge_fetch"])
1095            .observe(self.metrics.fetch_cost.as_secs_f64());
1096
1097        // Report any remaining metrics.
1098        if let Some(reporter) = &self.metrics_reporter {
1099            reporter.report(&mut self.metrics);
1100        }
1101    }
1102}
1103
1104/// A sync node in the merge iterator.
1105struct GenericNode<T> {
1106    /// Index of the node.
1107    node_index: usize,
1108    /// Iterator of this `Node`.
1109    iter: T,
1110    /// Current batch to be read. The node should ensure the batch is not empty (The
1111    /// cursor is not finished).
1112    ///
1113    /// `None` means the `iter` has reached EOF.
1114    cursor: Option<RowCursor>,
1115}
1116
1117impl<T> NodeCmp for GenericNode<T> {
1118    fn is_eof(&self) -> bool {
1119        self.cursor.is_none()
1120    }
1121
1122    fn is_behind(&self, other: &Self) -> bool {
1123        debug_assert!(!self.current_cursor().is_finished());
1124        debug_assert!(!other.current_cursor().is_finished());
1125
1126        // We only compare pk and timestamp so nodes in the cold
1127        // heap don't have overlapping timestamps with the hottest node
1128        // in the hot heap.
1129        self.current_cursor()
1130            .first_primary_key()
1131            .cmp(other.current_cursor().last_primary_key())
1132            .then_with(|| {
1133                self.current_cursor()
1134                    .first_timestamp()
1135                    .cmp(&other.current_cursor().last_timestamp())
1136            })
1137            == Ordering::Greater
1138    }
1139}
1140
1141impl<T> PartialEq for GenericNode<T> {
1142    fn eq(&self, other: &GenericNode<T>) -> bool {
1143        self.cursor == other.cursor
1144    }
1145}
1146
1147impl<T> Eq for GenericNode<T> {}
1148
1149impl<T> PartialOrd for GenericNode<T> {
1150    fn partial_cmp(&self, other: &GenericNode<T>) -> Option<Ordering> {
1151        Some(self.cmp(other))
1152    }
1153}
1154
1155impl<T> Ord for GenericNode<T> {
1156    fn cmp(&self, other: &GenericNode<T>) -> Ordering {
1157        // The std binary heap is a max heap, but we want the nodes are ordered in
1158        // ascend order, so we compare the nodes in reverse order.
1159        other.cursor.cmp(&self.cursor)
1160    }
1161}
1162
1163impl<T> GenericNode<T> {
1164    /// Returns current cursor.
1165    ///
1166    /// # Panics
1167    /// Panics if the node has reached EOF.
1168    fn current_cursor(&self) -> &RowCursor {
1169        self.cursor.as_ref().unwrap()
1170    }
1171}
1172
1173impl GenericNode<BoxedRecordBatchIterator> {
1174    /// Fetches a new batch from the iter and updates the cursor.
1175    /// It advances the current batch.
1176    /// Returns the fetched new batch.
1177    fn advance_batch(&mut self) -> Result<Option<RecordBatch>> {
1178        let batch = self.advance_inner_iter()?;
1179        let columns = batch.as_ref().map(SortColumns::try_new).transpose()?;
1180        self.cursor = columns.map(RowCursor::new);
1181
1182        Ok(batch)
1183    }
1184
1185    /// Skips one row.
1186    /// Returns the next batch if the current batch is finished.
1187    fn advance_row(&mut self) -> Result<Option<RecordBatch>> {
1188        let cursor = self.cursor.as_mut().unwrap();
1189        cursor.advance();
1190        if !cursor.is_finished() {
1191            return Ok(None);
1192        }
1193
1194        // Finished current batch, need to fetch a new batch.
1195        self.advance_batch()
1196    }
1197
1198    /// Fetches a non-empty batch from the iter.
1199    fn advance_inner_iter(&mut self) -> Result<Option<RecordBatch>> {
1200        while let Some(batch) = self.iter.next().transpose()? {
1201            if batch.num_rows() > 0 {
1202                return Ok(Some(batch));
1203            }
1204        }
1205        Ok(None)
1206    }
1207}
1208
1209type StreamNode = GenericNode<BoxedRecordBatchStream>;
1210type IterNode = GenericNode<BoxedRecordBatchIterator>;
1211
1212impl GenericNode<BoxedRecordBatchStream> {
1213    /// Fetches a new batch from the iter and updates the cursor.
1214    /// It advances the current batch.
1215    /// Returns the fetched new batch.
1216    async fn advance_batch(&mut self) -> Result<Option<RecordBatch>> {
1217        let batch = self.advance_inner_iter().await?;
1218        let columns = batch.as_ref().map(SortColumns::try_new).transpose()?;
1219        self.cursor = columns.map(RowCursor::new);
1220
1221        Ok(batch)
1222    }
1223
1224    /// Skips one row.
1225    /// Returns the next batch if the current batch is finished.
1226    async fn advance_row(&mut self) -> Result<Option<RecordBatch>> {
1227        let cursor = self.cursor.as_mut().unwrap();
1228        cursor.advance();
1229        if !cursor.is_finished() {
1230            return Ok(None);
1231        }
1232
1233        // Finished current batch, need to fetch a new batch.
1234        self.advance_batch().await
1235    }
1236
1237    /// Fetches a non-empty batch from the iter.
1238    async fn advance_inner_iter(&mut self) -> Result<Option<RecordBatch>> {
1239        while let Some(batch) = self.iter.try_next().await? {
1240            if batch.num_rows() > 0 {
1241                return Ok(Some(batch));
1242            }
1243        }
1244        Ok(None)
1245    }
1246}
1247
1248#[cfg(test)]
1249mod tests {
1250    use std::cmp::Reverse;
1251    use std::rc::Rc;
1252    use std::sync::Arc;
1253    use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
1254    use std::task::Poll;
1255
1256    use api::v1::OpType;
1257    use datatypes::arrow::array::builder::BinaryDictionaryBuilder;
1258    use datatypes::arrow::array::{
1259        DictionaryArray, Int64Array, StringArray, StringDictionaryBuilder,
1260        TimestampMillisecondArray, UInt8Array, UInt64Array,
1261    };
1262    use datatypes::arrow::datatypes::{DataType, Field, Schema, TimeUnit, UInt32Type};
1263    use datatypes::arrow::record_batch::RecordBatch;
1264    use futures::FutureExt;
1265
1266    use super::*;
1267    use crate::error::UnexpectedSnafu;
1268
1269    #[derive(Debug, Eq, PartialEq)]
1270    struct TestNode {
1271        id: usize,
1272        current_rank: Option<usize>,
1273        end_rank: usize,
1274    }
1275
1276    impl TestNode {
1277        fn new(id: usize, current_rank: usize, end_rank: usize) -> Self {
1278            Self {
1279                id,
1280                current_rank: Some(current_rank),
1281                end_rank,
1282            }
1283        }
1284    }
1285
1286    impl NodeCmp for TestNode {
1287        fn is_eof(&self) -> bool {
1288            self.current_rank.is_none()
1289        }
1290
1291        fn is_behind(&self, other: &Self) -> bool {
1292            self.current_rank.unwrap() > other.end_rank
1293        }
1294    }
1295
1296    impl PartialOrd for TestNode {
1297        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1298            Some(self.cmp(other))
1299        }
1300    }
1301
1302    impl Ord for TestNode {
1303        fn cmp(&self, other: &Self) -> Ordering {
1304            Reverse((self.current_rank, self.id)).cmp(&Reverse((other.current_rank, other.id)))
1305        }
1306    }
1307
1308    #[test]
1309    fn test_merge_algo_repairs_overlapping_hot_root_in_place() {
1310        let mut algo = MergeAlgo::new(vec![
1311            TestNode::new(0, 0, 10),
1312            TestNode::new(1, 5, 15),
1313            TestNode::new(2, 20, 25),
1314        ]);
1315        assert_eq!(0, algo.hot.peek().unwrap().id);
1316        assert_eq!((2, 1), (algo.hot.len(), algo.cold.len()));
1317
1318        algo.hottest_mut().unwrap().current_rank = Some(6);
1319        algo.repair_hot_root();
1320
1321        assert_eq!(1, algo.hot.peek().unwrap().id);
1322        assert_eq!((2, 1), (algo.hot.len(), algo.cold.len()));
1323    }
1324
1325    #[test]
1326    fn test_merge_algo_moves_root_beyond_remaining_hot_range_to_cold() {
1327        let mut algo = MergeAlgo::new(vec![
1328            TestNode::new(0, 0, 10),
1329            TestNode::new(1, 5, 7),
1330            TestNode::new(2, 20, 25),
1331        ]);
1332
1333        algo.hottest_mut().unwrap().current_rank = Some(8);
1334        algo.repair_hot_root();
1335
1336        assert_eq!(1, algo.hot.peek().unwrap().id);
1337        assert_eq!((1, 2), (algo.hot.len(), algo.cold.len()));
1338    }
1339
1340    #[test]
1341    fn test_merge_algo_removes_eof_root_and_refills_hot() {
1342        let mut algo = MergeAlgo::new(vec![TestNode::new(0, 0, 4), TestNode::new(1, 10, 14)]);
1343        assert_eq!((1, 1), (algo.hot.len(), algo.cold.len()));
1344
1345        algo.hottest_mut().unwrap().current_rank = None;
1346        algo.repair_hot_root();
1347
1348        assert_eq!(1, algo.hot.peek().unwrap().id);
1349        assert_eq!((1, 0), (algo.hot.len(), algo.cold.len()));
1350    }
1351
1352    #[test]
1353    fn test_merge_algo_single_hot_node_can_fetch_batch() {
1354        let algo = MergeAlgo::new(vec![TestNode::new(0, 0, 4), TestNode::new(1, 10, 14)]);
1355
1356        assert_eq!(0, algo.hot.peek().unwrap().id);
1357        assert_eq!((1, 1), (algo.hot.len(), algo.cold.len()));
1358        assert!(algo.can_fetch_batch());
1359    }
1360
1361    /// A merge node that counts its `Ord::cmp` invocations, to assert how many
1362    /// comparisons a repair performs. Unlike [TestNode], nodes with equal
1363    /// `current_rank` compare equal (no id tie-break), like rows with equal
1364    /// (primary key, timestamp, sequence).
1365    #[derive(Debug)]
1366    struct CountedNode {
1367        id: usize,
1368        current_rank: Option<usize>,
1369        end_rank: usize,
1370        compares: Rc<Cell<usize>>,
1371    }
1372
1373    impl CountedNode {
1374        fn new(id: usize, current_rank: usize, compares: &Rc<Cell<usize>>) -> Self {
1375            Self {
1376                id,
1377                current_rank: Some(current_rank),
1378                // Never behind, so nodes never move to the cold heap.
1379                end_rank: usize::MAX,
1380                compares: Rc::clone(compares),
1381            }
1382        }
1383    }
1384
1385    impl NodeCmp for CountedNode {
1386        fn is_eof(&self) -> bool {
1387            self.current_rank.is_none()
1388        }
1389
1390        fn is_behind(&self, other: &Self) -> bool {
1391            self.current_rank.unwrap() > other.end_rank
1392        }
1393    }
1394
1395    impl PartialEq for CountedNode {
1396        fn eq(&self, other: &Self) -> bool {
1397            self.current_rank == other.current_rank
1398        }
1399    }
1400
1401    impl Eq for CountedNode {}
1402
1403    impl PartialOrd for CountedNode {
1404        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1405            Some(self.cmp(other))
1406        }
1407    }
1408
1409    impl Ord for CountedNode {
1410        fn cmp(&self, other: &Self) -> Ordering {
1411            self.compares.set(self.compares.get() + 1);
1412            Reverse(self.current_rank).cmp(&Reverse(other.current_rank))
1413        }
1414    }
1415
1416    fn drain_merge_algo<T: NodeCmp>(algo: &mut MergeAlgo<T>) -> Vec<T> {
1417        let mut nodes = Vec::with_capacity(algo.hot.len());
1418        while let Some(node) = algo.pop_hot_for_batch_transition() {
1419            nodes.push(node);
1420        }
1421        nodes
1422    }
1423
1424    #[test]
1425    fn test_merge_algo_skips_tree_replay_when_winner_stays_hottest() {
1426        let compares = Rc::new(Cell::new(0));
1427        let mut algo = MergeAlgo::new(vec![
1428            CountedNode::new(0, 10, &compares),
1429            CountedNode::new(1, 50, &compares),
1430            CountedNode::new(2, 40, &compares),
1431            CountedNode::new(3, 30, &compares),
1432        ]);
1433        assert_eq!(0, algo.hot.peek().unwrap().id);
1434        assert_eq!((4, 0), (algo.hot.len(), algo.cold.len()));
1435
1436        // Advance the winner within its batch: it stays hotter than every
1437        // other node, so it remains the champion.
1438        algo.hottest_mut().unwrap().current_rank = Some(20);
1439        compares.set(0);
1440        algo.repair_hot_root();
1441
1442        // The second-best scan (1 compare) plus the champion-retention check
1443        // (1 compare) suffice; a full replay would cost 2 more compares.
1444        assert_eq!(2, compares.get());
1445        assert_eq!(0, algo.hot.peek().unwrap().id);
1446        assert_eq!((4, 0), (algo.hot.len(), algo.cold.len()));
1447
1448        // The tree still drains in merge order afterwards.
1449        let drained: Vec<_> = drain_merge_algo(&mut algo)
1450            .into_iter()
1451            .map(|node| node.id)
1452            .collect();
1453        assert_eq!(vec![0, 3, 2, 1], drained);
1454    }
1455
1456    #[test]
1457    fn test_merge_algo_retains_winner_tied_with_second_best() {
1458        let compares = Rc::new(Cell::new(0));
1459        let mut algo = MergeAlgo::new(vec![
1460            CountedNode::new(0, 10, &compares),
1461            CountedNode::new(1, 20, &compares),
1462            CountedNode::new(2, 20, &compares),
1463        ]);
1464        let winner_id = algo.hot.peek().unwrap().id;
1465
1466        // The winner drops to exactly tie the second hottest node.
1467        algo.hottest_mut().unwrap().current_rank = Some(20);
1468        compares.set(0);
1469        algo.repair_hot_root();
1470
1471        // A tie retains the champion without a replay: the same node stays
1472        // the winner and nothing moves to the cold heap.
1473        assert_eq!(2, compares.get());
1474        assert_eq!(winner_id, algo.hot.peek().unwrap().id);
1475        assert_eq!((3, 0), (algo.hot.len(), algo.cold.len()));
1476
1477        let mut drained: Vec<_> = drain_merge_algo(&mut algo)
1478            .into_iter()
1479            .map(|node| node.id)
1480            .collect();
1481        drained.sort_unstable();
1482        assert_eq!(vec![0, 1, 2], drained);
1483    }
1484
1485    #[test]
1486    fn test_merge_algo_caches_second_best_across_retention_repairs() {
1487        let compares = Rc::new(Cell::new(0));
1488        let mut algo = MergeAlgo::new(vec![
1489            CountedNode::new(0, 10, &compares),
1490            CountedNode::new(1, 50, &compares),
1491            CountedNode::new(2, 40, &compares),
1492            CountedNode::new(3, 30, &compares),
1493        ]);
1494
1495        // First retention repair computes the second-best slot.
1496        algo.hottest_mut().unwrap().current_rank = Some(20);
1497        algo.repair_hot_root();
1498        assert_eq!(0, algo.hot.peek().unwrap().id);
1499
1500        // While the winner keeps its slot and the tree is structurally
1501        // unchanged, repairs reuse the cached second-best slot: only the
1502        // retention check itself (1 compare) runs per repair.
1503        compares.set(0);
1504        for rank in 21..=23 {
1505            algo.hottest_mut().unwrap().current_rank = Some(rank);
1506            algo.repair_hot_root();
1507        }
1508        assert_eq!(3, compares.get());
1509        assert_eq!(0, algo.hot.peek().unwrap().id);
1510        assert_eq!((4, 0), (algo.hot.len(), algo.cold.len()));
1511
1512        // The tree still drains in merge order afterwards.
1513        let drained: Vec<_> = drain_merge_algo(&mut algo)
1514            .into_iter()
1515            .map(|node| node.id)
1516            .collect();
1517        assert_eq!(vec![0, 3, 2, 1], drained);
1518    }
1519
1520    fn drain_winner_tree<T: Ord>(tree: &mut WinnerTree<T>) -> Vec<T> {
1521        let mut values = Vec::with_capacity(tree.len());
1522        while let Some(value) = tree.pop() {
1523            values.push(value);
1524        }
1525        values
1526    }
1527
1528    #[test]
1529    fn test_winner_tree_empty() {
1530        let mut tree = WinnerTree::<i32>::with_capacity(0);
1531
1532        assert!(tree.is_empty());
1533        assert_eq!(0, tree.len());
1534        assert_eq!(None, tree.peek());
1535        assert_eq!(None, tree.winner_mut());
1536        assert_eq!(None, tree.second_best());
1537        assert_eq!(None, tree.pop());
1538        tree.replay_winner();
1539    }
1540
1541    #[test]
1542    fn test_winner_tree_single_element() {
1543        let mut tree = WinnerTree::with_capacity(1);
1544        tree.push(7);
1545
1546        assert!(!tree.is_empty());
1547        assert_eq!(1, tree.len());
1548        assert_eq!(Some(&7), tree.peek());
1549        assert_eq!(None, tree.second_best());
1550        assert_eq!(Some(7), tree.pop());
1551        assert!(tree.is_empty());
1552    }
1553
1554    #[test]
1555    fn test_winner_tree_drains_in_descending_order() {
1556        let mut tree = WinnerTree::with_capacity(8);
1557        for value in [3, 1, 4, 1, 5, 9, 2, 6] {
1558            tree.push(value);
1559        }
1560
1561        assert_eq!(Some(&9), tree.peek());
1562        assert_eq!(Some(&6), tree.second_best());
1563        assert_eq!(vec![9, 6, 5, 4, 3, 2, 1, 1], drain_winner_tree(&mut tree));
1564    }
1565
1566    #[test]
1567    fn test_winner_tree_non_power_of_two_capacity() {
1568        let mut tree = WinnerTree::with_capacity(5);
1569        for value in [40, 10, 50, 20, 30] {
1570            tree.push(value);
1571        }
1572
1573        assert_eq!(Some(&50), tree.peek());
1574        assert_eq!(Some(&40), tree.second_best());
1575        assert_eq!(vec![50, 40, 30, 20, 10], drain_winner_tree(&mut tree));
1576    }
1577
1578    #[test]
1579    fn test_winner_tree_replays_winner_after_mutation() {
1580        let mut tree = WinnerTree::with_capacity(4);
1581        for value in [7, 3, 9, 5] {
1582            tree.push(value);
1583        }
1584
1585        *tree.winner_mut().unwrap() = 1;
1586        tree.replay_winner();
1587
1588        assert_eq!(Some(&7), tree.peek());
1589        assert_eq!(Some(&5), tree.second_best());
1590        assert_eq!(vec![7, 5, 3, 1], drain_winner_tree(&mut tree));
1591    }
1592
1593    #[test]
1594    fn test_winner_tree_mutated_winner_can_stay_winner() {
1595        let mut tree = WinnerTree::with_capacity(3);
1596        for value in [1, 2, 9] {
1597            tree.push(value);
1598        }
1599
1600        *tree.winner_mut().unwrap() = 8;
1601        tree.replay_winner();
1602
1603        assert_eq!(Some(&8), tree.peek());
1604        assert_eq!(vec![8, 2, 1], drain_winner_tree(&mut tree));
1605    }
1606
1607    #[test]
1608    fn test_winner_tree_remove_and_reinsert() {
1609        let mut tree = WinnerTree::with_capacity(3);
1610        for value in [5, 9, 7] {
1611            tree.push(value);
1612        }
1613
1614        // Remove the winner; its slot is freed for a later reinsert.
1615        assert_eq!(Some(9), tree.pop());
1616        tree.push(8);
1617        assert_eq!(Some(&8), tree.peek());
1618        assert_eq!(vec![8, 7, 5], drain_winner_tree(&mut tree));
1619
1620        // Refill after the tree was drained to empty.
1621        tree.push(4);
1622        tree.push(6);
1623        assert_eq!(Some(&6), tree.peek());
1624        assert_eq!(vec![6, 4], drain_winner_tree(&mut tree));
1625    }
1626
1627    /// Drives a WinnerTree and a std BinaryHeap oracle with the same seeded op
1628    /// sequence (push / pop winner / mutate winner + replay) and compares
1629    /// observable behavior after every op. The number of live elements never
1630    /// exceeds `capacity`, mirroring how MergeAlgo uses the tree.
1631    fn assert_winner_tree_matches_oracle(
1632        seed: u64,
1633        value_range: u32,
1634        capacity: usize,
1635        num_ops: usize,
1636    ) {
1637        use rand::rngs::StdRng;
1638        use rand::{Rng, SeedableRng};
1639
1640        let mut rng = StdRng::seed_from_u64(seed);
1641        let mut tree = WinnerTree::<u32>::with_capacity(capacity);
1642        let mut oracle = BinaryHeap::<u32>::new();
1643        let mut next_value = 0_u32;
1644
1645        for _ in 0..num_ops {
1646            match rng.random_range(0..3) {
1647                0 if tree.len() < capacity => {
1648                    let pushed_value = next_value % value_range;
1649                    next_value += 1;
1650                    tree.push(pushed_value);
1651                    oracle.push(pushed_value);
1652                }
1653                1 => {
1654                    assert_eq!(oracle.pop(), tree.pop());
1655                }
1656                _ => {
1657                    let new_value = rng.random_range(0..value_range);
1658                    if let Some(winner) = tree.winner_mut() {
1659                        *winner = new_value;
1660                        tree.replay_winner();
1661
1662                        oracle.pop();
1663                        oracle.push(new_value);
1664                    }
1665                }
1666            }
1667
1668            assert_eq!(oracle.peek(), tree.peek());
1669            assert_eq!(oracle.len(), tree.len());
1670            let oracle_second_best = {
1671                let mut rest = oracle.clone();
1672                rest.pop();
1673                rest.peek().copied()
1674            };
1675            assert_eq!(oracle_second_best, tree.second_best().copied());
1676        }
1677
1678        // Both structures must drain in the same non-increasing order.
1679        let mut oracle_values = Vec::with_capacity(oracle.len());
1680        while let Some(value) = oracle.pop() {
1681            oracle_values.push(value);
1682        }
1683        assert_eq!(oracle_values, drain_winner_tree(&mut tree));
1684    }
1685
1686    #[test]
1687    fn test_winner_tree_matches_binary_heap_oracle() {
1688        for seed in [0x5eed, 0xdead_beef, 42] {
1689            assert_winner_tree_matches_oracle(seed, 1000, 13, 2000);
1690        }
1691    }
1692
1693    #[test]
1694    fn test_winner_tree_matches_oracle_with_duplicate_heavy_values() {
1695        // A tiny value range makes duplicates dominate, which exercises the
1696        // tie-breaking branches of the tree matches.
1697        assert_winner_tree_matches_oracle(0xc0ffee, 3, 8, 2000);
1698    }
1699
1700    #[test]
1701    fn test_winner_tree_matches_oracle_with_tiny_capacities() {
1702        for capacity in 1..=3 {
1703            assert_winner_tree_matches_oracle(0xbeef, 100, capacity, 500);
1704        }
1705    }
1706
1707    /// Creates a test RecordBatch with the specified data.
1708    fn create_test_record_batch(
1709        primary_keys: &[&[u8]],
1710        timestamps: &[i64],
1711        sequences: &[u64],
1712        op_types: &[OpType],
1713        field_values: &[i64],
1714    ) -> RecordBatch {
1715        let schema = Arc::new(Schema::new(vec![
1716            Field::new("field1", DataType::Int64, false),
1717            Field::new(
1718                "timestamp",
1719                DataType::Timestamp(TimeUnit::Millisecond, None),
1720                false,
1721            ),
1722            Field::new(
1723                "__primary_key",
1724                DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Binary)),
1725                false,
1726            ),
1727            Field::new("__sequence", DataType::UInt64, false),
1728            Field::new("__op_type", DataType::UInt8, false),
1729        ]));
1730
1731        let field1 = Arc::new(Int64Array::from_iter_values(field_values.iter().copied()));
1732        let timestamp = Arc::new(TimestampMillisecondArray::from_iter_values(
1733            timestamps.iter().copied(),
1734        ));
1735
1736        // Create primary key dictionary array using BinaryDictionaryBuilder
1737        let mut builder = BinaryDictionaryBuilder::<UInt32Type>::new();
1738        for &key in primary_keys {
1739            builder.append(key).unwrap();
1740        }
1741        let primary_key = Arc::new(builder.finish());
1742
1743        let sequence = Arc::new(UInt64Array::from_iter_values(sequences.iter().copied()));
1744        let op_type = Arc::new(UInt8Array::from_iter_values(
1745            op_types.iter().map(|&v| v as u8),
1746        ));
1747
1748        RecordBatch::try_new(
1749            schema,
1750            vec![field1, timestamp, primary_key, sequence, op_type],
1751        )
1752        .unwrap()
1753    }
1754
1755    fn new_test_iter(batches: Vec<RecordBatch>) -> BoxedRecordBatchIterator {
1756        Box::new(batches.into_iter().map(Ok))
1757    }
1758
1759    fn boundary_test_batches() -> (RecordBatch, RecordBatch, RecordBatch) {
1760        let first = create_test_record_batch(
1761            &[b"k1", b"k1"],
1762            &[1000, 2000],
1763            &[1, 2],
1764            &[OpType::Put, OpType::Put],
1765            &[10, 12],
1766        );
1767        let second = create_test_record_batch(
1768            &[b"k1", b"k1", b"k1"],
1769            &[1500, 2000, 2500],
1770            &[1, 1, 1],
1771            &[OpType::Put, OpType::Put, OpType::Put],
1772            &[11, 13, 14],
1773        );
1774        let pending = create_test_record_batch(
1775            &[b"k1", b"k1", b"k1"],
1776            &[1000, 1500, 2000],
1777            &[1, 1, 2],
1778            &[OpType::Put, OpType::Put, OpType::Put],
1779            &[10, 11, 12],
1780        );
1781        (first, second, pending)
1782    }
1783
1784    fn test_source_error() -> crate::error::Error {
1785        UnexpectedSnafu {
1786            reason: "test source failed".to_string(),
1787        }
1788        .build()
1789    }
1790
1791    #[test]
1792    fn test_row_cursor_last_row() {
1793        let batch = create_test_record_batch(
1794            &[b"k1", b"k1"],
1795            &[1000, 2000],
1796            &[21, 22],
1797            &[OpType::Put, OpType::Put],
1798            &[11, 12],
1799        );
1800        let mut cursor = RowCursor::new(SortColumns::try_new(&batch).unwrap());
1801
1802        assert!(!cursor.is_last_row());
1803        cursor.advance();
1804        assert!(cursor.is_last_row());
1805        cursor.advance();
1806        assert!(!cursor.is_last_row());
1807    }
1808
1809    /// Helper function to check if two record batches are equivalent.
1810    fn assert_record_batches_eq(expected: &[RecordBatch], actual: &[RecordBatch]) {
1811        for (exp, act) in expected.iter().zip(actual.iter()) {
1812            assert_eq!(exp, act,);
1813        }
1814    }
1815
1816    /// Helper function to collect all batches from a FlatMergeIterator.
1817    fn collect_merge_iterator_batches(iter: FlatMergeIterator) -> Vec<RecordBatch> {
1818        iter.map(|result| result.unwrap()).collect()
1819    }
1820
1821    #[test]
1822    fn test_merge_iterator_empty() {
1823        let schema = Arc::new(Schema::new(vec![
1824            Field::new("field1", DataType::Int64, false),
1825            Field::new(
1826                "timestamp",
1827                DataType::Timestamp(TimeUnit::Millisecond, None),
1828                false,
1829            ),
1830            Field::new(
1831                "__primary_key",
1832                DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Binary)),
1833                false,
1834            ),
1835            Field::new("__sequence", DataType::UInt64, false),
1836            Field::new("__op_type", DataType::UInt8, false),
1837        ]));
1838
1839        let mut merge_iter = FlatMergeIterator::new(schema, vec![], 1024).unwrap();
1840        assert!(merge_iter.next_batch().unwrap().is_none());
1841    }
1842
1843    #[test]
1844    fn test_merge_iterator_single_batch() {
1845        let batch = create_test_record_batch(
1846            &[b"k1", b"k1"],
1847            &[1000, 2000],
1848            &[21, 22],
1849            &[OpType::Put, OpType::Put],
1850            &[11, 12],
1851        );
1852
1853        let schema = batch.schema();
1854        let iter = Box::new(new_test_iter(vec![batch.clone()]));
1855
1856        let merge_iter = FlatMergeIterator::new(schema, vec![iter], 1024).unwrap();
1857        let result = collect_merge_iterator_batches(merge_iter);
1858
1859        assert_eq!(result.len(), 1);
1860        assert_record_batches_eq(&[batch], &result);
1861    }
1862
1863    #[test]
1864    fn test_merge_iterator_non_overlapping() {
1865        let batch1 = create_test_record_batch(
1866            &[b"k1", b"k1"],
1867            &[1000, 2000],
1868            &[21, 22],
1869            &[OpType::Put, OpType::Put],
1870            &[11, 12],
1871        );
1872        let batch2 = create_test_record_batch(
1873            &[b"k1", b"k1"],
1874            &[4000, 5000],
1875            &[24, 25],
1876            &[OpType::Put, OpType::Put],
1877            &[14, 15],
1878        );
1879        let batch3 = create_test_record_batch(
1880            &[b"k2", b"k2"],
1881            &[2000, 3000],
1882            &[22, 23],
1883            &[OpType::Delete, OpType::Put],
1884            &[12, 13],
1885        );
1886
1887        let schema = batch1.schema();
1888        let iter1 = Box::new(new_test_iter(vec![batch1.clone(), batch3.clone()]));
1889        let iter2 = Box::new(new_test_iter(vec![batch2.clone()]));
1890
1891        let merge_iter = FlatMergeIterator::new(schema, vec![iter1, iter2], 1024).unwrap();
1892        let result = collect_merge_iterator_batches(merge_iter);
1893
1894        // Results should be sorted by primary key, timestamp, sequence desc
1895        let expected = vec![batch1, batch2, batch3];
1896        assert_record_batches_eq(&expected, &result);
1897    }
1898
1899    #[test]
1900    fn test_merge_iterator_overlapping_timestamps() {
1901        // Create batches with overlapping timestamps but different sequences
1902        let batch1 = create_test_record_batch(
1903            &[b"k1", b"k1"],
1904            &[1000, 2000],
1905            &[21, 22],
1906            &[OpType::Put, OpType::Put],
1907            &[11, 12],
1908        );
1909        let batch2 = create_test_record_batch(
1910            &[b"k1", b"k1"],
1911            &[1500, 2500],
1912            &[31, 32],
1913            &[OpType::Put, OpType::Put],
1914            &[15, 25],
1915        );
1916
1917        let schema = batch1.schema();
1918        let iter1 = Box::new(new_test_iter(vec![batch1]));
1919        let iter2 = Box::new(new_test_iter(vec![batch2]));
1920
1921        let merge_iter = FlatMergeIterator::new(schema, vec![iter1, iter2], 1024).unwrap();
1922        let result = collect_merge_iterator_batches(merge_iter);
1923
1924        let expected = vec![
1925            create_test_record_batch(
1926                &[b"k1", b"k1"],
1927                &[1000, 1500],
1928                &[21, 31],
1929                &[OpType::Put, OpType::Put],
1930                &[11, 15],
1931            ),
1932            create_test_record_batch(&[b"k1"], &[2000], &[22], &[OpType::Put], &[12]),
1933            create_test_record_batch(&[b"k1"], &[2500], &[32], &[OpType::Put], &[25]),
1934        ];
1935        assert_record_batches_eq(&expected, &result);
1936    }
1937
1938    #[test]
1939    fn test_merge_iterator_duplicate_keys_sequences() {
1940        // Test with same primary key and timestamp but different sequences
1941        let batch1 = create_test_record_batch(
1942            &[b"k1", b"k1"],
1943            &[1000, 1000],
1944            &[20, 10],
1945            &[OpType::Put, OpType::Put],
1946            &[1, 2],
1947        );
1948        let batch2 = create_test_record_batch(
1949            &[b"k1"],
1950            &[1000],
1951            &[15], // Middle sequence
1952            &[OpType::Put],
1953            &[3],
1954        );
1955
1956        let schema = batch1.schema();
1957        let iter1 = Box::new(new_test_iter(vec![batch1]));
1958        let iter2 = Box::new(new_test_iter(vec![batch2]));
1959
1960        let merge_iter = FlatMergeIterator::new(schema, vec![iter1, iter2], 1024).unwrap();
1961        let result = collect_merge_iterator_batches(merge_iter);
1962
1963        // Should be sorted by sequence descending for same key/timestamp
1964        let expected = vec![
1965            create_test_record_batch(
1966                &[b"k1", b"k1"],
1967                &[1000, 1000],
1968                &[20, 15],
1969                &[OpType::Put, OpType::Put],
1970                &[1, 3],
1971            ),
1972            create_test_record_batch(&[b"k1"], &[1000], &[10], &[OpType::Put], &[2]),
1973        ];
1974        assert_record_batches_eq(&expected, &result);
1975    }
1976
1977    #[test]
1978    fn test_merge_iterator_empty_primary_keys() {
1979        // Tables without tags produce batches whose primary keys are all empty
1980        // byte strings.
1981        let batch1 = create_test_record_batch(
1982            &[b"", b""],
1983            &[1000, 3000],
1984            &[21, 23],
1985            &[OpType::Put, OpType::Put],
1986            &[11, 13],
1987        );
1988        let batch2 = create_test_record_batch(
1989            &[b"", b""],
1990            &[2000, 4000],
1991            &[22, 24],
1992            &[OpType::Put, OpType::Put],
1993            &[12, 14],
1994        );
1995
1996        let schema = batch1.schema();
1997        let iter1 = Box::new(new_test_iter(vec![batch1]));
1998        let iter2 = Box::new(new_test_iter(vec![batch2]));
1999
2000        let merge_iter = FlatMergeIterator::new(schema, vec![iter1, iter2], 1024).unwrap();
2001        let result = collect_merge_iterator_batches(merge_iter);
2002
2003        let num_rows: usize = result.iter().map(|batch| batch.num_rows()).sum();
2004        assert_eq!(4, num_rows);
2005        let mut timestamps = Vec::new();
2006        for batch in &result {
2007            let pk_idx = primary_key_column_index(batch.num_columns());
2008            // All rows share the same empty primary key, so each output
2009            // dictionary must contain a single empty value.
2010            let expected_keys = vec![b"".as_slice(); batch.num_rows()];
2011            assert_primary_key_dictionary(batch.column(pk_idx).as_ref(), &expected_keys, &[b""]);
2012            let timestamp = batch
2013                .column(time_index_column_index(batch.num_columns()))
2014                .as_any()
2015                .downcast_ref::<TimestampMillisecondArray>()
2016                .unwrap();
2017            timestamps.extend(timestamp.values().iter().copied());
2018        }
2019        // Rows are merged by timestamp since all primary keys are equal.
2020        assert_eq!(vec![1000, 2000, 3000, 4000], timestamps);
2021    }
2022
2023    /// Creates a test RecordBatch with an extra dictionary-encoded string tag
2024    /// column, mirroring the flat input schema of tables with string tags.
2025    fn create_test_record_batch_with_dict_tag(
2026        tags: &[&str],
2027        primary_keys: &[&[u8]],
2028        timestamps: &[i64],
2029        sequences: &[u64],
2030        op_types: &[OpType],
2031        field_values: &[i64],
2032    ) -> RecordBatch {
2033        let schema = Arc::new(Schema::new(vec![
2034            Field::new(
2035                "tag0",
2036                DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Utf8)),
2037                true,
2038            ),
2039            Field::new("field1", DataType::Int64, false),
2040            Field::new(
2041                "timestamp",
2042                DataType::Timestamp(TimeUnit::Millisecond, None),
2043                false,
2044            ),
2045            Field::new(
2046                "__primary_key",
2047                DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Binary)),
2048                false,
2049            ),
2050            Field::new("__sequence", DataType::UInt64, false),
2051            Field::new("__op_type", DataType::UInt8, false),
2052        ]));
2053
2054        let mut tag_builder = StringDictionaryBuilder::<UInt32Type>::new();
2055        for tag in tags {
2056            tag_builder.append(tag).unwrap();
2057        }
2058        let tag = Arc::new(tag_builder.finish());
2059
2060        let field1 = Arc::new(Int64Array::from_iter_values(field_values.iter().copied()));
2061        let timestamp = Arc::new(TimestampMillisecondArray::from_iter_values(
2062            timestamps.iter().copied(),
2063        ));
2064        let mut pk_builder = BinaryDictionaryBuilder::<UInt32Type>::new();
2065        for key in primary_keys {
2066            pk_builder.append(key).unwrap();
2067        }
2068        let primary_key = Arc::new(pk_builder.finish());
2069        let sequence = Arc::new(UInt64Array::from_iter_values(sequences.iter().copied()));
2070        let op_type = Arc::new(UInt8Array::from_iter_values(
2071            op_types.iter().map(|&v| v as u8),
2072        ));
2073
2074        RecordBatch::try_new(
2075            schema,
2076            vec![tag, field1, timestamp, primary_key, sequence, op_type],
2077        )
2078        .unwrap()
2079    }
2080
2081    #[test]
2082    fn test_merge_iterator_dictionary_encoded_tag_column() {
2083        // String tag columns are dictionary-encoded in the flat input schema,
2084        // and each source may carry a different dictionary.
2085        let batch1 = create_test_record_batch_with_dict_tag(
2086            &["us-west", "eu-west"],
2087            &[b"k1", b"k2"],
2088            &[1000, 2000],
2089            &[21, 22],
2090            &[OpType::Put, OpType::Put],
2091            &[11, 12],
2092        );
2093        let batch2 = create_test_record_batch_with_dict_tag(
2094            &["us-east", "eu-west"],
2095            &[b"k1", b"k2"],
2096            &[1500, 2500],
2097            &[23, 24],
2098            &[OpType::Put, OpType::Put],
2099            &[13, 14],
2100        );
2101
2102        let schema = batch1.schema();
2103        let iter1 = Box::new(new_test_iter(vec![batch1]));
2104        let iter2 = Box::new(new_test_iter(vec![batch2]));
2105
2106        let merge_iter = FlatMergeIterator::new(schema, vec![iter1, iter2], 1024).unwrap();
2107        let result = collect_merge_iterator_batches(merge_iter);
2108
2109        // Rows merged by (primary key, timestamp): (k1, 1000), (k1, 1500),
2110        // (k2, 2000), (k2, 2500).
2111        let num_rows: usize = result.iter().map(|batch| batch.num_rows()).sum();
2112        assert_eq!(4, num_rows);
2113        let mut tags = Vec::new();
2114        let mut timestamps = Vec::new();
2115        for batch in &result {
2116            let tag = batch
2117                .column(0)
2118                .as_any()
2119                .downcast_ref::<DictionaryArray<UInt32Type>>()
2120                .unwrap();
2121            let tag_values = tag.values().as_any().downcast_ref::<StringArray>().unwrap();
2122            tags.extend(
2123                tag.keys()
2124                    .iter()
2125                    .map(|key| tag_values.value(key.unwrap() as usize)),
2126            );
2127            let timestamp = batch
2128                .column(time_index_column_index(batch.num_columns()))
2129                .as_any()
2130                .downcast_ref::<TimestampMillisecondArray>()
2131                .unwrap();
2132            timestamps.extend(timestamp.values().iter().copied());
2133        }
2134        assert_eq!(vec!["us-west", "us-east", "eu-west", "eu-west"], tags);
2135        assert_eq!(vec![1000, 1500, 2000, 2500], timestamps);
2136    }
2137
2138    #[test]
2139    fn test_merge_iterator_retry_after_row_boundary_error_removes_source() {
2140        let (first, second, pending) = boundary_test_batches();
2141        let schema = first.schema();
2142        let first_source = Box::new(vec![Ok(first), Err(test_source_error())].into_iter())
2143            as BoxedRecordBatchIterator;
2144        let second_source = new_test_iter(vec![second.clone()]);
2145        let mut merge =
2146            FlatMergeIterator::new(schema, vec![first_source, second_source], 1024).unwrap();
2147
2148        assert!(merge.next_batch().is_err());
2149        assert_eq!(pending, merge.next_batch().unwrap().unwrap());
2150        assert_eq!(second.slice(1, 2), merge.next_batch().unwrap().unwrap());
2151        assert!(merge.next_batch().unwrap().is_none());
2152    }
2153
2154    #[tokio::test]
2155    async fn test_merge_reader_retry_after_row_boundary_error_removes_source() {
2156        let (first, second, pending) = boundary_test_batches();
2157        let schema = first.schema();
2158        let first_source = Box::pin(futures::stream::iter(vec![
2159            Ok(first),
2160            Err(test_source_error()),
2161        ])) as BoxedRecordBatchStream;
2162        let second_source =
2163            Box::pin(futures::stream::iter(vec![Ok(second.clone())])) as BoxedRecordBatchStream;
2164        let mut merge = FlatMergeReader::new(schema, vec![first_source, second_source], 1024, None)
2165            .await
2166            .unwrap();
2167
2168        assert!(merge.next_batch().await.is_err());
2169        assert_eq!(pending, merge.next_batch().await.unwrap().unwrap());
2170        assert_eq!(
2171            second.slice(1, 2),
2172            merge.next_batch().await.unwrap().unwrap()
2173        );
2174        assert!(merge.next_batch().await.unwrap().is_none());
2175    }
2176
2177    #[tokio::test]
2178    async fn test_merge_reader_cancelled_row_boundary_fetch_removes_source() {
2179        let (first, second, pending) = boundary_test_batches();
2180        let schema = first.schema();
2181        let fetch_pending = Arc::new(AtomicBool::new(false));
2182        let fetch_pending_on_poll = Arc::clone(&fetch_pending);
2183        let mut first_batch = Some(first);
2184        let first_source = Box::pin(futures::stream::poll_fn(move |_cx| {
2185            if let Some(batch) = first_batch.take() {
2186                Poll::Ready(Some(Ok(batch)))
2187            } else {
2188                fetch_pending_on_poll.store(true, AtomicOrdering::Relaxed);
2189                Poll::Pending
2190            }
2191        })) as BoxedRecordBatchStream;
2192        let second_source =
2193            Box::pin(futures::stream::iter(vec![Ok(second.clone())])) as BoxedRecordBatchStream;
2194        let mut merge = FlatMergeReader::new(schema, vec![first_source, second_source], 1024, None)
2195            .await
2196            .unwrap();
2197
2198        assert!(Box::pin(merge.next_batch()).now_or_never().is_none());
2199        assert!(fetch_pending.load(AtomicOrdering::Relaxed));
2200        assert_eq!(pending, merge.next_batch().await.unwrap().unwrap());
2201        assert_eq!(
2202            second.slice(1, 2),
2203            merge.next_batch().await.unwrap().unwrap()
2204        );
2205        assert!(merge.next_batch().await.unwrap().is_none());
2206    }
2207
2208    #[test]
2209    fn test_batch_builder_basic() {
2210        let schema = Arc::new(Schema::new(vec![
2211            Field::new("field1", DataType::Int64, false),
2212            Field::new(
2213                "timestamp",
2214                DataType::Timestamp(TimeUnit::Millisecond, None),
2215                false,
2216            ),
2217        ]));
2218
2219        let mut builder = BatchBuilder::new(schema.clone(), 2, 1024);
2220        assert!(builder.is_empty());
2221
2222        let batch = RecordBatch::try_new(
2223            schema,
2224            vec![
2225                Arc::new(Int64Array::from(vec![1, 2])),
2226                Arc::new(TimestampMillisecondArray::from(vec![1000, 2000])),
2227            ],
2228        )
2229        .unwrap();
2230
2231        builder.push_batch(0, batch);
2232        builder.push_row(0);
2233        builder.push_row(0);
2234
2235        assert!(!builder.is_empty());
2236        assert_eq!(builder.len(), 2);
2237
2238        let result_batch = builder.build_record_batch().unwrap().unwrap();
2239        assert_eq!(result_batch.num_rows(), 2);
2240    }
2241
2242    #[test]
2243    fn test_batch_builder_generic_three_column_schema() {
2244        let schema = Arc::new(Schema::new(vec![
2245            Field::new("field1", DataType::Int64, false),
2246            Field::new("field2", DataType::Int64, false),
2247            Field::new("field3", DataType::Int64, false),
2248        ]));
2249        let batch = RecordBatch::try_new(
2250            Arc::clone(&schema),
2251            vec![
2252                Arc::new(Int64Array::from(vec![1, 2])),
2253                Arc::new(Int64Array::from(vec![3, 4])),
2254                Arc::new(Int64Array::from(vec![5, 6])),
2255            ],
2256        )
2257        .unwrap();
2258        let mut builder = BatchBuilder::new(schema, 1, 2);
2259        builder.push_batch(0, batch.clone());
2260        builder.push_row(0);
2261        builder.push_row(0);
2262
2263        let output_batch = builder.build_record_batch().unwrap().unwrap();
2264
2265        assert_eq!(batch, output_batch);
2266    }
2267
2268    #[test]
2269    fn test_merge_iterator_rejects_batch_without_internal_columns() {
2270        // A generic schema without the flat-format internal columns cannot
2271        // drive row comparison; the merger must return an error instead of
2272        // panicking.
2273        let schema = Arc::new(Schema::new(vec![
2274            Field::new("field1", DataType::Int64, false),
2275            Field::new("field2", DataType::Int64, false),
2276        ]));
2277        let batch = RecordBatch::try_new(
2278            Arc::clone(&schema),
2279            vec![
2280                Arc::new(Int64Array::from(vec![1, 2])),
2281                Arc::new(Int64Array::from(vec![3, 4])),
2282            ],
2283        )
2284        .unwrap();
2285        let iter = Box::new(new_test_iter(vec![batch]));
2286
2287        let result = FlatMergeIterator::new(schema, vec![iter], 1024);
2288
2289        assert!(matches!(
2290            result,
2291            Err(crate::error::Error::InvalidRecordBatch { .. })
2292        ));
2293    }
2294
2295    fn assert_primary_key_dictionary(
2296        array: &dyn Array,
2297        expected_decoded: &[&[u8]],
2298        expected_values: &[&[u8]],
2299    ) {
2300        let dictionary = array.as_any().downcast_ref::<PrimaryKeyArray>().unwrap();
2301        let values = dictionary
2302            .values()
2303            .as_any()
2304            .downcast_ref::<BinaryArray>()
2305            .unwrap();
2306        let decoded: Vec<_> = dictionary
2307            .keys()
2308            .iter()
2309            .map(|key| values.value(key.unwrap() as usize))
2310            .collect();
2311        let dictionary_values: Vec<_> = values.iter().map(Option::unwrap).collect();
2312
2313        assert_eq!(expected_decoded, decoded);
2314        assert_eq!(expected_values, dictionary_values);
2315    }
2316
2317    #[test]
2318    fn test_interleave_primary_key_deduplicates_separate_dictionaries() {
2319        let batch0 = create_test_record_batch(
2320            &[b"k1", b"k2"],
2321            &[1000, 2000],
2322            &[1, 1],
2323            &[OpType::Put, OpType::Put],
2324            &[10, 20],
2325        );
2326        let batch1 = create_test_record_batch(
2327            &[b"k1", b"k2"],
2328            &[1000, 2000],
2329            &[1, 1],
2330            &[OpType::Put, OpType::Put],
2331            &[11, 21],
2332        );
2333        let pk_idx = primary_key_column_index(batch0.num_columns());
2334        let arrays: Vec<_> = [&batch0, &batch1]
2335            .into_iter()
2336            .map(|batch| batch.column(pk_idx).as_ref())
2337            .collect();
2338
2339        let output = interleave_primary_key(&arrays, &[(0, 0), (1, 0), (0, 1), (1, 1)]).unwrap();
2340
2341        assert_primary_key_dictionary(
2342            output.as_ref(),
2343            &[b"k1", b"k1", b"k2", b"k2"],
2344            &[b"k1", b"k2"],
2345        );
2346    }
2347
2348    #[test]
2349    fn test_interleave_primary_key_rejects_null_dictionary_value() {
2350        let primary_key = PrimaryKeyArray::try_new(
2351            UInt32Array::from(vec![0]),
2352            Arc::new(BinaryArray::from(vec![None::<&[u8]>])),
2353        )
2354        .unwrap();
2355
2356        let error = interleave_primary_key(&[&primary_key], &[(0, 0)]).unwrap_err();
2357
2358        assert!(error.to_string().contains("null dictionary value"));
2359    }
2360
2361    #[test]
2362    fn test_batch_builder_primary_key_has_no_state_between_builds() {
2363        let long_k1 = vec![b'a'; 4096];
2364        let long_k2 = vec![b'b'; 8192];
2365        let batch0 =
2366            create_test_record_batch(&[long_k1.as_slice()], &[1000], &[1], &[OpType::Put], &[10]);
2367        let batch1 =
2368            create_test_record_batch(&[long_k1.as_slice()], &[1000], &[1], &[OpType::Put], &[11]);
2369        let mut builder = BatchBuilder::new(batch0.schema(), 2, 4);
2370        builder.push_batch(0, batch0);
2371        builder.push_batch(1, batch1);
2372        builder.push_row(0);
2373        builder.push_row(1);
2374
2375        let first = builder.build_record_batch().unwrap().unwrap();
2376        let pk_idx = primary_key_column_index(first.num_columns());
2377        assert_primary_key_dictionary(
2378            first.column(pk_idx).as_ref(),
2379            &[long_k1.as_slice(), long_k1.as_slice()],
2380            &[long_k1.as_slice()],
2381        );
2382
2383        let batch0 =
2384            create_test_record_batch(&[long_k2.as_slice()], &[2000], &[2], &[OpType::Put], &[20]);
2385        let batch1 =
2386            create_test_record_batch(&[long_k2.as_slice()], &[2000], &[2], &[OpType::Put], &[21]);
2387        builder.push_batch(0, batch0);
2388        builder.push_batch(1, batch1);
2389        builder.push_row(0);
2390        builder.push_row(1);
2391
2392        let second = builder.build_record_batch().unwrap().unwrap();
2393        assert_primary_key_dictionary(
2394            second.column(pk_idx).as_ref(),
2395            &[long_k2.as_slice(), long_k2.as_slice()],
2396            &[long_k2.as_slice()],
2397        );
2398    }
2399
2400    #[test]
2401    fn test_row_cursor_comparison() {
2402        // Create test batches for cursor comparison
2403        let batch1 = create_test_record_batch(
2404            &[b"k1", b"k1"],
2405            &[1000, 2000],
2406            &[22, 21],
2407            &[OpType::Put, OpType::Put],
2408            &[11, 12],
2409        );
2410        let batch2 = create_test_record_batch(
2411            &[b"k1", b"k1"],
2412            &[1000, 2000],
2413            &[23, 20], // Different sequences
2414            &[OpType::Put, OpType::Put],
2415            &[11, 12],
2416        );
2417
2418        let columns1 = SortColumns::try_new(&batch1).unwrap();
2419        let columns2 = SortColumns::try_new(&batch2).unwrap();
2420
2421        let cursor1 = RowCursor::new(columns1);
2422        let cursor2 = RowCursor::new(columns2);
2423
2424        // cursors with same pk and timestamp should be ordered by sequence desc
2425        // cursor1 has sequence 22, cursor2 has sequence 23, so cursor2 < cursor1 (higher sequence comes first)
2426        assert!(cursor2 < cursor1);
2427    }
2428
2429    #[test]
2430    fn test_row_cursor_caches_current_primary_key() {
2431        let batch1 = create_test_record_batch(&[b"k1"], &[1000], &[1], &[OpType::Put], &[11]);
2432        let batch2 = create_test_record_batch(&[b"k2"], &[1000], &[1], &[OpType::Put], &[12]);
2433        let cursor1 = RowCursor::new(SortColumns::try_new(&batch1).unwrap());
2434        let cursor2 = RowCursor::new(SortColumns::try_new(&batch2).unwrap());
2435
2436        for _ in 0..5 {
2437            assert_eq!(Ordering::Less, cursor1.cmp(&cursor2));
2438        }
2439
2440        assert_eq!(1, cursor1.columns.primary_key_lookups());
2441        assert_eq!(1, cursor2.columns.primary_key_lookups());
2442    }
2443}