wireform-parquet-0.1.0.0: src/Parquet/Nested/Bloom.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Bloom construction from the present values of a shredded PLAIN leaf.
module Parquet.Nested.Bloom (buildBloomFilter) where
import Control.Monad.ST (ST)
import Data.Bits (shiftR, (.&.))
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Unsafe qualified as BSU
import Parquet.BloomFilter qualified as Bloom
import Parquet.Nested.Plain (PlainLayout (..), lengthAt, plainLayout, validPlainValues)
import Parquet.Types (SchemaElement)
-- Validate once before the ST insertion loop. BYTE_ARRAY lengths belong to
-- PLAIN framing and must not be included in the value's hash. BOOLEAN values
-- are hashed individually as 0/1 bytes, rather than as packed page bits.
buildBloomFilter :: SchemaElement -> Int -> ByteString -> Either String Bloom.Sbbf
buildBloomFilter field count bytes = do
layout <- plainLayout field
if not (validPlainValues layout count bytes)
then Left "Parquet.Nested: bloom values disagree with the physical PLAIN layout"
else pure $ case layout of
BooleanValues -> build (insertBooleans count bytes)
VariableValues -> build (insertByteArrays count bytes)
FixedValues width -> build (insertFixed count width bytes)
where
build :: (forall s. Bloom.SbbfBuilder s -> ST s ()) -> Bloom.Sbbf
build action = Bloom.buildSbbf (Bloom.optimalNumBytes (max 1 count) 0.01) action
insertFixed :: forall s. Int -> Int -> ByteString -> Bloom.SbbfBuilder s -> ST s ()
insertFixed count width bytes builder = go 0 0
where
go :: Int -> Int -> ST s ()
go !index !offset
| index == count = pure ()
| otherwise = do
Bloom.sbbfBuilderInsert builder (BS.take width (BS.drop offset bytes))
go (index + 1) (offset + width)
insertByteArrays :: forall s. Int -> ByteString -> Bloom.SbbfBuilder s -> ST s ()
insertByteArrays count bytes builder = go 0 0
where
go :: Int -> Int -> ST s ()
go !index !offset
| index == count = pure ()
| otherwise = do
let !length_ = lengthAt bytes offset
Bloom.sbbfBuilderInsert builder (BS.take length_ (BS.drop (offset + 4) bytes))
go (index + 1) (offset + 4 + length_)
insertBooleans :: forall s. Int -> ByteString -> Bloom.SbbfBuilder s -> ST s ()
insertBooleans count bytes builder = go 0
where
go :: Int -> ST s ()
go !index
| index == count = pure ()
| otherwise = do
let !value = BSU.unsafeIndex bytes (index `quot` 8) `shiftR` (index `rem` 8) .&. 1
Bloom.sbbfBuilderInsert builder (if value == 0 then falseByte else trueByte)
go (index + 1)
falseByte = BS.singleton 0
trueByte = BS.singleton 1