Crate rust_query

Source
Expand description

§Type safe SQLite using the Rust type system

The goal of this library is to allow using relational databases (only SQLite right now) using familiar Rust syntax. The library should guarantee that queries and migrations can not fail when they compile. While rust-query goes quite far to achieve this, there are still some exceptions that can cause queries to fail, such as integer overflow.

Writing queries using this library involves:

  • Interact with row/column references as Rust values.
  • Lifetimes to check the scopes of row/column references.
  • Procedural mutation of row sets with methods like filter and join.
  • “Combinators” like optional and aggregate.

Notably writing queries itself does not involve any new syntax or macro, while still being completely type safe. (There are macros to define the schema and to simplify defining composite types to retrieve from queries)

§What it looks like

Define a schema using the syntax of a module with structs:

use rust_query::migration::schema;

#[schema(MySchema)]
pub mod vN {
    // Structs are database tables
    pub struct User {
        // This table has one column with String (sqlite TEXT) type.
        pub name: String,
    }
    pub struct Image {
        pub description: String,
        // This column has a foreign key constraint to the User table
        pub uploaded_by: User,
    }
}

Get proof that we are running on a unique thread:

let mut client = LocalClient::try_new().unwrap();

Initialize a database:

let database = client
    .migrator(Config::open("my_database.sqlite"))
    .expect("database version is before supported versions")
    // migrations go here
    .finish()
    .expect("database version is after supported versions");

Perform a transaction!

let mut txn = client.transaction_mut(&database);
do_stuff_with_database(&mut txn);
// After we are done we commit the changes!
txn.commit();

Insert in the database:

// Lets make a new user 'mike',
let mike = User { name: "mike" };
let mike_id = txn.insert_ok(mike);
// and also insert a dog picture for 'mike'.
let dog_picture = Image {
    description: "dog",
    uploaded_by: mike_id,
};
let _picture_id = txn.insert_ok(dog_picture);

Query from the database:

// Now we want to get all pictures for 'mike'.
let mike_pictures = txn.query(|rows| {
    // Initially there is one empty row.
    // Lets join the pictures table.
    let picture = rows.join(Image);
    // Now lets filter for pictures from mike,
    rows.filter(picture.uploaded_by().eq(mike_id));
    // and finally turn the rows into a vec.
    rows.into_vec(picture.description())
});

println!("{mike_pictures:?}"); // This should print `["dog"]`.

The full example code can be found in insert_and_select.rs

§Examples

For more examples you can look at the examples directory.

§Roadmap

This project is under development and there are some things missing. Below is a checklist of planned features and implemented features. (Implemented features have a checkmark, planned features do not).

Schema:

  • Basic types (integer, real, text, blob, null)
  • Basic foreign keys
  • (Multi column) unique constraints
  • Check constraints
  • Overlapping foreign keys

Statements:

  • Multi row query + single row query (and optional query)
  • Single row insert, update and delete

Expressions:

  • Some basic math, boolean and string operations
  • Aggregate combinator
  • Optional combinator
  • Everything else

Advanced operations:

  • Window
  • Limit

Despite these limitations, I am dogfooding this query builder and using it in my own project: advent-of-wasm.

Modules§

args
Types that are used as closure arguments.
migration
Types to declare schemas and migrations.

Structs§

Database
Database is a proof that the database has been configured.
Expr
This is an expression that can be used in queries.
LocalClient
The primary interface to the database.
Select
Select is used to define what to query from the database for each row.
TableRow
Row reference that can be used in any query in the same transaction.
Transaction
Transaction can be used to query the database.
TransactionMut
Same as Transaction, but allows inserting new rows.
TransactionWeak
This is the weak version of TransactionMut.
UnixEpoch
Use this a value in a query to get the current datetime as a number.
Update
Defines a column update.

Traits§

FromExpr
Trait for values that can be retrieved from the database using one expression.
IntoExpr
Trait for all values that can be used as expressions in queries.
IntoSelect
This trait is implemented by everything that can be retrieved from the database.
IntoSelectExt
IntoSelectExt adds extra methods to values that implement IntoSelect.
Table
This trait is implemented for all table types as generated by the crate::migration::schema macro.

Functions§

aggregate
Perform an aggregate that returns a single result for each of the current rows.
optional
This is a combinator function that allows constructing single row optional queries.

Derive Macros§

FromExpr
Use in combination with #[rust_query(From = Thing)] to specify which tables this struct should implement FromExpr for.
Select
Derive Select to create a new *Select struct.