Add proper handling of null column values
[hcoop/smlsql.git] / libpq / pg.sml
index d240dad..1b99da2 100644 (file)
@@ -25,6 +25,8 @@ struct
 
     exception Sql of string
 
+    type value = string option
+
     fun cerrmsg con = Int32.toString (F_PQstatus.f' (C.Ptr.ro' con)) ^ ": "
                      ^ ZString.toML' (F_PQerrorMessage.f' (C.Ptr.ro' con))
 
@@ -73,6 +75,12 @@ struct
                end
        end
 
+    fun makeValue v =
+       if C.Ptr.isNull' v then
+           NONE
+       else
+           SOME (ZString.toML' v)
+
     fun fold c f b q =
        let
            val q = ZString.dupML' q
@@ -96,7 +104,11 @@ struct
                            let
                                fun build (~1, acc) = acc
                                  | build (j, acc) =
-                                   build (j-1, ZString.toML' (F_PQgetvalue.f' (roRes, i, j)) :: acc)
+                                   build (j-1,
+                                          if F_PQgetisnull.f' (roRes, i, j) <> 0 then
+                                              NONE :: acc
+                                          else
+                                              makeValue (F_PQgetvalue.f' (roRes, i, j)) :: acc)
                            in
                                builder (i+1, f (build (nf-1, []), acc))
                            end
@@ -117,18 +129,27 @@ struct
     type timestamp = Time.time
     exception Format of string
 
-    fun isNull s = s = ""
+    fun valueOf v =
+       case v of
+           NONE => raise Sql "Trying to read NULL value"
+         | SOME v => v
+
+    fun isNull s =
+       case s of
+           NONE => true
+         | _ => false
 
     fun intToSql n =
        if n < 0 then
            "-" ^ Int.toString(~n)
        else
            Int.toString n
-    fun intFromSql "" = 0
-      | intFromSql s =
+    fun intFromSql' "" = 0
+      | intFromSql' s =
        (case Int.fromString s of
             NONE => raise Format ("Bad integer: " ^ s)
           | SOME n => n)
+    fun intFromSql v = intFromSql' (valueOf v)
 
     fun stringToSql s =
        let
@@ -139,18 +160,19 @@ struct
        in
            foldl (fn (c, s) => s ^ xch c) "'" (String.explode s) ^ "'"
        end
-    fun stringFromSql s = s
+    val stringFromSql = valueOf
 
     fun realToSql s =
        if s < 0.0 then
            "-" ^ Real.toString(~s)
        else
            Real.toString s
-    fun realFromSql "" = 0.0
-      | realFromSql s =
+    fun realFromSql' "" = 0.0
+      | realFromSql' s =
        (case Real.fromString s of
             NONE => raise Format ("Bad real: " ^ s)
           | SOME r => r)
+    fun realFromSql v = realFromSql' (valueOf v)
     fun realToString s = realToSql s
 
     fun toMonth m =
@@ -204,7 +226,7 @@ struct
     fun offsetStr NONE = "+00"
       | offsetStr (SOME n) =
        let
-           val n = Int32.toInt (Time.toSeconds n) div 3600
+           val n = LargeInt.toInt (Time.toSeconds n) div 3600
        in
            if n < 0 then
                "-" ^ pad (~n, 2)
@@ -221,42 +243,45 @@ struct
            ".000000" ^ offsetStr (Date.offset d)
        end
     fun timestampToSql t = "'" ^ timestampToSqlUnquoted t ^ "'"
-    fun timestampFromSql s =
+    fun timestampFromSql' s =
        let
            val tokens = String.tokens (fn ch => ch = #"-" orelse ch = #" " orelse ch = #":"
                                                 orelse ch = #"." orelse ch = #"+") s
        in
            case tokens of
                [year, mon, day, hour, minute, second, _, offset] =>
-               Date.toTime (Date.date {day = intFromSql day, hour = intFromSql hour, minute = intFromSql minute,
-                                       month = toMonth (intFromSql mon),
-                                       offset = SOME (Time.fromSeconds (Int32.fromInt (intFromSql offset * 3600))),
-                                       second = intFromSql second div 1000, year = intFromSql year})
+               Date.toTime (Date.date {day = valOf (Int.fromString day), hour = valOf (Int.fromString hour), minute = valOf (Int.fromString minute),
+                                       month = toMonth (valOf (Int.fromString mon)),
+                                       offset = SOME (Time.fromSeconds (LargeInt.fromInt (valOf (Int.fromString offset) * 3600))),
+                                       second = valOf (Int.fromString second) div 1000, year = valOf (Int.fromString year)})
              | [year, mon, day, hour, minute, second, _] =>
-               Date.toTime (Date.date {day = intFromSql day, hour = intFromSql hour, minute = intFromSql minute,
-                                       month = toMonth (intFromSql mon),
+               Date.toTime (Date.date {day = valOf (Int.fromString day), hour = valOf (Int.fromString hour), minute = valOf (Int.fromString minute),
+                                       month = toMonth (valOf (Int.fromString mon)),
                                        offset = NONE,
-                                       second = intFromSql second, year = intFromSql year})
+                                       second = valOf (Int.fromString second), year = valOf (Int.fromString year)})
              | [year, mon, day, hour, minute, second] =>
-               Date.toTime (Date.date {day = intFromSql day, hour = intFromSql hour, minute = intFromSql minute,
-                                       month = toMonth (intFromSql mon),
+               Date.toTime (Date.date {day = valOf (Int.fromString day), hour = valOf (Int.fromString hour), minute = valOf (Int.fromString minute),
+                                       month = toMonth (valOf (Int.fromString mon)),
                                        offset = NONE,
-                                       second = intFromSql second div 1000, year = intFromSql year})
+                                       second = valOf (Int.fromString second) div 1000, year = valOf (Int.fromString year)})
              | _ => raise Format ("Invalid timestamp " ^ s)
        end
+    fun timestampFromSql v = timestampFromSql' (valueOf v)
                
 
     fun boolToSql true = "TRUE"
       | boolToSql false = "FALSE"
 
-    fun boolFromSql "FALSE" = false
-      | boolFromSql "f" = false
-      | boolFromSql "false" = false
-      | boolFromSql "n" = false
-      | boolFromSql "no" = false
-      | boolFromSql "0" = false
-      | boolFromSql "" = false
-      | boolFromSql _ = true
+    fun boolFromSql' "FALSE" = false
+      | boolFromSql' "f" = false
+      | boolFromSql' "false" = false
+      | boolFromSql' "n" = false
+      | boolFromSql' "no" = false
+      | boolFromSql' "0" = false
+      | boolFromSql' "" = false
+      | boolFromSql' _ = true
+
+    fun boolFromSql v = boolFromSql' (valueOf v)
 end
 
 structure PgClient = SqlClient(PgDriver)
\ No newline at end of file