dataframe-0.5.0.0: src/DataFrame/IO/Parquet/Binary.hs
{-# LANGUAGE TypeApplications #-}
module DataFrame.IO.Parquet.Binary where
import Control.Monad
import Data.Bits
import qualified Data.ByteString as BS
import Data.Char
import Data.IORef
import Data.Int
import Data.Word
littleEndianWord32 :: BS.ByteString -> Word32
littleEndianWord32 bytes
| BS.length bytes >= 4 =
foldr
(.|.)
0
( zipWith
(\b i -> fromIntegral b `shiftL` i)
(BS.unpack $ BS.take 4 bytes)
[0, 8, 16, 24]
)
| otherwise =
littleEndianWord32 (BS.take 4 $ bytes `BS.append` BS.pack [0, 0, 0, 0])
littleEndianWord64 :: BS.ByteString -> Word64
littleEndianWord64 bytes =
foldr
(.|.)
0
( zipWith
(\b i -> fromIntegral b `shiftL` i)
(BS.unpack $ BS.take 8 bytes)
[0, 8 ..]
)
littleEndianInt32 :: BS.ByteString -> Int32
littleEndianInt32 = fromIntegral . littleEndianWord32
word64ToLittleEndian :: Word64 -> BS.ByteString
word64ToLittleEndian w =
BS.map
(\i -> fromIntegral (w `shiftR` fromIntegral i))
(BS.pack [0, 8, 16, 24, 32, 40, 48, 56])
word32ToLittleEndian :: Word32 -> BS.ByteString
word32ToLittleEndian w =
BS.map (\i -> fromIntegral (w `shiftR` fromIntegral i)) (BS.pack [0, 8, 16, 24])
readUVarInt :: BS.ByteString -> (Word64, BS.ByteString)
readUVarInt xs = loop xs 0 0 0
where
{-
Each input byte contributes:
- lower 7 payload bits
- The high bit (0x80) is the continuation flag: 1 = more bytes follow, 0 = last byte
Why the magic number 10: For a 64‑bit integer we need at most ceil(64 / 7) = 10 bytes
-}
loop :: BS.ByteString -> Word64 -> Int -> Int -> (Word64, BS.ByteString)
loop bs result _ 10 = (result, bs)
loop xs result shift i = case BS.uncons xs of
Nothing -> error "readUVarInt: not enough input bytes"
Just (b, bs) ->
if b < 0x80
then (result .|. (fromIntegral b `shiftL` shift), bs)
else
let payloadBits = fromIntegral (b .&. 0x7f) :: Word64
in loop bs (result .|. (payloadBits `shiftL` shift)) (shift + 7) (i + 1)
readVarIntFromBytes :: (Integral a) => BS.ByteString -> (a, BS.ByteString)
readVarIntFromBytes bs = (fromIntegral n, rem)
where
(n, rem) = loop 0 0 bs
loop shift result bs = case BS.uncons bs of
Nothing -> (result, BS.empty)
Just (x, xs) ->
let res = result .|. (fromIntegral (x .&. 0x7f) :: Integer) `shiftL` shift
in if x .&. 0x80 /= 0x80 then (res, xs) else loop (shift + 7) res xs
readIntFromBytes :: (Integral a) => BS.ByteString -> (a, BS.ByteString)
readIntFromBytes bs =
let (n, rem) = readVarIntFromBytes bs
u = fromIntegral n :: Word32
in (fromIntegral $ (fromIntegral (u `shiftR` 1) :: Int32) .^. (-(n .&. 1)), rem)
readInt32FromBytes :: BS.ByteString -> (Int32, BS.ByteString)
readInt32FromBytes bs =
let (n', rem) = readVarIntFromBytes @Int64 bs
n = fromIntegral n' :: Int32
u = fromIntegral n :: Word32
in ((fromIntegral (u `shiftR` 1) :: Int32) .^. (-(n .&. 1)), rem)
readAndAdvance :: IORef Int -> BS.ByteString -> IO Word8
readAndAdvance bufferPos buffer = do
pos <- readIORef bufferPos
let b = BS.index buffer pos
modifyIORef bufferPos (+ 1)
return b
readVarIntFromBuffer :: (Integral a) => BS.ByteString -> IORef Int -> IO a
readVarIntFromBuffer buf bufferPos = do
start <- readIORef bufferPos
let loop i shift result = do
b <- readAndAdvance bufferPos buf
let res = result .|. (fromIntegral (b .&. 0x7f) :: Integer) `shiftL` shift
if b .&. 0x80 /= 0x80
then return res
else loop (i + 1) (shift + 7) res
fromIntegral <$> loop start 0 0
readIntFromBuffer :: (Integral a) => BS.ByteString -> IORef Int -> IO a
readIntFromBuffer buf bufferPos = do
n <- readVarIntFromBuffer buf bufferPos
let u = fromIntegral n :: Word32
return $ fromIntegral $ (fromIntegral (u `shiftR` 1) :: Int32) .^. (-(n .&. 1))
readInt32FromBuffer :: BS.ByteString -> IORef Int -> IO Int32
readInt32FromBuffer buf bufferPos = do
n <- (fromIntegral <$> readVarIntFromBuffer @Int64 buf bufferPos) :: IO Int32
let u = fromIntegral n :: Word32
return $ (fromIntegral (u `shiftR` 1) :: Int32) .^. (-(n .&. 1))
readString :: BS.ByteString -> IORef Int -> IO String
readString buf pos = do
nameSize <- readVarIntFromBuffer @Int buf pos
map (chr . fromIntegral) <$> replicateM nameSize (readAndAdvance pos buf)
readByteStringFromBytes :: BS.ByteString -> (BS.ByteString, BS.ByteString)
readByteStringFromBytes xs =
let
(size, rem) = readVarIntFromBytes @Int xs
in
BS.splitAt size rem
readByteString :: BS.ByteString -> IORef Int -> IO BS.ByteString
readByteString buf pos = do
size <- readVarIntFromBuffer @Int buf pos
BS.pack <$> replicateM size (readAndAdvance pos buf)
readByteString' :: BS.ByteString -> Int64 -> IO BS.ByteString
readByteString' buf size = BS.pack <$> mapM (`readSingleByte` buf) [0 .. (size - 1)]
readSingleByte :: Int64 -> BS.ByteString -> IO Word8
readSingleByte pos buffer = return $ BS.index buffer (fromIntegral pos)
readNoAdvance :: IORef Int -> BS.ByteString -> IO Word8
readNoAdvance bufferPos buffer = do
pos <- readIORef bufferPos
return $ BS.index buffer pos