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