kiroku-store-0.4.0.0: src/Kiroku/Store/Subscription/CheckpointInventory/SQL.hs
{-# LANGUAGE MultilineStrings #-}
module Kiroku.Store.Subscription.CheckpointInventory.SQL (
getSubscriptionCheckpointInventoryStmt,
) where
import Data.Int (Int32, Int64)
import Data.Text (Text)
import Data.Time.Clock (UTCTime)
import Data.Vector (Vector)
import Data.Vector qualified as V
import Hasql.Decoders qualified as D
import Hasql.Encoders qualified as E
import Hasql.Statement (Statement, preparable)
import Hasql.Statement qualified as Statement
import Kiroku.Store.Subscription.Types (
SubscriptionCheckpoint (..),
SubscriptionCheckpointInventory (..),
SubscriptionName (..),
)
import Kiroku.Store.Types (GlobalPosition (..))
data InventoryRow = InventoryRow
{ rowStorePosition :: !Int64
, rowSubscriptionName :: !(Maybe Text)
, rowConsumerGroupMember :: !(Maybe Int32)
, rowCheckpointPosition :: !(Maybe Int64)
, rowCheckpointUpdatedAt :: !(Maybe UTCTime)
}
getSubscriptionCheckpointInventoryStmt :: Statement () SubscriptionCheckpointInventory
getSubscriptionCheckpointInventoryStmt =
Statement.refineResult finalizeInventory $
preparable
"""
SELECT store_head.stream_version,
checkpoint.subscription_name,
checkpoint.consumer_group_member,
checkpoint.last_seen,
checkpoint.updated_at
FROM streams AS store_head
LEFT JOIN subscriptions AS checkpoint ON TRUE
WHERE store_head.stream_id = 0
ORDER BY checkpoint.subscription_name ASC,
checkpoint.consumer_group_member ASC
"""
E.noParams
(D.rowVector inventoryRow)
inventoryRow :: D.Row InventoryRow
inventoryRow =
InventoryRow
<$> D.column (D.nonNullable D.int8)
<*> D.column (D.nullable D.text)
<*> D.column (D.nullable D.int4)
<*> D.column (D.nullable D.int8)
<*> D.column (D.nullable D.timestamptz)
finalizeInventory :: Vector InventoryRow -> Either Text SubscriptionCheckpointInventory
finalizeInventory rows = case V.uncons rows of
Nothing -> Left "subscription checkpoint inventory: missing $all stream row"
Just (firstRow, remainingRows) ->
let capturedPosition = rowStorePosition firstRow
in case checkpointColumns firstRow of
EmptyCheckpoint
| V.null remainingRows ->
Right $
SubscriptionCheckpointInventory
(GlobalPosition capturedPosition)
V.empty
| otherwise ->
Left "subscription checkpoint inventory: empty checkpoint row was not the only result"
PartialCheckpoint ->
Left "subscription checkpoint inventory: partially null checkpoint row"
CompleteCheckpoint ->
SubscriptionCheckpointInventory (GlobalPosition capturedPosition)
<$> V.mapM (decodeCheckpoint capturedPosition) rows
data CheckpointColumns
= EmptyCheckpoint
| PartialCheckpoint
| CompleteCheckpoint
checkpointColumns :: InventoryRow -> CheckpointColumns
checkpointColumns row =
case ( rowSubscriptionName row
, rowConsumerGroupMember row
, rowCheckpointPosition row
, rowCheckpointUpdatedAt row
) of
(Nothing, Nothing, Nothing, Nothing) -> EmptyCheckpoint
(Just _, Just _, Just _, Just _) -> CompleteCheckpoint
_ -> PartialCheckpoint
decodeCheckpoint :: Int64 -> InventoryRow -> Either Text SubscriptionCheckpoint
decodeCheckpoint capturedPosition row
| rowStorePosition row /= capturedPosition =
Left "subscription checkpoint inventory: inconsistent repeated store position"
| otherwise =
case ( rowSubscriptionName row
, rowConsumerGroupMember row
, rowCheckpointPosition row
, rowCheckpointUpdatedAt row
) of
(Just name, Just member, Just position, Just updatedAt) ->
Right $
SubscriptionCheckpoint
(SubscriptionName name)
member
(GlobalPosition position)
updatedAt
(Nothing, Nothing, Nothing, Nothing) ->
Left "subscription checkpoint inventory: unexpected empty checkpoint row"
_ -> Left "subscription checkpoint inventory: partially null checkpoint row"