odbc_api/buffers/
row_vec.rs

1use std::{mem, ops::Deref};
2
3use crate::{
4    Error, RowSetBuffer, TruncationInfo,
5    buffers::Indicator,
6    handles::{CDataMut, Statement, StatementRef},
7};
8
9/// [`FetchRow`]s can be bound to a [`crate::Cursor`] to enable row-wise (bulk) fetching of data as
10/// opposed to column-wise fetching. Since all rows are bound to a C-API in a contiguous block of
11/// memory the row itself should be representable as such. Concretely that means that types like
12/// `String` can not be supported directly by [`FetchRow`]s for efficient bulk fetching, due to the
13/// fact it points to data on the heap.
14///
15/// This trait is implemented by tuples of [`FetchRowMember`]. In addition it can also be derived
16/// for structs where all members implement [`FetchRowMember`] using the `Fetch` derive macro if the
17/// optional derive feature is activated.
18///
19/// # Safety
20///
21/// * All the bound buffers need to be valid for the lifetime of the row.
22/// * The offsets into the memory for the field representing a column, must be constant for all
23///   types of the row. This is required to make the row suitable for fetching in bulk, as only the
24///   first row is bound explicitly, and the bindings for all consecutive rows is calculated by
25///   taking the size of the row in bytes multiplied by buffer index.
26pub unsafe trait FetchRow: Copy {
27    /// Binds the columns of the result set to members of the row.
28    ///
29    /// # Safety
30    ///
31    /// Caller must ensure self is alive and not moved in memory for the duration of the binding.
32    unsafe fn bind_columns_to_cursor(&mut self, cursor: StatementRef<'_>) -> Result<(), Error>;
33
34    /// If it exists, this returns the "buffer index" of a member, which has been truncated.
35    fn find_truncation(&self) -> Option<TruncationInfo>;
36}
37
38/// A row wise buffer intended to be bound with [crate::Cursor::bind_buffer] in order to obtain
39/// results from a cursor.
40///
41/// # Example
42///
43/// ```
44/// use odbc_api::{Connection, Error, Cursor, parameter::VarCharArray, buffers::RowVec};
45///
46/// fn send_greetings(conn: &mut Connection) -> Result<(), Error> {
47///     let max_rows_in_batch = 250;
48///     type Row = (VarCharArray<255>, VarCharArray<255>);
49///     let buffer = RowVec::<Row>::new(max_rows_in_batch);
50///     let query = "SELECT first_name, last_name FROM Persons";
51///     let parameters = ();
52///     let timeout_sec = None;
53///     let mut cursor = conn.execute(query, parameters, timeout_sec)?
54///         .expect("SELECT must yield a result set");
55///     let mut block_cursor = cursor.bind_buffer(buffer)?;
56///
57///     while let Some(batch) = block_cursor.fetch()? {
58///         for (first, last) in batch.iter() {
59///             let first = first.as_str()
60///                 .expect("First name must be UTF-8")
61///                 .expect("First Name must not be NULL");
62///             let last = last.as_str()
63///                 .expect("Last name must be UTF-8")
64///                 .expect("Last Name must not be NULL");
65///             println!("Hello {first} {last}!")
66///         }
67///     }
68///     Ok(())
69/// }
70/// ```
71///
72/// To fetch rows with this buffer type `R` must implement [`FetchRow`]. This is currently
73/// implemented for tuple types. Each element of these tuples must implement [`FetchRowMember`]. You
74/// can also derive [`FetchRow`] for structs with the `Fetch` derive macro if the feature is
75/// enabled.
76///
77/// Currently supported are: `f64`, `f32`, [`odbc_sys::Date`], [`odbc_sys::Timestamp`],
78/// [`odbc_sys::Time`], `i16`, `u16`, `i32`, `u32`, `i8`, `u8`, `Bit`, `i64`, `u64` and
79/// [`crate::parameter::VarCharArray`]. Fixed-size types can be wrapped in [`crate::Nullable`].
80pub struct RowVec<R> {
81    /// A mutable pointer to num_rows_fetched is passed to the C-API. It is used to write back the
82    /// number of fetched rows. `num_rows` is heap allocated, so the pointer is not invalidated,
83    /// even if the `ColumnarBuffer` instance is moved in memory.
84    num_rows: Box<usize>,
85    /// Here we actually store the rows. The length of `rows` is the capacity of the
86    /// `RowWiseBuffer` instance. It must not be 0.
87    rows: Vec<R>,
88}
89
90impl<R> RowVec<R> {
91    /// Allocates a new Row wise buffer, which can at most `capacity` number of rows in a single
92    /// call to [`crate::BlockCursor::fetch`].
93    ///
94    /// Panics if `capacity` is `0``.
95    pub fn new(capacity: usize) -> Self
96    where
97        R: Default + Clone + Copy,
98    {
99        if capacity == 0 {
100            panic!("RowWiseBuffer must have a capacity of at least `1`.")
101        }
102        RowVec {
103            num_rows: Box::new(0),
104            rows: vec![R::default(); capacity],
105        }
106    }
107
108    /// Number of valid rows in the buffer.
109    pub fn num_rows(&self) -> usize {
110        *self.num_rows
111    }
112}
113
114impl<R> Deref for RowVec<R> {
115    type Target = [R];
116
117    fn deref(&self) -> &[R] {
118        &self.rows[..*self.num_rows]
119    }
120}
121
122unsafe impl<R> RowSetBuffer for RowVec<R>
123where
124    R: FetchRow,
125{
126    fn bind_type(&self) -> usize {
127        mem::size_of::<R>()
128    }
129
130    fn row_array_size(&self) -> usize {
131        self.rows.len()
132    }
133
134    fn mut_num_fetch_rows(&mut self) -> &mut usize {
135        &mut self.num_rows
136    }
137
138    unsafe fn bind_colmuns_to_cursor(&mut self, cursor: StatementRef<'_>) -> Result<(), Error> {
139        let first = self
140            .rows
141            .first_mut()
142            .expect("rows in Row Wise buffers must not be empty.");
143        unsafe { first.bind_columns_to_cursor(cursor) }
144    }
145
146    fn find_truncation(&self) -> Option<TruncationInfo> {
147        self.rows
148            .iter()
149            .take(*self.num_rows)
150            .find_map(|row| row.find_truncation())
151    }
152}
153
154/// Can be used as a member of a [`FetchRow`] and bound to a column during row wise fetching.
155///
156/// # Safety
157///
158/// Must only be implemented for types completely representable by consecutive bytes. While members
159/// can bind to Variadic types, the length of the type used for buffering must be known at compile
160/// time. E.g. [`crate::parameter::VarCharArray`] can also bind to Variadic types but is fixed
161/// length at compile time.
162pub unsafe trait FetchRowMember: CDataMut + Copy {
163    /// `Some` if the indicator indicates truncation. Always `None` for fixed sized types.
164    fn find_truncation(&self, buffer_index: usize) -> Option<TruncationInfo> {
165        self.indicator().map(|indicator| TruncationInfo {
166            indicator: indicator.length(),
167            buffer_index,
168        })
169    }
170
171    /// Indicator for variable sized or nullable types, `None` for fixed sized types.
172    fn indicator(&self) -> Option<Indicator>;
173
174    /// Bind row element to column. Only called for the first row in a row wise buffer.
175    ///
176    /// # Safety
177    ///
178    /// It is the callers responsibility to ensure `self` lives for the duration of the binding.
179    unsafe fn bind_to_col(
180        &mut self,
181        col_index: u16,
182        cursor: &mut StatementRef<'_>,
183    ) -> Result<(), Error> {
184        unsafe { cursor.bind_col(col_index, self).into_result(cursor) }
185    }
186}
187
188macro_rules! impl_bind_columns_to_cursor {
189    ($offset:expr, $cursor:ident,) => (
190        Ok(())
191    );
192    ($offset:expr, $cursor:ident, $head:ident, $($tail:ident,)*) => (
193        {
194            $head.bind_to_col($offset, &mut $cursor)?;
195            impl_bind_columns_to_cursor!($offset+1, $cursor, $($tail,)*)
196        }
197    );
198}
199
200macro_rules! impl_find_truncation {
201    ($offset:expr,) => (
202        None
203    );
204    ($offset:expr, $head:ident, $($tail:ident,)*) => (
205        {
206            if let Some(truncation_info) = $head.find_truncation($offset) {
207                return Some(truncation_info);
208            }
209            impl_find_truncation!($offset+1, $($tail,)*)
210        }
211    );
212}
213
214macro_rules! impl_fetch_row_for_tuple{
215    ($($t:ident)*) => (
216        #[allow(unused_mut)]
217        #[allow(unused_variables)]
218        #[allow(non_snake_case)]
219        unsafe impl<$($t:FetchRowMember,)*> FetchRow for ($($t,)*)
220        {
221            unsafe fn bind_columns_to_cursor(&mut self, mut cursor: StatementRef<'_>) -> Result<(), Error> {
222                let &mut ($(ref mut $t,)*) = self;
223                #[allow(unused_unsafe)] // We do not need the unsafe in case it would bind nothing.
224                unsafe {
225                    impl_bind_columns_to_cursor!(1, cursor, $($t,)*)
226                }
227            }
228
229            fn find_truncation(&self) -> Option<TruncationInfo> {
230                let &($(ref $t,)*) = self;
231                impl_find_truncation!(0, $($t,)*)
232            }
233        }
234    );
235}
236
237impl_fetch_row_for_tuple! {}
238impl_fetch_row_for_tuple! { A }
239impl_fetch_row_for_tuple! { A B }
240impl_fetch_row_for_tuple! { A B C }
241impl_fetch_row_for_tuple! { A B C D }
242impl_fetch_row_for_tuple! { A B C D E }
243impl_fetch_row_for_tuple! { A B C D E F }
244impl_fetch_row_for_tuple! { A B C D E F G }
245impl_fetch_row_for_tuple! { A B C D E F G H }
246impl_fetch_row_for_tuple! { A B C D E F G H I }
247impl_fetch_row_for_tuple! { A B C D E F G H I J }
248impl_fetch_row_for_tuple! { A B C D E F G H I J K }
249impl_fetch_row_for_tuple! { A B C D E F G H I J K L }
250impl_fetch_row_for_tuple! { A B C D E F G H I J K L M }
251impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N }
252impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O }
253impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P }
254impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q }
255impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q R }
256impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q R S }
257impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q R S T }
258impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q R S T U }
259impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q R S T U V }
260impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q R S T U V W }
261impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q R S T U V W X }
262impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q R S T U V W X Y }
263impl_fetch_row_for_tuple! { A B C D E F G H I J K L M N O P Q R S T U V W X Y Z}
264
265#[cfg(test)]
266mod tests {
267
268    use super::RowVec;
269
270    #[derive(Default, Clone, Copy)]
271    struct DummyRow;
272
273    #[test]
274    #[should_panic]
275    fn construction_should_panic_on_capacity_zero() {
276        RowVec::<DummyRow>::new(0);
277    }
278
279    #[test]
280    #[should_panic]
281    fn index_should_panic_on_out_of_bound_access() {
282        let buffer = RowVec::<DummyRow>::new(1);
283        // Access within the capacity of rows, but the buffer is still empty.
284        let _ = buffer[0];
285    }
286}