odbc_api/parameter/
blob.rs

1use odbc_sys::{len_data_at_exec, CDataType, DATA_AT_EXEC};
2
3use crate::{
4    handles::{DelayedInput, HasDataType, Statement},
5    DataType, Error, ParameterCollection, ParameterTupleElement,
6};
7use std::{
8    ffi::c_void,
9    fs::File,
10    io::{self, BufRead, BufReader},
11    num::NonZeroUsize,
12    path::Path,
13};
14
15/// A `Blob` can stream its contents to the database batch by batch and may therefore be used to
16/// transfer large amounts of data, exceeding the drivers capabilities for normal input parameters.
17///
18/// # Safety
19///
20/// If a hint is implemented for `blob_size` it must be accurate before the first call to
21/// `next_batch`.
22pub unsafe trait Blob: HasDataType {
23    /// CData type of the binary data returned in the batches. Likely to be either
24    /// [`crate::sys::CDataType::Binary`], [`crate::sys::CDataType::Char`] or
25    /// [`crate::sys::CDataType::WChar`].
26    fn c_data_type(&self) -> CDataType;
27
28    /// Hint passed on to the driver regarding the combined size of all the batches. This hint is
29    /// passed then the parameter is bound to the statement, so its meaning is only defined before
30    /// the first call to `next_batch`. If `None` no hint about the total length of the batches is
31    /// passed to the driver and the indicator will be set to [`crate::sys::DATA_AT_EXEC`].
32    fn size_hint(&self) -> Option<usize>;
33
34    /// Retrieve the next batch of data from the source. Batches may not be empty. `None` indicates
35    /// the last batch has been reached.
36    fn next_batch(&mut self) -> io::Result<Option<&[u8]>>;
37
38    /// Convinience function. Same as calling [`self::BlobParam::new`].
39    fn as_blob_param(&mut self) -> BlobParam
40    where
41        Self: Sized,
42    {
43        BlobParam::new(self)
44    }
45}
46
47/// Parameter type which can be used to bind a [`self::Blob`] as parameter to a statement in order
48/// for its contents to be streamed to the database at statement execution time.
49pub struct BlobParam<'a> {
50    /// Should be [`crate::sys::DATA_AT_EXEC`] if no size hint is given, or the result of
51    /// [`crate::sys::len_data_at_exec`].
52    indicator: isize,
53    /// Trait object to be bound as a delayed parameter.
54    blob: &'a mut dyn Blob,
55}
56
57impl<'a> BlobParam<'a> {
58    pub fn new(blob: &'a mut impl Blob) -> Self {
59        let indicator = if let Some(size) = blob.size_hint() {
60            len_data_at_exec(size.try_into().unwrap())
61        } else {
62            DATA_AT_EXEC
63        };
64        Self { indicator, blob }
65    }
66}
67
68unsafe impl DelayedInput for BlobParam<'_> {
69    fn cdata_type(&self) -> CDataType {
70        self.blob.c_data_type()
71    }
72
73    fn indicator_ptr(&self) -> *const isize {
74        &self.indicator as *const isize
75    }
76
77    fn stream_ptr(&mut self) -> *mut c_void {
78        // Types must have the same size for the transmute to work in the reverse cast.
79        debug_assert_eq!(
80            std::mem::size_of::<*mut &mut dyn Blob>(),
81            std::mem::size_of::<*mut c_void>()
82        );
83        &mut self.blob as *mut &mut dyn Blob as *mut c_void
84    }
85}
86
87impl HasDataType for BlobParam<'_> {
88    fn data_type(&self) -> DataType {
89        self.blob.data_type()
90    }
91}
92
93unsafe impl ParameterCollection for BlobParam<'_> {
94    fn parameter_set_size(&self) -> usize {
95        1
96    }
97
98    unsafe fn bind_parameters_to(&mut self, stmt: &mut impl Statement) -> Result<(), Error> {
99        stmt.bind_delayed_input_parameter(1, self).into_result(stmt)
100    }
101}
102
103unsafe impl ParameterTupleElement for &mut BlobParam<'_> {
104    unsafe fn bind_to(
105        &mut self,
106        parameter_number: u16,
107        stmt: &mut impl Statement,
108    ) -> Result<(), Error> {
109        stmt.bind_delayed_input_parameter(parameter_number, *self)
110            .into_result(stmt)
111    }
112}
113
114/// Wraps borrowed bytes with a batch_size and implements [`self::Blob`]. Use this type to send long
115/// array of bytes to the database.
116pub struct BlobSlice<'a> {
117    /// If `true` the blob is going to be bound as [`DataType::LongVarbinary`] and the bytes are
118    /// interpreted as [`CDataType::Binary`]. If false the blob is going to be bound as
119    /// [`DataType::LongVarchar`] and the bytes are interpreted as [`CDataType::Char`].
120    pub is_binary: bool,
121    /// Maximum number of bytes transferred to the database in one go. May be largere than the
122    /// remaining blob size.
123    pub batch_size: usize,
124    /// Remaining bytes to transfer to the database.
125    pub blob: &'a [u8],
126}
127
128impl<'a> BlobSlice<'a> {
129    /// Construct a Blob from a byte slice. The blob is going to be bound as a `LongVarbinary` and
130    /// will be transmitted in one batch.
131    ///
132    /// # Example
133    ///
134    /// ```
135    /// use odbc_api::{Connection, parameter::{Blob, BlobSlice}, IntoParameter, Error};
136    ///
137    /// fn insert_image(
138    ///     conn: &Connection<'_>,
139    ///     id: &str,
140    ///     image_data: &[u8]
141    /// ) -> Result<(), Error>
142    /// {
143    ///     let mut blob = BlobSlice::from_byte_slice(image_data);
144    ///
145    ///     let insert = "INSERT INTO Images (id, image_data) VALUES (?,?)";
146    ///     let parameters = (&id.into_parameter(), &mut blob.as_blob_param());
147    ///     conn.execute(&insert, parameters, None)?;
148    ///     Ok(())
149    /// }
150    /// ```
151    pub fn from_byte_slice(blob: &'a [u8]) -> Self {
152        Self {
153            is_binary: true,
154            batch_size: blob.len(),
155            blob,
156        }
157    }
158
159    /// Construct a Blob from a text slice. The blob is going to be bound as a `LongVarchar` and
160    /// will be transmitted in one batch.
161    ///
162    /// # Example
163    ///
164    /// This example insert `title` as a normal input parameter but streams the potentially much
165    /// longer `String` in `text` to the database as a large text blob. This allows to circumvent
166    /// the size restrictions for `String` arguments of many drivers (usually around 4 or 8 KiB).
167    ///
168    /// ```
169    /// use odbc_api::{Connection, parameter::{Blob, BlobSlice}, IntoParameter, Error};
170    ///
171    /// fn insert_book(
172    ///     conn: &Connection<'_>,
173    ///     title: &str,
174    ///     text: &str
175    /// ) -> Result<(), Error>
176    /// {
177    ///     let mut blob = BlobSlice::from_text(&text);
178    ///
179    ///     let insert = "INSERT INTO Books (title, text) VALUES (?,?)";
180    ///     let parameters = (&title.into_parameter(), &mut blob.as_blob_param());
181    ///     conn.execute(&insert, parameters, None)?;
182    ///     Ok(())
183    /// }
184    /// ```
185    pub fn from_text(text: &'a str) -> Self {
186        Self {
187            is_binary: false,
188            batch_size: text.len(),
189            blob: text.as_bytes(),
190        }
191    }
192}
193
194impl HasDataType for BlobSlice<'_> {
195    fn data_type(&self) -> DataType {
196        if self.is_binary {
197            DataType::LongVarbinary {
198                length: NonZeroUsize::new(self.blob.len()),
199            }
200        } else {
201            DataType::LongVarchar {
202                length: NonZeroUsize::new(self.blob.len()),
203            }
204        }
205    }
206}
207
208unsafe impl Blob for BlobSlice<'_> {
209    fn c_data_type(&self) -> CDataType {
210        if self.is_binary {
211            CDataType::Binary
212        } else {
213            CDataType::Char
214        }
215    }
216
217    fn size_hint(&self) -> Option<usize> {
218        Some(self.blob.len())
219    }
220
221    fn next_batch(&mut self) -> io::Result<Option<&[u8]>> {
222        if self.blob.is_empty() {
223            return Ok(None);
224        }
225
226        if self.blob.len() >= self.batch_size {
227            let (head, tail) = self.blob.split_at(self.batch_size);
228            self.blob = tail;
229            Ok(Some(head))
230        } else {
231            let last_batch = self.blob;
232            self.blob = &[];
233            Ok(Some(last_batch))
234        }
235    }
236}
237
238/// Wraps an [`std::io::BufRead`] and implements [`self::Blob`]. Use this to stream contents from an
239/// [`std::io::BufRead`] to the database. The blob implementation is going to directly utilize the
240/// Buffer of the [`std::io::BufRead`] implementation, so the batch size is likely equal to that
241/// capacity.
242pub struct BlobRead<R> {
243    /// `true` if `size` is to interpreted as the exact ammount of bytes contained in the reader, at
244    /// the time of binding it as a parameter. `false` if `size` is to be interpreted as an upper
245    /// bound.
246    exact: bool,
247    size: usize,
248    consume: usize,
249    buf_read: R,
250}
251
252impl<R> BlobRead<R> {
253    /// Construct a blob read from any [`std::io::BufRead`]. The `upper bound` is used in the type
254    /// description then binding the blob as a parameter.
255    ///
256    /// # Examples
257    ///
258    /// This is more flexible than [`Self::from_path`]. Note however that files provide metadata
259    /// about the length of the data, which `io::BufRead` does not. This is not an issue for most
260    /// drivers, but some can perform optimization if they know the size in advance. In the tests
261    /// SQLite has shown a bug to only insert empty data if no size hint has been provided.
262    ///
263    /// ```
264    /// use std::io::BufRead;
265    /// use odbc_api::{Connection, parameter::{Blob, BlobRead}, IntoParameter, Error};
266    ///
267    /// fn insert_image_to_db(
268    ///     conn: &Connection<'_>,
269    ///     id: &str,
270    ///     image_data: impl BufRead) -> Result<(), Error>
271    /// {
272    ///     const MAX_IMAGE_SIZE: usize = 4 * 1024 * 1024;
273    ///     let mut blob = BlobRead::with_upper_bound(image_data, MAX_IMAGE_SIZE);
274    ///
275    ///     let sql = "INSERT INTO Images (id, image_data) VALUES (?, ?)";
276    ///     let parameters = (&id.into_parameter(), &mut blob.as_blob_param());
277    ///     let timeout_sec = None;
278    ///     conn.execute(sql, parameters, timeout_sec)?;
279    ///     Ok(())
280    /// }
281    /// ```
282    pub fn with_upper_bound(buf_read: R, upper_bound: usize) -> Self {
283        Self {
284            exact: false,
285            consume: 0,
286            size: upper_bound,
287            buf_read,
288        }
289    }
290
291    /// Construct a blob read from any [`std::io::BufRead`]. The `upper bound` is used in the type
292    /// description then binding the blob as a parameter and is also passed to indicate the size
293    /// of the actual value to the ODBC driver.
294    ///
295    /// # Safety
296    ///
297    /// The ODBC driver may use the exact size hint to allocate buffers internally. Too short may
298    /// lead to invalid writes and too long may lead to invalid reads, so to be save the hint must
299    /// be exact.
300    pub unsafe fn with_exact_size(buf_read: R, exact_size: usize) -> Self {
301        Self {
302            exact: true,
303            consume: 0,
304            size: exact_size,
305            buf_read,
306        }
307    }
308}
309
310impl BlobRead<BufReader<File>> {
311    /// Construct a blob from a Path. The metadata of the file is used to give the ODBC driver a
312    /// size hint.
313    ///
314    /// # Example
315    ///
316    /// [`BlobRead::from_path`] is the most convenient way to turn a file path into a [`Blob`]
317    /// parameter. The following example also demonstrates that the streamed blob parameter can be
318    /// combined with reqular input parmeters like `id`.
319    ///
320    /// ```
321    /// use std::{error::Error, path::Path};
322    /// use odbc_api::{Connection, parameter::{Blob, BlobRead}, IntoParameter};
323    ///
324    /// fn insert_image_to_db(
325    ///     conn: &Connection<'_>,
326    ///     id: &str,
327    ///     image_path: &Path) -> Result<(), Box<dyn Error>>
328    /// {
329    ///     let mut blob = BlobRead::from_path(&image_path)?;
330    ///
331    ///     let sql = "INSERT INTO Images (id, image_data) VALUES (?, ?)";
332    ///     let parameters = (&id.into_parameter(), &mut blob.as_blob_param());
333    ///     let timeout_sec = None;
334    ///     conn.execute(sql, parameters, timeout_sec)?;
335    ///     Ok(())
336    /// }
337    /// ```
338    pub fn from_path(path: &Path) -> io::Result<Self> {
339        let file = File::open(path)?;
340        let size = file.metadata()?.len().try_into().unwrap();
341        let buf_read = BufReader::new(file);
342        Ok(Self {
343            consume: 0,
344            exact: true,
345            size,
346            buf_read,
347        })
348    }
349}
350
351impl<R> HasDataType for BlobRead<R>
352where
353    R: BufRead,
354{
355    fn data_type(&self) -> DataType {
356        DataType::LongVarbinary {
357            length: NonZeroUsize::new(self.size),
358        }
359    }
360}
361
362unsafe impl<R> Blob for BlobRead<R>
363where
364    R: BufRead,
365{
366    fn c_data_type(&self) -> CDataType {
367        CDataType::Binary
368    }
369
370    fn size_hint(&self) -> Option<usize> {
371        if self.exact {
372            Some(self.size)
373        } else {
374            None
375        }
376    }
377
378    fn next_batch(&mut self) -> io::Result<Option<&[u8]>> {
379        if self.consume != 0 {
380            self.buf_read.consume(self.consume);
381        }
382        let batch = self.buf_read.fill_buf()?;
383        self.consume = batch.len();
384        if batch.is_empty() {
385            Ok(None)
386        } else {
387            Ok(Some(batch))
388        }
389    }
390}