1 # SQLite connections, typed values, prepared statements and transactions for C and Wasm. 2 import Base 3 4 type Connection is Type: 5 Connection{handle: File} 6 7 type Statement is Type: 8 Statement{handle: File} 9 10 type I64 is Data: 11 I64{lo: U32, hi: U32} 12 13 type F64 is Data: 14 F64{lo: U32, hi: U32} 15 16 type Value is Data: 17 Null{} 18 Integer{value: I64} 19 Real{value: F64} 20 Text{value: String} 21 Blob{bytes: List<&2, U32>} 22 23 type Row is Data: 24 Row{values: List<&2, Value>} 25 26 type Rows is Data: 27 Rows{columns: List<&2, String>, rows: List<&2, Row>} 28 29 type Stats is Data: 30 Stats{changes: I64, last_insert_rowid: I64} 31 32 def I64.from_u32(n: U32) -> I64: 33 I64{n, 0} 34 35 def 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 40 def 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 44 def 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 51 def 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. 56 def 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 64 def 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 75 def 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 82 def 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 89 def 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 96 def 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 103 def 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 110 def 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 126 def 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. 136 def F64.normal(sign: U32, exponent: U32, +fraction: U32) -> F64: 137 F64{(fraction << 29n : U32), (sign .|. (exponent << 20n) .|. (fraction >> 3n) : U32)} 138 139 def 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 151 def 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 158 def 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 167 def 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 171 def 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 178 def Row.get(row: Row, index: U32) -> Maybe<&2, Value>: 179 Row{values} = row 180 Row.get.go(U32.to_nat(index), values) 181 182 def 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 189 def raw.open(path: String, readonly: Bool) -> IO(Result<&1, &1, U32 & String, File>): 190 import "./effs/sqlite.c" 191 import "./effs/sqlite.js" 192 193 def raw.import(bytes: List<&2, U32>) -> IO(Result<&1, &1, U32 & String, File>): 194 import "./effs/sqlite.c" 195 import "./effs/sqlite.js" 196 197 def raw.close(handle: File) -> IO(Result<&1, &1, (U32 & String) & File, Unit>): 198 import "./effs/sqlite.c" 199 import "./effs/sqlite.js" 200 201 def raw.finalize(handle: File) -> IO(Result<&1, &1, U32 & String, Unit>): 202 import "./effs/sqlite.c" 203 import "./effs/sqlite.js" 204 205 def 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 209 def 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 213 def 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 217 def 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 221 def raw.reset(handle: File) -> IO(File & Result<&1, &1, U32 & String, Unit>): 222 import "./effs/sqlite.c" 223 import "./effs/sqlite.js" 224 225 def raw.clear_bindings(handle: File) -> IO(File & Result<&1, &1, U32 & String, Unit>): 226 import "./effs/sqlite.c" 227 import "./effs/sqlite.js" 228 229 def 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 233 def raw.parameter_count(handle: File) -> IO(File & Result<&1, &1, U32 & String, U32>): 234 import "./effs/sqlite.c" 235 import "./effs/sqlite.js" 236 237 def 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 241 def 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 245 def 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 249 def 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 253 def 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 257 def 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 261 def 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 268 def open(path: String) -> IO(Result<&1, &1, U32 & String, Connection>): 269 do IO>: 270 r : Result<&1, &1, U32 & String, File> <- raw.open(path, False{}) 271 return wrap.open(r) 272 273 def open_readonly(path: String) -> IO(Result<&1, &1, U32 & String, Connection>): 274 do IO>: 275 r : Result<&1, &1, U32 & String, File> <- raw.open(path, True{}) 276 return wrap.open(r) 277 278 def memory() -> IO(Result<&1, &1, U32 & String, Connection>): 279 open(":memory:") 280 281 def from_bytes(bytes: List<&2, U32>) -> IO(Result<&1, &1, U32 & String, Connection>): 282 do IO>: 283 r : Result<&1, &1, U32 & String, File> <- raw.import(bytes) 284 return wrap.open(r) 285 286 def 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 293 def close(db: Connection) -> IO(Result<&1, &1, (U32 & String) & Connection, Unit>): 294 Connection{handle} = db 295 do IO>: 296 r : Result<&1, &1, (U32 & String) & File, Unit> <- raw.close(handle) 297 return wrap.close(r) 298 299 def finalize(stmt: Statement) -> IO(Result<&1, &1, U32 & String, Unit>): 300 Statement{handle} = stmt 301 raw.finalize(handle) 302 303 def 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 310 def 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 314 def prepare(resource: Connection, sql: String) -> IO(Connection & Result<&1, &1, U32 & String, Statement>): 315 Connection{handle} = resource 316 do IO>: 317 r : File & Result<&1, &1, U32 & String, File> <- raw.prepare(handle, sql) 318 return wrap.prepare.pair(r) 319 320 def 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 324 def bind(resource: Statement, index: U32, value: Value) -> IO(Statement & Result<&1, &1, U32 & String, Unit>): 325 Statement{handle} = resource 326 do IO>: 327 r : File & Result<&1, &1, U32 & String, Unit> <- raw.bind(handle, index, value) 328 return wrap.bind.pair(r) 329 330 def 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 334 def bind_named(resource: Statement, name: String, value: Value) -> IO(Statement & Result<&1, &1, U32 & String, Unit>): 335 Statement{handle} = resource 336 do IO>: 337 r : File & Result<&1, &1, U32 & String, Unit> <- raw.bind_named(handle, name, value) 338 return wrap.bind_named.pair(r) 339 340 def 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 344 def step(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, Maybe<&2, Row>>): 345 Statement{handle} = resource 346 do IO>>: 347 r : File & Result<&1, &1, U32 & String, Maybe<&2, Row>> <- raw.step(handle) 348 return wrap.step.pair(r) 349 350 def 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 354 def reset(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, Unit>): 355 Statement{handle} = resource 356 do IO>: 357 r : File & Result<&1, &1, U32 & String, Unit> <- raw.reset(handle) 358 return wrap.reset.pair(r) 359 360 def 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 364 def clear_bindings(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, Unit>): 365 Statement{handle} = resource 366 do IO>: 367 r : File & Result<&1, &1, U32 & String, Unit> <- raw.clear_bindings(handle) 368 return wrap.clear_bindings.pair(r) 369 370 def 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 374 def column_names(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, List<&2, String>>): 375 Statement{handle} = resource 376 do IO>>: 377 r : File & Result<&1, &1, U32 & String, List<&2, String>> <- raw.column_names(handle) 378 return wrap.column_names.pair(r) 379 380 def 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 384 def parameter_count(resource: Statement) -> IO(Statement & Result<&1, &1, U32 & String, U32>): 385 Statement{handle} = resource 386 do IO>: 387 r : File & Result<&1, &1, U32 & String, U32> <- raw.parameter_count(handle) 388 return wrap.parameter_count.pair(r) 389 390 def 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 394 def parameter_index(resource: Statement, name: String) -> IO(Statement & Result<&1, &1, U32 & String, U32>): 395 Statement{handle} = resource 396 do IO>: 397 r : File & Result<&1, &1, U32 & String, U32> <- raw.parameter_index(handle, name) 398 return wrap.parameter_index.pair(r) 399 400 def 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 404 def script(resource: Connection, sql: String) -> IO(Connection & Result<&1, &1, U32 & String, Unit>): 405 Connection{handle} = resource 406 do IO>: 407 r : File & Result<&1, &1, U32 & String, Unit> <- raw.script(handle, sql) 408 return wrap.script.pair(r) 409 410 def 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 414 def execute(resource: Connection, sql: String, values: List<&2, Value>) -> IO(Connection & Result<&1, &1, U32 & String, Stats>): 415 Connection{handle} = resource 416 do IO>: 417 r : File & Result<&1, &1, U32 & String, Stats> <- raw.execute(handle, sql, values) 418 return wrap.execute.pair(r) 419 420 def 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 424 def query(resource: Connection, sql: String, values: List<&2, Value>) -> IO(Connection & Result<&1, &1, U32 & String, Rows>): 425 Connection{handle} = resource 426 do IO>: 427 r : File & Result<&1, &1, U32 & String, Rows> <- raw.query(handle, sql, values) 428 return wrap.query.pair(r) 429 430 def 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 434 def busy_timeout(resource: Connection, milliseconds: U32) -> IO(Connection & Result<&1, &1, U32 & String, Unit>): 435 Connection{handle} = resource 436 do IO>: 437 r : File & Result<&1, &1, U32 & String, Unit> <- raw.busy_timeout(handle, milliseconds) 438 return wrap.busy_timeout.pair(r) 439 440 def 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 444 def export(resource: Connection) -> IO(Connection & Result<&1, &1, U32 & String, List<&2, U32>>): 445 Connection{handle} = resource 446 do IO>>: 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. 451 def raw.dispose(handle: File) -> IO(Result<&1, &1, U32 & String, Unit>): 452 import "./effs/sqlite.c" 453 import "./effs/sqlite.js" 454 455 def dispose(db: Connection) -> IO(Result<&1, &1, U32 & String, Unit>): 456 Connection{handle} = db 457 raw.dispose(handle) 458 459 def begin(db: Connection) -> IO(Connection & Result<&1, &1, U32 & String, Unit>): 460 script(db, "BEGIN") 461 462 def commit(db: Connection) -> IO(Connection & Result<&1, &1, U32 & String, Unit>): 463 script(db, "COMMIT") 464 465 def 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. 469 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>: 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 484 def 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>: 487 cleaned : Result<&1, &1, U32 & String, Unit> <- dispose(db) 488 return cleanup.merge(A, result, cleaned) 489 490 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>): 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>: 496 pair : Connection & Result<&1, &1, U32 & String, A> <- body(db) 497 scope.connection.end(A, pair) 498 499 def 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>: 501 opened : Result<&1, &1, U32 & String, Connection> <- open(path) 502 scope.connection.start(A, opened, body) 503 504 def 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>: 507 cleaned : Result<&1, &1, U32 & String, Unit> <- finalize(stmt) 508 return (db, cleanup.merge(A, result, cleaned)) 509 510 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>): 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>: 516 answer : Statement & Result<&1, &1, U32 & String, A> <- body(stmt) 517 scope.statement.end(A, db, answer) 518 519 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>): 520 do IO>: 521 prepared : Connection & Result<&1, &1, U32 & String, Statement> <- prepare(db, sql) 522 scope.statement.start(A, prepared, body) 523 524 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>: 525 (db, cleaned) = pair 526 (db, cleanup.merge(A, result, cleaned)) 527 528 def 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>: 534 rolled : Connection & Result<&1, &1, U32 & String, Unit> <- rollback(db) 535 return transaction.rolled(A, Fail{error}, rolled) 536 537 def 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>: 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>: 545 rolled : Connection & Result<&1, &1, U32 & String, Unit> <- rollback(db) 546 return transaction.rolled(A, Fail{error}, rolled) 547 548 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>): 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>: 554 result : Connection & Result<&1, &1, U32 & String, A> <- body(db) 555 transaction.end(A, result) 556 557 def 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>: 559 begun : Connection & Result<&1, &1, U32 & String, Unit> <- begin(db) 560 transaction.start(A, begun, body)