{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables #-} module Database.FileArray ( FileArray , create, open, close , Element(..) , unsafeElement -- no index checks , msync ) where import Control.Monad (when) import Data.Bits (Bits, (.&.), complement) import Data.Typeable (Typeable) import Data.Word (Word64) import Foreign.ForeignPtr (ForeignPtr, finalizeForeignPtr, withForeignPtr) import Foreign.Ptr (plusPtr) import Foreign.Storable (Storable) import qualified Control.Exception as Exc import qualified Foreign.Storable as Storable import qualified System.IO as IO import qualified System.IO.MMap as MMap import qualified System.IO.MMap.Sync as MMapSync data FileArray a = FileArray { faCount :: Word64 , faPtr :: ForeignPtr a } data Element a = Element { read :: IO a , write :: a -> IO () } alignPage :: (Num a, Bits a) => a -> a alignPage x = (x + 0xFFF) .&. complement 0xFFF sizeOf :: Storable a => a -> Word64 sizeOf = fromIntegral . Storable.sizeOf create :: forall a. Storable a => FilePath -> Word64 -> IO (FileArray a) create filePath count = do IO.withBinaryFile filePath IO.ReadWriteMode $ \handle -> IO.hSetFileSize handle . alignPage . fromIntegral $ count * elemSize open filePath count where elemSize = sizeOf (undefined :: a) data MMapWrongRange = MMapWrongRange deriving (Show, Typeable) instance Exc.Exception MMapWrongRange open :: forall a. Storable a => FilePath -> Word64 -> IO (FileArray a) open filePath count = do (ptr, base, mapSize) <- MMap.mmapFileForeignPtr filePath MMap.ReadWrite Nothing when (base > 0 || fromIntegral mapSize < minFileSize) $ Exc.throwIO MMapWrongRange return $ FileArray count ptr where minFileSize = count * sizeOf (undefined :: a) close :: FileArray a -> IO () close = finalizeForeignPtr . faPtr {-# INLINE elementSize #-} elementSize :: forall a. Storable a => FileArray a -> Word64 elementSize _ = sizeOf (undefined :: a) {-# INLINE unsafeElement #-} unsafeElement :: Storable a => FileArray a -> Word64 -> IO (Element a) unsafeElement fileArray ix = withForeignPtr (faPtr fileArray) $ \keysPtr -> do let ptr = keysPtr `plusPtr` fromIntegral (ix * elementSize fileArray) return $ Element (Storable.peek ptr) (Storable.poke ptr) msync :: Storable a => FileArray a -> IO () msync fileArray = withForeignPtr (faPtr fileArray) $ \ptr -> MMapSync.msync ptr (fromIntegral fileSize) (Just MMapSync.Async) MMapSync.NoInvalidate where fileSize = faCount fileArray * elementSize fileArray