Crate aykroyd

source ·
Expand description

Zero-overhead ergonomic data access for Rust.

Aykroyd is a micro-ORM focused on developer ergonomics, with an uncompromising commitment to performance. Your database doesn’t have to be kept hidden behind abstraction layers or buried in repetitive boilerplate anymore.

Database queries are represented by a plain Rust struct that implements either Statement or Query (and maybe QueryOne). The traits Statement and Query share two common parent traits:

  • QueryText, which gives access to the text of the query, and
  • ToParams, which we can use to turn the struct into database parameters.

Using these together, a database client can prepare the text of a query and then run it on a database, passing in the required parameters.

In addition, the Query trait has an associated type Row which must implement:

  • FromRow, to be deserialized from database rows.

All of these traits can be derived automatically, so the usual gnarly database access code is reduced to simple struct definitions. These structs logically bind the query text to input parameters and output row types.

The binding is not magic, there is no verification against a database. Query and Statement implementations are an assertion by the developer, one that you would be wise to verify. It is recommended to write a suite of automated tests which can be run against any database tier.

use aykroyd::{FromRow, Query, Statement};

#[derive(Statement)]
#[aykroyd(text = "
    INSERT INTO pets (name, species) VALUES ($1, $2)
")]
struct InsertPet<'a> {
    name: &'a str,
    species: &'a str,
}

#[derive(FromRow)]
struct Pet {
    id: i32,
    name: String,
    species: String,
}

#[derive(Query)]
#[aykroyd(row(Pet), text = "
    SELECT id, name, species FROM pets
")]
struct GetAllPets;

Once you have a Statement or Query in hand, you’ll need a database connection to run it. The driver is a Client, and it could be synchronous or asynchronous, implementing the methods in the client specification;

Aykroyd supports the following database client crates:

DBBackend CrateFeatureSync/AsyncClient
PostgreSQLpostgrespostgresSyncaykroyd::postgres::Client
PostgreSQLtokio-postgrestokio-postgresAsyncaykroyd::tokio_postgres::Client
MySQL/MariaDBmysqlmysqlSyncaykroyd::mysql::Client
SQLiterusqliterusqliteSyncaykroyd::rusqlite::Client

§Examples

Here’s how it might look end-to-end with various clients.

The asynchronous PostgreSQL client, available when compiled with crate feature tokio-postgres.

use tokio_postgres::NoTls;
use aykroyd::tokio_postgres::{connect, Client};

// Connect to the database
let (mut client, conn) =
    connect("host=localhost user=postgres", NoTls).await?;

// As with tokio_postgres, you need to spawn a task for the connection.
tokio::spawn(async move {
    if let Err(e) = conn.await {
        eprintln!("connection error: {e}");
    }
});

// Execute a statement, returning the number of rows modified.
let insert_count = client.execute(&InsertPet {
    name: "Dan",
    species: "Felis asynchronous",
}).await?;
assert_eq!(insert_count, 1);

// Run a query and map the result objects.
let rows = client.query(&GetAllPets).await?;
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].name, "Dan");

The synchronous PostgreSQL client, available when compiled with crate feature postgres.

use postgres::NoTls;
use aykroyd::postgres::Client;

// Connect to the database
let mut client =
    Client::connect("host=localhost user=postgres", NoTls)?;

// Execute a statement, returning the number of rows modified.
let insert_count = client.execute(&InsertPet {
    name: "Dan",
    species: "Felis synchronous",
})?;
assert_eq!(insert_count, 1);

// Run a query and map the result objects.
let rows = client.query(&GetAllPets)?;
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].name, "Dan");

The synchronous MySQL/MariaDB client, available when compiled with crate feature mysql.

use aykroyd::mysql::Client;

// Connect to the database
let mut client =
    Client::new("mysql://user:password@locahost:3307/db_name")?;

// Execute a statement, returning the number of rows modified.
let insert_count = client.execute(&InsertPet {
    name: "Dan",
    species: "Felis maria",
})?;
assert_eq!(insert_count, 1);

// Run a query and map the result objects.
let rows = client.query(&GetAllPets)?;
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].name, "Dan");

The synchronous SQLite client, available when compiled with crate feature rusqlite.

use aykroyd::rusqlite::Client;

// Connect to the database
let mut client = Client::open("./my_db.db3")?;

// Execute a statement, returning the number of rows modified.
let insert_count = client.execute(&InsertPet {
    name: "Dan",
    species: "Felis localis",
})?;
assert_eq!(insert_count, 1);

// Run a query and map the result objects.
let rows = client.query(&GetAllPets)?;
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].name, "Dan");

Re-exports§

Modules§

  • Traits that represent database clients.
  • Query combinators.
  • Error handling.
  • mysqlmysql
    MySQL bindings.
  • postgrespostgres
    A synchronous client for PostgreSQL.
  • Traits to define database queries, and their derive macros.
  • Traits and structs for handling result rows.
  • rusqliterusqlite
    Sqlite bindings.
  • tokio_postgrestokio-postgres
    An asynchronous, pipelined, PostgreSQL client.

Macros§

Traits§

  • A type that can be produced from a database’s result row.
  • A database query that returns zero or more result rows.
  • A marker trait for a query that returns at most one row.
  • A database statement which returns no results.

Derive Macros§

  • FromRowderive
    Derive macro available if aykroyd is built with features = ["derive"].
  • Queryderive
    Derive macro available if aykroyd is built with features = ["derive"].
  • QueryOnederive
    Derive macro available if aykroyd is built with features = ["derive"].
  • Statementderive
    Derive macro available if aykroyd is built with features = ["derive"].