move over stuff from old project
This commit is contained in:
commit
8c8496da46
|
@ -0,0 +1,7 @@
|
||||||
|
(library
|
||||||
|
(name libunderwriter)
|
||||||
|
(inline_tests)
|
||||||
|
(preprocess (pps ppx_inline_test))
|
||||||
|
(flags -rectypes)
|
||||||
|
(libraries sqlite3 letsqlite)
|
||||||
|
)
|
|
@ -0,0 +1,18 @@
|
||||||
|
(lang dune 2.9)
|
||||||
|
(name letsqlite)
|
||||||
|
(generate_opam_files true)
|
||||||
|
(license CC0-1.0)
|
||||||
|
(authors "Peter McGoron")
|
||||||
|
(maintainers "code@mcgoron.com")
|
||||||
|
(package
|
||||||
|
(name letsqlite)
|
||||||
|
(synopsis "Simple monadic Sqlite3 wrapper")
|
||||||
|
(description "Simple monadic Sqlite3 wrapper")
|
||||||
|
(homepage "https://software.mcgoron.com/peter/letsqlite")
|
||||||
|
(bug_reports "code@mcgoron.com")
|
||||||
|
(depends
|
||||||
|
(dune (>= 2.9))
|
||||||
|
(sqlite3 (>= 5.0.3))
|
||||||
|
(ppx_inline_test (>= 0.14.1))
|
||||||
|
)
|
||||||
|
)
|
|
@ -0,0 +1,114 @@
|
||||||
|
module S = Sqlite3
|
||||||
|
module Rc = S.Rc
|
||||||
|
|
||||||
|
type 'a stmt_m =
|
||||||
|
| Failed of Rc.t
|
||||||
|
| Norm of ('a * S.stmt)
|
||||||
|
|
||||||
|
type ('a,'b) monad_fun = ('a * Sqlite3.stmt) -> 'b stmt_m
|
||||||
|
type rowdata = (string, Sqlite3.Data.t) Hashtbl.t
|
||||||
|
(* Monad definitions *)
|
||||||
|
let (>>$) s f = match s with
|
||||||
|
| Failed r -> Failed r
|
||||||
|
| Norm (x,s) -> f (x,s)
|
||||||
|
|
||||||
|
let (let$) = (>>$)
|
||||||
|
let (>-$) s f = match s with
|
||||||
|
| Failed r -> Failed r
|
||||||
|
| Norm (x,s) -> (match f (x,s) with
|
||||||
|
| Failed r -> Failed r
|
||||||
|
| Norm (_,s) -> Norm (x,s)
|
||||||
|
)
|
||||||
|
let (@>$) s f = fun x -> s x >>$ f
|
||||||
|
|
||||||
|
(* Helper definitions *)
|
||||||
|
let transform f (v,s) = Norm ((f v),s)
|
||||||
|
let inject v (_,s) = Norm (v,s)
|
||||||
|
let fail er (_,s) =
|
||||||
|
let er2 = S.finalize s
|
||||||
|
in if Rc.is_success er then Failed er2
|
||||||
|
else Failed er
|
||||||
|
let stmtfail er s = fail er ((),s)
|
||||||
|
let lift_err e tp =
|
||||||
|
if Rc.is_success e then Norm tp
|
||||||
|
else fail e tp
|
||||||
|
|
||||||
|
(* Helper functions *)
|
||||||
|
let arr_to_tbl tbl keys vals =
|
||||||
|
assert (Array.length keys = Array.length vals);
|
||||||
|
Array.iteri (fun i x -> Hashtbl.add tbl x vals.(i)) keys
|
||||||
|
|
||||||
|
(* Monadic functions *)
|
||||||
|
let prepare db stmt v =
|
||||||
|
try Norm (v,(S.prepare db stmt)) with
|
||||||
|
_ -> Failed Rc.ERROR
|
||||||
|
let reprepare db stmt (v,s) =
|
||||||
|
let x = S.finalize s
|
||||||
|
in if Rc.is_success x then prepare db stmt v
|
||||||
|
else Failed x
|
||||||
|
|
||||||
|
let reset (v,s) = lift_err (S.reset s) (v,s)
|
||||||
|
let step (v,s) = lift_err (S.step s) (v,s)
|
||||||
|
|
||||||
|
let rec fold f l (v,s) = match l with
|
||||||
|
| [] -> Norm (v,s)
|
||||||
|
| h::t -> f h (v,s) >>$ fold f t
|
||||||
|
|
||||||
|
let getrow (_,s) =
|
||||||
|
let r = S.step s
|
||||||
|
in match r with
|
||||||
|
| Rc.ROW -> if S.data_count s = 0 then stmtfail Rc.NOTFOUND s
|
||||||
|
else let keys = S.row_names s
|
||||||
|
in let tbl = Hashtbl.create (Array.length keys)
|
||||||
|
in arr_to_tbl tbl keys (S.row_data s);
|
||||||
|
Norm (((),tbl),s)
|
||||||
|
| x -> stmtfail x s
|
||||||
|
|
||||||
|
let exec_extract extrfun convfun ((_,arg),s) =
|
||||||
|
match extrfun arg with
|
||||||
|
| None -> stmtfail Rc.NOTFOUND s
|
||||||
|
| Some x -> (match convfun x with
|
||||||
|
| None -> stmtfail Rc.NOTFOUND s
|
||||||
|
| Some x -> Norm ((x,arg), s)
|
||||||
|
)
|
||||||
|
|
||||||
|
let get_exactly_one_row tr =
|
||||||
|
let$ (v,s) = getrow tr
|
||||||
|
in let r = S.step s
|
||||||
|
in if Rc.is_success r then reset (v,s)
|
||||||
|
else Failed r
|
||||||
|
|
||||||
|
let extract name conv tu =
|
||||||
|
exec_extract (fun tbl -> Hashtbl.find_opt tbl name) conv tu
|
||||||
|
|
||||||
|
let finalize = function
|
||||||
|
| Failed e -> raise (S.SqliteError (Rc.to_string e))
|
||||||
|
| Norm (v,s) -> Rc.check (S.finalize s); v
|
||||||
|
let bind_values l (v,s) = lift_err (S.bind_values s l) (v,s)
|
||||||
|
let clear_bindings (v,s) = lift_err (S.clear_bindings s) (v,s)
|
||||||
|
|
||||||
|
let exec db s = prepare db s () >>$ step
|
||||||
|
let reexec db s = reprepare db s @>$ step
|
||||||
|
let bsrc l = bind_values l @>$ step @>$ reset @>$ clear_bindings
|
||||||
|
|
||||||
|
let%test "simple usage" =
|
||||||
|
let db = S.db_open ":memory:"
|
||||||
|
in let s1 = ref []
|
||||||
|
in let fin = (
|
||||||
|
let m = exec db "CREATE TABLE tbl (x,y);"
|
||||||
|
>>$ reprepare db "INSERT INTO tbl VALUES(?,?);"
|
||||||
|
>>$ bsrc [S.Data.INT 1L; S.Data.INT 2L]
|
||||||
|
>>$ bsrc [S.Data.INT 2L; S.Data.INT 3L]
|
||||||
|
>>$ reprepare db "SELECT y FROM tbl WHERE x = ?;"
|
||||||
|
>>$ bind_values [S.Data.INT 1L]
|
||||||
|
>>$ get_exactly_one_row
|
||||||
|
>>$ extract "y" S.Data.to_int64
|
||||||
|
in let$ ((v,r),s) = m
|
||||||
|
in s1 := [v];
|
||||||
|
let$ ((v,r),s) = bind_values [S.Data.INT 2L] ((v,r),s)
|
||||||
|
>>$ get_exactly_one_row
|
||||||
|
>>$ extract "y" S.Data.to_int64
|
||||||
|
in s1 := v::!s1;
|
||||||
|
Norm ((),s)
|
||||||
|
)
|
||||||
|
in finalize fin; S.db_close db && !s1 = [3L; 2L]
|
|
@ -0,0 +1,141 @@
|
||||||
|
(** {1 Type definitions}
|
||||||
|
|
||||||
|
Types used for monad composition.
|
||||||
|
*)
|
||||||
|
type 'a stmt_m =
|
||||||
|
| Failed of Sqlite3.Rc.t
|
||||||
|
| Norm of ('a * Sqlite3.stmt)
|
||||||
|
(** The ['a] value in the [stmt_m] is referred to as the bundled value.
|
||||||
|
A function may "fail with [e]", in which it returns [Failed e],
|
||||||
|
or it may "return a value", in which it returns [Norm v s].
|
||||||
|
They are collectively refered to as the monad's values.
|
||||||
|
|
||||||
|
The bundled {Sqlite.stmt} value is refered to as [S], and the
|
||||||
|
value bundled is refered to as [V].
|
||||||
|
|
||||||
|
{i Never directly use [Failed].} You run the likelihood of leaking
|
||||||
|
memory.
|
||||||
|
*)
|
||||||
|
|
||||||
|
type ('a,'b) monad_fun = ('a * Sqlite3.stmt) -> 'b stmt_m
|
||||||
|
|
||||||
|
type rowdata = (string, Sqlite3.Data.t) Hashtbl.t
|
||||||
|
|
||||||
|
(** {1 Primary Functions} *)
|
||||||
|
|
||||||
|
val (>>$) : 'a stmt_m -> ('a,'b) monad_fun -> 'b stmt_m
|
||||||
|
(** [>>$] is the monadic composition function.
|
||||||
|
|
||||||
|
[s >>$ f] return [s] if [s] is [Failed] and [f V S] otherwise.
|
||||||
|
*)
|
||||||
|
|
||||||
|
val (let$) : 'a stmt_m -> ('a,'b) monad_fun -> 'b stmt_m
|
||||||
|
(** [let$ (v,s) = f (V,S) in ...] is equivalent to
|
||||||
|
[f (V,S) >>$ (fun (v,s) -> ...)]. *)
|
||||||
|
|
||||||
|
val (>-$) : 'a stmt_m -> ('a,'b) monad_fun -> 'a stmt_m
|
||||||
|
(** [>-$] is the passthrough function.
|
||||||
|
|
||||||
|
[s >-$ f] is equivalent to [s >>$ f] except that the bundled
|
||||||
|
value from [f (V,S)] is discarded and replaced with [V]. This function
|
||||||
|
passes the original [V] through.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** {1 Non-Execution Functions}
|
||||||
|
|
||||||
|
These functions do not execute any SQL queries. They exist to make dealing
|
||||||
|
with the monad easier.
|
||||||
|
*)
|
||||||
|
|
||||||
|
val inject : 'a -> ('b,'a) monad_fun
|
||||||
|
(** [inject x] returns [Norm x S], discarding [V]. *)
|
||||||
|
|
||||||
|
val fail : Sqlite3.Rc.t -> ('a,'b) monad_fun
|
||||||
|
(** [fail e] finalizes [S] and returns [Failure], where the error
|
||||||
|
code is [e] if [e] is not a success error code, and the return code
|
||||||
|
of {finalize} otherwise.
|
||||||
|
*)
|
||||||
|
|
||||||
|
val stmtfail : Sqlite3.Rc.t -> Sqlite3.stmt -> unit stmt_m
|
||||||
|
(** [stmtfail e s] is equivalent to [fail] except it assembles a statement
|
||||||
|
with unit type. *)
|
||||||
|
|
||||||
|
val lift_err : Sqlite3.Rc.t -> ('a,'a) monad_fun
|
||||||
|
(** [lift_err e] is equivalent to [fail e] when [e] is not a success
|
||||||
|
value, and otherwise returns the monad's values unchanged.
|
||||||
|
|
||||||
|
{b NOTE}: You may be tempted to define a function like
|
||||||
|
[lift : (Sqlite3.stmt -> Sqlite3.Rc.t) -> ('a,'a) monad_fun]
|
||||||
|
which would take a function and apply [lift_err (f S) V S]
|
||||||
|
to the function. However, the partial application [lift f]
|
||||||
|
would leave you with a {i weak type}, which will force your
|
||||||
|
type to have a fixed type. *)
|
||||||
|
|
||||||
|
(** {1 Execution functions}
|
||||||
|
|
||||||
|
All of these functions will (effectively) exceute {fail} and return
|
||||||
|
[Failed] on failure. All functions will omit error behavior unless
|
||||||
|
there is something different across any of them. *)
|
||||||
|
|
||||||
|
val prepare : Sqlite3.db -> string -> 'a -> 'a stmt_m
|
||||||
|
(** [prepare db stmt v] returns a monad with bundled value [v] and
|
||||||
|
the statement in [s].
|
||||||
|
*)
|
||||||
|
|
||||||
|
val reprepare : Sqlite3.db -> string -> ('a,'a) monad_fun
|
||||||
|
(** [reprepare db stmt] finalizes [S] and prepares [stmt] in
|
||||||
|
its place. *)
|
||||||
|
|
||||||
|
val reset : ('a,'a) monad_fun
|
||||||
|
(** [reset] resets [S] so that it may be executed again. *)
|
||||||
|
|
||||||
|
val step : ('a,'a) monad_fun
|
||||||
|
(** [step] is equivalent to the Sqlite3 function {step}.
|
||||||
|
|
||||||
|
{b NOTE}: [step] will fail when the statement returns a row.
|
||||||
|
*)
|
||||||
|
|
||||||
|
val fold : ('a -> ('b,'b) monad_fun) -> 'a list -> ('b,'b) monad_fun
|
||||||
|
(** [fold f l] applies each element of [l] to make a monadic function [f]. *)
|
||||||
|
|
||||||
|
val getrow : ('a,(unit * rowdata)) monad_fun
|
||||||
|
(** [getrow] steps [S] and stores the returned row. The value in [S] is
|
||||||
|
discarded.
|
||||||
|
|
||||||
|
The function returns a tuple with the unit type to interact with
|
||||||
|
{extract}.
|
||||||
|
*)
|
||||||
|
|
||||||
|
val exec_extract : ('a -> 'b option) -> ('b -> 'c option)
|
||||||
|
-> (('d * 'a),('c * 'a)) monad_fun
|
||||||
|
(** [exec_extract extr conv] extracts a value from the second element in
|
||||||
|
[V], and then applies [conv] to it. If both were successful, the
|
||||||
|
function replaces the first element of [V] with the converted
|
||||||
|
value. *)
|
||||||
|
|
||||||
|
val extract : string -> (Sqlite3.Data.t -> 'b option)
|
||||||
|
-> (('a * rowdata),('b * rowdata)) monad_fun
|
||||||
|
(** [extract name f] finds the value in the row with header name [name]
|
||||||
|
and applies [f] to it. If [f] succeeds then its return value is
|
||||||
|
stored in [V]. The rowdata is preserved.
|
||||||
|
*)
|
||||||
|
|
||||||
|
val finalize : 'a stmt_m -> 'a
|
||||||
|
(** [finalize] raises an exception if the execution failed, and [V]
|
||||||
|
otherwise. *)
|
||||||
|
|
||||||
|
val bind_values : Sqlite3.Data.t list -> ('a,'a) monad_fun
|
||||||
|
(** [bind_values l] binds each index of [l] to a positional parameter
|
||||||
|
in [S]. *)
|
||||||
|
|
||||||
|
val clear_bindings : ('a,'a) monad_fun
|
||||||
|
(** [clear_bindings] clears any values bound to [S]. *)
|
||||||
|
|
||||||
|
val exec : Sqlite3.db -> string -> unit stmt_m
|
||||||
|
(** [exec db s] is equivalent to a call to {prepare} followed by {step}. *)
|
||||||
|
|
||||||
|
val reexec : Sqlite3.db -> string -> ('a,'a) monad_fun
|
||||||
|
(** [reexec db s] is equivalent to [reprepare >>$ step]. *)
|
||||||
|
|
||||||
|
val bsrc : Sqlite3.Data.t list -> ('a,'a) monad_fun
|
||||||
|
(** [bsrc l] is equivalent to [bind_values >>$ step >>$ reset >>$ clear_bindings]. *)
|
|
@ -0,0 +1,31 @@
|
||||||
|
# This file is generated by dune, edit dune-project instead
|
||||||
|
opam-version: "2.0"
|
||||||
|
synopsis: "Simple monadic Sqlite3 wrapper"
|
||||||
|
description: "Simple monadic Sqlite3 wrapper"
|
||||||
|
maintainer: ["code@mcgoron.com"]
|
||||||
|
authors: ["Peter McGoron"]
|
||||||
|
license: "CC0-1.0"
|
||||||
|
homepage: "https://software.mcgoron.com/peter/letsqlite"
|
||||||
|
bug-reports: "code@mcgoron.com"
|
||||||
|
depends: [
|
||||||
|
"dune" {>= "2.9" & >= "2.9"}
|
||||||
|
"sqlite3" {>= "5.0.3"}
|
||||||
|
"ppx_inline_test" {>= "0.14.1"}
|
||||||
|
"odoc" {with-doc}
|
||||||
|
]
|
||||||
|
build: [
|
||||||
|
["dune" "subst"] {dev}
|
||||||
|
[
|
||||||
|
"dune"
|
||||||
|
"build"
|
||||||
|
"-p"
|
||||||
|
name
|
||||||
|
"-j"
|
||||||
|
jobs
|
||||||
|
"--promote-install-files=false"
|
||||||
|
"@install"
|
||||||
|
"@runtest" {with-test}
|
||||||
|
"@doc" {with-doc}
|
||||||
|
]
|
||||||
|
["dune" "install" "-p" name "--create-install-files" name]
|
||||||
|
]
|
Loading…
Reference in New Issue