Derive Macro Mvcc
#[derive(Mvcc)]
{
// Attributes available to this derive:
#[mvcc]
}
Expand description
Make a struct storable in a Database, by implementing Versioned for it.
Exactly one field must be marked #[mvcc(primary_key)]. Any others may be
indexed. The struct must also be Clone, because an update copies the record
before mutating it into a new version.
use mvcc::{Config, Database, Mvcc};
#[derive(Mvcc, Clone, Debug)]
#[mvcc(table = "accounts")]
pub struct Account {
#[mvcc(primary_key)]
pub id: u64,
/// No two accounts may share an owner.
#[mvcc(index(unique))]
pub owner: String,
/// Range-scannable, duplicates allowed.
#[mvcc(index)]
pub branch: u32,
pub balance: i64,
}
let db = Database::open(Config::in_memory())?;
db.register::<Account>()?;
db.transaction(|tx| {
tx.insert(Account { id: 1, owner: "ada".into(), branch: 10, balance: 500 })
})?;
// The derive emits `Account::BRANCH` for the indexed field, and that const —
// not a string — is what a scan takes.
let mut tx = db.begin();
let at_branch_10 = tx.scan_index(Account::BRANCH, 10u32..=10)?;
assert_eq!(at_branch_10.len(), 1);§Attribute reference
| attribute | position | meaning |
|---|---|---|
table = "name" | struct | table name, used only in error messages; defaults to the type name |
primary_key | field | required, exactly one |
index | field | secondary index on this field |
index(unique) | field | unique secondary index |
An index is always named after its field. There is no rename knob: the name is not a string anyone types, it is the associated const above, so renaming it would only decouple the const from the field it reads.
There is no skip: nothing is serialised, so every field is simply carried
along in the struct. Fields need no traits beyond what Versioned requires
of the struct as a whole — a record can hold a HashMap, an Instant, or a
function pointer.
§What it expands to
impl Versioned for Account— key type and extraction,memcmpkey encoding, and the index descriptor table.- a private
staticholding the index descriptors, const-constructed withextractas a plainfnpointer. - a private
static OnceLock<TableId>, filled byDatabase::register. Account::OWNER: Index<Account, String>— one associated const per indexed field, named after the field in upper case, which is how scans name an index. Carrying the field’s type is the point: it is what makestx.scan_index(Account::OWNER, 1u64..=2)a type error rather than a scan that matches nothing.
Everything is emitted inside a const _: () = { … }; block so the generated
statics cannot collide with user items or with a second derive in the same
module.
§What it deliberately does not do
It does not make the struct itself transactional. Account gains no interior
mutability, no Drop, and no hidden fields — it stays a plain Rust struct
you can construct, match on, and pass around. All transactional behaviour
lives on Transaction, which is where errors can actually be returned.
§Compile errors
Missing a primary key is rejected at expansion time rather than producing a type that fails obscurely later:
#[derive(Mvcc, Clone)]
struct NoKey {
name: String,
}So is declaring two of them, and so is applying the derive to an enum, a union, or a tuple struct.
The macro itself lives in the mvcc-derive crate. It is documented here, on
the re-export, because a compiling example needs the Versioned trait it
implements — which lives in this crate, so documenting it from the other
direction would mean a dependency cycle.
Derive Versioned. Documented at mvcc::Mvcc, which is how users reach it.