packages feed

prodapi-pg-0.1.0.0: src/Prod/Pg/DatabaseUtils.hs

{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Prod.Pg.DatabaseUtils where

import qualified Data.Binary.Builder as Builder
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Char8 as C8
import Data.Int (Int64)
import Database.PostgreSQL.Simple (Connection, FromRow, Only (..), ToRow)
import qualified Database.PostgreSQL.Simple as Sql
import Database.PostgreSQL.Simple.SqlQQ (sql)
import Database.PostgreSQL.Simple.ToField (Action (..), ToField (..))
import qualified Database.PostgreSQL.Simple.Types as Sql
import Prod.Tracer (Tracer (..), contramap)
import qualified System.Clock as Clock

-------------------------------------------------------------------------------

data PGConnectionTrace
    = RunQuery ByteString.ByteString
    | DoneQuery Clock.TimeSpec Clock.TimeSpec Int ByteString.ByteString
    | RunExec Sql.Query
    | DoneExec Clock.TimeSpec Clock.TimeSpec Int64 Sql.Query

instance Show PGConnectionTrace where
    show (RunQuery bs) = "`" <> C8.unpack bs <> "`"
    show (DoneQuery t0 t1 k _) = mconcat ["n-rows=", show k, "; delay=", show (Clock.diffTimeSpec t1 t0)]
    show (RunExec q) = "`" <> show q <> "`"
    show (DoneExec t0 t1 k _) = mconcat ["result=", show k, "; delay=", show (Clock.diffTimeSpec t1 t0)]

pgExec ::
    (ToRow q) =>
    Tracer IO PGConnectionTrace ->
    Connection ->
    Sql.Query ->
    q ->
    IO Int64
pgExec tracer conn q args = do
    runTracer tracer (RunExec q)
    t0 <- Clock.getTime Clock.Monotonic
    ret <- Sql.execute conn q args
    t1 <- Clock.getTime Clock.Monotonic
    runTracer tracer (DoneExec t0 t1 ret q)
    pure ret

pgQuery ::
    (ToRow q, FromRow r) =>
    Tracer IO PGConnectionTrace ->
    Connection ->
    Sql.Query ->
    q ->
    IO [r]
pgQuery tracer conn q args = do
    formatted <- Sql.formatQuery conn q args
    runTracer tracer (RunQuery formatted)
    t0 <- Clock.getTime Clock.Monotonic
    ret <- Sql.query conn q args
    t1 <- Clock.getTime Clock.Monotonic
    runTracer tracer (DoneQuery t0 t1 (length ret) formatted)
    pure ret

-------------------------------------------------------------------------------
data Trace
    = RefreshMview MviewName ConcurrentlyOrNot PGConnectionTrace
    | RefillMtable MtableFillingFunction PGConnectionTrace
    deriving (Show)

-------------------------------------------------------------------------------
newtype MviewName = MviewName ByteString.ByteString
    deriving (Show)

instance ToField MviewName where
    toField (MviewName n) = Plain (Builder.fromByteString n)

data ConcurrentlyOrNot
    = Concurrently
    | Blocking
    deriving (Show, Eq)

refreshMview ::
    Tracer IO Trace ->
    IO Connection ->
    MviewName ->
    ConcurrentlyOrNot ->
    IO ()
refreshMview tracer mkConn name c = do
    conn <- mkConn
    _ <- pgExec (contramap (RefreshMview name c) tracer) conn q (Only name) :: IO (Int64)
    pure ()
  where
    q = case c of
        Blocking -> [sql|REFRESH MATERIALIZED VIEW ? |]
        Concurrently -> [sql|REFRESH MATERIALIZED VIEW CONCURRENTLY ? |]

-------------------------------------------------------------------------------
newtype MtableFillingFunction = MtableFillingFunction ByteString.ByteString
    deriving (Show)

execFillMTable ::
    Tracer IO Trace ->
    IO Connection ->
    MtableFillingFunction ->
    IO ()
execFillMTable tracer mkConn selectfunc@(MtableFillingFunction sqlfun) = do
    conn <- mkConn
    _ <- pgQuery (contramap (RefillMtable selectfunc) tracer) conn q () :: IO [(Only ())]
    pure ()
  where
    q :: Sql.Query
    q = Sql.Query ("select " <> sqlfun <> "()")