sqlw - SQL Writer
The goal of this library is to enable fearless raw SQL queries, embracing SQL in an ergonomic & rusty way of doing things. sqlw gives you compile-time SQL query building with schema-safe field references, automatic parameter binding, and seamless integration with popular databases.
Quick Start
Define your schema, write a query, execute it. Here's the full flow:
use ;
// 1. Define your table schema
schema!;
// 2. Write a query with parameter binding
let min_age = 18;
let query = query_qmark!;
assert_eq!;
assert_eq!;
// 3. Map results to a struct
// 4. Execute against a database
let executor = new.await?;
let users: = executor.query_list.await?;
Defining Schemas with schema!
The schema! macro generates a struct with typed column constants. Each entry maps a column name to a Rust field:
use schema;
schema!;
This gives you a Product struct with named fields:
let widget = Product ;
let draft = default; // zero values
And column constants that carry type information into your queries:
// Product::TABLE.desc() -> "products"
// Product::ID.desc() -> "id"
// Product::ID -> Def<Product, Typed<i64>>
You can also define column-only constants (no struct field) by omitting the type:
schema!;
Writing Queries
sqlw provides two query macros that differ only in their placeholder style:
use query_qmark; // ? placeholders
use query_numbered; // $1, $2 placeholders
Variables in {curly braces} are automatically bound:
let name = "Laptop";
let price = 999.99;
let insert = query_qmark!;
assert_eq!;
assert_eq!;
The query macros work with all standard CRUD operations:
// SELECT
let users = query_qmark!;
// UPDATE
let new_email = "alice@example.com";
let update = query_qmark!;
// DELETE
let delete = query_qmark!;
Joins, aggregates, and subqueries work naturally since you're writing raw SQL:
schema!;
let user_id = 42;
let orders = query_qmark!;
Optional Values
Option<T> becomes NULL automatically:
let nickname: = None;
let height: = Some;
let query = query_qmark!;
assert_eq!;
Running Queries
The QueryExecutor trait provides methods for executing your queries:
use ;
let users: = executor.query_list.await?; // all matching rows
let user: = executor.query_one.await?; // at most one row
executor.query_void.await?; // discard results
Transactions
Turso executors support transactions:
let tx = executor.transaction.await?;
tx.query_void.await?;
tx.query_void.await?;
tx.commit.await?;
For a static sequence, use batch:
executor.batch.await?;
Mapping Results with FromRow
Derive FromRow to map database rows to your structs. Column names match field names by default:
let users: = executor.query_list.await?;
Need to map to a different column name? Use #[field]:
Or reference a schema constant directly:
For columns that may not exist in every query, mark them #[optional]:
Custom Types
Use custom types in queries by implementing From<T> for Value (already covered by the blanket Into<Value> conversion):
use Value;
;
let ts = Timestamp;
query_qmark!;
Backends
sqlw supports multiple databases through the sqlw-backend crate. Each backend has its own executor:
// Turso / LibSQL
use TursoExecutor;
// SQLite
use SqliteExecutor;
// PostgreSQL
use PostgresExecutor;
// MySQL
use MySqlExecutor;
Installation
Add both crates to your Cargo.toml:
[]
= "0.1"
= { = "0.1", = ["turso"] }
Pick your backend feature: turso (default), sqlite, postgres, or mysql. Enable chrono for chrono date/time support.