Rust

sqltgen generates async Rust code using sqlx. Model structs derive sqlx::FromRow. Query functions are async fn returning Result<…, sqlx::Error>, generic over sqlx::Executor so they accept a pool or a transaction.

Configuration

"rust": {
  "out": "src/db",
  "package": ""
}
FieldDescription
outOutput directory (relative to the project root).
packageUnused for Rust — set to "".
list_params"native" (default) or "dynamic".

What is generated

src/db/
  mod.rs              — re-exports all modules
  author.rs           — Author struct with sqlx::FromRow
  book.rs             — Book struct
  _sqltgen.rs         — shared helper trait (SqlxAdapter)
  queries.rs          — async query functions  (single-file form)
  users.rs            — per-group query files  (grouped form)

Model structs

#![allow(unused)]
fn main() {
// src/db/author.rs
#[derive(Debug, sqlx::FromRow)]
pub struct Author {
    pub id: i64,
    pub name: String,
    pub bio: Option<String>,
    pub birth_year: Option<i32>,
}
}
  • Non-null columns → bare type (i64, String, bool, …).
  • Nullable columns → Option<T>.
  • snake_case SQL names → snake_case Rust field names (unchanged).

Query functions

Each function is generic over sqlx::Executor, so the same function runs against a connection pool, a pooled connection, or a transaction. The Database bound is fixed to the configured engine (sqlx::Postgres / sqlx::Sqlite / sqlx::MySql).

#![allow(unused)]
fn main() {
// src/db/queries.rs
use super::author::Author;

pub async fn get_author<'e, E>(executor: E, id: i64)
        -> Result<Option<Author>, sqlx::Error>
where
    E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{
    sqlx::query_as::<_, Author>(SQL_GET_AUTHOR)
        .bind(id)
        .fetch_optional(executor)
        .await
}

pub async fn list_authors<'e, E>(executor: E)
        -> Result<Vec<Author>, sqlx::Error>
where
    E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{
    sqlx::query_as::<_, Author>(SQL_LIST_AUTHORS)
        .fetch_all(executor)
        .await
}

pub async fn delete_author<'e, E>(executor: E, id: i64)
        -> Result<(), sqlx::Error>
where
    E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{
    sqlx::query(SQL_DELETE_AUTHOR)
        .bind(id)
        .execute(executor)
        .await
        .map(|_| ())
}
}

_sqltgen.rs helper

The generated _sqltgen.rs file provides a shared SqlxAdapter trait that abstracts over the three pool types. This is an implementation detail — you do not need to call it directly.

mod.rs

#![allow(unused)]
fn main() {
pub mod author;
pub mod book;
pub mod queries;
mod _sqltgen;
}

Lints and formatting

Clippy — the generated crate root carries crate-wide allows that propagate to the submodules, so the generated module passes cargo clippy -- -D warnings with no action on your part:

#![allow(unused)]
#![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::module_inception)]
fn main() {
}

rustfmt — a formatter's output depends on your project's rustfmt.toml, which the generator cannot know, so sqltgen does not shape the generated code to any particular profile. Keep it out of cargo fmt --check by applying #[rustfmt::skip] to the mod declaration where you include the generated module:

#![allow(unused)]
fn main() {
#[rustfmt::skip]
pub mod db; // wherever you pull in the generated output directory
}

#[rustfmt::skip] on the mod declaration exempts the whole generated tree (the module file and all its submodules) from cargo fmt on stable Rust, regardless of your rustfmt configuration. (The ignore option in rustfmt.toml is nightly-only, so this attribute is the portable way to do it.)

Wiring up

Cargo.toml

[dependencies]
sqlx = { version = "0.8", features = [
    "runtime-tokio",
    "postgres",          # or "sqlite" / "mysql"
    "time",              # for Date, Time, Timestamp, TimestampTz
    "uuid",              # for UUID
    "rust_decimal",      # for NUMERIC / DECIMAL
] }
tokio = { version = "1", features = ["full"] }

Using the pool directly

mod db;
use db::queries;

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

    let author = queries::get_author(&pool, 1).await?;
    let all    = queries::list_authors(&pool).await?;

    Ok(())
}

&pool satisfies the Executor bound, so the call sites are unchanged from a concrete-pool API.

Inside a transaction

Because the functions are generic over Executor, pass &mut *tx to run several generated operations in one transaction and commit them atomically:

#![allow(unused)]
fn main() {
let mut tx = pool.begin().await?;
queries::create_sale(&mut *tx, customer_id).await?;
queries::add_sale_item(&mut *tx, book_id, qty, price).await?;
tx.commit().await?;   // both statements commit together, or neither
}

Database type per dialect

The Database bound on each function's Executor is fixed to the configured engine:

DialectPool typeDatabase bound
PostgreSQLsqlx::PgPoolsqlx::Postgres
SQLitesqlx::SqlitePoolsqlx::Sqlite
MySQLsqlx::MySqlPoolsqlx::MySql

Inline row types

JOIN queries or partial RETURNING queries produce additional structs in the query file:

#![allow(unused)]
fn main() {
#[derive(Debug, sqlx::FromRow)]
pub struct ListBooksWithAuthorRow {
    pub id: i64,
    pub title: String,
    pub genre: String,
    pub price: rust_decimal::Decimal,
    pub author_name: String,
    pub author_bio: Option<String>,
}

pub async fn list_books_with_author<'e, E>(executor: E)
        -> Result<Vec<ListBooksWithAuthorRow>, sqlx::Error>
where
    E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{ … }
}

List parameters

For PostgreSQL, list parameters use = ANY($1) with a slice bind:

#![allow(unused)]
fn main() {
// generated
pub async fn get_books_by_ids<'e, E>(executor: E, ids: &[i64])
        -> Result<Vec<Book>, sqlx::Error>
where
    E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{
    sqlx::query_as::<_, Book>(SQL_GET_BOOKS_BY_IDS)
        .bind(ids)
        .fetch_all(executor)
        .await
}
}

For SQLite and MySQL, the native strategy uses json_each / JSON_TABLE with a JSON-serialized string:

#![allow(unused)]
fn main() {
pub async fn get_books_by_ids<'e, E>(executor: E, ids: &[i64])
        -> Result<Vec<Book>, sqlx::Error>
where
    E: sqlx::Executor<'e, Database = sqlx::Sqlite>,
{
    let ids_json = serde_json::to_string(ids).unwrap();
    sqlx::query_as::<_, Book>(SQL_GET_BOOKS_BY_IDS)
        .bind(ids_json)
        .fetch_all(executor)
        .await
}
}

Naming conventions

SQLRust
GetAuthorget_author
ListBooksWithAuthorlist_books_with_author
birth_year columnbirth_year field
Author tableAuthor struct