1use mvcc::{Config, Database, Mvcc, ReadCommitted, Result, Serializable, Snapshot};
9
10#[derive(Mvcc, Clone, Debug)]
11#[mvcc(table = "doctors")]
12struct Doctor {
13 #[mvcc(primary_key)]
14 id: u64,
15 name: String,
16 on_call: bool,
17}
18
19#[derive(Mvcc, Clone, Debug)]
20#[mvcc(table = "counters")]
21struct Counter {
22 #[mvcc(primary_key)]
23 id: u64,
24 value: i64,
25}
26
27fn banner(title: &str) {
28 println!("\n\x1b[1m{title}\x1b[0m");
29 println!("{}", "─".repeat(title.len()));
30}
31
32fn main() -> Result<()> {
33 let db = Database::open(Config::in_memory())?;
34 db.register::<Doctor>()?;
35 db.register::<Counter>()?;
36
37 db.transaction(|tx| {
38 tx.insert(Doctor {
39 id: 1,
40 name: "ada".into(),
41 on_call: true,
42 })?;
43 tx.insert(Doctor {
44 id: 2,
45 name: "bob".into(),
46 on_call: true,
47 })?;
48 tx.insert(Counter { id: 1, value: 0 })
49 })?;
50
51 banner("1. Snapshot: a reader is unaffected by concurrent commits");
53 {
54 let mut reader = db.begin_with::<Snapshot>();
55 let before = reader.get::<Counter>(&1)?.unwrap().value;
56
57 db.transaction(|tx| tx.update::<Counter>(&1, |c| c.value = 42).map(|_| ()))?;
59
60 let after = reader.get::<Counter>(&1)?.unwrap().value;
61 println!(" reader saw {before} before, {after} after a concurrent commit");
62 assert_eq!(before, after, "snapshot isolation must be stable");
63 println!(" ✓ the snapshot held: readers never block and never change under you");
64 }
65
66 banner("2. ReadCommitted: each statement sees a fresh snapshot");
68 {
69 let mut reader = db.begin_with::<ReadCommitted>();
70 let before = reader.get::<Counter>(&1)?.unwrap().value;
71
72 db.transaction(|tx| tx.update::<Counter>(&1, |c| c.value = 99).map(|_| ()))?;
73
74 let after = reader.get::<Counter>(&1)?.unwrap().value;
75 println!(" reader saw {before} before, {after} after a concurrent commit");
76 assert_ne!(
77 before, after,
78 "read committed should observe the new commit"
79 );
80 println!(" ✓ non-repeatable read — the tradeoff this level makes for cheapness");
81 }
82
83 banner("3. Write-write conflict: first committer wins");
85 {
86 let mut first = db.begin_with::<Snapshot>();
87 let mut second = db.begin_with::<Snapshot>();
88
89 first.update::<Counter>(&1, |c| c.value += 1)?;
90 first.commit()?;
91
92 let result = second.update::<Counter>(&1, |c| c.value += 1);
95 match result {
96 Err(e) => println!(
97 " second transaction: {e} (retriable: {})",
98 e.is_retriable()
99 ),
100 Ok(_) => unreachable!("the stale write should have been rejected"),
101 }
102 println!(" ✓ no lost update");
103 }
104
105 banner("4. Write skew: allowed under Snapshot");
107 {
108 db.transaction(|tx| {
112 tx.update::<Doctor>(&1, |d| d.on_call = true)?;
113 tx.update::<Doctor>(&2, |d| d.on_call = true).map(|_| ())
114 })?;
115
116 let mut t1 = db.begin_with::<Snapshot>();
117 let mut t2 = db.begin_with::<Snapshot>();
118
119 let t1_sees = t1.get::<Doctor>(&1)?.unwrap().on_call as u8
120 + t1.get::<Doctor>(&2)?.unwrap().on_call as u8;
121 let t2_sees = t2.get::<Doctor>(&1)?.unwrap().on_call as u8
122 + t2.get::<Doctor>(&2)?.unwrap().on_call as u8;
123 let names: Vec<String> = {
124 let mut tx = db.begin();
125 tx.scan::<Doctor>()?
126 .iter()
127 .map(|d| d.name.clone())
128 .collect()
129 };
130 println!(" on the rota: {}", names.join(", "));
131 println!(" t1 counts {t1_sees} on call, t2 counts {t2_sees} — both think it is safe");
132
133 t1.update::<Doctor>(&1, |d| d.on_call = false)?;
134 t2.update::<Doctor>(&2, |d| d.on_call = false)?;
135 t1.commit()?;
136 t2.commit()?;
137
138 let mut check = db.begin();
139 let remaining = check.get::<Doctor>(&1)?.unwrap().on_call as u8
140 + check.get::<Doctor>(&2)?.unwrap().on_call as u8;
141 println!(" ✗ {remaining} doctors on call — the invariant is broken");
142 println!(" Both transactions were individually legal. This is write skew,");
143 println!(" and it is why Snapshot is not Serializable.");
144 }
145
146 banner("5. Write skew: prevented under Serializable");
148 {
149 db.transaction(|tx| {
150 tx.update::<Doctor>(&1, |d| d.on_call = true)?;
151 tx.update::<Doctor>(&2, |d| d.on_call = true).map(|_| ())
152 })?;
153
154 let mut t1 = db.begin_with::<Serializable>();
155 let mut t2 = db.begin_with::<Serializable>();
156
157 let _ = t1.get::<Doctor>(&1)?;
159 let _ = t1.get::<Doctor>(&2)?;
160 let _ = t2.get::<Doctor>(&1)?;
161 let _ = t2.get::<Doctor>(&2)?;
162
163 t1.update::<Doctor>(&1, |d| d.on_call = false)?;
164 t2.update::<Doctor>(&2, |d| d.on_call = false)?;
165
166 t1.commit()?;
167 match t2.commit() {
168 Err(e) => println!(" t2: {e} (retriable: {})", e.is_retriable()),
169 Ok(()) => unreachable!("t2 read a row t1 changed; it must not commit"),
170 }
171
172 let mut check = db.begin();
173 let remaining = check.get::<Doctor>(&1)?.unwrap().on_call as u8
174 + check.get::<Doctor>(&2)?.unwrap().on_call as u8;
175 println!(" ✓ {remaining} doctor still on call — the invariant held");
176 println!(" t2 read doctor 1, which t1 changed, so t2 could not be serialized.");
177 }
178
179 banner("6. Retries are the normal way to use Serializable");
181 {
182 let mut attempts = 0;
185 db.transaction_with::<Serializable, _, _>(|tx| {
186 attempts += 1;
187 let ada = tx.get::<Doctor>(&1)?.unwrap().on_call;
188 let bob = tx.get::<Doctor>(&2)?.unwrap().on_call;
189 if ada && bob {
190 tx.update::<Doctor>(&1, |d| d.on_call = false)?;
191 }
192 Ok(())
193 })?;
194 println!(" committed after {attempts} attempt(s)");
195 println!(" ✓ write your transaction as if it runs alone; let the engine retry it");
196 }
197
198 banner("Summary");
199 println!(" ReadCommitted cheapest; non-repeatable reads and phantoms");
200 println!(" RepeatableRead stable snapshot; write skew possible");
201 println!(" Snapshot stable snapshot; write skew possible ← default");
202 println!(" Serializable no anomalies; expect retriable aborts");
203
204 Ok(())
205}