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