tmp-postgres 1.7.1.0 → 1.8.0.0
raw patch · 8 files changed
+1177/−1144 lines, 8 files
Files
- CHANGELOG.md +6/−0
- src/Database/Postgres/Temp.hs +40/−40
- src/Database/Postgres/Temp/Internal.hs +77/−58
- src/Database/Postgres/Temp/Internal/Config.hs +917/−0
- src/Database/Postgres/Temp/Internal/Core.hs +77/−68
- src/Database/Postgres/Temp/Internal/Partial.hs +0/−921
- test/Spec.hs +57/−54
- tmp-postgres.cabal +3/−3
CHANGELOG.md view
@@ -2,3 +2,9 @@ 1.7.1.0 #35 Add Lenses for configuration++1.8.0.0+ Get rid of `Partial` prefix. Add a `Complete` prefix to the internal configuration types.+ Remove the `partial` prefix.+ Expand abbreviations.+ Order the Event type creation time.
src/Database/Postgres/Temp.hs view
@@ -76,53 +76,53 @@ , Config (..) , prettyPrintConfig -- *** 'Config' Lenses- , configPlanL- , configSocketL- , configDataDirL+ , planL+ , socketClassL+ , dataDirectoryL , configPortL- -- ** 'PartialPlan'- , PartialPlan (..)- -- *** 'PartialPlan' lenses- , partialPlanConfigL- , partialPlanCreateDbL- , partialPlanDataDirectoryL- , partialPlanInitDbL+ -- ** 'Plan'+ , Plan (..)+ -- *** 'Plan' lenses+ , postgresConfigFileL+ , createDbConfigL+ , dataDirectoryStringL+ , initDbConfigL , partialPlanLoggerL- , partialPlanPostgresL- -- ** 'PartialPostgresPlan'- , PartialPostgresPlan (..)- -- *** 'PartialPostgresPlan' lenses- , partialPostgresPlanClientConfigL- , partialPostgresPlanProcessConfigL- -- ** 'PartialProcessConfig'- , PartialProcessConfig (..)- -- *** 'PartialProcessConfig' Lenses- , partialProcessConfigCmdLineL- , partialProcessConfigEnvVarsL- , partialProcessConfigStdErrL- , partialProcessConfigStdInL- , partialProcessConfigStdOutL- -- ** 'PartialEnvVars'- , PartialEnvVars (..)- -- *** 'PartialEnvVars' Lenses- , partialEnvVarsInheritL- , partialEnvVarsSpecificL- -- ** 'PartialCommandLineArgs'- , PartialCommandLineArgs (..)- -- *** 'PartialCommandLineArgs' Lenses- , partialCommandLineArgsIndexBasedL- , partialCommandLineArgsKeyBasedL- -- ** 'PartialDirectoryType'- , PartialDirectoryType (..)- -- ** 'PartialSocketClass'- , PartialSocketClass (..)+ , postgresPlanL+ -- ** 'PostgresPlan'+ , PostgresPlan (..)+ -- *** 'PostgresPlan' lenses+ , connectionOptionsL+ , postgresConfigL+ -- ** 'ProcessConfig'+ , ProcessConfig (..)+ -- *** 'ProcessConfig' Lenses+ , commandLineL+ , environmentVariablesL+ , stdErrL+ , stdInL+ , stdOutL+ -- ** 'EnvVars'+ , EnvVars (..)+ -- *** 'EnvVars' Lenses+ , inheritL+ , specificL+ -- ** 'CommandLineArgs'+ , CommandLineArgs (..)+ -- *** 'CommandLineArgs' Lenses+ , indexBasedL+ , keyBasedL+ -- ** 'DirectoryType'+ , DirectoryType (..)+ -- ** 'SocketClass'+ , SocketClass (..) -- ** 'Logger' , Logger- -- * Internal events passed to the 'partialPlanLogger' .+ -- * Internal events passed to the 'logger' . , Event (..) -- * Errors , StartError (..) ) where import Database.Postgres.Temp.Internal import Database.Postgres.Temp.Internal.Core-import Database.Postgres.Temp.Internal.Partial+import Database.Postgres.Temp.Internal.Config
src/Database/Postgres/Temp/Internal.hs view
@@ -6,7 +6,7 @@ module Database.Postgres.Temp.Internal where import Database.Postgres.Temp.Internal.Core-import Database.Postgres.Temp.Internal.Partial+import Database.Postgres.Temp.Internal.Config import Control.Exception import Control.Monad (void)@@ -20,12 +20,12 @@ -- | Handle for holding temporary resources, the @postgres@ process handle -- and postgres connection information. The 'DB' also includes the--- final 'Plan' that was used to start @initdb@, @createdb@ and+-- final 'CompletePlan' that was used to start @initdb@, @createdb@ and -- @postgres@. See 'toConnectionString' for converting a 'DB' to -- postgresql connection string. data DB = DB { dbResources :: Resources- -- ^ Temporary resources and the final 'Plan'.+ -- ^ Temporary resources and the final 'CompletePlan'. , dbPostgresProcess :: PostgresProcess -- ^ @postgres@ process handle and the connection options. }@@ -118,17 +118,17 @@ your @postgres@ might start and run faster if you use 'defaultConfig'. -'defaultConfig' also sets the 'partialPlanInitDb' to+'defaultConfig' also sets the 'initDbConfig' to 'pure' 'standardProcessConfig' and-'partialPostgresPlanProcessConfig' to 'standardProcessConfig'.+'postgresConfig' to 'standardProcessConfig'. To append additional lines to \"postgresql.conf\" file create a custom 'Config' like the following. @ custom = defaultConfig <> mempty- { configPlan = mempty- { partialPlanConfig =+ { plan = mempty+ { postgresConfigFile = [ "wal_level = replica" , "archive_mode = on" , "max_wal_senders = 2"@@ -142,7 +142,7 @@ Or using the provided lenses and your favorite lens library @- custom = defaultConfig & 'configPlanL' . 'partialPlanConfigL' <>~+ custom = defaultConfig & 'planL' . 'postgresConfigFile' <>~ [ "wal_level = replica" , "archive_mode = on" , "max_wal_senders = 2"@@ -159,17 +159,17 @@ -} defaultConfig :: Config defaultConfig = mempty- { configPlan = mempty- { partialPlanLogger = pure mempty- , partialPlanConfig = defaultPostgresConfig- , partialPlanCreateDb = Nothing- , partialPlanInitDb = pure standardProcessConfig- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsKeyBased = Map.singleton "--no-sync" Nothing+ { plan = mempty+ { logger = pure mempty+ , postgresConfigFile = defaultPostgresConfig+ , createDbConfig = Nothing+ , initDbConfig = pure standardProcessConfig+ { commandLine = mempty+ { keyBased = Map.singleton "--no-sync" Nothing } }- , partialPlanPostgres = mempty- { partialPostgresPlanProcessConfig = standardProcessConfig+ , postgresPlan = mempty+ { postgresConfig = standardProcessConfig } } }@@ -180,8 +180,8 @@ @ defaultPostgresConf extra = defaultConfig <> mempty- { configPlan = mempty- { partialPlanConfig = extra+ { plan = mempty+ { postgresConfigFile = extra } } @@@ -189,21 +189,53 @@ -} defaultPostgresConf :: [String] -> Config defaultPostgresConf extra = defaultConfig <> mempty- { configPlan = mempty- { partialPlanConfig = extra+ { plan = mempty+ { postgresConfigFile = extra } } --- | Create temporary resources and use them to make a 'Config'.--- The generated 'Config' is combined with the passed in @extraConfiguration@--- to create a 'Plan' that is used to create a database.--- The output 'DB' includes references to the temporary resources for--- cleanup and the final plan that was used to generate the database and--- processes+{-|++Create zero or more temporary resources and use them to make a 'Config'.++The passed in config is inspected and a generated config is created.+The final config is built by++ @+ generated '<>' extra+ @++Returns a 'DB' that requires cleanup. `startConfig` should be+used with a `bracket` and 'stop', e.g.++ @+ `withConfig` :: `Config` -> (`DB` -> IO a) -> IO (Either `StartError` a)+ 'withConfig' plan f = `bracket` (`startConfig` plan) (either mempty `stop`) $+ either (pure . Left) (fmap Right . f)+ @++or just use 'withConfig'. If you are calling 'startConfig' you+probably want 'withConfig' anyway.++Based on the value of 'socketClass' a \"postgresql.conf\" is created with++ @+ listen_addresses = \'IP_ADDRESS\'+ @++ if it is 'CIpSocket'. If is 'CUnixSocket' then the lines++ @+ listen_addresses = ''+ unix_socket_directories = \'SOCKET_DIRECTORY\'+ @++are added.++-} startConfig :: Config- -- ^ @extraConfiguration@ that is 'mappend'ed to the generated `Config`.- -- The extra config is 'mappend'ed second, e.g.- -- @generatedConfig <> extraConfiguration@+ -- ^ @extra@ configuration that is 'mappend'ed last to the generated `Config`.+ -- @generated <> extra@ -> IO (Either StartError DB) startConfig extra = try $ evalContT $ do dbResources@Resources {..} <-@@ -217,7 +249,7 @@ start :: IO (Either StartError DB) start = startConfig defaultConfig --- | Stop the @postgres@ process and cleanup any temporary directories that+-- | Stop the @postgres@ process and cleanup any temporary resources that -- might have been created. stop :: DB -> IO () stop DB {..} = do@@ -230,13 +262,12 @@ stopPostgres :: DB -> IO ExitCode stopPostgres = stopPostgresProcess . dbPostgresProcess --- | Restart the @postgres@ using the 'Plan' from the 'DB'--- (e.g. @resourcesPlan . dbResources@)+-- | Restart the @postgres@ from 'DB' using the prior 'Plan' restart :: DB -> IO (Either StartError DB) restart db@DB{..} = try $ do void $ stopPostgres db let plan = resourcesPlan dbResources- bracketOnError (startPostgresProcess (planLogger plan) $ planPostgres plan)+ bracketOnError (startPostgresProcess (completePlanLogger plan) $ completePlanPostgres plan) stopPostgresProcess $ \result -> pure $ db { dbPostgresProcess = result } @@ -251,25 +282,8 @@ -- Exception safe interface ------------------------------------------------------------------------------- {-|-Exception safe default database create. Takes an @action@ continuation-which is given a 'DB' it can use to connect-to (see 'toConnectionString' or 'postgresProcessClientOptions').-All of the database resources are automatically cleaned up on-completion even in the face of exceptions.-Based on the value of 'configSocket' a \"postgresql.conf\" is created with-- @- listen_addresses = \'IP_ADDRESS\'- @-- if it is 'IpSocket'. If is 'UnixSocket' then the lines-- @- listen_addresses = ''- unix_socket_directories = SOCKET_DIRECTORY- @--are added. This occurs as a side effect of calling 'withConfig'.+Exception safe database create with options. See 'startConfig' for more+details. Calls 'stop' even in the face of exceptions. -} withConfig :: Config -- ^ @extraConfiguration@. Combined with the generated 'Config'. See@@ -280,8 +294,13 @@ withConfig plan f = bracket (startConfig plan) (either mempty stop) $ either (pure . Left) (fmap Right . f) --- | Default expectation safe interface. Equivalent to 'withConfig' the--- 'defaultConfig'+{-| Default expectation safe interface. Equivalent to++ @+ 'with' = withConfig' 'defaultConfig'+ @++-} with :: (DB -> IO a) -- ^ @action@ continuation. -> IO (Either StartError a)@@ -299,11 +318,11 @@ optionsToDefaultConfig opts@Client.Options {..} = let generated = optionsToConfig opts startingConfig =- if partialPlanCreateDb (configPlan generated) == mempty+ if createDbConfig (plan generated) == mempty then defaultConfig else defaultConfig <> mempty- { configPlan = mempty- { partialPlanCreateDb = pure standardProcessConfig+ { plan = mempty+ { createDbConfig = pure standardProcessConfig } } in startingConfig <> generated
+ src/Database/Postgres/Temp/Internal/Config.hs view
@@ -0,0 +1,917 @@+{-# OPTIONS_HADDOCK prune #-}+{-| This module provides types and functions for combining partial+ configs into a complete configs to ultimately make a 'CompletePlan'.++ This module has two classes of types.++ Types like 'ProcessConfig' that could be used by any+ library that needs to combine process options.++ Finally it has types and functions for creating 'CompletePlan's that+ use temporary resources. This is used to create the default+ behavior of 'Database.Postgres.Temp.startConfig' and related+ functions.+|-}+module Database.Postgres.Temp.Internal.Config where++import Database.Postgres.Temp.Internal.Core++import Control.Applicative.Lift+import Control.Exception+import Control.Monad (join)+import Control.Monad.Trans.Class+import Control.Monad.Trans.Cont+import qualified Data.Map.Strict as Map+import Data.Map.Strict (Map)+import Data.Maybe+import Data.Monoid+import Data.Monoid.Generic+import Data.Typeable+import qualified Database.PostgreSQL.Simple.Options as Client+import GHC.Generics (Generic)+import Network.Socket.Free (getFreePort)+import System.Directory+import System.Environment+import System.IO+import System.IO.Error+import System.IO.Temp (createTempDirectory)+import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))++prettyMap :: (Pretty a, Pretty b) => Map a b -> Doc+prettyMap theMap =+ let xs = Map.toList theMap+ in vsep $ map (uncurry prettyKeyPair) xs++-- | The environment variables can be declared to+-- inherit from the running process or they+-- can be specifically added.+data EnvVars = EnvVars+ { inherit :: Last Bool+ , specific :: Map String String+ }+ deriving stock (Generic, Show, Eq)++instance Semigroup EnvVars where+ x <> y = EnvVars+ { inherit =+ inherit x <> inherit y+ , specific =+ specific y <> specific x+ }++instance Monoid EnvVars where+ mempty = EnvVars mempty mempty++instance Pretty EnvVars where+ pretty EnvVars {..}+ = text "inherit:"+ <+> pretty (getLast inherit)+ <> hardline+ <> text "specific:"+ <> softline+ <> indent 2 (prettyMap specific)++-- | Combine the current environment+-- (if indicated by 'inherit')+-- with 'specific'+completeEnvVars :: [(String, String)] -> EnvVars -> Either [String] [(String, String)]+completeEnvVars envs EnvVars {..} = case getLast inherit of+ Nothing -> Left ["Inherit not specified"]+ Just x -> Right $ (if x then envs else [])+ <> Map.toList specific++-- | A type to help combine command line arguments.+data CommandLineArgs = CommandLineArgs+ { keyBased :: Map String (Maybe String)+ -- ^ Arguments of the form @-h foo@, @--host=foo@ and @--switch@.+ -- The key is `mappend`ed with value so the key should include+ -- the space or equals (as shown in the first two examples+ -- respectively).+ -- The 'Dual' monoid is used so the last key wins.+ , indexBased :: Map Int String+ -- ^ Arguments that appear at the end of the key based+ -- arguments.+ -- The 'Dual' monoid is used so the last key wins.+ }+ deriving stock (Generic, Show, Eq)+ deriving Monoid via GenericMonoid CommandLineArgs++instance Semigroup CommandLineArgs where+ x <> y = CommandLineArgs+ { keyBased =+ keyBased y <> keyBased x+ , indexBased =+ indexBased y <> indexBased x+ }++instance Pretty CommandLineArgs where+ pretty p@CommandLineArgs {..}+ = text "keyBased:"+ <> softline+ <> indent 2 (prettyMap keyBased)+ <> hardline+ <> text "indexBased:"+ <> softline+ <> indent 2 (prettyMap indexBased)+ <> hardline+ <> text "completed:" <+> text (unwords (completeCommandLineArgs p))++-- Take values as long as the index is the successor of the+-- last index.+takeWhileInSequence :: [(Int, a)] -> [a]+takeWhileInSequence ((0, x):xs) = x : go 0 xs where+ go _ [] = []+ go prev ((next, a):rest)+ | prev + 1 == next = a : go next rest+ | otherwise = []+takeWhileInSequence _ = []++-- | This convert the 'CommandLineArgs' to '+completeCommandLineArgs :: CommandLineArgs -> [String]+completeCommandLineArgs CommandLineArgs {..}+ = map (\(name, mvalue) -> maybe name (name <>) mvalue)+ (Map.toList keyBased)+ <> takeWhileInSequence (Map.toList indexBased)++-- | The monoidial version of 'ProcessConfig'. Used to combine overrides with+-- defaults when creating a 'ProcessConfig'.+data ProcessConfig = ProcessConfig+ { environmentVariables :: EnvVars+ -- ^ A monoid for combine environment variables or replacing them.+ -- for the maps the 'Dual' monoid is used. So the last key wins.+ , commandLine :: CommandLineArgs+ -- ^ A monoid for combine command line arguments or replacing them+ , stdIn :: Last Handle+ -- ^ A monoid for configuring the standard input 'Handle'+ , stdOut :: Last Handle+ -- ^ A monoid for configuring the standard output 'Handle'+ , stdErr :: Last Handle+ -- ^ A monoid for configuring the standard error 'Handle'+ }+ deriving stock (Generic, Eq, Show)+ deriving Semigroup via GenericSemigroup ProcessConfig+ deriving Monoid via GenericMonoid ProcessConfig++prettyHandle :: Handle -> Doc+prettyHandle _ = text "[HANDLE]"++instance Pretty ProcessConfig where+ pretty ProcessConfig {..}+ = text "environmentVariables:"+ <> softline+ <> indent 2 (pretty environmentVariables)+ <> hardline+ <> text "commandLine:"+ <> softline+ <> indent 2 (pretty environmentVariables)+ <> hardline+ <> text "stdIn:" <+>+ pretty (prettyHandle <$> getLast stdIn)+ <> hardline+ <> text "stdOut:" <+>+ pretty (prettyHandle <$> getLast stdOut)+ <> hardline+ <> text "stdErr:" <+>+ pretty (prettyHandle <$> getLast stdErr)+++-- | The 'standardProcessConfig' sets the handles to 'stdin', 'stdout' and+-- 'stderr' and inherits the environment variables from the calling+-- process.+standardProcessConfig :: ProcessConfig+standardProcessConfig = mempty+ { environmentVariables = mempty+ { inherit = pure True+ }+ , stdIn = pure stdin+ , stdOut = pure stdout+ , stdErr = pure stderr+ }++-- A helper to add more info to all the error messages.+addErrorContext :: String -> Either [String] a -> Either [String] a+addErrorContext cxt = either (Left . map (cxt <>)) Right++-- A helper for creating an error if a 'Last' is not defined.+getOption :: String -> Last a -> Errors [String] a+getOption optionName = \case+ Last (Just x) -> pure x+ Last Nothing -> failure ["Missing " ++ optionName ++ " option"]++-- | Turn a 'ProcessConfig' into a 'ProcessConfig'. Fails if+-- any values are missing.+completeProcessConfig+ :: [(String, String)] -> ProcessConfig -> Either [String] CompleteProcessConfig+completeProcessConfig envs ProcessConfig {..} = runErrors $ do+ let completeProcessConfigCmdLine = completeCommandLineArgs commandLine+ completeProcessConfigEnvVars <- eitherToErrors $+ completeEnvVars envs environmentVariables+ completeProcessConfigStdIn <-+ getOption "stdIn" stdIn+ completeProcessConfigStdOut <-+ getOption "stdOut" stdOut+ completeProcessConfigStdErr <-+ getOption "stdErr" stdErr++ pure CompleteProcessConfig {..}++-- | A type to track whether a file is temporary and needs to be cleaned up.+data CompleteDirectoryType = CPermanent FilePath | CTemporary FilePath+ deriving(Show, Eq, Ord)++-- | Get the file path of a 'CompleteDirectoryType', regardless if it is a+-- 'CPermanent' or 'CTemporary' type.+toFilePath :: CompleteDirectoryType -> FilePath+toFilePath = \case+ CPermanent x -> x+ CTemporary x -> x++instance Pretty CompleteDirectoryType where+ pretty = \case+ CPermanent x -> text "CPermanent" <+> pretty x+ CTemporary x -> text "CTemporary" <+> pretty x++makePermanent :: CompleteDirectoryType -> CompleteDirectoryType+makePermanent = \case+ CTemporary x -> CPermanent x+ x -> x++-- | The monoidial version of 'CompleteDirectoryType'. Used to combine overrides with+-- defaults when creating a 'CompleteDirectoryType'. The monoid instance treats+-- 'Temporary' as 'mempty' and takes the last 'Permanent' value.+data DirectoryType+ = Permanent FilePath+ -- ^ A permanent file that should not be generated.+ | Temporary+ -- ^ A temporary file that needs to generated.+ deriving(Show, Eq, Ord)++instance Pretty DirectoryType where+ pretty = \case+ Permanent x -> text "CPermanent" <+> pretty x+ Temporary -> text "CTemporary"++instance Semigroup DirectoryType where+ x <> y = case (x, y) of+ (a, Temporary ) -> a+ (_, a@Permanent {}) -> a++instance Monoid DirectoryType where+ mempty = Temporary++-- | Either create a'CTemporary' directory or do nothing to a 'CPermanent'+-- one.+setupDirectoryType :: String -> DirectoryType -> IO CompleteDirectoryType+setupDirectoryType p = \case+ Temporary -> CTemporary <$> createTempDirectory "/tmp" p+ Permanent x -> pure $ CPermanent x++-- Either create a temporary directory or do nothing+rmDirIgnoreErrors :: FilePath -> IO ()+rmDirIgnoreErrors mainDir = do+ let ignoreDirIsMissing e+ | isDoesNotExistError e = return ()+ | otherwise = throwIO e+ removeDirectoryRecursive mainDir `catch` ignoreDirIsMissing++-- | Either remove a 'CTemporary' directory or do nothing to a 'CPermanent'+-- one.+cleanupDirectoryType :: CompleteDirectoryType -> IO ()+cleanupDirectoryType = \case+ CPermanent _ -> pure ()+ CTemporary filePath -> rmDirIgnoreErrors filePath++-- | A type for configuring the listening address of the @postgres@ process.+-- @postgres@ can listen on several types of sockets simulatanously but we+-- don't support that behavior. One can either listen on a IP based socket+-- or a UNIX domain socket.+data CompleteSocketClass+ = CIpSocket String+ -- ^ IP socket type. The 'String' is either an IP address or+ -- a host that will resolve to an IP address.+ | CUnixSocket CompleteDirectoryType+ -- ^ UNIX domain socket+ deriving (Show, Eq, Ord, Generic, Typeable)++instance Pretty CompleteSocketClass where+ pretty = \case+ CIpSocket x -> text "CIpSocket:" <+> pretty x+ CUnixSocket x -> text "CUnixSocket:" <+> pretty x++-- | Create the extra config lines for listening based on the 'CompleteSocketClass'+socketClassToConfig :: CompleteSocketClass -> [String]+socketClassToConfig = \case+ CIpSocket ip -> ["listen_addresses = '" <> ip <> "'"]+ CUnixSocket dir ->+ [ "listen_addresses = ''"+ , "unix_socket_directories = '" <> toFilePath dir <> "'"+ ]++-- | Many processes require a \"host\" flag. We can generate one from the+-- 'CompleteSocketClass'.+socketClassToHostFlag :: CompleteSocketClass -> [(String, Maybe String)]+socketClassToHostFlag x = [("-h", Just (socketClassToHost x))]++-- | Get the IP address, host name or UNIX domain socket directory+-- as a 'String'+socketClassToHost :: CompleteSocketClass -> String+socketClassToHost = \case+ CIpSocket ip -> ip+ CUnixSocket dir -> toFilePath dir++-- | The monoidial version of 'CompleteSocketClass'. Used to combine overrides with+-- defaults when creating a 'CompleteSocketClass'. The monoid instance treats+-- 'UnixSocket mempty' as 'mempty' and combines the+data SocketClass+ = IpSocket (Last String)+ -- ^ The monoid for combining IP address configuration+ | UnixSocket DirectoryType+ -- ^ The monoid for combining UNIX socket configuration+ deriving stock (Show, Eq, Ord, Generic, Typeable)++instance Pretty SocketClass where+ pretty = \case+ IpSocket x -> "CIpSocket:" <+> pretty (getLast x)+ UnixSocket x -> "CUnixSocket" <+> pretty x++instance Semigroup SocketClass where+ x <> y = case (x, y) of+ (IpSocket a, IpSocket b) -> IpSocket $ a <> b+ (a@(IpSocket _), UnixSocket _) -> a+ (UnixSocket _, a@(IpSocket _)) -> a+ (UnixSocket a, UnixSocket b) -> UnixSocket $ a <> b++instance Monoid SocketClass where+ mempty = UnixSocket mempty++-- | Turn a 'SocketClass' to a 'CompleteSocketClass'. If the 'IpSocket' is+-- 'Nothing' default to \"127.0.0.1\". If the is a 'UnixSocket'+-- optionally create a temporary directory if configured to do so.+setupSocketClass :: SocketClass -> IO CompleteSocketClass+setupSocketClass theClass = case theClass of+ IpSocket mIp -> pure $ CIpSocket $ fromMaybe "127.0.0.1" $+ getLast mIp+ UnixSocket mFilePath ->+ CUnixSocket <$> setupDirectoryType "tmp-postgres-socket" mFilePath++-- | Cleanup the UNIX socket temporary directory if one was created.+cleanupSocketConfig :: CompleteSocketClass -> IO ()+cleanupSocketConfig = \case+ CIpSocket {} -> pure ()+ CUnixSocket dir -> cleanupDirectoryType dir++-- | @postgres@ process config and corresponding client connection+-- 'Client.Options'.+data PostgresPlan = PostgresPlan+ { postgresConfig :: ProcessConfig+ -- ^ Monoid for the @postgres@ ProcessConfig.+ , connectionOptions :: Client.Options+ -- ^ Monoid for the @postgres@ client connection options.+ }+ deriving stock (Generic)+ deriving Semigroup via GenericSemigroup PostgresPlan+ deriving Monoid via GenericMonoid PostgresPlan++instance Pretty PostgresPlan where+ pretty PostgresPlan {..}+ = text "postgresConfig:"+ <> softline+ <> indent 2 (pretty postgresConfig)+ <> hardline+ <> text "connectionOptions:"+ <> softline+ <> indent 2 (prettyOptions connectionOptions)++-- | Turn a 'PostgresPlan' into a 'CompletePostgresPlan'. Fails if any+-- values are missing.+completePostgresPlan :: [(String, String)] -> PostgresPlan -> Either [String] CompletePostgresPlan+completePostgresPlan envs PostgresPlan {..} = runErrors $ do+ let completePostgresPlanClientOptions = connectionOptions+ completePostgresPlanProcessConfig <-+ eitherToErrors $ addErrorContext "postgresConfig: " $+ completeProcessConfig envs postgresConfig++ pure CompletePostgresPlan {..}+-------------------------------------------------------------------------------+-- Plan+-------------------------------------------------------------------------------+-- | The monoidial version of 'CompletePlan'. Used to combine overrides with defaults+-- when creating a plan.+data Plan = Plan+ { logger :: Last Logger+ , initDbConfig :: Maybe ProcessConfig+ , createDbConfig :: Maybe ProcessConfig+ , postgresPlan :: PostgresPlan+ , postgresConfigFile :: [String]+ , dataDirectoryString :: Last String+ }+ deriving stock (Generic)+ deriving Semigroup via GenericSemigroup Plan+ deriving Monoid via GenericMonoid Plan++instance Pretty Plan where+ pretty Plan {..}+ = text "initDbConfig:"+ <> softline+ <> indent 2 (pretty initDbConfig)+ <> hardline+ <> text "initDbConfig:"+ <> softline+ <> indent 2 (pretty createDbConfig)+ <> hardline+ <> text "postgresPlan:"+ <> softline+ <> indent 2 (pretty postgresPlan)+ <> hardline+ <> text "postgresConfigFile:"+ <> softline+ <> indent 2 (vsep $ map text postgresConfigFile)+ <> hardline+ <> text "dataDirectoryString:" <+> pretty (getLast dataDirectoryString)++-- | Turn a 'Plan' into a 'CompletePlan'. Fails if any values are missing.+completePlan :: [(String, String)] -> Plan -> Either [String] CompletePlan+completePlan envs Plan {..} = runErrors $ do+ completePlanLogger <- getOption "logger" logger+ completePlanInitDb <- eitherToErrors $ addErrorContext "initDbConfig: " $+ traverse (completeProcessConfig envs) initDbConfig+ completePlanCreateDb <- eitherToErrors $ addErrorContext "createDbConfig: " $+ traverse (completeProcessConfig envs) createDbConfig+ completePlanPostgres <- eitherToErrors $ addErrorContext "postgresPlan: " $+ completePostgresPlan envs postgresPlan+ let completePlanConfig = unlines postgresConfigFile+ completePlanDataDirectory <- getOption "dataDirectoryString"+ dataDirectoryString++ pure CompletePlan {..}++-- Returns 'True' if the 'Plan' has a+-- 'Just' 'initDbConfig'+hasInitDb :: Plan -> Bool+hasInitDb Plan {..} = isJust initDbConfig++-- Returns 'True' if the 'Plan' has a+-- 'Just' 'createDbConfig'+hasCreateDb :: Plan -> Bool+hasCreateDb Plan {..} = isJust createDbConfig++-- | 'Resources' holds a description of the temporary folders (if there are any)+-- and includes the final 'CompletePlan' that can be used with 'startPlan'.+-- See 'setupConfig' for an example of how to create a 'Resources'.+data Resources = Resources+ { resourcesPlan :: CompletePlan+ -- ^ Final 'CompletePlan'. See 'startPlan' for information on 'CompletePlan's+ , resourcesSocket :: CompleteSocketClass+ -- ^ The 'CompleteSocketClass'. Used to track if a temporary directory was made+ -- as the socket location.+ , resourcesDataDir :: CompleteDirectoryType+ -- ^ The data directory. Used to track if a temporary directory was used.+ }++instance Pretty Resources where+ pretty Resources {..}+ = text "resourcePlan:"+ <> softline+ <> indent 2 (pretty resourcesPlan)+ <> hardline+ <> text "resourcesSocket:"+ <+> pretty resourcesSocket+ <> hardline+ <> text "resourcesDataDir:"+ <+> pretty resourcesDataDir++-- | Make the 'resourcesDataDir' 'CPermanent' so it will not+-- get cleaned up.+makeResourcesDataDirPermanent :: Resources -> Resources+makeResourcesDataDirPermanent r = r+ { resourcesDataDir = makePermanent $ resourcesDataDir r+ }++-- | The high level options for overriding default behavior.+data Config = Config+ { plan :: Plan+ -- ^ Extend or replace any of the configuration used to create a final+ -- 'CompletePlan'+ , socketClass :: SocketClass+ -- ^ Override the default 'CompleteSocketClass' by setting this.+ , dataDirectory :: DirectoryType+ -- ^ Override the default temporary data directory by passing in+ -- 'CPermanent DIRECTORY'+ , port :: Last (Maybe Int)+ -- ^ A monoid for using an existing port (via 'Just' 'PORT_NUMBER') or+ -- requesting a free port (via a 'Nothing')+ }+ deriving stock (Generic)+ deriving Semigroup via GenericSemigroup Config+ deriving Monoid via GenericMonoid Config++instance Pretty Config where+ pretty Config {..}+ = text "plan:"+ <> softline+ <> pretty plan+ <> hardline+ <> text "socketClass:"+ <> softline+ <> pretty socketClass+ <> hardline+ <> text "dataDirectory:"+ <> softline+ <> pretty dataDirectory+ <> hardline+ <> text "port:" <+> pretty (getLast port)++-- | Create a 'Plan' that sets the command line options of all processes+-- (@initdb@, @postgres@ and @createdb@) using a+toPlan+ :: Bool+ -- ^ Make @initdb@ options+ -> Bool+ -- ^ Make @createdb@ options+ -> Int+ -- ^ port+ -> CompleteSocketClass+ -- ^ Whether to listen on a IP address or UNIX domain socket+ -> FilePath+ -- ^ The @postgres@ data directory+ -> Plan+toPlan makeInitDb makeCreateDb port socketClass dataDirectoryString = mempty+ { postgresConfigFile = socketClassToConfig socketClass+ , dataDirectoryString = pure dataDirectoryString+ , postgresPlan = mempty+ { postgresConfig = mempty+ { commandLine = mempty+ { keyBased = Map.fromList+ [ ("-p", Just $ show port)+ , ("-D", Just dataDirectoryString)+ ]+ }+ }+ , connectionOptions = mempty+ { Client.host = pure $ socketClassToHost socketClass+ , Client.port = pure port+ , Client.dbname = pure "postgres"+ }+ }+ , createDbConfig = if makeCreateDb+ then pure $ mempty+ { commandLine = mempty+ { keyBased = Map.fromList $+ socketClassToHostFlag socketClass <>+ [("-p ", Just $ show port)]+ }+ }+ else Nothing+ , initDbConfig = if makeInitDb+ then pure $ mempty+ { commandLine = mempty+ { keyBased = Map.fromList+ [("--pgdata=", Just dataDirectoryString)]+ }++ }+ else Nothing+ }+++-- | Create all the temporary resources from a 'Config'. This also combines the+-- 'Plan' from 'toPlan' with the @extraConfig@ passed in.+setupConfig+ :: Config+ -- ^ @extraConfig@ to 'mappend' after the default config+ -> IO Resources+setupConfig Config {..} = evalContT $ do+ envs <- lift getEnvironment+ thePort <- lift $ maybe getFreePort pure $ join $ getLast port+ resourcesSocket <- ContT $ bracketOnError+ (setupSocketClass socketClass) cleanupSocketConfig+ resourcesDataDir <- ContT $ bracketOnError+ (setupDirectoryType "tmp-postgres-data" dataDirectory) cleanupDirectoryType+ let hostAndDir = toPlan+ (hasInitDb plan)+ (hasCreateDb plan)+ thePort+ resourcesSocket+ (toFilePath resourcesDataDir)+ finalPlan = hostAndDir <> plan+ resourcesPlan <- lift $+ either (throwIO . CompletePlanFailed (show $ pretty finalPlan)) pure $+ completePlan envs finalPlan+ pure Resources {..}++-- | Free the temporary resources created by 'setupConfig'+cleanupConfig :: Resources -> IO ()+cleanupConfig Resources {..} = do+ cleanupSocketConfig resourcesSocket+ cleanupDirectoryType resourcesDataDir+-------------------------------------------------------------------------------+-- Config Generation+-------------------------------------------------------------------------------+-- | Attempt to create a config from a 'Client.Options'. This is useful if+-- want to create a database owned by a specific user you will also log in as+-- among other use cases. It is possible some 'Client.Options' are not+-- supported so don't hesitate to open an issue on github if you find one.+optionsToConfig :: Client.Options -> Config+optionsToConfig opts@Client.Options {..}+ = ( mempty+ { plan = optionsToPlan opts+ , port = maybe (Last Nothing) (pure . pure) $ getLast port+ , socketClass = maybe mempty hostToSocketClass $ getLast host+ }+ )+-- Convert the 'Client.Options' to a 'Plan' that can+-- be connected to with the 'Client.Options'.+optionsToPlan :: Client.Options -> Plan+optionsToPlan opts@Client.Options {..}+ = maybe mempty dbnameToPlan (getLast dbname)+ <> maybe mempty userToPlan (getLast user)+ <> clientOptionsToPlan opts++-- Wrap the 'Client.Options' in an appropiate+-- 'PostgresPlan'+clientOptionsToPlan :: Client.Options -> Plan+clientOptionsToPlan opts = mempty+ { postgresPlan = mempty+ { connectionOptions = opts+ }+ }++-- Create a 'Plan' given a user+userToPlan :: String -> Plan+userToPlan user = mempty+ { createDbConfig = pure $ mempty+ { commandLine = mempty+ { keyBased = Map.singleton "--username=" $ Just user+ }+ }+ , initDbConfig = pure $ mempty+ { commandLine = mempty+ { keyBased = Map.singleton "--username=" $ Just user+ }+ }+ }++-- Adds a @createdb@ ProcessPlan with the argument+-- as the database name.+dbnameToPlan :: String -> Plan+dbnameToPlan dbName = mempty+ { createDbConfig = pure $ mempty+ { commandLine = mempty+ { indexBased = Map.singleton 0 dbName+ }+ }+ }++-- Parse a host string as either an UNIX domain socket directory+-- or a domain or IP.+hostToSocketClass :: String -> SocketClass+hostToSocketClass hostOrSocketPath = case hostOrSocketPath of+ '/' : _ -> UnixSocket $ Permanent hostOrSocketPath+ _ -> IpSocket $ pure hostOrSocketPath++-------------------------------------------------------------------------------+-- Lenses+-- Most this code was generated with microlens-th+-------------------------------------------------------------------------------+-- | Local Lens alias+type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t+-- | Local Lens' alias+type Lens' s a = Lens s s a a++-- | Lens for 'inherit'+inheritL :: Lens' EnvVars (Last Bool)+inheritL f_aj5e (EnvVars x_aj5f x_aj5g)+ = fmap (`EnvVars` x_aj5g)+ (f_aj5e x_aj5f)+{-# INLINE inheritL #-}++-- | Lens for 'specific'+specificL :: Lens' EnvVars (Map String String)+specificL f_aj5i (EnvVars x_aj5j x_aj5k)+ = fmap (EnvVars x_aj5j)+ (f_aj5i x_aj5k)+{-# INLINE specificL #-}++-- | Lens for 'commandLine'+commandLineL ::+ Lens' ProcessConfig CommandLineArgs+commandLineL+ f_allv+ (ProcessConfig x_allw x_allx x_ally x_allz x_allA)+ = fmap+ (\ y_allB+ -> ProcessConfig x_allw y_allB x_ally x_allz+ x_allA)+ (f_allv x_allx)+{-# INLINE commandLineL #-}++-- | Lens for 'environmentVariables'+environmentVariablesL ::+ Lens' ProcessConfig EnvVars+environmentVariablesL+ f_allC+ (ProcessConfig x_allD x_allE x_allF x_allG x_allH)+ = fmap+ (\ y_allI+ -> ProcessConfig y_allI x_allE x_allF x_allG+ x_allH)+ (f_allC x_allD)+{-# INLINE environmentVariablesL #-}++-- | Lens for 'stdErr'+stdErrL ::+ Lens' ProcessConfig (Last Handle)+stdErrL+ f_allJ+ (ProcessConfig x_allK x_allL x_allM x_allN x_allO)+ = fmap+ (ProcessConfig x_allK x_allL x_allM x_allN)+ (f_allJ x_allO)+{-# INLINE stdErrL #-}++-- | Lens for 'stdIn'+stdInL ::+ Lens' ProcessConfig (Last Handle)+stdInL+ f_allQ+ (ProcessConfig x_allR x_allS x_allT x_allU x_allV)+ = fmap+ (\ y_allW+ -> ProcessConfig x_allR x_allS y_allW x_allU+ x_allV)+ (f_allQ x_allT)+{-# INLINE stdInL #-}++-- | Lens for 'stdOut'+stdOutL ::+ Lens' ProcessConfig (Last Handle)+stdOutL+ f_allX+ (ProcessConfig x_allY x_allZ x_alm0 x_alm1 x_alm2)+ = fmap+ (\ y_alm3+ -> ProcessConfig x_allY x_allZ x_alm0 y_alm3+ x_alm2)+ (f_allX x_alm1)+{-# INLINE stdOutL #-}++-- | Lens for 'connectionOptions'+connectionOptionsL ::+ Lens' PostgresPlan Client.Options+connectionOptionsL+ f_am1y+ (PostgresPlan x_am1z x_am1A)+ = fmap (PostgresPlan x_am1z)+ (f_am1y x_am1A)+{-# INLINE connectionOptionsL #-}++-- | Lens for 'postgresConfig'+postgresConfigL ::+ Lens' PostgresPlan ProcessConfig+postgresConfigL+ f_am1C+ (PostgresPlan x_am1D x_am1E)+ = fmap (`PostgresPlan` x_am1E)+ (f_am1C x_am1D)+{-# INLINE postgresConfigL #-}++-- | Lens for 'postgresConfigFile'+postgresConfigFileL :: Lens' Plan [String]+postgresConfigFileL+ f_amcw+ (Plan x_amcx x_amcy x_amcz x_amcA x_amcB x_amcC)+ = fmap+ (\ y_amcD+ -> Plan x_amcx x_amcy x_amcz x_amcA y_amcD+ x_amcC)+ (f_amcw x_amcB)+{-# INLINE postgresConfigFileL #-}++-- | Lens for 'createDbConfig'+createDbConfigL ::+ Lens' Plan (Maybe ProcessConfig)+createDbConfigL+ f_amcE+ (Plan x_amcF x_amcG x_amcH x_amcI x_amcJ x_amcK)+ = fmap+ (\ y_amcL+ -> Plan x_amcF x_amcG y_amcL x_amcI x_amcJ+ x_amcK)+ (f_amcE x_amcH)+{-# INLINE createDbConfigL #-}++-- | Lens for 'dataDirectoryString'+dataDirectoryStringL :: Lens' Plan (Last String)+dataDirectoryStringL+ f_amcM+ (Plan x_amcN x_amcO x_amcP x_amcQ x_amcR x_amcS)+ = fmap+ (Plan x_amcN x_amcO x_amcP x_amcQ x_amcR)+ (f_amcM x_amcS)+{-# INLINE dataDirectoryStringL #-}++-- | Lens for 'initDbConfig'+initDbConfigL ::+ Lens' Plan (Maybe ProcessConfig)+initDbConfigL+ f_amcU+ (Plan x_amcV x_amcW x_amcX x_amcY x_amcZ x_amd0)+ = fmap+ (\ y_amd1+ -> Plan x_amcV y_amd1 x_amcX x_amcY x_amcZ+ x_amd0)+ (f_amcU x_amcW)+{-# INLINE initDbConfigL #-}++-- | Lens for 'logger'+partialPlanLoggerL :: Lens' Plan (Last Logger)+partialPlanLoggerL+ f_amd2+ (Plan x_amd3 x_amd4 x_amd5 x_amd6 x_amd7 x_amd8)+ = fmap+ (\ y_amd9+ -> Plan y_amd9 x_amd4 x_amd5 x_amd6 x_amd7+ x_amd8)+ (f_amd2 x_amd3)+{-# INLINE partialPlanLoggerL #-}++-- | Lens for 'postgresPlan'+postgresPlanL :: Lens' Plan PostgresPlan+postgresPlanL+ f_amda+ (Plan x_amdb x_amdc x_amdd x_amde x_amdf x_amdg)+ = fmap+ (\ y_amdh+ -> Plan x_amdb x_amdc x_amdd y_amdh x_amdf+ x_amdg)+ (f_amda x_amde)+{-# INLINE postgresPlanL #-}++-- | Lens for 'resourcesDataDir'+resourcesDataDirL :: Lens' Resources CompleteDirectoryType+resourcesDataDirL f_ampd (Resources x_ampe x_ampf x_ampg)+ = fmap (Resources x_ampe x_ampf)+ (f_ampd x_ampg)+{-# INLINE resourcesDataDirL #-}++-- | Lens for 'resourcesPlan'+resourcesPlanL :: Lens' Resources CompletePlan+resourcesPlanL f_ampi (Resources x_ampj x_ampk x_ampl)+ = fmap (\ y_ampm -> Resources y_ampm x_ampk x_ampl)+ (f_ampi x_ampj)+{-# INLINE resourcesPlanL #-}++-- | Lens for 'resourcesSocket'+resourcesSocketL :: Lens' Resources CompleteSocketClass+resourcesSocketL f_ampn (Resources x_ampo x_ampp x_ampq)+ = fmap (\ y_ampr -> Resources x_ampo y_ampr x_ampq)+ (f_ampn x_ampp)+{-# INLINE resourcesSocketL #-}++-- | Lens for 'dataDirectory'+dataDirectoryL :: Lens' Config DirectoryType+dataDirectoryL f_amyD (Config x_amyE x_amyF x_amyG x_amyH)+ = fmap (\ y_amyI -> Config x_amyE x_amyF y_amyI x_amyH)+ (f_amyD x_amyG)+{-# INLINE dataDirectoryL #-}++-- | Lens for 'plan'+planL :: Lens' Config Plan+planL f_amyJ (Config x_amyK x_amyL x_amyM x_amyN)+ = fmap (\ y_amyO -> Config y_amyO x_amyL x_amyM x_amyN)+ (f_amyJ x_amyK)+{-# INLINE planL #-}++-- | Lens for 'port'+configPortL :: Lens' Config (Last (Maybe Int))+configPortL f_amyP (Config x_amyQ x_amyR x_amyS x_amyT)+ = fmap (Config x_amyQ x_amyR x_amyS)+ (f_amyP x_amyT)+{-# INLINE configPortL #-}++-- | Lens for 'socketClass'+socketClassL :: Lens' Config SocketClass+socketClassL f_amyV (Config x_amyW x_amyX x_amyY x_amyZ)+ = fmap (\ y_amz0 -> Config x_amyW y_amz0 x_amyY x_amyZ)+ (f_amyV x_amyX)+{-# INLINE socketClassL #-}++-- | Lens for 'indexBased'+indexBasedL ::+ Lens' CommandLineArgs (Map Int String)+indexBasedL+ f_amNr+ (CommandLineArgs x_amNs x_amNt)+ = fmap (CommandLineArgs x_amNs)+ (f_amNr x_amNt)+{-# INLINE indexBasedL #-}++-- | Lens for 'keyBased'+keyBasedL ::+ Lens' CommandLineArgs (Map String (Maybe String))+keyBasedL+ f_amNv+ (CommandLineArgs x_amNw x_amNx)+ = fmap (`CommandLineArgs` x_amNx)+ (f_amNv x_amNw)+{-# INLINE keyBasedL #-}
src/Database/Postgres/Temp/Internal/Core.hs view
@@ -26,10 +26,19 @@ -- | Internal events for debugging data Event- = StartPostgres+ = StartPlan String+ -- ^ The first event. This useful for debugging+ -- what is actual passed to the @initdb@, @createdb@ and+ -- @postgres@.+ | StartPostgres+ -- ^ The second event. Postgres is about to get called | WaitForDB- | StartPlan String+ -- ^ The third event. Postgres started. We are not about to+ -- setup a reconnect loop (racing with a process checker) | TryToConnect+ -- ^ The fourth event and (possibly all subsequent events).+ -- We are looping trying to successfully connect to the @postgres@+ -- process. deriving (Show, Eq, Ord) -- | A list of failures that can occur when starting. This is not@@ -46,7 +55,7 @@ -- ^ @createdb@ failed. This can be from invalid configuration or -- the database might already exist. | CompletePlanFailed String [String]- -- ^ The 'Database.Postgres.Temp.Partial.PartialPlan' was missing info and a complete 'Plan' could+ -- ^ The 'Database.Postgres.Temp.Config.Plan' was missing info and a complete 'CompletePlan' could -- not be created. deriving (Show, Eq, Ord, Typeable) @@ -70,45 +79,45 @@ -- | 'ProcessConfig' contains the configuration necessary for starting a -- process. It is essentially a stripped down 'System.Process.CreateProcess'.-data ProcessConfig = ProcessConfig- { processConfigEnvVars :: [(String, String)]+data CompleteProcessConfig = CompleteProcessConfig+ { completeProcessConfigEnvVars :: [(String, String)] -- ^ Environment variables- , processConfigCmdLine :: [String]+ , completeProcessConfigCmdLine :: [String] -- ^ Command line arguements- , processConfigStdIn :: Handle+ , completeProcessConfigStdIn :: Handle -- ^ The 'Handle' for standard input- , processConfigStdOut :: Handle+ , completeProcessConfigStdOut :: Handle -- ^ The 'Handle' for standard output- , processConfigStdErr :: Handle+ , completeProcessConfigStdErr :: Handle -- ^ The 'Handle' for standard error } prettyKeyPair ::(Pretty a, Pretty b) => a -> b -> Doc prettyKeyPair k v = pretty k <> text ": " <> pretty v -instance Pretty ProcessConfig where- pretty ProcessConfig {..}- = text "processConfigEnvVars:"+instance Pretty CompleteProcessConfig where+ pretty CompleteProcessConfig {..}+ = text "completeProcessConfigEnvVars:" <> softline- <> indent 2 (vsep (map (uncurry prettyKeyPair) processConfigEnvVars))+ <> indent 2 (vsep (map (uncurry prettyKeyPair) completeProcessConfigEnvVars)) <> hardline- <> text "processConfigCmdLine:"+ <> text "completeProcessConfigCmdLine:" <> softline- <> text (unwords processConfigCmdLine)+ <> text (unwords completeProcessConfigCmdLine) -- | Start a process interactively and return the 'ProcessHandle' startProcess :: String -- ^ Process name- -> ProcessConfig+ -> CompleteProcessConfig -- ^ Process config -> IO ProcessHandle-startProcess name ProcessConfig {..} = (\(_, _, _, x) -> x) <$>- createProcess_ name (proc name processConfigCmdLine)- { std_err = UseHandle processConfigStdErr- , std_out = UseHandle processConfigStdOut- , std_in = UseHandle processConfigStdIn- , env = Just processConfigEnvVars+startProcess name CompleteProcessConfig {..} = (\(_, _, _, x) -> x) <$>+ createProcess_ name (proc name completeProcessConfigCmdLine)+ { std_err = UseHandle completeProcessConfigStdErr+ , std_out = UseHandle completeProcessConfigStdOut+ , std_in = UseHandle completeProcessConfigStdIn+ , env = Just completeProcessConfigEnvVars } -- | Stop a 'ProcessHandle'. An alias for 'waitForProcess'@@ -119,30 +128,30 @@ executeProcess :: String -- ^ Process name- -> ProcessConfig+ -> CompleteProcessConfig -- ^ Process config -> IO ExitCode executeProcess name = startProcess name >=> waitForProcess ------------------------------------------------------------------------------- -- PostgresProcess Life cycle management ---------------------------------------------------------------------------------- | 'PostgresPlan' is used be 'startPostgresProcess' to start the @postgres@+-- | 'CompletePostgresPlan' is used be 'startPostgresProcess' to start the @postgres@ -- and then attempt to connect to it.-data PostgresPlan = PostgresPlan- { postgresPlanProcessConfig :: ProcessConfig+data CompletePostgresPlan = CompletePostgresPlan+ { completePostgresPlanProcessConfig :: CompleteProcessConfig -- ^ The process config for @postgres@- , postgresPlanClientOptions :: Client.Options+ , completePostgresPlanClientOptions :: Client.Options -- ^ Connection options. Used to verify that @postgres@ is ready. } -instance Pretty PostgresPlan where- pretty PostgresPlan {..}- = text "postgresPlanProcessConfig:"+instance Pretty CompletePostgresPlan where+ pretty CompletePostgresPlan {..}+ = text "completePostgresPlanProcessConfig:" <> softline- <> indent 2 (pretty postgresPlanProcessConfig)+ <> indent 2 (pretty completePostgresPlanProcessConfig) <> hardline- <> text "postgresPlanClientOptions:"- <+> prettyOptions postgresPlanClientOptions+ <> text "completePostgresPlanClientOptions:"+ <+> prettyOptions completePostgresPlanClientOptions prettyOptions :: Client.Options -> Doc prettyOptions = text . BSC.unpack . Client.toConnectionString@@ -196,12 +205,12 @@ -- | Start the @postgres@ process and block until a successful connection -- occurs. A separate thread we continously check to see if the @postgres@ -- process has crashed.-startPostgresProcess :: Logger -> PostgresPlan -> IO PostgresProcess-startPostgresProcess logger PostgresPlan {..} = do+startPostgresProcess :: Logger -> CompletePostgresPlan -> IO PostgresProcess+startPostgresProcess logger CompletePostgresPlan {..} = do logger StartPostgres - let startAction = PostgresProcess postgresPlanClientOptions- <$> startProcess "postgres" postgresPlanProcessConfig+ let startAction = PostgresProcess completePostgresPlanClientOptions+ <$> startProcess "postgres" completePostgresPlanProcessConfig -- Start postgres and stop if an exception occurs bracketOnError startAction stopPostgresProcess $@@ -214,7 +223,7 @@ logger WaitForDB -- We assume that 'template1' exist and make connection -- options to test if postgres is ready.- let options = postgresPlanClientOptions+ let options = completePostgresPlanClientOptions { Client.dbname = pure "template1" } @@ -225,43 +234,43 @@ -- Postgres is now ready so return return result ---------------------------------------------------------------------------------- Plan+-- CompletePlan ---------------------------------------------------------------------------------- | 'Plan' is the low level configuration necessary for creating a database+-- | 'CompletePlan' is the low level configuration necessary for creating a database -- starting @postgres@ and creating a database. There is no validation done--- on the 'Plan'. It is recommend that one use the higher level functions+-- on the 'CompletePlan'. It is recommend that one use the higher level functions -- such as 'Database.Postgres.Temp.start' which will generate plans that--- are valid. 'Plan's are used internally but are exposed if the higher+-- are valid. 'CompletePlan's are used internally but are exposed if the higher -- level plan generation is not sufficent.-data Plan = Plan- { planLogger :: Logger- , planInitDb :: Maybe ProcessConfig- , planCreateDb :: Maybe ProcessConfig- , planPostgres :: PostgresPlan- , planConfig :: String- , planDataDirectory :: FilePath+data CompletePlan = CompletePlan+ { completePlanLogger :: Logger+ , completePlanInitDb :: Maybe CompleteProcessConfig+ , completePlanCreateDb :: Maybe CompleteProcessConfig+ , completePlanPostgres :: CompletePostgresPlan+ , completePlanConfig :: String+ , completePlanDataDirectory :: FilePath } -instance Pretty Plan where- pretty Plan {..}- = text "planInitDb:"+instance Pretty CompletePlan where+ pretty CompletePlan {..}+ = text "completePlanInitDb:" <> softline- <> indent 2 (pretty planInitDb)+ <> indent 2 (pretty completePlanInitDb) <> hardline- <> text "planCreateDb:"+ <> text "completePlanCreateDb:" <> softline- <> indent 2 (pretty planCreateDb)+ <> indent 2 (pretty completePlanCreateDb) <> hardline- <> text "planPostgres:"+ <> text "completePlanPostgres:" <> softline- <> indent 2 (pretty planPostgres)+ <> indent 2 (pretty completePlanPostgres) <> hardline- <> text "planConfig:"+ <> text "completePlanConfig:" <> softline- <> indent 2 (pretty planConfig)+ <> indent 2 (pretty completePlanConfig) <> hardline- <> text "planDataDirectory:"- <+> pretty planDataDirectory+ <> text "completePlanDataDirectory:"+ <+> pretty completePlanDataDirectory -- A simple helper to throw 'ExitCode's when they are 'ExitFailure'. throwIfNotSuccess :: Exception e => (ExitCode -> e) -> ExitCode -> IO ()@@ -274,19 +283,19 @@ -- Additionally it writes a \"postgresql.conf\" and does not return until -- the @postgres@ process is ready. See 'startPostgresProcess' for more -- details.-startPlan :: Plan -> IO PostgresProcess-startPlan plan@Plan {..} = do- planLogger $ StartPlan $ show $ pretty plan- for_ planInitDb $ executeProcess "initdb" >=>+startPlan :: CompletePlan -> IO PostgresProcess+startPlan plan@CompletePlan {..} = do+ completePlanLogger $ StartPlan $ show $ pretty plan+ for_ completePlanInitDb $ executeProcess "initdb" >=> throwIfNotSuccess InitDbFailed -- We must provide a config file before we can start postgres.- writeFile (planDataDirectory <> "/postgresql.conf") planConfig+ writeFile (completePlanDataDirectory <> "/postgresql.conf") completePlanConfig - let startAction = startPostgresProcess planLogger planPostgres+ let startAction = startPostgresProcess completePlanLogger completePlanPostgres bracketOnError startAction stopPostgresProcess $ \result -> do- for_ planCreateDb $ executeProcess "createdb" >=>+ for_ completePlanCreateDb $ executeProcess "createdb" >=> throwIfNotSuccess CreateDbFailed pure result
− src/Database/Postgres/Temp/Internal/Partial.hs
@@ -1,921 +0,0 @@-{-# OPTIONS_HADDOCK prune #-}-{-| This module provides types and functions for combining partial- configs into a complete configs to ultimately make a 'Plan'.-- This module has two classes of types.-- Types like 'PartialProcessConfig' that could be used by any- library that needs to combine process options.-- Finally it has types and functions for creating 'Plan's that- use temporary resources. This is used to create the default- behavior of 'Database.Postgres.Temp.startConfig' and related- functions.-|-}-module Database.Postgres.Temp.Internal.Partial where--import Database.Postgres.Temp.Internal.Core--import Control.Applicative.Lift-import Control.Exception-import Control.Monad (join)-import Control.Monad.Trans.Class-import Control.Monad.Trans.Cont-import qualified Data.Map.Strict as Map-import Data.Map.Strict (Map)-import Data.Maybe-import Data.Monoid-import Data.Monoid.Generic-import Data.Typeable-import qualified Database.PostgreSQL.Simple.Options as Client-import GHC.Generics (Generic)-import Network.Socket.Free (getFreePort)-import System.Directory-import System.Environment-import System.IO-import System.IO.Error-import System.IO.Temp (createTempDirectory)-import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))--prettyMap :: (Pretty a, Pretty b) => Map a b -> Doc-prettyMap theMap =- let xs = Map.toList theMap- in vsep $ map (uncurry prettyKeyPair) xs---- | The environment variables can be declared to--- inherit from the running process or they--- can be specifically added.-data PartialEnvVars = PartialEnvVars- { partialEnvVarsInherit :: Last Bool- , partialEnvVarsSpecific :: Map String String- }- deriving stock (Generic, Show, Eq)--instance Semigroup PartialEnvVars where- x <> y = PartialEnvVars- { partialEnvVarsInherit =- partialEnvVarsInherit x <> partialEnvVarsInherit y- , partialEnvVarsSpecific =- partialEnvVarsSpecific y <> partialEnvVarsSpecific x- }--instance Monoid PartialEnvVars where- mempty = PartialEnvVars mempty mempty--instance Pretty PartialEnvVars where- pretty PartialEnvVars {..}- = text "partialEnvVarsInherit:"- <+> pretty (getLast partialEnvVarsInherit)- <> hardline- <> text "partialEnvVarsSpecific:"- <> softline- <> indent 2 (prettyMap partialEnvVarsSpecific)---- | Combine the current environment--- (if indicated by 'partialEnvVarsInherit')--- with 'partialEnvVarsSpecific'-completePartialEnvVars :: [(String, String)] -> PartialEnvVars -> Either [String] [(String, String)]-completePartialEnvVars envs PartialEnvVars {..} = case getLast partialEnvVarsInherit of- Nothing -> Left ["Inherit not specified"]- Just x -> Right $ (if x then envs else [])- <> Map.toList partialEnvVarsSpecific---- | A type to help combine command line arguments.-data PartialCommandLineArgs = PartialCommandLineArgs- { partialCommandLineArgsKeyBased :: Map String (Maybe String)- -- ^ Arguments of the form @-h foo@, @--host=foo@ and @--switch@.- -- The key is `mappend`ed with value so the key should include- -- the space or equals (as shown in the first two examples- -- respectively).- -- The 'Dual' monoid is used so the last key wins.- , partialCommandLineArgsIndexBased :: Map Int String- -- ^ Arguments that appear at the end of the key based- -- arguments.- -- The 'Dual' monoid is used so the last key wins.- }- deriving stock (Generic, Show, Eq)- deriving Monoid via GenericMonoid PartialCommandLineArgs--instance Semigroup PartialCommandLineArgs where- x <> y = PartialCommandLineArgs- { partialCommandLineArgsKeyBased =- partialCommandLineArgsKeyBased y <> partialCommandLineArgsKeyBased x- , partialCommandLineArgsIndexBased =- partialCommandLineArgsIndexBased y <> partialCommandLineArgsIndexBased x- }--instance Pretty PartialCommandLineArgs where- pretty p@PartialCommandLineArgs {..}- = text "partialCommandLineArgsKeyBased:"- <> softline- <> indent 2 (prettyMap partialCommandLineArgsKeyBased)- <> hardline- <> text "partialCommandLineArgsIndexBased:"- <> softline- <> indent 2 (prettyMap partialCommandLineArgsIndexBased)- <> hardline- <> text "completed:" <+> text (unwords (completeCommandLineArgs p))---- Take values as long as the index is the successor of the--- last index.-takeWhileInSequence :: [(Int, a)] -> [a]-takeWhileInSequence ((0, x):xs) = x : go 0 xs where- go _ [] = []- go prev ((next, a):rest)- | prev + 1 == next = a : go next rest- | otherwise = []-takeWhileInSequence _ = []---- | This convert the 'PartialCommandLineArgs' to '-completeCommandLineArgs :: PartialCommandLineArgs -> [String]-completeCommandLineArgs PartialCommandLineArgs {..}- = map (\(name, mvalue) -> maybe name (name <>) mvalue)- (Map.toList partialCommandLineArgsKeyBased)- <> takeWhileInSequence (Map.toList partialCommandLineArgsIndexBased)---- | The monoidial version of 'ProcessConfig'. Used to combine overrides with--- defaults when creating a 'ProcessConfig'.-data PartialProcessConfig = PartialProcessConfig- { partialProcessConfigEnvVars :: PartialEnvVars- -- ^ A monoid for combine environment variables or replacing them.- -- for the maps the 'Dual' monoid is used. So the last key wins.- , partialProcessConfigCmdLine :: PartialCommandLineArgs- -- ^ A monoid for combine command line arguments or replacing them- , partialProcessConfigStdIn :: Last Handle- -- ^ A monoid for configuring the standard input 'Handle'- , partialProcessConfigStdOut :: Last Handle- -- ^ A monoid for configuring the standard output 'Handle'- , partialProcessConfigStdErr :: Last Handle- -- ^ A monoid for configuring the standard error 'Handle'- }- deriving stock (Generic, Eq, Show)- deriving Semigroup via GenericSemigroup PartialProcessConfig- deriving Monoid via GenericMonoid PartialProcessConfig--prettyHandle :: Handle -> Doc-prettyHandle _ = text "[HANDLE]"--instance Pretty PartialProcessConfig where- pretty PartialProcessConfig {..}- = text "partialProcessConfigEnvVars:"- <> softline- <> indent 2 (pretty partialProcessConfigEnvVars)- <> hardline- <> text "partialProcessConfigCmdLine:"- <> softline- <> indent 2 (pretty partialProcessConfigEnvVars)- <> hardline- <> text "partialProcessConfigStdIn:" <+>- pretty (prettyHandle <$> getLast partialProcessConfigStdIn)- <> hardline- <> text "partialProcessConfigStdOut:" <+>- pretty (prettyHandle <$> getLast partialProcessConfigStdOut)- <> hardline- <> text "partialProcessConfigStdErr:" <+>- pretty (prettyHandle <$> getLast partialProcessConfigStdErr)----- | The 'standardProcessConfig' sets the handles to 'stdin', 'stdout' and--- 'stderr' and inherits the environment variables from the calling--- process.-standardProcessConfig :: PartialProcessConfig-standardProcessConfig = mempty- { partialProcessConfigEnvVars = mempty- { partialEnvVarsInherit = pure True- }- , partialProcessConfigStdIn = pure stdin- , partialProcessConfigStdOut = pure stdout- , partialProcessConfigStdErr = pure stderr- }---- A helper to add more info to all the error messages.-addErrorContext :: String -> Either [String] a -> Either [String] a-addErrorContext cxt = either (Left . map (cxt <>)) Right---- A helper for creating an error if a 'Last' is not defined.-getOption :: String -> Last a -> Errors [String] a-getOption optionName = \case- Last (Just x) -> pure x- Last Nothing -> failure ["Missing " ++ optionName ++ " option"]---- | Turn a 'PartialProcessConfig' into a 'ProcessConfig'. Fails if--- any values are missing.-completeProcessConfig- :: [(String, String)] -> PartialProcessConfig -> Either [String] ProcessConfig-completeProcessConfig envs PartialProcessConfig {..} = runErrors $ do- let processConfigCmdLine = completeCommandLineArgs partialProcessConfigCmdLine- processConfigEnvVars <- eitherToErrors $- completePartialEnvVars envs partialProcessConfigEnvVars- processConfigStdIn <-- getOption "partialProcessConfigStdIn" partialProcessConfigStdIn- processConfigStdOut <-- getOption "partialProcessConfigStdOut" partialProcessConfigStdOut- processConfigStdErr <-- getOption "partialProcessConfigStdErr" partialProcessConfigStdErr-- pure ProcessConfig {..}---- | A type to track whether a file is temporary and needs to be cleaned up.-data DirectoryType = Permanent FilePath | Temporary FilePath- deriving(Show, Eq, Ord)---- | Get the file path of a 'DirectoryType', regardless if it is a--- 'Permanent' or 'Temporary' type.-toFilePath :: DirectoryType -> FilePath-toFilePath = \case- Permanent x -> x- Temporary x -> x--instance Pretty DirectoryType where- pretty = \case- Permanent x -> text "Permanent" <+> pretty x- Temporary x -> text "Temporary" <+> pretty x--makePermanent :: DirectoryType -> DirectoryType-makePermanent = \case- Temporary x -> Permanent x- x -> x---- | The monoidial version of 'DirectoryType'. Used to combine overrides with--- defaults when creating a 'DirectoryType'. The monoid instance treats--- 'PTemporary' as 'mempty' and takes the last 'PPermanent' value.-data PartialDirectoryType- = PPermanent FilePath- -- ^ A permanent file that should not be generated.- | PTemporary- -- ^ A temporary file that needs to generated.- deriving(Show, Eq, Ord)--instance Pretty PartialDirectoryType where- pretty = \case- PPermanent x -> text "Permanent" <+> pretty x- PTemporary -> text "Temporary"--instance Semigroup PartialDirectoryType where- x <> y = case (x, y) of- (a, PTemporary ) -> a- (_, a@PPermanent {}) -> a--instance Monoid PartialDirectoryType where- mempty = PTemporary---- | Either create a'Temporary' directory or do nothing to a 'Permanent'--- one.-setupDirectoryType :: String -> PartialDirectoryType -> IO DirectoryType-setupDirectoryType pattern = \case- PTemporary -> Temporary <$> createTempDirectory "/tmp" pattern- PPermanent x -> pure $ Permanent x---- Either create a temporary directory or do nothing-rmDirIgnoreErrors :: FilePath -> IO ()-rmDirIgnoreErrors mainDir = do- let ignoreDirIsMissing e- | isDoesNotExistError e = return ()- | otherwise = throwIO e- removeDirectoryRecursive mainDir `catch` ignoreDirIsMissing---- | Either remove a 'Temporary' directory or do nothing to a 'Permanent'--- one.-cleanupDirectoryType :: DirectoryType -> IO ()-cleanupDirectoryType = \case- Permanent _ -> pure ()- Temporary filePath -> rmDirIgnoreErrors filePath---- | A type for configuring the listening address of the @postgres@ process.--- @postgres@ can listen on several types of sockets simulatanously but we--- don't support that behavior. One can either listen on a IP based socket--- or a UNIX domain socket.-data SocketClass- = IpSocket String- -- ^ IP socket type. The 'String' is either an IP address or- -- a host that will resolve to an IP address.- | UnixSocket DirectoryType- -- ^ UNIX domain socket- deriving (Show, Eq, Ord, Generic, Typeable)--instance Pretty SocketClass where- pretty = \case- IpSocket x -> text "IpSocket:" <+> pretty x- UnixSocket x -> text "UnixSocket:" <+> pretty x---- | Create the extra config lines for listening based on the 'SocketClass'-socketClassToConfig :: SocketClass -> [String]-socketClassToConfig = \case- IpSocket ip -> ["listen_addresses = '" <> ip <> "'"]- UnixSocket dir ->- [ "listen_addresses = ''"- , "unix_socket_directories = '" <> toFilePath dir <> "'"- ]---- | Many processes require a \"host\" flag. We can generate one from the--- 'SocketClass'.-socketClassToHostFlag :: SocketClass -> [(String, Maybe String)]-socketClassToHostFlag x = [("-h", Just (socketClassToHost x))]---- | Get the IP address, host name or UNIX domain socket directory--- as a 'String'-socketClassToHost :: SocketClass -> String-socketClassToHost = \case- IpSocket ip -> ip- UnixSocket dir -> toFilePath dir---- | The monoidial version of 'SocketClass'. Used to combine overrides with--- defaults when creating a 'SocketClass'. The monoid instance treats--- 'PUnixSocket mempty' as 'mempty' and combines the-data PartialSocketClass- = PIpSocket (Last String)- -- ^ The monoid for combining IP address configuration- | PUnixSocket PartialDirectoryType- -- ^ The monoid for combining UNIX socket configuration- deriving stock (Show, Eq, Ord, Generic, Typeable)--instance Pretty PartialSocketClass where- pretty = \case- PIpSocket x -> "IpSocket:" <+> pretty (getLast x)- PUnixSocket x -> "UnixSocket" <+> pretty x--instance Semigroup PartialSocketClass where- x <> y = case (x, y) of- (PIpSocket a, PIpSocket b) -> PIpSocket $ a <> b- (a@(PIpSocket _), PUnixSocket _) -> a- (PUnixSocket _, a@(PIpSocket _)) -> a- (PUnixSocket a, PUnixSocket b) -> PUnixSocket $ a <> b--instance Monoid PartialSocketClass where- mempty = PUnixSocket mempty---- | Turn a 'PartialSocketClass' to a 'SocketClass'. If the 'PIpSocket' is--- 'Nothing' default to \"127.0.0.1\". If the is a 'PUnixSocket'--- optionally create a temporary directory if configured to do so.-setupPartialSocketClass :: PartialSocketClass -> IO SocketClass-setupPartialSocketClass theClass = case theClass of- PIpSocket mIp -> pure $ IpSocket $ fromMaybe "127.0.0.1" $- getLast mIp- PUnixSocket mFilePath ->- UnixSocket <$> setupDirectoryType "tmp-postgres-socket" mFilePath---- | Cleanup the UNIX socket temporary directory if one was created.-cleanupSocketConfig :: SocketClass -> IO ()-cleanupSocketConfig = \case- IpSocket {} -> pure ()- UnixSocket dir -> cleanupDirectoryType dir---- | @postgres@ process config and corresponding client connection--- 'Client.Options'.-data PartialPostgresPlan = PartialPostgresPlan- { partialPostgresPlanProcessConfig :: PartialProcessConfig- -- ^ Monoid for the @postgres@ ProcessConfig.- , partialPostgresPlanClientConfig :: Client.Options- -- ^ Monoid for the @postgres@ client connection options.- }- deriving stock (Generic)- deriving Semigroup via GenericSemigroup PartialPostgresPlan- deriving Monoid via GenericMonoid PartialPostgresPlan--instance Pretty PartialPostgresPlan where- pretty PartialPostgresPlan {..}- = text "partialPostgresPlanProcessConfig:"- <> softline- <> indent 2 (pretty partialPostgresPlanProcessConfig)- <> hardline- <> text "partialPostgresPlanClientConfig:"- <> softline- <> indent 2 (prettyOptions partialPostgresPlanClientConfig)---- | Turn a 'PartialPostgresPlan' into a 'PostgresPlan'. Fails if any--- values are missing.-completePostgresPlan :: [(String, String)] -> PartialPostgresPlan -> Either [String] PostgresPlan-completePostgresPlan envs PartialPostgresPlan {..} = runErrors $ do- let postgresPlanClientOptions = partialPostgresPlanClientConfig- postgresPlanProcessConfig <-- eitherToErrors $ addErrorContext "partialPostgresPlanProcessConfig: " $- completeProcessConfig envs partialPostgresPlanProcessConfig-- pure PostgresPlan {..}----------------------------------------------------------------------------------- PartialPlan----------------------------------------------------------------------------------- | The monoidial version of 'Plan'. Used to combine overrides with defaults--- when creating a plan.-data PartialPlan = PartialPlan- { partialPlanLogger :: Last Logger- , partialPlanInitDb :: Maybe PartialProcessConfig- , partialPlanCreateDb :: Maybe PartialProcessConfig- , partialPlanPostgres :: PartialPostgresPlan- , partialPlanConfig :: [String]- , partialPlanDataDirectory :: Last String- }- deriving stock (Generic)- deriving Semigroup via GenericSemigroup PartialPlan- deriving Monoid via GenericMonoid PartialPlan--instance Pretty PartialPlan where- pretty PartialPlan {..}- = text "partialPlanInitDb:"- <> softline- <> indent 2 (pretty partialPlanInitDb)- <> hardline- <> text "partialPlanInitDb:"- <> softline- <> indent 2 (pretty partialPlanCreateDb)- <> hardline- <> text "partialPlanPostgres:"- <> softline- <> indent 2 (pretty partialPlanPostgres)- <> hardline- <> text "partialPlanConfig:"- <> softline- <> indent 2 (vsep $ map text partialPlanConfig)- <> hardline- <> text "partialPlanDataDirectory:" <+> pretty (getLast partialPlanDataDirectory)---- | Turn a 'PartialPlan' into a 'Plan'. Fails if any values are missing.-completePlan :: [(String, String)] -> PartialPlan -> Either [String] Plan-completePlan envs PartialPlan {..} = runErrors $ do- planLogger <- getOption "partialPlanLogger" partialPlanLogger- planInitDb <- eitherToErrors $ addErrorContext "partialPlanInitDb: " $- traverse (completeProcessConfig envs) partialPlanInitDb- planCreateDb <- eitherToErrors $ addErrorContext "partialPlanCreateDb: " $- traverse (completeProcessConfig envs) partialPlanCreateDb- planPostgres <- eitherToErrors $ addErrorContext "partialPlanPostgres: " $- completePostgresPlan envs partialPlanPostgres- let planConfig = unlines partialPlanConfig- planDataDirectory <- getOption "partialPlanDataDirectory"- partialPlanDataDirectory-- pure Plan {..}---- Returns 'True' if the 'PartialPlan' has a--- 'Just' 'partialPlanInitDb'-hasInitDb :: PartialPlan -> Bool-hasInitDb PartialPlan {..} = isJust partialPlanInitDb---- Returns 'True' if the 'PartialPlan' has a--- 'Just' 'partialPlanCreateDb'-hasCreateDb :: PartialPlan -> Bool-hasCreateDb PartialPlan {..} = isJust partialPlanCreateDb---- | 'Resources' holds a description of the temporary folders (if there are any)--- and includes the final 'Plan' that can be used with 'startPlan'.--- See 'setupConfig' for an example of how to create a 'Resources'.-data Resources = Resources- { resourcesPlan :: Plan- -- ^ Final 'Plan'. See 'startPlan' for information on 'Plan's- , resourcesSocket :: SocketClass- -- ^ The 'SocketClass'. Used to track if a temporary directory was made- -- as the socket location.- , resourcesDataDir :: DirectoryType- -- ^ The data directory. Used to track if a temporary directory was used.- }--instance Pretty Resources where- pretty Resources {..}- = text "resourcePlan:"- <> softline- <> indent 2 (pretty resourcesPlan)- <> hardline- <> text "resourcesSocket:"- <+> pretty resourcesSocket- <> hardline- <> text "resourcesDataDir:"- <+> pretty resourcesDataDir---- | Make the 'resourcesDataDir' 'Permanent' so it will not--- get cleaned up.-makeResourcesDataDirPermanent :: Resources -> Resources-makeResourcesDataDirPermanent r = r- { resourcesDataDir = makePermanent $ resourcesDataDir r- }---- | The high level options for overriding default behavior.-data Config = Config- { configPlan :: PartialPlan- -- ^ Extend or replace any of the configuration used to create a final- -- 'Plan'- , configSocket :: PartialSocketClass- -- ^ Override the default 'SocketClass' by setting this.- , configDataDir :: PartialDirectoryType- -- ^ Override the default temporary data directory by passing in- -- 'Permanent DIRECTORY'- , configPort :: Last (Maybe Int)- -- ^ A monoid for using an existing port (via 'Just PORT_NUMBER') or- -- requesting a free port (via a 'Nothing')- }- deriving stock (Generic)- deriving Semigroup via GenericSemigroup Config- deriving Monoid via GenericMonoid Config--instance Pretty Config where- pretty Config {..}- = text "configPlan:"- <> softline- <> pretty configPlan- <> hardline- <> text "configSocket:"- <> softline- <> pretty configSocket- <> hardline- <> text "configDataDir:"- <> softline- <> pretty configDataDir- <> hardline- <> text "configPort:" <+> pretty (getLast configPort)---- | Create a 'PartialPlan' that sets the command line options of all processes--- (@initdb@, @postgres@ and @createdb@) using a-toPlan- :: Bool- -- ^ Make @initdb@ options- -> Bool- -- ^ Make @createdb@ options- -> Int- -- ^ port- -> SocketClass- -- ^ Whether to listen on a IP address or UNIX domain socket- -> FilePath- -- ^ The @postgres@ data directory- -> PartialPlan-toPlan makeInitDb makeCreateDb port socketClass dataDirectory = mempty- { partialPlanConfig = socketClassToConfig socketClass- , partialPlanDataDirectory = pure dataDirectory- , partialPlanPostgres = mempty- { partialPostgresPlanProcessConfig = mempty- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsKeyBased = Map.fromList- [ ("-p", Just $ show port)- , ("-D", Just dataDirectory)- ]- }- }- , partialPostgresPlanClientConfig = mempty- { Client.host = pure $ socketClassToHost socketClass- , Client.port = pure port- , Client.dbname = pure "postgres"- }- }- , partialPlanCreateDb = if makeCreateDb- then pure $ mempty- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsKeyBased = Map.fromList $- socketClassToHostFlag socketClass <>- [("-p ", Just $ show port)]- }- }- else Nothing- , partialPlanInitDb = if makeInitDb- then pure $ mempty- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsKeyBased = Map.fromList $- [("--pgdata=", Just dataDirectory)]- }-- }- else Nothing- }----- | Create all the temporary resources from a 'Config'. This also combines the--- 'PartialPlan' from 'toPlan' with the @extraConfig@ passed in.-setupConfig- :: Config- -- ^ @extraConfig@ to 'mappend' after the default config- -> IO Resources-setupConfig Config {..} = evalContT $ do- envs <- lift getEnvironment- port <- lift $ maybe getFreePort pure $ join $ getLast configPort- resourcesSocket <- ContT $ bracketOnError- (setupPartialSocketClass configSocket) cleanupSocketConfig- resourcesDataDir <- ContT $ bracketOnError- (setupDirectoryType "tmp-postgres-data" configDataDir) cleanupDirectoryType- let hostAndDirPartial = toPlan- (hasInitDb configPlan)- (hasCreateDb configPlan)- port- resourcesSocket- (toFilePath resourcesDataDir)- finalPlan = hostAndDirPartial <> configPlan- resourcesPlan <- lift $- either (throwIO . CompletePlanFailed (show $ pretty finalPlan)) pure $- completePlan envs finalPlan- pure Resources {..}---- | Free the temporary resources created by 'setupConfig'-cleanupConfig :: Resources -> IO ()-cleanupConfig Resources {..} = do- cleanupSocketConfig resourcesSocket- cleanupDirectoryType resourcesDataDir----------------------------------------------------------------------------------- Config Generation----------------------------------------------------------------------------------- | Attempt to create a config from a 'Client.Options'. This is useful if--- want to create a database owned by a specific user you will also log in as--- among other use cases. It is possible some 'Client.Options' are not--- supported so don't hesitate to open an issue on github if you find one.-optionsToConfig :: Client.Options -> Config-optionsToConfig opts@Client.Options {..}- = ( mempty- { configPlan = optionsToPlan opts- , configPort = maybe (Last Nothing) (pure . pure) $ getLast port- , configSocket = maybe mempty hostToSocketClass $ getLast host- }- )--- Convert the 'Client.Options' to a 'PartialPlan' that can--- be connected to with the 'Client.Options'.-optionsToPlan :: Client.Options -> PartialPlan-optionsToPlan opts@Client.Options {..}- = maybe mempty dbnameToPlan (getLast dbname)- <> maybe mempty userToPlan (getLast user)- <> clientOptionsToPlan opts---- Wrap the 'Client.Options' in an appropiate--- 'PartialPostgresPlan'-clientOptionsToPlan :: Client.Options -> PartialPlan-clientOptionsToPlan opts = mempty- { partialPlanPostgres = mempty- { partialPostgresPlanClientConfig = opts- }- }---- Create a 'PartialPlan' given a user-userToPlan :: String -> PartialPlan-userToPlan user = mempty- { partialPlanCreateDb = pure $ mempty- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsKeyBased = Map.singleton "--username=" $ Just user- }- }- , partialPlanInitDb = pure $ mempty- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsKeyBased = Map.singleton "--username=" $ Just user- }- }- }---- Adds a @createdb@ PartialProcessPlan with the argument--- as the database name.-dbnameToPlan :: String -> PartialPlan-dbnameToPlan dbName = mempty- { partialPlanCreateDb = pure $ mempty- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsIndexBased = Map.singleton 0 dbName- }- }- }---- Parse a host string as either an UNIX domain socket directory--- or a domain or IP.-hostToSocketClass :: String -> PartialSocketClass-hostToSocketClass hostOrSocketPath = case hostOrSocketPath of- '/' : _ -> PUnixSocket $ PPermanent hostOrSocketPath- _ -> PIpSocket $ pure hostOrSocketPath------------------------------------------------------------------------------------ Lenses--- Most this code was generated with microlens-th----------------------------------------------------------------------------------- | Local Lens alias-type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t--- | Local Lens' alias-type Lens' s a = Lens s s a a---- | Lens for 'partialEnvVarsInherit'-partialEnvVarsInheritL :: Lens' PartialEnvVars (Last Bool)-partialEnvVarsInheritL f_aj5e (PartialEnvVars x_aj5f x_aj5g)- = (fmap (\ y_aj5h -> (PartialEnvVars y_aj5h) x_aj5g))- (f_aj5e x_aj5f)-{-# INLINE partialEnvVarsInheritL #-}---- | Lens for 'partialEnvVarsSpecific'-partialEnvVarsSpecificL :: Lens' PartialEnvVars (Map String String)-partialEnvVarsSpecificL f_aj5i (PartialEnvVars x_aj5j x_aj5k)- = (fmap (\ y_aj5l -> (PartialEnvVars x_aj5j) y_aj5l))- (f_aj5i x_aj5k)-{-# INLINE partialEnvVarsSpecificL #-}---- | Lens for 'partialProcessConfigCmdLine'-partialProcessConfigCmdLineL ::- Lens' PartialProcessConfig PartialCommandLineArgs-partialProcessConfigCmdLineL- f_allv- (PartialProcessConfig x_allw x_allx x_ally x_allz x_allA)- = (fmap- (\ y_allB- -> ((((PartialProcessConfig x_allw) y_allB) x_ally) x_allz)- x_allA))- (f_allv x_allx)-{-# INLINE partialProcessConfigCmdLineL #-}---- | Lens for 'partialProcessConfigEnvVars'-partialProcessConfigEnvVarsL ::- Lens' PartialProcessConfig PartialEnvVars-partialProcessConfigEnvVarsL- f_allC- (PartialProcessConfig x_allD x_allE x_allF x_allG x_allH)- = (fmap- (\ y_allI- -> ((((PartialProcessConfig y_allI) x_allE) x_allF) x_allG)- x_allH))- (f_allC x_allD)-{-# INLINE partialProcessConfigEnvVarsL #-}---- | Lens for 'partialProcessConfigStdErr'-partialProcessConfigStdErrL ::- Lens' PartialProcessConfig (Last Handle)-partialProcessConfigStdErrL- f_allJ- (PartialProcessConfig x_allK x_allL x_allM x_allN x_allO)- = (fmap- (\ y_allP- -> ((((PartialProcessConfig x_allK) x_allL) x_allM) x_allN)- y_allP))- (f_allJ x_allO)---- | Lens for 'partialProcessConfigStdIn'-{-# INLINE partialProcessConfigStdErrL #-}-partialProcessConfigStdInL ::- Lens' PartialProcessConfig (Last Handle)-partialProcessConfigStdInL- f_allQ- (PartialProcessConfig x_allR x_allS x_allT x_allU x_allV)- = (fmap- (\ y_allW- -> ((((PartialProcessConfig x_allR) x_allS) y_allW) x_allU)- x_allV))- (f_allQ x_allT)-{-# INLINE partialProcessConfigStdInL #-}---- | Lens for 'partialProcessConfigStdOut'-partialProcessConfigStdOutL ::- Lens' PartialProcessConfig (Last Handle)-partialProcessConfigStdOutL- f_allX- (PartialProcessConfig x_allY x_allZ x_alm0 x_alm1 x_alm2)- = (fmap- (\ y_alm3- -> ((((PartialProcessConfig x_allY) x_allZ) x_alm0) y_alm3)- x_alm2))- (f_allX x_alm1)-{-# INLINE partialProcessConfigStdOutL #-}---- | Lens for 'partialPostgresPlanClientConfig'-partialPostgresPlanClientConfigL ::- Lens' PartialPostgresPlan Client.Options-partialPostgresPlanClientConfigL- f_am1y- (PartialPostgresPlan x_am1z x_am1A)- = (fmap (\ y_am1B -> (PartialPostgresPlan x_am1z) y_am1B))- (f_am1y x_am1A)-{-# INLINE partialPostgresPlanClientConfigL #-}---- | Lens for 'partialPostgresPlanProcessConfig'-partialPostgresPlanProcessConfigL ::- Lens' PartialPostgresPlan PartialProcessConfig-partialPostgresPlanProcessConfigL- f_am1C- (PartialPostgresPlan x_am1D x_am1E)- = (fmap (\ y_am1F -> (PartialPostgresPlan y_am1F) x_am1E))- (f_am1C x_am1D)-{-# INLINE partialPostgresPlanProcessConfigL #-}---- | Lens for 'partialPlanConfig'-partialPlanConfigL :: Lens' PartialPlan [String]-partialPlanConfigL- f_amcw- (PartialPlan x_amcx x_amcy x_amcz x_amcA x_amcB x_amcC)- = (fmap- (\ y_amcD- -> (((((PartialPlan x_amcx) x_amcy) x_amcz) x_amcA) y_amcD)- x_amcC))- (f_amcw x_amcB)-{-# INLINE partialPlanConfigL #-}---- | Lens for 'partialPlanCreateDb'-partialPlanCreateDbL ::- Lens' PartialPlan (Maybe PartialProcessConfig)-partialPlanCreateDbL- f_amcE- (PartialPlan x_amcF x_amcG x_amcH x_amcI x_amcJ x_amcK)- = (fmap- (\ y_amcL- -> (((((PartialPlan x_amcF) x_amcG) y_amcL) x_amcI) x_amcJ)- x_amcK))- (f_amcE x_amcH)-{-# INLINE partialPlanCreateDbL #-}---- | Lens for 'partialPlanDataDirectory'-partialPlanDataDirectoryL :: Lens' PartialPlan (Last String)-partialPlanDataDirectoryL- f_amcM- (PartialPlan x_amcN x_amcO x_amcP x_amcQ x_amcR x_amcS)- = (fmap- (\ y_amcT- -> (((((PartialPlan x_amcN) x_amcO) x_amcP) x_amcQ) x_amcR)- y_amcT))- (f_amcM x_amcS)-{-# INLINE partialPlanDataDirectoryL #-}---- | Lens for 'partialPlanInitDb'-partialPlanInitDbL ::- Lens' PartialPlan (Maybe PartialProcessConfig)-partialPlanInitDbL- f_amcU- (PartialPlan x_amcV x_amcW x_amcX x_amcY x_amcZ x_amd0)- = (fmap- (\ y_amd1- -> (((((PartialPlan x_amcV) y_amd1) x_amcX) x_amcY) x_amcZ)- x_amd0))- (f_amcU x_amcW)-{-# INLINE partialPlanInitDbL #-}---- | Lens for 'partialPlanLogger'-partialPlanLoggerL :: Lens' PartialPlan (Last Logger)-partialPlanLoggerL- f_amd2- (PartialPlan x_amd3 x_amd4 x_amd5 x_amd6 x_amd7 x_amd8)- = (fmap- (\ y_amd9- -> (((((PartialPlan y_amd9) x_amd4) x_amd5) x_amd6) x_amd7)- x_amd8))- (f_amd2 x_amd3)-{-# INLINE partialPlanLoggerL #-}---- | Lens for 'partialPlanPostgres'-partialPlanPostgresL :: Lens' PartialPlan PartialPostgresPlan-partialPlanPostgresL- f_amda- (PartialPlan x_amdb x_amdc x_amdd x_amde x_amdf x_amdg)- = (fmap- (\ y_amdh- -> (((((PartialPlan x_amdb) x_amdc) x_amdd) y_amdh) x_amdf)- x_amdg))- (f_amda x_amde)-{-# INLINE partialPlanPostgresL #-}---- | Lens for 'resourcesDataDir'-resourcesDataDirL :: Lens' Resources DirectoryType-resourcesDataDirL f_ampd (Resources x_ampe x_ampf x_ampg)- = (fmap (\ y_amph -> ((Resources x_ampe) x_ampf) y_amph))- (f_ampd x_ampg)-{-# INLINE resourcesDataDirL #-}---- | Lens for 'resourcesPlan'-resourcesPlanL :: Lens' Resources Plan-resourcesPlanL f_ampi (Resources x_ampj x_ampk x_ampl)- = (fmap (\ y_ampm -> ((Resources y_ampm) x_ampk) x_ampl))- (f_ampi x_ampj)-{-# INLINE resourcesPlanL #-}---- | Lens for 'resourcesSocket'-resourcesSocketL :: Lens' Resources SocketClass-resourcesSocketL f_ampn (Resources x_ampo x_ampp x_ampq)- = (fmap (\ y_ampr -> ((Resources x_ampo) y_ampr) x_ampq))- (f_ampn x_ampp)-{-# INLINE resourcesSocketL #-}---- | Lens for 'configDataDir'-configDataDirL :: Lens' Config PartialDirectoryType-configDataDirL f_amyD (Config x_amyE x_amyF x_amyG x_amyH)- = (fmap (\ y_amyI -> (((Config x_amyE) x_amyF) y_amyI) x_amyH))- (f_amyD x_amyG)-{-# INLINE configDataDirL #-}---- | Lens for 'configPlan'-configPlanL :: Lens' Config PartialPlan-configPlanL f_amyJ (Config x_amyK x_amyL x_amyM x_amyN)- = (fmap (\ y_amyO -> (((Config y_amyO) x_amyL) x_amyM) x_amyN))- (f_amyJ x_amyK)-{-# INLINE configPlanL #-}---- | Lens for 'configPort'-configPortL :: Lens' Config (Last (Maybe Int))-configPortL f_amyP (Config x_amyQ x_amyR x_amyS x_amyT)- = (fmap (\ y_amyU -> (((Config x_amyQ) x_amyR) x_amyS) y_amyU))- (f_amyP x_amyT)-{-# INLINE configPortL #-}---- | Lens for 'configSocket'-configSocketL :: Lens' Config PartialSocketClass-configSocketL f_amyV (Config x_amyW x_amyX x_amyY x_amyZ)- = (fmap (\ y_amz0 -> (((Config x_amyW) y_amz0) x_amyY) x_amyZ))- (f_amyV x_amyX)-{-# INLINE configSocketL #-}---- | Lens for 'partialCommandLineArgsIndexBased'-partialCommandLineArgsIndexBasedL ::- Lens' PartialCommandLineArgs (Map Int String)-partialCommandLineArgsIndexBasedL- f_amNr- (PartialCommandLineArgs x_amNs x_amNt)- = (fmap (\ y_amNu -> (PartialCommandLineArgs x_amNs) y_amNu))- (f_amNr x_amNt)-{-# INLINE partialCommandLineArgsIndexBasedL #-}---- | Lens for 'partialCommandLineArgsKeyBased'-partialCommandLineArgsKeyBasedL ::- Lens' PartialCommandLineArgs (Map String (Maybe String))-partialCommandLineArgsKeyBasedL- f_amNv- (PartialCommandLineArgs x_amNw x_amNx)- = (fmap (\ y_amNy -> (PartialCommandLineArgs y_amNy) x_amNx))- (f_amNv x_amNw)-{-# INLINE partialCommandLineArgsKeyBasedL #-}
test/Spec.hs view
@@ -1,6 +1,6 @@ import Test.Hspec import Database.Postgres.Temp.Internal.Core-import Database.Postgres.Temp.Internal.Partial+import Database.Postgres.Temp.Internal.Config import Database.Postgres.Temp.Internal import Control.Exception import System.IO.Error@@ -29,10 +29,10 @@ -- Cleanup -fromCreateDb :: Maybe PartialProcessConfig -> Config+fromCreateDb :: Maybe ProcessConfig -> Config fromCreateDb createDb = mempty- { configPlan = mempty- { partialPlanCreateDb = createDb+ { plan = mempty+ { createDbConfig = createDb } } @@ -45,20 +45,20 @@ defaultConfigShouldMatchDefaultPlan = it "default options should match default plan" $ withRunner $ \DB{..} -> do let Resources {..} = dbResources- Plan {..} = resourcesPlan- PostgresPlan {..} = planPostgres- Client.dbname postgresPlanClientOptions `shouldBe` pure "postgres"- let Temporary tmpDataDir = resourcesDataDir+ CompletePlan {..} = resourcesPlan+ CompletePostgresPlan {..} = completePlanPostgres+ Client.dbname completePostgresPlanClientOptions `shouldBe` pure "postgres"+ let CTemporary tmpDataDir = resourcesDataDir tmpDataDir `shouldStartWith` "/tmp/tmp-postgres-data"- let Just port = getLast $ Client.port postgresPlanClientOptions+ let Just port = getLast $ Client.port completePostgresPlanClientOptions port `shouldSatisfy` (>32768)- let UnixSocket (Temporary unixSocket) = resourcesSocket+ let CUnixSocket (CTemporary unixSocket) = resourcesSocket unixSocket `shouldStartWith` "/tmp/tmp-postgres-socket"- postgresPlanClientOptions `shouldBe`+ completePostgresPlanClientOptions `shouldBe` (mempty- { Client.port = Client.port postgresPlanClientOptions- , Client.host = Client.host postgresPlanClientOptions- , Client.dbname = Client.dbname postgresPlanClientOptions+ { Client.port = Client.port completePostgresPlanClientOptions+ , Client.host = Client.host completePostgresPlanClientOptions+ , Client.dbname = Client.dbname completePostgresPlanClientOptions } ) @@ -72,38 +72,38 @@ it "returns the right client options for the plan" $ do let customPlan = mempty- { configPlan = mempty- { partialPlanPostgres = mempty- { partialPostgresPlanClientConfig = mempty+ { plan = mempty+ { postgresPlan = mempty+ { connectionOptions = mempty { Client.user = pure expectedUser , Client.password = pure expectedPassword , Client.dbname = pure expectedDbName } }- , partialPlanInitDb = pure standardProcessConfig- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsKeyBased =+ , initDbConfig = pure standardProcessConfig+ { commandLine = mempty+ { keyBased = Map.singleton "--username=" $ Just "user-name" }- , partialProcessConfigEnvVars = mempty- { partialEnvVarsSpecific = Map.singleton "PGPASSWORD" "password"- , partialEnvVarsInherit = pure True+ , environmentVariables = mempty+ { specific = Map.singleton "PGPASSWORD" "password"+ , inherit = pure True } }- , partialPlanCreateDb = pure standardProcessConfig- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsKeyBased =+ , createDbConfig = pure standardProcessConfig+ { commandLine = mempty+ { keyBased = Map.singleton "--username=" $ Just "user-name"- , partialCommandLineArgsIndexBased =+ , indexBased = Map.singleton 0 expectedDbName }- , partialProcessConfigEnvVars =+ , environmentVariables = mempty- { partialEnvVarsSpecific = Map.singleton "PGPASSWORD" "password"- , partialEnvVarsInherit = pure True+ { specific = Map.singleton "PGPASSWORD" "password"+ , inherit = pure True } }- , partialPlanConfig = [extraConfig]+ , postgresConfigFile = [extraConfig] } } -- hmm maybe I should provide lenses@@ -115,9 +115,9 @@ actualDuration `shouldBe` expectedDuration let Resources {..} = dbResources- Plan {..} = resourcesPlan- actualOptions = postgresPlanClientOptions planPostgres- actualPostgresConfig = planConfig+ CompletePlan {..} = resourcesPlan+ actualOptions = completePostgresPlanClientOptions completePlanPostgres+ actualPostgresConfig = completePlanConfig Client.user actualOptions `shouldBe` pure expectedUser Client.dbname actualOptions `shouldBe` pure expectedDbName Client.password actualOptions `shouldBe` pure expectedPassword@@ -126,8 +126,8 @@ invalidConfigFailsQuickly :: (Config -> IO ()) -> Spec invalidConfigFailsQuickly action = it "quickly fails with an invalid option" $ do let customPlan = mempty- { configPlan = mempty- { partialPlanConfig =+ { plan = mempty+ { postgresConfigFile = [ "log_directory = /this/does/not/exist" , "logging_collector = true" ]@@ -175,10 +175,13 @@ one `shouldBe` (1 :: Int) +{-+ Reenable after temp files are configurable #41 it "cleans up temp files" $ \(Runner runner) -> do initialFiles <- listDirectory "/tmp" runner $ const $ pure () listDirectory "/tmp" `shouldReturn` initialFiles+ -} -- This assumes that the directory is initially empty withInitDbEmptyInitially :: SpecWith Runner@@ -201,13 +204,13 @@ runner (const $ pure ()) `shouldThrow` (== CreateDbFailed (ExitFailure 1)) spec :: Spec-spec = do+spec = parallel $ do let defaultIpPlan = defaultConfig- { configSocket = PIpSocket $ Last Nothing+ { socketClass = IpSocket $ Last Nothing } specificHostIpPlan = defaultConfig- { configSocket = PIpSocket $ pure "localhost"+ { socketClass = IpSocket $ pure "localhost" } describe "start" $ do@@ -217,8 +220,8 @@ let startAction plan = bracket (either throwIO pure =<< startConfig plan) stop pure throwsIfInitDbIsNotOnThePath $ startAction defaultConfig invalidConfigFailsQuickly $ void . startAction- customConfigWork $ \plan@Config{..} f ->- bracket (either throwIO pure =<< startConfig plan) stop f+ customConfigWork $ \config@Config{..} f ->+ bracket (either throwIO pure =<< startConfig config) stop f describe "with" $ do let startAction = either throwIO pure =<< with (const $ pure ()) throwsIfInitDbIsNotOnThePath startAction@@ -254,7 +257,7 @@ someStandardTests "postgres" defaultConfigShouldMatchDefaultPlan - describe "start/stop" $ do+ describe "start/stop" $ parallel $ do before (pure $ Runner $ \f -> bracket (either throwIO pure =<< start) stop f) $ do someStandardTests "postgres" defaultConfigShouldMatchDefaultPlan@@ -277,8 +280,8 @@ let invalidCreateDbPlan = defaultConfig <> fromCreateDb ( pure $ standardProcessConfig- { partialProcessConfigCmdLine = mempty- { partialCommandLineArgsIndexBased =+ { commandLine = mempty+ { indexBased = Map.singleton 0 "template1" } }@@ -287,10 +290,10 @@ createDbThrowsIfTheDbExists let noCreateTemplate1 = mempty- { configPlan = mempty- { partialPlanCreateDb = Nothing- , partialPlanPostgres = mempty- { partialPostgresPlanClientConfig = mempty+ { plan = mempty+ { createDbConfig = Nothing+ , postgresPlan = mempty+ { connectionOptions = mempty { Client.dbname = pure "template1" } }@@ -320,7 +323,7 @@ it "fails on non-empty data directory" $ \dirPath -> do writeFile (dirPath <> "/PG_VERSION") "1 million" let nonEmptyFolderPlan = defaultConfig- { configDataDir = PPermanent dirPath+ { dataDirectory = Permanent dirPath } startAction = bracket (either throwIO pure =<< startConfig nonEmptyFolderPlan) stop $ const $ pure () @@ -329,9 +332,9 @@ it "works if on non-empty if initdb is disabled" $ \dirPath -> do throwIfNotSuccess id =<< system ("initdb " <> dirPath) let nonEmptyFolderPlan = defaultConfig- { configDataDir = PPermanent dirPath- , configPlan = (configPlan defaultConfig)- { partialPlanInitDb = Nothing+ { dataDirectory = Permanent dirPath+ , plan = (plan defaultConfig)+ { initDbConfig = Nothing } } bracket (either throwIO pure =<< startConfig nonEmptyFolderPlan) stop $ \db -> do@@ -342,8 +345,8 @@ one `shouldBe` (1 :: Int) let justBackupResources = mempty- { configPlan = mempty- { partialPlanConfig =+ { plan = mempty+ { postgresConfigFile = [ "wal_level=replica" , "archive_mode=on" , "max_wal_senders=2"
tmp-postgres.cabal view
@@ -1,7 +1,7 @@ name: tmp-postgres-version: 1.7.1.0+version: 1.8.0.0 synopsis: Start and stop a temporary postgres-description: See README.md+description: Start and stop a temporary postgres. See README.md homepage: https://github.com/jfischoff/tmp-postgres#readme license: BSD3 license-file: LICENSE@@ -19,7 +19,7 @@ exposed-modules: Database.Postgres.Temp , Database.Postgres.Temp.Internal , Database.Postgres.Temp.Internal.Core- , Database.Postgres.Temp.Internal.Partial+ , Database.Postgres.Temp.Internal.Config default-extensions: ApplicativeDo , DeriveFunctor