Besu is a typesafe, async, database-agnostic SQL query builder for Rust. The query you write is the SQL that runs: there is no query planner, no identity map and no hidden second statement.

#[derive(Table)]
#[besu(table = "users")]
pub struct User {
    #[besu(primary_key, auto_increment)]
    pub id: i64,
    pub email: String,
    pub name: String,
    pub age: i32,
    pub bio: Option<String>,
}

let adults: Vec<User> = db
    .select(users)
    .filter(users.age.gte(18))
    .order_by(users.name.asc())
    .limit(50)
    .all()
    .await?;
SELECT "users"."id", "users"."email", "users"."name", "users"."age", "users"."bio"
FROM "users" WHERE ("users"."age" >= $1) ORDER BY "users"."name" ASC LIMIT $2

What it checks

The compiler is the first reviewer. These are errors, not runtime failures:

  • a column that does not exist, or a table that is not in the query;
  • comparing a text column with an integer;
  • inserting into a NOT NULL column that was not provided;
  • selecting a NOT NULL column through a LEFT JOIN and treating it as Option;
  • UPDATE or DELETE with no WHERE clause, unless you say all_rows();
  • the wrong number of arguments to sql!.

What it does not do

  • No lazy loading. A relation is a second statement you can read, never an invisible one.
  • No implicit joins. If you want a join, you write one.
  • No query cache, no identity map, no change tracking. A query is a value; running it runs it.
  • No schema inference at runtime. The schema is a type, generated by #[derive(Table)], and it is the same type the migration tooling uses.

The crates

CrateRole
besu-coreDialect, Driver, Executor, Sql, Value, row decoding. No query API and no database, so SQL can be built with nothing attached
besuThe builders and expression DSL: select, insert, update, delete, joins, upserts, aggregates, transactions, raw SQL
besu-schemaOne serialisable schema IR shared by migrations, introspection and DDL generation
besu-macros#[derive(Table)]
besu-sqlite · besu-postgres · besu-mysqlDialect, driver, DDL and introspection for each database
besu-anyAnyDatabase: one type over all three backends, chosen from a connection string
besu-clibesu push, besu pull, besu generate, besu migrate
besu-studioA development-only schema and data browser

The layers are independent. besu-core knows nothing about the query API, besu knows nothing about sqlx, and each driver knows nothing about the others. Take the whole stack, or take the layer you need.

Driverless SQL

A dialect can render a query with no connection, which is what makes the SQL testable:

let db = besu::dry::<besu_postgres::Postgres>();

let sql = db.select(users).filter(users.age.gt(18)).to_sql()?;
assert!(sql.sql().starts_with(
    r#"SELECT "users"."id", "users"."email", … WHERE ("users"."age" > $1)"#
));
// PostgreSQL also carries a trailing parameter-type comment, the prepared-statement cache
// key: `\n/* besu:i */`.
assert!(sql.sql().ends_with("/* besu:i */"));

Awaiting a query built this way returns an error rather than panicking, so a driverless build can be exercised end to end.

Where to go next