pgmq-config-0.6.0.0: test/PartitionSpec.hs
{-# LANGUAGE CPP #-}
module PartitionSpec (tests) where
import Control.Exception (finally)
import Control.Monad (forM_)
import Data.Int (Int32)
import Data.List (isInfixOf)
import EphemeralDb (withPgmqDb)
import Hasql.Decoders qualified as D
import Hasql.Encoders qualified as E
import Hasql.Pool qualified as Pool
import Hasql.Session qualified as Session
import Hasql.Statement (preparable)
import Pgmq.Config
import Pgmq.Types (QueueName, parseQueueName, queueNameToText)
import System.Environment (lookupEnv)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))
#ifdef PGMQ_EFFECTFUL
import Effectful (runEff)
import Effectful.Error.Static (runError)
import Pgmq.Config.Effectful (ensureQueuesReportEff)
import Pgmq.Effectful (PgmqRuntimeError, runPgmq)
#endif
tests :: TestTree
tests = testGroup "Partition compatibility" [backendTests backend | backend <- backends]
backends :: [Bool]
#ifdef PGMQ_EFFECTFUL
backends = [False, True]
#else
backends = [False]
#endif
reconcile :: Bool -> Pool.Pool -> [QueueConfig] -> IO (Either String [ReconcileAction])
#ifdef PGMQ_EFFECTFUL
reconcile True pool configs = either (Left . show) Right <$> runEff (runError @PgmqRuntimeError (runPgmq pool (ensureQueuesReportEff configs)))
#endif
reconcile _ pool configs = either (Left . show) Right <$> Pool.use pool (ensureQueuesReport configs)
backendTests :: Bool -> TestTree
backendTests effectful =
testGroup
(if effectful then "effectful" else "direct")
$ [ testCase ("creation and skip " <> show requested) $ isolated $ \pool -> withPartman pool $ do
stock <- (== Just "1.12.0") <$> lookupEnv "PGMQ_TEST_SCHEMA_VERSION"
qn <- right (parseQueueName "partition_config")
let config n = partitionedQueue qn (PartitionConfig "10" "100" n)
result <- reconcile effectful pool [config requested]
if stock && requested /= Nothing
then case result of
Left err -> assertBool "explicit premake requires 1.13" ("42883" `isInfixOf` err)
Right _ -> assertFailure "unsupported premake succeeded"
else do
actions <- right result
case actions of
[CreatedQueue q _] -> q @?= qn
other -> assertFailure ("expected creation: " <> show other)
let expected = maybe 4 id requested
counts pool qn >>= (@?= [expected, expected])
-- Turn every creation call into an error, proving the existing-queue
-- path makes no call even when a different (invalid) count is requested.
let signature = "queue_name text, partition_interval text DEFAULT '10000', retention_interval text DEFAULT '100000'" <> if stock then "" else ", premake integer DEFAULT 4"
session pool (Session.script ("CREATE OR REPLACE FUNCTION pgmq.create_partitioned(" <> signature <> ") RETURNS void LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'unexpected partition creation'; END $$"))
skipped <- right =<< reconcile effectful pool [config (Just 0)]
case skipped of
[SkippedQueue q] -> q @?= qn
other -> assertFailure ("expected skip: " <> show other)
counts pool qn >>= (@?= [expected, expected])
| requested <- [Nothing, Just 2]
]
++ [ testCase "zero and negative counts fail on new queues" $ forM_ [0, -1] $ \n -> isolated $ \pool -> withPartman pool $ do
stock <- (== Just "1.12.0") <$> lookupEnv "PGMQ_TEST_SCHEMA_VERSION"
qn <- right (parseQueueName "invalid_partition_config")
result <- reconcile effectful pool [partitionedQueue qn (PartitionConfig "10" "100" (Just n))]
case result of
Left err -> assertBool "server error retained" ((if stock then "42883" else "premake must be at least 1") `isInfixOf` err)
Right _ -> assertFailure "invalid premake succeeded"
]
counts :: Pool.Pool -> QueueName -> IO [Int32]
counts pool qn =
session pool $
Session.statement (queueNameToText qn) $
preparable
"select premake from partman.part_config where parent_table in ('pgmq.q_' || $1, 'pgmq.a_' || $1) order by parent_table"
(E.param (E.nonNullable E.text))
(D.rowList (D.column (D.nonNullable D.int4)))
session :: Pool.Pool -> Session.Session a -> IO a
session pool action = right =<< Pool.use pool action
right :: (Show e) => Either e a -> IO a
right = either (\err -> assertFailure (show err) >> fail "unreachable") pure
isolated :: (Pool.Pool -> IO ()) -> IO ()
isolated action = right =<< withPgmqDb (\pool -> action pool `finally` Pool.release pool)
withPartman :: Pool.Pool -> IO () -> IO ()
withPartman pool action = do
available <-
session pool $
Session.statement () $
preparable
"select exists (select from pg_extension where extname = 'pg_partman')"
E.noParams
(D.singleRow (D.column (D.nonNullable D.bool)))
required <- (== Just "1") <$> lookupEnv "PGMQ_REQUIRE_PARTMAN"
if available then action else if required then assertFailure "pg_partman required" else putStrLn "SKIPPED: pg_partman is unavailable"