blob
only.Expand description
Incremental BLOB I/O.
Note that SQLite does not provide API-level access to change the size of a BLOB; that must be performed through SQL statements.
There are two choices for how to perform IO on a Blob
.
-
The implementations it provides of the
std::io::Read
,std::io::Write
, andstd::io::Seek
traits. -
A positional IO API, e.g.
Blob::read_at
,Blob::write_at
and similar.
Documenting these in order:
1. std::io
trait implementations.
Blob
conforms to std::io::Read
, std::io::Write
, and std::io::Seek
,
so it plays nicely with other types that build on these (such as
std::io::BufReader
and std::io::BufWriter
). However, you must be careful
with the size of the blob. For example, when using a BufWriter
, the
BufWriter
will accept more data than the Blob
will allow, so make sure
to call flush
and check for errors. (See the unit tests in this module for
an example.)
2. Positional IO
Blob
s also offer a pread
/ pwrite
-style positional IO api in the form
of Blob::read_at
, Blob::write_at
, Blob::raw_read_at
,
Blob::read_at_exact
, and Blob::raw_read_at_exact
.
These APIs all take the position to read from or write to from as a
parameter, instead of using an internal pos
value.
Positional IO Read Variants
For the read
functions, there are several functions provided:
These can be divided along two axes: raw/not raw, and exact/inexact:
-
Raw/not raw refers to the type of the destination buffer. The raw functions take a
&mut [MaybeUninit<u8>]
as the destination buffer, where the “normal” functions take a&mut [u8]
.Using
MaybeUninit
here can be more efficient in some cases, but is often inconvenient, so both are provided. -
Exact/inexact refers to to whether or not the entire buffer must be filled in order for the call to be considered a success.
The “exact” functions require the provided buffer be entirely filled, or they return an error, whereas the “inexact” functions read as much out of the blob as is available, and return how much they were able to read.
The inexact functions are preferable if you do not know the size of the blob already, and the exact functions are preferable if you do.
Comparison to using the std::io
traits:
In general, the positional methods offer the following Pro/Cons compared to
using the implementation std::io::{Read, Write, Seek}
we provide for
Blob
:
-
(Pro) There is no need to first seek to a position in order to perform IO on it as the position is a parameter.
-
(Pro)
Blob
’s positional read functions don’t mutate the blob in any way, and take&self
. No&mut
access required. -
(Pro) Positional IO functions return
Err(rusqlite::Error)
on failure, rather thanErr(std::io::Error)
. Returningrusqlite::Error
is more accurate and convenient.Note that for the
std::io
API, no data is lost however, and it can be recovered withio_err.downcast::<rusqlite::Error>()
(this can be easy to forget, though). -
(Pro, for now). A
raw
version of the read API exists which can allow reading into a&mut [MaybeUninit<u8>]
buffer, which avoids a potential costly initialization step. (However,std::io
traits will certainly gain this someday, which is why this is only a “Pro, for now”). -
(Con) The set of functions is more bare-bones than what is offered in
std::io
, which has a number of adapters, handy algorithms, further traits. -
(Con) No meaningful interoperability with other crates, so if you need that you must use
std::io
.
To generalize: the std::io
traits are useful because they conform to a
standard interface that a lot of code knows how to handle, however that
interface is not a perfect fit for Blob
, so another small set of
functions is provided as well.
Example (std::io
)
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE test_table (content BLOB);")?;
// Insert a BLOB into the `content` column of `test_table`. Note that the Blob
// I/O API provides no way of inserting or resizing BLOBs in the DB -- this
// must be done via SQL.
db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?;
// Get the row id off the BLOB we just inserted.
let rowid = db.last_insert_rowid();
// Open the BLOB we just inserted for IO.
let mut blob = db.blob_open(DatabaseName::Main, "test_table", "content", rowid, false)?;
// Write some data into the blob. Make sure to test that the number of bytes
// written matches what you expect; if you try to write too much, the data
// will be truncated to the size of the BLOB.
let bytes_written = blob.write(b"01234567")?;
assert_eq!(bytes_written, 8);
// Move back to the start and read into a local buffer.
// Same guidance - make sure you check the number of bytes read!
blob.seek(SeekFrom::Start(0))?;
let mut buf = [0u8; 20];
let bytes_read = blob.read(&mut buf[..])?;
assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10
// Insert another BLOB, this time using a parameter passed in from
// rust (potentially with a dynamic size).
db.execute(
"INSERT INTO test_table (content) VALUES (?1)",
[ZeroBlob(64)],
)?;
// given a new row ID, we can reopen the blob on that row
let rowid = db.last_insert_rowid();
blob.reopen(rowid)?;
// Just check that the size is right.
assert_eq!(blob.len(), 64);
Example (Positional)
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE test_table (content BLOB);")?;
// Insert a blob into the `content` column of `test_table`. Note that the Blob
// I/O API provides no way of inserting or resizing blobs in the DB -- this
// must be done via SQL.
db.execute("INSERT INTO test_table (content) VALUES (ZEROBLOB(10))", [])?;
// Get the row id off the blob we just inserted.
let rowid = db.last_insert_rowid();
// Open the blob we just inserted for IO.
let mut blob = db.blob_open(DatabaseName::Main, "test_table", "content", rowid, false)?;
// Write some data into the blob.
blob.write_at(b"ABCDEF", 2)?;
// Read the whole blob into a local buffer.
let mut buf = [0u8; 10];
blob.read_at_exact(&mut buf, 0)?;
assert_eq!(&buf, b"\0\0ABCDEF\0\0");
// Insert another blob, this time using a parameter passed in from
// rust (potentially with a dynamic size).
db.execute(
"INSERT INTO test_table (content) VALUES (?1)",
[ZeroBlob(64)],
)?;
// given a new row ID, we can reopen the blob on that row
let rowid = db.last_insert_rowid();
blob.reopen(rowid)?;
assert_eq!(blob.len(), 64);
Structs
- Handle to an open BLOB. See
rusqlite::blob
documentation for in-depth discussion. - BLOB of length N that is filled with zeroes.