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 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 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 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 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 data.seek(SeekFrom::Current(100)).unwrap();
329
330 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 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}