Skip to main content

diesel/sqlite/connection/
sqlite_blob.rs

1#![allow(unsafe_code)] // ffi calls
2#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3extern crate libsqlite3_sys as ffi;
4
5#[cfg(all(target_family = "wasm", target_os = "unknown"))]
6use sqlite_wasm_rs as ffi;
7
8use super::SqliteConnection;
9use crate::query_source::{ColumnHasTable, NamedTable};
10use crate::result::Error;
11
12/// A read only SQLite Blob
13///
14/// This interface allows to incrementally read a blob from a SQLite database.
15/// Notably this type implements [`std::io::Read`] and [`std::io::Seek`] to integrate
16/// with standard Rust IO mechanisms.
17///
18/// You can use [`SqliteConnection::get_read_only_blob`](super::SqliteConnection::get_read_only_blob)
19/// to get a new instance of this type
20///
21/// See the [SQLite documentation](https://sqlite.org/c3ref/blob_open.html) for more details
22#[expect(missing_debug_implementations)]
23#[cfg_attr(not(feature = "std"), expect(dead_code))]
24pub struct SqliteReadOnlyBlob<'conn> {
25    // `None` once closed, so the handle is closed at most once
26    pub(crate) blob: Option<core::ptr::NonNull<ffi::sqlite3_blob>>,
27    pub(crate) read_index: usize,
28
29    pub(crate) blob_size: usize,
30    pub(crate) _pd: core::marker::PhantomData<&'conn mut ffi::sqlite3_blob>,
31}
32
33impl Drop for SqliteReadOnlyBlob<'_> {
34    fn drop(&mut self) {
35        use crate::util::std_compat::panicking;
36
37        // `close` already closed the handle
38        if self.blob.is_none() {
39            return;
40        }
41
42        if let Err(error_message) = self.close_inner() {
43            if panicking() {
44                #[cfg(feature = "std")]
45                {
    ::std::io::_eprint(format_args!("Error closing SQLite blob: {0}\n",
            error_message));
};eprintln!("Error closing SQLite blob: {error_message}");
46            } else {
47                {
    ::core::panicking::panic_fmt(format_args!("Error closing SQLite blob: {0}",
            error_message));
};panic!("Error closing SQLite blob: {error_message}");
48            }
49        }
50    }
51}
52
53impl SqliteReadOnlyBlob<'_> {
54    /// Is the blob storage empty
55    pub fn is_empty(&self) -> bool {
56        self.len() == 0
57    }
58
59    /// The size of the underlying blob in bytes
60    pub fn len(&self) -> usize {
61        self.blob_size
62    }
63
64    /// Close the handle
65    ///
66    /// Even if an error is returned, the handle is still closed (from the sqlite documentation):
67    ///
68    /// > The BLOB handle is closed unconditionally. Even if this routine returns an error code,
69    /// > the handle is still closed.
70    pub fn close(mut self) -> Result<(), crate::result::Error> {
71        self.close_inner()
72    }
73
74    fn close_inner(&mut self) -> Result<(), crate::result::Error> {
75        let Some(blob) = self.blob.take() else {
76            return Err(crate::result::Error::ClosingHandle("handle already closed"));
77        };
78
79        // SAFETY: `blob` came from a successful `sqlite3_blob_open`, and taking it out of `self`
80        // guarantees that this open handle is closed at most once.
81        //
82        // From the sqlite3_blob_close documentation:
83        //
84        //     If an error occurs while committing the transaction, an error code is returned and
85        //     the transaction rolled back.
86        //
87        // As we are in read-only mode here, this is not an issue
88        let close_result = unsafe { ffi::sqlite3_blob_close(blob.as_ptr()) };
89
90        if close_result != ffi::SQLITE_OK {
91            let error_message = super::error_message(close_result);
92            return Err(crate::result::Error::ClosingHandle(error_message));
93        }
94
95        Ok(())
96    }
97}
98
99#[cfg(feature = "std")]
100#[allow(clippy::std_instead_of_core)] // needs a newer rust version
101fn to_io_error(error: core::num::TryFromIntError) -> std::io::Error {
102    std::io::Error::new(std::io::ErrorKind::InvalidInput, Box::new(error))
103}
104
105// SEE https://github.com/rust-lang/rust/issues/48331
106#[cfg(feature = "std")]
107impl std::io::Read for SqliteReadOnlyBlob<'_> {
108    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
109        let Some(blob) = self.blob else {
110            return Err(std::io::Error::other("SQLite blob handle is closed"));
111        };
112        let buflen: i32 = buf.len().try_into().map_err(to_io_error)?;
113        let offset: i32 = self.read_index.try_into().map_err(to_io_error)?;
114
115        // From the sqlite docs:
116        //
117        // > If offset iOffset is less than N bytes from the end of the BLOB, SQLITE_ERROR is returned and no data is read.
118        //
119        // Thus we need to make sure to not provide a buffer that is too big for the remaining data
120        // from the blob.
121        let read_length: i32 = (i32::try_from(self.blob_size)
122            .map_err(to_io_error)?
123            .saturating_sub(offset))
124        .min(buflen);
125
126        // SAFETY: `blob` is `Some`, so the handle is open, and `'conn` keeps its connection alive.
127        // `read_length` is at most `buf.len()` and the bytes left after `offset`, so the write
128        // stays within `buf` and the read within the blob.
129        let ret = unsafe {
130            ffi::sqlite3_blob_read(
131                blob.as_ptr(),
132                buf.as_mut_ptr() as *mut core::ffi::c_void,
133                read_length,
134                offset,
135            )
136        };
137
138        if ret != ffi::SQLITE_OK {
139            let error_message = crate::sqlite::connection::error_message(ret);
140            return Err(std::io::Error::other(error_message.to_string()));
141        }
142
143        self.read_index += usize::try_from(read_length).map_err(to_io_error)?;
144        if true {
    if !(self.read_index <= self.blob_size) {
        ::core::panicking::panic("assertion failed: self.read_index <= self.blob_size")
    };
};debug_assert!(self.read_index <= self.blob_size);
145
146        usize::try_from(read_length).map_err(to_io_error)
147    }
148}
149
150#[cfg(feature = "std")]
151impl std::io::Seek for SqliteReadOnlyBlob<'_> {
152    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
153        match pos {
154            std::io::SeekFrom::Start(n) => {
155                self.read_index = usize::try_from(n).map_err(to_io_error)?.min(self.blob_size);
156            }
157            std::io::SeekFrom::End(n) => {
158                self.read_index = if n.is_positive() {
159                    self.blob_size
160                } else {
161                    self.blob_size
162                        .checked_sub(usize::try_from(n.unsigned_abs()).map_err(to_io_error)?)
163                        .ok_or(std::io::ErrorKind::InvalidInput)?
164                };
165            }
166            std::io::SeekFrom::Current(n) => {
167                let n = isize::try_from(n).map_err(to_io_error)?;
168
169                if n.is_negative() {
170                    self.read_index = self
171                        .read_index
172                        .checked_sub(n.unsigned_abs())
173                        .ok_or(std::io::ErrorKind::InvalidInput)?;
174                } else {
175                    self.read_index = (self.read_index + n.unsigned_abs()).min(self.blob_size);
176                }
177            }
178        }
179
180        u64::try_from(self.read_index).map_err(to_io_error)
181    }
182}
183
184impl SqliteConnection {
185    /// Returns an object that can be used to stream a BLOB from the database
186    ///
187    /// # Example
188    ///
189    /// ```rust
190    /// # include!("../../doctest_setup.rs");
191    /// # table! {
192    /// #     myblobs {
193    /// #         id -> Integer,
194    /// #         mydata -> Blob,
195    /// #     }
196    /// # }
197    /// # fn main() {
198    /// #     run_test().unwrap();
199    /// # }
200    /// # fn run_test() -> Result<(), Box<dyn std::error::Error>> {
201    /// use std::io::Read;
202    /// use diesel::connection::SimpleConnection;
203    /// let conn = &mut SqliteConnection::establish(":memory:").unwrap();
204    /// conn.batch_execute("CREATE TABLE myblobs (id INTEGER PRIMARY KEY, mydata BLOB)")?;
205    /// conn.batch_execute("INSERT INTO myblobs (mydata) VALUES ('abc')")?;
206    /// let mut data = conn.get_read_only_blob(myblobs::mydata, 1)?;
207    /// let mut buf = vec![];
208    /// data.read_to_end(&mut buf)?;
209    /// assert_eq!(buf, b"abc");
210    /// # Ok(())
211    /// # }
212    /// ```
213    pub fn get_read_only_blob<'conn, 'query, U>(
214        &'conn self,
215        blob_column: U,
216        row_id: i64,
217    ) -> Result<SqliteReadOnlyBlob<'conn>, Error>
218    where
219        'query: 'conn,
220        U: ColumnHasTable,
221        U::Table: NamedTable,
222    {
223        let table = blob_column.table();
224
225        let database_name = table.schema().unwrap_or("main");
226        let column_name = blob_column.name();
227        let table_name = table.table();
228
229        self.raw_connection
230            .blob_open(database_name, table_name, column_name, row_id)
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237    use crate::prelude::*;
238
239    fn connection() -> SqliteConnection {
240        SqliteConnection::establish(":memory:").unwrap()
241    }
242
243    #[diesel_test_helper::test]
244    fn read_bytes_from_blob() {
245        table! {
246            blobs {
247                id -> Integer,
248                data -> Blob,
249                data2 -> Blob,
250            }
251        }
252
253        use std::io::Read;
254
255        let conn = &mut connection();
256
257        let _ =
258            crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB, data2 BLOB)")
259                .execute(conn);
260
261        let _ = crate::sql_query(
262            "INSERT INTO blobs (data, data2) VALUES ('abc', 'def'), ('123', '456')",
263        )
264        .execute(conn);
265
266        let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
267        let mut buf = vec![];
268        data.read_to_end(&mut buf).unwrap();
269
270        assert_eq!(buf, b"abc");
271
272        let mut data2 = conn.get_read_only_blob(blobs::data2, 1).unwrap();
273        let mut buf = vec![];
274        data2.read_to_end(&mut buf).unwrap();
275
276        assert_eq!(buf, b"def");
277    }
278
279    #[diesel_test_helper::test]
280    fn read_seek_bytes() {
281        table! {
282            blobs {
283                id -> Integer,
284                data -> Blob,
285            }
286        }
287
288        use std::io::Read;
289        use std::io::Seek;
290        use std::io::SeekFrom;
291
292        let conn = &mut connection();
293
294        let _ = crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
295            .execute(conn);
296
297        let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('abcdefghi')").execute(conn);
298
299        let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
300
301        let mut buf = [0; 1];
302        assert_eq!(data.read(&mut buf).unwrap(), 1);
303        assert_eq!(&buf, b"a");
304
305        // Seek one forward
306        assert_eq!(data.seek(SeekFrom::Current(1)).unwrap(), 2);
307
308        let mut buf = [0; 1];
309        assert_eq!(data.read(&mut buf).unwrap(), 1);
310        assert_eq!(&buf, b"c");
311
312        // Seek back to start
313        assert_eq!(data.seek(SeekFrom::Start(0)).unwrap(), 0);
314
315        let mut buf = [0; 1];
316        assert_eq!(data.read(&mut buf).unwrap(), 1);
317        assert_eq!(&buf, b"a");
318
319        // Seek relative to end
320        assert_eq!(data.seek(SeekFrom::End(-2)).unwrap(), 7);
321
322        let mut buf = [0; 1];
323        assert_eq!(data.read(&mut buf).unwrap(), 1);
324        assert_eq!(&buf, b"h");
325
326        // Seek after end
327        data.seek(SeekFrom::Current(100)).unwrap();
328
329        // Now we don't get any bytes back
330        let mut buf = [0; 1];
331        assert_eq!(data.read(&mut buf).unwrap(), 0);
332    }
333
334    #[diesel_test_helper::test]
335    fn before_start_blob_seeks_return_errors_without_moving_cursor() {
336        table! {
337            blobs {
338                id -> Integer,
339                data -> Blob,
340            }
341        }
342
343        use std::io::{ErrorKind, Read, Seek, SeekFrom};
344
345        let conn = &mut connection();
346        crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
347            .execute(conn)
348            .unwrap();
349        crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')")
350            .execute(conn)
351            .unwrap();
352
353        let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
354        for position in [
355            SeekFrom::End(-4),
356            SeekFrom::Current(-2),
357            SeekFrom::End(i64::MIN),
358            SeekFrom::Current(i64::MIN),
359        ] {
360            assert_eq!(data.seek(SeekFrom::Start(1)).unwrap(), 1);
361            assert_eq!(
362                data.seek(position).unwrap_err().kind(),
363                ErrorKind::InvalidInput
364            );
365            assert_eq!(data.stream_position().unwrap(), 1);
366
367            let mut buf = [0; 1];
368            data.read_exact(&mut buf).unwrap();
369            assert_eq!(&buf, b"b");
370        }
371
372        assert_eq!(data.seek(SeekFrom::End(-3)).unwrap(), 0);
373        let mut buf = [0; 1];
374        data.read_exact(&mut buf).unwrap();
375        assert_eq!(&buf, b"a");
376        assert_eq!(data.seek(SeekFrom::Current(-1)).unwrap(), 0);
377    }
378
379    #[diesel_test_helper::test]
380    fn use_conn_after_blob_drop() {
381        table! {
382            blobs {
383                id -> Integer,
384                data -> Blob,
385            }
386        }
387
388        let conn = &mut connection();
389
390        let _ = crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
391            .execute(conn);
392
393        let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')").execute(conn);
394
395        let data = conn.get_read_only_blob(blobs::data, 1).unwrap();
396        drop(data);
397
398        let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('def')").execute(conn);
399    }
400
401    #[diesel_test_helper::test]
402    fn use_conn_after_blob_close() {
403        // Explicit close previously let `Drop` close the native handle a second time.
404        // Reusing the connection verifies that the handle is closed exactly once.
405        table! {
406            blobs {
407                id -> Integer,
408                data -> Blob,
409            }
410        }
411
412        let conn = &mut connection();
413
414        crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
415            .execute(conn)
416            .unwrap();
417        assert_eq!(
418            crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')")
419                .execute(conn)
420                .unwrap(),
421            1
422        );
423
424        let data = conn.get_read_only_blob(blobs::data, 1).unwrap();
425        data.close().unwrap();
426
427        assert_eq!(
428            crate::sql_query("INSERT INTO blobs (data) VALUES ('def')")
429                .execute(conn)
430                .unwrap(),
431            1
432        );
433        assert_eq!(blobs::table.count().get_result::<i64>(conn).unwrap(), 2);
434    }
435
436    #[diesel_test_helper::test]
437    fn blob_transaction() {
438        table! {
439            blobs {
440                id -> Integer,
441                data -> Blob,
442            }
443        }
444
445        use std::io::Read;
446
447        let conn = &mut connection();
448
449        let _ = crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
450            .execute(conn);
451
452        let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')").execute(conn);
453
454        {
455            let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
456            let mut buf = vec![];
457            data.read_to_end(&mut buf).unwrap();
458            assert_eq!(buf, b"abc");
459        }
460
461        let res = conn.exclusive_transaction(|conn| {
462            crate::sql_query("UPDATE blobs SET data = 'def' WHERE id = 1").execute(conn)?;
463
464            let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
465            let mut buf = vec![];
466            data.read_to_end(&mut buf).unwrap();
467            assert_eq!(buf, b"def");
468
469            Result::<(), _>::Err(Error::RollbackTransaction)
470        });
471
472        assert_eq!(res.unwrap_err(), Error::RollbackTransaction);
473
474        let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
475        let mut buf = vec![];
476        data.read_to_end(&mut buf).unwrap();
477        assert_eq!(buf, b"abc");
478    }
479}