packages feed

relocant-1.0.0: test/SpecHelper/DB.hs

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeApplications #-}
module SpecHelper.DB
  ( createTemplate
  , dropTemplate
  , withTemplateCloned
  ) where

import Control.Exception (bracket, bracket_)
import Data.ByteString (ByteString)
import Data.Int (Int32)
import Data.String (fromString)
import Database.PostgreSQL.Simple qualified as DB
import Database.PostgreSQL.Simple.SqlQQ qualified as DB (sql)
import Database.PostgreSQL.Simple.Types qualified as DB (QualifiedIdentifier)
import System.Random (randomIO)
import Text.Printf (printf)

import Relocant.DB qualified as DB (init)


createTemplate :: IO ()
createTemplate = do
  withDB "relocant" (createDatabase "relocant_base")
  withDB "relocant_base" $ \conn -> do
    DB.init "public.relocant_migration" conn
    setTemplate "relocant_base" conn
    DB.close conn

dropTemplate :: IO ()
dropTemplate =
  withDB "relocant" $ \conn -> do
    unsetTemplate "relocant_base" conn
    dropDatabase "relocant_base" conn

withTemplateCloned :: (DB.Connection -> IO a) -> IO a
withTemplateCloned f = do
  n <- randomIO @Int32
  withClonedDB "relocant_base" (fromString (printf "relocant_test_%d" n)) f

withClonedDB :: String -> String -> (DB.Connection -> IO a) -> IO a
withClonedDB base cloned f = do
  bracket_
    (withDB (dbname base) (cloneTemplate (ident base) (ident cloned)))
    (withDB (dbname base) (dropDatabase (ident cloned)))
    (withDB (dbname cloned) f)
 where
  dbname :: String -> ByteString
  dbname = fromString
  ident :: String -> DB.QualifiedIdentifier
  ident = fromString

withDB :: ByteString -> (DB.Connection -> IO a) -> IO a
withDB name =
  bracket (DB.connectPostgreSQL ("dbname=" <> name)) DB.close

createDatabase :: DB.QualifiedIdentifier -> DB.Connection -> IO ()
createDatabase base conn = do
  _ <- DB.execute conn [DB.sql|
    CREATE DATABASE ?
  |] (DB.Only base)
  pure ()

setTemplate :: DB.QualifiedIdentifier -> DB.Connection -> IO ()
setTemplate base conn = do
  _ <- DB.execute conn [DB.sql|
    ALTER DATABASE ? IS_TEMPLATE = true;
  |] (DB.Only base)
  pure ()

unsetTemplate :: DB.QualifiedIdentifier -> DB.Connection -> IO ()
unsetTemplate base conn = do
  _ <- DB.execute conn [DB.sql|
    ALTER DATABASE ? IS_TEMPLATE = false;
  |] (DB.Only base)
  pure ()

cloneTemplate :: DB.QualifiedIdentifier -> DB.QualifiedIdentifier -> DB.Connection -> IO ()
cloneTemplate base cloned conn = do
  _ <- DB.execute conn [DB.sql|
    CREATE DATABASE ? TEMPLATE ?
  |] (cloned, base)
  pure ()

dropDatabase :: DB.QualifiedIdentifier -> DB.Connection -> IO ()
dropDatabase name conn = do
  _ <- DB.execute conn [DB.sql|
    DROP DATABASE ?
  |] (DB.Only name)
  pure ()