~/bend-docscommunity

sqlite.bend source

sqlite.bend on the hub · documented module

# SQLite connections, typed values, prepared statements and transactions for C and Wasm.import Basetype Connection is Type:  Connection{handle: File}type Statement is Type:  Statement{handle: File}type I64 is Data:  I64{lo: U32, hi: U32}type F64 is Data:  F64{lo: U32, hi: U32}type Value is Data:  Null{}  Integer{value: I64}  Real{value: F64}  Text{value: String}  Blob{bytes: List<&2, U32>}type Row is Data:  Row{values: List<&2, Value>}type Rows is Data:  Rows{columns: List<&2, String>, rows: List<&2, Row>}type Stats is Data:  Stats{changes: I64, last_insert_rowid: I64}def I64.from_u32(n: U32) -> I64:  I64{n, 0}def I64.is_eq(a: I64, b: I64) -> Bool:  I64{al, ah} = a  I64{bl, bh} = b  U32.is_eq(al, bl) && U32.is_eq(ah, bh)def I64.neg(a: I64) -> I64:  I64{+lo, hi} = a  I64{(0 - lo : U32), (0 - hi - Bool.pick(U32, U32.is_zero(lo), 0, 1) : U32)}def I64.to_u32.go(z: Bool, lo: U32) -> Maybe<&2, U32>:  match z:    case True{}:      Some{lo}    case False{}:      None{}def I64.to_u32(a: I64) -> Maybe<&2, U32>:  I64{lo, hi} = a  I64.to_u32.go(U32.is_zero(hi), lo)# Unsigned division by ten, with 16-bit limbs to avoid intermediate overflow.def I64.div10(a: I64) -> I64 & U32:  I64{+lo, +hi} = a  +a3 = (hi >> 16n : U32)  +a2 = ((a3 % 10) * 65536 + (hi .&. 65535) : U32)  +a1 = ((a2 % 10) * 65536 + (lo >> 16n) : U32)  +a0 = ((a1 % 10) * 65536 + (lo .&. 65535) : U32)  (I64{((a1 / 10) * 65536 + a0 / 10 : U32), ((a3 / 10) * 65536 + a2 / 10 : U32)}, (a0 % 10 : U32))def I64.digits(fuel: Nat, qr: I64 & U32, rest: String) -> String:  match fuel:    case 0n:      rest    case 1n+p:      match qr:        case (I64{0, 0}, r):          SCon{Chr{(48 + r : U32)}, rest}        case (I64{lo, hi}, r):          I64.digits(p, I64.div10(I64{lo, hi}), SCon{Chr{(48 + r : U32)}, rest})def I64.show.sign(negative: Bool, n: I64) -> String:  match negative:    case True{}:      "-" ++ I64.digits(20n, I64.div10(I64.neg(n)), "")    case False{}:      I64.digits(20n, I64.div10(n), "")def I64.show(n: I64) -> String:  match n:    case I64{0, 0}:      "0"    case I64{lo, +hi}:      I64.show.sign(U32.is_ge(hi, 2147483648), I64{lo, hi})def I64.read.end(ok: Bool, negative: Bool, +n: I64) -> Maybe<&2, I64>:  match ok:    case False{}:      None{}    case True{}:      Some{Bool.pick(I64, negative, I64.neg(n), n)}def I64.read.advance(ok: Bool, n: I64) -> Maybe<&2, I64>:  match ok:    case True{}:      Some{n}    case False{}:      None{}def I64.read.finish(+negative: Bool, seen: Bool, n: Maybe<&2, I64>) -> Maybe<&2, I64>:  match n:    case None{}:      None{}    case Some{I64{+lo, +hi}}:      I64.read.end(seen && (U32.is_lt(hi, 2147483648) || (negative && U32.is_eq(hi, 2147483648) && U32.is_zero(lo))), negative, I64{lo, hi})def I64.read.digits(s: String, negative: Bool, seen: Bool, n: Maybe<&2, I64>) -> Maybe<&2, I64>:  match s:    case SNil{}:      I64.read.finish(negative, seen, n)    case SCon{Chr{+c}, tail}:      match n:        case None{}:          None{}        case Some{I64{+lo, +hi}}:          +digit = (c - 48 : U32)          +low = ((lo .&. 65535) * 10 + digit : U32)          +high = ((lo >> 16n) * 10 + (low >> 16n) : U32)          next = {I64{((high << 16n) .|. (low .&. 65535) : U32), (hi * 10 + (high >> 16n) : U32)} : I64}          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)))          I64.read.digits(tail, negative, True{}, I64.read.advance(ok, next))def I64.read(s: String) -> Maybe<&2, I64>:  match s:    case SCon{'-', tail}:      I64.read.digits(tail, True{}, False{}, Some{I64{0, 0}})    case SCon{'+', tail}:      I64.read.digits(tail, False{}, False{}, Some{I64{0, 0}})    case rest:      I64.read.digits(rest, False{}, False{}, Some{I64{0, 0}})# Exact widening of binary32, including subnormals. SQLite itself maps NaN to NULL.def F64.normal(sign: U32, exponent: U32, +fraction: U32) -> F64:  F64{(fraction << 29n : U32), (sign .|. (exponent << 20n) .|. (fraction >> 3n) : U32)}def F64.subnormal(fuel: Nat, ready: Bool, sign: U32, exponent: U32, +fraction: U32) -> F64:  match fuel:    case 0n:      F64.normal(sign, exponent, (fraction .&. 8388607 : U32))    case 1n+p:      match ready:        case True{}:          F64.normal(sign, exponent, (fraction .&. 8388607 : U32))        case False{}:          +next = (fraction << 1n : U32)          F64.subnormal(p, U32.is_ge(next, 8388608), sign, (exponent - 1 : U32), next)def F64.small(sign: U32, fraction: U32) -> F64:  match fraction:    case 0:      F64{0, sign}    case m:      F64.subnormal(23n, False{}, sign, 897, m)def F64.widen(exponent: U32, sign: U32, fraction: U32) -> F64:  match exponent:    case 0:      F64.small(sign, fraction)    case 255:      F64.normal(sign, 2047, fraction)    case e:      F64.normal(sign, (e + 896 : U32), fraction)def F64.from_f32(n: F32) -> F64:  +bits = F32.bits(n)  F64.widen(((bits >> 23n) .&. 255 : U32), (bits .&. 2147483648 : U32), (bits .&. 8388607 : U32))def Row.get.go(index: Nat, values: List<&2, Value>) -> Maybe<&2, Value>:  match index:    case 0n:      List.head(&2, Value, values)    case 1n+p:      Row.get.go(p, List.tail(&2, Value, values))def Row.get(row: Row, index: U32) -> Maybe<&2, Value>:  Row{values} = row  Row.get.go(U32.to_nat(index), values)def Rows.is_single(rows: Rows) -> Bool:  match rows:    case Rows{columns, Con{row, Nil{}}}:      True{}    case Rows{columns, other}:      False{}def raw.open(path: String, readonly: Bool) -> IO(Result<&1, &1, U32 & String, File>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.import(bytes: List<&2, U32>) -> IO(Result<&1, &1, U32 & String, File>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.close(handle: File) -> IO(Result<&1, &1, (U32 & String) & File, Unit>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.finalize(handle: File) -> IO(Result<&1, &1, U32 & String, Unit>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.prepare(handle: File, sql: String) -> IO(File & Result<&1, &1, U32 & String, File>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.bind(handle: File, index: U32, value: Value) -> IO(File & Result<&1, &1, U32 & String, Unit>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.bind_named(handle: File, name: String, value: Value) -> IO(File & Result<&1, &1, U32 & String, Unit>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.step(handle: File) -> IO(File & Result<&1, &1, U32 & String, Maybe<&2, Row>>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.reset(handle: File) -> IO(File & Result<&1, &1, U32 & String, Unit>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.clear_bindings(handle: File) -> IO(File & Result<&1, &1, U32 & String, Unit>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.column_names(handle: File) -> IO(File & Result<&1, &1, U32 & String, List<&2, String>>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.parameter_count(handle: File) -> IO(File & Result<&1, &1, U32 & String, U32>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.parameter_index(handle: File, name: String) -> IO(File & Result<&1, &1, U32 & String, U32>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.script(handle: File, sql: String) -> IO(File & Result<&1, &1, U32 & String, Unit>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.execute(handle: File, sql: String, values: List<&2, Value>) -> IO(File & Result<&1, &1, U32 & String, Stats>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.query(handle: File, sql: String, values: List<&2, Value>) -> IO(File & Result<&1, &1, U32 & String, Rows>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.busy_timeout(handle: File, milliseconds: U32) -> IO(File & Result<&1, &1, U32 & String, Unit>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def raw.export(handle: File) -> IO(File & Result<&1, &1, U32 & String, List<&2, U32>>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def wrap.open(r: Result<&1, &1, U32 & String, File>) -> Result<&1, &1, U32 & String, Connection>:  match r:    case Done{handle}:      Done{Connection{handle}}    case Fail{error}:      Fail{error}def open(path: String) -> IO(Result<&1, &1, U32 & String, Connection>):  do IO<Result<&1, &1, U32 & String, Connection>>:    r : Result<&1, &1, U32 & String, File> <- raw.open(path, False{})    return wrap.open(r)def open_readonly(path: String) -> IO(Result<&1, &1, U32 & String, Connection>):  do IO<Result<&1, &1, U32 & String, Connection>>:    r : Result<&1, &1, U32 & String, File> <- raw.open(path, True{})    return wrap.open(r)def memory() -> IO(Result<&1, &1, U32 & String, Connection>):  open(":memory:")def from_bytes(bytes: List<&2, U32>) -> IO(Result<&1, &1, U32 & String, Connection>):  do IO<Result<&1, &1, U32 & String, Connection>>:    r : Result<&1, &1, U32 & String, File> <- raw.import(bytes)    return wrap.open(r)def wrap.close(r: Result<&1, &1, (U32 & String) & File, Unit>) -> Result<&1, &1, (U32 & String) & Connection, Unit>:  match r:    case Done{u}:      Done{u}    case Fail{(error, handle)}:      Fail{(error, Connection{handle})}def close(db: Connection) -> IO(Result<&1, &1, (U32 & String) & Connection, Unit>):  Connection{handle} = db  do IO<Result<&1, &1, (U32 & String) & Connection, Unit>>:    r : Result<&1, &1, (U32 & String) & File, Unit> <- raw.close(handle)    return wrap.close(r)def finalize(stmt: Statement) -> IO(Result<&1, &1, U32 & String, Unit>):  Statement{handle} = stmt  raw.finalize(handle)def wrap.prepare(r: Result<&1, &1, U32 & String, File>) -> Result<&1, &1, U32 & String, Statement>:  match r:    case Done{handle}:      Done{Statement{handle}}    case Fail{error}:      Fail{error}def wrap.prepare.pair(pair: File & Result<&1, &1, U32 & String, File>) -> Connection & Result<&1, &1, U32 & String, Statement>:  (handle, r) = pair  (Connection{handle}, wrap.prepare(r))def prepare(resource: Connection, sql: String) -> IO(Connection & Result<&1, &1, U32 & String, Statement>):  Connection{handle} = resource  do IO<Connection & Result<&1, &1, U32 & String, Statement>>:    r : File & Result<&1, &1, U32 & String, File> <- raw.prepare(handle, sql)    return wrap.prepare.pair(r)def wrap.bind.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Statement & Result<&1, &1, U32 & String, Unit>:  (handle, r) = pair  (Statement{handle}, r)def bind(resource: Statement, index: U32, value: Value) -> IO(Statement & Result<&1, &1, U32 & String, Unit>):  Statement{handle} = resource  do IO<Statement & Result<&1, &1, U32 & String, Unit>>:    r : File & Result<&1, &1, U32 & String, Unit> <- raw.bind(handle, index, value)    return wrap.bind.pair(r)def wrap.bind_named.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Statement & Result<&1, &1, U32 & String, Unit>:  (handle, r) = pair  (Statement{handle}, r)def bind_named(resource: Statement, name: String, value: Value) -> IO(Statement & Result<&1, &1, U32 & String, Unit>):  Statement{handle} = resource  do IO<Statement & Result<&1, &1, U32 & String, Unit>>:    r : File & Result<&1, &1, U32 & String, Unit> <- raw.bind_named(handle, name, value)    return wrap.bind_named.pair(r)def wrap.step.pair(pair: File & Result<&1, &1, U32 & String, Maybe<&2, Row>>) -> Statement & Result<&1, &1, U32 & String, Maybe<&2, Row>>:  (handle, r) = pair  (Statement{handle}, r)def step(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, Maybe<&2, Row>>):  Statement{handle} = resource  do IO<Statement & Result<&1, &1, U32 & String, Maybe<&2, Row>>>:    r : File & Result<&1, &1, U32 & String, Maybe<&2, Row>> <- raw.step(handle)    return wrap.step.pair(r)def wrap.reset.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Statement & Result<&1, &1, U32 & String, Unit>:  (handle, r) = pair  (Statement{handle}, r)def reset(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, Unit>):  Statement{handle} = resource  do IO<Statement & Result<&1, &1, U32 & String, Unit>>:    r : File & Result<&1, &1, U32 & String, Unit> <- raw.reset(handle)    return wrap.reset.pair(r)def wrap.clear_bindings.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Statement & Result<&1, &1, U32 & String, Unit>:  (handle, r) = pair  (Statement{handle}, r)def clear_bindings(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, Unit>):  Statement{handle} = resource  do IO<Statement & Result<&1, &1, U32 & String, Unit>>:    r : File & Result<&1, &1, U32 & String, Unit> <- raw.clear_bindings(handle)    return wrap.clear_bindings.pair(r)def wrap.column_names.pair(pair: File & Result<&1, &1, U32 & String, List<&2, String>>) -> Statement & Result<&1, &1, U32 & String, List<&2, String>>:  (handle, r) = pair  (Statement{handle}, r)def column_names(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, List<&2, String>>):  Statement{handle} = resource  do IO<Statement & Result<&1, &1, U32 & String, List<&2, String>>>:    r : File & Result<&1, &1, U32 & String, List<&2, String>> <- raw.column_names(handle)    return wrap.column_names.pair(r)def wrap.parameter_count.pair(pair: File & Result<&1, &1, U32 & String, U32>) -> Statement & Result<&1, &1, U32 & String, U32>:  (handle, r) = pair  (Statement{handle}, r)def parameter_count(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, U32>):  Statement{handle} = resource  do IO<Statement & Result<&1, &1, U32 & String, U32>>:    r : File & Result<&1, &1, U32 & String, U32> <- raw.parameter_count(handle)    return wrap.parameter_count.pair(r)def wrap.parameter_index.pair(pair: File & Result<&1, &1, U32 & String, U32>) -> Statement & Result<&1, &1, U32 & String, U32>:  (handle, r) = pair  (Statement{handle}, r)def parameter_index(resource: Statement, name: String) -> IO(Statement & Result<&1, &1, U32 & String, U32>):  Statement{handle} = resource  do IO<Statement & Result<&1, &1, U32 & String, U32>>:    r : File & Result<&1, &1, U32 & String, U32> <- raw.parameter_index(handle, name)    return wrap.parameter_index.pair(r)def wrap.script.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Connection & Result<&1, &1, U32 & String, Unit>:  (handle, r) = pair  (Connection{handle}, r)def script(resource: Connection, sql: String) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):  Connection{handle} = resource  do IO<Connection & Result<&1, &1, U32 & String, Unit>>:    r : File & Result<&1, &1, U32 & String, Unit> <- raw.script(handle, sql)    return wrap.script.pair(r)def wrap.execute.pair(pair: File & Result<&1, &1, U32 & String, Stats>) -> Connection & Result<&1, &1, U32 & String, Stats>:  (handle, r) = pair  (Connection{handle}, r)def execute(resource: Connection, sql: String, values: List<&2, Value>) -> IO(Connection & Result<&1, &1, U32 & String, Stats>):  Connection{handle} = resource  do IO<Connection & Result<&1, &1, U32 & String, Stats>>:    r : File & Result<&1, &1, U32 & String, Stats> <- raw.execute(handle, sql, values)    return wrap.execute.pair(r)def wrap.query.pair(pair: File & Result<&1, &1, U32 & String, Rows>) -> Connection & Result<&1, &1, U32 & String, Rows>:  (handle, r) = pair  (Connection{handle}, r)def query(resource: Connection, sql: String, values: List<&2, Value>) -> IO(Connection & Result<&1, &1, U32 & String, Rows>):  Connection{handle} = resource  do IO<Connection & Result<&1, &1, U32 & String, Rows>>:    r : File & Result<&1, &1, U32 & String, Rows> <- raw.query(handle, sql, values)    return wrap.query.pair(r)def wrap.busy_timeout.pair(pair: File & Result<&1, &1, U32 & String, Unit>) -> Connection & Result<&1, &1, U32 & String, Unit>:  (handle, r) = pair  (Connection{handle}, r)def busy_timeout(resource: Connection, milliseconds: U32) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):  Connection{handle} = resource  do IO<Connection & Result<&1, &1, U32 & String, Unit>>:    r : File & Result<&1, &1, U32 & String, Unit> <- raw.busy_timeout(handle, milliseconds)    return wrap.busy_timeout.pair(r)def wrap.export.pair(pair: File & Result<&1, &1, U32 & String, List<&2, U32>>) -> Connection & Result<&1, &1, U32 & String, List<&2, U32>>:  (handle, r) = pair  (Connection{handle}, r)def export(resource: Connection) -> IO(Connection & Result<&1, &1, U32 & String, List<&2, U32>>):  Connection{handle} = resource  do IO<Connection & Result<&1, &1, U32 & String, List<&2, U32>>>:    r : File & Result<&1, &1, U32 & String, List<&2, U32>> <- raw.export(handle)    return wrap.export.pair(r)# Scoped cleanup also reclaims statements left open by the callback.def raw.dispose(handle: File) -> IO(Result<&1, &1, U32 & String, Unit>):  import "./effs/sqlite.c"  import "./effs/sqlite.js"def dispose(db: Connection) -> IO(Result<&1, &1, U32 & String, Unit>):  Connection{handle} = db  raw.dispose(handle)def begin(db: Connection) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):  script(db, "BEGIN")def commit(db: Connection) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):  script(db, "COMMIT")def rollback(db: Connection) -> IO(Connection & Result<&1, &1, U32 & String, Unit>):  script(db, "ROLLBACK")# Preserve the primary error if both the body and its cleanup fail.def cleanup.merge(-A: Type, result: Result<&1, &1, U32 & String, A>, cleanup: Result<&1, &1, U32 & String, Unit>) -> Result<&1, &1, U32 & String, A>:  match result:    case Done{value}:      match cleanup:        case Done{u}:          Done{value}        case Fail{error}:          Fail{error}    case Fail{(code, message)}:      match cleanup:        case Done{u}:          Fail{(code, message)}        case Fail{(other, detail)}:          Fail{(code, message ++ "; cleanup: " ++ detail)}def scope.connection.end(-A: Type, pair: Connection & Result<&1, &1, U32 & String, A>) -> IO(Result<&1, &1, U32 & String, A>):  (db, result) = pair  do IO<Result<&1, &1, U32 & String, A>>:    cleaned : Result<&1, &1, U32 & String, Unit> <- dispose(db)    return cleanup.merge(A, result, cleaned)def 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>):  match opened:    case Fail{error}:      IO.pure(Result<&1, &1, U32 & String, A>, Fail{error})    case Done{db}:      do IO<Result<&1, &1, U32 & String, A>>:        pair : Connection & Result<&1, &1, U32 & String, A> <- body(db)        scope.connection.end(A, pair)def with_connection(-A: Type, path: String, body: Connection -> IO(Connection & Result<&1, &1, U32 & String, A>)) -> IO(Result<&1, &1, U32 & String, A>):  do IO<Result<&1, &1, U32 & String, A>>:    opened : Result<&1, &1, U32 & String, Connection> <- open(path)    scope.connection.start(A, opened, body)def scope.statement.end(-A: Type, db: Connection, pair: Statement & Result<&1, &1, U32 & String, A>) -> IO(Connection & Result<&1, &1, U32 & String, A>):  (stmt, result) = pair  do IO<Connection & Result<&1, &1, U32 & String, A>>:    cleaned : Result<&1, &1, U32 & String, Unit> <- finalize(stmt)    return (db, cleanup.merge(A, result, cleaned))def 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>):  match pair:    case (db, Fail{error}):      IO.pure(Connection & Result<&1, &1, U32 & String, A>, (db, Fail{error}))    case (db, Done{stmt}):      do IO<Connection & Result<&1, &1, U32 & String, A>>:        answer : Statement & Result<&1, &1, U32 & String, A> <- body(stmt)        scope.statement.end(A, db, answer)def 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>):  do IO<Connection & Result<&1, &1, U32 & String, A>>:    prepared : Connection & Result<&1, &1, U32 & String, Statement> <- prepare(db, sql)    scope.statement.start(A, prepared, body)def 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>:  (db, cleaned) = pair  (db, cleanup.merge(A, result, cleaned))def transaction.committed(-A: Type, value: A, pair: Connection & Result<&1, &1, U32 & String, Unit>) -> IO(Connection & Result<&1, &1, U32 & String, A>):  match pair:    case (db, Done{u}):      IO.pure(Connection & Result<&1, &1, U32 & String, A>, (db, Done{value}))    case (db, Fail{error}):      do IO<Connection & Result<&1, &1, U32 & String, A>>:        rolled : Connection & Result<&1, &1, U32 & String, Unit> <- rollback(db)        return transaction.rolled(A, Fail{error}, rolled)def transaction.end(-A: Type, pair: Connection & Result<&1, &1, U32 & String, A>) -> IO(Connection & Result<&1, &1, U32 & String, A>):  match pair:    case (db, Done{value}):      do IO<Connection & Result<&1, &1, U32 & String, A>>:        committed : Connection & Result<&1, &1, U32 & String, Unit> <- commit(db)        transaction.committed(A, value, committed)    case (db, Fail{error}):      do IO<Connection & Result<&1, &1, U32 & String, A>>:        rolled : Connection & Result<&1, &1, U32 & String, Unit> <- rollback(db)        return transaction.rolled(A, Fail{error}, rolled)def 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>):  match pair:    case (db, Fail{error}):      IO.pure(Connection & Result<&1, &1, U32 & String, A>, (db, Fail{error}))    case (db, Done{u}):      do IO<Connection & Result<&1, &1, U32 & String, A>>:        result : Connection & Result<&1, &1, U32 & String, A> <- body(db)        transaction.end(A, result)def transaction(-A: Type, db: Connection, body: Connection -> IO(Connection & Result<&1, &1, U32 & String, A>)) -> IO(Connection & Result<&1, &1, U32 & String, A>):  do IO<Connection & Result<&1, &1, U32 & String, A>>:    begun : Connection & Result<&1, &1, U32 & String, Unit> <- begin(db)    transaction.start(A, begun, body)