packages feed

franz-0.4: src/Database/Franz/Contents.hs

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE RecordWildCards #-}
module Database.Franz.Contents
  ( Contents
  , Database.Franz.Contents.indexNames
  , Item(..)
  , toList
  , last
  , length
  , index
  , lookupIndex
  -- * Internal
  , getResponse
  , readContents
  ) where

import Data.Serialize hiding (getInt64le)
import qualified Data.ByteString.Char8 as B
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import Database.Franz.Internal
import Database.Franz.Protocol
import Database.Franz.Reader
import Data.Int
import Prelude hiding (length, last)

data Item = Item
  { seqNo :: !Int
  , indices :: !(U.Vector Int64)
  , payload :: !B.ByteString
  } deriving (Show, Eq)

data Contents = Contents
  { indexNames :: !(V.Vector IndexName)
  , payloads :: !B.ByteString
  , indicess :: !IndexVec
  , length :: !Int
  , payloadOffset :: !Int
  , seqnoOffset :: !Int
  }

toList :: Contents -> [Item]
toList contents = [unsafeIndex contents i | i <- [0..length contents - 1]]

-- A vector containing file offsets and extra indices
type IndexVec = V.Vector (Int, U.Vector Int64)

getIndexVec :: V.Vector IndexName -> Int -> Get IndexVec
getIndexVec names len = V.replicateM len
  $ (,) <$> getInt64le <*> U.convert `fmap` traverse (const getInt64le) names

getResponse :: Get Contents
getResponse = do
  PayloadHeader seqnoOffset s1 payloadOffset indexNames <- get
  let length = s1 - seqnoOffset
  if length <= 0
    then pure Contents{ payloads = B.empty, indicess = V.empty, ..}
    else do
      indicess <- getIndexVec indexNames length
      payloads <- getByteString $ fst (V.unsafeLast indicess) - payloadOffset
      pure Contents{..}

last :: Contents -> Maybe Item
last contents
  | i >= 0 = Just $ unsafeIndex contents i
  | otherwise = Nothing
  where
    i = length contents - 1

index :: Contents -> Int -> Maybe Item
index contents i
  | i >= length contents || i < 0 = Nothing
  | otherwise = Just $ unsafeIndex contents i

unsafeIndex :: Contents -> Int -> Item
unsafeIndex Contents{..} i = Item{..}
  where
    ofs0 = maybe payloadOffset fst $ indicess V.!? (i - 1)
    (ofs1, indices) = indicess V.! i
    seqNo = seqnoOffset + i + 1
    payload = B.take (ofs1 - payloadOffset) $ B.drop (ofs0 - payloadOffset) payloads

lookupIndex :: Contents -> IndexName -> Maybe (Item -> Int64)
lookupIndex Contents{indexNames} name
  = (\j Item{indices} -> indices U.! j) <$> V.elemIndex name indexNames

readContents :: Stream -> QueryResult -> IO Contents
readContents Stream{indexNames, payloadHandle, indexHandle} ((seqnoOffset, payloadOffset), (s1, p1)) = do
  let length = s1 - seqnoOffset
  -- byte offset + number of indices
  let indexSize = 8 * (V.length indexNames + 1)
  indexBS <- hGetRange indexHandle (indexSize * length) (indexSize * succ seqnoOffset)
  payloads <- hGetRange payloadHandle (p1 - payloadOffset) payloadOffset
  let indicess = either error id $ runGet (getIndexVec indexNames length) indexBS
  pure Contents{..}