Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SQLx

sqlx is an async SQL toolkit for Rust. It supports connection pools, prepared queries, transactions, and compile-time checked queries for supported databases. To use sqlx in your application, add the following crates to your project:

cargo add tokio --features full
cargo add sqlx --features sqlite,postgres,runtime-tokio

Connect to SQLite and query data

sqlx-badge cat-database-badge

Use SqlitePoolOptions::connect to open an in-memory SQLite database backed by a connection pool that only allows a maximum of 1 concurrent connections. Create a table and insert rows with sqlx::query and Executor::execute, then load the results with Query::fetch_all. Each row is read by calling Row::try_get for the selected columns.

use sqlx::{Error, Row, sqlite::SqlitePoolOptions};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let pool = SqlitePoolOptions::new()
        .max_connections(1)
        .connect("sqlite::memory:")
        .await?;

    sqlx::query(
        "CREATE TABLE IF NOT EXISTS location (
            id                  INTEGER PRIMARY KEY,
            name                TEXT NOT NULL,
            latitude            REAL NOT NULL,
            longitude           REAL NOT NULL
            )
        ",
    )
    .execute(&pool)
    .await?;

    sqlx::query("INSERT INTO location (name, latitude, longitude) VALUES (?1, ?2, ?3)")
        .bind("Null Island")
        .bind(0.00)
        .bind(0.00)
        .execute(&pool)
        .await?;

    sqlx::query("INSERT INTO location (name, latitude, longitude) VALUES (?1, ?2, ?3)")
        .bind("Titanic")
        .bind(41.726931)
        .bind(-49.948253)
        .execute(&pool)
        .await?;

    let locations = sqlx::query("SELECT name, latitude, longitude FROM location")
        .fetch_all(&pool)
        .await?;

    for row in locations {
        let name: String = row.try_get("name")?;
        let lat: f64 = row.try_get("latitude")?;
        let longitude: f64 = row.try_get("longitude")?;
        println!("{name} is at coordinates latitude: {lat} and longitude: {longitude}");
    }
    Ok(())
}

Connect to Postgres and query typed rows

sqlx-badge cat-database-badge

Use PgPool::connect to open a connection pool for an existing locations Postgres database. Query values in the database and map query results into a typed Location struct with sqlx::query_as and FromRow, then load all rows with QueryAs::fetch_all.

use sqlx::{Error, postgres::PgPool};

#[derive(sqlx::FromRow)]
struct Location {
    name: String,
    latitude: f64,
    longitude: f64,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let pool =
        PgPool::connect("postgresql://postgres:postgres@localhost/locations").await?;

    let locations = sqlx::query_as::<_, Location>("SELECT name, latitude, longitude FROM location")
        .fetch_all(&pool)
        .await?;

    for location in locations {
        println!(
            "{} is at coordinates latitude: {} and longitude: {}",
            location.name, location.latitude, location.longitude
        );
    }

    Ok(())
}

Compile-time checked queries

sqlx-badge cat-database-badge

Use Connection::connect to open a Postgres connection and sqlx::query_as! to validate the SQL query against the database schema at compile time. The macro maps the selected columns into a Location struct and fetches a single row using Query::fetch_one.

use sqlx::{Connection, Error, postgres::PgConnection};

struct Location {
    name: String,
    latitude: f64,
    longitude: f64,
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let mut conn =
        PgConnection::connect("postgresql://postgres:postgres@localhost/locations").await?;

    let location = sqlx::query_as!(
        Location,
        "SELECT  name, latitude, longitude FROM location where id = $1",
        1i32
    )
    .fetch_one(&mut conn)
    .await?;

    println!(
        "{} is at coordinates latitude: {} and longitude: {}",
        location.name, location.latitude, location.longitude
    );

    Ok(())
}

Set DATABASE_URL to a Postgres database that already contains the location table before building. This may be achieved by either adding a DATABASE_URL variable to a .env file or running:

export DATABASE_URL=<your_database_url>

Transactions with SQLx

sqlx-badge cat-database-badge

Use PgPoolOptions::connect to open a connection pool for an existing locations Postgres database. Start a transaction with Pool::begin, run two INSERT statements with sqlx::query, and save both rows together with Transaction::commit.

use sqlx::{Error, postgres::PgPoolOptions};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let pool = PgPoolOptions::new()
        .max_connections(3)
        .connect("postgresql://postgres:postgres@localhost/locations")
        .await?;

    let mut tx = pool.begin().await?;

    sqlx::query("INSERT INTO location (name, latitude, longitude) VALUES ($1, $2, $3)")
        .bind("Area 51")
        .bind(37.2350)
        .bind(-115.8111)
        .execute(&mut *tx)
        .await?;

    sqlx::query("INSERT INTO location (name, latitude, longitude) VALUES ($1, $2, $3)")
        .bind("Point Nemo")
        .bind(-48.8767)
        .bind(-123.3933)
        .execute(&mut *tx)
        .await?;

    tx.commit().await?;
    Ok(())
}