diesel/sqlite/connection/
sqlite_blob.rs1#![allow(unsafe_code)] #[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#[expect(missing_debug_implementations)]
23#[cfg_attr(not(feature = "std"), expect(dead_code))]
24pub struct SqliteReadOnlyBlob<'conn> {
25 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 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 pub fn is_empty(&self) -> bool {
56 self.len() == 0
57 }
58
59 pub fn len(&self) -> usize {
61 self.blob_size
62 }
63
64 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 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)] fn 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#[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 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 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 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 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 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 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 data.seek(SeekFrom::Current(100)).unwrap();
328
329 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 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}