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