sqlite.bend
bend-over-sqlite@0.1.0.1 · 560 lines · 0 definitions
1# SQLite connections, typed values, prepared statements and transactions for C and Wasm.
2import Base
3
4type Connection is Type:
5 Connection{handle: File}
6
7type Statement is Type:
8 Statement{handle: File}
9
10type I64 is Data:
11 I64{lo: U32, hi: U32}
12
13type F64 is Data:
14 F64{lo: U32, hi: U32}
15
16type Value is Data:
17 Null{}
18 Integer{value: I64}
19 Real{value: F64}
20 Text{value: String}
21 Blob{bytes: List<&2, U32>}
22
23type Row is Data:
24 Row{values: List<&2, Value>}
25
26type Rows is Data:
27 Rows{columns: List<&2, String>, rows: List<&2, Row>}
28
29type Stats is Data:
30 Stats{changes: I64, last_insert_rowid: I64}
31
32def I64.from_u32(n: U32) -> I64:
33 I64{n, 0}
34
35def I64.is_eq(a: I64, b: I64) -> Bool:
36 I64{al, ah} = a
37 I64{bl, bh} = b
38 U32.is_eq(al, bl) && U32.is_eq(ah, bh)
39
40def I64.neg(a: I64) -> I64:
41 I64{+lo, hi} = a
42 I64{(0 - lo : U32), (0 - hi - Bool.pick(U32, U32.is_zero(lo), 0, 1) : U32)}
43
44def I64.to_u32.go(z: Bool, lo: U32) -> Maybe<&2, U32>:
45 match z:
46 case True{}:
47 Some{lo}
48 case False{}:
49 None{}
50
51def I64.to_u32(a: I64) -> Maybe<&2, U32>:
52 I64{lo, hi} = a
53 I64.to_u32.go(U32.is_zero(hi), lo)
54
55# Unsigned division by ten, with 16-bit limbs to avoid intermediate overflow.
56def I64.div10(a: I64) -> I64 & U32:
57 I64{+lo, +hi} = a
58 +a3 = (hi >> 16n : U32)
59 +a2 = ((a3 % 10) * 65536 + (hi .&. 65535) : U32)
60 +a1 = ((a2 % 10) * 65536 + (lo >> 16n) : U32)
61 +a0 = ((a1 % 10) * 65536 + (lo .&. 65535) : U32)
62 (I64{((a1 / 10) * 65536 + a0 / 10 : U32), ((a3 / 10) * 65536 + a2 / 10 : U32)}, (a0 % 10 : U32))
63
64def I64.digits(fuel: Nat, qr: I64 & U32, rest: String) -> String:
65 match fuel:
66 case 0n:
67 rest
68 case 1n+p:
69 match qr:
70 case (I64{0, 0}, r):
71 SCon{Chr{(48 + r : U32)}, rest}
72 case (I64{lo, hi}, r):
73 I64.digits(p, I64.div10(I64{lo, hi}), SCon{Chr{(48 + r : U32)}, rest})
74
75def I64.show.sign(negative: Bool, n: I64) -> String:
76 match negative:
77 case True{}:
78 "-" ++ I64.digits(20n, I64.div10(I64.neg(n)), "")
79 case False{}:
80 I64.digits(20n, I64.div10(n), "")
81
82def I64.show(n: I64) -> String:
83 match n:
84 case I64{0, 0}:
85 "0"
86 case I64{lo, +hi}:
87 I64.show.sign(U32.is_ge(hi, 2147483648), I64{lo, hi})
88
89def I64.read.end(ok: Bool, negative: Bool, +n: I64) -> Maybe<&2, I64>:
90 match ok:
91 case False{}:
92 None{}
93 case True{}:
94 Some{Bool.pick(I64, negative, I64.neg(n), n)}
95
96def I64.read.advance(ok: Bool, n: I64) -> Maybe<&2, I64>:
97 match ok:
98 case True{}:
99 Some{n}
100 case False{}:
101 None{}
102
103def I64.read.finish(+negative: Bool, seen: Bool, n: Maybe<&2, I64>) -> Maybe<&2, I64>:
104 match n:
105 case None{}:
106 None{}
107 case Some{I64{+lo, +hi}}:
108 I64.read.end(seen && (U32.is_lt(hi, 2147483648) || (negative && U32.is_eq(hi, 2147483648) && U32.is_zero(lo))), negative, I64{lo, hi})
109
110def I64.read.digits(s: String, negative: Bool, seen: Bool, n: Maybe<&2, I64>) -> Maybe<&2, I64>:
111 match s:
112 case SNil{}:
113 I64.read.finish(negative, seen, n)
114 case SCon{Chr{+c}, tail}:
115 match n:
116 case None{}:
117 None{}
118 case Some{I64{+lo, +hi}}:
119 +digit = (c - 48 : U32)
120 +low = ((lo .&. 65535) * 10 + digit : U32)
121 +high = ((lo >> 16n) * 10 + (low >> 16n) : U32)
122 next = {I64{((high << 16n) .|. (low .&. 65535) : U32), (hi * 10 + (high >> 16n) : U32)} : I64}
123 ok = U32.is_ge(c, 48) && U32.is_le(c, 57) && (U32.is_lt(hi, 214748364) || (U32.is_eq(hi, 214748364) && U32.is_le(lo, 3435973836)))
124 I64.read.digits(tail, negative, True{}, I64.read.advance(ok, next))
125
126def I64.read(s: String) -> Maybe<&2, I64>:
127 match s:
128 case SCon{'-', tail}:
129 I64.read.digits(tail, True{}, False{}, Some{I64{0, 0}})
130 case SCon{'+', tail}:
131 I64.read.digits(tail, False{}, False{}, Some{I64{0, 0}})
132 case rest:
133 I64.read.digits(rest, False{}, False{}, Some{I64{0, 0}})
134
135# Exact widening of binary32, including subnormals. SQLite itself maps NaN to NULL.
136def F64.normal(sign: U32, exponent: U32, +fraction: U32) -> F64:
137 F64{(fraction << 29n : U32), (sign .|. (exponent << 20n) .|. (fraction >> 3n) : U32)}
138
139def F64.subnormal(fuel: Nat, ready: Bool, sign: U32, exponent: U32, +fraction: U32) -> F64:
140 match fuel:
141 case 0n:
142 F64.normal(sign, exponent, (fraction .&. 8388607 : U32))
143 case 1n+p:
144 match ready:
145 case True{}:
146 F64.normal(sign, exponent, (fraction .&. 8388607 : U32))
147 case False{}:
148 +next = (fraction << 1n : U32)
149 F64.subnormal(p, U32.is_ge(next, 8388608), sign, (exponent - 1 : U32), next)
150
151def F64.small(sign: U32, fraction: U32) -> F64:
152 match fraction:
153 case 0:
154 F64{0, sign}
155 case m:
156 F64.subnormal(23n, False{}, sign, 897, m)
157
158def F64.widen(exponent: U32, sign: U32, fraction: U32) -> F64:
159 match exponent:
160 case 0:
161 F64.small(sign, fraction)
162 case 255:
163 F64.normal(sign, 2047, fraction)
164 case e:
165 F64.normal(sign, (e + 896 : U32), fraction)
166
167def F64.from_f32(n: F32) -> F64:
168 +bits = F32.bits(n)
169 F64.widen(((bits >> 23n) .&. 255 : U32), (bits .&. 2147483648 : U32), (bits .&. 8388607 : U32))
170
171def Row.get.go(index: Nat, values: List<&2, Value>) -> Maybe<&2, Value>:
172 match index:
173 case 0n:
174 List.head(&2, Value, values)
175 case 1n+p:
176 Row.get.go(p, List.tail(&2, Value, values))
177
178def Row.get(row: Row, index: U32) -> Maybe<&2, Value>:
179 Row{values} = row
180 Row.get.go(U32.to_nat(index), values)
181
182def Rows.is_single(rows: Rows) -> Bool:
183 match rows:
184 case Rows{columns, Con{row, Nil{}}}:
185 True{}
186 case Rows{columns, other}:
187 False{}
188
189def raw.open(path: String, readonly: Bool) -> IO(Result<&1, &1, U32 & String, File>):
190 import "./effs/sqlite.c"
191 import "./effs/sqlite.js"
192
193def raw.import(bytes: List<&2, U32>) -> IO(Result<&1, &1, U32 & String, File>):
194 import "./effs/sqlite.c"
195 import "./effs/sqlite.js"
196
197def raw.close(handle: File) -> IO(Result<&1, &1, (U32 & String) & File, Unit>):
198 import "./effs/sqlite.c"
199 import "./effs/sqlite.js"
200
201def raw.finalize(handle: File) -> IO(Result<&1, &1, U32 & String, Unit>):
202 import "./effs/sqlite.c"
203 import "./effs/sqlite.js"
204
205def raw.prepare(handle: File, sql: String) -> IO(File & Result<&1, &1, U32 & String, File>):
206 import "./effs/sqlite.c"
207 import "./effs/sqlite.js"
208
209def raw.bind(handle: File, index: U32, value: Value) -> IO(File & Result<&1, &1, U32 & String, Unit>):
210 import "./effs/sqlite.c"
211 import "./effs/sqlite.js"
212
213def raw.bind_named(handle: File, name: String, value: Value) -> IO(File & Result<&1, &1, U32 & String, Unit>):
214 import "./effs/sqlite.c"
215 import "./effs/sqlite.js"
216
217def raw.step(handle: File) -> IO(File & Result<&1, &1, U32 & String, Maybe<&2, Row>>):
218 import "./effs/sqlite.c"
219 import "./effs/sqlite.js"
220
221def raw.reset(handle: File) -> IO(File & Result<&1, &1, U32 & String, Unit>):
222 import "./effs/sqlite.c"
223 import "./effs/sqlite.js"
224
225def raw.clear_bindings(handle: File) -> IO(File & Result<&1, &1, U32 & String, Unit>):
226 import "./effs/sqlite.c"
227 import "./effs/sqlite.js"
228
229def raw.column_names(handle: File) -> IO(File & Result<&1, &1, U32 & String, List<&2, String>>):
230 import "./effs/sqlite.c"
231 import "./effs/sqlite.js"
232
233def raw.parameter_count(handle: File) -> IO(File & Result<&1, &1, U32 & String, U32>):
234 import "./effs/sqlite.c"
235 import "./effs/sqlite.js"
236
237def raw.parameter_index(handle: File, name: String) -> IO(File & Result<&1, &1, U32 & String, U32>):
238 import "./effs/sqlite.c"
239 import "./effs/sqlite.js"
240
241def raw.script(handle: File, sql: String) -> IO(File & Result<&1, &1, U32 & String, Unit>):
242 import "./effs/sqlite.c"
243 import "./effs/sqlite.js"
244
245def raw.execute(handle: File, sql: String, values: List<&2, Value>) -> IO(File & Result<&1, &1, U32 & String, Stats>):
246 import "./effs/sqlite.c"
247 import "./effs/sqlite.js"
248
249def raw.query(handle: File, sql: String, values: List<&2, Value>) -> IO(File & Result<&1, &1, U32 & String, Rows>):
250 import "./effs/sqlite.c"
251 import "./effs/sqlite.js"
252
253def raw.busy_timeout(handle: File, milliseconds: U32) -> IO(File & Result<&1, &1, U32 & String, Unit>):
254 import "./effs/sqlite.c"
255 import "./effs/sqlite.js"
256
257def raw.export(handle: File) -> IO(File & Result<&1, &1, U32 & String, List<&2, U32>>):
258 import "./effs/sqlite.c"
259 import "./effs/sqlite.js"
260
261def wrap.open(r: Result<&1, &1, U32 & String, File>) -> Result<&1, &1, U32 & String, Connection>:
262 match r:
263 case Done{handle}:
264 Done{Connection{handle}}
265 case Fail{error}:
266 Fail{error}
267
268def open(path: String) -> IO(Result<&1, &1, U32 & String, Connection>):
269 do IO<Result<&1, &1, U32 & String, Connection>>:
270 r : Result<&1, &1, U32 & String, File> <- raw.open(path, False{})
271 return wrap.open(r)
272
273def open_readonly(path: String) -> IO(Result<&1, &1, U32 & String, Connection>):
274 do IO<Result<&1, &1, U32 & String, Connection>>:
275 r : Result<&1, &1, U32 & String, File> <- raw.open(path, True{})
276 return wrap.open(r)
277
278def memory() -> IO(Result<&1, &1, U32 & String, Connection>):
279 open(":memory:")
280
281def from_bytes(bytes: List<&2, U32>) -> IO(Result<&1, &1, U32 & String, Connection>):
282 do IO<Result<&1, &1, U32 & String, Connection>>:
283 r : Result<&1, &1, U32 & String, File> <- raw.import(bytes)
284 return wrap.open(r)
285
286def wrap.close(r: Result<&1, &1, (U32 & String) & File, Unit>) -> Result<&1, &1, (U32 & String) & Connection, Unit>:
287 match r:
288 case Done{u}:
289 Done{u}
290 case Fail{(error, handle)}:
291 Fail{(error, Connection{handle})}
292
293def close(db: Connection) -> IO(Result<&1, &1, (U32 & String) & Connection, Unit>):
294 Connection{handle} = db
295 do IO<Result<&1, &1, (U32 & String) & Connection, Unit>>:
296 r : Result<&1, &1, (U32 & String) & File, Unit> <- raw.close(handle)
297 return wrap.close(r)
298
299def finalize(stmt: Statement) -> IO(Result<&1, &1, U32 & String, Unit>):
300 Statement{handle} = stmt
301 raw.finalize(handle)
302
303def wrap.prepare(r: Result<&1, &1, U32 & String, File>) -> Result<&1, &1, U32 & String, Statement>:
304 match r:
305 case Done{handle}:
306 Done{Statement{handle}}
307 case Fail{error}:
308 Fail{error}
309
310def wrap.prepare.pair(pair: File & Result<&1, &1, U32 & String, File>) -> Connection & Result<&1, &1, U32 & String, Statement>:
311 (handle, r) = pair
312 (Connection{handle}, wrap.prepare(r))
313
314def prepare(resource: Connection, sql: String) -> IO(Connection & Result<&1, &1, U32 & String, Statement>):
315 Connection{handle} = resource
316 do IO<Connection & Result<&1, &1, U32 & String, Statement>>:
317 r : File & Result<&1, &1, U32 & String, File> <- raw.prepare(handle, sql)
318 return wrap.prepare.pair(r)
319
320def wrap.bind.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Statement & Result<&1, &1, U32 & String, Unit>:
321 (handle, r) = pair
322 (Statement{handle}, r)
323
324def bind(resource: Statement, index: U32, value: Value) -> IO(Statement & Result<&1, &1, U32 & String, Unit>):
325 Statement{handle} = resource
326 do IO<Statement & Result<&1, &1, U32 & String, Unit>>:
327 r : File & Result<&1, &1, U32 & String, Unit> <- raw.bind(handle, index, value)
328 return wrap.bind.pair(r)
329
330def wrap.bind_named.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Statement & Result<&1, &1, U32 & String, Unit>:
331 (handle, r) = pair
332 (Statement{handle}, r)
333
334def bind_named(resource: Statement, name: String, value: Value) -> IO(Statement & Result<&1, &1, U32 & String, Unit>):
335 Statement{handle} = resource
336 do IO<Statement & Result<&1, &1, U32 & String, Unit>>:
337 r : File & Result<&1, &1, U32 & String, Unit> <- raw.bind_named(handle, name, value)
338 return wrap.bind_named.pair(r)
339
340def wrap.step.pair(pair: File & Result<&1, &1, U32 & String, Maybe<&2, Row>>) -> Statement & Result<&1, &1, U32 & String, Maybe<&2, Row>>:
341 (handle, r) = pair
342 (Statement{handle}, r)
343
344def step(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, Maybe<&2, Row>>):
345 Statement{handle} = resource
346 do IO<Statement & Result<&1, &1, U32 & String, Maybe<&2, Row>>>:
347 r : File & Result<&1, &1, U32 & String, Maybe<&2, Row>> <- raw.step(handle)
348 return wrap.step.pair(r)
349
350def wrap.reset.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Statement & Result<&1, &1, U32 & String, Unit>:
351 (handle, r) = pair
352 (Statement{handle}, r)
353
354def reset(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, Unit>):
355 Statement{handle} = resource
356 do IO<Statement & Result<&1, &1, U32 & String, Unit>>:
357 r : File & Result<&1, &1, U32 & String, Unit> <- raw.reset(handle)
358 return wrap.reset.pair(r)
359
360def wrap.clear_bindings.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Statement & Result<&1, &1, U32 & String, Unit>:
361 (handle, r) = pair
362 (Statement{handle}, r)
363
364def clear_bindings(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, Unit>):
365 Statement{handle} = resource
366 do IO<Statement & Result<&1, &1, U32 & String, Unit>>:
367 r : File & Result<&1, &1, U32 & String, Unit> <- raw.clear_bindings(handle)
368 return wrap.clear_bindings.pair(r)
369
370def wrap.column_names.pair(pair: File & Result<&1, &1, U32 & String, List<&2, String>>) -> Statement & Result<&1, &1, U32 & String, List<&2, String>>:
371 (handle, r) = pair
372 (Statement{handle}, r)
373
374def column_names(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, List<&2, String>>):
375 Statement{handle} = resource
376 do IO<Statement & Result<&1, &1, U32 & String, List<&2, String>>>:
377 r : File & Result<&1, &1, U32 & String, List<&2, String>> <- raw.column_names(handle)
378 return wrap.column_names.pair(r)
379
380def wrap.parameter_count.pair(pair: File & Result<&1, &1, U32 & String, U32>) -> Statement & Result<&1, &1, U32 & String, U32>:
381 (handle, r) = pair
382 (Statement{handle}, r)
383
384def parameter_count(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, U32>):
385 Statement{handle} = resource
386 do IO<Statement & Result<&1, &1, U32 & String, U32>>:
387 r : File & Result<&1, &1, U32 & String, U32> <- raw.parameter_count(handle)
388 return wrap.parameter_count.pair(r)
389
390def wrap.parameter_index.pair(pair: File & Result<&1, &1, U32 & String, U32>) -> Statement & Result<&1, &1, U32 & String, U32>:
391 (handle, r) = pair
392 (Statement{handle}, r)
393
394def parameter_index(resource: Statement, name: String) -> IO(Statement & Result<&1, &1, U32 & String, U32>):
395 Statement{handle} = resource
396 do IO<Statement & Result<&1, &1, U32 & String, U32>>:
397 r : File & Result<&1, &1, U32 & String, U32> <- raw.parameter_index(handle, name)
398 return wrap.parameter_index.pair(r)
399
400def wrap.script.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Connection & Result<&1, &1, U32 & String, Unit>:
401 (handle, r) = pair
402 (Connection{handle}, r)
403
404def script(resource: Connection, sql: String) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):
405 Connection{handle} = resource
406 do IO<Connection & Result<&1, &1, U32 & String, Unit>>:
407 r : File & Result<&1, &1, U32 & String, Unit> <- raw.script(handle, sql)
408 return wrap.script.pair(r)
409
410def wrap.execute.pair(pair: File & Result<&1, &1, U32 & String, Stats>) -> Connection & Result<&1, &1, U32 & String, Stats>:
411 (handle, r) = pair
412 (Connection{handle}, r)
413
414def execute(resource: Connection, sql: String, values: List<&2, Value>) -> IO(Connection & Result<&1, &1, U32 & String, Stats>):
415 Connection{handle} = resource
416 do IO<Connection & Result<&1, &1, U32 & String, Stats>>:
417 r : File & Result<&1, &1, U32 & String, Stats> <- raw.execute(handle, sql, values)
418 return wrap.execute.pair(r)
419
420def wrap.query.pair(pair: File & Result<&1, &1, U32 & String, Rows>) -> Connection & Result<&1, &1, U32 & String, Rows>:
421 (handle, r) = pair
422 (Connection{handle}, r)
423
424def query(resource: Connection, sql: String, values: List<&2, Value>) -> IO(Connection & Result<&1, &1, U32 & String, Rows>):
425 Connection{handle} = resource
426 do IO<Connection & Result<&1, &1, U32 & String, Rows>>:
427 r : File & Result<&1, &1, U32 & String, Rows> <- raw.query(handle, sql, values)
428 return wrap.query.pair(r)
429
430def wrap.busy_timeout.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Connection & Result<&1, &1, U32 & String, Unit>:
431 (handle, r) = pair
432 (Connection{handle}, r)
433
434def busy_timeout(resource: Connection, milliseconds: U32) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):
435 Connection{handle} = resource
436 do IO<Connection & Result<&1, &1, U32 & String, Unit>>:
437 r : File & Result<&1, &1, U32 & String, Unit> <- raw.busy_timeout(handle, milliseconds)
438 return wrap.busy_timeout.pair(r)
439
440def wrap.export.pair(pair: File & Result<&1, &1, U32 & String, List<&2, U32>>) -> Connection & Result<&1, &1, U32 & String, List<&2, U32>>:
441 (handle, r) = pair
442 (Connection{handle}, r)
443
444def export(resource: Connection) -> IO(Connection & Result<&1, &1, U32 & String, List<&2, U32>>):
445 Connection{handle} = resource
446 do IO<Connection & Result<&1, &1, U32 & String, List<&2, U32>>>:
447 r : File & Result<&1, &1, U32 & String, List<&2, U32>> <- raw.export(handle)
448 return wrap.export.pair(r)
449
450# Scoped cleanup also reclaims statements left open by the callback.
451def raw.dispose(handle: File) -> IO(Result<&1, &1, U32 & String, Unit>):
452 import "./effs/sqlite.c"
453 import "./effs/sqlite.js"
454
455def dispose(db: Connection) -> IO(Result<&1, &1, U32 & String, Unit>):
456 Connection{handle} = db
457 raw.dispose(handle)
458
459def begin(db: Connection) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):
460 script(db, "BEGIN")
461
462def commit(db: Connection) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):
463 script(db, "COMMIT")
464
465def rollback(db: Connection) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):
466 script(db, "ROLLBACK")
467
468# Preserve the primary error if both the body and its cleanup fail.
469def cleanup.merge(-A: Type, result: Result<&1, &1, U32 & String, A>, cleanup: Result<&1, &1, U32 & String, Unit>) -> Result<&1, &1, U32 & String, A>:
470 match result:
471 case Done{value}:
472 match cleanup:
473 case Done{u}:
474 Done{value}
475 case Fail{error}:
476 Fail{error}
477 case Fail{(code, message)}:
478 match cleanup:
479 case Done{u}:
480 Fail{(code, message)}
481 case Fail{(other, detail)}:
482 Fail{(code, message ++ "; cleanup: " ++ detail)}
483
484def scope.connection.end(-A: Type, pair: Connection & Result<&1, &1, U32 & String, A>) -> IO(Result<&1, &1, U32 & String, A>):
485 (db, result) = pair
486 do IO<Result<&1, &1, U32 & String, A>>:
487 cleaned : Result<&1, &1, U32 & String, Unit> <- dispose(db)
488 return cleanup.merge(A, result, cleaned)
489
490def scope.connection.start(-A: Type, opened: Result<&1, &1, U32 & String, Connection>, body: Connection -> IO(Connection & Result<&1, &1, U32 & String, A>)) -> IO(Result<&1, &1, U32 & String, A>):
491 match opened:
492 case Fail{error}:
493 IO.pure(Result<&1, &1, U32 & String, A>, Fail{error})
494 case Done{db}:
495 do IO<Result<&1, &1, U32 & String, A>>:
496 pair : Connection & Result<&1, &1, U32 & String, A> <- body(db)
497 scope.connection.end(A, pair)
498
499def with_connection(-A: Type, path: String, body: Connection -> IO(Connection & Result<&1, &1, U32 & String, A>)) -> IO(Result<&1, &1, U32 & String, A>):
500 do IO<Result<&1, &1, U32 & String, A>>:
501 opened : Result<&1, &1, U32 & String, Connection> <- open(path)
502 scope.connection.start(A, opened, body)
503
504def scope.statement.end(-A: Type, db: Connection, pair: Statement & Result<&1, &1, U32 & String, A>) -> IO(Connection & Result<&1, &1, U32 & String, A>):
505 (stmt, result) = pair
506 do IO<Connection & Result<&1, &1, U32 & String, A>>:
507 cleaned : Result<&1, &1, U32 & String, Unit> <- finalize(stmt)
508 return (db, cleanup.merge(A, result, cleaned))
509
510def scope.statement.start(-A: Type, pair: Connection & Result<&1, &1, U32 & String, Statement>, body: Statement -> IO(Statement & Result<&1, &1, U32 & String, A>)) -> IO(Connection & Result<&1, &1, U32 & String, A>):
511 match pair:
512 case (db, Fail{error}):
513 IO.pure(Connection & Result<&1, &1, U32 & String, A>, (db, Fail{error}))
514 case (db, Done{stmt}):
515 do IO<Connection & Result<&1, &1, U32 & String, A>>:
516 answer : Statement & Result<&1, &1, U32 & String, A> <- body(stmt)
517 scope.statement.end(A, db, answer)
518
519def with_statement(-A: Type, db: Connection, sql: String, body: Statement -> IO(Statement & Result<&1, &1, U32 & String, A>)) -> IO(Connection & Result<&1, &1, U32 & String, A>):
520 do IO<Connection & Result<&1, &1, U32 & String, A>>:
521 prepared : Connection & Result<&1, &1, U32 & String, Statement> <- prepare(db, sql)
522 scope.statement.start(A, prepared, body)
523
524def transaction.rolled(-A: Type, result: Result<&1, &1, U32 & String, A>, pair: Connection & Result<&1, &1, U32 & String, Unit>) -> Connection & Result<&1, &1, U32 & String, A>:
525 (db, cleaned) = pair
526 (db, cleanup.merge(A, result, cleaned))
527
528def transaction.committed(-A: Type, value: A, pair: Connection & Result<&1, &1, U32 & String, Unit>) -> IO(Connection & Result<&1, &1, U32 & String, A>):
529 match pair:
530 case (db, Done{u}):
531 IO.pure(Connection & Result<&1, &1, U32 & String, A>, (db, Done{value}))
532 case (db, Fail{error}):
533 do IO<Connection & Result<&1, &1, U32 & String, A>>:
534 rolled : Connection & Result<&1, &1, U32 & String, Unit> <- rollback(db)
535 return transaction.rolled(A, Fail{error}, rolled)
536
537def transaction.end(-A: Type, pair: Connection & Result<&1, &1, U32 & String, A>) -> IO(Connection & Result<&1, &1, U32 & String, A>):
538 match pair:
539 case (db, Done{value}):
540 do IO<Connection & Result<&1, &1, U32 & String, A>>:
541 committed : Connection & Result<&1, &1, U32 & String, Unit> <- commit(db)
542 transaction.committed(A, value, committed)
543 case (db, Fail{error}):
544 do IO<Connection & Result<&1, &1, U32 & String, A>>:
545 rolled : Connection & Result<&1, &1, U32 & String, Unit> <- rollback(db)
546 return transaction.rolled(A, Fail{error}, rolled)
547
548def transaction.start(-A: Type, pair: Connection & Result<&1, &1, U32 & String, Unit>, body: Connection -> IO(Connection & Result<&1, &1, U32 & String, A>)) -> IO(Connection & Result<&1, &1, U32 & String, A>):
549 match pair:
550 case (db, Fail{error}):
551 IO.pure(Connection & Result<&1, &1, U32 & String, A>, (db, Fail{error}))
552 case (db, Done{u}):
553 do IO<Connection & Result<&1, &1, U32 & String, A>>:
554 result : Connection & Result<&1, &1, U32 & String, A> <- body(db)
555 transaction.end(A, result)
556
557def transaction(-A: Type, db: Connection, body: Connection -> IO(Connection & Result<&1, &1, U32 & String, A>)) -> IO(Connection & Result<&1, &1, U32 & String, A>):
558 do IO<Connection & Result<&1, &1, U32 & String, A>>:
559 begun : Connection & Result<&1, &1, U32 & String, Unit> <- begin(db)
560 transaction.start(A, begun, body)