packages feed

lsm-tree-1.0.0.0: test/Test/Database/LSMTree/Model/Table.hs

{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}

module Test.Database.LSMTree.Model.Table (tests) where

import qualified Data.ByteString as BS
import qualified Data.Vector as V
import           Database.LSMTree (ResolveValue (..), ResolveViaSemigroup (..),
                     SerialiseKey (..), SerialiseValue (..))
import           Database.LSMTree.Model.Table (LookupResult (..), Table,
                     Update (..), lookups)
import qualified Database.LSMTree.Model.Table as Model
import           GHC.Exts (IsList (..))
import           Test.QuickCheck.Instances ()
import           Test.Tasty
import           Test.Tasty.QuickCheck

tests :: TestTree
tests = testGroup "Database.LSMTree.Model.Table"
    [ testProperty "lookup-insert" prop_lookupInsert
    , testProperty "lookup-delete" prop_lookupDelete
    , testProperty "insert-insert" prop_insertInsert
    , testProperty "upsert-insert" prop_upsertInsert
    , testProperty "upsert=lookup+insert" prop_upsertDef
    , testProperty "insert-commutes" prop_insertCommutes
    ]

type Key = BS.ByteString

newtype Value = Value BS.ByteString
  deriving stock (Eq, Show)
  deriving newtype (Arbitrary, Semigroup, SerialiseValue)
  deriving ResolveValue via (ResolveViaSemigroup Value)

type Blob = BS.ByteString

type Tbl = Table Key Value Blob

updates :: V.Vector (Key, Update Value Blob) -> Table Key Value Blob -> Table Key Value Blob
updates = Model.updates Model.getResolve

inserts :: V.Vector (Key, Value, Maybe Blob) -> Table Key Value Blob -> Table Key Value Blob
inserts = Model.inserts Model.getResolve

deletes :: V.Vector Key -> Table Key Value Blob -> Table Key Value Blob
deletes = Model.deletes Model.getResolve

mupserts :: V.Vector (Key, Value) -> Table Key Value Blob -> Table Key Value Blob
mupserts = Model.mupserts Model.getResolve

-- | You can lookup what you inserted.
prop_lookupInsert :: Key -> Value -> Tbl -> Property
prop_lookupInsert k v tbl =
    lookups (V.singleton k) (inserts (V.singleton (k, v, Nothing)) tbl) === V.singleton (Found v)

-- | You cannot lookup what you have deleted
prop_lookupDelete :: Key -> Tbl -> Property
prop_lookupDelete k tbl =
    lookups (V.singleton k) (deletes (V.singleton k) tbl) === V.singleton NotFound

-- | Last insert wins.
prop_insertInsert :: Key -> Value -> Value -> Tbl -> Property
prop_insertInsert k v1 v2 tbl =
    inserts (V.fromList [(k, v1, Nothing), (k, v2, Nothing)]) tbl === inserts (V.singleton (k, v2, Nothing)) tbl

-- | Updating after insert is the same as inserting merged value.
--
-- Note: the order of merge.
prop_upsertInsert :: Key -> Value -> Value -> Tbl -> Property
prop_upsertInsert k v1 v2 tbl =
    updates (V.fromList [(k, Insert v1 Nothing), (k, Upsert v2)]) tbl
      === inserts (V.singleton (k, resolve v2 v1, Nothing)) tbl

-- | Upsert is the same as lookup followed by an insert.
prop_upsertDef :: Key -> Value -> Tbl -> Property
prop_upsertDef k v tbl =
    tbl' === mupserts (V.singleton (k, v)) tbl
  where
    tbl' = case toList (lookups (V.singleton k) tbl) of
        [Found v'] -> inserts (V.singleton (k, resolve v v', Nothing)) tbl
        [FoundWithBlob v' _] -> inserts (V.singleton (k, resolve v v', Nothing)) tbl
        _          -> inserts (V.singleton (k, v, Nothing)) tbl

-- | Different key inserts commute.
prop_insertCommutes :: Key -> Value -> Key -> Value -> Tbl -> Property
prop_insertCommutes k1 v1 k2 v2 tbl = k1 /= k2 ==>
    inserts (V.fromList [(k1, v1, Nothing), (k2, v2, Nothing)]) tbl === inserts (V.fromList [(k2, v2, Nothing), (k1, v1, Nothing)]) tbl

instance (SerialiseKey k, SerialiseValue v, SerialiseValue b, Arbitrary k, Arbitrary v, Arbitrary b) => Arbitrary (Table k v b) where
    arbitrary = fromList <$> arbitrary
    shrink t  = fromList <$> shrink (toList t)