2015-08-08 22:11:31 +08:00
|
|
|
//! incremental BLOB I/O
|
2015-12-15 02:45:44 +08:00
|
|
|
use std::io;
|
2015-08-02 00:51:02 +08:00
|
|
|
use std::mem;
|
|
|
|
use std::ptr;
|
|
|
|
|
|
|
|
use super::ffi;
|
2015-12-15 02:38:36 +08:00
|
|
|
use {Error, Result, Connection, DatabaseName};
|
2015-08-02 00:51:02 +08:00
|
|
|
|
2015-08-08 22:11:31 +08:00
|
|
|
/// Handle to an open BLOB
|
2015-12-13 17:53:29 +08:00
|
|
|
pub struct Blob<'conn> {
|
|
|
|
conn: &'conn Connection,
|
2015-08-02 00:51:02 +08:00
|
|
|
blob: *mut ffi::sqlite3_blob,
|
|
|
|
pos: i32,
|
|
|
|
}
|
|
|
|
|
2015-12-13 17:53:29 +08:00
|
|
|
impl Connection {
|
2015-12-15 02:38:36 +08:00
|
|
|
/// Open a handle to the BLOB located in `row`, `column`, `table` in database `db`.
|
2015-12-05 18:28:33 +08:00
|
|
|
///
|
|
|
|
/// # 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.
|
2015-12-13 18:05:11 +08:00
|
|
|
pub fn blob_open<'a>(&'a self,
|
2015-12-15 02:38:36 +08:00
|
|
|
db: DatabaseName,
|
2015-12-13 18:05:11 +08:00
|
|
|
table: &str,
|
|
|
|
column: &str,
|
|
|
|
row: i64,
|
|
|
|
read_only: bool)
|
|
|
|
-> Result<Blob<'a>> {
|
2015-08-02 00:51:02 +08:00
|
|
|
let mut c = self.db.borrow_mut();
|
|
|
|
let mut blob = ptr::null_mut();
|
2015-12-15 02:38:36 +08:00
|
|
|
let db = try!(db.to_cstring());
|
2015-08-02 00:51:02 +08:00
|
|
|
let table = try!(super::str_to_cstring(table));
|
|
|
|
let column = try!(super::str_to_cstring(column));
|
2015-12-13 18:05:11 +08:00
|
|
|
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)
|
|
|
|
};
|
2015-08-02 00:51:02 +08:00
|
|
|
c.decode_result(rc).map(|_| {
|
2015-12-13 18:05:11 +08:00
|
|
|
Blob {
|
|
|
|
conn: self,
|
|
|
|
blob: blob,
|
|
|
|
pos: 0,
|
|
|
|
}
|
2015-08-02 00:51:02 +08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-13 17:53:29 +08:00
|
|
|
impl<'conn> Blob<'conn> {
|
2015-08-08 22:11:31 +08:00
|
|
|
/// Move a BLOB handle to a new row
|
2015-12-05 18:28:33 +08:00
|
|
|
///
|
|
|
|
/// # Failure
|
|
|
|
///
|
|
|
|
/// Will return `Err` if the underlying SQLite BLOB reopen call fails.
|
2015-12-13 17:53:29 +08:00
|
|
|
pub fn reopen(&mut self, row: i64) -> Result<()> {
|
2015-12-13 18:05:11 +08:00
|
|
|
let rc = unsafe { ffi::sqlite3_blob_reopen(self.blob, row) };
|
2015-08-02 00:51:02 +08:00
|
|
|
if rc != ffi::SQLITE_OK {
|
|
|
|
return self.conn.decode_result(rc);
|
|
|
|
}
|
|
|
|
self.pos = 0;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2015-08-08 22:11:31 +08:00
|
|
|
/// Return the size in bytes of the BLOB
|
2015-08-02 00:51:02 +08:00
|
|
|
pub fn size(&self) -> i32 {
|
2015-12-13 18:05:11 +08:00
|
|
|
unsafe { ffi::sqlite3_blob_bytes(self.blob) }
|
2015-08-02 00:51:02 +08:00
|
|
|
}
|
|
|
|
|
2015-08-08 22:11:31 +08:00
|
|
|
/// Close a BLOB handle
|
2015-12-05 18:28:33 +08:00
|
|
|
///
|
|
|
|
/// # Failure
|
|
|
|
///
|
|
|
|
/// Will return `Err` if the underlying SQLite close call fails.
|
2015-12-13 17:53:29 +08:00
|
|
|
pub fn close(mut self) -> Result<()> {
|
2015-08-02 00:51:02 +08:00
|
|
|
self.close_()
|
|
|
|
}
|
|
|
|
|
2015-12-13 17:53:29 +08:00
|
|
|
fn close_(&mut self) -> Result<()> {
|
2015-08-02 00:51:02 +08:00
|
|
|
let rc = unsafe { ffi::sqlite3_blob_close(self.blob) };
|
|
|
|
self.blob = ptr::null_mut();
|
|
|
|
self.conn.decode_result(rc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-15 02:45:44 +08:00
|
|
|
impl<'conn> io::Read for Blob<'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.
|
|
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
|
|
if buf.len() > ::std::i32::MAX as usize {
|
2015-12-15 03:13:14 +08:00
|
|
|
return Err(io::Error::new(io::ErrorKind::InvalidInput,
|
|
|
|
Error {
|
|
|
|
code: ffi::SQLITE_TOOBIG,
|
|
|
|
message: "buffer too long".to_string(),
|
|
|
|
}));
|
2015-12-15 02:45:44 +08:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
};
|
2015-12-15 03:13:14 +08:00
|
|
|
self.conn
|
|
|
|
.decode_result(rc)
|
|
|
|
.map(|_| {
|
|
|
|
self.pos += n;
|
|
|
|
n as usize
|
|
|
|
})
|
|
|
|
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
2015-12-15 02:45:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-15 02:50:18 +08:00
|
|
|
impl<'conn> io::Write for Blob<'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.
|
|
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
|
|
if buf.len() > ::std::i32::MAX as usize {
|
2015-12-15 03:13:14 +08:00
|
|
|
return Err(io::Error::new(io::ErrorKind::InvalidInput,
|
|
|
|
Error {
|
|
|
|
code: ffi::SQLITE_TOOBIG,
|
|
|
|
message: "buffer too long".to_string(),
|
|
|
|
}));
|
2015-12-15 02:50:18 +08:00
|
|
|
}
|
|
|
|
let n = buf.len() as i32;
|
|
|
|
let size = self.size();
|
|
|
|
if self.pos + n > size {
|
2015-12-15 03:13:14 +08:00
|
|
|
return Err(io::Error::new(io::ErrorKind::Other,
|
|
|
|
Error {
|
|
|
|
code: ffi::SQLITE_MISUSE,
|
|
|
|
message: format!("pos = {} + n = {} > size = {}",
|
|
|
|
self.pos,
|
|
|
|
n,
|
|
|
|
size),
|
|
|
|
}));
|
2015-12-15 02:50:18 +08:00
|
|
|
}
|
|
|
|
if n <= 0 {
|
|
|
|
return Ok(0);
|
|
|
|
}
|
|
|
|
let rc = unsafe {
|
|
|
|
ffi::sqlite3_blob_write(self.blob, mem::transmute(buf.as_ptr()), n, self.pos)
|
|
|
|
};
|
2015-12-15 03:13:14 +08:00
|
|
|
self.conn
|
|
|
|
.decode_result(rc)
|
|
|
|
.map(|_| {
|
|
|
|
self.pos += n;
|
|
|
|
n as usize
|
|
|
|
})
|
|
|
|
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
2015-12-15 02:50:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn flush(&mut self) -> io::Result<()> {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-15 03:12:11 +08:00
|
|
|
impl<'conn> io::Seek for Blob<'conn> {
|
|
|
|
/// Seek to an offset, in bytes, in BLOB.
|
|
|
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
|
|
|
let pos = match pos {
|
|
|
|
io::SeekFrom::Start(offset) => offset as i64,
|
|
|
|
io::SeekFrom::Current(offset) => self.pos as i64 + offset,
|
|
|
|
io::SeekFrom::End(offset) => self.size() as i64 + offset,
|
|
|
|
};
|
|
|
|
|
|
|
|
if pos < 0 {
|
2015-12-15 03:13:14 +08:00
|
|
|
Err(io::Error::new(io::ErrorKind::InvalidInput,
|
|
|
|
"invalid seek to negative position"))
|
2015-12-15 03:12:11 +08:00
|
|
|
} else if pos > ::std::i32::MAX as i64 {
|
2015-12-15 03:13:14 +08:00
|
|
|
Err(io::Error::new(io::ErrorKind::InvalidInput,
|
|
|
|
"invalid seek to position > i32::MAX"))
|
2015-12-15 03:12:11 +08:00
|
|
|
} else {
|
|
|
|
self.pos = pos as i32;
|
|
|
|
Ok(pos as u64)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-02 00:51:02 +08:00
|
|
|
#[allow(unused_must_use)]
|
2015-12-13 17:53:29 +08:00
|
|
|
impl<'conn> Drop for Blob<'conn> {
|
2015-08-02 00:51:02 +08:00
|
|
|
fn drop(&mut self) {
|
|
|
|
self.close_();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2015-12-15 03:12:11 +08:00
|
|
|
use std::io::{Read, Write, Seek, SeekFrom};
|
2015-12-15 02:38:36 +08:00
|
|
|
use {Connection, DatabaseName};
|
2015-08-02 00:51:02 +08:00
|
|
|
|
2015-12-13 18:05:11 +08:00
|
|
|
#[test]
|
|
|
|
#[cfg_attr(rustfmt, rustfmt_skip)]
|
2015-08-02 00:51:02 +08:00
|
|
|
fn test_blob() {
|
2015-12-13 17:53:29 +08:00
|
|
|
let db = Connection::open_in_memory().unwrap();
|
2015-08-02 00:51:02 +08:00
|
|
|
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();
|
|
|
|
|
2015-12-15 02:38:36 +08:00
|
|
|
let mut blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, false).unwrap();
|
2015-08-02 00:51:02 +08:00
|
|
|
blob.write(b"Clob").unwrap();
|
|
|
|
let err = blob.write(b"5678901");
|
2015-12-13 18:05:11 +08:00
|
|
|
// writeln!(io::stderr(), "{:?}", err);
|
2015-08-02 00:51:02 +08:00
|
|
|
assert!(err.is_err());
|
|
|
|
|
|
|
|
assert!(blob.reopen(rowid).is_ok());
|
|
|
|
assert!(blob.close().is_ok());
|
|
|
|
|
2015-12-15 02:38:36 +08:00
|
|
|
blob = db.blob_open(DatabaseName::Main, "test", "content", rowid, true).unwrap();
|
2015-08-02 00:51:02 +08:00
|
|
|
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());
|
2015-12-15 03:12:11 +08:00
|
|
|
blob.seek(SeekFrom::Start(0));
|
2015-08-02 00:51:02 +08:00
|
|
|
}
|
2015-12-13 18:05:11 +08:00
|
|
|
}
|