Get started with PostgreSQL
The same five-minute path as SQLite, with a server in place of a file. PostgreSQL adds
RETURNING on every write, $n placeholders, and identity columns instead of
AUTOINCREMENT.
Add the crates
besu enables derive and migrate by default. besu-postgres enables sqlx and
runtime-tokio; the DDL and the dialect work without either.
The database has to exist before you connect. Besu creates tables, indexes and constraints,
but it never runs CREATE DATABASE or CREATE SCHEMA.
Define a table
The derive emits the row type, a users handle, a UserInsert struct and a
&'static TableDef. See schema declaration for the rest.
Connect
Sqlx::new is an alias for Sqlx::connect, for symmetry with the other drivers. Both accept
a libpq-style URL, so TLS and application-name settings travel in the query string. See
connecting to PostgreSQL for pool configuration.
Create the table
push introspects, diffs against the struct, and applies only what is missing.
It sends one statement (reformatted here):
push refuses anything that would lose data. In a development loop where columns come and go,
use push_destructive, which is the explicit opt-in.
Insert
users.insert(..) takes exactly the columns the database cannot fill in: not the generated
id, not the nullable bio, and not a column with a default.
.returning(users) appends RETURNING with the whole column list, which is how the generated
id comes back without a second query.
Select
The trailing comment is part of the statement the driver sends. It records the shape of the bound parameters, because PostgreSQL fixes a parameter's type at prepare time and sqlx caches prepared statements by SQL text alone. Two statements with the same text and different parameter types would otherwise share a cache entry with the wrong types.
Check the SQL without a database
dry gives you the full typed builder and the exact SQL with no connection, which is also how
the builder compiles for wasm32. Awaiting a query built this way returns an error.
Next steps
- Schema declaration — every
#[besu(...)]attribute. - PostgreSQL column types —
NUMERIC,JSONB,UUID. - Indexes and constraints — partial indexes and checks.
- Connecting to PostgreSQL — pools and transactions.
- Get started with SQLite or MySQL.
Gotchas
- Every identifier is quoted. PostgreSQL folds an unquoted name to lower case, so a table
declared
#[besu(table = "Users")]becomes"Users"and no longer matches a table you created asusers. pushis not transactional: statements run one at a time, and a failure part-way leaves the earlier ones applied. Usebesu generateplusbesu migratefor anything that matters.- A schema named with
#[besu(schema = "app")]has to exist already. Table identity is(schema, name), soapp.usersandusersare two different tables. - An unsigned column is not an unsigned type.
u32is aBIGINTandu64is aNUMERIC(20,0); the range check happens in Rust, and the database will happily accept a negative number you wrote by hand. - Renaming a column is a drop plus an add unless you declare
#[besu(renamed_from = "old_name")], in which casepushemits oneALTER TABLE ... RENAME COLUMNand the data survives. - The
/* besu:... */suffix is part of the SQL. An assertion that compares the whole string has to include it.
