(*
* <COPYRIGHT>
* Copyright 2003 Gerd Stolpmann
*
* <GPL>
* This file is part of WTimer.
*
* WTimer is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* WTimer 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WDialog; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* </>
*)
(* $Id: db.ml,v 1.9 2003/03/23 11:59:13 gerd Exp $
* ----------------------------------------------------------------------
*
*)
module Connection = Db_DBVARIANT.Connection
(* Db_pg.Connection for PostgreSQL
* Db_my.Connection for MySQL
*)
module Types = struct
open Db_types.Types
type user_name = User of string
type user = { u_name : user_name;
u_description : string;
u_password : string option;
u_login : bool;
u_admin : bool;
}
exception No_such_user of user_name
exception User_already_exists of user_name
type inst_name = Instance of string
type instance = { i_name : inst_name;
i_description : string;
}
exception No_such_instance of inst_name
exception Instance_already_exists of inst_name
type perm_type = [ `Write | `Read | `Owner ]
type perm_set = { p_instance : inst_name;
p_set : (user_name * perm_type) list;
}
type entry = { mutable e_id : int option;
e_instance : inst_name;
e_day : date;
e_index : int;
e_start : time option;
e_end : time option;
e_duration : interval option;
e_project : string;
e_description : string;
}
type day = { d_instance : inst_name;
d_day : date;
d_entries : entry list;
}
let string_of_user_name (User s) = s
let string_of_inst_name (Instance s) = s
end
module User = struct
open Types
open Db_types.Types
open Db_types
open Connection
open Printf
let list c =
let l = exec c "select name,description,password IS NULL,password,login,admin from users" in
List.map
(fun row ->
match row with
[n; d; p_null; p; l; a] ->
{ u_name = User n;
u_description = d;
u_password = if is_bool_true p_null then None else Some p;
u_login = is_bool_true l;
u_admin = is_bool_true a;
}
| _ ->
failwith "Db.User.list: Unexpected return list of SELECT"
)
l
let get c (User n) =
let l = exec c
(sprintf "select description,password IS NULL,password,login,admin from users where name = '%s'" (quote n)) in
match l with
[] ->
raise (No_such_user (User n))
| [row] ->
( match row with
[d; p_null; p; l; a] ->
{ u_name = User n;
u_description = d;
u_password = if is_bool_true p_null then None else Some p;
u_login = is_bool_true l;
u_admin = is_bool_true a;
}
| _ ->
failwith "Db.User.get: Unexpected return list of SELECT"
)
| _ ->
failwith "Db.User.get: Unexpected return list of SELECT"
let exists c uname =
let l = exec c
(sprintf "select count(*) from users where name = '%s'"
(quote (string_of_user_name uname))) in
match l with
[[ n ]] ->
int_of_string n > 0
| _ ->
failwith "Db.User.exists: Unexpected return list of SELECT"
let insert c u =
if exists c u.u_name then raise (User_already_exists u.u_name);
exec_nr c
(sprintf "insert into users (name,description,password,login,admin) values ('%s', '%s', %s, %s, %s)"
(quote (string_of_user_name u.u_name))
(quote u.u_description)
(match u.u_password with
None -> "NULL"
| Some pw -> "'" ^ quote pw ^ "'")
(if u.u_login then bool_true else bool_false)
(if u.u_admin then bool_true else bool_false)
)
let update c u =
if not (exists c u.u_name) then raise (No_such_user u.u_name);
exec_nr c
(sprintf "update users set description = '%s', password = %s, login = %s, admin = %s where name = '%s'"
(quote u.u_description)
(match u.u_password with
None -> "NULL"
| Some pw -> "'" ^ quote pw ^ "'")
(if u.u_login then bool_true else bool_false)
(if u.u_admin then bool_true else bool_false)
(quote (string_of_user_name u.u_name)))
let delete c uname =
if not (exists c uname) then raise (No_such_user uname);
exec_nr c
(sprintf "delete from users where name = '%s'"
(quote (string_of_user_name uname)));
if not have_on_delete_cascade then begin
exec_nr c
(sprintf "delete from permission where username = '%s'"
(quote (string_of_user_name uname)));
end
let encrypt_password pw =
Digest.to_hex (Digest.string pw)
end
module Instance = struct
open Types
open Db_types.Types
open Db_types
open Connection
open Printf
let list c =
let l = exec c "select name,description from instance" in
List.map
(fun row ->
match row with
[n; d] ->
{ i_name = Instance n;
i_description = d
}
| _ ->
failwith "Db.Instance.list: Unexpected return list of SELECT"
)
l
let get c iname =
let l = exec c
(sprintf "select description from instance where name = '%s'"
(quote (string_of_inst_name iname))) in
match l with
[] -> raise (No_such_instance iname)
| [[d]] ->
{ i_name = iname;
i_description = d
}
| _ ->
failwith "Db.Instance.get: Unexpected return list of SELECT"
let exists c iname =
let l = exec c
(sprintf "select count(*) from instance where name = '%s'"
(quote (string_of_inst_name iname))) in
match l with
[[ n ]] ->
int_of_string n > 0
| _ ->
failwith "Db.Instance.exists: Unexpected return list of SELECT"
let insert c i =
if exists c i.i_name then raise (Instance_already_exists i.i_name);
exec_nr c
(sprintf "insert into instance (name,description) values ('%s', '%s')"
(quote (string_of_inst_name i.i_name))
(quote i.i_description))
let update c i =
if not (exists c i.i_name) then raise (No_such_instance i.i_name);
exec_nr c
(sprintf "update instance set description = '%s' where name = '%s'"
(quote i.i_description)
(quote (string_of_inst_name i.i_name)))
let delete c iname =
if not (exists c iname) then raise (No_such_instance iname);
exec_nr c
(sprintf "delete from instance where name = '%s'"
(quote (string_of_inst_name iname)));
if not have_on_delete_cascade then begin
exec_nr c
(sprintf "delete from permission where instance = '%s'"
(quote (string_of_inst_name iname)));
exec_nr c
(sprintf "delete from entry where instance = '%s'"
(quote (string_of_inst_name iname)));
end
end
module Permission = struct
open Types
open Db_types.Types
open Db_types
open Connection
open Printf
let pt_map = [ `Read, "R";
`Write, "W";
`Owner, "O" ]
let pt_rev = List.map (fun (a,b) -> (b,a)) pt_map
let priority =
function
`Read -> 0
| `Write -> 1
| `Owner -> 2
let check c iname uname pt =
let query =
(* Include all rights with same priority as pt or higher priority *)
let pt_pri = priority pt in
let rights =
List.filter (fun (pt',_) -> priority pt' >= pt_pri) pt_map in
String.concat
" or "
(List.map (fun (_,c) -> "type = '" ^ c ^ "'") rights)
in
let l = exec c
(sprintf "select count(*) from permission \
where instance = '%s' \
and username = '%s' \
and (%s)"
(quote (string_of_inst_name iname))
(quote (string_of_user_name uname))
query) in
match l with
[[ n ]] ->
int_of_string n > 0
| _ ->
failwith "Db.Permission.check: Unexpected return list of SELECT"
let get c iname =
if not (Instance.exists c iname) then (raise (No_such_instance iname));
let l = exec c
(sprintf "select username, type from permission \
where instance = '%s'"
(quote (string_of_inst_name iname))) in
let l' = List.map
(fun e ->
match e with
[username; typ] ->
let typ' =
try List.assoc typ pt_rev
with Not_found ->
failwith "Db.Permission.get: Unexpected return value"
in
(User username,typ')
| _ ->
failwith "Db.Permission.get: Unexpected return list of SELECT"
)
l in
(* l': List of tuples (username, [type]) *)
let l'' = List.sort (fun (u,_) (v,_) -> compare u v) l' in
(* l'': The same list sorted by username *)
(* A function that makes l'' more dense: *)
let rec compress p =
match p with
(u1, t1) :: (u2, t2) :: p' when u1 = u2 ->
if priority t1 > priority t2 then
compress ((u1, t1) :: p')
else
compress ((u1, t2) :: p')
| (u, t) :: p' ->
(u, t) :: compress p'
| [] ->
[]
in
{ p_instance = iname;
p_set = compress l''
}
let delete c iname =
(* Internal function *)
exec_nr c (sprintf "delete from permission where instance = '%s'"
(quote (string_of_inst_name iname)))
let insert c iname p =
(* Internal function *)
List.iter
(fun (user_name, right) ->
exec_nr c (sprintf "insert into permission \
(instance,username,type) \
values ('%s','%s','%s')"
(quote (string_of_inst_name iname))
(quote (string_of_user_name user_name))
(List.assoc right pt_map))
)
p
let update c pset =
if not (Instance.exists c pset.p_instance) then
raise (No_such_instance pset.p_instance);
delete c pset.p_instance;
insert c pset.p_instance pset.p_set
end
module Entry = struct
open Types
open Db_types.Types
open Db_types
open Connection
open Printf
let list c iname start_date end_date =
if not (Instance.exists c iname) then raise (No_such_instance iname);
let l =
exec c (sprintf "select distinct(day) from entry \
where instance = '%s' \
and day >= '%s' \
and day <= '%s'"
(quote (string_of_inst_name iname))
(Date.to_string start_date)
(Date.to_string end_date)) in
List.map
(fun e ->
match e with
[ d ] ->
Date.from_string d
| _ ->
failwith "Db.Entry.list: Unexpected return list of SELECT"
)
l
let get c iname date =
if not (Instance.exists c iname) then
raise(No_such_instance iname);
let l =
exec c (sprintf "select id,row_index,period_start,period_start is null, \
period_end,period_end is null, \
duration,duration is null,project,description \
from entry \
where instance = '%s' \
and day = '%s'"
(quote (string_of_inst_name iname))
(Date.to_string date))
in
let l' =
List.map
(fun e ->
match e with
[id;index;period_s;period_s_null;period_e;period_e_null;
duration;duration_null;project;descr] ->
{ e_id = Some(int_of_string id);
e_instance = iname;
e_day = date;
e_index = int_of_string index;
e_start =
if is_bool_true period_s_null then
None
else
Some(Time.from_string period_s);
e_end =
if is_bool_true period_e_null then
None
else
Some(Time.from_string period_e);
e_duration =
if is_bool_true duration_null then
None
else
Some(Interval.from_string duration);
e_project = project;
e_description = descr;
}
| _ ->
failwith "Db.Entry.get: Unexpected return list of SELECT"
)
l
in
let l'' = List.sort (fun e e' -> compare e.e_index e'.e_index) l' in
{ d_instance = iname;
d_day = date;
d_entries = l'';
}
let delete c iname date =
(* Internal function *)
exec_nr c (sprintf "delete from entry \
where instance = '%s' \
and day = '%s'"
(quote (string_of_inst_name iname))
(Date.to_string date)
)
let insert c l =
(* Internal function *)
let opt_literal pre post f =
function
None -> "null"
| Some s -> pre ^ f s ^ post
in
List.iter
(fun e ->
let new_or_old_id =
match e.e_id with
Some id -> string_of_int id
| None -> expr_next_serial_value "entry" "id"
in
exec_nr c (sprintf "insert into entry \
(id,instance,day,row_index,period_start,period_end, \
duration,project,description) \
values \
(%s,'%s','%s',%d,%s,%s,%s,'%s','%s')"
new_or_old_id
(quote (string_of_inst_name e.e_instance))
(Date.to_string e.e_day)
e.e_index
(opt_literal
(have_time_keyword ^ " '") "'" Time.to_string e.e_start)
(opt_literal
(have_time_keyword ^ " '") "'" Time.to_string e.e_end)
(opt_literal
(have_interval_keyword ^ " '")
"'" Interval.to_string e.e_duration)
(quote e.e_project)
(quote e.e_description)
);
match e.e_id with
None ->
e.e_id <- Some(get_last_serial_value c "entry" "id")
| Some _ -> ()
)
l
let update c day =
if not (Instance.exists c day.d_instance) then
raise(No_such_instance day.d_instance);
(* Check plausibility: *)
if List.exists (fun e -> e.e_day <> day.d_day) day.d_entries then
invalid_arg "Db.Entry.update: invalid date found";
if List.exists (fun e -> e.e_instance <> day.d_instance) day.d_entries then
invalid_arg "Db.Entry.update: invalid instance found";
(* Check that every index occurs at most once: *)
let h = Hashtbl.create 10 in
List.iter
(fun e ->
if Hashtbl.mem h e.e_index then
invalid_arg "Db.Entry.update: index occurs more than once";
Hashtbl.add h e.e_index ()
)
day.d_entries;
(* Delete the day, and insert it again: *)
delete c day.d_instance day.d_day;
insert c day.d_entries
end
module Session = struct
open Wd_types
open Connection
open Printf
exception Invalid_session_checksum = Wd_dialog.Invalid_session_checksum
exception Session_not_found = Wd_dialog.Session_not_found
class db_session_manager db : session_manager_type =
Wd_dialog.database_session_manager
~allocate:(fun () ->
(* Delete very old records: *)
exec_nr
db
(sprintf "delete from wd_session \
where last_used < (current_date - %s)"
(Connection.day_interval 7)
);
(* Compute a new key: *)
exec_nr
db
(sprintf "insert into wd_session \
(id, skey, svalue, schecksum, last_used) \
values (%s, null, '', '', current_date)"
(expr_next_serial_value "wd_session" "id"));
get_last_serial_value db "wd_session" "id")
~insert:(fun id key ->
exec_nr
db
(sprintf
"update wd_session set skey = '%s' where id = %d" key id);
)
~update:(fun id key value checksum ->
exec_nr
db
(sprintf "update wd_session \
set svalue = '%s', \
schecksum = '%s', \
last_used = current_date \
where skey = '%s'"
value
checksum
key);
commit db;
)
~lookup:(fun id key ->
let slice_length = 7000 in
(* Retrieve the checksum: *)
let checksum =
let rows =
exec db
(sprintf
"select schecksum from wd_session where skey = '%s'"
(quote key)) in
match rows with
[ [ v_checksum ] ] -> v_checksum
| _ -> raise Session_not_found
in
(* Now load the session string *)
let slices = ref [] in
let last_length = ref 1 in
(* 0 means: end of string reached *)
let pos = ref 1 in
while !last_length <> 0 do
let rows =
exec db
(sprintf "select substring(svalue from %d for %d) \
from wd_session where skey = '%s'"
!pos
slice_length
(quote key)) in
( match rows with
[ [ v ] ] ->
slices := v :: !slices;
pos := !pos + String.length v;
last_length := String.length v
| _ ->
assert false
)
done;
(* Concatenate the slices and create the session object *)
let value =
String.concat "" (List.rev !slices) in
(value, checksum)
)
()
end
(* ======================================================================
* History:
*
* $Log: db.ml,v $
* Revision 1.9 2003/03/23 11:59:13 gerd
* GPL
*
* Revision 1.8 2003/03/09 17:56:49 gerd
* Using now the new database_session_manager class
*
* Revision 1.7 2003/02/07 15:34:34 gerd
* Work around limitation in ocamlodbc: long session strings
* are retrieved slice by slice
*
* Revision 1.6 2003/01/16 01:42:03 gerd
* MySQL port
*
* Revision 1.5 2003/01/16 00:39:25 gerd
* Continued.
*
* Revision 1.4 2002/11/20 21:32:46 gerd
* Added u_password, u_login, u_admin
*
* Revision 1.3 2002/11/16 12:34:41 gerd
* Moved date, time, interval to Db_types
*
* Revision 1.2 2002/10/12 23:08:32 gerd
* Moved date_rec, time_rec, interval_rec to Types module
*
* Revision 1.1 2002/10/05 23:37:20 gerd
* Initial revision
*
*
*)