clickhouse-migrate-0.1.0.0: src/ClickHouseMigrate/Internal/Reporting.hs
-- | Convert validated storage into safe immutable observations, without SQL.
module ClickHouseMigrate.Internal.Reporting where
import ClickHouseMigrate.Internal.ExclusiveTypes (AccessDescription)
import ClickHouseMigrate.Internal.LedgerCodec (definitions)
import ClickHouseMigrate.Internal.LedgerStore qualified as Store
import ClickHouseMigrate.Internal.LedgerTypes qualified as L
import ClickHouseMigrate.Internal.TargetTypes qualified as T
import ClickHouseMigrate.Internal.Transport qualified as Transport
import ClickHouseMigrate.Ledger qualified as Ledger
import ClickHouseMigrate.Plan qualified as Plan
import ClickHouseMigrate.Prelude
import ClickHouseMigrate.Report qualified as R
import ClickHouseMigrate.Target (ServerIdentity, TargetKey)
summaryDefinition :: L.Definition -> R.DefinitionSummary
summaryDefinition (L.Definition ordinal componentIndex component dependencies migration checksum steps) =
R.DefinitionSummary ordinal componentIndex component dependencies migration checksum [(name, digest) | L.StepDefinition name digest <- steps]
emptyHistory :: TargetKey -> R.InspectionAccess -> R.HistoryReport
emptyHistory target access = R.HistoryReport target Nothing R.LedgerUnknown Nothing access [] [] False
observationReport :: TargetKey -> Maybe ServerIdentity -> R.InspectionAccess -> Store.Observation -> R.HistoryReport
observationReport target server access = \case
Store.Missing -> R.HistoryReport target server R.LedgerMissing Nothing access [] [] False
Store.EmptyNamespace -> R.HistoryReport target server R.LedgerUninitialized Nothing access [] [] False
Store.History projection -> fromProjection server access projection
fromProjection :: Maybe ServerIdentity -> R.InspectionAccess -> L.Projection -> R.HistoryReport
fromProjection server access projection =
let L.Projection position@(L.LedgerPosition ledger _ _) target completed active adoption events = projection
complete (L.RecordedMigration definition state checkpoints) =
migrationSummary
events
definition
(case state of L.Applied -> R.Applied; L.Adopted -> R.Adopted)
(latestAttempt definition events)
checkpoints
running = case active of
Nothing -> []
Just (L.ActiveMigration definition attempt checkpoints _) ->
[migrationSummary events definition (if any failed checkpoints then R.Failed else R.InProgress) (Just (L.AttemptId attempt)) checkpoints]
adopting = case adoption of
Nothing -> []
Just (L.ActiveAdoption _ entries) -> [migrationSummary events definition R.InProgress Nothing checkpoints | L.RecordedMigration definition _ checkpoints <- entries]
in R.HistoryReport
target
server
(R.LedgerInitializedAt ledger)
(Just position)
access
(map eventReport events)
(map complete completed ++ running ++ adopting)
(isJust active || isJust adoption)
where
failed (L.Failed _ _) = True; failed _ = False
latestAttempt :: L.Definition -> [L.LedgerEvent] -> Maybe L.AttemptId
latestAttempt definition events = case [L.AttemptId attempt | event <- events, sameMigration definition event, Just attempt <- [L.attemptId event]] of
[] -> Nothing
values -> Just (last values)
sameMigration :: L.Definition -> L.LedgerEvent -> Bool
sameMigration (L.Definition _ _ component _ migration _ _) event = case L.context event of
Just (L.EventContext c m _ _) -> c == component && m == migration
_ -> False
stepAttempt :: L.Definition -> Int -> Maybe Text -> [L.LedgerEvent] -> Maybe R.StepAttempt
stepAttempt definition index expected events = do
event <- case [event | event <- reverse events, sameMigration definition event, L.StartStep i query <- [L.body event], i == index, maybe True (== query) expected] of
value : _ -> Just value
_ -> Nothing
attempt <- L.AttemptId <$> L.attemptId event
case L.body event of
L.StartStep _ query -> case drop index (L.steps definition) of L.StepDefinition name _ : _ -> Just (R.StepAttempt attempt name (T.QueryId query)); _ -> Nothing
_ -> Nothing
migrationSummary :: [L.LedgerEvent] -> L.Definition -> R.MigrationStatus -> Maybe L.AttemptId -> [L.StepState] -> R.MigrationSummary
migrationSummary events definition state attempt checkpoints =
R.MigrationSummary
(summaryDefinition definition)
state
attempt
[ let (status, query, evidence) = case checkpoint of
L.NeverStarted -> (R.StepPending, Nothing, Nothing)
L.Started value -> (R.StepUncertain, Just value, Nothing)
L.Succeeded value -> (R.StepObservedSuccess, Just value, Nothing)
L.Failed value _ -> (R.StepObservedFailure, Just value, Nothing)
L.Accepted (L.Evidence level references) -> (R.StepAccepted, Nothing, Just (R.EvidenceSummary level references))
in R.StepSummary name checksum status (if status == R.StepPending then Nothing else stepAttempt definition index query events) evidence
| (index, (L.StepDefinition name checksum, checkpoint)) <- zip [0 ..] (zip (L.steps definition) checkpoints)
]
pendingSummary :: L.Definition -> R.MigrationSummary
pendingSummary definition =
R.MigrationSummary
(summaryDefinition definition)
R.Pending
Nothing
[R.StepSummary name checksum R.StepPending Nothing Nothing | L.StepDefinition name checksum <- L.steps definition]
inspectHistory :: Plan.MigrationPlan -> R.HistoryReport -> R.InspectionReport
inspectHistory plan history@(R.HistoryReport _ _ _ _ _ events recorded unresolved) =
let supplied = definitions plan
actual = [definition | R.MigrationSummary definition _ _ _ <- recorded]
expected = map summaryDefinition supplied
changes = [R.DefinitionChanged index (at index expected) (Just definition) | (index, definition) <- zip [0 ..] actual, at index expected /= Just definition]
attempts = [R.UnresolvedAttempt attempt | R.MigrationSummary _ state (Just attempt) _ <- recorded, state `elem` [R.InProgress, R.Failed]]
batches = if unresolved && null attempts then case reverse [batch | R.HistoryEvent _ _ _ _ _ _ _ _ _ (R.AdoptionBeginning batch) <- events] of batch : _ -> [R.IncompleteAdoption batch]; _ -> [] else []
current =
[ case at index recorded of
Just value@(R.MigrationSummary observed _ _ _) | observed == definition -> value
Just (R.MigrationSummary _ _ attempt steps) -> R.MigrationSummary definition R.Invalid attempt steps
Nothing -> pendingSummary source
| (index, (definition, source)) <- zip [0 ..] (zip expected supplied)
]
in R.InspectionReport history current (changes ++ attempts ++ batches)
compatibilityError :: R.InspectionReport -> Maybe R.RunError
compatibilityError (R.InspectionReport _ _ issues)
| any changed issues = Just (R.IncompatibleHistory issues)
| not (null issues) = Just (R.LedgerFailed L.UnresolvedHistory)
| otherwise = Nothing
where
changed (R.DefinitionChanged _ _ _) = True; changed _ = False
isCurrent :: R.InspectionReport -> Bool
isCurrent (R.InspectionReport _ migrations issues) = null issues && all (\(R.MigrationSummary _ state _ _) -> state `elem` [R.Applied, R.Adopted]) migrations
runHistory :: R.RunReport -> R.HistoryReport
runHistory (R.RunReport _ _ _ _ history _ _ _) = history
runPosition :: R.RunReport -> Maybe L.LedgerPosition
runPosition report = case runHistory report of R.HistoryReport _ _ _ position _ _ _ _ -> position
updateRun :: Plan.MigrationPlan -> R.HistoryReport -> R.RunReport -> R.RunReport
updateRun plan history (R.RunReport identity target access acquired _ _ observations initialized) = let R.InspectionReport _ migrations _ = inspectHistory plan history in R.RunReport identity target access acquired history migrations observations initialized
recordSql :: R.SqlObservation -> R.RunReport -> R.RunReport
recordSql value report@(R.RunReport _ _ _ _ _ _ observations _) = report {R.observations = observations ++ [value]}
initialRun :: Text -> TargetKey -> AccessDescription -> Plan.MigrationPlan -> R.RunReport
initialRun run target access plan =
let history = emptyHistory target (R.GuardedObservation access)
R.InspectionReport _ migrations _ = inspectHistory plan history
in R.RunReport run target access False history migrations [] Nothing
historyPosition :: R.HistoryReport -> Maybe L.LedgerPosition
historyPosition (R.HistoryReport _ _ _ position _ _ _ _) = position
stepReference :: L.Definition -> Int -> Maybe R.StepReference
stepReference (L.Definition _ _ component _ migration checksum steps) index = case drop index steps of
L.StepDefinition step digest : _ | index >= 0 -> Just (R.StepReference component migration step checksum digest)
_ -> Nothing
eventReport :: L.LedgerEvent -> R.HistoryEvent
eventReport (L.LedgerEvent number event ledger run attempt time audit runner context body) =
R.HistoryEvent
number
event
ledger
run
(L.AttemptId <$> attempt)
time
audit
runner
(fmap (\(L.EventContext component migration checksum step) -> (component, migration, checksum, step)) context)
(bodyReport body)
bodyReport :: L.EventBody -> R.HistoryBody
bodyReport = \case
L.Initialize target -> R.LedgerInitialized target
L.StartMigration definition -> R.MigrationDefinition (summaryDefinition definition)
L.StartStep index query -> R.StepIntent index (T.QueryId query)
L.SucceedStep index query -> R.StepCompletion index (T.QueryId query)
L.FailStep index query (L.FailureEvidence code status server) -> R.ExecutionFailure index (T.QueryId query) (R.FailureSummary code status server)
L.CompleteMigration -> R.MigrationCompletion
L.AuthorizeRecovery (L.Recovery action position previous next (L.QuiescenceEvidence target audit)) -> R.RecoveryAuthorization (recoveryAction action) position (L.AttemptId previous) (L.AttemptId next) target audit
L.AcceptCompletion index (L.Evidence level references) -> R.AcceptedCompletion index (R.EvidenceSummary level references)
L.StartAdoption (L.AdoptionBatch source snapshot mapping definitionHash count) -> R.AdoptionBeginning (R.AdoptionSummary source snapshot mapping definitionHash count)
L.AdoptEntry definition (L.Evidence level references) -> R.AdoptedDefinition (summaryDefinition definition) (R.EvidenceSummary level references)
L.CompleteAdoption digest -> R.AdoptionCompletion digest
recoveryAction :: L.RecoveryAction -> R.RecoveryAction
recoveryAction = \case L.Resume -> R.Resume; L.RetryStep index -> R.RetryStep index; L.AcceptStep index -> R.AcceptStep index; L.Finalize -> R.Finalize
outcomeError :: Transport.TransportOutcome -> Maybe L.AppendReceipt -> R.RunError
outcomeError outcome receipt = case outcome of
Transport.NotSubmitted failure -> R.LedgerRequestFailed Ledger.PositiveNonSubmission failure receipt
Transport.ObservedServerFailure failure -> R.LedgerRequestFailed Ledger.ObservedFailure failure receipt
Transport.SubmissionUncertain failure -> R.LedgerRequestFailed Ledger.UncertainSubmission failure receipt
_ -> R.UnexpectedRuntimeFailure
storeError :: Store.StoreError -> R.RunError
storeError = \case
Store.LedgerProblem failure -> R.LedgerFailed failure
Store.AccessProblem failure -> R.AccessFailed failure
Store.RequestProblem outcome receipt -> outcomeError outcome receipt
Store.InitializationProblem ledger stage query outcome -> case outcomeError outcome Nothing of
R.LedgerRequestFailed evidence failure _ -> R.InitializationFailed (ledgerIdText ledger) stage query evidence failure
_ -> R.UnexpectedRuntimeFailure
where
ledgerIdText (T.LedgerId value) = value
at :: Int -> [a] -> Maybe a
at index values
| index < 0 = Nothing
| otherwise = case drop index values of value : _ -> Just value; _ -> Nothing