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.