odbc_api/
cursor.rs

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