rusqlite/blob/pos_io.rs
1use super::Blob;
2
3use std::convert::TryFrom;
4use std::mem::MaybeUninit;
5use std::slice::from_raw_parts_mut;
6
7use crate::ffi;
8use crate::{Error, Result};
9
10impl<'conn> Blob<'conn> {
11 /// Write `buf` to `self` starting at `write_start`, returning an error if
12 /// `write_start + buf.len()` is past the end of the blob.
13 ///
14 /// If an error is returned, no data is written.
15 ///
16 /// Note: the blob cannot be resized using this function -- that must be
17 /// done using SQL (for example, an `UPDATE` statement).
18 ///
19 /// Note: This is part of the positional I/O API, and thus takes an absolute
20 /// position write to, instead of using the internal position that can be
21 /// manipulated by the `std::io` traits.
22 ///
23 /// Unlike the similarly named [`FileExt::write_at`][fext_write_at] function
24 /// (from `std::os::unix`), it's always an error to perform a "short write".
25 ///
26 /// [fext_write_at]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.write_at
27 #[inline]
28 pub fn write_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> {
29 let len = self.len();
30
31 if buf.len().saturating_add(write_start) > len {
32 return Err(Error::BlobSizeError);
33 }
34 // We know `len` fits in an `i32`, so either:
35 //
36 // 1. `buf.len() + write_start` overflows, in which case we'd hit the
37 // return above (courtesy of `saturating_add`).
38 //
39 // 2. `buf.len() + write_start` doesn't overflow but is larger than len,
40 // in which case ditto.
41 //
42 // 3. `buf.len() + write_start` doesn't overflow but is less than len.
43 // This means that both `buf.len()` and `write_start` can also be
44 // losslessly converted to i32, since `len` came from an i32.
45 // Sanity check the above.
46 debug_assert!(i32::try_from(write_start).is_ok() && i32::try_from(buf.len()).is_ok());
47 self.conn.decode_result(unsafe {
48 ffi::sqlite3_blob_write(
49 self.blob,
50 buf.as_ptr().cast(),
51 buf.len() as i32,
52 write_start as i32,
53 )
54 })
55 }
56
57 /// An alias for `write_at` provided for compatibility with the conceptually
58 /// equivalent [`std::os::unix::FileExt::write_all_at`][write_all_at]
59 /// function from libstd:
60 ///
61 /// [write_all_at]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#method.write_all_at
62 #[inline]
63 pub fn write_all_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> {
64 self.write_at(buf, write_start)
65 }
66
67 /// Read as much as possible from `offset` to `offset + buf.len()` out of
68 /// `self`, writing into `buf`. On success, returns the number of bytes
69 /// written.
70 ///
71 /// If there's insufficient data in `self`, then the returned value will be
72 /// less than `buf.len()`.
73 ///
74 /// See also [`Blob::raw_read_at`], which can take an uninitialized buffer,
75 /// or [`Blob::read_at_exact`] which returns an error if the entire `buf` is
76 /// not read.
77 ///
78 /// Note: This is part of the positional I/O API, and thus takes an absolute
79 /// position to read from, instead of using the internal position that can
80 /// be manipulated by the `std::io` traits. Consequently, it does not change
81 /// that value either.
82 #[inline]
83 pub fn read_at(&self, buf: &mut [u8], read_start: usize) -> Result<usize> {
84 // Safety: this is safe because `raw_read_at` never stores uninitialized
85 // data into `as_uninit`.
86 let as_uninit: &mut [MaybeUninit<u8>] =
87 unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast(), buf.len()) };
88 self.raw_read_at(as_uninit, read_start).map(|s| s.len())
89 }
90
91 /// Read as much as possible from `offset` to `offset + buf.len()` out of
92 /// `self`, writing into `buf`. On success, returns the portion of `buf`
93 /// which was initialized by this call.
94 ///
95 /// If there's insufficient data in `self`, then the returned value will be
96 /// shorter than `buf`.
97 ///
98 /// See also [`Blob::read_at`], which takes a `&mut [u8]` buffer instead of
99 /// a slice of `MaybeUninit<u8>`.
100 ///
101 /// Note: This is part of the positional I/O API, and thus takes an absolute
102 /// position to read from, instead of using the internal position that can
103 /// be manipulated by the `std::io` traits. Consequently, it does not change
104 /// that value either.
105 #[inline]
106 pub fn raw_read_at<'a>(
107 &self,
108 buf: &'a mut [MaybeUninit<u8>],
109 read_start: usize,
110 ) -> Result<&'a mut [u8]> {
111 let len = self.len();
112
113 let read_len = match len.checked_sub(read_start) {
114 None | Some(0) => 0,
115 Some(v) => v.min(buf.len()),
116 };
117
118 if read_len == 0 {
119 // We could return `Ok(&mut [])`, but it seems confusing that the
120 // pointers don't match, so fabricate a empty slice of u8 with the
121 // same base pointer as `buf`.
122 let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), 0) };
123 return Ok(empty);
124 }
125
126 // At this point we believe `read_start as i32` is lossless because:
127 //
128 // 1. `len as i32` is known to be lossless, since it comes from a SQLite
129 // api returning an i32.
130 //
131 // 2. If we got here, `len.checked_sub(read_start)` was Some (or else
132 // we'd have hit the `if read_len == 0` early return), so `len` must
133 // be larger than `read_start`, and so it must fit in i32 as well.
134 debug_assert!(i32::try_from(read_start).is_ok());
135
136 // We also believe that `read_start + read_len <= len` because:
137 //
138 // 1. This is equivalent to `read_len <= len - read_start` via algebra.
139 // 2. We know that `read_len` is `min(len - read_start, buf.len())`
140 // 3. Expanding, this is `min(len - read_start, buf.len()) <= len - read_start`,
141 // or `min(A, B) <= A` which is clearly true.
142 //
143 // Note that this stuff is in debug_assert so no need to use checked_add
144 // and such -- we'll always panic on overflow in debug builds.
145 debug_assert!(read_start + read_len <= len);
146
147 // These follow naturally.
148 debug_assert!(buf.len() >= read_len);
149 debug_assert!(i32::try_from(buf.len()).is_ok());
150 debug_assert!(i32::try_from(read_len).is_ok());
151
152 unsafe {
153 self.conn.decode_result(ffi::sqlite3_blob_read(
154 self.blob,
155 buf.as_mut_ptr().cast(),
156 read_len as i32,
157 read_start as i32,
158 ))?;
159
160 Ok(from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), read_len))
161 }
162 }
163
164 /// Equivalent to [`Blob::read_at`], but returns a `BlobSizeError` if `buf`
165 /// is not fully initialized.
166 #[inline]
167 pub fn read_at_exact(&self, buf: &mut [u8], read_start: usize) -> Result<()> {
168 let n = self.read_at(buf, read_start)?;
169 if n != buf.len() {
170 Err(Error::BlobSizeError)
171 } else {
172 Ok(())
173 }
174 }
175
176 /// Equivalent to [`Blob::raw_read_at`], but returns a `BlobSizeError` if
177 /// `buf` is not fully initialized.
178 #[inline]
179 pub fn raw_read_at_exact<'a>(
180 &self,
181 buf: &'a mut [MaybeUninit<u8>],
182 read_start: usize,
183 ) -> Result<&'a mut [u8]> {
184 let buflen = buf.len();
185 let initted = self.raw_read_at(buf, read_start)?;
186 if initted.len() != buflen {
187 Err(Error::BlobSizeError)
188 } else {
189 Ok(initted)
190 }
191 }
192}
193
194#[cfg(test)]
195mod test {
196 use crate::{Connection, DatabaseName, Result};
197 // to ensure we don't modify seek pos
198 use std::io::Seek as _;
199
200 #[test]
201 fn test_pos_io() -> Result<()> {
202 let db = Connection::open_in_memory()?;
203 db.execute_batch("CREATE TABLE test_table(content BLOB);")?;
204 db.execute("INSERT INTO test_table(content) VALUES (ZEROBLOB(10))", [])?;
205
206 let rowid = db.last_insert_rowid();
207 let mut blob = db.blob_open(DatabaseName::Main, "test_table", "content", rowid, false)?;
208 // modify the seek pos to ensure we aren't using it or modifying it.
209 blob.seek(std::io::SeekFrom::Start(1)).unwrap();
210
211 let one2ten: [u8; 10] = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10];
212 blob.write_at(&one2ten, 0).unwrap();
213
214 let mut s = [0u8; 10];
215 blob.read_at_exact(&mut s, 0).unwrap();
216 assert_eq!(&s, &one2ten, "write should go through");
217 blob.read_at_exact(&mut s, 1).unwrap_err();
218
219 blob.read_at_exact(&mut s, 0).unwrap();
220 assert_eq!(&s, &one2ten, "should be unchanged");
221
222 let mut fives = [0u8; 5];
223 blob.read_at_exact(&mut fives, 0).unwrap();
224 assert_eq!(&fives, &[1u8, 2, 3, 4, 5]);
225
226 blob.read_at_exact(&mut fives, 5).unwrap();
227 assert_eq!(&fives, &[6u8, 7, 8, 9, 10]);
228 blob.read_at_exact(&mut fives, 7).unwrap_err();
229 blob.read_at_exact(&mut fives, 12).unwrap_err();
230 blob.read_at_exact(&mut fives, 10).unwrap_err();
231 blob.read_at_exact(&mut fives, i32::MAX as usize)
232 .unwrap_err();
233 blob.read_at_exact(&mut fives, i32::MAX as usize + 1)
234 .unwrap_err();
235
236 // zero length writes are fine if in bounds
237 blob.read_at_exact(&mut [], 10).unwrap();
238 blob.read_at_exact(&mut [], 0).unwrap();
239 blob.read_at_exact(&mut [], 5).unwrap();
240
241 blob.write_all_at(&[16, 17, 18, 19, 20], 5).unwrap();
242 blob.read_at_exact(&mut s, 0).unwrap();
243 assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);
244
245 blob.write_at(&[100, 99, 98, 97, 96], 6).unwrap_err();
246 blob.write_at(&[100, 99, 98, 97, 96], i32::MAX as usize)
247 .unwrap_err();
248 blob.write_at(&[100, 99, 98, 97, 96], i32::MAX as usize + 1)
249 .unwrap_err();
250
251 blob.read_at_exact(&mut s, 0).unwrap();
252 assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);
253
254 let mut s2: [std::mem::MaybeUninit<u8>; 10] = [std::mem::MaybeUninit::uninit(); 10];
255 {
256 let read = blob.raw_read_at_exact(&mut s2, 0).unwrap();
257 assert_eq!(read, &s);
258 assert!(std::ptr::eq(read.as_ptr(), s2.as_ptr().cast()));
259 }
260
261 let mut empty = [];
262 assert!(std::ptr::eq(
263 blob.raw_read_at_exact(&mut empty, 0).unwrap().as_ptr(),
264 empty.as_ptr().cast(),
265 ));
266 blob.raw_read_at_exact(&mut s2, 5).unwrap_err();
267
268 let end_pos = blob.stream_position().unwrap();
269 assert_eq!(end_pos, 1);
270 Ok(())
271 }
272}