{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} module Arbor.Postgres.Core where import Arbor.Postgres.Password import Control.Exception (catch, throw) import Control.Lens import Control.Monad (void) import Data.ByteString import Data.Generics.Product.Any import Data.Int import Data.Monoid ((<>)) import Data.String import Network.URI import qualified Arbor.Postgres.Config as Z import qualified Arbor.Postgres.Env as E import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Database.PostgreSQL.Simple as PGS parseConfig :: Z.PostgresConfig -> ByteString parseConfig postgresConfig = do let host = postgresConfig ^. the @"host" let dbname = postgresConfig ^. the @"database" let user = postgresConfig ^. the @"user" let mPassword = postgresConfig ^. the @"password" let kvPassword = case mPassword of Just (Password password) -> [("password", password)] Nothing -> [] let kvs = [("host", host), ("dbname", dbname), ("user", user)] <> kvPassword let pairs = kvs <&> (\(k, v) -> k <> "='" <> v <> "'") T.encodeUtf8 $ T.intercalate " " pairs -- because we need a double quoted, not single quoted string, -- we need to explicitly not use sql interpolation here. createDatabaseStatement :: T.Text -> PGS.Query createDatabaseStatement db = fromString . T.unpack $ "CREATE DATABASE \"" <> db <> "\"" createDatabase :: Z.PostgresConfig -> IO () createDatabase postgresConfig = do conn <- PGS.connectPostgreSQL $ parseConfig $ postgresConfig { Z.database = "postgres" } let q = createDatabaseStatement (postgresConfig ^. the @"database") void $ PGS.execute_ conn q `catch` duplicateDatabase -- https://www.postgresql.org/docs/8.2/static/errcodes-appendix.html -- 42P04 DUPLICATE DATABASE duplicate_database duplicateDatabase :: PGS.SqlError -> IO Int64 duplicateDatabase e = if PGS.sqlState e == "42P04" then return 0 else throw e connectPostgres :: Z.PostgresConfig -> IO E.PostgresEnv connectPostgres postgresConfig = do conn <- PGS.connectPostgreSQL $ parseConfig postgresConfig return $ E.PostgresEnv conn (mkConnectionString postgresConfig) newtype Table = Table { table :: T.Text } deriving (IsString, Show) mkConnectionString :: Z.PostgresConfig -> URI mkConnectionString config = do let host = config ^. the @"host" & T.unpack let dbname = config ^. the @"database" & T.unpack let auth = pure $ URIAuth "" host ":5432" let q = "" let frag = "" URI "postgresql:" auth ("/" <> dbname) q frag mkResourceURI :: URI -> Table -> [(T.Text, T.Text)] -> URI mkResourceURI uri (Table tbl) kvs = do let q = "?" <> T.intercalate "&" (uncurry (\k v -> k <> "=" <> v) <$> (("table", tbl) : kvs)) & T.unpack uri { uriQuery = q }