Get started with MySQL
MySQL is the same shape again, with two differences that change the code rather than the
wording: there is no RETURNING, so a generated key is read back with a second SELECT, and
identifiers are quoted with backticks.
Add the crates
besu enables derive and migrate by default. besu-mysql enables sqlx and
runtime-tokio.
The database has to exist before you connect. Besu creates tables and constraints, but no
CREATE DATABASE.
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 pins the session to utf8mb4 and to +00:00, because timestamps are encoded as
UTC and a non-UTF-8 connection would mangle text. Sqlx::connect leaves the URL exactly as
written. See connecting to MySQL for the trade-off and pool
configuration.
Create the table
push introspects, diffs against the struct, and applies only what is missing.
It sends one statement (reformatted here):
The UNIQUE KEY clause is how a #[besu(unique)] String is declared: MySQL cannot index a
TEXT column without a prefix length, so the key covers the first 255 characters. The key is
named after the column, which is how introspection tells it apart from a unique index you
declared yourself.
Insert
MySQL has no RETURNING. An insert resolves to the driver's MySqlQueryResult, from which
last_insert_id() is available, and Besu refuses a returning clause with an error rather
than silently ignoring it.
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.
Select
An OFFSET without a LIMIT is not grammatical on MySQL, so the builder inserts an unbounded
LIMIT when you set only an offset.
Check the SQL without a database
dry gives you the typed builder and the exact SQL with no connection. Awaiting a query built
this way returns an error rather than a fake result.
Next steps
- Schema declaration — every
#[besu(...)]attribute. - MySQL column types — unsigned integers and wide decimals.
- Indexes and constraints — prefix keys and foreign keys.
- Connecting to MySQL — charset, timezone and pools.
- Get started with SQLite or PostgreSQL.
Gotchas
AUTO_INCREMENTrequires the column to be a key. The derive does not enforce the pairing, soauto_incrementwithoutprimary_keyis accepted by the macro and rejected by the server.- A
UNIQUEon aStringis a 255-character prefix key, not a constraint over the whole value. Two rows whose text agrees in the first 255 characters collide. AUNIQUEon a JSON column is rendered as nothing, because MySQL cannot index one; such a schema reports drift on every push. - Every
TEXTcolumn is a fullTEXT; there is no length argument in the derive. Use#[besu(sql_type = "besu::VarChar<64>")]when you wantVARCHAR(64). pushis not transactional. Usebesu generateplusbesu migratewhen a half-applied schema change would matter.- MySQL has no
RETURNINGand noFULL OUTER JOIN; the builder rejects both when the SQL is built rather than sending something the server cannot parse. It also has no partial indexes. - A
DateTime<Utc>field is declaredDATETIME(6)and introspects asTimestamp, which MySQL's normalisation does not fold back, so preferNaiveDateTime; see MySQL column types.
