odbc_api/cursor/
block_cursor.rs

1use std::{mem::MaybeUninit, ptr, thread::panicking};
2
3use crate::{
4    Error,
5    handles::{AsStatementRef, Statement as _},
6};
7
8use super::{Cursor, RowSetBuffer, error_handling_for_fetch, unbind_buffer_from_cursor};
9
10/// In order to save on network overhead, it is recommended to use block cursors instead of fetching
11/// values individually. This can greatly reduce the time applications need to fetch data. You can
12/// create a block cursor by binding preallocated memory to a cursor using [`Cursor::bind_buffer`].
13/// A block cursor saves on a lot of IO overhead by fetching an entire set of rows (called *rowset*)
14/// at once into the buffer bound to it. Reusing the same buffer for each rowset also saves on
15/// allocations. A challange with using block cursors might be database schemas with columns there
16/// individual fields can be very large. In these cases developers can choose to:
17///
18/// 1. Reserve less memory for each individual field than the schema indicates and deciding on a
19///    sensible upper bound themselves. This risks truncation of values though, if they are larger
20///    than the upper bound. Using [`BlockCursor::fetch_with_truncation_check`] instead of
21///    [`Cursor::next_row`] your application can detect these truncations. This is usually the best
22///    choice, since individual fields in a table rarely actually take up several GiB of memory.
23/// 2. Calculate the number of rows dynamically based on the maximum expected row size.
24///    [`crate::buffers::BufferDesc::bytes_per_row`], can be helpful with this task.
25/// 3. Not use block cursors and fetch rows slowly with high IO overhead. Calling
26///    [`crate::CursorRow::get_data`] and [`crate::CursorRow::get_text`] to fetch large individual
27///    values.
28///
29/// See: <https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/block-cursors>
30pub struct BlockCursor<C: AsStatementRef, B> {
31    buffer: B,
32    cursor: C,
33}
34
35impl<C, B> BlockCursor<C, B>
36where
37    C: Cursor,
38{
39    pub(crate) fn new(buffer: B, cursor: C) -> Self {
40        Self { buffer, cursor }
41    }
42
43    /// Fills the bound buffer with the next row set.
44    ///
45    /// # Return
46    ///
47    /// `None` if the result set is empty and all row sets have been extracted. `Some` with a
48    /// reference to the internal buffer otherwise.
49    ///
50    /// ```
51    /// use odbc_api::{buffers::TextRowSet, Cursor};
52    ///
53    /// fn print_all_values(mut cursor: impl Cursor) {
54    ///     let batch_size = 100;
55    ///     let max_string_len = 4000;
56    ///     let buffer = TextRowSet::for_cursor(batch_size, &mut cursor, Some(4000)).unwrap();
57    ///     let mut cursor = cursor.bind_buffer(buffer).unwrap();
58    ///     // Iterate over batches
59    ///     while let Some(batch) = cursor.fetch().unwrap() {
60    ///         // ... print values in batch ...
61    ///     }
62    /// }
63    /// ```
64    pub fn fetch(&mut self) -> Result<Option<&B>, Error>
65    where
66        B: RowSetBuffer,
67    {
68        self.fetch_with_truncation_check(false)
69    }
70
71    /// Fills the bound buffer with the next row set. Should `error_for_truncation` be `true`and any
72    /// diagnostic indicate truncation of a value an error is returned.
73    ///
74    /// # Return
75    ///
76    /// `None` if the result set is empty and all row sets have been extracted. `Some` with a
77    /// reference to the internal buffer otherwise.
78    ///
79    /// Call this method to find out wether there are any truncated values in the batch, without
80    /// inspecting all its rows and columns.
81    ///
82    /// ```
83    /// use odbc_api::{buffers::TextRowSet, Cursor};
84    ///
85    /// fn print_all_values(mut cursor: impl Cursor) {
86    ///     let batch_size = 100;
87    ///     let max_string_len = 4000;
88    ///     let buffer = TextRowSet::for_cursor(batch_size, &mut cursor, Some(4000)).unwrap();
89    ///     let mut cursor = cursor.bind_buffer(buffer).unwrap();
90    ///     // Iterate over batches
91    ///     while let Some(batch) = cursor.fetch_with_truncation_check(true).unwrap() {
92    ///         // ... print values in batch ...
93    ///     }
94    /// }
95    /// ```
96    pub fn fetch_with_truncation_check(
97        &mut self,
98        error_for_truncation: bool,
99    ) -> Result<Option<&B>, Error>
100    where
101        B: RowSetBuffer,
102    {
103        let mut stmt = self.cursor.as_stmt_ref();
104        unsafe {
105            let result = stmt.fetch();
106            let has_row =
107                error_handling_for_fetch(result, stmt, &self.buffer, error_for_truncation)?;
108            Ok(has_row.then_some(&self.buffer))
109        }
110    }
111
112    /// Unbinds the buffer from the underlying statement handle. Potential usecases for this
113    /// function include.
114    ///
115    /// 1. Binding a different buffer to the "same" cursor after letting it point to the next result
116    ///    set obtained with [Cursor::more_results`].
117    /// 2. Reusing the same buffer with a different statement.
118    pub fn unbind(self) -> Result<(C, B), Error> {
119        // In this method we want to deconstruct self and move cursor out of it. We need to
120        // negotiate with the compiler a little bit though, since BlockCursor does implement `Drop`.
121
122        // We want to move `cursor` out of self, which would make self partially uninitialized.
123        let dont_drop_me = MaybeUninit::new(self);
124        let self_ptr = dont_drop_me.as_ptr();
125
126        // Safety: We know `dont_drop_me` is valid at this point so reading the ptr is okay
127        let mut cursor = unsafe { ptr::read(&(*self_ptr).cursor) };
128        let buffer = unsafe { ptr::read(&(*self_ptr).buffer) };
129
130        // Now that we have cursor out of block cursor, we need to unbind the buffer.
131        unbind_buffer_from_cursor(&mut cursor)?;
132
133        Ok((cursor, buffer))
134    }
135}
136
137impl<C, B> BlockCursor<C, B>
138where
139    B: RowSetBuffer,
140    C: AsStatementRef,
141{
142    /// Maximum amount of rows fetched from the database in the next call to fetch.
143    pub fn row_array_size(&self) -> usize {
144        self.buffer.row_array_size()
145    }
146}
147
148impl<C, B> Drop for BlockCursor<C, B>
149where
150    C: AsStatementRef,
151{
152    fn drop(&mut self) {
153        if let Err(e) = unbind_buffer_from_cursor(&mut self.cursor) {
154            // Avoid panicking, if we already have a panic. We don't want to mask the original
155            // error.
156            if !panicking() {
157                panic!("Unexpected error unbinding columns: {e:?}")
158            }
159        }
160    }
161}