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
8/// A read only SQLite Blob
9///
10/// This interface allows to incrementally read a blob from a SQLite database.
11/// Notably this type implements [`std::io::Read`] and [`std::io::Seek`] to integrate
12/// with standard Rust IO mechanisms.
13///
14/// You can use [`SqliteConnection::get_read_only_blob`](super::SqliteConnection::get_read_only_blob)
15/// to get a new instance of this type
16///
17/// See the [SQLite documentation](https://sqlite.org/c3ref/blob_open.html) for more details
18#[expect(missing_debug_implementations)]
19#[cfg_attr(not(feature = "std"), expect(dead_code))]
20pub struct SqliteReadOnlyBlob<'conn> {
21    pub(crate) blob: core::ptr::NonNull<ffi::sqlite3_blob>,
22    pub(crate) read_index: usize,
23
24    pub(crate) blob_size: usize,
25    pub(crate) _pd: core::marker::PhantomData<&'conn mut ffi::sqlite3_blob>,
26}
27
28impl Drop for SqliteReadOnlyBlob<'_> {
29    fn drop(&mut self) {
30        use crate::util::std_compat::panicking;
31
32        if let Err(error_message) = self.close_inner() {
33            if panicking() {
34                #[cfg(feature = "std")]
35                {
    ::std::io::_eprint(format_args!("Error closing SQLite blob: {0}\n",
            error_message));
};eprintln!("Error closing SQLite blob: {error_message}");
36            } else {
37                {
    ::core::panicking::panic_fmt(format_args!("Error closing SQLite blob: {0}",
            error_message));
};panic!("Error closing SQLite blob: {error_message}");
38            }
39        }
40    }
41}
42
43impl SqliteReadOnlyBlob<'_> {
44    /// Is the blob storage empty
45    pub fn is_empty(&self) -> bool {
46        self.len() == 0
47    }
48
49    /// The size of the underlying blob in bytes
50    pub fn len(&self) -> usize {
51        self.blob_size
52    }
53
54    /// Close the handle
55    ///
56    /// Even if an error is returned, the handle is still closed (from the sqlite documentation):
57    ///
58    /// > The BLOB handle is closed unconditionally. Even if this routine returns an error code,
59    /// > the handle is still closed.
60    pub fn close(mut self) -> Result<(), crate::result::Error> {
61        self.close_inner()
62    }
63
64    fn close_inner(&mut self) -> Result<(), crate::result::Error> {
65        // SAFETY: From the sqlite3_blob_close documentation:
66        //
67        //     If an error occurs while committing the transaction, an error code is returned and
68        //     the transaction rolled back.
69        //
70        // As we are in read-only mode here, this is not an issue
71        let close_result = unsafe { ffi::sqlite3_blob_close(self.blob.as_ptr()) };
72
73        if close_result != ffi::SQLITE_OK {
74            let error_message = super::error_message(close_result);
75            return Err(crate::result::Error::ClosingHandle(error_message));
76        }
77
78        Ok(())
79    }
80}
81
82#[cfg(feature = "std")]
83fn to_io_error(error: core::num::TryFromIntError) -> std::io::Error {
84    std::io::Error::new(std::io::ErrorKind::InvalidInput, Box::new(error))
85}
86
87// SEE https://github.com/rust-lang/rust/issues/48331
88#[cfg(feature = "std")]
89impl std::io::Read for SqliteReadOnlyBlob<'_> {
90    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
91        let buflen: i32 = buf.len().try_into().map_err(to_io_error)?;
92        let offset: i32 = self.read_index.try_into().map_err(to_io_error)?;
93
94        // From the sqlite docs:
95        //
96        // > If offset iOffset is less than N bytes from the end of the BLOB, SQLITE_ERROR is returned and no data is read.
97        //
98        // Thus we need to make sure to not provide a buffer that is too big for the remaining data
99        // from the blob.
100        let read_length: i32 = (i32::try_from(self.blob_size)
101            .map_err(to_io_error)?
102            .saturating_sub(offset))
103        .min(buflen);
104
105        let ret = unsafe {
106            ffi::sqlite3_blob_read(
107                self.blob.as_ptr(),
108                buf.as_mut_ptr() as *mut core::ffi::c_void,
109                read_length,
110                offset,
111            )
112        };
113
114        if ret != ffi::SQLITE_OK {
115            let error_message = crate::sqlite::connection::error_message(ret);
116            return Err(std::io::Error::other(error_message.to_string()));
117        }
118
119        self.read_index += usize::try_from(read_length).map_err(to_io_error)?;
120        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);
121
122        usize::try_from(read_length).map_err(to_io_error)
123    }
124}
125
126#[cfg(feature = "std")]
127impl std::io::Seek for SqliteReadOnlyBlob<'_> {
128    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
129        match pos {
130            std::io::SeekFrom::Start(n) => {
131                self.read_index = usize::try_from(n).map_err(to_io_error)?.min(self.blob_size);
132            }
133            std::io::SeekFrom::End(n) => {
134                self.read_index = if n.is_positive() {
135                    self.blob_size
136                } else {
137                    self.blob_size - usize::try_from(n.unsigned_abs()).map_err(to_io_error)?
138                };
139            }
140            std::io::SeekFrom::Current(n) => {
141                let n = isize::try_from(n).map_err(to_io_error)?;
142
143                if n.is_negative() {
144                    self.read_index = if self.read_index < n.unsigned_abs() {
145                        0
146                    } else {
147                        self.read_index - n.unsigned_abs()
148                    };
149                } else {
150                    self.read_index = (self.read_index + n.unsigned_abs()).min(self.blob_size);
151                }
152            }
153        }
154
155        u64::try_from(self.read_index).map_err(to_io_error)
156    }
157}