Remove "semi-safe" term.

Based on comments from [this reddit
thread](http://www.reddit.com/r/rust/comments/2lapta/rusqlite_ergonomic_semisafe_bindings_to_sqlite/).
This commit is contained in:
John Gallagher 2014-11-10 12:56:32 -05:00
parent 75dd753fbb
commit 8fa377b36c
2 changed files with 14 additions and 48 deletions

View File

@ -2,7 +2,7 @@
[![Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/rusqlite) [![Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/rusqlite)
Rusqlite is an ergonomic, semi-safe wrapper for using SQLite from Rust. It attempts to expose Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full
[API documentation](http://www.rust-ci.org/jgallagher/rusqlite/doc/rusqlite/). [API documentation](http://www.rust-ci.org/jgallagher/rusqlite/doc/rusqlite/).
@ -53,54 +53,20 @@ fn main() {
} }
``` ```
## "Semi-Safe"? ### Design of SqliteRows and SqliteRow
There are two parts of Rusqlite that are not as safe as a proper Rust library should be. Both are
related to the API of SQLite itself. SQLite is a phenomenal piece of software, but its API does
not mesh very well with the ownership semantics of Rust.
### Semi-Safe: SqliteConnection
The first form of "semi-safeness" is the `SqliteConnection` handle itself. The underlying C handle,
[sqlite3](https://www.sqlite.org/c3ref/sqlite3.html), has at least two pieces of internal state
that can be affected across multiple SQLite calls: the last insertion ID (retrieved via
[sqlite3_last_insert_rowid()](https://www.sqlite.org/c3ref/last_insert_rowid.html)) and a detailed
error message for the most recent error (retrieved via
[sqlite3_errmsg()](https://www.sqlite.org/c3ref/errcode.html)). As mentioned by the documentation
for both functions, this internal state is inherently not thread safe. Even if SQLite is using
locks to provide thread safety (which is the default), multiple threads accessing the same
connection can cause undefined behavior with these functions (e.g., if both threads insert a row
and then both threads try to get the last insertion row ID, both threads will get the same row ID
of whichever insertion happened second).
This could be addressed in Rust by making any calls that might affect the internal state of the
connection borrow the connection mutably until they complete. This is the tactic taken by
[rust-sqlite3](https://github.com/dckc/rust-sqlite3), and it is the most correct option from a
Rust point of view. However, it causes problems with things like transactions. Therefore,
Rusqlite's `SqliteConnection` uses a [RefCell](http://doc.rust-lang.org/std/cell/) internally
to allow the connection to be shared even though it is mutable.
The practical implication of this is that `SqliteConnection` is *not* thread-safe, and must not be
used from multiple threads at the same time, but you must enforce this with little-to-no help from
the type system. If you use a single connection from multiple threads, you may encounter a panic
(from the underlying RefCell), or you may introduce data races (as described above with the last
insertion ID or error message).
### Semi-Safe: SqliteRows and SqliteRow
To retrieve the result rows from a query, SQLite requires you to call To retrieve the result rows from a query, SQLite requires you to call
[sqlite3_step()](https://www.sqlite.org/c3ref/step.html) on a prepared statement. You can only [sqlite3_step()](https://www.sqlite.org/c3ref/step.html) on a prepared statement. You can only
retrieve the values of the "current" row. From the Rust point of view, this means that each row retrieve the values of the "current" row. From the Rust point of view, this means that each row
is only valid until the next row is fetched. Again, is only valid until the next row is fetched. [rust-sqlite3](https://github.com/dckc/rust-sqlite3)
[rust-sqlite3](https://github.com/dckc/rust-sqlite3) solves this the correct way with lifetimes. solves this the correct way with lifetimes. However, this means that the result rows do not
However, this means that the result rows do not satisfy the satisfy the [Iterator](http://doc.rust-lang.org/std/iter/trait.Iterator.html) trait, which means
[Iterator](http://doc.rust-lang.org/std/iter/trait.Iterator.html) trait, which means you cannot you cannot (as easily) loop over the rows, or use many of the helpful Iterator methods like `map`
(as easily) loop over the rows, or use many of the helpful Iterator methods like `map` and and `filter`.
`filter`.
Instead, Rusqlite's `SqliteRows` handle does conform to `Iterator`. It performs checks at runtime Instead, Rusqlite's `SqliteRows` handle does conform to `Iterator`. It ensures safety by
to ensure you do not try to retrieve the values of a "stale" row, and will panic if you do so. performing checks at runtime to ensure you do not try to retrieve the values of a "stale" row, and
A specific example that will panic: will panic if you do so. A specific example that will panic:
```rust ```rust
fn bad_function_will_panic(conn: &SqliteConnection) -> SqliteResult<i64> { fn bad_function_will_panic(conn: &SqliteConnection) -> SqliteResult<i64> {

View File

@ -1,4 +1,4 @@
//! Rusqlite is an ergonomic, semi-safe wrapper for using SQLite from Rust. It attempts to expose //! Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
//! an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). //! an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
//! //!
//! ```rust //! ```rust
@ -591,10 +591,10 @@ pub struct SqliteRow<'stmt> {
impl<'stmt> SqliteRow<'stmt> { impl<'stmt> SqliteRow<'stmt> {
/// Get the value of a particular column of the result row. /// Get the value of a particular column of the result row.
/// ///
/// Note that `SqliteRow` falls into the "semi-safe" category of rusqlite. When you are /// Note that `SqliteRow` can panic at runtime if you use it incorrectly. When you are
/// retrieving the rows of a query, a row becomes stale once you have requested the next row, /// retrieving the rows of a query, a row becomes stale once you have requested the next row,
/// and the values can no longer be retrieved. In general (when using a loop over the rows, for /// and the values can no longer be retrieved. In general (when using looping over the rows,
/// example) this isn't an issue, but it means you cannot do something like this: /// for example) this isn't an issue, but it means you cannot do something like this:
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{SqliteConnection, SqliteResult}; /// # use rusqlite::{SqliteConnection, SqliteResult};