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_pg.ml,v 1.5 2003/03/23 11:59:13 gerd Exp $
 * ----------------------------------------------------------------------
 *
 *)

(* Access PostgreSQL by ocamlodbc: *)

module Connection = struct
  module D = Ocamlodbc (* abbr *)

  type bind_data = 
      (string * string * string)   (* dbname, username, passwd *)

  type conn_handle =
      Unbound
    | Bound of bind_data
    | Connected of (D.connection * bind_data * float * bool)
        (* The float is the time when the last query was executed. A negative
	 * number means that the alarm is inactive.
	 * The bool means that there is a transaction.
	 *)
  type connection = conn_handle ref
      
  open Printf

  let _exec conn s =
    (* prerr_endline ("EXECUTE " ^ s); *)
    let code, recs = D.execute conn s in
    if code <> 0 then
      failwith "Database error";

    (*
    List.iter
      (fun row ->
	 prerr_endline ("ROW: " ^ String.concat "," row)
      )
      recs;
    *)

    recs

  let _exec_nr conn s = ignore(_exec conn s)

  let _begin_transaction =
    ref
      (fun c ->
	 _exec_nr c "begin work";
	 (* Since PG-7.3, the "set" changes only apply to the current transaction *)
	 _exec_nr c "set transaction isolation level read committed";
	 _exec_nr c "set constraints all immediate";
	 _exec_nr c "set datestyle = iso"
      )

  let _rollback_transaction = 
    ref
      (fun c -> _exec_nr c "rollback")

  let _commit_transaction =
    ref
      (fun c -> _exec_nr c "commit")

  let _disconnect c =
    D.disconnect c

  let _connect (db_name, db_user, db_passwd) =
    let c = D.connect db_name db_user db_passwd in
    c

  let create () =
    ref Unbound

  let close_after_timeout c t =
    let now = Unix.time() in
    match !c with
	Connected(c',bd,last,trans) ->
	  if last > 0.0 && now -. last > float t then (
	    (* timeout! *)
	    c := Bound bd;
	    if trans then !_rollback_transaction c';
	    _disconnect c';
	  )
      | _ ->
	  ()

  let bind ~db_name ~user_name ~passwd c =
    match !c with
	Unbound -> c := Bound (db_name, user_name, passwd)
      | _ -> failwith "Db_pg.bind"

  let unbind c =
    match !c with
	Unbound -> ()
      | Bound(_,_,_) -> c := Unbound
      | Connected (c',_,_,trans) -> 
	  c := Unbound;
	  if trans then !_rollback_transaction c';
	  _disconnect c'

  let exec c s =
    match !c with
	Unbound -> failwith "Db_pg.exec"
      | Bound (db_name, db_user, db_passwd as bd) -> 
	  let c' = _connect (db_name, db_user, db_passwd) in
	  c := Connected(c', bd, -1.0, true);
	  !_begin_transaction c';
	  _exec c' s
      | Connected(c',bd,_,trans) ->
	  c := Connected(c', bd, -1.0, true);
	  if not trans then !_begin_transaction c';
	  _exec c' s

  let exec_nr conn s = ignore(exec conn s)

  let commit c =
    match !c with
	Connected(c',bd,_,trans) ->
	  c := Connected(c', bd, Unix.time(),false);
	  if trans then (
	    try !_commit_transaction c'
	    with
		error ->
		  c := Bound bd;
		  _disconnect c';
		  raise error
	  )
      | _ ->
	  assert false

  let rollback c =
    match !c with
	Connected(c',bd,_,trans) ->
	  c := Connected(c', bd, Unix.time(),false);
	  if trans then (
	    try !_rollback_transaction c'
	    with
		error ->
		  c := Bound bd;
		  _disconnect c';
		  raise error
	  )
      | _ ->
	  assert false

  let is_connected c =
    match !c with
	Connected _ -> true
      | _ -> false

  (* ----------------------- SPECIAL FUNCTIONS ----------------------- *)

  let have_on_delete_cascade = true		     

  let have_time_keyword = "time"

  let have_interval_keyword = "interval"

  let day_interval d =
    sprintf "interval '%d days'" d

  let quote_re = Netstring_str.regexp "[\\']"

  let quote s =
    Netstring_str.global_replace quote_re "\\\\\\0" s

  let bool_true = "true"
  let bool_false = "false"

  let is_bool_true =
    function
	"1" | "t" | "true" | "y" | "yes" -> true
      | _   -> false

  let is_bool_false =
    function
	"0" | "f" | "false" | "n" | "no" -> true
      | _   -> false

  let expr_next_serial_value table column =
    (* Return the SQL expression to increment the counter for [table] and
     * [column], and to return the new value.
     *)
    sprintf "nextval('%s_%s_seq')" table column

  let expr_last_serial_value table column =
    (* Return the SQL expression to return the counter for [table] and
     * [column] again
     *)
    sprintf "currval('%s_%s_seq')" table column

  let get_last_serial_value c table column =
    let l = exec c (sprintf "select %s" 
		      (expr_last_serial_value table column)) in
    match l with
	[[ r ]] -> int_of_string r
      | _ -> failwith "get_last_serial_value"

end

(* ======================================================================
 * History:
 * 
 * $Log: db_pg.ml,v $
 * Revision 1.5  2003/03/23 11:59:13  gerd
 * 	GPL
 *
 * Revision 1.4  2003/02/04 01:42:40  gerd
 * 	The code does no longer use signals to get rid of inactive
 * database connections. Instead, new features of ocamlnet are used
 * (js_idle_worker).
 *
 * Revision 1.3  2003/02/03 01:28:59  gerd
 * 	Continued.
 *
 * Revision 1.2  2003/01/16 01:42:03  gerd
 * 	MySQL port
 *
 * Revision 1.1  2003/01/16 00:31:10  gerd
 * 	Initial revision.
 *
 * 
 *)

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