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}