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