Skip to main content

mito2/read/
last_row.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//! Utilities to read the last row of each time series.
16
17use std::sync::Arc;
18
19use api::v1::OpType;
20use datatypes::arrow::array::{Array, BinaryArray, UInt8Array, UInt32Array};
21use datatypes::arrow::compute::{concat_batches, take_record_batch};
22use datatypes::arrow::record_batch::RecordBatch;
23use futures::{Stream, TryStreamExt};
24use snafu::ResultExt;
25use store_api::storage::{FileId, TimeSeriesRowSelector};
26
27use crate::cache::{
28    CacheStrategy, SelectorResult, SelectorResultKey, SelectorResultValue,
29    selector_result_cache_hit, selector_result_cache_miss,
30};
31use crate::error::{ComputeArrowSnafu, Result};
32use crate::read::read_columns::JsonTargetTypes;
33use crate::read::{BoxedRecordBatchStream, timestamp_array_to_i64_slice};
34use crate::sst::parquet::DEFAULT_READ_BATCH_SIZE;
35use crate::sst::parquet::flat_format::{
36    op_type_column_index, primary_key_column_index, time_index_column_index,
37};
38use crate::sst::parquet::format::{PrimaryKeyArray, primary_key_offsets};
39use crate::sst::parquet::read_columns::ParquetReadColumns;
40use crate::sst::parquet::reader::FlatRowGroupReader;
41
42/// Cached last row reader for flat format row group.
43/// If the last rows are already cached (as flat `RecordBatch`), returns cached values.
44/// Otherwise, reads from the row group, selects last rows, and updates the cache.
45pub(crate) enum FlatRowGroupLastRowCachedReader {
46    /// Cache hit, reads last rows from cached value.
47    Hit(FlatLastRowCacheReader),
48    /// Cache miss, reads from row group reader and updates cache.
49    Miss(FlatRowGroupLastRowReader),
50}
51
52impl FlatRowGroupLastRowCachedReader {
53    pub(crate) fn new(
54        file_id: FileId,
55        row_group_idx: usize,
56        cache_strategy: CacheStrategy,
57        read_cols: &ParquetReadColumns,
58        json_target_types: JsonTargetTypes,
59        reader: FlatRowGroupReader,
60    ) -> Self {
61        let key = SelectorResultKey {
62            file_id,
63            row_group_idx,
64            selector: TimeSeriesRowSelector::LastRow { after_merge: false },
65        };
66
67        if let Some(value) = cache_strategy.get_selector_result(&key) {
68            let is_flat = matches!(&value.result, SelectorResult::Flat(_));
69            let schema_matches = value.read_cols == *read_cols;
70            let json_target_types_matches = value.json_target_types == json_target_types;
71            if is_flat && schema_matches && json_target_types_matches {
72                Self::new_hit(value)
73            } else {
74                Self::new_miss(key, read_cols, json_target_types, reader, cache_strategy)
75            }
76        } else {
77            Self::new_miss(key, read_cols, json_target_types, reader, cache_strategy)
78        }
79    }
80
81    /// Returns the next RecordBatch.
82    pub(crate) async fn next_batch(&mut self) -> Result<Option<RecordBatch>> {
83        match self {
84            FlatRowGroupLastRowCachedReader::Hit(r) => r.next_batch(),
85            FlatRowGroupLastRowCachedReader::Miss(r) => r.next_batch().await,
86        }
87    }
88
89    fn new_hit(value: Arc<SelectorResultValue>) -> Self {
90        selector_result_cache_hit();
91        Self::Hit(FlatLastRowCacheReader { value, idx: 0 })
92    }
93
94    fn new_miss(
95        key: SelectorResultKey,
96        read_cols: &ParquetReadColumns,
97        json_target_types: JsonTargetTypes,
98        reader: FlatRowGroupReader,
99        cache_strategy: CacheStrategy,
100    ) -> Self {
101        selector_result_cache_miss();
102        Self::Miss(FlatRowGroupLastRowReader::new(
103            key,
104            read_cols.clone(),
105            json_target_types,
106            reader,
107            cache_strategy,
108        ))
109    }
110}
111
112/// Iterates over cached flat last rows.
113pub(crate) struct FlatLastRowCacheReader {
114    value: Arc<SelectorResultValue>,
115    idx: usize,
116}
117
118impl FlatLastRowCacheReader {
119    fn next_batch(&mut self) -> Result<Option<RecordBatch>> {
120        let batches = match &self.value.result {
121            SelectorResult::Flat(batches) => batches,
122            SelectorResult::PrimaryKey(_) => unreachable!(),
123        };
124        if self.idx < batches.len() {
125            let res = Ok(Some(batches[self.idx].clone()));
126            self.idx += 1;
127            res
128        } else {
129            Ok(None)
130        }
131    }
132}
133
134/// Buffer that accumulates small `RecordBatch`es and tracks total row count.
135pub(crate) struct BatchBuffer {
136    batches: Vec<RecordBatch>,
137    num_rows: usize,
138}
139
140impl BatchBuffer {
141    fn new() -> Self {
142        Self {
143            batches: Vec::new(),
144            num_rows: 0,
145        }
146    }
147
148    /// Returns true if total buffered rows reaches `DEFAULT_READ_BATCH_SIZE`.
149    fn is_full(&self) -> bool {
150        self.num_rows >= DEFAULT_READ_BATCH_SIZE
151    }
152
153    /// Extends the buffer from a slice of batches.
154    fn extend_from_slice(&mut self, batches: &[RecordBatch]) {
155        for batch in batches {
156            self.num_rows += batch.num_rows();
157        }
158        self.batches.extend_from_slice(batches);
159    }
160
161    fn push(&mut self, batch: RecordBatch) {
162        self.num_rows += batch.num_rows();
163        self.batches.push(batch);
164    }
165
166    /// Returns true if the buffer has no batches.
167    fn is_empty(&self) -> bool {
168        self.batches.is_empty()
169    }
170
171    /// Concatenates all buffered batches into one, resets the buffer, and returns the result.
172    fn concat(&mut self) -> Result<RecordBatch> {
173        debug_assert!(!self.batches.is_empty());
174        let schema = self.batches[0].schema();
175        let merged = concat_batches(&schema, &self.batches).context(ComputeArrowSnafu)?;
176        self.batches.clear();
177        self.num_rows = 0;
178        Ok(merged)
179    }
180}
181
182/// Reads last rows from a flat format row group and caches the results.
183pub(crate) struct FlatRowGroupLastRowReader {
184    key: SelectorResultKey,
185    reader: FlatRowGroupReader,
186    selector: FlatLastTimestampSelector,
187    yielded_batches: Vec<RecordBatch>,
188    cache_strategy: CacheStrategy,
189    read_cols: ParquetReadColumns,
190    json_target_types: JsonTargetTypes,
191    /// Accumulates small selector-output batches before concatenating.
192    pending: BatchBuffer,
193}
194
195impl FlatRowGroupLastRowReader {
196    fn new(
197        key: SelectorResultKey,
198        read_cols: ParquetReadColumns,
199        json_target_types: JsonTargetTypes,
200        reader: FlatRowGroupReader,
201        cache_strategy: CacheStrategy,
202    ) -> Self {
203        Self {
204            key,
205            reader,
206            selector: FlatLastTimestampSelector::default(),
207            yielded_batches: vec![],
208            cache_strategy,
209            read_cols,
210            json_target_types,
211            pending: BatchBuffer::new(),
212        }
213    }
214
215    /// Concatenates pending batches and records the result in `yielded_batches`.
216    fn flush_pending(&mut self) -> Result<Option<RecordBatch>> {
217        if self.pending.is_empty() {
218            return Ok(None);
219        }
220        let merged = self.pending.concat()?;
221        self.yielded_batches.push(merged.clone());
222        Ok(Some(merged))
223    }
224
225    async fn next_batch(&mut self) -> Result<Option<RecordBatch>> {
226        if self.pending.is_full() {
227            return self.flush_pending();
228        }
229
230        while let Some(batch) = self.reader.next_batch().await? {
231            self.selector.on_next(batch, &mut self.pending)?;
232            if self.pending.is_full() {
233                return self.flush_pending();
234            }
235        }
236
237        // Reader exhausted — flush remaining selector state.
238        self.selector.finish(&mut self.pending)?;
239        if !self.pending.is_empty() {
240            let result = self.flush_pending();
241            // All last rows in row group are yielded, update cache.
242            self.maybe_update_cache();
243            return result;
244        }
245
246        // All last rows in row group are yielded, update cache.
247        self.maybe_update_cache();
248        Ok(None)
249    }
250
251    fn maybe_update_cache(&mut self) {
252        if self.yielded_batches.is_empty() {
253            return;
254        }
255        let batches = std::mem::take(&mut self.yielded_batches);
256        let value = Arc::new(SelectorResultValue::new_flat(
257            batches,
258            self.read_cols.clone(),
259            self.json_target_types.clone(),
260        ));
261        self.cache_strategy.put_selector_result(self.key, value);
262    }
263}
264
265/// Selects the last-timestamp rows per primary key from flat `RecordBatch`.
266///
267/// Assumes that input batches are sorted by primary key then by timestamp,
268/// and contain only PUT operations (no DELETE).
269#[derive(Default)]
270pub(crate) struct FlatLastTimestampSelector {
271    /// State for the currently in-progress primary key.
272    current_key: Option<LastKeyState>,
273}
274
275#[derive(Debug)]
276struct LastKeyState {
277    key: Vec<u8>,
278    last_timestamp: i64,
279    slices: Vec<RecordBatch>,
280}
281
282impl LastKeyState {
283    fn new(key: Vec<u8>, last_timestamp: i64, first_slice: RecordBatch) -> Self {
284        Self {
285            key,
286            last_timestamp,
287            slices: vec![first_slice],
288        }
289    }
290}
291
292impl FlatLastTimestampSelector {
293    /// Keeps the latest timestamp candidates in one sorted PUT batch, including ties.
294    pub(crate) fn select_batch(batch: RecordBatch) -> Result<RecordBatch> {
295        if batch.num_rows() <= 1 {
296            return Ok(batch);
297        }
298        let Some(op_types) = batch
299            .column(op_type_column_index(batch.num_columns()))
300            .as_any()
301            .downcast_ref::<UInt8Array>()
302        else {
303            return Ok(batch);
304        };
305        if op_types.values().iter().any(|op| *op != OpType::Put as u8) {
306            return Ok(batch);
307        }
308        let mut selector = Self::default();
309        let mut pending = BatchBuffer::new();
310        selector.on_next(batch.clone(), &mut pending)?;
311        selector.finish(&mut pending)?;
312        if pending.num_rows == batch.num_rows() {
313            return Ok(batch);
314        }
315        pending.concat()
316    }
317
318    /// Processes the next batch and appends completed-key results into `output_buffer`.
319    pub(crate) fn on_next(
320        &mut self,
321        batch: RecordBatch,
322        output_buffer: &mut BatchBuffer,
323    ) -> Result<()> {
324        if batch.num_rows() == 0 {
325            return Ok(());
326        }
327
328        let num_columns = batch.num_columns();
329        let pk_col_idx = primary_key_column_index(num_columns);
330        let ts_col_idx = time_index_column_index(num_columns);
331
332        let pk_array = batch
333            .column(pk_col_idx)
334            .as_any()
335            .downcast_ref::<PrimaryKeyArray>()
336            .unwrap();
337        let offsets = primary_key_offsets(pk_array)?;
338        if offsets.is_empty() {
339            return Ok(());
340        }
341
342        // Dictionary keys may differ for the same primary key, e.g. readers that wrap binary
343        // keys into a dictionary with identity keys, so merge adjacent ranges by key bytes.
344        let mut series_ranges: Vec<(usize, usize)> = Vec::with_capacity(offsets.len() - 1);
345        for window in offsets.windows(2) {
346            let (start, end) = (window[0], window[1]);
347            match series_ranges.last_mut() {
348                Some(last)
349                    if primary_key_bytes_at(&batch, pk_col_idx, last.0)
350                        == primary_key_bytes_at(&batch, pk_col_idx, start) =>
351                {
352                    last.1 = end;
353                }
354                _ => series_ranges.push((start, end)),
355            }
356        }
357
358        let ts_values = timestamp_array_to_i64_slice(batch.column(ts_col_idx));
359        let num_ranges = series_ranges.len();
360        // Rows of keys that start and end inside this batch. They are gathered with one
361        // `take` instead of buffering a slice per key: concatenating thousands of single-row
362        // slices merges their dictionaries once per slice.
363        let mut completed_rows = Vec::new();
364        for (i, &(range_start, range_end)) in series_ranges.iter().enumerate() {
365            let range_key = primary_key_bytes_at(&batch, pk_col_idx, range_start);
366            let range_last_ts = ts_values[range_end - 1];
367            let range_last_ts_start = last_timestamp_start(ts_values, range_start, range_end);
368            let is_last_range = i + 1 == num_ranges;
369
370            // Only the first range can continue the key carried over from previous batches.
371            if let Some(state) = self.current_key.as_mut()
372                && state.key.as_slice() == range_key
373            {
374                let range_slice = batch.slice(range_last_ts_start, range_end - range_last_ts_start);
375                if range_last_ts > state.last_timestamp {
376                    state.last_timestamp = range_last_ts;
377                    state.slices.clear();
378                    state.slices.push(range_slice);
379                } else if range_last_ts == state.last_timestamp {
380                    state.slices.push(range_slice);
381                }
382                if !is_last_range {
383                    self.flush_current_key(output_buffer);
384                }
385                continue;
386            }
387
388            // Flushes the carried-over key before rows of this batch to keep the output sorted.
389            self.flush_current_key(output_buffer);
390            if is_last_range {
391                // The key may continue in the next batch.
392                self.current_key = Some(LastKeyState::new(
393                    range_key.to_vec(),
394                    range_last_ts,
395                    batch.slice(range_last_ts_start, range_end - range_last_ts_start),
396                ));
397            } else {
398                completed_rows.extend(range_last_ts_start as u32..range_end as u32);
399            }
400        }
401
402        if !completed_rows.is_empty() {
403            let indices = UInt32Array::from(completed_rows);
404            let selected = take_record_batch(&batch, &indices).context(ComputeArrowSnafu)?;
405            output_buffer.push(selected);
406        }
407
408        Ok(())
409    }
410
411    /// Finishes the selector and appends remaining results into `output_buffer`.
412    pub(crate) fn finish(&mut self, output_buffer: &mut BatchBuffer) -> Result<()> {
413        self.flush_current_key(output_buffer);
414        Ok(())
415    }
416
417    fn flush_current_key(&mut self, output_buffer: &mut BatchBuffer) {
418        let Some(state) = self.current_key.take() else {
419            return;
420        };
421        output_buffer.extend_from_slice(&state.slices);
422    }
423}
424
425/// Reader that keeps only the last row of each time series from a flat RecordBatch stream.
426/// Assumes input is sorted, deduped, and contains no delete operations.
427pub(crate) struct FlatLastRowReader {
428    stream: BoxedRecordBatchStream,
429    selector: FlatLastTimestampSelector,
430    pending: BatchBuffer,
431}
432
433impl FlatLastRowReader {
434    /// Creates a new `FlatLastRowReader`.
435    pub(crate) fn new(stream: BoxedRecordBatchStream) -> Self {
436        Self {
437            stream,
438            selector: FlatLastTimestampSelector::default(),
439            pending: BatchBuffer::new(),
440        }
441    }
442
443    /// Converts the reader into a stream of RecordBatches.
444    pub(crate) fn into_stream(mut self) -> impl Stream<Item = Result<RecordBatch>> {
445        async_stream::try_stream! {
446            while let Some(batch) = self.stream.try_next().await? {
447                self.selector.on_next(batch, &mut self.pending)?;
448                if self.pending.is_full() {
449                    yield self.pending.concat()?;
450                }
451            }
452            self.selector.finish(&mut self.pending)?;
453            if !self.pending.is_empty() {
454                yield self.pending.concat()?;
455            }
456        }
457    }
458}
459
460/// Gets the primary key bytes at `index` from the primary key dictionary column.
461fn primary_key_bytes_at(batch: &RecordBatch, pk_col_idx: usize, index: usize) -> &[u8] {
462    let pk_dict = batch
463        .column(pk_col_idx)
464        .as_any()
465        .downcast_ref::<PrimaryKeyArray>()
466        .unwrap();
467    let key = pk_dict.keys().value(index);
468    let binary_values = pk_dict
469        .values()
470        .as_any()
471        .downcast_ref::<BinaryArray>()
472        .unwrap();
473    binary_values.value(key as usize)
474}
475
476/// Finds the start index of rows sharing the last (maximum) timestamp
477/// within the range `[range_start, range_end)`.
478fn last_timestamp_start(ts_values: &[i64], range_start: usize, range_end: usize) -> usize {
479    debug_assert!(range_start < range_end);
480
481    let last_ts = ts_values[range_end - 1];
482    let mut start = range_end - 1;
483    while start > range_start && ts_values[start - 1] == last_ts {
484        start -= 1;
485    }
486    start
487}
488
489#[cfg(test)]
490mod tests {
491    use std::sync::Arc;
492
493    use datatypes::arrow::array::{
494        ArrayRef, BinaryDictionaryBuilder, DictionaryArray, Int64Array, TimestampMillisecondArray,
495        UInt8Array, UInt64Array,
496    };
497    use datatypes::arrow::datatypes::{DataType, Field, Schema, SchemaRef, TimeUnit, UInt32Type};
498    use datatypes::arrow::record_batch::RecordBatch;
499
500    use super::*;
501
502    #[test]
503    fn test_select_batch_preserves_timestamp_ties_and_deletes()
504    -> std::result::Result<(), Box<dyn std::error::Error>> {
505        let input = new_flat_batch(
506            &[b"a", b"a", b"a", b"b", b"b", b"c"],
507            &[1, 2, 2, 1, 2, 1],
508            &[1, 2, 3, 4, 5, 6],
509        );
510        let expected = take_record_batch(&input, &UInt32Array::from(vec![1, 2, 4, 5]))?;
511        assert_eq!(
512            expected,
513            FlatLastTimestampSelector::select_batch(input.clone())?
514        );
515        let empty = input.slice(0, 0);
516        assert_eq!(
517            empty,
518            FlatLastTimestampSelector::select_batch(empty.clone())?
519        );
520        let mut columns = input.columns().to_vec();
521        columns[op_type_column_index(input.num_columns())] = Arc::new(UInt8Array::from(vec![
522            OpType::Delete as u8,
523            OpType::Put as u8,
524            OpType::Put as u8,
525            OpType::Put as u8,
526            OpType::Put as u8,
527            OpType::Put as u8,
528        ]));
529        let with_delete = RecordBatch::try_new(input.schema(), columns)?;
530        assert_eq!(
531            with_delete,
532            FlatLastTimestampSelector::select_batch(with_delete.clone())?
533        );
534        Ok(())
535    }
536
537    /// Helper to build a flat format RecordBatch for testing.
538    fn new_flat_batch(primary_keys: &[&[u8]], timestamps: &[i64], fields: &[i64]) -> RecordBatch {
539        let num_rows = timestamps.len();
540        assert_eq!(primary_keys.len(), num_rows);
541        assert_eq!(fields.len(), num_rows);
542
543        let columns: Vec<ArrayRef> = vec![
544            // field0 column
545            Arc::new(Int64Array::from_iter_values(fields.iter().copied())),
546            // ts column (time index)
547            Arc::new(TimestampMillisecondArray::from_iter_values(
548                timestamps.iter().copied(),
549            )),
550            // __primary_key column (dictionary(uint32, binary))
551            {
552                let mut builder = BinaryDictionaryBuilder::<UInt32Type>::new();
553                for &pk in primary_keys {
554                    builder.append(pk).unwrap();
555                }
556                Arc::new(builder.finish())
557            },
558            // __sequence column
559            Arc::new(UInt64Array::from_iter_values(vec![1u64; num_rows])),
560            // __op_type column
561            Arc::new(UInt8Array::from_iter_values(vec![1u8; num_rows])),
562        ];
563
564        RecordBatch::try_new(test_flat_schema(), columns).unwrap()
565    }
566
567    fn test_flat_schema() -> SchemaRef {
568        let fields = vec![
569            Field::new("field0", DataType::Int64, false),
570            Field::new(
571                "ts",
572                DataType::Timestamp(TimeUnit::Millisecond, None),
573                false,
574            ),
575            Field::new(
576                "__primary_key",
577                DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Binary)),
578                false,
579            ),
580            Field::new("__sequence", DataType::UInt64, false),
581            Field::new("__op_type", DataType::UInt8, false),
582        ];
583        Arc::new(Schema::new(fields))
584    }
585
586    /// Collects all rows from the selector across all result batches.
587    fn collect_flat_results(
588        selector: &mut FlatLastTimestampSelector,
589        batches: Vec<RecordBatch>,
590    ) -> Vec<(Vec<u8>, i64)> {
591        let mut output_buffer = BatchBuffer::new();
592        let mut results = Vec::new();
593        for batch in batches {
594            selector.on_next(batch, &mut output_buffer).unwrap();
595            for r in output_buffer.batches.drain(..) {
596                extract_flat_rows(&r, &mut results);
597            }
598            output_buffer.num_rows = 0;
599        }
600        selector.finish(&mut output_buffer).unwrap();
601        for r in output_buffer.batches.drain(..) {
602            extract_flat_rows(&r, &mut results);
603        }
604        results
605    }
606
607    /// Extracts (primary_key, timestamp) pairs from a result batch.
608    fn extract_flat_rows(batch: &RecordBatch, out: &mut Vec<(Vec<u8>, i64)>) {
609        let ts_col = batch
610            .column(1)
611            .as_any()
612            .downcast_ref::<TimestampMillisecondArray>()
613            .unwrap();
614        let pk_col = batch
615            .column(2)
616            .as_any()
617            .downcast_ref::<PrimaryKeyArray>()
618            .unwrap();
619        let binary_values = pk_col
620            .values()
621            .as_any()
622            .downcast_ref::<BinaryArray>()
623            .unwrap();
624
625        for i in 0..batch.num_rows() {
626            let key_idx = pk_col.keys().value(i);
627            let pk = binary_values.value(key_idx as usize).to_vec();
628            let ts = ts_col.value(i);
629            out.push((pk, ts));
630        }
631    }
632
633    #[test]
634    fn test_flat_single_batch_one_key() {
635        let mut selector = FlatLastTimestampSelector::default();
636        let batch = new_flat_batch(&[b"k1", b"k1", b"k1"], &[1, 2, 3], &[10, 20, 30]);
637        let results = collect_flat_results(&mut selector, vec![batch]);
638        assert_eq!(vec![(b"k1".to_vec(), 3)], results);
639    }
640
641    #[test]
642    fn test_flat_single_batch_multiple_keys() {
643        let mut selector = FlatLastTimestampSelector::default();
644        let batch = new_flat_batch(
645            &[b"k1", b"k1", b"k2", b"k2", b"k3"],
646            &[1, 2, 3, 4, 5],
647            &[10, 20, 30, 40, 50],
648        );
649        let results = collect_flat_results(&mut selector, vec![batch]);
650        assert_eq!(
651            vec![
652                (b"k1".to_vec(), 2),
653                (b"k2".to_vec(), 4),
654                (b"k3".to_vec(), 5),
655            ],
656            results
657        );
658    }
659
660    #[test]
661    fn test_flat_key_spans_batches() {
662        let mut selector = FlatLastTimestampSelector::default();
663        let batches = vec![
664            new_flat_batch(&[b"k1", b"k1"], &[1, 2], &[10, 20]),
665            new_flat_batch(&[b"k1", b"k2"], &[3, 4], &[30, 40]),
666            new_flat_batch(&[b"k2", b"k3"], &[5, 6], &[50, 60]),
667        ];
668        let results = collect_flat_results(&mut selector, batches);
669        assert_eq!(
670            vec![
671                (b"k1".to_vec(), 3),
672                (b"k2".to_vec(), 5),
673                (b"k3".to_vec(), 6),
674            ],
675            results
676        );
677    }
678
679    #[test]
680    fn test_flat_duplicate_last_timestamps() {
681        let mut selector = FlatLastTimestampSelector::default();
682        // k1 has two rows with the same last timestamp (3).
683        let batch = new_flat_batch(
684            &[b"k1", b"k1", b"k1", b"k2"],
685            &[1, 3, 3, 5],
686            &[10, 20, 30, 40],
687        );
688        let results = collect_flat_results(&mut selector, vec![batch]);
689        assert_eq!(
690            vec![
691                (b"k1".to_vec(), 3),
692                (b"k1".to_vec(), 3),
693                (b"k2".to_vec(), 5),
694            ],
695            results
696        );
697    }
698
699    #[test]
700    fn test_flat_duplicate_last_timestamps_across_batches() {
701        let mut selector = FlatLastTimestampSelector::default();
702        // k1's last timestamp (3) spans two batches.
703        let batches = vec![
704            new_flat_batch(&[b"k1", b"k1"], &[1, 3], &[10, 20]),
705            new_flat_batch(&[b"k1", b"k2"], &[3, 5], &[30, 40]),
706        ];
707        let results = collect_flat_results(&mut selector, batches);
708        assert_eq!(
709            vec![
710                (b"k1".to_vec(), 3),
711                (b"k1".to_vec(), 3),
712                (b"k2".to_vec(), 5),
713            ],
714            results
715        );
716    }
717
718    #[test]
719    fn test_flat_pending_chain_dropped_by_higher_timestamp() {
720        let mut selector = FlatLastTimestampSelector::default();
721        let batches = vec![
722            new_flat_batch(&[b"k1", b"k1"], &[1, 3], &[10, 20]),
723            new_flat_batch(&[b"k1", b"k1"], &[3, 3], &[21, 22]),
724            new_flat_batch(&[b"k1", b"k1"], &[4, 4], &[23, 24]),
725        ];
726        let results = collect_flat_results(&mut selector, batches);
727        assert_eq!(vec![(b"k1".to_vec(), 4), (b"k1".to_vec(), 4)], results);
728    }
729
730    #[test]
731    fn test_flat_concat_across_parent_dictionaries() {
732        let mut selector = FlatLastTimestampSelector::default();
733        let mut output_buffer = BatchBuffer::new();
734        let batches = vec![
735            new_flat_batch(&[b"a", b"b", b"b", b"c"], &[1, 1, 2, 3], &[10, 20, 21, 30]),
736            new_flat_batch(
737                &[b"c", b"d", b"d", b"e", b"e"],
738                &[3, 4, 4, 5, 5],
739                &[31, 40, 41, 50, 51],
740            ),
741            new_flat_batch(&[b"f", b"g"], &[6, 7], &[60, 70]),
742        ];
743        for batch in batches {
744            selector.on_next(batch, &mut output_buffer).unwrap();
745        }
746        selector.finish(&mut output_buffer).unwrap();
747        let merged = output_buffer.concat().unwrap();
748
749        let mut rows = Vec::new();
750        extract_flat_rows(&merged, &mut rows);
751        let fields = merged
752            .column(0)
753            .as_any()
754            .downcast_ref::<Int64Array>()
755            .unwrap()
756            .values()
757            .to_vec();
758        assert_eq!(
759            vec![
760                (b"a".to_vec(), 1),
761                (b"b".to_vec(), 2),
762                (b"c".to_vec(), 3),
763                (b"c".to_vec(), 3),
764                (b"d".to_vec(), 4),
765                (b"d".to_vec(), 4),
766                (b"e".to_vec(), 5),
767                (b"e".to_vec(), 5),
768                (b"f".to_vec(), 6),
769                (b"g".to_vec(), 7),
770            ],
771            rows
772        );
773        assert_eq!(vec![10, 21, 30, 31, 40, 41, 50, 51, 60, 70], fields);
774    }
775
776    /// Rebuilds the primary key column with one dictionary key per row, as
777    /// `wrap_pk_binary_to_dict` does for binary primary keys.
778    fn with_identity_pk_keys(batch: RecordBatch) -> RecordBatch {
779        let pk_col_idx = primary_key_column_index(batch.num_columns());
780        let values = (0..batch.num_rows())
781            .map(|i| Some(primary_key_bytes_at(&batch, pk_col_idx, i)))
782            .collect::<BinaryArray>();
783        let keys = UInt32Array::from_iter_values(0..batch.num_rows() as u32);
784        let mut columns = batch.columns().to_vec();
785        columns[pk_col_idx] = Arc::new(DictionaryArray::new(keys, Arc::new(values)));
786        RecordBatch::try_new(batch.schema(), columns).unwrap()
787    }
788
789    #[test]
790    fn test_flat_identity_dictionary_keys() {
791        let mut selector = FlatLastTimestampSelector::default();
792        let batches = vec![
793            with_identity_pk_keys(new_flat_batch(&[b"a", b"a", b"b"], &[1, 2, 3], &[0; 3])),
794            with_identity_pk_keys(new_flat_batch(&[b"b", b"c", b"c"], &[4, 5, 6], &[0; 3])),
795        ];
796        let results = collect_flat_results(&mut selector, batches);
797        assert_eq!(
798            vec![(b"a".to_vec(), 2), (b"b".to_vec(), 4), (b"c".to_vec(), 6)],
799            results
800        );
801    }
802
803    #[test]
804    fn test_flat_finish_is_one_shot() {
805        let mut selector = FlatLastTimestampSelector::default();
806        let batch = new_flat_batch(&[b"k1", b"k1", b"k2"], &[1, 2, 3], &[10, 20, 30]);
807        let mut output_buffer = BatchBuffer::new();
808
809        // Feed one batch: completed keys can be emitted before EOF.
810        selector.on_next(batch, &mut output_buffer).unwrap();
811        let mut pre_finish = Vec::new();
812        for r in output_buffer.batches.drain(..) {
813            extract_flat_rows(&r, &mut pre_finish);
814        }
815        output_buffer.num_rows = 0;
816        assert_eq!(vec![(b"k1".to_vec(), 2)], pre_finish);
817
818        // Simulate EOF by calling finish().
819        selector.finish(&mut output_buffer).unwrap();
820        assert!(!output_buffer.is_empty());
821        output_buffer.batches.clear();
822        output_buffer.num_rows = 0;
823
824        // A second finish after EOF should not yield any more rows.
825        selector.finish(&mut output_buffer).unwrap();
826        assert!(output_buffer.is_empty());
827    }
828}