packages feed

ephemeral-pg-0.1.0.0: src/EphemeralPg/Database.hs

-- | Database handle and connection utilities.
--
-- This module provides the 'Database' type representing a running
-- PostgreSQL instance, and functions to extract connection information.
module EphemeralPg.Database
  ( -- * Database handle
    Database (..),
    PostgresProcess (..),

    -- * Connection information
    connectionSettings,
    connectionString,

    -- * Accessors
    dataDirectory,
    socketDirectory,
    port,
    databaseName,
    user,
  )
where

import Data.Text (Text)
import Data.Text qualified as T
import Data.Word (Word16)
import Hasql.Connection.Settings qualified as Settings
import System.Posix.Types (CPid)
import System.Process.Typed (Process)

-- | Handle to a running PostgreSQL server process.
data PostgresProcess = PostgresProcess
  { -- | The typed-process handle
    postgresProcess :: Process () () (),
    -- | Process ID for signal handling
    postgresPid :: CPid
  }

instance Show PostgresProcess where
  show PostgresProcess {postgresPid} =
    "PostgresProcess { pid = " <> show postgresPid <> " }"

-- | A running PostgreSQL database instance.
--
-- Obtain via 'EphemeralPg.start' or 'EphemeralPg.with'. The database is
-- ready to accept connections immediately after creation.
data Database = Database
  { -- | Path to the PostgreSQL data directory
    dbDataDirectory :: FilePath,
    -- | Path to the Unix socket directory
    dbSocketDirectory :: FilePath,
    -- | Port number the server is listening on
    dbPort :: Word16,
    -- | Name of the database
    dbDatabaseName :: Text,
    -- | Username for connections
    dbUser :: Text,
    -- | Optional password
    dbPassword :: Maybe Text,
    -- | Handle to the running postgres process
    dbProcess :: PostgresProcess,
    -- | Cleanup action (removes temp directories if needed)
    dbCleanup :: IO (),
    -- | Whether data directory is temporary
    dbDataDirIsTemp :: Bool,
    -- | Whether socket directory is temporary
    dbSocketDirIsTemp :: Bool
  }

instance Show Database where
  show db =
    "Database { port = "
      <> show (dbPort db)
      <> ", database = "
      <> show (dbDatabaseName db)
      <> ", user = "
      <> show (dbUser db)
      <> ", dataDir = "
      <> show (dbDataDirectory db)
      <> " }"

-- | Get hasql connection settings for this database.
--
-- Example:
--
-- @
-- db <- 'EphemeralPg.start' 'EphemeralPg.defaultConfig'
-- case db of
--   Right database -> do
--     Right conn <- Connection.acquire ('connectionSettings' database)
--     -- use connection...
--   Left err -> handleError err
-- @
connectionSettings :: Database -> Settings.Settings
connectionSettings db =
  mconcat
    [ Settings.hostAndPort (T.pack $ dbSocketDirectory db) (dbPort db),
      Settings.dbname (dbDatabaseName db),
      Settings.user (dbUser db),
      maybe mempty Settings.password (dbPassword db)
    ]

-- | Get a libpq-compatible connection string.
--
-- Format: @host=/path/to/socket port=5432 dbname=postgres user=username@
--
-- Useful for interoperating with other PostgreSQL libraries.
connectionString :: Database -> Text
connectionString db =
  T.unwords
    [ "host=" <> T.pack (dbSocketDirectory db),
      "port=" <> T.pack (show $ dbPort db),
      "dbname=" <> dbDatabaseName db,
      "user=" <> dbUser db
    ]

-- | Get the data directory path.
dataDirectory :: Database -> FilePath
dataDirectory = dbDataDirectory

-- | Get the socket directory path.
socketDirectory :: Database -> FilePath
socketDirectory = dbSocketDirectory

-- | Get the port number.
port :: Database -> Word16
port = dbPort

-- | Get the database name.
databaseName :: Database -> Text
databaseName = dbDatabaseName

-- | Get the username.
user :: Database -> Text
user = dbUser