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}