oracle_rs/
cursor.rs

1//! Scrollable cursor support for Oracle connections
2//!
3//! This module provides types for scrollable cursors that allow
4//! bidirectional navigation through result sets.
5//!
6//! # Example
7//!
8//! ```rust,ignore
9//! use oracle_rs::{Connection, ScrollableCursor, FetchOrientation};
10//!
11//! let conn = Connection::connect("localhost:1521/ORCLPDB1", "user", "pass").await?;
12//!
13//! // Open a scrollable cursor
14//! let mut cursor = conn.open_scrollable_cursor(
15//!     "SELECT * FROM employees ORDER BY employee_id"
16//! ).await?;
17//!
18//! // Navigate to different positions
19//! let first_row = cursor.scroll(FetchOrientation::First, 0).await?;
20//! let last_row = cursor.scroll(FetchOrientation::Last, 0).await?;
21//! let row_5 = cursor.scroll(FetchOrientation::Absolute, 5).await?;
22//!
23//! // Close the cursor
24//! cursor.close().await?;
25//! ```
26
27use crate::constants::FetchOrientation;
28use crate::row::Row;
29use crate::statement::ColumnInfo;
30
31/// A scrollable cursor for navigating result sets
32///
33/// Scrollable cursors allow moving forward and backward through result sets,
34/// jumping to specific positions, and fetching from various positions.
35/// This incurs additional overhead on the server to maintain cursor state.
36#[derive(Debug)]
37pub struct ScrollableCursor {
38    /// Cursor ID on the server
39    pub(crate) cursor_id: u16,
40    /// Column metadata
41    pub(crate) columns: Vec<ColumnInfo>,
42    /// Whether the cursor is open
43    pub(crate) is_open: bool,
44    /// Current row position (1-based, 0 means before first row)
45    pub(crate) position: i64,
46    /// Total row count (if known)
47    pub(crate) row_count: Option<u64>,
48}
49
50impl ScrollableCursor {
51    /// Create a new scrollable cursor
52    pub(crate) fn new(cursor_id: u16, columns: Vec<ColumnInfo>) -> Self {
53        Self {
54            cursor_id,
55            columns,
56            is_open: true,
57            position: 0,
58            row_count: None,
59        }
60    }
61
62    /// Get the cursor ID
63    pub fn cursor_id(&self) -> u16 {
64        self.cursor_id
65    }
66
67    /// Get column metadata
68    pub fn columns(&self) -> &[ColumnInfo] {
69        &self.columns
70    }
71
72    /// Check if the cursor is open
73    pub fn is_open(&self) -> bool {
74        self.is_open
75    }
76
77    /// Get the current position (1-based)
78    pub fn position(&self) -> i64 {
79        self.position
80    }
81
82    /// Get the total row count if known
83    pub fn row_count(&self) -> Option<u64> {
84        self.row_count
85    }
86
87    /// Mark the cursor as closed
88    pub(crate) fn mark_closed(&mut self) {
89        self.is_open = false;
90    }
91
92    /// Update position after a scroll operation
93    pub(crate) fn update_position(&mut self, new_position: i64) {
94        self.position = new_position;
95    }
96}
97
98/// Options for creating scrollable cursors
99#[derive(Debug, Clone, Default)]
100pub struct ScrollableCursorOptions {
101    /// Array size for batch fetching
102    pub array_size: u32,
103}
104
105impl ScrollableCursorOptions {
106    /// Create new scrollable cursor options
107    pub fn new() -> Self {
108        Self::default()
109    }
110
111    /// Set the array size
112    pub fn with_array_size(mut self, size: u32) -> Self {
113        self.array_size = size;
114        self
115    }
116}
117
118/// Result from a scroll operation
119#[derive(Debug)]
120pub struct ScrollResult {
121    /// The rows fetched
122    pub rows: Vec<Row>,
123    /// New cursor position after scroll
124    pub position: i64,
125    /// Whether the cursor hit the beginning
126    pub at_beginning: bool,
127    /// Whether the cursor hit the end
128    pub at_end: bool,
129}
130
131impl ScrollResult {
132    /// Create a new scroll result
133    pub fn new(rows: Vec<Row>, position: i64) -> Self {
134        Self {
135            rows,
136            position,
137            at_beginning: false,
138            at_end: false,
139        }
140    }
141
142    /// Get the first row if any
143    pub fn first(&self) -> Option<&Row> {
144        self.rows.first()
145    }
146
147    /// Check if no rows were returned
148    pub fn is_empty(&self) -> bool {
149        self.rows.is_empty()
150    }
151
152    /// Get the number of rows returned
153    pub fn len(&self) -> usize {
154        self.rows.len()
155    }
156}
157
158/// Scroll mode for Python-compatible API
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum ScrollMode {
161    /// Move to first row
162    First,
163    /// Move to last row
164    Last,
165    /// Move relative to current position (default)
166    Relative,
167    /// Move to absolute position
168    Absolute,
169}
170
171impl Default for ScrollMode {
172    fn default() -> Self {
173        Self::Relative
174    }
175}
176
177impl From<ScrollMode> for FetchOrientation {
178    fn from(mode: ScrollMode) -> Self {
179        match mode {
180            ScrollMode::First => FetchOrientation::First,
181            ScrollMode::Last => FetchOrientation::Last,
182            ScrollMode::Relative => FetchOrientation::Relative,
183            ScrollMode::Absolute => FetchOrientation::Absolute,
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::constants::OracleType;
192
193    #[test]
194    fn test_scrollable_cursor_creation() {
195        let columns = vec![
196            ColumnInfo::new("ID", OracleType::Number),
197            ColumnInfo::new("NAME", OracleType::Varchar),
198        ];
199        let cursor = ScrollableCursor::new(123, columns);
200
201        assert_eq!(cursor.cursor_id(), 123);
202        assert_eq!(cursor.columns().len(), 2);
203        assert!(cursor.is_open());
204        assert_eq!(cursor.position(), 0);
205    }
206
207    #[test]
208    fn test_scrollable_cursor_close() {
209        let mut cursor = ScrollableCursor::new(1, Vec::new());
210        assert!(cursor.is_open());
211        cursor.mark_closed();
212        assert!(!cursor.is_open());
213    }
214
215    #[test]
216    fn test_scroll_result() {
217        let rows = vec![Row::new(vec![crate::row::Value::Integer(1)])];
218        let result = ScrollResult::new(rows, 5);
219
220        assert_eq!(result.len(), 1);
221        assert!(!result.is_empty());
222        assert_eq!(result.position, 5);
223        assert!(result.first().is_some());
224    }
225
226    #[test]
227    fn test_scroll_mode_conversion() {
228        assert_eq!(FetchOrientation::from(ScrollMode::First), FetchOrientation::First);
229        assert_eq!(FetchOrientation::from(ScrollMode::Last), FetchOrientation::Last);
230        assert_eq!(FetchOrientation::from(ScrollMode::Relative), FetchOrientation::Relative);
231        assert_eq!(FetchOrientation::from(ScrollMode::Absolute), FetchOrientation::Absolute);
232    }
233
234    #[test]
235    fn test_scrollable_cursor_options() {
236        let opts = ScrollableCursorOptions::new()
237            .with_array_size(50);
238        assert_eq!(opts.array_size, 50);
239    }
240}