packages feed

ron-rdt-0.11: lib/RON/Experimental/Data/ORSet.hs

{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module RON.Experimental.Data.ORSet (
    ORSet,
    ORMap,
    add,
    add_,
    decode,
    empty,
    getDecode,
    lookupLww,
    lookupLwwThrow,
    lookupLwwDecode,
    lookupLwwDecodeThrow,
    lookupSet,
) where

import RON.Prelude

import Data.Map.Strict qualified as Map
import Data.Text.Lazy qualified as TextL
import Data.Text.Lazy.Encoding qualified as TextL

import Data.List (stripPrefix)
import RON.Error (MonadE, liftMaybe)
import RON.Event (ReplicaClock, advanceToUuid, getEventUuid)
import RON.Experimental.Data (
    AsAtom,
    AsAtoms,
    fromAtoms,
    toAtom,
    toAtoms,
 )
import RON.Store.Class (MonadStore, appendPatch, loadWholeObjectLog)
import RON.Text.Serialize (serializeAtom)
import RON.Types (Op (..), OpenFrame, Payload, UUID)
import RON.Types.Experimental (Patch (..), Ref (..))

import RON.Experimental.Data.ORSet.Type (ORMap, ORSet (..))

decode :: (Applicative f) => UUID -> OpenFrame -> f (ORSet a)
decode objectId ops =
    pure
        . ORSet
        $ Map.fromListWith
            (maxOn fst)
            [ (itemId, (opId, payload))
            | Op{opId, refId, payload} <- ops
            , opId /= objectId
            , let
                itemId =
                    case payload of
                        [] -> refId -- tombstone
                        _ : _ -> opId -- add
            ]

-- | Add value to the set. Return the reference to the set item.
add ::
    (AsAtoms item, MonadStore m, ReplicaClock m) =>
    Ref (ORSet item) ->
    item ->
    m UUID
add (Ref object path) value = do
    advanceToUuid object
    opId <- getEventUuid
    appendPatch
        Patch
            { object
            , log =
                Op{opId, refId = object, payload = path ++ toAtoms value} :| []
            }
    pure opId

{- |
    Add value to the set or map.

    @add_ :: Ref (ORSet a)   -> a      -> m ()@
    @add_ :: Ref (ORMap k v) -> (k, v) -> m ()@
-}
add_ ::
    (AsAtoms item, MonadStore m, ReplicaClock m) =>
    Ref (ORSet item) ->
    item ->
    m ()
add_ ref = void . add ref

-- | Get items from database and decode
getDecode :: (AsAtoms a, MonadE m, MonadStore m) => Ref (ORSet a) -> m [a]
getDecode (Ref object pre) = do
    -- TODO loadObjectLog object (PayloadPrefix pre)
    ops <- loadWholeObjectLog object mempty
    let alivePayloads =
            filter (not . null) $
                map snd $
                    toList $
                        Map.fromListWith
                            (maxOn fst)
                            [ (itemId, (opId, payload'))
                            | Op{opId, refId, payload} <- ops
                            , (itemId, payload') <-
                                case payload of
                                    [] -> pure (refId, payload)
                                    (stripPrefix pre -> Just suf) ->
                                        pure (opId, suf)
                                    _ -> []
                            ]
    traverse fromAtoms alivePayloads

lookupLww :: (AsAtom k) => k -> ORMap k v -> Maybe Payload
lookupLww key (ORSet s) =
    snd
        <$> maximumMayOn
            fst
            [(item, value) | (item, k : value) <- Map.elems s, k == toAtom key]

-- | Like 'lookupLww' but also decode payload.
lookupLwwDecode ::
    (AsAtom k, AsAtoms v, MonadE m) => k -> ORMap k v -> m (Maybe v)
lookupLwwDecode key = traverse fromAtoms . lookupLww key

lookupLwwThrow :: (AsAtom k, MonadE m) => k -> ORMap k v -> m Payload
lookupLwwThrow key obj =
    liftMaybe ("key " <> showAtom key <> " must present") $ lookupLww key obj
  where
    showAtom = TextL.toStrict . TextL.decodeUtf8 . serializeAtom . toAtom

-- | Like 'lookupLwwDecode' but assert that key exists.
lookupLwwDecodeThrow :: (AsAtom k, AsAtoms v, MonadE m) => k -> ORMap k v -> m v
lookupLwwDecodeThrow key = lookupLwwThrow key >=> fromAtoms

empty :: ORSet a
empty = ORSet Map.empty

lookupSet :: (AsAtom k, AsAtoms v, MonadE m) => k -> ORMap k v -> m [v]
lookupSet key (ORSet s) =
    traverse
        fromAtoms
        [value | (_item, k : value) <- Map.elems s, k == toAtom key]