-
Notifications
You must be signed in to change notification settings - Fork 37
/
bikereg.ml
168 lines (142 loc) · 5.72 KB
/
bikereg.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
(* Copyright (C) 2014--2024 Petter A. Urkedal <[email protected]>
*
* 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_request.Infix
open Caqti_type.Std
let bike =
let open Bike in
let intro frameno owner stolen = {frameno; owner; stolen} 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 =
unit ->. unit @@
{eos|
CREATE TEMPORARY TABLE bikereg (
frameno text NOT NULL,
owner text NOT NULL,
stolen timestamp NULL
)
|eos}
let reg_bike =
t2 string string ->. unit @@
"INSERT INTO bikereg (frameno, owner) VALUES (?, ?)"
let report_stolen =
string ->. unit @@
"UPDATE bikereg SET stolen = current_timestamp WHERE frameno = ?"
let select_stolen =
unit ->* bike @@
"SELECT * FROM bikereg WHERE NOT stolen IS NULL"
let select_owner =
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)