Plasma GitLab Archive
Projects Blog Knowledge

(*
 * <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
 *
 * 
 *)

This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml