packages feed

clickhouse-migrate-0.1.0.0: src/ClickHouseMigrate/ExclusiveAccess.hs

-- | Explicit integration with the application's administrative coordinator.
module ClickHouseMigrate.ExclusiveAccess
  ( ExclusiveAccess,
    externallySerialized,
    AccessDescription,
    exclusiveAccessDescription,
    accessStrategy,
    accessScope,
    rejectAccessAcquisition,
    AccessError (..),
  )
where

import ClickHouseMigrate.Internal.ExclusiveTypes
import ClickHouseMigrate.Prelude
import ClickHouseMigrate.Target (TargetKey)
import Control.Exception (throwIO)
import Data.Text qualified as Text

-- | Acquire excludes ALL administrators for the complete target, and must be
-- exception safe. Release must not admit a successor until this client cannot
-- submit and all predecessor server work, including ledger INSERTs/DDL, is
-- quiescent. Returning True from 'alive' attests the lease is still held; a lost
-- lease cannot fence already submitted SQL. Types cannot verify these external
-- promises. There is deliberately no default/no-op provider.
externallySerialized :: Text -> Text -> Int -> (TargetKey -> IO lease) -> (lease -> IO ()) -> (lease -> IO Bool) -> Either AccessError ExclusiveAccess
externallySerialized strategy scope acquisitionMicros acquire release alive
  | valid strategy && valid scope && acquisitionMicros > 0 && acquisitionMicros <= 300000000 =
      Right (ExclusiveAccess (AccessDescription strategy scope) acquisitionMicros acquire release alive)
  | otherwise = Left InvalidAccessStrategy
  where
    valid value = not (Text.null (Text.strip value)) && Text.length value <= 1024 && not (Text.any (== '\0') value)

accessStrategy :: AccessDescription -> Text
accessStrategy (AccessDescription strategy _) = strategy

accessScope :: AccessDescription -> Text
accessScope (AccessDescription _ scope) = scope

-- | Reject acquisition with a safe typed code from a caller-owned provider.
rejectAccessAcquisition :: AccessError -> IO a
rejectAccessAcquisition = throwIO . AccessAcquisitionException

-- | Read-only description; this does not acquire or expose a live guard.
exclusiveAccessDescription :: ExclusiveAccess -> AccessDescription
exclusiveAccessDescription (ExclusiveAccess description _ _ _ _) = description