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)?;
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)?;
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    ///     conn.execute(sql, parameters)?;
278    ///     Ok(())
279    /// }
280    /// ```
281    pub fn with_upper_bound(buf_read: R, upper_bound: usize) -> Self {
282        Self {
283            exact: false,
284            consume: 0,
285            size: upper_bound,
286            buf_read,
287        }
288    }
289
290    /// Construct a blob read from any [`std::io::BufRead`]. The `upper bound` is used in the type
291    /// description then binding the blob as a parameter and is also passed to indicate the size
292    /// of the actual value to the ODBC driver.
293    ///
294    /// # Safety
295    ///
296    /// The ODBC driver may use the exact size hint to allocate buffers internally. Too short may
297    /// lead to invalid writes and too long may lead to invalid reads, so to be save the hint must
298    /// be exact.
299    pub unsafe fn with_exact_size(buf_read: R, exact_size: usize) -> Self {
300        Self {
301            exact: true,
302            consume: 0,
303            size: exact_size,
304            buf_read,
305        }
306    }
307}
308
309impl BlobRead<BufReader<File>> {
310    /// Construct a blob from a Path. The metadata of the file is used to give the ODBC driver a
311    /// size hint.
312    ///
313    /// # Example
314    ///
315    /// [`BlobRead::from_path`] is the most convenient way to turn a file path into a [`Blob`]
316    /// parameter. The following example also demonstrates that the streamed blob parameter can be
317    /// combined with reqular input parmeters like `id`.
318    ///
319    /// ```
320    /// use std::{error::Error, path::Path};
321    /// use odbc_api::{Connection, parameter::{Blob, BlobRead}, IntoParameter};
322    ///
323    /// fn insert_image_to_db(
324    ///     conn: &Connection<'_>,
325    ///     id: &str,
326    ///     image_path: &Path) -> Result<(), Box<dyn Error>>
327    /// {
328    ///     let mut blob = BlobRead::from_path(&image_path)?;
329    ///
330    ///     let sql = "INSERT INTO Images (id, image_data) VALUES (?, ?)";
331    ///     let parameters = (&id.into_parameter(), &mut blob.as_blob_param());
332    ///     conn.execute(sql, parameters)?;
333    ///     Ok(())
334    /// }
335    /// ```
336    pub fn from_path(path: &Path) -> io::Result<Self> {
337        let file = File::open(path)?;
338        let size = file.metadata()?.len().try_into().unwrap();
339        let buf_read = BufReader::new(file);
340        Ok(Self {
341            consume: 0,
342            exact: true,
343            size,
344            buf_read,
345        })
346    }
347}
348
349impl<R> HasDataType for BlobRead<R>
350where
351    R: BufRead,
352{
353    fn data_type(&self) -> DataType {
354        DataType::LongVarbinary {
355            length: NonZeroUsize::new(self.size),
356        }
357    }
358}
359
360unsafe impl<R> Blob for BlobRead<R>
361where
362    R: BufRead,
363{
364    fn c_data_type(&self) -> CDataType {
365        CDataType::Binary
366    }
367
368    fn size_hint(&self) -> Option<usize> {
369        if self.exact {
370            Some(self.size)
371        } else {
372            None
373        }
374    }
375
376    fn next_batch(&mut self) -> io::Result<Option<&[u8]>> {
377        if self.consume != 0 {
378            self.buf_read.consume(self.consume);
379        }
380        let batch = self.buf_read.fill_buf()?;
381        self.consume = batch.len();
382        if batch.is_empty() {
383            Ok(None)
384        } else {
385            Ok(Some(batch))
386        }
387    }
388}