odbc_api/parameter/blob.rs
1use odbc_sys::{CDataType, DATA_AT_EXEC, len_data_at_exec};
2
3use crate::{
4 DataType, Error, ParameterCollection, ParameterTupleElement,
5 handles::{DelayedInput, HasDataType, Statement},
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 unsafe { 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 unsafe { stmt.bind_delayed_input_parameter(parameter_number, *self) }.into_result(stmt)
110 }
111}
112
113/// Wraps borrowed bytes with a batch_size and implements [`self::Blob`]. Use this type to send long
114/// array of bytes to the database.
115pub struct BlobSlice<'a> {
116 /// If `true` the blob is going to be bound as [`DataType::LongVarbinary`] and the bytes are
117 /// interpreted as [`CDataType::Binary`]. If false the blob is going to be bound as
118 /// [`DataType::LongVarchar`] and the bytes are interpreted as [`CDataType::Char`].
119 pub is_binary: bool,
120 /// Maximum number of bytes transferred to the database in one go. May be largere than the
121 /// remaining blob size.
122 pub batch_size: usize,
123 /// Remaining bytes to transfer to the database.
124 pub blob: &'a [u8],
125}
126
127impl<'a> BlobSlice<'a> {
128 /// Construct a Blob from a byte slice. The blob is going to be bound as a `LongVarbinary` and
129 /// will be transmitted in one batch.
130 ///
131 /// # Example
132 ///
133 /// ```
134 /// use odbc_api::{Connection, parameter::{Blob, BlobSlice}, IntoParameter, Error};
135 ///
136 /// fn insert_image(
137 /// conn: &Connection<'_>,
138 /// id: &str,
139 /// image_data: &[u8]
140 /// ) -> Result<(), Error>
141 /// {
142 /// let mut blob = BlobSlice::from_byte_slice(image_data);
143 ///
144 /// let insert = "INSERT INTO Images (id, image_data) VALUES (?,?)";
145 /// let parameters = (&id.into_parameter(), &mut blob.as_blob_param());
146 /// conn.execute(&insert, parameters, None)?;
147 /// Ok(())
148 /// }
149 /// ```
150 pub fn from_byte_slice(blob: &'a [u8]) -> Self {
151 Self {
152 is_binary: true,
153 batch_size: blob.len(),
154 blob,
155 }
156 }
157
158 /// Construct a Blob from a text slice. The blob is going to be bound as a `LongVarchar` and
159 /// will be transmitted in one batch.
160 ///
161 /// # Example
162 ///
163 /// This example insert `title` as a normal input parameter but streams the potentially much
164 /// longer `String` in `text` to the database as a large text blob. This allows to circumvent
165 /// the size restrictions for `String` arguments of many drivers (usually around 4 or 8 KiB).
166 ///
167 /// ```
168 /// use odbc_api::{Connection, parameter::{Blob, BlobSlice}, IntoParameter, Error};
169 ///
170 /// fn insert_book(
171 /// conn: &Connection<'_>,
172 /// title: &str,
173 /// text: &str
174 /// ) -> Result<(), Error>
175 /// {
176 /// let mut blob = BlobSlice::from_text(&text);
177 ///
178 /// let insert = "INSERT INTO Books (title, text) VALUES (?,?)";
179 /// let parameters = (&title.into_parameter(), &mut blob.as_blob_param());
180 /// conn.execute(&insert, parameters, None)?;
181 /// Ok(())
182 /// }
183 /// ```
184 pub fn from_text(text: &'a str) -> Self {
185 Self {
186 is_binary: false,
187 batch_size: text.len(),
188 blob: text.as_bytes(),
189 }
190 }
191}
192
193impl HasDataType for BlobSlice<'_> {
194 fn data_type(&self) -> DataType {
195 if self.is_binary {
196 DataType::LongVarbinary {
197 length: NonZeroUsize::new(self.blob.len()),
198 }
199 } else {
200 DataType::LongVarchar {
201 length: NonZeroUsize::new(self.blob.len()),
202 }
203 }
204 }
205}
206
207unsafe impl Blob for BlobSlice<'_> {
208 fn c_data_type(&self) -> CDataType {
209 if self.is_binary {
210 CDataType::Binary
211 } else {
212 CDataType::Char
213 }
214 }
215
216 fn size_hint(&self) -> Option<usize> {
217 Some(self.blob.len())
218 }
219
220 fn next_batch(&mut self) -> io::Result<Option<&[u8]>> {
221 if self.blob.is_empty() {
222 return Ok(None);
223 }
224
225 if self.blob.len() >= self.batch_size {
226 let (head, tail) = self.blob.split_at(self.batch_size);
227 self.blob = tail;
228 Ok(Some(head))
229 } else {
230 let last_batch = self.blob;
231 self.blob = &[];
232 Ok(Some(last_batch))
233 }
234 }
235}
236
237/// Wraps an [`std::io::BufRead`] and implements [`self::Blob`]. Use this to stream contents from an
238/// [`std::io::BufRead`] to the database. The blob implementation is going to directly utilize the
239/// Buffer of the [`std::io::BufRead`] implementation, so the batch size is likely equal to that
240/// capacity.
241pub struct BlobRead<R> {
242 /// `true` if `size` is to interpreted as the exact ammount of bytes contained in the reader, at
243 /// the time of binding it as a parameter. `false` if `size` is to be interpreted as an upper
244 /// bound.
245 exact: bool,
246 size: usize,
247 consume: usize,
248 buf_read: R,
249}
250
251impl<R> BlobRead<R> {
252 /// Construct a blob read from any [`std::io::BufRead`]. The `upper bound` is used in the type
253 /// description then binding the blob as a parameter.
254 ///
255 /// # Examples
256 ///
257 /// This is more flexible than [`Self::from_path`]. Note however that files provide metadata
258 /// about the length of the data, which `io::BufRead` does not. This is not an issue for most
259 /// drivers, but some can perform optimization if they know the size in advance. In the tests
260 /// SQLite has shown a bug to only insert empty data if no size hint has been provided.
261 ///
262 /// ```
263 /// use std::io::BufRead;
264 /// use odbc_api::{Connection, parameter::{Blob, BlobRead}, IntoParameter, Error};
265 ///
266 /// fn insert_image_to_db(
267 /// conn: &Connection<'_>,
268 /// id: &str,
269 /// image_data: impl BufRead) -> Result<(), Error>
270 /// {
271 /// const MAX_IMAGE_SIZE: usize = 4 * 1024 * 1024;
272 /// let mut blob = BlobRead::with_upper_bound(image_data, MAX_IMAGE_SIZE);
273 ///
274 /// let sql = "INSERT INTO Images (id, image_data) VALUES (?, ?)";
275 /// let parameters = (&id.into_parameter(), &mut blob.as_blob_param());
276 /// let timeout_sec = None;
277 /// conn.execute(sql, parameters, timeout_sec)?;
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 /// let timeout_sec = None;
333 /// conn.execute(sql, parameters, timeout_sec)?;
334 /// Ok(())
335 /// }
336 /// ```
337 pub fn from_path(path: &Path) -> io::Result<Self> {
338 let file = File::open(path)?;
339 let size = file.metadata()?.len().try_into().unwrap();
340 let buf_read = BufReader::new(file);
341 Ok(Self {
342 consume: 0,
343 exact: true,
344 size,
345 buf_read,
346 })
347 }
348}
349
350impl<R> HasDataType for BlobRead<R>
351where
352 R: BufRead,
353{
354 fn data_type(&self) -> DataType {
355 DataType::LongVarbinary {
356 length: NonZeroUsize::new(self.size),
357 }
358 }
359}
360
361unsafe impl<R> Blob for BlobRead<R>
362where
363 R: BufRead,
364{
365 fn c_data_type(&self) -> CDataType {
366 CDataType::Binary
367 }
368
369 fn size_hint(&self) -> Option<usize> {
370 if self.exact { Some(self.size) } else { None }
371 }
372
373 fn next_batch(&mut self) -> io::Result<Option<&[u8]>> {
374 if self.consume != 0 {
375 self.buf_read.consume(self.consume);
376 }
377 let batch = self.buf_read.fill_buf()?;
378 self.consume = batch.len();
379 if batch.is_empty() {
380 Ok(None)
381 } else {
382 Ok(Some(batch))
383 }
384 }
385}