packages feed

lsm-tree-1.0.0.0: src-extras/Database/LSMTree/Extras/ReferenceImpl.hs

module Database.LSMTree.Extras.ReferenceImpl (
    -- * Page types
    Key (..),
    Value (..),
    Operation (..),
    BlobRef (..),
    PageSerialised,
    PageIntermediate,

    -- * Page size
    PageSize (..),
    pageSizeEmpty,
    pageSizeAddElem,

    -- * Encoding and decoding
    DiskPageSize(..),
    encodePage,
    decodePage,
    serialisePage,
    deserialisePage,

    -- * Overflow pages
    pageOverflowPrefixSuffixLen,
    pageDiskPages,
    pageSerialisedChunks,

    -- * Conversions to real implementation types
    toRawPage,
    toRawPageMaybeOverfull,
    toEntry,
    toEntryPrefix,
    toSerialisedKey,
    toSerialisedValue,
    toBlobSpan,

    -- * Conversions from real implementation types
    fromRawPage,
    fromEntry,
    fromEntryPrefix,
    fromSerialisedKey,
    fromSerialisedValue,
    fromSerialisedValuePrefix,
    fromRawOverflowPages,
    fromBlobSpan,

    -- * Test case types and generators
    PageContentFits(..),
    pageContentFitsInvariant,
    PageContentOrdered(..),
    pageContentOrderedInvariant,
    PageContentMaybeOverfull(..),
    PageContentSingle(..),

    -- ** Generators and shrinkers
    genPageContentFits,
    genPageContentMaybeOverfull,
    genPageContentSingle,
    genPageContentNearFull,
    genPageContentMedium,
    MinKeySize(..),
    noMinKeySize,
    orderdKeyOps,
    shrinkKeyOps,
    shrinkOrderedKeyOps,
) where

import qualified Data.ByteString as BS
import qualified Data.ByteString.Short as SBS
import           Data.Maybe (fromMaybe, isJust)
import           Data.Primitive.ByteArray (ByteArray (..))

import           Database.LSMTree.Internal.BlobRef (BlobSpan (..))
import           Database.LSMTree.Internal.Entry (Entry)
import qualified Database.LSMTree.Internal.Entry as Entry
import qualified Database.LSMTree.Internal.RawBytes as RB
import           Database.LSMTree.Internal.RawOverflowPage
import           Database.LSMTree.Internal.RawPage
import           Database.LSMTree.Internal.Serialise
import           FormatPage hiding (PageContentFits, PageContentMaybeOverfull,
                     PageContentSingle)

import           Test.QuickCheck

-- | A test case consisting of a key\/operation sequence that is guaranteed to
-- either fit into a single 4k disk page, or be a single entry that spans a
-- one primary 4k disk page and zero or more overflow disk pages.
--
-- The keys /are not/ ordered.
--
newtype PageContentFits = PageContentFits [(Key, Operation)]
  deriving stock (Eq, Show)

-- | A test case consisting of a key\/operation sequence that is guaranteed to
-- either fit into a single 4k disk page, or be a single entry that spans a
-- one primary 4k disk page and zero or more overflow disk pages.
--
-- The keys /are/ in strictly ascending order.
--
newtype PageContentOrdered = PageContentOrdered [(Key, Operation)]
  deriving stock (Eq, Show)

-- | A test case consisting of a key\/operation sequence that is /not/
-- guaranteed to fit into a a 4k disk page.
--
-- These test cases are useful for checking the boundary conditions for what
-- can fit into a disk page.
--
-- The keys /are not/ ordered.
--
newtype PageContentMaybeOverfull = PageContentMaybeOverfull [(Key, Operation)]
  deriving stock (Eq, Show)

-- | A tests case consisting of a single key\/operation pair.
--
data PageContentSingle = PageContentSingle Key Operation
  deriving stock (Eq, Show)

instance Arbitrary PageContentFits where
    arbitrary =
      PageContentFits <$>
        genPageContentFits DiskPage4k noMinKeySize

    shrink (PageContentFits kops) =
      map PageContentFits (shrinkKeyOps kops)

pageContentFitsInvariant :: PageContentFits -> Bool
pageContentFitsInvariant (PageContentFits kops) =
    isJust (calcPageSize DiskPage4k kops)

instance Arbitrary PageContentOrdered where
    arbitrary =
      PageContentOrdered . orderdKeyOps <$>
        genPageContentFits DiskPage4k noMinKeySize

    shrink (PageContentOrdered kops) =
      map PageContentOrdered (shrinkOrderedKeyOps kops)

-- | Strictly increasing, so no duplicates.
pageContentOrderedInvariant :: PageContentOrdered -> Bool
pageContentOrderedInvariant (PageContentOrdered kops) =
    pageContentFitsInvariant (PageContentFits kops)
 && let ks = map fst kops
     in and (zipWith (<) ks (drop 1 ks))

instance Arbitrary PageContentMaybeOverfull where
    arbitrary =
      PageContentMaybeOverfull <$>
        genPageContentMaybeOverfull DiskPage4k noMinKeySize

    shrink (PageContentMaybeOverfull kops) =
      map PageContentMaybeOverfull (shrinkKeyOps kops)

instance Arbitrary PageContentSingle where
    arbitrary =
      uncurry PageContentSingle <$>
        genPageContentSingle DiskPage4k noMinKeySize

    shrink (PageContentSingle k op) =
      map (uncurry PageContentSingle) (shrink (k, op))

-------------------------------------------------------------------------------
-- Conversions between reference and real implementation types
--

-- | Convert from 'PageContentFits' (from the reference implementation)
-- to 'RawPage' (from the real implementation).
--
toRawPage :: PageContentFits -> (RawPage, [RawOverflowPage])
toRawPage (PageContentFits kops) =
    fromMaybe overfull (toRawPageMaybeOverfull (PageContentMaybeOverfull kops))
  where
    overfull =
      error $ "toRawPage: encountered overfull page, but PageContentFits is "
           ++ "supposed to be guaranteed to fit, i.e. not to be overfull."

toRawPageMaybeOverfull :: PageContentMaybeOverfull
                       -> Maybe (RawPage, [RawOverflowPage])
toRawPageMaybeOverfull (PageContentMaybeOverfull kops) = do
    serialised <- serialisePage <$> encodePage DiskPage4k kops
    (page : overflowPages) <- pure (pageSerialisedChunks DiskPage4k serialised)
    pure (makeRawPageBS page, map makeRawOverflowPageBS overflowPages)

makeRawPageBS :: BS.ByteString -> RawPage
makeRawPageBS bs =
    case SBS.toShort bs of
      SBS.SBS ba -> makeRawPage (ByteArray ba) 0

makeRawOverflowPageBS :: BS.ByteString -> RawOverflowPage
makeRawOverflowPageBS bs =
    case SBS.toShort bs of
      SBS.SBS ba -> makeRawOverflowPage (ByteArray ba) 0 (BS.length bs)

fromRawPage :: (RawPage, [RawOverflowPage]) -> PageContentFits
fromRawPage (page, overflowPages)
  | rawPageNumKeys page == 1
  = PageContentFits . (:[]) $
      case rawPageIndex page 0 of
        IndexEntry k e ->
          (fromSerialisedKey k, fromEntry e)

        IndexEntryOverflow k e suffixlen ->
          ( fromSerialisedKey k
          , fromEntryPrefix e (fromIntegral suffixlen) overflowPages
          )

        IndexNotPresent -> error "fromRawPage: 'rawPageIndex page 0' fails"

  | otherwise
  = PageContentFits
      [ case rawPageIndex page (fromIntegral i) of
          IndexEntry k e -> (fromSerialisedKey k, fromEntry e)
          _              -> error "fromRawPage: 'rawPageIndex page i' fails"
      | i <- [0 .. fromIntegral (rawPageNumKeys page) - 1 :: Int] ]

toEntry :: Operation -> Entry SerialisedValue BlobSpan
toEntry op =
    case op of
      Insert v Nothing ->
        Entry.Insert (toSerialisedValue v)

      Insert v (Just b) ->
        Entry.InsertWithBlob (toSerialisedValue v) (toBlobSpan b)

      Mupsert v ->
        Entry.Upsert (toSerialisedValue v)

      Delete ->
        Entry.Delete

toEntryPrefix :: Operation -> Int -> Entry SerialisedValue BlobSpan
toEntryPrefix op prefixlen =
    case op of
      Insert v Nothing ->
        Entry.Insert (toSerialisedValue (takeValue prefixlen v))

      Insert v (Just b) ->
        Entry.InsertWithBlob (toSerialisedValue (takeValue prefixlen v))
                             (toBlobSpan b)

      Mupsert v ->
        Entry.Upsert (toSerialisedValue (takeValue prefixlen v))

      Delete ->
        Entry.Delete
  where
    takeValue n (Value v) = Value (BS.take n v)

fromEntry :: Entry SerialisedValue BlobSpan -> Operation
fromEntry e =
    case e of
      Entry.Insert v ->
        Insert (fromSerialisedValue v) Nothing

      Entry.InsertWithBlob v b ->
        Insert (fromSerialisedValue v) (Just (fromBlobSpan b))

      Entry.Upsert v ->
        Mupsert (fromSerialisedValue v)

      Entry.Delete ->
        Delete

fromEntryPrefix :: Entry SerialisedValue BlobSpan
                -> Int
                -> [RawOverflowPage]
                -> Operation
fromEntryPrefix e suffix overflow =
    case e of
      Entry.Insert v ->
        Insert (fromSerialisedValuePrefix v suffix overflow) Nothing

      Entry.InsertWithBlob v b ->
        Insert (fromSerialisedValuePrefix v suffix overflow)
               (Just (fromBlobSpan b))

      Entry.Upsert v ->
        Mupsert (fromSerialisedValuePrefix v suffix overflow)

      Entry.Delete ->
        Delete

toSerialisedKey :: Key -> SerialisedKey
toSerialisedKey (Key k) = SerialisedKey (RB.fromByteString k)

toSerialisedValue :: Value -> SerialisedValue
toSerialisedValue (Value v) = SerialisedValue (RB.fromByteString v)

fromSerialisedKey :: SerialisedKey -> Key
fromSerialisedKey (SerialisedKey k) = Key (RB.toByteString k)

fromSerialisedValue :: SerialisedValue -> Value
fromSerialisedValue (SerialisedValue v) = Value (RB.toByteString v)

fromSerialisedValuePrefix :: SerialisedValue -> Int -> [RawOverflowPage]
                          -> Value
fromSerialisedValuePrefix (SerialisedValue v) suffixlen overflowPages =
    Value $ RB.toByteString v
         <> BS.take suffixlen (fromRawOverflowPages overflowPages)

fromRawOverflowPages :: [RawOverflowPage] -> BS.ByteString
fromRawOverflowPages =
    RB.toByteString
  . mconcat
  . map rawOverflowPageRawBytes

toBlobSpan :: BlobRef -> BlobSpan
toBlobSpan  (BlobRef x y) = BlobSpan x y

fromBlobSpan :: BlobSpan -> BlobRef
fromBlobSpan  (BlobSpan x y) = BlobRef x y