packages feed

lsm-tree-1.0.0.0: src-core/Database/LSMTree/Internal/Index.hs

{-# OPTIONS_HADDOCK not-home #-}

-- |
--  Provides support for working with fence pointer indexes of different types
--  and their accumulators.
--
--  Keys used with an index are subject to the key size constraints of the
--  concrete type of the index. These constraints are stated in the descriptions
--  of the modules "Database.LSMTree.Internal.Index.Compact" and
--  "Database.LSMTree.Internal.Index.Ordinary", respectively.
--
--  Part of the functionality that this module provides is the construction of
--  serialised indexes in a mostly incremental fashion. The incremental part of
--  serialisation is provided through index accumulators, while the
--  non-incremental bits are provided through the index operations 'headerLBS'
--  and 'finalLBS'. To completely serialise an index interleaved with its
--  construction, proceed as follows:
--
--   1. Use 'headerLBS' to generate the header of the serialised index.
--
--   2. Incrementally construct the index using the operations of 'IndexAcc',
--      and assemble the body of the serialised index from the generated chunks.
--
--   3. Use 'finalLBS' to generate the footer of the serialised index.
--
module Database.LSMTree.Internal.Index
(
    -- * Index types
    IndexType (Compact, Ordinary),
    indexToIndexType,

    -- * Indexes
    Index (CompactIndex, OrdinaryIndex),
    search,
    sizeInPages,
    headerLBS,
    finalLBS,
    fromSBS,

    -- * Index accumulators
    IndexAcc (CompactIndexAcc, OrdinaryIndexAcc),
    newWithDefaults,
    appendSingle,
    appendMulti,
    unsafeEnd
)
where

import           Control.Arrow (second)
import           Control.DeepSeq (NFData (rnf))
import           Control.Monad.ST.Strict (ST)
import           Data.ByteString.Lazy (LazyByteString)
import           Data.ByteString.Short (ShortByteString)
import           Data.Word (Word32)
import           Database.LSMTree.Internal.Chunk (Chunk)
import           Database.LSMTree.Internal.Entry (NumEntries)
import           Database.LSMTree.Internal.Index.Compact (IndexCompact)
import qualified Database.LSMTree.Internal.Index.Compact as Compact (finalLBS,
                     fromSBS, headerLBS, search, sizeInPages)
import           Database.LSMTree.Internal.Index.CompactAcc (IndexCompactAcc)
import qualified Database.LSMTree.Internal.Index.CompactAcc as Compact
                     (appendMulti, appendSingle, newWithDefaults, unsafeEnd)
import           Database.LSMTree.Internal.Index.Ordinary (IndexOrdinary)
import qualified Database.LSMTree.Internal.Index.Ordinary as Ordinary (finalLBS,
                     fromSBS, headerLBS, search, sizeInPages)
import           Database.LSMTree.Internal.Index.OrdinaryAcc (IndexOrdinaryAcc)
import qualified Database.LSMTree.Internal.Index.OrdinaryAcc as Ordinary
                     (appendMulti, appendSingle, newWithDefaults, unsafeEnd)
import           Database.LSMTree.Internal.Page (NumPages, PageSpan)
import           Database.LSMTree.Internal.Serialise (SerialisedKey)

-- * Index types

-- | The type of supported index types.
data IndexType = Compact | Ordinary
    deriving stock (Eq, Show)

instance NFData IndexType where
    rnf Compact  = ()
    rnf Ordinary = ()

-- * Indexes

-- | The type of supported indexes.
data Index
    = CompactIndex  !IndexCompact
    | OrdinaryIndex !IndexOrdinary
    deriving stock (Eq, Show)

instance NFData Index where
    rnf (CompactIndex  index) = rnf index
    rnf (OrdinaryIndex index) = rnf index

indexToIndexType :: Index -> IndexType
indexToIndexType CompactIndex{}  = Compact
indexToIndexType OrdinaryIndex{} = Ordinary

{-|
    Searches for a page span that contains a key–value pair with the given key.
    If there is indeed such a pair, the result is the corresponding page span;
    if there is no such pair, the result is an arbitrary but valid page span.
-}
search :: SerialisedKey -> Index -> PageSpan
search key (CompactIndex  index) = Compact.search  key index
search key (OrdinaryIndex index) = Ordinary.search key index

-- | Yields the number of pages covered by an index.
sizeInPages :: Index -> NumPages
sizeInPages (CompactIndex  index) = Compact.sizeInPages  index
sizeInPages (OrdinaryIndex index) = Ordinary.sizeInPages index

{-|
    Yields the header of the serialised form of an index.

    See [the module documentation]("Database.LSMTree.Internal.Index") for how to
    generate a complete serialised index.
-}
headerLBS :: IndexType -> LazyByteString
headerLBS Compact  = Compact.headerLBS
headerLBS Ordinary = Ordinary.headerLBS

{-|
    Yields the footer of the serialised form of an index.

    See [the module documentation]("Database.LSMTree.Internal.Index") for how to
    generate a complete serialised index.
-}
finalLBS :: NumEntries -> Index -> LazyByteString
finalLBS entryCount (CompactIndex  index) = Compact.finalLBS  entryCount index
finalLBS entryCount (OrdinaryIndex index) = Ordinary.finalLBS entryCount index

{-|
    Reads an index along with the number of entries of the respective run from a
    byte string.

    The byte string must contain the serialised index exactly, with no leading
    or trailing space. Furthermore, its contents must be stored 64-bit-aligned.

    The contents of the byte string may be directly used as the backing memory
    for the constructed index. Currently, this is done for compact indexes.

    For deserialising numbers, the endianness of the host system is used. If
    serialisation has been done with a different endianness, this mismatch is
    detected by looking at the type–version indicator.
-}
fromSBS :: IndexType -> ShortByteString -> Either String (NumEntries, Index)
fromSBS Compact input  = second CompactIndex  <$> Compact.fromSBS  input
fromSBS Ordinary input = second OrdinaryIndex <$> Ordinary.fromSBS input

-- * Index accumulators

{-|
    The type of supported index accumulators, where an index accumulator denotes
    an index under incremental construction.

    Incremental index construction is only guaranteed to work correctly when the
    supplied key ranges do not overlap and are given in ascending order.
-}
data IndexAcc s = CompactIndexAcc  !(IndexCompactAcc  s)
                | OrdinaryIndexAcc !(IndexOrdinaryAcc s)

-- | Create a new index accumulator, using a default configuration.
newWithDefaults :: IndexType -> ST s (IndexAcc s)
newWithDefaults Compact  = CompactIndexAcc  <$> Compact.newWithDefaults
newWithDefaults Ordinary = OrdinaryIndexAcc <$> Ordinary.newWithDefaults

{-|
    Adds information about a single page that fully comprises one or more
    key–value pairs to an index and outputs newly available chunks.

    See the documentation of the 'IndexAcc' type for constraints to adhere to.
-}
appendSingle :: (SerialisedKey, SerialisedKey)
             -> IndexAcc s
             -> ST s (Maybe Chunk)
appendSingle pageInfo (CompactIndexAcc  indexAcc) = Compact.appendSingle
                                                        pageInfo
                                                        indexAcc
appendSingle pageInfo (OrdinaryIndexAcc indexAcc) = Ordinary.appendSingle
                                                        pageInfo
                                                        indexAcc

{-|
    Adds information about multiple pages that together comprise a single
    key–value pair to an index and outputs newly available chunks.

    The provided 'Word32' value denotes the number of /overflow/ pages, so that
    the number of pages that comprise the key–value pair is the successor of
    that number.

    See the documentation of the 'IndexAcc' type for constraints to adhere to.
-}
appendMulti :: (SerialisedKey, Word32) -> IndexAcc s -> ST s [Chunk]
appendMulti pagesInfo (CompactIndexAcc  indexAcc) = Compact.appendMulti
                                                        pagesInfo
                                                        indexAcc
appendMulti pagesInfo (OrdinaryIndexAcc indexAcc) = Ordinary.appendMulti
                                                        pagesInfo
                                                        indexAcc

{-|
    Returns the constructed index, along with a final chunk in case the
    serialised key list has not been fully output yet, thereby invalidating the
    index under construction. Executing @unsafeEnd index@ is only safe when
    @index@ is not used afterwards.
-}
unsafeEnd :: IndexAcc s -> ST s (Maybe Chunk, Index)
unsafeEnd (CompactIndexAcc  indexAcc) = second CompactIndex         <$>
                                        Compact.unsafeEnd  indexAcc
unsafeEnd (OrdinaryIndexAcc indexAcc) = second OrdinaryIndex        <$>
                                        Ordinary.unsafeEnd indexAcc