CurryDB-0.1.1.0: Database/Curry/HashMap.hs
{-# LANGUAGE OverloadedStrings #-}
module Database.Curry.HashMap (
HashMap, Key,
new,
load, save,
insert, insertWith,
delete,
lookup,
keys,
) where
import Control.Applicative
import Control.Concurrent.STM
import qualified Control.Exception as E
import Control.Monad
import qualified Data.Array.MArray as MA
import Data.Binary
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import Data.Hashable
import qualified Data.HashMap.Strict as HMS
import Data.Maybe
import qualified Filesystem as FS
import qualified Filesystem.Path as FP
import System.IO
import Database.Curry.Binary ()
import Prelude hiding (lookup)
type Key = S.ByteString
newtype HashMap v =
HashMap { unHM :: TArray Int (HMS.HashMap Key v) }
bucketNum :: Int
bucketNum = 32
bucketIx :: Key -> Int
bucketIx key = hash key `mod` bucketNum
new :: IO (HashMap v)
new = atomically $ do
HashMap <$> MA.newArray (0, bucketNum - 1) HMS.empty
load :: Binary v => FP.FilePath -> HashMap v -> IO ()
load path hm = do
vs <- decode . L.fromChunks . (\x -> [x]) <$> FS.readFile path
_ <- E.evaluate vs -- force and raise exception when data is corrupted.
when (length vs /= bucketNum) $
fail "load: data corrupted"
atomically $ do
forM_ (zip [0..] vs) $ \(ix, v) -> do
MA.writeArray (unHM hm) ix v
save :: Binary v => FP.FilePath -> HashMap v -> IO ()
save path hm = do
vs <- atomically $ forM_ [0 .. bucketNum - 1] $ \ix -> MA.readArray (unHM hm) ix
atomicWriteFile path vs
insert :: Key -> v -> HashMap v -> STM ()
insert = insertWith const
{-# INLINE insert #-}
insertWith :: (v -> v -> v) -> Key -> v -> HashMap v -> STM ()
insertWith f k v hm = do
let bix = bucketIx k
bkt <- MA.readArray (unHM hm) bix
MA.writeArray (unHM hm) bix $! HMS.insertWith f k v bkt
{-# INLINE insertWith #-}
delete :: Key -> HashMap v -> STM ()
delete k hm = do
let bix = bucketIx k
bkt <- MA.readArray (unHM hm) bix
MA.writeArray (unHM hm) bix $! HMS.delete k bkt
{-# INLINE delete #-}
lookup :: Key -> HashMap v -> STM (Maybe v)
lookup k hm = do
let bix = bucketIx k
HMS.lookup k <$> MA.readArray (unHM hm) bix
{-# INLINE lookup #-}
keys :: HashMap v -> STM [Key]
keys hm = do
bkts <- forM [0 .. bucketNum - 1] $ \ix ->
HMS.keys <$> MA.readArray (unHM hm) ix
return $ concat bkts
{-# INLINE keys #-}
--
atomicWriteFile :: Binary b => FP.FilePath -> b -> IO ()
atomicWriteFile path b = do
let tmpPath = path FP.<.> "tmp"
FS.withFile tmpPath WriteMode $ \h ->
L.hPut h $ encode b
-- rename operation is atomic
FS.rename tmpPath path