sqll 0.2.4

Efficient interface to SQLite that doesn't get in your way
Documentation
sqll-0.2.4 has been yanked.

sqll

Efficient interface interface to SQLite that doesn't get in your way.

Usage

The two primary methods to interact with an SQLite database through this crate is through execute and prepare.

execute is used for batch statements, and allows for multiple queries to be specified. prepare only allows for a single statement to be specified, but in turn permits reading rows and binding query parameters.

Special consideration needs to be taken about the thread safety of connections. You can read more about that in the Connection documentation.

You can find simple examples of this below.

Examples

  • examples/persons.rs - A simple table with users, a primary key, inserting and querying.
  • examples/axum.rs - Create an in-memory database connection and serve it using axum. This showcases how do properly handle external synchronization for the best performance.

Features

  • std - Enable usage of the Rust standard library. Enabled by default.
  • alloc - Enable usage of the Rust alloc library. This is required and is enabled by default. Disabling this option will currently cause a compile error.
  • bundled - Use a bundled version of sqlite. The bundle is provided by the sqll-sys crate and the sqlite version used is part of the build metadata of that crate.
  • threadsafe - Enable usage of sqlite with the threadsafe option set. We assume any system level libraries have this build option enabled, if this is disabled the bundled feature has to be enabled. If threadsafe is disabled, Connection and Statement does not implement Send. But it is also important to understand that if this option is not set, sqlite may not be used by multiple threads at all even if threads have distinct connections. To disable mutexes instead which allows for efficient one connection per thread the OpenOptions::no_mutex option should be used instead.

Example

Open an in-memory connection, create a table, and insert some rows:

use sqll::Connection;

let c = Connection::open_memory()?;

c.execute(r#"
    CREATE TABLE users (name TEXT, age INTEGER);

    INSERT INTO users VALUES ('Alice', 42);
    INSERT INTO users VALUES ('Bob', 69);
"#)?;

Prepared Statements

Correct handling of prepared statements are crucial to get good performance out of sqlite. They contain all the state associated with a query and are expensive to construct so they should be re-used.

Using a Prepare::PERSISTENT prepared statement to perform multiple queries:

use sqll::{Connection, Prepare};

let c = Connection::open_memory()?;

c.execute(r#"
    CREATE TABLE users (name TEXT, age INTEGER);

    INSERT INTO users VALUES ('Alice', 42);
    INSERT INTO users VALUES ('Bob', 69);
"#)?;

let mut stmt = c.prepare_with("SELECT * FROM users WHERE age > ?", Prepare::PERSISTENT)?;

let mut results = Vec::new();

for age in [40, 50] {
    stmt.reset()?;
    stmt.bind(1, age)?;

    while let Some(row) = stmt.next()? {
        results.push((row.get::<String>(0)?, row.get::<i64>(1)?));
    }
}

let expected = vec![
    (String::from("Alice"), 42),
    (String::from("Bob"), 69),
    (String::from("Bob"), 69),
];

assert_eq!(results, expected);

Why do we need another sqlite interface?

For other low-level crates, it is difficult to set up and use prepared statements, They are mostly implemented in a manner which requires the caller to borrow the connection in use.

This library implements database objects through the v2 API which ensures that the database remains alive for as long as objects associated with it are alive. This is implemented in the SQLite library itself.

Prepared statements can be expensive to create and should be cached and re-used to achieve the best performance. This is something that crates like rusqlite implements, but can easily be done manually by simply storing the Statement object directly. Statements can also benefit from using the Prepare::PERSISTENT option which this library supports through prepare_with.

This library is designed to the extent possible to avoid intermediary allocations. For example calling execute doesn't allocate externally of the sqlite3 bindings or we require that c-strings are used when SQLite itself doesn't provide an API for using Rust strings directly.

License

This is a rewrite of the sqlite crate, and components used from there have been copied under the MIT license.