(* Copyright (C) 2022--2023 Petter A. Urkedal * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at your * option) any later version, with the LGPL-3.0 Linking Exception. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * and the LGPL-3.0 Linking Exception along with this library. If not, see * and , respectively. *) open Caqti_platform open Printf let bool_of_pgstring = function | "t" -> true | "f" -> false | _ -> invalid_arg "Pg_ext.bool_of_string" let pgstring_of_bool = function true -> "t" | false -> "f" let pdate_of_pgstring = Conv.pdate_of_iso8601 let pgstring_of_ptime x = Ptime.to_rfc3339 ~space:true ~tz_offset_s:0 ~frac_s:6 x let pgstring_of_ptime_span t = let is_neg = Ptime.Span.compare t Ptime.Span.zero < 0 in let d, ps = Ptime.Span.(to_d_ps (abs t ))in let buf = Buffer.create 32 in if d <> 0 then bprintf buf "%d days " (if is_neg then -d else d); let s, ps = Int64.(div ps 1_000_000_000_000L |> to_int, rem ps 1_000_000_000_000L) in let hour, s = s / 3600, s mod 3600 in let minute, s = s / 60, s mod 60 in if is_neg then Buffer.add_char buf '-'; bprintf buf "%02d:%02d:%02d" hour minute s; if ps <> 0L then bprintf buf ".%06Ld" (Int64.div ps 1_000_000L); Buffer.contents buf let ps_of_decimal s = try (match String.length s with | 2 -> Ok (Int64.(mul (of_string s) 1_000_000_000_000L)) | n when n < 2 -> Error "Missing digits in seconds of interval string." | _ when s.[2] <> '.' -> Error "Expected period after seconds in interval string." | n -> let buf = Bytes.make 14 '0' in Bytes.blit_string s 0 buf 0 2; Bytes.blit_string s 3 buf 2 (min 12 (n - 3)); Ok (Int64.of_string (Bytes.to_string buf))) with Failure _ -> Error "Seconds in interval string is not numeric." let ps_of_hms s = (match String.split_on_char ':' s with | [hours; minutes; seconds] -> (try let hour = int_of_string hours in let minute = int_of_string minutes in (match ps_of_decimal seconds with | Ok ps -> let hm = Int64.of_int (abs hour * 60 + minute) in let ps = Int64.(add ps (mul hm 60_000_000_000_000L)) in Ok (if s.[0] = '-' then Int64.neg ps else ps) | Error msg -> Error msg) with Failure _ -> Error "Non-integer hour or minute in interval string.") | _ -> Error "Expected HH:MM:SS[.F] in interval string.") let ptime_span_of_pgstring s = let span_of_d_ps (d, ps) = let d, ps = if Int64.compare ps Int64.zero >= 0 then (d, ps) else (d - 1, Int64.add ps 86_400_000_000_000_000L) in (match Ptime.Span.of_d_ps (d, ps) with | Some t -> Ok t | None -> Error "Out for range for Ptime.span.") in try (match String.split_on_char ' ' s with | [d; "days"] -> span_of_d_ps (int_of_string d, 0L) | [d; "days"; hms] -> Result.bind (ps_of_hms hms) @@ fun ps -> span_of_d_ps (int_of_string d, ps) | [hms] -> Result.bind (ps_of_hms hms) @@ fun ps -> span_of_d_ps (0, ps) | _ -> Error "Unhandled interval format.") with Failure _ -> Error "Non-integer days in interval string." let cause_of_sqlstate sqlstate = (match sqlstate.[0], sqlstate.[1], sqlstate with | '2', '3', "23001" -> `Restrict_violation | '2', '3', "23502" -> `Not_null_violation | '2', '3', "23503" -> `Foreign_key_violation | '2', '3', "23505" -> `Unique_violation | '2', '3', "23514" -> `Check_violation | '2', '3', "23P01" -> `Exclusion_violation | '2', '3', _ -> `Integrity_constraint_violation__don't_match | '5', '3', "53100" -> `Disk_full | '5', '3', "53200" -> `Out_of_memory | '5', '3', "53300" -> `Too_many_connections | '5', '3', "53400" -> `Configuration_limit_exceeded | '5', '3', _ -> `Insufficient_resources__don't_match | _ -> `Unspecified__don't_match)