rearrange functions

* Replace getrow with get_exactly_one_row, since there was no
  way to detect when another row would be available
* Introduce rowfold, which allows for iterating over all rows
  returns fromed a SQL query
* Introduce functions into the interface that were left out
This commit is contained in:
Peter McGoron 2021-10-09 22:25:47 -04:00
parent 27962fc7d2
commit 57b48639ab
2 changed files with 55 additions and 24 deletions

View File

@ -23,7 +23,7 @@ let (@>$) s f = fun x -> s x >>$ f
(* Helper definitions *) (* Helper definitions *)
let transform f (v,s) = Norm ((f v),s) let transform f (v,s) = Norm ((f v),s)
let inject v (_,s) = Norm (v,s) let inject v = transform (fun _ -> v)
let fail er (_,s) = let fail er (_,s) =
let er2 = S.finalize s let er2 = S.finalize s
in if Rc.is_success er then Failed er2 in if Rc.is_success er then Failed er2
@ -38,6 +38,12 @@ let arr_to_tbl tbl keys vals =
assert (Array.length keys = Array.length vals); assert (Array.length keys = Array.length vals);
Array.iteri (fun i x -> Hashtbl.add tbl x vals.(i)) keys Array.iteri (fun i x -> Hashtbl.add tbl x vals.(i)) keys
let create_rowtbl s rowdata =
let keys = S.row_names s
in let tbl = Hashtbl.create (Array.length keys)
in arr_to_tbl tbl keys rowdata;
tbl
(* Monadic functions *) (* Monadic functions *)
let prepare db stmt v = let prepare db stmt v =
try Norm (v,(S.prepare db stmt)) with try Norm (v,(S.prepare db stmt)) with
@ -49,19 +55,29 @@ let reprepare db stmt (v,s) =
let reset (v,s) = lift_err (S.reset s) (v,s) let reset (v,s) = lift_err (S.reset s) (v,s)
let step (v,s) = lift_err (S.step s) (v,s) let step (v,s) = lift_err (S.step s) (v,s)
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 rowfold f (v,s) =
let g x arr = f x (create_rowtbl s arr)
in let (r,x) = S.fold ~f:g ~init:v s
in if Rc.is_success r then Norm (x,s)
else stmtfail r s
let rowfold_init f i = inject i @>$ rowfold f
let iter f = rowfold_init (fun () x -> f x) ()
let rec fold f l (v,s) = match l with let rec fold f l (v,s) = match l with
| [] -> Norm (v,s) | [] -> Norm (v,s)
| h::t -> f h (v,s) >>$ fold f t | h::t -> f h (v,s) >>$ fold f t
let getrow (_,s) = let get_exactly_one_row (_,s) =
let r = S.step s let r = S.step s
in match r with in match r with
| Rc.ROW -> if S.data_count s = 0 then stmtfail Rc.NOTFOUND s | Rc.ROW -> if S.data_count s = 0 then stmtfail Rc.NOTFOUND s
else let keys = S.row_names s else let tbl = create_rowtbl s (S.row_data s)
in let tbl = Hashtbl.create (Array.length keys) in let r = S.step s
in arr_to_tbl tbl keys (S.row_data s); in if Rc.is_success r then
Norm (((),tbl),s) reset (((),tbl),s)
else stmtfail r s
| x -> stmtfail x s | x -> stmtfail x s
let exec_extract extrfun convfun ((_,arg),s) = let exec_extract extrfun convfun ((_,arg),s) =
@ -72,20 +88,12 @@ let exec_extract extrfun convfun ((_,arg),s) =
| Some x -> Norm ((x,arg), 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 = let extract name conv tu =
exec_extract (fun tbl -> Hashtbl.find_opt tbl name) conv tu exec_extract (fun tbl -> Hashtbl.find_opt tbl name) conv tu
let finalize = function let finalize = function
| Failed e -> raise (S.SqliteError (Rc.to_string e)) | Failed e -> raise (S.SqliteError (Rc.to_string e))
| Norm (v,s) -> Rc.check (S.finalize s); v | 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 exec db s = prepare db s () >>$ step
let reexec db s = reprepare db s @>$ step let reexec db s = reprepare db s @>$ step
@ -105,7 +113,7 @@ let%test "simple usage" =
>>$ extract "y" S.Data.to_int64 >>$ extract "y" S.Data.to_int64
in let$ ((v,r),s) = m in let$ ((v,r),s) = m
in s1 := [v]; in s1 := [v];
let$ ((v,r),s) = bind_values [S.Data.INT 2L] ((v,r),s) let$ ((v,_),s) = bind_values [S.Data.INT 2L] ((v,r),s)
>>$ get_exactly_one_row >>$ get_exactly_one_row
>>$ extract "y" S.Data.to_int64 >>$ extract "y" S.Data.to_int64
in s1 := v::!s1; in s1 := v::!s1;

View File

@ -1,16 +1,20 @@
(** Letsqlite main interface. *)
(** {1 Type definitions} (** {1 Type definitions}
Types used for monad composition. Types used for monad composition.
*) *)
type 'a stmt_m = type 'a stmt_m =
| Failed of Sqlite3.Rc.t | Failed of Sqlite3.Rc.t
| Norm of ('a * Sqlite3.stmt) | Norm of ('a * Sqlite3.stmt)
(** The ['a] value in the [stmt_m] is referred to as the bundled value. (** 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], 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]. or it may "return a value", in which it returns [Norm v s].
They are collectively refered to as the monad's values. They are collectively refered to as the monad's values.
The bundled {Sqlite.stmt} value is refered to as [S], and the The bundled {!Sqlite.stmt} value is refered to as [S], and the
value bundled is refered to as [V]. value bundled is refered to as [V].
{i Never directly use [Failed].} You run the likelihood of leaking {i Never directly use [Failed].} You run the likelihood of leaking
@ -33,6 +37,10 @@ val (let$) : 'a stmt_m -> ('a,'b) monad_fun -> 'b stmt_m
(** [let$ (v,s) = f (V,S) in ...] is equivalent to (** [let$ (v,s) = f (V,S) in ...] is equivalent to
[f (V,S) >>$ (fun (v,s) -> ...)]. *) [f (V,S) >>$ (fun (v,s) -> ...)]. *)
val (@>$) : ('a,'b) monad_fun -> ('b,'c) monad_fun -> ('a,'c) monad_fun
(** [f @>$ g] composes to monadic functions so they become another
monadic function. *)
val (>-$) : 'a stmt_m -> ('a,'b) monad_fun -> 'a stmt_m val (>-$) : 'a stmt_m -> ('a,'b) monad_fun -> 'a stmt_m
(** [>-$] is the passthrough function. (** [>-$] is the passthrough function.
@ -47,13 +55,16 @@ These functions do not execute any SQL queries. They exist to make dealing
with the monad easier. with the monad easier.
*) *)
val transform : ('a -> 'b) -> ('a,'b) monad_fun
(** [transform f] maps [V] to [f V]. *)
val inject : 'a -> ('b,'a) monad_fun val inject : 'a -> ('b,'a) monad_fun
(** [inject x] returns [Norm x S], discarding [V]. *) (** [inject x] returns [Norm x S], discarding [V]. *)
val fail : Sqlite3.Rc.t -> ('a,'b) monad_fun val fail : Sqlite3.Rc.t -> ('a,'b) monad_fun
(** [fail e] finalizes [S] and returns [Failure], where the error (** [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 code is [e] if [e] is not a success error code, and the return code
of {finalize} otherwise. of {!Sqlite3.finalize} otherwise.
*) *)
val stmtfail : Sqlite3.Rc.t -> Sqlite3.stmt -> unit stmt_m val stmtfail : Sqlite3.Rc.t -> Sqlite3.stmt -> unit stmt_m
@ -73,7 +84,7 @@ val lift_err : Sqlite3.Rc.t -> ('a,'a) monad_fun
(** {1 Execution functions} (** {1 Execution functions}
All of these functions will (effectively) exceute {fail} and return All of these functions will (effectively) exceute {!fail} and return
[Failed] on failure. All functions will omit error behavior unless [Failed] on failure. All functions will omit error behavior unless
there is something different across any of them. *) there is something different across any of them. *)
@ -90,20 +101,32 @@ val reset : ('a,'a) monad_fun
(** [reset] resets [S] so that it may be executed again. *) (** [reset] resets [S] so that it may be executed again. *)
val step : ('a,'a) monad_fun val step : ('a,'a) monad_fun
(** [step] is equivalent to the Sqlite3 function {step}. (** [step] is equivalent to {!Sqlite3.step}.
{b NOTE}: [step] will fail when the statement returns a row. {b NOTE}: [step] will fail when the statement returns a row.
*) *)
val rowfold : ('a -> rowdata -> 'a) -> ('a,'a) monad_fun
(** [rowfold f] iterates over each row returns by [S] and applies
[f] to it, returning some value. *)
val rowfold_init : ('a -> rowdata -> 'a) -> 'a -> ('b,'a) monad_fun
(** [rowfold_initf i] changes [V] to [i] and then applies [rowfold]. *)
val iter : (rowdata -> unit) -> ('a,unit) monad_fun
(** [rowfold_unit f] is equivalent to [rowfold_init f ()] but [f]
is not passed a unit argument. *)
val fold : ('a -> ('b,'b) monad_fun) -> 'a list -> ('b,'b) monad_fun 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]. *) (** [fold f l] applies each element of [l] to make a monadic function [f]. *)
val getrow : ('a,(unit * rowdata)) monad_fun val get_exactly_one_row : ('a,(unit * rowdata)) monad_fun
(** [getrow] steps [S] and stores the returned row. The value in [S] is (** [get_exactly_one_row] steps [S] and stores the returned row. The
discarded. value in [S] is discarded. The function fails if there is not exactly
one row.
The function returns a tuple with the unit type to interact with The function returns a tuple with the unit type to interact with
{extract}. {!extract}.
*) *)
val exec_extract : ('a -> 'b option) -> ('b -> 'c option) val exec_extract : ('a -> 'b option) -> ('b -> 'c option)
@ -132,7 +155,7 @@ val clear_bindings : ('a,'a) monad_fun
(** [clear_bindings] clears any values bound to [S]. *) (** [clear_bindings] clears any values bound to [S]. *)
val exec : Sqlite3.db -> string -> unit stmt_m val exec : Sqlite3.db -> string -> unit stmt_m
(** [exec db s] is equivalent to a call to {prepare} followed by {step}. *) (** [exec db s] is equivalent to a call to {!prepare} followed by {!step}. *)
val reexec : Sqlite3.db -> string -> ('a,'a) monad_fun val reexec : Sqlite3.db -> string -> ('a,'a) monad_fun
(** [reexec db s] is equivalent to [reprepare >>$ step]. *) (** [reexec db s] is equivalent to [reprepare >>$ step]. *)