diff --git a/Cargo.toml b/Cargo.toml
index c5114c0..38200cb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ name = "rusqlite"
 [features]
 load_extension = ["libsqlite3-sys/load_extension"]
 backup = []
+blob = []
 functions = []
 trace = []
 
diff --git a/src/blob.rs b/src/blob.rs
new file mode 100644
index 0000000..30b4c2e
--- /dev/null
+++ b/src/blob.rs
@@ -0,0 +1,213 @@
+//! incremental BLOB I/O
+use std::mem;
+use std::ptr;
+
+use super::ffi;
+use {Error, Result, Connection};
+
+/// Handle to an open BLOB
+pub struct Blob<'conn> {
+    conn: &'conn Connection,
+    blob: *mut ffi::sqlite3_blob,
+    pos: i32,
+}
+
+/// Enumeration of possible methods to seek within an BLOB.
+pub enum SeekFrom {
+    Start(i32),
+    End(i32),
+    Current(i32),
+}
+
+impl Connection {
+    /// 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)
+                         -> Result<Blob<'a>> {
+        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(|_| {
+            Blob {
+                conn: self,
+                blob: blob,
+                pos: 0,
+            }
+        })
+    }
+}
+
+impl<'conn> Blob<'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) -> Result<()> {
+        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(())
+    }
+
+    /// 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
+    ///
+    /// # 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]) -> Result<i32> {
+        if buf.len() > ::std::i32::MAX as usize {
+            return Err(Error {
+                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 {
+            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
+        })
+    }
+
+    /// 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]) -> Result<i32> {
+        if buf.len() > ::std::i32::MAX as usize {
+            return Err(Error {
+                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 {
+            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)
+        };
+        self.conn.decode_result(rc).map(|_| {
+            self.pos += n;
+            n
+        })
+    }
+
+    /// Seek to an offset, in bytes, in BLOB.
+    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,
+        };
+    }
+
+    /// Close a BLOB handle
+    ///
+    /// # Failure
+    ///
+    /// Will return `Err` if the underlying SQLite close call fails.
+    pub fn close(mut self) -> Result<()> {
+        self.close_()
+    }
+
+    fn close_(&mut self) -> Result<()> {
+        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 Blob<'conn> {
+    fn drop(&mut self) {
+        self.close_();
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use Connection;
+
+    #[test]
+    #[cfg_attr(rustfmt, rustfmt_skip)]
+    fn test_blob() {
+        let db = Connection::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));
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 25d98c9..f0451c2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -86,6 +86,7 @@ mod named_params;
 #[cfg(feature = "trace")]pub mod trace;
 #[cfg(feature = "backup")]pub mod backup;
 #[cfg(feature = "functions")] pub mod functions;
+#[cfg(feature = "blob")] pub mod blob;
 
 /// Old name for `Result`. `SqliteResult` is deprecated.
 pub type SqliteResult<T> = Result<T>;