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, keeping the connection exclusively borrowed while it is open
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 mut 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        drop(data);
272
273        let mut data2 = conn.get_read_only_blob(blobs::data2, 1).unwrap();
274        let mut buf = vec![];
275        data2.read_to_end(&mut buf).unwrap();
276
277        assert_eq!(buf, b"def");
278    }
279
280    #[diesel_test_helper::test]
281    fn read_seek_bytes() {
282        table! {
283            blobs {
284                id -> Integer,
285                data -> Blob,
286            }
287        }
288
289        use std::io::Read;
290        use std::io::Seek;
291        use std::io::SeekFrom;
292
293        let conn = &mut connection();
294
295        let _ = crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
296            .execute(conn);
297
298        let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('abcdefghi')").execute(conn);
299
300        let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
301
302        let mut buf = [0; 1];
303        assert_eq!(data.read(&mut buf).unwrap(), 1);
304        assert_eq!(&buf, b"a");
305
306        // Seek one forward
307        assert_eq!(data.seek(SeekFrom::Current(1)).unwrap(), 2);
308
309        let mut buf = [0; 1];
310        assert_eq!(data.read(&mut buf).unwrap(), 1);
311        assert_eq!(&buf, b"c");
312
313        // Seek back to start
314        assert_eq!(data.seek(SeekFrom::Start(0)).unwrap(), 0);
315
316        let mut buf = [0; 1];
317        assert_eq!(data.read(&mut buf).unwrap(), 1);
318        assert_eq!(&buf, b"a");
319
320        // Seek relative to end
321        assert_eq!(data.seek(SeekFrom::End(-2)).unwrap(), 7);
322
323        let mut buf = [0; 1];
324        assert_eq!(data.read(&mut buf).unwrap(), 1);
325        assert_eq!(&buf, b"h");
326
327        // Seek after end
328        data.seek(SeekFrom::Current(100)).unwrap();
329
330        // Now we don't get any bytes back
331        let mut buf = [0; 1];
332        assert_eq!(data.read(&mut buf).unwrap(), 0);
333    }
334
335    #[diesel_test_helper::test]
336    fn before_start_blob_seeks_return_errors_without_moving_cursor() {
337        table! {
338            blobs {
339                id -> Integer,
340                data -> Blob,
341            }
342        }
343
344        use std::io::{ErrorKind, Read, Seek, SeekFrom};
345
346        let conn = &mut connection();
347        crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
348            .execute(conn)
349            .unwrap();
350        crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')")
351            .execute(conn)
352            .unwrap();
353
354        let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
355        for position in [
356            SeekFrom::End(-4),
357            SeekFrom::Current(-2),
358            SeekFrom::End(i64::MIN),
359            SeekFrom::Current(i64::MIN),
360        ] {
361            assert_eq!(data.seek(SeekFrom::Start(1)).unwrap(), 1);
362            assert_eq!(
363                data.seek(position).unwrap_err().kind(),
364                ErrorKind::InvalidInput
365            );
366            assert_eq!(data.stream_position().unwrap(), 1);
367
368            let mut buf = [0; 1];
369            data.read_exact(&mut buf).unwrap();
370            assert_eq!(&buf, b"b");
371        }
372
373        assert_eq!(data.seek(SeekFrom::End(-3)).unwrap(), 0);
374        let mut buf = [0; 1];
375        data.read_exact(&mut buf).unwrap();
376        assert_eq!(&buf, b"a");
377        assert_eq!(data.seek(SeekFrom::Current(-1)).unwrap(), 0);
378    }
379
380    #[diesel_test_helper::test]
381    fn use_conn_after_blob_drop() {
382        table! {
383            blobs {
384                id -> Integer,
385                data -> Blob,
386            }
387        }
388
389        let conn = &mut connection();
390
391        let _ = crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
392            .execute(conn);
393
394        let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')").execute(conn);
395
396        let data = conn.get_read_only_blob(blobs::data, 1).unwrap();
397        drop(data);
398
399        let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('def')").execute(conn);
400    }
401
402    #[diesel_test_helper::test]
403    fn use_conn_after_blob_close() {
404        // Explicit close previously let `Drop` close the native handle a second time.
405        // Reusing the connection verifies that the handle is closed exactly once.
406        table! {
407            blobs {
408                id -> Integer,
409                data -> Blob,
410            }
411        }
412
413        let conn = &mut connection();
414
415        crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
416            .execute(conn)
417            .unwrap();
418        assert_eq!(
419            crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')")
420                .execute(conn)
421                .unwrap(),
422            1
423        );
424
425        let data = conn.get_read_only_blob(blobs::data, 1).unwrap();
426        data.close().unwrap();
427
428        assert_eq!(
429            crate::sql_query("INSERT INTO blobs (data) VALUES ('def')")
430                .execute(conn)
431                .unwrap(),
432            1
433        );
434        assert_eq!(blobs::table.count().get_result::<i64>(conn).unwrap(), 2);
435    }
436
437    #[diesel_test_helper::test]
438    fn blob_transaction() {
439        table! {
440            blobs {
441                id -> Integer,
442                data -> Blob,
443            }
444        }
445
446        use std::io::Read;
447
448        let conn = &mut connection();
449
450        let _ = crate::sql_query("CREATE TABLE blobs (id INTEGER PRIMARY KEY, data BLOB)")
451            .execute(conn);
452
453        let _ = crate::sql_query("INSERT INTO blobs (data) VALUES ('abc')").execute(conn);
454
455        {
456            let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
457            let mut buf = vec![];
458            data.read_to_end(&mut buf).unwrap();
459            assert_eq!(buf, b"abc");
460        }
461
462        let res = conn.exclusive_transaction(|conn| {
463            crate::sql_query("UPDATE blobs SET data = 'def' WHERE id = 1").execute(conn)?;
464
465            let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
466            let mut buf = vec![];
467            data.read_to_end(&mut buf).unwrap();
468            assert_eq!(buf, b"def");
469
470            Result::<(), _>::Err(Error::RollbackTransaction)
471        });
472
473        assert_eq!(res.unwrap_err(), Error::RollbackTransaction);
474
475        let mut data = conn.get_read_only_blob(blobs::data, 1).unwrap();
476        let mut buf = vec![];
477        data.read_to_end(&mut buf).unwrap();
478        assert_eq!(buf, b"abc");
479    }
480}