(* mosml/src/dynlibs/mpq/Postgres.sml.
sestoft@dina.kvl.dk -- 1998 -- version 0.05 of 2000-04-28 *)
open Dynlib;
exception Closed and Null;
(* Obtain a handle pointing to the library defining the C functions: *)
val dlh = dlopen { lib = "libmpq.so",
flag = RTLD_LAZY,
global = false }
prim_type pgconn_ (* an abstract object containing a PGconn pointer *)
type dbconn = { conn : pgconn_, closed : bool ref }
prim_type dbresult (* a finalized object containing a PGResult pointer *)
(* The alphabetical order of constructors below must agree
with function pq_resultstatus in file mpq.c *)
datatype dbresultstatus =
Bad_response (* An unexpected response was received *)
| Command_ok (* The query was a command *)
| Copy_in (* The query was "copy
from ..." *)
| Copy_out (* The query was "copy to ..." *)
| Empty_query
| Fatal_error
| Nonfatal_error
| Tuples_ok (* The query successfully returned tuples *)
val pq_setdb : { dbhost : string option, (* 0 database server host *)
dbname : string option, (* 1 database name *)
dboptions : string option, (* 2 options *)
dbport : string option, (* 3 database server port *)
dbpwd : string option, (* 4 user passwd *)
dbtty : string option, (* 5 tty for error log *)
dbuser : string option (* 6 database user *)
} -> pgconn_ =
app1 (dlsym dlh "pq_setdb")
val pq_db : pgconn_ -> string =
app1 (dlsym dlh "pq_db")
val pq_host : pgconn_ -> string option =
app1 (dlsym dlh "pq_host")
val pq_options : pgconn_ -> string =
app1 (dlsym dlh "pq_options")
val pq_port : pgconn_ -> string =
app1 (dlsym dlh "pq_port")
val pq_tty : pgconn_ -> string =
app1 (dlsym dlh "pq_tty")
val pq_status : pgconn_ -> bool =
app1 (dlsym dlh "pq_status")
val pq_errormessage : pgconn_ -> string option =
app1 (dlsym dlh "pq_errormessage")
val pq_finish : pgconn_ -> unit =
app1 (dlsym dlh "pq_finish")
val pq_reset : pgconn_ -> unit =
app1 (dlsym dlh "pq_reset")
val pq_exec : pgconn_ -> string -> dbresult =
app2 (dlsym dlh "pq_exec")
val pq_resultstatus : dbresult -> dbresultstatus =
app1 (dlsym dlh "pq_resultstatus")
val pq_ntuples : dbresult -> int =
app1 (dlsym dlh "pq_ntuples")
val pq_cmdtuples : dbresult -> int =
app1 (dlsym dlh "pq_cmdtuples")
val pq_nfields : dbresult -> int =
app1 (dlsym dlh "pq_nfields")
val pq_fname : dbresult -> int -> string =
app2 (dlsym dlh "pq_fname")
val pq_ftype : dbresult -> int -> int =
app2 (dlsym dlh "pq_ftype")
val pq_fnumber : dbresult -> string -> int =
app2 (dlsym dlh "pq_fnumber")
val pq_fsize : dbresult -> int -> int =
app2 (dlsym dlh "pq_fsize")
val pq_getint : dbresult -> int -> int -> int =
app3 (dlsym dlh "pq_getint")
val pq_getreal : dbresult -> int -> int -> real =
app3 (dlsym dlh "pq_getreal")
val pq_getstring : dbresult -> int -> int -> string =
app3 (dlsym dlh "pq_getstring")
val pq_getbool : dbresult -> int -> int -> bool =
app3 (dlsym dlh "pq_getbool")
val pq_isnull : dbresult -> int -> int -> bool =
app3 (dlsym dlh "pq_getisnull")
(* Auxiliaries *)
fun errormsg fcn conn =
case pq_errormessage conn of
NONE => raise Fail ("Postgres." ^ fcn ^ ": unknown error")
| SOME msg => raise Fail ("Postgres." ^ fcn ^ ": " ^ msg)
fun checknot (ref false) = ()
| checknot (ref true) = raise Closed
(* Monitoring the database connection *)
fun db {conn, closed} =
(checknot closed; pq_db conn)
fun host {conn, closed} =
(checknot closed; pq_host conn)
fun options {conn, closed} =
(checknot closed; pq_options conn)
fun port {conn, closed} =
(checknot closed; pq_port conn)
fun tty {conn, closed} =
(checknot closed; pq_tty conn)
fun status {conn, closed} =
(checknot closed; pq_status conn)
fun errormessage {conn, closed} : string option =
(checknot closed; pq_errormessage conn)
fun reset {conn, closed} =
(checknot closed; pq_reset conn)
(* Query execution and result set access *)
fun execute {conn, closed} query =
(checknot closed;
(pq_exec conn query) handle Fail _ => errormsg "execute" conn)
fun resultstatus dbres =
pq_resultstatus dbres
fun ntuples dbres =
pq_ntuples dbres
fun cmdtuples dbres =
pq_cmdtuples dbres
fun nfields dbres =
pq_nfields dbres
fun fname dbres fno =
pq_fname dbres fno
fun fnames dbres =
Vector.tabulate(nfields dbres, pq_fname dbres)
fun fnumber dbres fname =
let val i = pq_fnumber dbres fname
in if i < 0 then NONE else SOME i end
fun fsize dbres fno =
pq_fsize dbres fno
fun getint dbres fno tupno =
if pq_isnull dbres tupno fno then
raise Null
else
pq_getint dbres tupno fno
fun getreal dbres fno tupno =
if pq_isnull dbres tupno fno then
raise Null
else
pq_getreal dbres tupno fno
fun getstring dbres fno tupno =
if pq_isnull dbres tupno fno then
raise Null
else
pq_getstring dbres tupno fno
fun pq_getdatetime dbres fno tupno : Date.date =
case Date.fromString (pq_getstring dbres fno tupno) of
NONE => raise Fail "Postgres.pq_getdatetime"
| SOME dt => dt
fun getdatetime dbres fno tupno =
if pq_isnull dbres tupno fno then
raise Null
else
pq_getdatetime dbres tupno fno
fun pq_gettime dbres fno tupno : int * int * int =
let val s = pq_getstring dbres fno tupno
open Substring (* for getc, all *)
fun getint src = Option.valOf (Int.scan StringCvt.DEC getc src)
fun drop p = StringCvt.dropl p getc
fun isSep c = (c = #":")
val (hour, src1) = getint (all s)
val (min, src2) = getint (drop isSep src1)
val (sec, src3) = getint (drop isSep src2)
in
(hour, min, sec)
end
handle Option.Option => raise Fail "Postgres.pq_gettime"
fun gettime dbres fno tupno =
if pq_isnull dbres tupno fno then
raise Null
else
pq_gettime dbres tupno fno
fun pq_getdate dbres fno tupno : int * int * int =
let val s = pq_getstring dbres fno tupno
open Substring (* for getc, all *)
fun getint src = Option.valOf (Int.scan StringCvt.DEC getc src)
fun drop p = StringCvt.dropl p getc
fun isSep c = (c = #"-")
val (month, src1) = getint (all s)
val (day, src2) = getint (drop isSep src1)
val (year, src3) = getint (drop isSep src2)
in
(year, month, day)
end
handle Option.Option => raise Fail "Postgres.pq_getdate"
fun getdate dbres fno tupno =
if pq_isnull dbres tupno fno then
raise Null
else
pq_getdate dbres tupno fno
fun getbool dbres fno tupno =
if pq_isnull dbres tupno fno then
raise Null
else
pq_getbool dbres tupno fno
fun isnull dbres fno tupno =
pq_isnull dbres tupno fno
type oid = int (* In reality, a 32 bit quantity *)
datatype dynval =
Bool of bool (* psql bool *)
| Int of int (* psql int4 *)
| Real of real (* psql float8, float4 *)
| String of string (* psql text, varchar *)
| Date of int * int * int (* psql date yyyy-mm-dd *)
| Time of int * int * int (* psql time hh:mm:ss *)
| DateTime of Date.date (* psql datetime *)
| Oid of oid (* psql oid *)
| Bytea of Word8Array.array (* psql bytea *)
| NullVal (* psql NULL *)
datatype dyntype =
BoolTy | IntTy | RealTy | StringTy | DateTy | TimeTy | DateTimeTy
| OidTy | ByteArrTy | UnknownTy of oid
(* A translation from Postgres types to Moscow ML types: *)
fun totag "bool" = SOME BoolTy
| totag "int4" = SOME IntTy
| totag "float8" = SOME RealTy
| totag "float4" = SOME RealTy
| totag "text" = SOME StringTy
| totag "varchar" = SOME StringTy
| totag "date" = SOME DateTy
| totag "datetime" = SOME DateTimeTy
| totag "abstime" = SOME DateTimeTy
| totag "time" = SOME TimeTy
| totag "oid" = SOME OidTy
| totag "bytea" = SOME ByteArrTy
| totag _ = NONE
(* Translation from Moscow ML types to Postgres types: *)
fun fromtag BoolTy = "bool"
| fromtag IntTy = "int4"
| fromtag RealTy = "float8"
| fromtag StringTy = "text"
| fromtag TimeTy = "time"
| fromtag DateTy = "date"
| fromtag DateTimeTy = "datetime"
| fromtag OidTy = "oid"
| fromtag ByteArrTy = "bytea"
| fromtag (UnknownTy _) = raise Fail "Postgres.fromtag"
local
val typetable = ref [] : (oid * dyntype) list ref
in
fun settypetable pconn : unit =
let (* Get the oid-to-typname translation from PostgreSQL: *)
val res = execute pconn "select typname as type, oid from pg_type"
val tups =
List.tabulate(ntuples res,
fn tupno => (pq_getstring res tupno 0,
pq_getint res tupno 1))
fun addty ((tyname, oid), res) =
case totag tyname of
NONE => res
| SOME tag => (oid, tag) :: res
in
typetable := List.foldr addty [] tups
end
fun typeof oid =
let fun h [] = UnknownTy oid
| h ((k, tyno) :: rest) = if k = oid then tyno else h rest
in h (!typetable) end
end
fun ftype dbres fno =
typeof (pq_ftype dbres fno)
fun ftypes dbres =
Vector.tabulate(nfields dbres, ftype dbres)
(* Copying to and from the database *)
val pq_endcopy : pgconn_ -> unit =
app1 (dlsym dlh "pq_endcopy")
val pq_getline : pgconn_ -> string option =
app1 (dlsym dlh "pq_getline")
(* Implements "copy to stdout" : *)
fun copytableto (pconn as { conn, closed } : dbconn,
tablename : string,
put : string -> unit) : unit =
let val res = execute pconn ("copy " ^ tablename ^ " to stdout");
fun loop NONE = ()
| loop (SOME s) = (put s; loop (pq_getline conn))
in
case resultstatus res of
Copy_out => (loop (pq_getline conn); pq_endcopy conn)
| _ => raise Fail "Postgres.copytableto"
end
val pq_putline : pgconn_ -> string -> unit =
app2 (dlsym dlh "pq_putline")
(* Implements "copy from stdin" : *)
fun copytablefrom (pconn as { conn, closed } : dbconn,
tablename : string,
useput : (string -> unit) -> unit) : unit =
let val res = execute pconn ("copy " ^ tablename ^ " from stdin");
val putline = pq_putline conn
in
case resultstatus res of
Copy_in => (useput putline;
pq_putline conn "\\.\n";
pq_endcopy conn)
| _ => raise Fail "Postgres.copytablefrom"
end
fun openbase (args as
{ dbhost : string option, (* 0 database server host *)
dbname : string option, (* 1 database name *)
dboptions : string option, (* 2 options *)
dbport : string option, (* 3 database server port *)
dbpwd : string option, (* 4 user passwd *)
dbtty : string option, (* 5 tty for error log *)
dbuser : string option (* 6 database user *)
}) : dbconn =
let val conn = pq_setdb args
in
if pq_status conn then
let val pconn = { conn = conn, closed = ref false }
in (* The typetable is created anew for each database opened: *)
settypetable pconn;
pconn
end
else
errormsg "openbase" conn
end
fun closebase { conn : pgconn_, closed : bool ref } =
(pq_finish conn; closed := true)
fun getdynfield dbres fno : int -> dynval =
case ftype dbres fno of
BoolTy => (fn tupno =>
if pq_isnull dbres tupno fno then NullVal
else Bool (pq_getbool dbres tupno fno))
| IntTy => (fn tupno =>
if pq_isnull dbres tupno fno then NullVal
else Int (pq_getint dbres tupno fno))
| RealTy => (fn tupno =>
if pq_isnull dbres tupno fno then NullVal
else Real (pq_getreal dbres tupno fno))
| StringTy => (fn tupno =>
if pq_isnull dbres tupno fno then NullVal
else String (pq_getstring dbres tupno fno))
| TimeTy => (fn tupno =>
if pq_isnull dbres tupno fno then NullVal
else Time (pq_gettime dbres tupno fno))
| DateTy => (fn tupno =>
if pq_isnull dbres tupno fno then NullVal
else Date (pq_getdate dbres tupno fno))
| DateTimeTy => (fn tupno =>
if pq_isnull dbres tupno fno then NullVal
else DateTime (pq_getdatetime dbres tupno fno))
| OidTy => raise Fail "Postgres.getdynfield: unimplemented: OidTy"
| ByteArrTy => raise Fail "Postgres.getdynfield: unimplemented: ByteArrTy"
| UnknownTy oid =>
raise Fail ("Postgres.getdynfield: unknown type, oid = "
^ Int.toString oid)
fun applyto x f = f x
fun getdyntup dbres : int -> dynval vector =
let val getters = Vector.tabulate(nfields dbres, getdynfield dbres)
in fn fieldno => Vector.map (applyto fieldno) getters end
fun getdyntups dbres : dynval vector vector =
Vector.tabulate(ntuples dbres, getdyntup dbres)
local
fun i2s i = StringCvt.padLeft #"0" 2 (Int.toString i)
fun fmttrip sep (a,b,c) = String.concat[i2s a, sep, i2s b, sep, i2s c]
in
fun dynval2s (Bool b) = Bool.toString b
| dynval2s (Int i) = Int.toString i
| dynval2s (Real r) = Real.toString r
| dynval2s (String s) = s
| dynval2s (Date ymd) = fmttrip "-" ymd
| dynval2s (Time hms) = fmttrip ":" hms
| dynval2s (DateTime dt) = Date.toString dt
| dynval2s (Oid oid) = ""
| dynval2s (Bytea w8a) = ""
| dynval2s NullVal = "NULL"
end
(* Formatting the result of database queries *)
local
open Msp
infix &&
in
fun formattable (dbres : dbresult) =
let fun (f o g) x = f (g x)
val fieldnms = prmap (th o $) (vec2list (fnames dbres))
fun fmtval dynval =
case dynval of
Int _ => tda "ALIGN=RIGHT" ($ (dynval2s dynval))
| Real _ => tda "ALIGN=RIGHT" ($ (dynval2s dynval))
| _ => td ($ (dynval2s dynval))
fun fmtrow tuple = tr(prmap fmtval (vec2list tuple))
val tuples = vec2list (getdyntups dbres)
in
tablea "BORDER" (tr fieldnms && prsep Nl fmtrow tuples)
end
fun showquery (dbconn : dbconn) (sql : string) : wseq =
let val dbres = execute dbconn sql
in
case resultstatus dbres of
Tuples_ok => formattable dbres
| _ => $ "Error: Database query failed or was not a SELECT"
end
handle Fail msg => $ msg
end