From 1bf12f8150ace74abde0a8e988d75deb2f1de4c2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 1 Aug 2015 18:51:02 +0200 Subject: [PATCH 1/5] Feature blob IO. --- Cargo.toml | 1 + src/blob.rs | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/blob.rs diff --git a/Cargo.toml b/Cargo.toml index 5d05ed5..c9a45d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ name = "rusqlite" [features] load_extension = ["libsqlite3-sys/load_extension"] +blob = [] [dependencies] time = "~0.1.0" diff --git a/src/blob.rs b/src/blob.rs new file mode 100644 index 0000000..747c729 --- /dev/null +++ b/src/blob.rs @@ -0,0 +1,137 @@ +use std::mem; +use std::ptr; + +use super::ffi; +use {SqliteError, SqliteResult, SqliteConnection}; + +pub struct SqliteBlob<'conn> { + conn: &'conn SqliteConnection, + blob: *mut ffi::sqlite3_blob, + pos: i32, +} + +pub enum SeekFrom { + Start(i32), + End(i32), + Current(i32), +} + +impl SqliteConnection { + pub fn blob_open<'a>(&'a self, db: &str, table: &str, column: &str, row: i64, read_only: bool) -> SqliteResult> { + let mut c = self.db.borrow_mut(); + let mut blob = ptr::null_mut(); + let db = try!(super::str_to_cstring(db)); + let table = try!(super::str_to_cstring(table)); + let column = try!(super::str_to_cstring(column)); + let rc = unsafe{ ffi::sqlite3_blob_open(c.db(), db.as_ptr(), table.as_ptr(), column.as_ptr(), row, if read_only { 0 } else { 1 }, &mut blob) }; + c.decode_result(rc).map(|_| { + SqliteBlob{ conn: self, blob: blob, pos: 0 } + }) + } +} + +impl<'conn> SqliteBlob<'conn> { + pub fn reopen(&mut self, row: i64) -> SqliteResult<()> { + let rc = unsafe{ ffi::sqlite3_blob_reopen(self.blob, row) }; + if rc != ffi::SQLITE_OK { + return self.conn.decode_result(rc); + } + self.pos = 0; + Ok(()) + } + + pub fn size(&self) -> i32 { + unsafe{ ffi::sqlite3_blob_bytes(self.blob) } + } + + pub fn read(&mut self, buf: &mut [u8]) -> SqliteResult { + let mut n = buf.len() as i32; + let size = self.size(); + if self.pos + n > size { + n = size - self.pos; + } + if n <= 0 { + return Ok(0); + } + let rc = unsafe { ffi::sqlite3_blob_read(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) }; + self.conn.decode_result(rc).map(|_| { + self.pos += n; + n + }) + } + + pub fn write(&mut self, buf: &[u8]) -> SqliteResult { + let n = buf.len() as i32; + let size = self.size(); + if self.pos + n > size { + return Err(SqliteError{code: ffi::SQLITE_MISUSE, message: format!("pos = {} + n = {} > size = {}", self.pos, n, size)}); + } + if n <= 0 { + return Ok(0); + } + let rc = unsafe { ffi::sqlite3_blob_write(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) }; + self.conn.decode_result(rc).map(|_| { + self.pos += n; + n + }) + } + + pub fn seek(&mut self, pos: SeekFrom) { + self.pos = match pos { + SeekFrom::Start(offset) => offset, + SeekFrom::Current(offset) => self.pos + offset, + SeekFrom::End(offset) => self.size() + offset + }; + } + + pub fn close(mut self) -> SqliteResult<()> { + self.close_() + } + + fn close_(&mut self) -> SqliteResult<()> { + let rc = unsafe { ffi::sqlite3_blob_close(self.blob) }; + self.blob = ptr::null_mut(); + self.conn.decode_result(rc) + } +} + +#[allow(unused_must_use)] +impl<'conn> Drop for SqliteBlob<'conn> { + fn drop(&mut self) { + self.close_(); + } +} + +#[cfg(test)] +mod test { + use SqliteConnection; + + #[test] + fn test_blob() { + let db = SqliteConnection::open_in_memory().unwrap(); + let sql = "BEGIN; + CREATE TABLE test (content BLOB); + INSERT INTO test VALUES (ZEROBLOB(10)); + END;"; + db.execute_batch(sql).unwrap(); + let rowid = db.last_insert_rowid(); + + let mut blob = db.blob_open("main", "test", "content", rowid, false).unwrap(); + blob.write(b"Clob").unwrap(); + let err = blob.write(b"5678901"); + //writeln!(io::stderr(), "{:?}", err); + assert!(err.is_err()); + + assert!(blob.reopen(rowid).is_ok()); + assert!(blob.close().is_ok()); + + blob = db.blob_open("main", "test", "content", rowid, true).unwrap(); + let mut bytes = [0u8; 5]; + assert_eq!(5, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(5, blob.read(&mut bytes[..]).unwrap()); + assert_eq!(0, blob.read(&mut bytes[..]).unwrap()); + + assert!(blob.reopen(rowid).is_ok()); + blob.seek(super::SeekFrom::Start(0)); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f98780b..ef0b291 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,6 +79,7 @@ pub use transaction::{SqliteTransactionBehavior, pub mod types; mod transaction; #[cfg(feature = "load_extension")] mod load_extension_guard; +#[cfg(feature = "blob")] pub mod blob; /// A typedef of the result returned by many methods. pub type SqliteResult = Result; @@ -664,7 +665,7 @@ impl<'conn> SqliteStatement<'conn> { } /// Executes the prepared statement and maps a function over the resulting - /// rows. + /// rows. /// /// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility /// for accessing stale rows. From d1f5ce24355f7fc124790e8cf7a2a7d24efa4cee Mon Sep 17 00:00:00 2001 From: Gwenael Treguier Date: Sat, 8 Aug 2015 09:39:35 +0200 Subject: [PATCH 2/5] Check when buffer is too long. --- src/blob.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/blob.rs b/src/blob.rs index 747c729..0d9691a 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -45,6 +45,12 @@ impl<'conn> SqliteBlob<'conn> { } pub fn read(&mut self, buf: &mut [u8]) -> SqliteResult { + if buf.len() > ::std::i32::MAX as usize { + return Err(SqliteError { + code: ffi::SQLITE_TOOBIG, + message: "buffer too long".to_string() + }); + } let mut n = buf.len() as i32; let size = self.size(); if self.pos + n > size { @@ -61,6 +67,12 @@ impl<'conn> SqliteBlob<'conn> { } pub fn write(&mut self, buf: &[u8]) -> SqliteResult { + if buf.len() > ::std::i32::MAX as usize { + return Err(SqliteError { + code: ffi::SQLITE_TOOBIG, + message: "buffer too long".to_string() + }); + } let n = buf.len() as i32; let size = self.size(); if self.pos + n > size { From 3b830b4bcec8c46445e256a069195e160f5f3b55 Mon Sep 17 00:00:00 2001 From: Gwenael Treguier Date: Sat, 8 Aug 2015 16:11:31 +0200 Subject: [PATCH 3/5] Add documentation --- src/blob.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/blob.rs b/src/blob.rs index 0d9691a..c404e2a 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -1,15 +1,18 @@ +//! incremental BLOB I/O use std::mem; use std::ptr; use super::ffi; use {SqliteError, SqliteResult, SqliteConnection}; +/// Handle to an open BLOB pub struct SqliteBlob<'conn> { conn: &'conn SqliteConnection, blob: *mut ffi::sqlite3_blob, pos: i32, } +/// Enumeration of possible methods to seek within an BLOB. pub enum SeekFrom { Start(i32), End(i32), @@ -17,6 +20,7 @@ pub enum SeekFrom { } impl SqliteConnection { + /// Open a handle to the BLOB located in `row`, `column`, `table` in database `db` ('main', 'temp', ...) pub fn blob_open<'a>(&'a self, db: &str, table: &str, column: &str, row: i64, read_only: bool) -> SqliteResult> { let mut c = self.db.borrow_mut(); let mut blob = ptr::null_mut(); @@ -31,6 +35,7 @@ impl SqliteConnection { } impl<'conn> SqliteBlob<'conn> { + /// Move a BLOB handle to a new row pub fn reopen(&mut self, row: i64) -> SqliteResult<()> { let rc = unsafe{ ffi::sqlite3_blob_reopen(self.blob, row) }; if rc != ffi::SQLITE_OK { @@ -40,10 +45,12 @@ impl<'conn> SqliteBlob<'conn> { Ok(()) } + /// Return the size in bytes of the BLOB pub fn size(&self) -> i32 { unsafe{ ffi::sqlite3_blob_bytes(self.blob) } } + /// Read data from a BLOB incrementally pub fn read(&mut self, buf: &mut [u8]) -> SqliteResult { if buf.len() > ::std::i32::MAX as usize { return Err(SqliteError { @@ -66,6 +73,9 @@ impl<'conn> SqliteBlob<'conn> { }) } + /// Write data into a BLOB incrementally + /// + /// This function may only modify the contents of the BLOB; it is not possible to increase the size of a BLOB using this API. pub fn write(&mut self, buf: &[u8]) -> SqliteResult { if buf.len() > ::std::i32::MAX as usize { return Err(SqliteError { @@ -88,6 +98,7 @@ impl<'conn> SqliteBlob<'conn> { }) } + /// Seek to an offset, in bytes, in BLOB. pub fn seek(&mut self, pos: SeekFrom) { self.pos = match pos { SeekFrom::Start(offset) => offset, @@ -96,6 +107,7 @@ impl<'conn> SqliteBlob<'conn> { }; } + /// Close a BLOB handle pub fn close(mut self) -> SqliteResult<()> { self.close_() } From 2cf0455f8d646d9feaa6aa7d59f38e181135245b Mon Sep 17 00:00:00 2001 From: Gwenael Treguier Date: Sat, 5 Dec 2015 11:28:33 +0100 Subject: [PATCH 4/5] Add Failure documentation --- src/blob.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/blob.rs b/src/blob.rs index c404e2a..98be5ad 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -21,6 +21,11 @@ pub enum SeekFrom { impl SqliteConnection { /// Open a handle to the BLOB located in `row`, `column`, `table` in database `db` ('main', 'temp', ...) + /// + /// # Failure + /// + /// Will return `Err` if `db`/`table`/`column` cannot be converted to a C-compatible string or if the + /// underlying SQLite BLOB open call fails. pub fn blob_open<'a>(&'a self, db: &str, table: &str, column: &str, row: i64, read_only: bool) -> SqliteResult> { let mut c = self.db.borrow_mut(); let mut blob = ptr::null_mut(); @@ -36,6 +41,10 @@ impl SqliteConnection { impl<'conn> SqliteBlob<'conn> { /// Move a BLOB handle to a new row + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite BLOB reopen call fails. pub fn reopen(&mut self, row: i64) -> SqliteResult<()> { let rc = unsafe{ ffi::sqlite3_blob_reopen(self.blob, row) }; if rc != ffi::SQLITE_OK { @@ -51,6 +60,10 @@ impl<'conn> SqliteBlob<'conn> { } /// Read data from a BLOB incrementally + /// + /// # Failure + /// + /// Will return `Err` if `buf` length > i32 max value or if the underlying SQLite read call fails. pub fn read(&mut self, buf: &mut [u8]) -> SqliteResult { if buf.len() > ::std::i32::MAX as usize { return Err(SqliteError { @@ -76,6 +89,11 @@ impl<'conn> SqliteBlob<'conn> { /// Write data into a BLOB incrementally /// /// This function may only modify the contents of the BLOB; it is not possible to increase the size of a BLOB using this API. + /// + /// # Failure + /// + /// Will return `Err` if `buf` length > i32 max value or if `buf` length + offset > BLOB size + /// or if the underlying SQLite write call fails. pub fn write(&mut self, buf: &[u8]) -> SqliteResult { if buf.len() > ::std::i32::MAX as usize { return Err(SqliteError { @@ -108,6 +126,10 @@ impl<'conn> SqliteBlob<'conn> { } /// Close a BLOB handle + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite close call fails. pub fn close(mut self) -> SqliteResult<()> { self.close_() } From 888dce0d8fcf71ea4c94c5af4897c31b0c630aa2 Mon Sep 17 00:00:00 2001 From: Gwenael Treguier Date: Sun, 13 Dec 2015 11:05:11 +0100 Subject: [PATCH 5/5] Rustfmt --- src/blob.rs | 60 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/src/blob.rs b/src/blob.rs index ebc24fb..30b4c2e 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -26,15 +26,37 @@ impl Connection { /// /// Will return `Err` if `db`/`table`/`column` cannot be converted to a C-compatible string or if the /// underlying SQLite BLOB open call fails. - pub fn blob_open<'a>(&'a self, db: &str, table: &str, column: &str, row: i64, read_only: bool) -> Result> { + pub fn blob_open<'a>(&'a self, + db: &str, + table: &str, + column: &str, + row: i64, + read_only: bool) + -> Result> { let mut c = self.db.borrow_mut(); let mut blob = ptr::null_mut(); let db = try!(super::str_to_cstring(db)); let table = try!(super::str_to_cstring(table)); let column = try!(super::str_to_cstring(column)); - let rc = unsafe{ ffi::sqlite3_blob_open(c.db(), db.as_ptr(), table.as_ptr(), column.as_ptr(), row, if read_only { 0 } else { 1 }, &mut blob) }; + let rc = unsafe { + ffi::sqlite3_blob_open(c.db(), + db.as_ptr(), + table.as_ptr(), + column.as_ptr(), + row, + if read_only { + 0 + } else { + 1 + }, + &mut blob) + }; c.decode_result(rc).map(|_| { - Blob{ conn: self, blob: blob, pos: 0 } + Blob { + conn: self, + blob: blob, + pos: 0, + } }) } } @@ -46,7 +68,7 @@ impl<'conn> Blob<'conn> { /// /// Will return `Err` if the underlying SQLite BLOB reopen call fails. pub fn reopen(&mut self, row: i64) -> Result<()> { - let rc = unsafe{ ffi::sqlite3_blob_reopen(self.blob, row) }; + let rc = unsafe { ffi::sqlite3_blob_reopen(self.blob, row) }; if rc != ffi::SQLITE_OK { return self.conn.decode_result(rc); } @@ -56,7 +78,7 @@ impl<'conn> Blob<'conn> { /// Return the size in bytes of the BLOB pub fn size(&self) -> i32 { - unsafe{ ffi::sqlite3_blob_bytes(self.blob) } + unsafe { ffi::sqlite3_blob_bytes(self.blob) } } /// Read data from a BLOB incrementally @@ -68,7 +90,7 @@ impl<'conn> Blob<'conn> { if buf.len() > ::std::i32::MAX as usize { return Err(Error { code: ffi::SQLITE_TOOBIG, - message: "buffer too long".to_string() + message: "buffer too long".to_string(), }); } let mut n = buf.len() as i32; @@ -79,7 +101,9 @@ impl<'conn> Blob<'conn> { if n <= 0 { return Ok(0); } - let rc = unsafe { ffi::sqlite3_blob_read(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) }; + let rc = unsafe { + ffi::sqlite3_blob_read(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) + }; self.conn.decode_result(rc).map(|_| { self.pos += n; n @@ -98,18 +122,23 @@ impl<'conn> Blob<'conn> { if buf.len() > ::std::i32::MAX as usize { return Err(Error { code: ffi::SQLITE_TOOBIG, - message: "buffer too long".to_string() + message: "buffer too long".to_string(), }); } let n = buf.len() as i32; let size = self.size(); if self.pos + n > size { - return Err(Error{code: ffi::SQLITE_MISUSE, message: format!("pos = {} + n = {} > size = {}", self.pos, n, size)}); + return Err(Error { + code: ffi::SQLITE_MISUSE, + message: format!("pos = {} + n = {} > size = {}", self.pos, n, size), + }); } if n <= 0 { return Ok(0); } - let rc = unsafe { ffi::sqlite3_blob_write(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) }; + let rc = unsafe { + ffi::sqlite3_blob_write(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) + }; self.conn.decode_result(rc).map(|_| { self.pos += n; n @@ -118,10 +147,10 @@ impl<'conn> Blob<'conn> { /// Seek to an offset, in bytes, in BLOB. pub fn seek(&mut self, pos: SeekFrom) { - self.pos = match pos { + self.pos = match pos { SeekFrom::Start(offset) => offset, SeekFrom::Current(offset) => self.pos + offset, - SeekFrom::End(offset) => self.size() + offset + SeekFrom::End(offset) => self.size() + offset, }; } @@ -152,7 +181,8 @@ impl<'conn> Drop for Blob<'conn> { mod test { use Connection; - #[test] + #[test] + #[cfg_attr(rustfmt, rustfmt_skip)] fn test_blob() { let db = Connection::open_in_memory().unwrap(); let sql = "BEGIN; @@ -165,7 +195,7 @@ mod test { let mut blob = db.blob_open("main", "test", "content", rowid, false).unwrap(); blob.write(b"Clob").unwrap(); let err = blob.write(b"5678901"); - //writeln!(io::stderr(), "{:?}", err); + // writeln!(io::stderr(), "{:?}", err); assert!(err.is_err()); assert!(blob.reopen(rowid).is_ok()); @@ -180,4 +210,4 @@ mod test { assert!(blob.reopen(rowid).is_ok()); blob.seek(super::SeekFrom::Start(0)); } -} \ No newline at end of file +}