Skip to main content

oxisql_core/
cursor.rs

1//! [`Cursor`] — incremental result-set traversal over a [`Vec<Row>`].
2
3use crate::Row;
4
5/// An incremental cursor for traversing a materialised result set.
6///
7/// `Cursor` wraps a `Vec<Row>` and tracks the current position, supporting
8/// both peek-ahead access and owned-row iteration via [`Iterator`].
9///
10/// # Example
11///
12/// ```rust
13/// use oxisql_core::{Cursor, Row, Value};
14///
15/// let rows = vec![
16///     Row::new(vec!["id".into()], vec![Value::I64(1)]),
17///     Row::new(vec!["id".into()], vec![Value::I64(2)]),
18/// ];
19/// let mut cursor = Cursor::new(rows);
20/// assert_eq!(cursor.len(), 2);
21///
22/// let first = cursor.advance().expect("first row");
23/// assert_eq!(first.try_get::<i64>("id").unwrap(), 1);
24///
25/// cursor.reset();
26/// assert_eq!(cursor.position(), 0);
27/// ```
28#[derive(Debug, Clone)]
29pub struct Cursor {
30    rows: Vec<Row>,
31    position: usize,
32}
33
34impl Cursor {
35    /// Create a new `Cursor` over `rows`, positioned at the beginning.
36    pub fn new(rows: Vec<Row>) -> Self {
37        Self { rows, position: 0 }
38    }
39
40    /// Advance by one and return a shared reference to the current row, or
41    /// `None` when past the end.
42    ///
43    /// Unlike the [`Iterator`] implementation (which yields owned `Row`s),
44    /// this method borrows the row in place, allowing repeated access to the
45    /// same row without cloning.
46    pub fn advance(&mut self) -> Option<&Row> {
47        let pos = self.position;
48        if pos < self.rows.len() {
49            self.position = pos + 1;
50            self.rows.get(pos)
51        } else {
52            None
53        }
54    }
55
56    /// Return a shared reference to the *current* row without advancing, or
57    /// `None` when past the end.
58    pub fn peek(&self) -> Option<&Row> {
59        self.rows.get(self.position)
60    }
61
62    /// Reset the cursor back to position 0.
63    pub fn reset(&mut self) {
64        self.position = 0;
65    }
66
67    /// Return the current cursor position (zero-based index into the row
68    /// collection; equal to the number of rows consumed so far).
69    pub fn position(&self) -> usize {
70        self.position
71    }
72
73    /// Return the total number of rows in the cursor's collection.
74    pub fn len(&self) -> usize {
75        self.rows.len()
76    }
77
78    /// Return `true` if the underlying row collection is empty.
79    pub fn is_empty(&self) -> bool {
80        self.rows.is_empty()
81    }
82
83    /// Return the number of rows that have not yet been consumed.
84    pub fn remaining(&self) -> usize {
85        self.rows.len().saturating_sub(self.position)
86    }
87
88    /// Consume the cursor and return the underlying row vector.
89    pub fn into_rows(self) -> Vec<Row> {
90        self.rows
91    }
92
93    /// Advance the cursor by up to `n` positions without returning rows.
94    ///
95    /// The cursor will not move past the end of the collection.
96    ///
97    /// Note: this method is named `skip_by` to avoid shadowing the
98    /// [`Iterator::skip`] adapter, which takes ownership of the iterator.
99    pub fn skip_by(&mut self, n: usize) {
100        self.position = (self.position + n).min(self.rows.len());
101    }
102}
103
104impl Iterator for Cursor {
105    type Item = Row;
106
107    /// Advance the cursor and return an owned clone of the current row, or
108    /// `None` when past the end.
109    fn next(&mut self) -> Option<Row> {
110        let pos = self.position;
111        if pos < self.rows.len() {
112            self.position = pos + 1;
113            Some(self.rows[pos].clone())
114        } else {
115            None
116        }
117    }
118}