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