{-# LANGUAGE AllowAmbiguousTypes #-} {-# OPTIONS_HADDOCK hide #-} module PopKey.Internal3 where import Data.Bifunctor import qualified Data.ByteString as BS import Data.Functor.Contravariant import HaskellWorks.Data.RankSelect.CsPoppy import qualified HaskellWorks.Data.RankSelect.CsPoppy.Internal.Alpha0 as A0 import qualified HaskellWorks.Data.RankSelect.CsPoppy.Internal.Alpha1 as A1 import Data.Foldable import Data.List (sortOn) import Data.Store import GHC.Generics hiding (R) import GHC.Word import Unsafe.Coerce import PopKey.Internal1 import PopKey.Internal2 import PopKey.Encoding -- Bool here is whether the decoding function is the canonical decoding function from when -- the index was first built. it allows the Store instance to skip re-building the structure -- before serialization. the Functor instance should still be observably-valid from the safe public API, -- at least modulo bottoms and the fact that mapping the identity will cause performance artefacts. data PopKey k v where PopKeyInt :: forall s v . Bool -> !(F s PKPrim) -> (F' s BS.ByteString -> v) -> PopKey Int v PopKeyAny :: forall s k v . Bool -> !(F s PKPrim) -> (F' s BS.ByteString -> v) -> !(F (Shape k) PKPrim) -> PopKey k v instance Functor (PopKey k) where {-# INLINE fmap #-} fmap f (PopKeyInt _ p d) = PopKeyInt False p (f . d) fmap f (PopKeyAny _ pv d pk) = PopKeyAny False pv (f . d) pk instance Foldable (PopKey k) where {-# INLINE foldr #-} foldr f z p@(PopKeyInt _ pr vd) = foldr (\i -> f (vd do rawq i pr)) z [ 0 .. (length p - 1) ] foldr f z p@(PopKeyAny _ pr vd _) = foldr (\i -> f (vd do rawq i pr)) z [ 0 .. (length p - 1) ] {-# INLINE length #-} length (PopKeyInt _ p _) = flength p length (PopKeyAny _ _ _ p) = flength p {-# INLINABLE foldrWithKey #-} foldrWithKey :: PopKeyEncoding k => (k -> v -> b -> b) -> b -> PopKey k v -> b foldrWithKey f z p@(PopKeyInt _ pr vd) = foldr do \i -> f i (vd do rawq i pr) do z do [ 0 .. (length p - 1) ] foldrWithKey f z p@(PopKeyAny _ pr vd pk) = foldr do \i -> f (pkDecode $ rawq i pk) (vd do rawq i pr) do z do [ 0 .. (length p - 1) ] {-# INLINABLE foldlWithKey' #-} foldlWithKey' :: PopKeyEncoding k => (a -> k -> v -> a) -> a -> PopKey k v -> a foldlWithKey' f z p@(PopKeyInt _ pr vd) = foldl' do \a i -> f a i (vd do rawq i pr) do z do [ 0 .. (length p - 1) ] foldlWithKey' f z p@(PopKeyAny _ pr vd pk) = foldl' do \a i -> f a (pkDecode $ rawq i pk) (vd do rawq i pr) do z do [ 0 .. (length p - 1) ] ------------------------------------------- -- PopKey serialization for mmap loading -- ------------------------------------------- class BiSerialize a where bencode :: a -> (BS.ByteString , BS.ByteString) default bencode :: (Generic a , GBiSerialize a (Rep a)) => a -> (BS.ByteString , BS.ByteString) bencode = gbencode @a @(Rep a) . from bdecode :: (BS.ByteString , BS.ByteString) -> a default bdecode :: (Generic a , GBiSerialize a (Rep a)) => (BS.ByteString , BS.ByteString) -> a bdecode = to . gbdecode @a @(Rep a) class GBiSerialize s f where gbencode :: f a -> (BS.ByteString , BS.ByteString) gbdecode :: (BS.ByteString , BS.ByteString) -> f a instance GBiSerialize s U1 where {-# INLINE gbencode #-} gbencode = const mempty {-# INLINE gbdecode #-} gbdecode = const mempty instance BiSerialize a => GBiSerialize s (K1 i a) where {-# INLINE gbencode #-} gbencode (K1 x) = bencode x {-# INLINE gbdecode #-} gbdecode = K1 . bdecode instance (GBiSerialize s a , GBiSerialize s b) => GBiSerialize s (a :*: b) where {-# INLINE gbencode #-} gbencode (a :*: b) = do let (a1 , a2) = gbencode @s a (b1 , b2) = gbencode @s b (encode (a1 , b1) , encode (a2 , b2)) {-# INLINE gbdecode #-} gbdecode (r1 , r2) = do let (a1 , b1) = decodeEx r1 (a2 , b2) = decodeEx r2 gbdecode @s (a1 , a2) :*: gbdecode @s (b1 , b2) instance (GBiSerialize s a , GBiSerialize s b) => GBiSerialize s (a :+: b) where gbencode (L1 x) = do let (b1 , b2) = gbencode @s x (encode False <> b1 , b2) gbencode (R1 x) = do let (b1 , b2) = gbencode @s x (encode True <> b1 , b2) gbdecode ((BS.splitAt 1 -> (b , b1)) , b2) = if decodeEx b then R1 (gbdecode @s (b1 , b2)) else L1 (gbdecode @s (b1 , b2)) instance GBiSerialize s f => GBiSerialize s (M1 i t f) where {-# INLINE gbencode #-} gbencode (M1 x) = gbencode @s x {-# INLINE gbdecode #-} gbdecode = M1 . gbdecode @s instance BiSerialize CsPoppy where bencode (CsPoppy bv (A0.CsPoppyIndex a01 a02) (A1.CsPoppyIndex a11 a12)) = (,) do encode (a01 , a02 , a11 , a12) do encode bv bdecode (bs , bv) = do let (a01 , a02 , a11 , a12) = decodeEx bs CsPoppy (decodeEx bv) (A0.CsPoppyIndex a01 a02) (A1.CsPoppyIndex a11 a12) instance BiSerialize BS.ByteString where {-# INLINE bencode #-} bencode x = (mempty , x) {-# INLINE bdecode #-} bdecode (_ , x) = x instance BiSerialize Word32 where {-# INLINE bencode #-} bencode x = (encode x , mempty) {-# INLINE bdecode #-} bdecode (x , _) = decodeEx x instance BiSerialize PKPrim instance BiSerialize a => BiSerialize (Maybe a) instance (BiSerialize a , BiSerialize b) => BiSerialize (a , b) -- poppy is undefined here if the first value is 0 data Custom = Custom {-# UNPACK #-} !Word32 CsPoppy instance BiSerialize Custom where bencode (Custom l ppy) = do let x :: Maybe (Word32 , CsPoppy) = if l == 0 then Nothing else Just (l , ppy) bencode x bdecode r = case bdecode r of Nothing -> Custom 0 undefined Just (l , ppy) -> Custom l ppy -- serializable format for F data SF a = SSingle a | SProd !(SF a) !(SF a) | SSum !Custom !(SF a) !(SF a) deriving (Generic,BiSerialize) fromF :: F s a -> SF a fromF (Single x) = SSingle x fromF (Prod x y) = SProd (fromF x) (fromF y) fromF (Sum l ppy x y) = SSum (Custom l ppy) (fromF x) (fromF y) -- there's a reason this module is internal toF :: SF a -> F s a toF (SSingle x) = unsafeCoerce do Single x toF (SProd x y) = unsafeCoerce do Prod (toF x) (toF y) toF (SSum (Custom l ppy) x y) = unsafeCoerce do Sum l ppy (toF x) (toF y) data SPopKey k v = SPopKeyInt !(SF PKPrim) | SPopKeyAny !(SF PKPrim) !(SF PKPrim) deriving (Generic,BiSerialize) toSPopKey :: PopKey k v -> SPopKey k v toSPopKey (PopKeyInt _ p _) = SPopKeyInt (fromF p) toSPopKey (PopKeyAny _ p1 _ p2) = SPopKeyAny (fromF p1) (fromF p2) fromSPopKey :: forall k v . (PopKeyEncoding k , PopKeyEncoding v) => SPopKey k v -> PopKey k v fromSPopKey (SPopKeyInt p) = unsafeCoerce (PopKeyInt True (toF p) (pkDecode @v)) fromSPopKey (SPopKeyAny pv pk) = PopKeyAny True (toF pv) (pkDecode @v) (toF pk) fromSPopKey' :: PopKeyEncoding v => SPopKey Int v -> PopKey Int v fromSPopKey' (SPopKeyInt p) = PopKeyInt True (toF p) pkDecode fromSPopKey' _ = error "Incorrect PopKey type: expected Int." -- re-encode using whatever the current value encoding is {-# INLINABLE normalise #-} normalise :: (PopKeyEncoding k , PopKeyEncoding v) => PopKey k v -> PopKey k v normalise p@(PopKeyInt True _ _) = p normalise p@(PopKeyInt _ _ _) = makePopKey' (toList p) normalise p@(PopKeyAny True _ _ _) = p normalise p@(PopKeyAny _ _ _ _) = makePopKey (foldrWithKey (\k v -> (:) (k,v)) [] p) toStoreEnc :: (PopKeyEncoding k , PopKeyEncoding v) => PopKey k v -> (Bool , BS.ByteString , BS.ByteString) toStoreEnc (normalise -> p) = do let (b1 , b2) = bencode (toSPopKey p) case p of PopKeyInt _ _ _ -> (True , b1 , b2) PopKeyAny _ _ _ _ -> (False , b1 , b2) fromStoreEnc :: forall k v . (PopKeyEncoding k , PopKeyEncoding v) => (Bool , BS.ByteString , BS.ByteString) -> PopKey k v fromStoreEnc (True , b1 , b2) = unsafeCoerce (fromSPopKey' (bdecode (b1 , b2) :: SPopKey Int v)) fromStoreEnc (False , b1 , b2) = fromSPopKey (bdecode (b1 , b2)) instance (PopKeyEncoding k , PopKeyEncoding v) => Store (PopKey k v) where size = contramap toStoreEnc size peek = fmap fromStoreEnc peek poke = poke . toStoreEnc {-# INLINE makePopKey #-} -- | Create a poppy-backed key-value storage structure. makePopKey :: forall f k v . (Foldable f , PopKeyEncoding k , PopKeyEncoding v) => f (k , v) -> PopKey k v makePopKey = makePopKeyWithEncoding (shape @k) (shape @v) (pkEncode @v) (pkDecode @v) where makePopKeyWithEncoding :: Foldable f => I (Shape k) -> I s -> (v -> F' s BS.ByteString) -> (F' s BS.ByteString -> v) -> f (k , v) -> PopKey k v makePopKeyWithEncoding ik iv ev dv xs = do let (ks , vs) = unzip (lastv $ sortOn fst (foldr ((:) . first pkEncode) [] xs)) PopKeyAny do True do construct iv ev vs do dv do construct ik id ks where -- for duplicate keys, use the last value lastv :: forall a b . Ord a => [(a,b)] -> [(a,b)] lastv [] = [] lastv [ x ] = [ x ] lastv (x : ys@(y : _)) = if fst x == fst y then lastv ys else x : lastv ys -- | Create a poppy-backed structure with elements implicitly indexed by their position. {-# INLINE makePopKey' #-} makePopKey' :: forall f v . (Foldable f , PopKeyEncoding v) => f v -> PopKey Int v makePopKey' = go (shape @v) (pkEncode @v) (pkDecode @v) . foldr (:) [] where go :: I s -> (a -> F' s BS.ByteString) -> (F' s BS.ByteString -> a) -> [ a ] -> PopKey Int a go i e d xs = PopKeyInt do True do construct i e xs do d data PopKeyStore k v = PopKeyStore (forall f . Foldable f => f (k , v) -> IO ()) (IO (PopKey k v)) data PopKeyStore' v = PopKeyStore' (forall f . Foldable f => f v -> IO ()) (IO (PopKey Int v)) class StorePopKey k v f | f -> k , f -> v where type Input f storePopKey :: Foldable t => f -> t (Input f) -> IO () loadPopKey :: f -> IO (PopKey k v) instance StorePopKey k v (PopKeyStore k v) where type Input (PopKeyStore k v) = (k , v) storePopKey (PopKeyStore a _) = a loadPopKey (PopKeyStore _ b) = b instance StorePopKey Int v (PopKeyStore' v) where type Input (PopKeyStore' v) = v storePopKey (PopKeyStore' a _) = a loadPopKey (PopKeyStore' _ b) = b