Skip to main content

odbc_api/
cursor.rs

1mod block_cursor;
2mod concurrent_block_cursor;
3mod polling_cursor;
4
5use log::warn;
6use odbc_sys::HStmt;
7
8use crate::{
9    Error, ResultSetMetadata,
10    buffers::Indicator,
11    error::ExtendResult,
12    handles::{
13        AsStatementRef, CDataMut, DiagnosticStream, SqlResult, State, Statement, StatementRef,
14    },
15    parameter::{Binary, CElement, Text, VarCell, VarKind, WideText},
16};
17
18use std::{
19    mem::{MaybeUninit, size_of},
20    ptr,
21    thread::panicking,
22};
23
24pub use self::{
25    block_cursor::{BlockCursor, BlockCursorIterator},
26    concurrent_block_cursor::ConcurrentBlockCursor,
27    polling_cursor::{BlockCursorPolling, CursorPolling},
28};
29
30/// Cursors are used to process and iterate the result sets returned by executing queries.
31///
32/// # Example: Fetching result in batches
33///
34/// ```rust
35/// use odbc_api::{Cursor, buffers::{BufferDesc, ColumnarAnyBuffer}, Error};
36///
37/// /// Fetches all values from the first column of the cursor as i32 in batches of 100 and stores
38/// /// them in a vector.
39/// fn fetch_all_ints(cursor: impl Cursor) -> Result<Vec<i32>, Error> {
40///     let mut all_ints = Vec::new();
41///     // Batch size determines how many values we fetch at once.
42///     let batch_size = 100;
43///     // We expect the first column to hold INTEGERs (or a type convertible to INTEGER). Use
44///     // the metadata on the result set, if you want to investige the types of the columns at
45///     // runtime.
46///     let description = BufferDesc::I32 { nullable: false };
47///     // This is the buffer we bind to the driver, and repeatedly use to fetch each batch
48///     let buffer = ColumnarAnyBuffer::from_descs(batch_size, [description]);
49///     // Bind buffer to cursor
50///     let mut row_set_buffer = cursor.bind_buffer(buffer)?;
51///     // Fetch data batch by batch
52///     while let Some(batch) = row_set_buffer.fetch()? {
53///         all_ints.extend_from_slice(batch.column(0).as_slice().unwrap())
54///     }
55///     Ok(all_ints)
56/// }
57/// ```
58pub trait Cursor: ResultSetMetadata {
59    /// Advances the cursor to the next row in the result set. This is **Slow**. Bind
60    /// [`crate::buffers`] instead, for good performance.
61    ///
62    /// ⚠ While this method is very convenient due to the fact that the application does not have
63    /// to declare and bind specific buffers, it is also in many situations extremely slow. Concrete
64    /// performance depends on the ODBC driver in question, but it is likely it performs a roundtrip
65    /// to the datasource for each individual row. It is also likely an extra conversion is
66    /// performed then requesting individual fields, since the C buffer type is not known to the
67    /// driver in advance. Consider binding a buffer to the cursor first using
68    /// [`Self::bind_buffer`].
69    ///
70    /// That being said, it is a convenient programming model, as the developer does not need to
71    /// prepare and allocate the buffers beforehand. It is also a good way to retrieve really large
72    /// single values out of a data source (like one large text file). See [`CursorRow::get_text`].
73    fn next_row(&mut self) -> Result<Option<CursorRow<'_>>, Error> {
74        let row_available = unsafe {
75            self.as_stmt_ref()
76                .fetch()
77                .into_result_bool(&self.as_stmt_ref())?
78        };
79        let ret = if row_available {
80            Some(unsafe { CursorRow::new(self.as_stmt_ref()) })
81        } else {
82            None
83        };
84        Ok(ret)
85    }
86
87    /// Binds this cursor to a buffer holding a row set.
88    fn bind_buffer<B>(self, row_set_buffer: B) -> Result<BlockCursor<Self, B>, Error>
89    where
90        Self: Sized,
91        B: RowSetBuffer;
92
93    /// For some datasources it is possible to create more than one result set at once via a call to
94    /// execute. E.g. by calling a stored procedure or executing multiple SQL statements at once.
95    /// This method consumes the current cursor and creates a new one representing the next result
96    /// set should it exist.
97    fn more_results(self) -> Result<Option<Self>, Error>
98    where
99        Self: Sized;
100
101    /// Close the current cursor explicitly. Allows application to handle errors emitted by
102    /// `SQLCloseCursor`.
103    fn close(self) -> Result<(), Error>;
104}
105
106/// An individual row of an result set. See [`crate::Cursor::next_row`].
107pub struct CursorRow<'s> {
108    statement: StatementRef<'s>,
109}
110
111impl<'s> CursorRow<'s> {
112    /// # Safety
113    ///
114    /// `statement` must be in a cursor state.
115    unsafe fn new(statement: StatementRef<'s>) -> Self {
116        CursorRow { statement }
117    }
118}
119
120impl CursorRow<'_> {
121    /// Fills a suitable target buffer with a field from the current row of the result set. This
122    /// method drains the data from the field. It can be called repeatedly to if not all the data
123    /// fit in the output buffer at once. It should not called repeatedly to fetch the same value
124    /// twice. Column index starts at `1`.
125    ///
126    /// You can use [`crate::Nullable`] to fetch nullable values.
127    ///
128    /// # Example
129    ///
130    /// ```
131    /// # use odbc_api::{Cursor, Error, Nullable};
132    /// # fn fetch_values_example(cursor: &mut impl Cursor) -> Result<(), Error> {
133    /// // Declare nullable value to fetch value into. ODBC values layout is different from Rusts
134    /// // option. We can not use `Option<i32>` directly.
135    /// let mut field = Nullable::<i32>::null();
136    /// // Move cursor to next row
137    /// let mut row = cursor.next_row()?.unwrap();
138    /// // Fetch first column into field
139    /// row.get_data(1, &mut field)?;
140    /// // Convert nullable value to Option for convinience
141    /// let field = field.into_opt();
142    /// if let Some(value) = field {
143    ///     println!("Value: {}", value);
144    /// } else {
145    ///     println!("Value is NULL");
146    /// }
147    /// # Ok(())
148    /// # }
149    /// ```
150    pub fn get_data(
151        &mut self,
152        col_or_param_num: u16,
153        target: &mut (impl CElement + CDataMut),
154    ) -> Result<(), Error> {
155        self.statement
156            .get_data(col_or_param_num, target)
157            .into_result(&self.statement)
158            .provide_context_for_diagnostic(|record, function| {
159                if record.state == State::INDICATOR_VARIABLE_REQUIRED_BUT_NOT_SUPPLIED {
160                    Error::UnableToRepresentNull(record)
161                } else {
162                    Error::Diagnostics { record, function }
163                }
164            })
165    }
166
167    /// Retrieves arbitrary large character data from the row and stores it in the buffer. Column
168    /// index starts at `1`. The used encoding is accordig to the ODBC standard determined by your
169    /// system local. Ultimatly the choice is up to the implementation of your ODBC driver, which
170    /// often defaults to always UTF-8.
171    ///
172    /// # Example
173    ///
174    /// Retrieve an arbitrary large text file from a database field.
175    ///
176    /// ```
177    /// use odbc_api::{Connection, Error, IntoParameter, Cursor};
178    ///
179    /// fn get_large_text(name: &str, conn: &mut Connection<'_>) -> Result<Option<String>, Error> {
180    ///     let query = "SELECT content FROM LargeFiles WHERE name=?";
181    ///     let parameters = &name.into_parameter();
182    ///     let timeout_sec = None;
183    ///     let mut cursor = conn
184    ///         .execute(query, parameters, timeout_sec)?
185    ///         .expect("Assume select statement creates cursor");
186    ///     if let Some(mut row) = cursor.next_row()? {
187    ///         let mut buf = Vec::new();
188    ///         row.get_text(1, &mut buf)?;
189    ///         let ret = String::from_utf8(buf).unwrap();
190    ///         Ok(Some(ret))
191    ///     } else {
192    ///         Ok(None)
193    ///     }
194    /// }
195    /// ```
196    ///
197    /// # Return
198    ///
199    /// `true` indicates that the value has not been `NULL` and the value has been placed in `buf`.
200    /// `false` indicates that the value is `NULL`. The buffer is cleared in that case.
201    pub fn get_text(&mut self, col_or_param_num: u16, buf: &mut Vec<u8>) -> Result<bool, Error> {
202        self.get_variadic::<Text>(col_or_param_num, buf)
203    }
204
205    /// Retrieves arbitrary large character data from the row and stores it in the buffer. Column
206    /// index starts at `1`. The used encoding is UTF-16.
207    ///
208    /// # Return
209    ///
210    /// `true` indicates that the value has not been `NULL` and the value has been placed in `buf`.
211    /// `false` indicates that the value is `NULL`. The buffer is cleared in that case.
212    pub fn get_wide_text(
213        &mut self,
214        col_or_param_num: u16,
215        buf: &mut Vec<u16>,
216    ) -> Result<bool, Error> {
217        self.get_variadic::<WideText>(col_or_param_num, buf)
218    }
219
220    /// Retrieves arbitrary large binary data from the row and stores it in the buffer. Column index
221    /// starts at `1`.
222    ///
223    /// # Return
224    ///
225    /// `true` indicates that the value has not been `NULL` and the value has been placed in `buf`.
226    /// `false` indicates that the value is `NULL`. The buffer is cleared in that case.
227    pub fn get_binary(&mut self, col_or_param_num: u16, buf: &mut Vec<u8>) -> Result<bool, Error> {
228        self.get_variadic::<Binary>(col_or_param_num, buf)
229    }
230
231    fn get_variadic<K: VarKind>(
232        &mut self,
233        col_or_param_num: u16,
234        buf: &mut Vec<K::Element>,
235    ) -> Result<bool, Error> {
236        if buf.capacity() == 0 {
237            // User did just provide an empty buffer. So it is fair to assume not much domain
238            // knowledge has been used to decide its size. We just default to 256 to increase the
239            // chance that we get it done with one alloctaion. The buffer size being 0 we need at
240            // least 1 anyway. If the capacity is not `0` we'll leave the buffer size untouched as
241            // we do not want to prevent users from providing better guessen based on domain
242            // knowledge.
243            // This also implicitly makes sure that we can at least hold one terminating zero.
244            buf.reserve(256);
245        }
246        // Utilize all of the allocated buffer.
247        buf.resize(buf.capacity(), K::ZERO);
248
249        // Did we learn how much capacity we need in the last iteration? We use this only to panic
250        // on erroneous implementations of get_data and avoid endless looping until we run out of
251        // memory.
252        let mut remaining_length_known = false;
253        // We repeatedly fetch data and add it to the buffer. The buffer length is therefore the
254        // accumulated value size. The target always points to the last window in buf which is going
255        // to contain the **next** part of the data, whereas buf contains the entire accumulated
256        // value so far.
257        let mut target =
258            VarCell::<&mut [K::Element], K>::from_buffer(buf.as_mut_slice(), Indicator::NoTotal);
259        self.get_data(col_or_param_num, &mut target)?;
260        while !target.is_complete() {
261            // Amount of payload bytes (excluding terminating zeros) fetched with the last call to
262            // get_data.
263            let fetched = target
264                .len_in_bytes()
265                .expect("ODBC driver must always report how many bytes were fetched.");
266            match target.indicator() {
267                // If Null the value would be complete
268                Indicator::Null => unreachable!(),
269                // We do not know how large the value is. Let's fetch the data with repeated calls
270                // to get_data.
271                Indicator::NoTotal => {
272                    let old_len = buf.len();
273                    // Use an exponential strategy for increasing buffer size.
274                    buf.resize(old_len * 2, K::ZERO);
275                    let buf_extend = &mut buf[(old_len - K::TERMINATING_ZEROES)..];
276                    target = VarCell::<&mut [K::Element], K>::from_buffer(
277                        buf_extend,
278                        Indicator::NoTotal,
279                    );
280                }
281                // We did not get all of the value in one go, but the data source has been friendly
282                // enough to tell us how much is missing.
283                Indicator::Length(len) => {
284                    if remaining_length_known {
285                        panic!(
286                            "SQLGetData has been unable to fetch all data, even though the \
287                            capacity of the target buffer has been adapted to hold the entire \
288                            payload based on the indicator of the last part. You may consider \
289                            filing a bug with the ODBC driver you are using."
290                        )
291                    }
292                    remaining_length_known = true;
293                    // Amount of bytes missing from the value using get_data, excluding terminating
294                    // zero.
295                    let still_missing_in_bytes = len - fetched;
296                    let still_missing = still_missing_in_bytes / size_of::<K::Element>();
297                    let old_len = buf.len();
298                    buf.resize(old_len + still_missing, K::ZERO);
299                    let buf_extend = &mut buf[(old_len - K::TERMINATING_ZEROES)..];
300                    target = VarCell::<&mut [K::Element], K>::from_buffer(
301                        buf_extend,
302                        Indicator::NoTotal,
303                    );
304                }
305            }
306            // Fetch binary data into buffer.
307            self.get_data(col_or_param_num, &mut target)?;
308        }
309        // We did get the complete value, including the terminating zero. Let's resize the buffer to
310        // match the retrieved value exactly (excluding terminating zero).
311        if let Some(len_in_bytes) = target.indicator().length() {
312            // Since the indicator refers to value length without terminating zero, and capacity is
313            // including the terminating zero this also implicitly drops the terminating zero at the
314            // end of the buffer.
315            let shrink_by_bytes = target.capacity_in_bytes() - len_in_bytes;
316            let shrink_by_chars = shrink_by_bytes / size_of::<K::Element>();
317            buf.resize(buf.len() - shrink_by_chars, K::ZERO);
318            Ok(true)
319        } else {
320            // value is NULL
321            buf.clear();
322            Ok(false)
323        }
324    }
325}
326
327/// Cursors are used to process and iterate the result sets returned by executing queries. Created
328/// by either a prepared query or direct execution. Usually utilized through the [`crate::Cursor`]
329/// trait.
330#[derive(Debug)]
331pub struct CursorImpl<Stmt: AsStatementRef> {
332    /// A statement handle in cursor mode.
333    statement: Stmt,
334}
335
336impl<S> Drop for CursorImpl<S>
337where
338    S: AsStatementRef,
339{
340    fn drop(&mut self) {
341        let mut stmt = self.statement.as_stmt_ref();
342        if let Err(e) = stmt.close_cursor().into_result(&stmt) {
343            // Avoid panicking, if we already have a panic. We don't want to mask the original
344            // error.
345            if !panicking() {
346                panic!("Unexpected error closing cursor: {e:?}")
347            }
348        }
349    }
350}
351
352impl<S> AsStatementRef for CursorImpl<S>
353where
354    S: AsStatementRef,
355{
356    fn as_stmt_ref(&mut self) -> StatementRef<'_> {
357        self.statement.as_stmt_ref()
358    }
359}
360
361impl<S> ResultSetMetadata for CursorImpl<S> where S: AsStatementRef {}
362
363impl<S> Cursor for CursorImpl<S>
364where
365    S: AsStatementRef,
366{
367    fn bind_buffer<B>(mut self, mut row_set_buffer: B) -> Result<BlockCursor<Self, B>, Error>
368    where
369        B: RowSetBuffer,
370    {
371        let stmt = self.statement.as_stmt_ref();
372        unsafe {
373            bind_row_set_buffer_to_statement(stmt, &mut row_set_buffer)?;
374        }
375        Ok(BlockCursor::new(row_set_buffer, self))
376    }
377
378    fn more_results(self) -> Result<Option<Self>, Error>
379    where
380        Self: Sized,
381    {
382        // Consume self without calling drop to avoid calling close_cursor.
383        let mut statement = self.into_stmt();
384        let mut stmt = statement.as_stmt_ref();
385
386        let has_another_result = unsafe { stmt.more_results() }.into_result_bool(&stmt)?;
387        let next = if has_another_result {
388            Some(CursorImpl { statement })
389        } else {
390            None
391        };
392        Ok(next)
393    }
394
395    fn close(self) -> Result<(), Error> {
396        let mut stmt = self.into_stmt();
397        let mut stmt = stmt.as_stmt_ref();
398        stmt.close_cursor().into_result(&stmt)?;
399        Ok(())
400    }
401}
402
403impl<S> CursorImpl<S>
404where
405    S: AsStatementRef,
406{
407    /// Users of this library are encouraged not to call this constructor directly but rather invoke
408    /// [`crate::Connection::execute`] or [`crate::Prepared::execute`] to get a cursor and utilize
409    /// it using the [`crate::Cursor`] trait. This method is public so users with an understanding
410    /// of the raw ODBC C-API have a way to create a cursor, after they left the safety rails of the
411    /// Rust type System, in order to implement a use case not covered yet, by the safe abstractions
412    /// within this crate.
413    ///
414    /// # Safety
415    ///
416    /// `statement` must be in Cursor state, for the invariants of this type to hold.
417    pub unsafe fn new(statement: S) -> Self {
418        Self { statement }
419    }
420
421    /// Deconstructs the `CursorImpl` without calling drop. This is a way to get to the underlying
422    /// statement, while preventing a call to close cursor.
423    pub fn into_stmt(self) -> S {
424        // We want to move `statement` out of self, which would make self partially uninitialized.
425        let dont_drop_me = MaybeUninit::new(self);
426        let self_ptr = dont_drop_me.as_ptr();
427
428        // Safety: We know `dont_drop_me` is valid at this point so reading the ptr is okay
429        unsafe { ptr::read(&(*self_ptr).statement) }
430    }
431
432    pub(crate) fn as_sys(&mut self) -> HStmt {
433        self.as_stmt_ref().as_sys()
434    }
435}
436
437/// A Row set buffer binds row, or column wise buffers to a cursor in order to fill them with row
438/// sets with each call to fetch.
439///
440/// # Safety
441///
442/// Implementers of this trait must ensure that every pointer bound in `bind_to_cursor` stays valid
443/// even if an instance is moved in memory. Bound members should therefore be likely references
444/// themselves. To bind stack allocated buffers it is recommended to implement this trait on the
445/// reference type instead.
446pub unsafe trait RowSetBuffer {
447    /// Declares the bind type of the Row set buffer. `0` Means a columnar binding is used. Any non
448    /// zero number is interpreted as the size of a single row in a row wise binding style.
449    fn bind_type(&self) -> usize;
450
451    /// The batch size for bulk cursors, if retrieving many rows at once.
452    fn row_array_size(&self) -> usize;
453
454    /// Mutable reference to the number of fetched rows.
455    ///
456    /// # Safety
457    ///
458    /// Implementations of this method must take care that the returned referenced stays valid, even
459    /// if `self` should be moved.
460    fn mut_num_fetch_rows(&mut self) -> &mut usize;
461
462    /// Binds the buffer either column or row wise to the cursor.
463    ///
464    /// # Safety
465    ///
466    /// It's the implementation's responsibility to ensure that all bound buffers are valid until
467    /// unbound or the statement handle is deleted.
468    unsafe fn bind_colmuns_to_cursor(&mut self, cursor: StatementRef<'_>) -> Result<(), Error>;
469
470    /// Find an indicator larger than the maximum element size of the buffer.
471    fn find_truncation(&self) -> Option<TruncationInfo>;
472}
473
474/// Returned by [`RowSetBuffer::find_truncation`]. Contains information about the truncation found.
475#[derive(Clone, Copy, PartialEq, Eq, Debug)]
476pub struct TruncationInfo {
477    /// Length of the untruncated value if known
478    pub indicator: Option<usize>,
479    /// Zero based buffer index of the column in which the truncation occurred.
480    pub buffer_index: usize,
481}
482
483unsafe impl<T: RowSetBuffer> RowSetBuffer for &mut T {
484    fn bind_type(&self) -> usize {
485        (**self).bind_type()
486    }
487
488    fn row_array_size(&self) -> usize {
489        (**self).row_array_size()
490    }
491
492    fn mut_num_fetch_rows(&mut self) -> &mut usize {
493        (*self).mut_num_fetch_rows()
494    }
495
496    unsafe fn bind_colmuns_to_cursor(&mut self, cursor: StatementRef<'_>) -> Result<(), Error> {
497        unsafe { (*self).bind_colmuns_to_cursor(cursor) }
498    }
499
500    fn find_truncation(&self) -> Option<TruncationInfo> {
501        (**self).find_truncation()
502    }
503}
504
505/// Binds a row set buffer to a statment. Implementation is shared between synchronous and
506/// asynchronous cursors.
507unsafe fn bind_row_set_buffer_to_statement(
508    mut stmt: StatementRef<'_>,
509    row_set_buffer: &mut impl RowSetBuffer,
510) -> Result<(), Error> {
511    unsafe {
512        stmt.set_row_bind_type(row_set_buffer.bind_type())
513            .into_result(&stmt)?;
514        let size = row_set_buffer.row_array_size();
515        let sql_result = stmt.set_row_array_size(size);
516
517        // Search for "option value changed". A QODBC driver reported "option value changed", yet
518        // set the value to `723477590136`. We want to panic if something like this happens.
519        //
520        // See: <https://github.com/pacman82/odbc-api/discussions/742#discussioncomment-13887516>
521        let mut diagnostic_stream = DiagnosticStream::new(&stmt);
522        // We just rememeber that we have seen "option value changed", before asking for the array
523        // size, in order to not mess with other diagnostic records.
524        let mut option_value_changed = false;
525        while let Some(record) = diagnostic_stream.next() {
526            warn!("{record}");
527            if record.state == State::OPTION_VALUE_CHANGED {
528                option_value_changed = true;
529            }
530        }
531        if option_value_changed {
532            // Now rejecting a too large buffer size is save, but not if the value is something
533            // even larger after. Zero is also suspicious.
534            let actual_size = stmt.row_array_size().into_result(&stmt)?;
535            warn!(
536                "Row array size set by the driver to: {actual_size}. Desired size had been: {size}"
537            );
538            if actual_size > size || actual_size == 0 {
539                panic!(
540                    "Your ODBC buffer changed the array size for bulk fetchin in an unsound way. \
541                    To prevent undefined behavior the application must panic. You can try \
542                    different batch sizes for bulk fetching, or report a bug with your ODBC driver \
543                    provider. This behavior has been observed with QODBC drivers. If you are using \
544                    one try fetching row by row rather than the faster bulk fetch."
545                )
546            }
547        }
548
549        sql_result
550            // We already logged diagnostic records then we were looking for Option value changed
551            .into_result_without_logging(&stmt)
552            // SAP anywhere has been seen to return with an "invalid attribute" error instead of
553            // a success with "option value changed" info. Let us map invalid attributes during
554            // setting row set array size to something more precise.
555            .provide_context_for_diagnostic(|record, function| {
556                if record.state == State::INVALID_ATTRIBUTE_VALUE {
557                    Error::InvalidRowArraySize { record, size }
558                } else {
559                    Error::Diagnostics { record, function }
560                }
561            })?;
562        stmt.set_num_rows_fetched(row_set_buffer.mut_num_fetch_rows())
563            .into_result(&stmt)?;
564        row_set_buffer.bind_colmuns_to_cursor(stmt)?;
565        Ok(())
566    }
567}
568
569/// Error handling for bulk fetching is shared between synchronous and asynchronous usecase.
570fn error_handling_for_fetch(
571    result: SqlResult<()>,
572    mut stmt: StatementRef,
573    buffer: &impl RowSetBuffer,
574    error_for_truncation: bool,
575) -> Result<bool, Error> {
576    // Only check for truncation if a) the user indicated that he wants to error instead of just
577    // ignoring it and if there is at least one diagnostic record. ODBC standard requires a
578    // diagnostic record to be there in case of truncation. Sadly we can not rely on this particular
579    // record to be there, as the driver could generate a large amount of diagnostic records,
580    // while we are limited in the amount we can check. The second check serves as an optimization
581    // for the happy path.
582    if error_for_truncation && result == SqlResult::SuccessWithInfo(()) {
583        if let Some(TruncationInfo {
584            indicator,
585            buffer_index,
586        }) = buffer.find_truncation()
587        {
588            return Err(Error::TooLargeValueForBuffer {
589                indicator,
590                buffer_index,
591            });
592        }
593    }
594
595    let has_row = result
596        .on_success(|| true)
597        .on_no_data(|| false)
598        .into_result(&stmt.as_stmt_ref())
599        // Oracle's ODBC driver does not support 64Bit integers. Furthermore, it does not
600        // tell it to the user when binding parameters, but rather now then we fetch
601        // results. The error code returned is `HY004` rather than `HY003` which should
602        // be used to indicate invalid buffer types.
603        .provide_context_for_diagnostic(|record, function| {
604            if record.state == State::INVALID_SQL_DATA_TYPE {
605                Error::OracleOdbcDriverDoesNotSupport64Bit(record)
606            } else {
607                Error::Diagnostics { record, function }
608            }
609        })?;
610    Ok(has_row)
611}
612
613/// Unbinds buffer and num_rows_fetched from the cursor. This implementation is shared between
614/// unbind and the drop handler, and the synchronous and asynchronous variant.
615fn unbind_buffer_from_cursor(cursor: &mut impl AsStatementRef) -> Result<(), Error> {
616    // Now that we have cursor out of block cursor, we need to unbind the buffer.
617    let mut stmt = cursor.as_stmt_ref();
618    stmt.unbind_cols().into_result(&stmt)?;
619    stmt.unset_num_rows_fetched().into_result(&stmt)?;
620    Ok(())
621}