bolty-0.2.0.0: src/Database/Bolty/Stats.hs
-- | Query statistics (nodes created, relationships deleted, etc.).
module Database.Bolty.Stats
( QueryStats(..)
, parseStats
, formatStatsLine
) where
import Prelude
import Data.Int (Int64)
import Data.Kind (Type)
import qualified Data.HashMap.Lazy as H
import qualified Data.Text as T
import Data.PackStream.Ps (Ps(..))
import Data.PackStream.Integer (fromPSInteger)
-- | Typed query statistics from the PULL SUCCESS metadata.
-- All integer fields default to 0 and booleans to False when absent.
type QueryStats :: Type
data QueryStats = QueryStats
{ nodesCreated :: !Int64
, nodesDeleted :: !Int64
, relationshipsCreated :: !Int64
, relationshipsDeleted :: !Int64
, propertiesSet :: !Int64
, labelsAdded :: !Int64
, labelsRemoved :: !Int64
, indexesAdded :: !Int64
, indexesRemoved :: !Int64
, constraintsAdded :: !Int64
, constraintsRemoved :: !Int64
, containsUpdates :: !Bool
, containsSystemUpdates :: !Bool
, systemUpdates :: !Int64
}
deriving stock (Show, Eq)
-- | Parse the raw @stats@ field from PULL SUCCESS metadata.
-- Returns 'Nothing' if the field is absent (read-only queries).
-- The server uses hyphenated keys (e.g. @"nodes-created"@).
parseStats :: Maybe Ps -> Maybe QueryStats
parseStats Nothing = Nothing
parseStats (Just (PsDictionary m)) = Just QueryStats
{ nodesCreated = intField "nodes-created" m
, nodesDeleted = intField "nodes-deleted" m
, relationshipsCreated = intField "relationships-created" m
, relationshipsDeleted = intField "relationships-deleted" m
, propertiesSet = intField "properties-set" m
, labelsAdded = intField "labels-added" m
, labelsRemoved = intField "labels-removed" m
, indexesAdded = intField "indexes-added" m
, indexesRemoved = intField "indexes-removed" m
, constraintsAdded = intField "constraints-added" m
, constraintsRemoved = intField "constraints-removed" m
, containsUpdates = boolField "contains-updates" m
, containsSystemUpdates = boolField "contains-system-updates" m
, systemUpdates = intField "system-updates" m
}
parseStats (Just _) = Nothing
intField :: T.Text -> H.HashMap T.Text Ps -> Int64
intField key m = case H.lookup key m of
Just (PsInteger n) -> case fromPSInteger n of
Just i -> i
Nothing -> 0
_ -> 0
boolField :: T.Text -> H.HashMap T.Text Ps -> Bool
boolField key m = case H.lookup key m of
Just (PsBoolean b) -> b
_ -> False
-- | One-line summary of every counter on a 'QueryStats' record. Format:
--
-- > Stats: +nodes=1 -nodes=0 +rels=0 -rels=0 props=1 +labels=1 -labels=0 +idx=0 -idx=0 +constr=0 -constr=0
--
-- Useful for diagnostic logging and CLI rollback messages where you want
-- the whole counter set on one line for terse comparison across runs.
formatStatsLine :: QueryStats -> T.Text
formatStatsLine s = T.concat
[ "Stats: "
, "+nodes=", T.pack (show (nodesCreated s))
, " -nodes=", T.pack (show (nodesDeleted s))
, " +rels=", T.pack (show (relationshipsCreated s))
, " -rels=", T.pack (show (relationshipsDeleted s))
, " props=", T.pack (show (propertiesSet s))
, " +labels=", T.pack (show (labelsAdded s))
, " -labels=", T.pack (show (labelsRemoved s))
, " +idx=", T.pack (show (indexesAdded s))
, " -idx=", T.pack (show (indexesRemoved s))
, " +constr=", T.pack (show (constraintsAdded s))
, " -constr=", T.pack (show (constraintsRemoved s))
]