src/Db.bend
mylsm-lsm-store@0.3.2.0 · 490 lines · 75 definitions
1import Base
2import mylsm-lsm-store@0.3.2.0/src/Keys.bend as Keys
3import mylsm-lsm-store@0.3.2.0/src/MemTable.bend as MemTable
4import mylsm-lsm-store@0.3.2.0/src/Sstable.bend as Sstable
5import mylsm-lsm-store@0.3.2.0/src/Wal.bend as Wal
6import mylsm-lsm-store@0.3.2.0/src/Fs.bend as Fs
7import mylsm-lsm-store@0.3.2.0/src/Manifest.bend as Manifest
8import mylsm-lsm-store@0.3.2.0/src/Decimal.bend as Decimal
9import mylsm-lsm-store@0.3.2.0/src/CrashPoint.bend as CrashPoint
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29type BEntry is Data:
30 BEntry{tab: String, key: String, val: Maybe<&2, String>}
31
32def default_batch_cap() -> Nat:
33 1n
34
35def bcache_bound() -> Nat:
36 256n
37
38
39
40type Db is Data:
41 Db{dir: String, mem: MemTable.MemTable, frozen: MemTable.MemTable, batch_cap: Nat, bcache: List<&2, BEntry>, levels: List<&2, List<&2, Sstable.Table>>, flushed: Nat, manifest_token: String, mem_count: Nat, frozen_count: Nat}
42
43
44
45
46type RotRes is Data:
47 Rot{mem: MemTable.MemTable, frozen: MemTable.MemTable, mem_count: Nat, frozen_count: Nat}
48
49def wal_path(+dir: String) -> String:
50 dir ++ "/wal.log"
51
52def open_db(+dir: String) -> Db:
53 Db{dir, MemTable.empty(), MemTable.empty(), default_batch_cap(), Nil{}, Nil{}, 0n, Manifest.serialize(Manifest.M{Nil{}}), 0n, 0n}
54
55
56
57def apply_mut(+mem: MemTable.MemTable, +mut: Wal.Mut) -> MemTable.MemTable:
58 match mut:
59 case Wal.Put{key, val}:
60 MemTable.put(mem, key, val)
61 case Wal.Del{key}:
62 MemTable.del(mem, key)
63
64def apply_batch(+muts: List<&2, Wal.Mut>, +mem: MemTable.MemTable) -> MemTable.MemTable:
65 match muts:
66 case Nil{}:
67 mem
68 case Con{+h, t}:
69 apply_batch(t, apply_mut(mem, h))
70
71
72
73def table_entries(+tab: Sstable.Table) -> List<&2, MemTable.Entry>:
74 match tab:
75 case Sstable.Tbl{entries, filter, nbits, smallest, largest, count, chunks, blocks}:
76 entries
77
78def level_entries(+tabs: List<&2, Sstable.Table>) -> List<&2, MemTable.Entry>:
79 match tabs:
80 case Nil{}:
81 Nil{}
82 case Con{+h, t}:
83 List.append(&2, MemTable.Entry, table_entries(h), level_entries(t))
84
85def all_level_entries(+lvls: List<&2, List<&2, Sstable.Table>>) -> List<&2, MemTable.Entry>:
86 match lvls:
87 case Nil{}:
88 Nil{}
89 case Con{+h, t}:
90 List.append(&2, MemTable.Entry, level_entries(h), all_level_entries(t))
91
92def all_entries(+db: Db) -> List<&2, MemTable.Entry>:
93 match db:
94 case Db{dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}:
95 match mem:
96 case MemTable.MT{entries}:
97 List.append(&2, MemTable.Entry, entries, all_level_entries(levels))
98
99def table_hit_if(present: Bool, tab: Sstable.Table, +key: String) -> Maybe<&2, Maybe<&2, String>>:
100 match present:
101 case False{}:
102 None{}
103 case True{}:
104 Sstable.block_get_hit(tab, key)
105
106def table_hit(+tab: Sstable.Table, +key: String) -> Maybe<&2, Maybe<&2, String>>:
107 table_hit_if(Sstable.maybe_present(tab, key), tab, key)
108
109def tables_hits(+tabs: List<&2, Sstable.Table>, +key: String) -> List<&2, Maybe<&2, Maybe<&2, String>>>:
110 match tabs:
111 case Nil{}:
112 Nil{}
113 case Con{+h, t}:
114 Con{table_hit(h, key), tables_hits(t, key)}
115
116def levels_hits(+lvls: List<&2, List<&2, Sstable.Table>>, +key: String) -> List<&2, Maybe<&2, Maybe<&2, String>>>:
117 match lvls:
118 case Nil{}:
119 Nil{}
120 case Con{h, t}:
121 List.append(&2, Maybe<&2, Maybe<&2, String>>, tables_hits(h, key), levels_hits(t, key))
122
123def hits_first(+hits: List<&2, Maybe<&2, Maybe<&2, String>>>) -> Maybe<&2, Maybe<&2, String>>:
124 match hits:
125 case Nil{}:
126 None{}
127 case Con{None{}, t}:
128 hits_first(t)
129 case Con{Some{found}, t}:
130 Some{found}
131
132def flatten_opt(+opt: Maybe<&2, Maybe<&2, String>>) -> Maybe<&2, String>:
133 match opt:
134 case None{}:
135 None{}
136 case Some{inner}:
137 inner
138
139def db_get(+db: Db, +key: String) -> Maybe<&2, String>:
140 match db:
141 case Db{dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}:
142 flatten_opt(hits_first(Con{MemTable.get_hit(mem, key), Con{MemTable.get_hit(frozen, key), levels_hits(levels, key)}}))
143
144
145
146
147
148
149
150def pick_hit(hit: Bool, ans: Maybe<&2, String>, best: Maybe<&2, Maybe<&2, String>>) -> Maybe<&2, Maybe<&2, String>>:
151 match hit:
152 case True{}:
153 Some{ans}
154 case False{}:
155 best
156
157def bcache_go(+cache: List<&2, BEntry>, +tab: String, +key: String, +best: Maybe<&2, Maybe<&2, String>>) -> Maybe<&2, Maybe<&2, String>>:
158 match cache:
159 case Nil{}:
160 best
161 case Con{BEntry{t, ky, val}, rest}:
162 bcache_go(rest, tab, key, pick_hit(Bool.and(String.eq(t, tab), String.eq(ky, key)), val, best))
163
164def bcache_lookup(+cache: List<&2, BEntry>, +tab: String, +key: String) -> Maybe<&2, Maybe<&2, String>>:
165 bcache_go(cache, tab, key, None{})
166
167def bcache_push_all(+fresh: List<&2, BEntry>, +old: List<&2, BEntry>) -> List<&2, BEntry>:
168 List.take(&2, BEntry, List.append(&2, BEntry, fresh, old), bcache_bound())
169
170def probe_fill(hit: Maybe<&2, Maybe<&2, String>>, +tab: String, +key: String) -> (Maybe<&2, Maybe<&2, String>> & List<&2, BEntry>):
171 match hit:
172 case None{}:
173 (None{}, Nil{})
174 case Some{+inner}:
175 (Some{inner}, Con{BEntry{tab, key, inner}, Nil{}})
176
177def probe_cached(cached: Maybe<&2, Maybe<&2, String>>, +tab: Sstable.Table, +key: String) -> (Maybe<&2, Maybe<&2, String>> & List<&2, BEntry>):
178 match cached:
179 case None{}:
180 probe_fill(table_hit(tab, key), Sstable.table_id(tab), key)
181 case Some{ans}:
182 (Some{ans}, Nil{})
183
184def table_probe(+tab: Sstable.Table, +key: String, +cache: List<&2, BEntry>) -> (Maybe<&2, Maybe<&2, String>> & List<&2, BEntry>):
185 probe_cached(bcache_lookup(cache, Sstable.table_id(tab), key), tab, key)
186
187def tables_cons(cur: (Maybe<&2, Maybe<&2, String>> & List<&2, BEntry>), rest: (List<&2, Maybe<&2, Maybe<&2, String>>> & List<&2, BEntry>)) -> (List<&2, Maybe<&2, Maybe<&2, String>>> & List<&2, BEntry>):
188 match cur:
189 case (hit, delta):
190 match rest:
191 case (rhits, rdelta):
192 (Con{hit, rhits}, List.append(&2, BEntry, delta, rdelta))
193
194def tables_walk(+tabs: List<&2, Sstable.Table>, +key: String, +cache: List<&2, BEntry>) -> (List<&2, Maybe<&2, Maybe<&2, String>>> & List<&2, BEntry>):
195 match tabs:
196 case Nil{}:
197 (Nil{}, Nil{})
198 case Con{+h, t}:
199 tables_cons(table_probe(h, key, cache), tables_walk(t, key, cache))
200
201def levels_cons(cur: (List<&2, Maybe<&2, Maybe<&2, String>>> & List<&2, BEntry>), rest: (List<&2, Maybe<&2, Maybe<&2, String>>> & List<&2, BEntry>)) -> (List<&2, Maybe<&2, Maybe<&2, String>>> & List<&2, BEntry>):
202 match cur:
203 case (chits, cdelta):
204 match rest:
205 case (rhits, rdelta):
206 (List.append(&2, Maybe<&2, Maybe<&2, String>>, chits, rhits), List.append(&2, BEntry, cdelta, rdelta))
207
208def levels_cached(+lvls: List<&2, List<&2, Sstable.Table>>, +key: String, +cache: List<&2, BEntry>) -> (List<&2, Maybe<&2, Maybe<&2, String>>> & List<&2, BEntry>):
209 match lvls:
210 case Nil{}:
211 (Nil{}, Nil{})
212 case Con{+h, t}:
213 levels_cons(tables_walk(h, key, cache), levels_cached(t, key, cache))
214
215def cached_levels(lr: (List<&2, Maybe<&2, Maybe<&2, String>>> & List<&2, BEntry>), +dir: String, +mem: MemTable.MemTable, +frozen: MemTable.MemTable, +batch_cap: Nat, +bcache: List<&2, BEntry>, +levels: List<&2, List<&2, Sstable.Table>>, +flushed: Nat, +manifest_token: String, +mem_count: Nat, +frozen_count: Nat) -> Db & Maybe<&2, String>:
216 match lr:
217 case (hits, delta):
218 (Db{dir, mem, frozen, batch_cap, bcache_push_all(delta, bcache), levels, flushed, manifest_token, mem_count, frozen_count}, flatten_opt(hits_first(hits)))
219
220def cached_frozen(fhit: Maybe<&2, Maybe<&2, String>>, +dir: String, +mem: MemTable.MemTable, +frozen: MemTable.MemTable, +batch_cap: Nat, +bcache: List<&2, BEntry>, +levels: List<&2, List<&2, Sstable.Table>>, +flushed: Nat, +manifest_token: String, +key: String, +mem_count: Nat, +frozen_count: Nat) -> Db & Maybe<&2, String>:
221 match fhit:
222 case Some{found}:
223 (Db{dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}, flatten_opt(Some{found}))
224 case None{}:
225 cached_levels(levels_cached(levels, key, bcache), dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count)
226
227def cached_mem(mhit: Maybe<&2, Maybe<&2, String>>, +dir: String, +mem: MemTable.MemTable, +frozen: MemTable.MemTable, +batch_cap: Nat, +bcache: List<&2, BEntry>, +levels: List<&2, List<&2, Sstable.Table>>, +flushed: Nat, +manifest_token: String, +key: String, +mem_count: Nat, +frozen_count: Nat) -> Db & Maybe<&2, String>:
228 match mhit:
229 case Some{found}:
230 (Db{dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}, flatten_opt(Some{found}))
231 case None{}:
232 cached_frozen(MemTable.get_hit(frozen, key), dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, key, mem_count, frozen_count)
233
234def db_get_cached(+db: Db, +key: String) -> Db & Maybe<&2, String>:
235 match db:
236 case Db{dir, +mem, +frozen, +batch_cap, +bcache, +levels, +flushed, +manifest_token, +mem_count, +frozen_count}:
237 cached_mem(MemTable.get_hit(mem, key), dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, key, mem_count, frozen_count)
238
239
240
241
242def wal_frame(+data: String) -> String:
243 Wal.dashes(String.length(data)) ++ ";" ++ data
244
245
246
247def wal_tail(fr: (File & Result<&1, &1, U32 & String, Unit>), +dir: String) -> IO(Result<&1, &1, U32 & String, Unit>):
248 match fr:
249 case (f2, r):
250 do IO<Result<&1, &1, U32 & String, Unit>>:
251 res : Unit <- IO.try(Unit, IO.pure(Result<&1, &1, U32 & String, Unit>, r))
252 _cp1 : Unit <- IO.try(Unit, CrashPoint.hit("wal.appended"))
253 _cls : Unit <- File.close(f2)
254 _syn : Unit <- IO.try(Unit, Fs.fsync(wal_path(dir)))
255 _cp2 : Unit <- IO.try(Unit, CrashPoint.hit("wal.synced"))
256 _prm : Unit <- IO.try(Unit, Fs.chmod(wal_path(dir), U32.from_nat(384n)))
257 return Done{res}
258
259def wal_append(+dir: String, +data: String) -> IO(Result<&1, &1, U32 & String, Unit>):
260 do IO<Result<&1, &1, U32 & String, Unit>>:
261 f : File <- IO.try(File, File.open(wal_path(dir), "a"))
262 fr : (File & Result<&1, &1, U32 & String, Unit>) <- File.write(f, wal_frame(data))
263 wal_tail(fr, dir)
264
265def batch_len(+muts: List<&2, Wal.Mut>) -> Nat:
266 List.length(&2, Wal.Mut, muts)
267
268def rotate_cnt(full: Bool, +grown: MemTable.MemTable, +frozen: MemTable.MemTable, +count: Nat, +fcount: Nat) -> RotRes:
269 match full:
270 case True{}:
271 Rot{MemTable.empty(), grown, 0n, count}
272 case False{}:
273 Rot{grown, frozen, count, fcount}
274
275def frozen_empty(+frozen: MemTable.MemTable) -> Bool:
276 match frozen:
277 case MemTable.MT{entries}:
278 match entries:
279 case Nil{}:
280 True{}
281 case Con{_, _}:
282 False{}
283
284def rotate_after(+grown: MemTable.MemTable, +frozen: MemTable.MemTable, +count: Nat, +fcount: Nat) -> RotRes:
285 rotate_cnt(Bool.and(Nat.is_le(4096n, count), frozen_empty(frozen)), grown, frozen, count, fcount)
286
287def apply_and_rotate(+muts: List<&2, Wal.Mut>, +mem: MemTable.MemTable, +frozen: MemTable.MemTable, +mem_count: Nat, +frozen_count: Nat) -> RotRes:
288 rotate_after(apply_batch(muts, mem), frozen, Nat.add(mem_count, batch_len(muts)), frozen_count)
289
290def apply_done(res: RotRes, +dir: String, +batch_cap: Nat, +bcache: List<&2, BEntry>, +levels: List<&2, List<&2, Sstable.Table>>, +flushed: Nat, +manifest_token: String) -> Db:
291 match res:
292 case Rot{mem2, frozen2, mc, fc}:
293 Db{dir, mem2, frozen2, batch_cap, bcache, levels, flushed, manifest_token, mc, fc}
294
295def rot_done(res: RotRes, +dir: String, +batch_cap: Nat, +bcache: List<&2, BEntry>, +levels: List<&2, List<&2, Sstable.Table>>, +flushed: Nat, +manifest_token: String) -> Result<&1, &1, U32 & String, Db>:
296 Done{apply_done(res, dir, batch_cap, bcache, levels, flushed, manifest_token)}
297
298def db_write(+db: Db, +batch: Wal.Batch) -> IO(Result<&1, &1, U32 & String, Db>):
299 match db:
300 case Db{dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}:
301 match batch:
302 case Wal.Batch{muts}:
303 do IO<Result<&1, &1, U32 & String, Db>>:
304 _res : Unit <- IO.try(Unit, wal_append(dir, Wal.encode(Wal.Batch{muts})))
305 return rot_done(apply_and_rotate(muts, mem, frozen, mem_count, frozen_count), dir, batch_cap, bcache, levels, flushed, manifest_token)
306
307def db_put(+db: Db, +key: String, +val: String) -> IO(Result<&1, &1, U32 & String, Db>):
308 db_write(db, Wal.Batch{Con{Wal.Put{key, val}, Nil{}}})
309
310def batch_cap_of(+db: Db) -> Nat:
311 match db:
312 case Db{dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}:
313 batch_cap
314
315def with_batch_cap(+db: Db, +cap: Nat) -> Db:
316 match db:
317 case Db{dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}:
318 Db{dir, mem, frozen, cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}
319
320def staged_muts(+staged: List<&2, Wal.Batch>) -> List<&2, Wal.Mut>:
321 match staged:
322 case Nil{}:
323 Nil{}
324 case Con{Wal.Batch{muts}, t}:
325 List.append(&2, Wal.Mut, muts, staged_muts(t))
326
327def db_write_staged(+db: Db, +staged: List<&2, Wal.Batch>) -> IO(Result<&1, &1, U32 & String, Db>):
328 match db:
329 case Db{dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}:
330 db_write(Db{dir, mem, frozen, batch_cap, bcache, levels, flushed, manifest_token, mem_count, frozen_count}, Wal.Batch{staged_muts(List.reverse(&2, Wal.Batch, staged))})
331
332def db_del(+db: Db, +key: String) -> IO(Result<&1, &1, U32 & String, Db>):
333 db_write(db, Wal.Batch{Con{Wal.Del{key}, Nil{}}})
334
335
336
337
338
339
340
341
342
343def reopen_chunk_chars() -> Nat:
344 1048576n
345
346type ReChunk is Data:
347 ReChunk{bytes: Nat, text: String}
348
349def reopen_chunk_parse(pair: Decimal.Parse) -> Result<&1, &1, U32 & String, ReChunk>:
350 match pair:
351 case Decimal.Rejected{error}:
352 Fail{(U32.from_nat(8n), "bad UTF-8 chunk envelope")}
353 case Decimal.Accepted{Decimal.Dec{value, rest}}:
354 Done{ReChunk{value, rest}}
355
356def reopen_dash_step(+done: Bool, isdash: Bool, +acc: Nat) -> (Bool & Nat):
357 match done:
358 case True{}:
359 (True{}, acc)
360 case False{}:
361 match isdash:
362 case True{}:
363 (False{}, Nat.add(1n, acc))
364 case False{}:
365 (True{}, acc)
366
367def reopen_dash_snd(st: (Bool & Nat)) -> Nat:
368 match st:
369 case (d, a):
370 a
371
372def reopen_dash_fst(st: (Bool & Nat), +ch: Char) -> (Bool & Nat):
373 match st:
374 case (done, acc):
375 reopen_dash_step(done, Char.is_eq(ch, Char.from_u32(45)), acc)
376
377def reopen_dash_go(fuel: Nat, st: (Bool & Nat), str: String) -> Nat:
378 match fuel:
379 case 0n:
380 reopen_dash_snd(st)
381 case 1n+f:
382 match str:
383 case SNil{}:
384 reopen_dash_snd(st)
385 case SCon{h, t}:
386 reopen_dash_go(f, reopen_dash_fst(st, h), t)
387
388def reopen_dashes_len(+str: String) -> Nat:
389 reopen_dash_go(Nat.add(String.length(str), 1n), (False{}, 0n), str)
390
391def reopen_semi_is(eq: Bool, +num: Nat) -> Maybe<&2, Nat>:
392 match eq:
393 case True{}:
394 Some{num}
395 case False{}:
396 None{}
397
398def reopen_semi(+ch: Char, +num: Nat) -> Maybe<&2, Nat>:
399 reopen_semi_is(Char.is_eq(ch, Char.from_u32(59)), num)
400
401def reopen_semi_go(dropped: String, +num: Nat) -> Maybe<&2, Nat>:
402 match dropped:
403 case SNil{}:
404 None{}
405 case SCon{h, t}:
406 reopen_semi(h, num)
407
408def reopen_frame_len(+str: String) -> Maybe<&2, Nat>:
409 +num = reopen_dashes_len(str)
410 reopen_semi_go(String.drop(str, num), num)
411
412def reopen_consumed(+num: Nat) -> Nat:
413 Nat.add(String.length(Wal.dashes(num)), Nat.add(1n, num))
414
415def reopen_clip(+mlen: Maybe<&2, Nat>, +str: String) -> String:
416 match mlen:
417 case None{}:
418 str
419 case Some{n}:
420 String.take(String.drop(str, Nat.add(String.length(Wal.dashes(n)), 1n)), n)
421
422def reopen_drop(+mlen: Maybe<&2, Nat>, +str: String) -> String:
423 match mlen:
424 case None{}:
425 str
426 case Some{n}:
427 String.drop(str, reopen_consumed(n))
428
429def reopen_dec_apply(+m2: Maybe<&2, Wal.Batch>, +res: MemTable.MemTable) -> MemTable.MemTable:
430 match m2:
431 case None{}:
432 res
433 case Some{Wal.Batch{muts}}:
434 apply_batch(muts, res)
435
436def reopen_replay_go(fuel: Nat, +mem: MemTable.MemTable, +str: String) -> MemTable.MemTable:
437 match fuel:
438 case 0n:
439 mem
440 case 1n+g:
441 +fl = reopen_frame_len(str)
442 reopen_replay_go(g, reopen_dec_apply(Wal.decode(reopen_clip(fl, str)), mem), reopen_drop(fl, str))
443
444def reopen_replay(+str: String) -> MemTable.MemTable:
445 reopen_replay_go(Nat.add(String.length(str), 1n), MemTable.empty(), str)
446
447def reopen_assemble(+mem: MemTable.MemTable, +dir: String) -> Db:
448 Db{dir, mem, MemTable.empty(), default_batch_cap(), Nil{}, Nil{}, 0n, Manifest.serialize(Manifest.M{Nil{}}), MemTable.count(mem), 0n}
449
450
451
452
453def reopen_read_loop(chunks_left: Nat, +path: String, +offset: Nat, +acc: String, pending: Maybe<&2, ReChunk>) -> IO(Result<&1, &1, U32 & String, String>):
454 match chunks_left pending:
455 case 0n _:
456 IO.pure(Result<&1, &1, U32 & String, String>, Fail{(U32.from_nat(7n), "wal exceeds streaming read limit")})
457 case 1n+rest None{}:
458 do IO<Result<&1, &1, U32 & String, String>>:
459 raw : String <- IO.try(String, Fs.read_utf8_chunk(path, offset, reopen_chunk_chars()))
460 chunk : ReChunk <- IO.try(ReChunk, IO.pure(Result<&1, &1, U32 & String, ReChunk>, reopen_chunk_parse(Decimal.parse_semicolon(raw, reopen_chunk_chars()))))
461 reopen_read_loop(rest, path, offset, acc, Some{chunk})
462 case 1n+rest Some{ReChunk{bytes, text}}:
463 match bytes:
464 case 0n:
465 IO.pure(Result<&1, &1, U32 & String, String>, Done{acc})
466 case 1n+more:
467 reopen_read_loop(rest, path, Nat.add(offset, 1n+more), acc ++ text, None{})
468
469def reopen_read(+dir: String) -> IO(Result<&1, &1, U32 & String, Db>):
470 do IO<Result<&1, &1, U32 & String, Db>>:
471 content : String <- IO.try(String, reopen_read_loop(8192n, wal_path(dir), 0n, "", None{}))
472 return Done{reopen_assemble(reopen_replay(content), dir)}
473
474def reopen_exists(ok: Result<&1, &1, U32 & String, Bool>, +dir: String) -> IO(Result<&1, &1, U32 & String, Db>):
475 match ok:
476 case Fail{e}:
477 IO.pure(Result<&1, &1, U32 & String, Db>, Fail{e})
478 case Done{present}:
479 match present:
480 case False{}:
481 IO.pure(Result<&1, &1, U32 & String, Db>, Done{open_db(dir)})
482 case True{}:
483 reopen_read(dir)
484
485def reopen(+dir: String) -> IO(Result<&1, &1, U32 & String, Db>):
486 do IO<Result<&1, &1, U32 & String, Db>>:
487 r : Result<&1, &1, U32 & String, Bool> <- Fs.exists(wal_path(dir))
488 reopen_exists(r, dir)
489
490