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)