upsert
upsert(&self, values: T) where T derives #[derive(SqlxInsert)] (see insert)
— insert, or update in place on conflict. Requires #[unique(col)] on the method to name the
conflict column:
#[unique(username)]
async fn upsert(&self, values: UserValues) -> sqlx::Result<UserRow>;
Leaving off #[unique(...)] is a compile error. col is checked at compile time against T's
fields, same as any other DSL column reference — but that only confirms the field exists, not
that the database treats it as unique. You still have to add the actual constraint (a UNIQUE
column/index or PRIMARY KEY) in your migration/schema; the macro has no way to see that it's
missing.
- Postgres/SQLite:
ON CONFLICT (col)names a specific conflict target. Ifcolhas no matching unique or exclusion constraint, everyupsertcall fails at runtime with something likethere is no unique or exclusion constraint matching the ON CONFLICT specification— the first time you actually hit a conflict, not at compile time. - MySQL:
ON DUPLICATE KEY UPDATEdoesn't name a column at all — it fires on any unique-key violation on the table. Ifcolisn't actually backed by a unique key, there's no error; the statement just inserts a fresh duplicate row instead of updating, which is easy to miss since nothing fails.
Always row-returning — unlike insert/insert_all/upsert_all, there's no ()-returning
form of single-row upsert.
Per-dialect SQL
Postgres/SQLite:
INSERT INTO "table" (cols...) VALUES ($1, ...)
ON CONFLICT (conflict_col) DO UPDATE SET other_col = EXCLUDED.other_col, ...
RETURNING *
Every column except the conflict column is re-set from EXCLUDED.
MySQL has no ON CONFLICT/RETURNING. Instead:
INSERT INTO `table` (cols...) VALUES (?, ...)
ON DUPLICATE KEY UPDATE col = VALUES(col), ... -- every column, including the conflict column
followed by SELECT * FROM `table` WHERE {conflict_col} = ?. MySQL infers which unique key was
violated automatically (it isn't named the way Postgres/SQLite's ON CONFLICT target is), so the
generated code picks which field to bind into that follow-up SELECT based on the runtime value
of conflict_col — though since #[unique(col)] is itself compile-time checked against T's
fields, this always resolves to a real field in practice.
See insert for the MySQL Clone requirement on every field.
See also
upsert_all — the batch form.