lsm-tree-1.0.0.0: src-core/Database/LSMTree/Internal/Snapshot/Codec.hs
{-# OPTIONS_HADDOCK not-home #-}
-- | Encoders and decoders for snapshot metadata
--
module Database.LSMTree.Internal.Snapshot.Codec (
-- * Versioning
SnapshotVersion (..)
, prettySnapshotVersion
, currentSnapshotVersion
, allCompatibleSnapshotVersions
-- * Writing and reading files
, writeFileSnapshotMetaData
, readFileSnapshotMetaData
, encodeSnapshotMetaData
-- * Encoding and decoding
, Encode (..)
, Decode (..)
, DecodeVersioned (..)
, Versioned (..)
) where
import Codec.CBOR.Decoding
import Codec.CBOR.Encoding
import Codec.CBOR.Read
import Codec.CBOR.Write
import Control.Monad.Class.MonadThrow (Exception (displayException),
MonadThrow (..))
import Data.Bifunctor (Bifunctor (..))
import qualified Data.ByteString.Char8 as BSC
import Data.ByteString.Lazy (ByteString)
import qualified Data.Map.Strict as Map
import qualified Data.Vector as V
import Database.LSMTree.Internal.Config
import Database.LSMTree.Internal.CRC32C
import qualified Database.LSMTree.Internal.CRC32C as FS
import Database.LSMTree.Internal.MergeSchedule
import qualified Database.LSMTree.Internal.MergingRun as MR
import Database.LSMTree.Internal.RunBuilder (IndexType (..),
RunBloomFilterAlloc (..), RunDataCaching (..),
RunParams (..))
import Database.LSMTree.Internal.RunNumber
import Database.LSMTree.Internal.Snapshot
import qualified System.FS.API as FS
import System.FS.API (FsPath, HasFS)
import Text.Printf
{-------------------------------------------------------------------------------
Versioning
-------------------------------------------------------------------------------}
-- | The version of a snapshot.
--
-- A snapshot format version is a number. Version numbers are consecutive and
-- increasing. A single release of the library may support several older
-- snapshot format versions, and thereby provide backwards compatibility.
-- Support for old versions is not guaranteed indefinitely, but backwards
-- compatibility is guaranteed for at least the previous version, and preferably
-- for more. Forwards compatibility is not provided at all: snapshots with a
-- later version than the current version for the library release will always
-- fail.
data SnapshotVersion = V0 | V1
deriving stock (Show, Eq, Ord)
-- | Pretty-print a snapshot version
--
-- >>> prettySnapshotVersion currentSnapshotVersion
-- "v1"
prettySnapshotVersion :: SnapshotVersion -> String
prettySnapshotVersion V0 = "v0"
prettySnapshotVersion V1 = "v1"
-- | The current snapshot version
--
-- >>> currentSnapshotVersion
-- V1
currentSnapshotVersion :: SnapshotVersion
currentSnapshotVersion = V1
-- | All snapshot versions that the current snapshpt version is compatible with.
--
-- >>> allCompatibleSnapshotVersions
-- [V0,V1]
--
-- >>> last allCompatibleSnapshotVersions == currentSnapshotVersion
-- True
allCompatibleSnapshotVersions :: [SnapshotVersion]
allCompatibleSnapshotVersions = [V0, V1]
isCompatible :: SnapshotVersion -> Either String ()
isCompatible otherVersion
-- for the moment, all versions are backwards compatible:
| otherVersion `elem` allCompatibleSnapshotVersions
= Right ()
| otherwise = Left "forward compatibility not supported"
{-------------------------------------------------------------------------------
Writing and reading files
-------------------------------------------------------------------------------}
{-# SPECIALISE
writeFileSnapshotMetaData ::
HasFS IO h
-> FsPath
-> FsPath
-> SnapshotMetaData
-> IO ()
#-}
-- | Encode 'SnapshotMetaData' and write it to 'SnapshotMetaDataFile'.
--
-- In the presence of exceptions, newly created files will not be removed. It is
-- up to the user of this function to clean up the files.
writeFileSnapshotMetaData ::
MonadThrow m
=> HasFS m h
-> FsPath -- ^ Target file for snapshot metadata
-> FsPath -- ^ Target file for checksum
-> SnapshotMetaData
-> m ()
writeFileSnapshotMetaData hfs contentPath checksumPath snapMetaData = do
(_, checksum) <-
FS.withFile hfs contentPath (FS.WriteMode FS.MustBeNew) $ \h ->
hPutAllChunksCRC32C hfs h (encodeSnapshotMetaData snapMetaData) initialCRC32C
let checksumFileName = ChecksumsFileName (BSC.pack "metadata")
checksumFile = Map.singleton checksumFileName checksum
writeChecksumsFile hfs checksumPath checksumFile
encodeSnapshotMetaData :: SnapshotMetaData -> ByteString
encodeSnapshotMetaData = toLazyByteString . encode . Versioned
{-# SPECIALISE
readFileSnapshotMetaData ::
HasFS IO h
-> FsPath
-> FsPath
-> IO SnapshotMetaData
#-}
-- | Read from 'SnapshotMetaDataFile' and attempt to decode it to
-- 'SnapshotMetaData'.
readFileSnapshotMetaData ::
(MonadThrow m)
=> HasFS m h
-> FsPath -- ^ Source file for snapshot metadata
-> FsPath -- ^ Source file for checksum
-> m SnapshotMetaData
readFileSnapshotMetaData hfs contentPath checksumPath = do
checksumFile <- readChecksumsFile hfs checksumPath
let checksumFileName = ChecksumsFileName (BSC.pack "metadata")
expectedChecksum <- getChecksum hfs checksumPath checksumFile checksumFileName
(lbs, actualChecksum) <- FS.withFile hfs contentPath FS.ReadMode $ \h -> do
n <- FS.hGetSize hfs h
FS.hGetExactlyCRC32C hfs h n initialCRC32C
expectChecksum hfs contentPath expectedChecksum actualChecksum
expectValidFile hfs contentPath FormatSnapshotMetaData (decodeSnapshotMetaData lbs)
decodeSnapshotMetaData :: ByteString -> Either String SnapshotMetaData
decodeSnapshotMetaData lbs = bimap displayException (getVersioned . snd) (deserialiseFromBytes decode lbs)
{-------------------------------------------------------------------------------
Encoding and decoding
-------------------------------------------------------------------------------}
class Encode a where
encode :: a -> Encoding
-- | Decoder that is not parameterised by a 'SnapshotVersion'.
--
-- Used only for 'SnapshotVersion' and 'Versioned', which live outside the
-- 'SnapshotMetaData' type hierarchy.
class Decode a where
decode :: Decoder s a
-- | Decoder parameterised by a 'SnapshotVersion'.
--
-- Used for every type in the 'SnapshotMetaData' type hierarchy.
class DecodeVersioned a where
decodeVersioned :: SnapshotVersion -> Decoder s a
newtype Versioned a = Versioned { getVersioned :: a }
deriving stock (Show, Eq)
instance Encode a => Encode (Versioned a) where
encode (Versioned x) =
encodeListLen 2
<> encode currentSnapshotVersion
<> encode x
-- | Decodes a 'SnapshotVersion' first, and then passes that into the versioned
-- decoder for @a@.
instance DecodeVersioned a => Decode (Versioned a) where
decode = do
_ <- decodeListLenOf 2
version <- decode
case isCompatible version of
Right () -> pure ()
Left errMsg ->
fail $
printf "Incompatible snapshot format version found. Version %s \
\is not backwards compatible with version %s : %s"
(prettySnapshotVersion currentSnapshotVersion)
(prettySnapshotVersion version)
errMsg
Versioned <$> decodeVersioned version
{-------------------------------------------------------------------------------
Encoding and decoding: Versioning
-------------------------------------------------------------------------------}
instance Encode SnapshotVersion where
encode ver =
encodeListLen 1
<> case ver of
V0 -> encodeWord 0
V1 -> encodeWord 1
instance Decode SnapshotVersion where
decode = do
_ <- decodeListLenOf 1
ver <- decodeWord
case ver of
0 -> pure V0
1 -> pure V1
_ -> fail ("Unknown snapshot format version number: " <> show ver)
{-------------------------------------------------------------------------------
Encoding and decoding: SnapshotMetaData
-------------------------------------------------------------------------------}
-- SnapshotMetaData
instance Encode SnapshotMetaData where
encode (SnapshotMetaData label config writeBuffer levels mergingTree) =
encodeListLen 5
<> encode label
<> encode config
<> encode writeBuffer
<> encode levels
<> encodeMaybe mergingTree
instance DecodeVersioned SnapshotMetaData where
decodeVersioned ver = do
_ <- decodeListLenOf 5
SnapshotMetaData
<$> decodeVersioned ver
<*> decodeVersioned ver
<*> decodeVersioned ver
<*> decodeVersioned ver
<*> decodeMaybe ver
-- SnapshotLabel
instance Encode SnapshotLabel where
encode (SnapshotLabel s) = encodeString s
instance DecodeVersioned SnapshotLabel where
decodeVersioned _v = SnapshotLabel <$> decodeString
instance Encode SnapshotRun where
encode SnapshotRun { snapRunNumber, snapRunCaching, snapRunIndex } =
encodeListLen 4
<> encodeWord 0
<> encode snapRunNumber
<> encode snapRunCaching
<> encode snapRunIndex
instance DecodeVersioned SnapshotRun where
decodeVersioned v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(4, 0) -> do snapRunNumber <- decodeVersioned v
snapRunCaching <- decodeVersioned v
snapRunIndex <- decodeVersioned v
pure SnapshotRun{..}
_ -> fail ("[SnapshotRun] Unexpected combination of list length and tag: " <> show (n, tag))
{-------------------------------------------------------------------------------
Encoding and decoding: TableConfig
-------------------------------------------------------------------------------}
-- TableConfig
instance Encode TableConfig where
encode
( TableConfig
{ confMergePolicy = mergePolicy
, confMergeSchedule = mergeSchedule
, confSizeRatio = sizeRatio
, confWriteBufferAlloc = writeBufferAlloc
, confBloomFilterAlloc = bloomFilterAlloc
, confFencePointerIndex = fencePointerIndex
, confDiskCachePolicy = diskCachePolicy
, confMergeBatchSize = mergeBatchSize
}
) =
encodeListLen 8
<> encode mergePolicy
<> encode mergeSchedule
<> encode sizeRatio
<> encode writeBufferAlloc
<> encode bloomFilterAlloc
<> encode fencePointerIndex
<> encode diskCachePolicy
<> encode mergeBatchSize
instance DecodeVersioned TableConfig where
decodeVersioned v@V0 = do
decodeListLenOf 7
confMergePolicy <- decodeVersioned v
confMergeSchedule <- decodeVersioned v
confSizeRatio <- decodeVersioned v
confWriteBufferAlloc <- decodeVersioned v
confBloomFilterAlloc <- decodeVersioned v
confFencePointerIndex <- decodeVersioned v
confDiskCachePolicy <- decodeVersioned v
let confMergeBatchSize = case confWriteBufferAlloc of
AllocNumEntries n -> MergeBatchSize n
pure TableConfig {..}
-- We introduced the confMergeBatchSize in V1
decodeVersioned v@V1 = do
decodeListLenOf 8
confMergePolicy <- decodeVersioned v
confMergeSchedule <- decodeVersioned v
confSizeRatio <- decodeVersioned v
confWriteBufferAlloc <- decodeVersioned v
confBloomFilterAlloc <- decodeVersioned v
confFencePointerIndex <- decodeVersioned v
confDiskCachePolicy <- decodeVersioned v
confMergeBatchSize <- decodeVersioned v
pure TableConfig {..}
-- MergePolicy
instance Encode MergePolicy where
encode LazyLevelling = encodeWord 0
instance DecodeVersioned MergePolicy where
decodeVersioned _v = do
tag <- decodeWord
case tag of
0 -> pure LazyLevelling
_ -> fail ("[MergePolicy] Unexpected tag: " <> show tag)
-- SizeRatio
instance Encode SizeRatio where
encode Four = encodeInt 4
instance DecodeVersioned SizeRatio where
decodeVersioned _v = do
x <- decodeWord64
case x of
4 -> pure Four
_ -> fail ("Expected 4, but found " <> show x)
-- WriteBufferAlloc
instance Encode WriteBufferAlloc where
encode (AllocNumEntries numEntries) =
encodeListLen 2
<> encodeWord 0
<> encodeInt numEntries
instance DecodeVersioned WriteBufferAlloc where
decodeVersioned _v = do
_ <- decodeListLenOf 2
tag <- decodeWord
case tag of
0 -> AllocNumEntries <$> decodeInt
_ -> fail ("[WriteBufferAlloc] Unexpected tag: " <> show tag)
-- RunParams and friends
instance Encode RunParams where
encode RunParams { runParamCaching, runParamAlloc, runParamIndex } =
encodeListLen 4
<> encodeWord 0
<> encode runParamCaching
<> encode runParamAlloc
<> encode runParamIndex
instance DecodeVersioned RunParams where
decodeVersioned v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(4, 0) -> do runParamCaching <- decodeVersioned v
runParamAlloc <- decodeVersioned v
runParamIndex <- decodeVersioned v
pure RunParams{..}
_ -> fail ("[RunParams] Unexpected combination of list length and tag: " <> show (n, tag))
instance Encode RunDataCaching where
encode CacheRunData = encodeWord 0
encode NoCacheRunData = encodeWord 1
instance DecodeVersioned RunDataCaching where
decodeVersioned _v = do
tag <- decodeWord
case tag of
0 -> pure CacheRunData
1 -> pure NoCacheRunData
_ -> fail ("[RunDataCaching] Unexpected tag: " <> show tag)
instance Encode IndexType where
encode Ordinary = encodeWord 0
encode Compact = encodeWord 1
instance DecodeVersioned IndexType where
decodeVersioned _v = do
tag <- decodeWord
case tag of
0 -> pure Ordinary
1 -> pure Compact
_ -> fail ("[IndexType] Unexpected tag: " <> show tag)
instance Encode RunBloomFilterAlloc where
encode (RunAllocFixed bits) =
encodeListLen 2
<> encodeWord 0
<> encodeDouble bits
encode (RunAllocRequestFPR fpr) =
encodeListLen 2
<> encodeWord 1
<> encodeDouble fpr
instance DecodeVersioned RunBloomFilterAlloc where
decodeVersioned _v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(2, 0) -> RunAllocFixed <$> decodeDouble
(2, 1) -> RunAllocRequestFPR <$> decodeDouble
_ -> fail ("[RunBloomFilterAlloc] Unexpected combination of list length and tag: " <> show (n, tag))
-- BloomFilterAlloc
instance Encode BloomFilterAlloc where
encode (AllocFixed x) =
encodeListLen 2
<> encodeWord 0
<> encodeDouble x
encode (AllocRequestFPR x) =
encodeListLen 2
<> encodeWord 1
<> encodeDouble x
instance DecodeVersioned BloomFilterAlloc where
decodeVersioned _v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(2, 0) -> AllocFixed <$> decodeDouble
(2, 1) -> AllocRequestFPR <$> decodeDouble
_ -> fail ("[BloomFilterAlloc] Unexpected combination of list length and tag: " <> show (n, tag))
-- FencePointerIndexType
instance Encode FencePointerIndexType where
encode CompactIndex = encodeWord 0
encode OrdinaryIndex = encodeWord 1
instance DecodeVersioned FencePointerIndexType where
decodeVersioned _v = do
tag <- decodeWord
case tag of
0 -> pure CompactIndex
1 -> pure OrdinaryIndex
_ -> fail ("[FencePointerIndexType] Unexpected tag: " <> show tag)
-- DiskCachePolicy
instance Encode DiskCachePolicy where
encode DiskCacheAll =
encodeListLen 1
<> encodeWord 0
encode (DiskCacheLevelOneTo x) =
encodeListLen 2
<> encodeWord 1
<> encodeInt x
encode DiskCacheNone =
encodeListLen 1
<> encodeWord 2
instance DecodeVersioned DiskCachePolicy where
decodeVersioned _v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(1, 0) -> pure DiskCacheAll
(2, 1) -> DiskCacheLevelOneTo <$> decodeInt
(1, 2) -> pure DiskCacheNone
_ -> fail ("[DiskCachePolicy] Unexpected combination of list length and tag: " <> show (n, tag))
-- MergeSchedule
instance Encode MergeSchedule where
encode OneShot = encodeWord 0
encode Incremental = encodeWord 1
instance DecodeVersioned MergeSchedule where
decodeVersioned _v = do
tag <- decodeWord
case tag of
0 -> pure OneShot
1 -> pure Incremental
_ -> fail ("[MergeSchedule] Unexpected tag: " <> show tag)
-- MergeBatchSize
instance Encode MergeBatchSize where
encode (MergeBatchSize n) = encodeInt n
instance DecodeVersioned MergeBatchSize where
decodeVersioned _v = MergeBatchSize <$> decodeInt
{-------------------------------------------------------------------------------
Encoding and decoding: SnapLevels
-------------------------------------------------------------------------------}
-- SnapLevels
instance Encode r => Encode (SnapLevels r) where
encode (SnapLevels levels) = encode levels
instance DecodeVersioned r => DecodeVersioned (SnapLevels r) where
decodeVersioned v = SnapLevels <$> decodeVersioned v
-- SnapLevel
instance Encode r => Encode (SnapLevel r) where
encode (SnapLevel incomingRuns residentRuns) =
encodeListLen 2
<> encode incomingRuns
<> encode residentRuns
instance DecodeVersioned r => DecodeVersioned (SnapLevel r) where
decodeVersioned v = do
_ <- decodeListLenOf 2
SnapLevel <$> decodeVersioned v <*> decodeVersioned v
-- Vector
instance Encode r => Encode (V.Vector r) where
encode = encodeVector
instance DecodeVersioned r => DecodeVersioned (V.Vector r) where
decodeVersioned = decodeVector
-- RunNumber
instance Encode RunNumber where
encode (RunNumber x) = encodeInt x
instance DecodeVersioned RunNumber where
decodeVersioned _v = RunNumber <$> decodeInt
-- SnapIncomingRun
instance Encode r => Encode (SnapIncomingRun r) where
encode (SnapIncomingMergingRun mpfl nd nc smrs) =
encodeListLen 5
<> encodeWord 0
<> encode mpfl
<> encode nd
<> encode nc
<> encode smrs
encode (SnapIncomingSingleRun x) =
encodeListLen 2
<> encodeWord 1
<> encode x
instance DecodeVersioned r => DecodeVersioned (SnapIncomingRun r) where
decodeVersioned v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(5, 0) -> SnapIncomingMergingRun
<$> decodeVersioned v <*> decodeVersioned v
<*> decodeVersioned v <*> decodeVersioned v
(2, 1) -> SnapIncomingSingleRun <$> decodeVersioned v
_ -> fail ("[SnapIncomingRun] Unexpected combination of list length and tag: " <> show (n, tag))
-- MergePolicyForLevel
instance Encode MergePolicyForLevel where
encode LevelTiering = encodeWord 0
encode LevelLevelling = encodeWord 1
instance DecodeVersioned MergePolicyForLevel where
decodeVersioned _v = do
tag <- decodeWord
case tag of
0 -> pure LevelTiering
1 -> pure LevelLevelling
_ -> fail ("[MergePolicyForLevel] Unexpected tag: " <> show tag)
-- SnapMergingRun
instance (Encode t, Encode r) => Encode (SnapMergingRun t r) where
encode (SnapCompletedMerge md r) =
encodeListLen 3
<> encodeWord 0
<> encode md
<> encode r
encode (SnapOngoingMerge rp mc rs mt) =
encodeListLen 5
<> encodeWord 1
<> encode rp
<> encode mc
<> encode rs
<> encode mt
instance (DecodeVersioned t, DecodeVersioned r) => DecodeVersioned (SnapMergingRun t r) where
decodeVersioned v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(3, 0) -> SnapCompletedMerge <$> decodeVersioned v
<*> decodeVersioned v
(5, 1) -> SnapOngoingMerge <$> decodeVersioned v <*> decodeVersioned v
<*> decodeVersioned v <*> decodeVersioned v
_ -> fail ("[SnapMergingRun] Unexpected combination of list length and tag: " <> show (n, tag))
-- NominalDebt, NominalCredits, MergeDebt and MergeCredits
instance Encode NominalDebt where
encode (NominalDebt x) = encodeInt x
instance DecodeVersioned NominalDebt where
decodeVersioned _v = NominalDebt <$> decodeInt
instance Encode NominalCredits where
encode (NominalCredits x) = encodeInt x
instance DecodeVersioned NominalCredits where
decodeVersioned _v = NominalCredits <$> decodeInt
instance Encode MergeDebt where
encode (MergeDebt (MergeCredits x)) = encodeInt x
instance DecodeVersioned MergeDebt where
decodeVersioned _v = (MergeDebt . MergeCredits) <$> decodeInt
instance Encode MergeCredits where
encode (MergeCredits x) = encodeInt x
instance DecodeVersioned MergeCredits where
decodeVersioned _v = MergeCredits <$> decodeInt
-- MergeType
instance Encode MR.LevelMergeType where
encode MR.MergeMidLevel = encodeWord 0
encode MR.MergeLastLevel = encodeWord 1
instance DecodeVersioned MR.LevelMergeType where
decodeVersioned _v = do
tag <- decodeWord
case tag of
0 -> pure MR.MergeMidLevel
1 -> pure MR.MergeLastLevel
_ -> fail ("[LevelMergeType] Unexpected tag: " <> show tag)
-- | We start the tags for these merge types at an offset. This way, if we
-- serialise @MR.MergeMidLevel :: MR.LevelMergeType@ as 0 and then accidentally
-- try deserialising it as a @MR.TreeMergeType@, that will fail.
--
-- However, 'MR.LevelMergeType' and 'MR.TreeMergeType' are only different
-- (overlapping) subsets of 'MR.MergeType'. In particular, 'MR.MergeLastLevel'
-- and 'MR.MergeLevel' are semantically the same. Encoding them as the same
-- number leaves the door open to relaxing the restrictions on which merge types
-- can occur where, e.g. decoding them as a general 'MR.MergeType', without
-- having to change the file format.
instance Encode MR.TreeMergeType where
encode MR.MergeLevel = encodeWord 1
encode MR.MergeUnion = encodeWord 2
instance DecodeVersioned MR.TreeMergeType where
decodeVersioned _v = do
tag <- decodeWord
case tag of
1 -> pure MR.MergeLevel
2 -> pure MR.MergeUnion
_ -> fail ("[TreeMergeType] Unexpected tag: " <> show tag)
{-------------------------------------------------------------------------------
Encoding and decoding: SnapMergingTree
-------------------------------------------------------------------------------}
-- SnapMergingTree
instance Encode r => Encode (SnapMergingTree r) where
encode (SnapMergingTree tState) = encode tState
instance DecodeVersioned r => DecodeVersioned (SnapMergingTree r) where
decodeVersioned ver = SnapMergingTree <$> decodeVersioned ver
-- SnapMergingTreeState
instance Encode r => Encode (SnapMergingTreeState r) where
encode (SnapCompletedTreeMerge x) =
encodeListLen 2
<> encodeWord 0
<> encode x
encode (SnapPendingTreeMerge x) =
encodeListLen 2
<> encodeWord 1
<> encode x
encode (SnapOngoingTreeMerge smrs) =
encodeListLen 2
<> encodeWord 2
<> encode smrs
instance DecodeVersioned r => DecodeVersioned (SnapMergingTreeState r) where
decodeVersioned v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(2, 0) -> SnapCompletedTreeMerge <$> decodeVersioned v
(2, 1) -> SnapPendingTreeMerge <$> decodeVersioned v
(2, 2) -> SnapOngoingTreeMerge <$> decodeVersioned v
_ -> fail ("[SnapMergingTreeState] Unexpected combination of list length and tag: " <> show (n, tag))
-- SnapPendingMerge
instance Encode r => Encode (SnapPendingMerge r) where
encode (SnapPendingLevelMerge pe mt) =
encodeListLen 3
<> encodeWord 0
<> encodeList pe
<> encodeMaybe mt
encode (SnapPendingUnionMerge mts) =
encodeListLen 2
<> encodeWord 1
<> encodeList mts
instance DecodeVersioned r => DecodeVersioned (SnapPendingMerge r) where
decodeVersioned v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(3, 0) -> SnapPendingLevelMerge <$> decodeList v <*> decodeMaybe v
(2, 1) -> SnapPendingUnionMerge <$> decodeList v
_ -> fail ("[SnapPendingMerge] Unexpected combination of list length and tag: " <> show (n, tag))
-- SnapPreExistingRun
instance Encode r => Encode (SnapPreExistingRun r) where
encode (SnapPreExistingRun x) =
encodeListLen 2
<> encodeWord 0
<> encode x
encode (SnapPreExistingMergingRun smrs) =
encodeListLen 2
<> encodeWord 1
<> encode smrs
instance DecodeVersioned r => DecodeVersioned (SnapPreExistingRun r) where
decodeVersioned v = do
n <- decodeListLen
tag <- decodeWord
case (n, tag) of
(2, 0) -> SnapPreExistingRun <$> decodeVersioned v
(2, 1) -> SnapPreExistingMergingRun <$> decodeVersioned v
_ -> fail ("[SnapPreExistingRun] Unexpected combination of list length and tag: " <> show (n, tag))
-- Utilities for encoding/decoding Maybe values
--Note: the Maybe encoding cannot be nested like (Maybe (Maybe a)), but it is
-- ok for fields of records.
encodeMaybe :: Encode a => Maybe a -> Encoding
encodeMaybe = \case
Nothing -> encodeNull
Just en -> encode en
decodeMaybe :: DecodeVersioned a => SnapshotVersion -> Decoder s (Maybe a)
decodeMaybe v = do
tok <- peekTokenType
case tok of
TypeNull -> Nothing <$ decodeNull
_ -> Just <$> decodeVersioned v
encodeList :: Encode a => [a] -> Encoding
encodeList xs =
encodeListLen (fromIntegral (length xs))
<> foldr (\x r -> encode x <> r) mempty xs
decodeList :: DecodeVersioned a => SnapshotVersion -> Decoder s [a]
decodeList v = do
n <- decodeListLen
decodeSequenceLenN (flip (:)) [] reverse n (decodeVersioned v)
encodeVector :: Encode a => V.Vector a -> Encoding
encodeVector xs =
encodeListLen (fromIntegral (V.length xs))
<> foldr (\x r -> encode x <> r) mempty xs
decodeVector :: DecodeVersioned a => SnapshotVersion -> Decoder s (V.Vector a)
decodeVector v = do
n <- decodeListLen
decodeSequenceLenN (flip (:)) [] (V.reverse . V.fromList)
n (decodeVersioned v)