clickhouse-migrate-0.1.0.0: src/ClickHouseMigrate/Inspection.hs
-- | Read-only observations. No inspection path initializes or repairs history.
module ClickHouseMigrate.Inspection
( InspectionOptions (Observe, Guarded),
VerifyOptions,
verifyOptions,
status,
history,
verify,
)
where
import ClickHouseMigrate.Internal.Exclusive (checkGuard, withExclusive)
import ClickHouseMigrate.Internal.ExclusiveTypes (AccessResult (..), ExclusiveAccess (..))
import ClickHouseMigrate.Internal.Identity (freshUuid)
import ClickHouseMigrate.Internal.LedgerStore qualified as Store
import ClickHouseMigrate.Internal.Reporting qualified as Reporting
import ClickHouseMigrate.Internal.RuntimeTypes (InspectionOptions (..), VerifyOptions (..))
import ClickHouseMigrate.Internal.ServerPolicy (TargetCheckError (..), withTransportChecked)
import ClickHouseMigrate.Internal.TargetTypes (QueryId (..), TargetConfig (..))
import ClickHouseMigrate.Internal.Transport qualified as Transport
import ClickHouseMigrate.Plan (MigrationPlan)
import ClickHouseMigrate.Prelude
import ClickHouseMigrate.Report qualified as R
import Control.Exception (SomeAsyncException, SomeException, fromException, throwIO, try)
import Data.IORef (newIORef, readIORef, writeIORef)
verifyOptions :: Bool -> VerifyOptions
verifyOptions = VerifyOptions
history :: InspectionOptions -> TargetConfig -> IO (Either R.InspectionFailure R.HistoryReport)
history options config@(TargetConfig target _ _ _ _ _ _) = do
latest <- newIORef Nothing
observed <- try @SomeException $ do
probe <- QueryId . ("chm-" <>) <$> freshUuid
let scoped guard access =
let check = case guard of Nothing -> pure (Right ()); Just capability -> checkGuard capability >>= pure . either (const (Left (Transport.TransportError Transport.AdministrativeAccessLost Nothing Nothing))) (const (Right ()))
in withTransportChecked config probe check $ \transport -> Store.storeResult $ do
observation <- Store.load guard transport config
let report = Reporting.observationReport target (Just (Transport.transportIdentity transport)) access observation
writeIORef latest (Just report)
pure report
flatten (Transport.ScopeResult result cleanup) =
( case result of
Left failure -> Left (R.TargetFailed failure)
Right (Left (TargetRejected failure)) -> Left (R.TargetFailed failure)
Right (Left (ProbeFailure failure)) -> Left (Reporting.outcomeError failure Nothing)
Right (Right (Left failure)) -> Left (Reporting.storeError failure)
Right (Right (Right report)) -> Right report,
map R.TransportCleanup cleanup
)
case options of
Observe -> flatten <$> scoped Nothing R.UnguardedObservation
Guarded provider@(ExclusiveAccess description _ _ _ _) -> do
AccessResult result cleanup <- withExclusive provider target (\guard -> scoped (Just guard) (R.GuardedObservation description))
pure $ case result of
Left failure -> (Left (R.AccessFailed failure), map R.AccessCleanup cleanup)
Right value -> let (answer, errors) = flatten value in (answer, errors ++ map R.AccessCleanup cleanup)
snapshot <- readIORef latest
case observed of
Left exception | isJust (fromException exception :: Maybe SomeAsyncException) -> throwIO exception
Left _ -> pure (Left (R.InspectionFailure R.UnexpectedRuntimeFailure snapshot []))
Right (Left failure, cleanup) -> pure (Left (R.InspectionFailure failure snapshot cleanup))
Right (Right _, cleanup@(_ : _)) -> pure (Left (R.InspectionFailure R.CleanupFailed snapshot cleanup))
Right (Right report, []) -> pure (Right report)
status :: InspectionOptions -> TargetConfig -> MigrationPlan -> IO (Either R.InspectionFailure R.InspectionReport)
status options config plan = fmap (Reporting.inspectHistory plan) <$> history options config
verify :: InspectionOptions -> TargetConfig -> VerifyOptions -> MigrationPlan -> IO (Either R.InspectionFailure R.VerificationReport)
verify options config (VerifyOptions requireCurrent) plan = do
result <- status options config plan
pure $
result >>= \report@(R.InspectionReport snapshot _ _) -> case Reporting.compatibilityError report of
Just failure -> Left (R.InspectionFailure failure (Just snapshot) [])
Nothing | requireCurrent && not (Reporting.isCurrent report) -> Left (R.InspectionFailure R.VerificationNotCurrent (Just snapshot) [])
Nothing -> Right (R.VerificationReport report (Reporting.isCurrent report))