Skip to main content

odbc_api/cursor/
polling_cursor.rs

1use crate::{
2    Error,
3    handles::{AsStatementRef, Statement, StatementRef},
4    sleep::{Sleep, wait_for},
5};
6
7use super::{
8    RowSetBuffer, bind_row_set_buffer_to_statement, error_handling_for_fetch,
9    unbind_buffer_from_cursor,
10};
11
12use std::thread::panicking;
13
14/// The asynchronous sibiling of [`super::CursorImpl`]. Use this to fetch results in asynchronous
15/// code.
16///
17/// Like [`super::CursorImpl`] this is an ODBC statement handle in cursor state. However unlike its
18/// synchronous sibling this statement handle is in asynchronous polling mode.
19pub struct CursorPolling<Stmt: Statement> {
20    /// A statement handle in cursor state with asynchronous mode enabled.
21    statement: Stmt,
22}
23
24impl<S> CursorPolling<S>
25where
26    S: Statement,
27{
28    /// Users of this library are encouraged not to call this constructor directly. This method is
29    /// pubilc so users with an understanding of the raw ODBC C-API have a way to create an
30    /// asynchronous cursor, after they left the safety rails of the Rust type System, in order to
31    /// implement a use case not covered yet, by the safe abstractions within this crate.
32    ///
33    /// # Safety
34    ///
35    /// `statement` must be in Cursor state, for the invariants of this type to hold. Preferable
36    /// `statement` should also have asynchrous mode enabled, otherwise constructing a synchronous
37    /// [`super::CursorImpl`] is more suitable.
38    pub unsafe fn new(statement: S) -> Self {
39        Self { statement }
40    }
41
42    /// Binds this cursor to a buffer holding a row set.
43    pub fn bind_buffer<B>(
44        mut self,
45        mut row_set_buffer: B,
46    ) -> Result<BlockCursorPolling<Self, B>, Error>
47    where
48        B: RowSetBuffer,
49    {
50        let stmt = self.statement.as_stmt_ref();
51        unsafe {
52            bind_row_set_buffer_to_statement(stmt, &mut row_set_buffer)?;
53        }
54        Ok(BlockCursorPolling::new(row_set_buffer, self))
55    }
56}
57
58impl<S> AsStatementRef for CursorPolling<S>
59where
60    S: Statement,
61{
62    fn as_stmt_ref(&mut self) -> StatementRef<'_> {
63        self.statement.as_stmt_ref()
64    }
65}
66
67impl<S> Drop for CursorPolling<S>
68where
69    S: Statement,
70{
71    fn drop(&mut self) {
72        if let Err(e) = self
73            .statement
74            .end_cursor_scope()
75            .into_result(&self.statement)
76        {
77            // Avoid panicking, if we already have a panic. We don't want to mask the original
78            // error.
79            if !panicking() {
80                panic!("Unexpected error closing cursor: {e:?}")
81            }
82        }
83    }
84}
85
86/// Asynchronously iterates in blocks (called row sets) over a result set, filling a buffers with
87/// a lot of rows at once, instead of iterating the result set row by row. This is usually much
88/// faster. Asynchronous sibiling of [`super::BlockCursor`].
89pub struct BlockCursorPolling<C, B>
90where
91    C: AsStatementRef,
92{
93    buffer: B,
94    cursor: C,
95}
96
97impl<C, B> BlockCursorPolling<C, B>
98where
99    C: AsStatementRef,
100{
101    fn new(buffer: B, cursor: C) -> Self {
102        Self { buffer, cursor }
103    }
104
105    /// Fills the bound buffer with the next row set.
106    ///
107    /// # Return
108    ///
109    /// `None` if the result set is empty and all row sets have been extracted. `Some` with a
110    /// reference to the internal buffer otherwise.
111    pub async fn fetch(&mut self, sleep: impl Sleep) -> Result<Option<&B>, Error>
112    where
113        B: RowSetBuffer,
114    {
115        self.fetch_with_truncation_check(false, sleep).await
116    }
117
118    /// Fills the bound buffer with the next row set. Should `error_for_truncation` be `true`and any
119    /// diagnostic indicate truncation of a value an error is returned.
120    ///
121    /// # Return
122    ///
123    /// `None` if the result set is empty and all row sets have been extracted. `Some` with a
124    /// reference to the internal buffer otherwise.
125    ///
126    /// Call this method to find out whether there are any truncated values in the batch, without
127    /// inspecting all its rows and columns.
128    pub async fn fetch_with_truncation_check(
129        &mut self,
130        error_for_truncation: bool,
131        mut sleep: impl Sleep,
132    ) -> Result<Option<&B>, Error>
133    where
134        B: RowSetBuffer,
135    {
136        let mut stmt = self.cursor.as_stmt_ref();
137        let result = unsafe { wait_for(|| stmt.fetch(), &mut sleep).await };
138        let has_row = error_handling_for_fetch(result, stmt, &self.buffer, error_for_truncation)?;
139        Ok(has_row.then_some(&self.buffer))
140    }
141}
142
143impl<C, B> Drop for BlockCursorPolling<C, B>
144where
145    C: AsStatementRef,
146{
147    fn drop(&mut self) {
148        if let Err(e) = unbind_buffer_from_cursor(&mut self.cursor) {
149            // Avoid panicking, if we already have a panic. We don't want to mask the original
150            // error.
151            if !panicking() {
152                panic!("Unexpected error unbinding columns: {e:?}")
153            }
154        }
155    }
156}