2 * http://www.ocsigen.org
3 * Module ocsipersist.ml
4 * Copyright (C) 2007 Vincent Balat - Gabriel Kerneis
5 * Laboratoire PPS - CNRS Université Paris Diderot
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, with linking exception;
10 * either version 2.1 of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 (** Module Ocsipersist: persistent data *)
29 (** Data are divided into stores.
30 Create one store for your project, where you will save all your data.
32 type store
= string Lwt.t
34 exception Ocsipersist_error
37 (*****************************************************************************)
40 (** getting the directory from config file *)
41 let rec parse_global_config = function
43 | (Element
("database", [("file", s
)], []))::[] -> Some s
44 | _
-> raise
(Ocsigen_extensions.Error_in_config_file
45 ("Unexpected content inside Ocsipersist config"))
47 (* This reference is overwritten when the init function (at the end of the file)
48 is run, which occurs when the extension is loaded *)
49 let db_file = ref ((Ocsigen_config.get_datadir
())^
"/ocsidb")
52 (*****************************************************************************)
53 (** Useful functions on database *)
58 let rec bind_safely stmt
= function
60 | (value, name
)::q
as l
->
61 match Sqlite3.bind stmt
(bind_parameter_index stmt name
) value with
62 | Rc.OK
-> bind_safely stmt q
63 | Rc.BUSY
| Rc.LOCKED
-> yield () ; bind_safely stmt l
64 | rc
-> ignore
(finalize stmt
) ; failwith
(Rc.to_string rc
)
67 if not
(db_close db
) then
68 ignore
(Ocsigen_messages.errlog
"Couldn't close database")
70 let m = Mutex.create
()
76 try db_open
!db_file with e
-> Mutex.unlock
m; raise e
88 Lwt_preemptive.detach
aux ()
90 (* Référence indispensable pour les codes de retours et leur signification :
91 * http://sqlite.org/capi3ref.html
92 * Langage compris par SQLite : http://www.sqlite.org/lang.html
96 let sql = sprintf
"CREATE TABLE IF NOT EXISTS %s (key TEXT, value BLOB, PRIMARY KEY(key) ON CONFLICT REPLACE)" table
in
98 let stmt = prepare
db sql in
101 | Rc.DONE
-> ignore
(finalize
stmt)
102 | Rc.BUSY
| Rc.LOCKED
-> yield () ; aux ()
103 | rc
-> ignore
(finalize
stmt) ; failwith
(Rc.to_string rc
)
107 exec_safely create >>= fun () ->
110 let db_remove (table
, key
) =
111 let sql = sprintf
"DELETE FROM %s WHERE key = :key " table
in
113 let stmt = bind_safely (prepare
db sql) [Data.TEXT key
,":key"] in
116 | Rc.DONE
-> ignore
(finalize
stmt)
117 | Rc.BUSY
| Rc.LOCKED
-> yield () ; aux ()
118 | rc
-> ignore
(finalize
stmt) ; failwith
(Rc.to_string rc
)
123 let (db_get
, db_replace
, db_replace_if_exists
) =
124 let get (table
, key
) db =
125 let sqlget = sprintf
"SELECT value FROM %s WHERE key = :key " table
in
126 let stmt = bind_safely (prepare
db sqlget) [Data.TEXT key
,":key"] in
130 let value = match column
stmt 0 with
134 ignore
(finalize
stmt);
136 | Rc.DONE
-> ignore
(finalize
stmt) ; raise Not_found
137 | Rc.BUSY
| Rc.LOCKED
-> yield () ; aux ()
138 | rc
-> ignore
(finalize
stmt) ; failwith
(Rc.to_string rc
)
141 let replace (table
, key
) value db =
142 let sqlreplace = sprintf
"INSERT INTO %s VALUES ( :key , :value )" table
in
145 (prepare
db sqlreplace)
146 [Data.TEXT key
,":key"; Data.BLOB
value, ":value"]
150 | Rc.DONE
-> ignore
(finalize
stmt)
151 | Rc.BUSY
| Rc.LOCKED
-> yield () ; aux ()
152 | rc
-> ignore
(finalize
stmt) ; failwith
(Rc.to_string rc
)
155 ((fun tablekey
-> exec_safely (get tablekey
)),
156 (fun tablekey
value -> exec_safely (replace tablekey
value)),
157 (fun tablekey
value -> exec_safely
158 (fun db -> ignore
(get tablekey
db); replace tablekey
value db)))
161 let db_iter_step table rowid
=
163 sprintf
"SELECT key , value , ROWID FROM %s WHERE ROWID > :rowid" table
in
165 let stmt = bind_safely (prepare
db sql) [Data.INT rowid
, ":rowid"] in
169 (match (column
stmt 0,column
stmt 1, column
stmt 2) with
170 | (Data.TEXT k
, Data.BLOB v
, Data.INT rowid
) ->
171 ignore
(finalize
stmt) ;
173 | _
-> assert false )
174 | Rc.DONE
-> ignore
(finalize
stmt) ; None
175 | Rc.BUSY
| Rc.LOCKED
-> yield () ; aux ()
176 | rc
-> ignore
(finalize
stmt) ; failwith
(Rc.to_string rc
)
181 let db_iter_block table f
=
182 let sql = sprintf
"SELECT key , value FROM %s " table
in
184 let stmt = prepare
db sql in
188 (match (column
stmt 0,column
stmt 1) with
189 | (Data.TEXT k
, Data.BLOB v
) -> f k
(Marshal.from_string v
0); aux()
190 | _
-> assert false )
191 | Rc.DONE
-> ignore
(finalize
stmt)
192 | Rc.BUSY
| Rc.LOCKED
-> yield () ; aux ()
193 | rc
-> ignore
(finalize
stmt) ; failwith
(Rc.to_string rc
)
198 let db_length table
=
199 let sql = sprintf
"SELECT count(*) FROM %s " table
in
201 let stmt = prepare
db sql in
205 let value = match column
stmt 0 with
206 | Data.INT s
-> Int64.to_int s
209 ignore
(finalize
stmt);
211 | Rc.DONE
-> ignore
(finalize
stmt) ; raise Not_found
212 | Rc.BUSY
| Rc.LOCKED
-> yield () ; aux ()
213 | rc
-> ignore
(finalize
stmt) ; failwith
(Rc.to_string rc
)
222 (*****************************************************************************)
223 (** Public functions: *)
225 (** Type of persistent data *)
226 type 'a t
= string * string
228 let open_store name
: store
=
229 let s = "store___"^name
in
232 let make_persistent_lazy ~store ~name ~default
=
233 store
>>= fun store
->
234 let pvname = (store
, name
) in
236 (fun () -> db_get
pvname >>= (fun _
-> return
()))
239 let def = Marshal.to_string
(default
()) []
240 in db_replace
pvname def
242 (fun () -> return
pvname)
244 let make_persistent ~store ~name ~default
=
245 make_persistent_lazy ~store ~name ~default
:(fun () -> default
)
247 let get (pvname : 'a t
) : 'a
=
249 (fun r -> return
(Marshal.from_string
r 0))
252 let data = Marshal.to_string v
[] in
253 db_replace
pvname data
255 (** Type of persistent tables *)
256 type '
value table
= string Lwt.t
258 (** name SHOULD NOT begin with "store___" *)
259 let open_table name
= db_create name
261 let table_name table
= table
264 table
>>= fun table
->
265 db_get
(table
, key
) >>= fun v
->
266 return
(Marshal.from_string v
0)
268 let add table key
value =
269 table
>>= fun table
->
270 let data = Marshal.to_string
value [] in
271 db_replace
(table
, key
) data
273 let replace_if_exists table key
value =
274 table
>>= fun table
->
275 let data = Marshal.to_string
value [] in
276 db_replace_if_exists
(table
, key
) data
278 let remove table key
=
279 table
>>= fun table
->
280 db_remove (table
, key
)
282 let iter_step f table
=
283 table
>>= fun table
->
285 db_iter_step table rowid
>>=
288 | Some
(k
,v
,rowid'
) ->
289 f k
(Marshal.from_string v
0) >>= (fun () -> aux rowid'
))
293 let fold_step f table beg
=
294 table
>>= fun table
->
295 let rec aux rowid beg
=
296 db_iter_step table rowid
>>=
299 | Some
(k
, v
, rowid'
) ->
300 f k
(Marshal.from_string v
0) beg
>>= (fun res
-> aux rowid' res
))
304 let iter_block f table
=
305 table
>>= fun table
->
306 db_iter_block table f
308 let iter_table = iter_step
310 let fold_table = fold_step
313 table
>>= fun table
->
317 (* Registration of the extension *)
320 db_file := Ocsigen_config.get_datadir
() ^
"/ocsidb";
321 (match parse_global_config config
with
323 | Some d
-> db_file := d
325 (* We check that we can access the database *)
326 try Lwt_unix.run
(exec_safely (fun _
-> ()))
328 Ocsigen_messages.errlog
330 "Error opening database file '%s' when registering Ocsipersist. \
331 Check that the directory exists, and that Ocsigen has enough \
336 let _ = Ocsigen_extensions.register_extension