mte/unikernel/duniverse/ocaml-caqti/examples/bikereg.ml
2025-11-11 02:07:51 +01:00

168 lines
5.8 KiB
OCaml

(* Copyright (C) 2014--2025 Petter A. Urkedal <paurkedal@gmail.com>
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version, with the LGPL-3.0 Linking Exception.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* and the LGPL-3.0 Linking Exception along with this library. If not, see
* <http://www.gnu.org/licenses/> and <https://spdx.org>, respectively.
*)
(* This is an example of how to use Caqti directly in application code.
* This way of defining typed wrappers around queries should also work for
* code-generators. *)
open Lwt.Infix
(* This is an example of a custom type, but it is experimental for now. The
* alternative is to use plain tuples, as the other queries below, and coverting
* in client code. *)
module Bike = struct
type t = {
frameno: string;
owner: string;
stolen: Ptime.t option;
}
end
(* Query Strings
* =============
*
* Queries are normally defined in advance. This allows Caqti to emit
* prepared queries which are reused throughout the lifetime of the
* connection. *)
module Q = struct
open Caqti_template.Create
let bike =
let open Bike in
let intro frameno owner stolen = Ok {frameno; owner; stolen} in
let open Caqti_template.Row_type in
product intro
@@ proj string (fun bike -> bike.frameno)
@@ proj string (fun bike -> bike.owner)
@@ proj (option ptime) (fun bike -> bike.stolen)
@@ proj_end
let create_bikereg =
direct T.(unit -->. unit)
{eos|
CREATE TEMPORARY TABLE bikereg (
frameno text NOT NULL,
owner text NOT NULL,
stolen timestamp NULL
)
|eos}
let reg_bike =
static T.(t2 string string -->. unit)
"INSERT INTO bikereg (frameno, owner) VALUES (?, ?)"
let report_stolen =
static T.(string -->. unit)
"UPDATE bikereg SET stolen = current_timestamp WHERE frameno = ?"
let select_stolen =
static T.(unit -->* bike)
"SELECT * FROM bikereg WHERE NOT stolen IS NULL"
let select_owner =
static T.(string -->? string)
"SELECT owner FROM bikereg WHERE frameno = ?"
end
(* Wrappers around the Generic Execution Functions
* ===============================================
*
* Here we combine the above queries with a suitable execution function, for
* convenience and to enforce type safety. We could have defined these in a
* functor on CONNECTION and used the resulting module in place of Db. *)
(* Db.exec runs a statement which must not return any rows. Errors are
* reported as exceptions. *)
let create_bikereg (module Db : Caqti_lwt.CONNECTION) =
Db.exec Q.create_bikereg ()
let reg_bike (module Db : Caqti_lwt.CONNECTION) frameno owner =
Db.exec Q.reg_bike (frameno, owner)
let report_stolen (module Db : Caqti_lwt.CONNECTION) frameno =
Db.exec Q.report_stolen frameno
(* Db.find runs a query which must return at most one row. The result is a
* option, since it's common to seach for entries which don't exist. *)
let find_bike_owner frameno (module Db : Caqti_lwt.CONNECTION) =
Db.find_opt Q.select_owner frameno
(* Db.iter_s iterates sequentially over the set of result rows of a query. *)
let iter_s_stolen (module Db : Caqti_lwt.CONNECTION) f =
Db.iter_s Q.select_stolen f ()
(* There is also a Db.iter_p for parallel processing, and Db.fold and
* Db.fold_s for accumulating information from the result rows. *)
(* Test Code
* ========= *)
let (>>=?) m f =
m >>= (function | Ok x -> f x | Error err -> Lwt.return (Error err))
let test db =
(* Examples of statement execution: Create and populate the register. *)
create_bikereg db >>=? fun () ->
reg_bike db "BIKE-0000" "Arthur Dent" >>=? fun () ->
reg_bike db "BIKE-0001" "Ford Prefect" >>=? fun () ->
reg_bike db "BIKE-0002" "Zaphod Beeblebrox" >>=? fun () ->
reg_bike db "BIKE-0003" "Trillian" >>=? fun () ->
reg_bike db "BIKE-0004" "Marvin" >>=? fun () ->
report_stolen db "BIKE-0000" >>=? fun () ->
report_stolen db "BIKE-0004" >>=? fun () ->
(* Examples of single-row queries. *)
let show_owner frameno =
find_bike_owner frameno db >>=? fun owner_opt ->
(match owner_opt with
| Some owner -> Lwt_io.printf "%s is owned by %s.\n" frameno owner
| None -> Lwt_io.printf "%s is not registered.\n" frameno)
>>= Lwt.return_ok in
show_owner "BIKE-0003" >>=? fun () ->
show_owner "BIKE-0042" >>=? fun () ->
(* An example multi-row query. *)
Lwt_io.printf "Stolen:" >>= fun () ->
iter_s_stolen db
(fun bike ->
let stolen =
match bike.Bike.stolen with Some x -> x | None -> assert false in
Lwt_io.printf "\t%s %s %s\n" bike.Bike.frameno
(Ptime.to_rfc3339 stolen) bike.Bike.owner >>= Lwt.return_ok)
let report_error = function
| Ok () -> Lwt.return_unit
| Error err ->
Lwt_io.eprintl (Caqti_error.show err) >|= fun () -> exit 69
let main {Testlib.uris; connect_config} = Lwt_main.run begin
uris |> Lwt_list.iter_s begin fun uri ->
Caqti_lwt_unix.with_connection ~config:connect_config uri test
>>= report_error
end
end
let main_cmd =
let open Cmdliner in
let doc = "Caqti bikereg example." in
(* If you wish to play with this outside the Caqti distribution, replace
"Testlib.common_args" with the "uris" definition from that function. *)
let term = Term.(const main $ Testlib.common_args ()) in
let info = Cmd.info ~doc "bikereg" in
Cmd.v info term
let () = exit (Cmdliner.Cmd.eval main_cmd)