Skip to content

Commit f09a04a

Browse files
authored
Merge pull request #333 from IntersectMBO/mheinzel/resolve-value-totality
Remove Totality requirement for ResolveValue
2 parents 3b3add1 + bfd74ac commit f09a04a

File tree

3 files changed

+33
-47
lines changed

3 files changed

+33
-47
lines changed

src/Database/LSMTree/Internal/Serialise/Class.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import qualified Data.ByteString as BS
1919
import qualified Data.ByteString.Builder as B
2020
import qualified Data.ByteString.Lazy as LBS
2121
import qualified Data.ByteString.Short.Internal as SBS
22+
import Data.Monoid (Sum (..))
2223
import qualified Data.Primitive as P
2324
import Data.Proxy (Proxy)
2425
import qualified Data.Vector.Primitive as VP
@@ -88,6 +89,18 @@ class SerialiseValue v where
8889
-- TODO: Unused so far, we might not need it.
8990
deserialiseValueN :: [RawBytes] -> v
9091

92+
93+
-- | An instance for 'Sum' which is transparent to the serialisation of @a@.
94+
--
95+
-- Note: If you want to serialize @Sum a@ differently than @a@, then you should
96+
-- create another @newtype@ over 'Sum' and define your alternative serialization.
97+
instance SerialiseValue a => SerialiseValue (Sum a) where
98+
serialiseValue (Sum v) = serialiseValue v
99+
100+
deserialiseValue = Sum . deserialiseValue
101+
102+
deserialiseValueN = Sum . deserialiseValueN
103+
91104
-- | Test the __Identity__ law for the 'SerialiseValue' class
92105
serialiseValueIdentity :: (Eq v, SerialiseValue v) => v -> Bool
93106
serialiseValueIdentity x = deserialiseValue (serialiseValue x) == x

src/Database/LSMTree/Monoidal.hs

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ module Database.LSMTree.Monoidal (
9999
, resolveDeserialised
100100
-- ** Properties
101101
, resolveValueValidOutput
102-
, resolveValueTotality
103102
, resolveValueAssociativity
104103

105104
-- * Utility types
@@ -111,9 +110,9 @@ import Control.Monad (void, (<$!>))
111110
import Data.Bifunctor (Bifunctor (..))
112111
import Data.Coerce (coerce)
113112
import Data.Kind (Type)
113+
import Data.Monoid (Sum (..))
114114
import Data.Typeable (Proxy (Proxy), Typeable)
115115
import qualified Data.Vector as V
116-
import Data.Word (Word64)
117116
import Database.LSMTree.Common (IOLike, Range (..), SerialiseKey,
118117
SerialiseValue (..), Session (..), SnapshotName,
119118
closeSession, deleteSnapshot, listSnapshots, openSession,
@@ -369,7 +368,7 @@ open (Session sesh) override snap =
369368
label = Common.makeSnapshotLabel (Proxy @(k, v)) <> " (monoidal)"
370369

371370
{-------------------------------------------------------------------------------
372-
Mutiple writable table handles
371+
Multiple writable table handles
373372
-------------------------------------------------------------------------------}
374373

375374
{-# SPECIALISE duplicate :: TableHandle IO k v -> IO (TableHandle IO k v) #-}
@@ -435,25 +434,20 @@ merge = undefined
435434
-------------------------------------------------------------------------------}
436435

437436
-- | A class to specify how to resolve/merge values when using monoidal updates
438-
-- (mupserts). This is required for merging entries during compaction and also
439-
-- for doing lookups, to resolve multiple entries of the same key on the fly.
437+
-- ('Mupsert'). This is required for merging entries during compaction and also
438+
-- for doing lookups, resolving multiple entries of the same key on the fly.
440439
-- The class has some laws, which should be tested (e.g. with QuickCheck).
441440
--
441+
-- It is okay to assume that the input bytes can be deserialised using
442+
-- 'deserialiseValue', as they are produced by either 'serialiseValue' or
443+
-- 'resolveValue' itself, which are required to produce deserialisable output.
444+
--
442445
-- Prerequisites:
443446
--
444447
-- * [Valid Output] The result of resolution should always be deserialisable.
445448
-- See 'resolveValueValidOutput'.
446449
-- * [Associativity] Resolving values should be associative.
447450
-- See 'resolveValueAssociativity'.
448-
-- * [Totality] For any input 'RawBytes', resolution should successfully provide
449-
-- a result. This is a pretty strong requirement. Usually it is sufficient to
450-
-- handle input produced by 'serialiseValue' and 'resolveValue' (which are
451-
-- are required to be deserialisable by 'deserialiseValue'),
452-
-- but this makes sure no error occurs in the middle of compaction, which
453-
-- could lead to corruption.
454-
-- See 'resolveValueTotality'.
455-
--
456-
-- TODO: Revisit Totality. How are errors handled during run merging?
457451
--
458452
-- Future opportunities for optimisations:
459453
--
@@ -465,8 +459,10 @@ merge = undefined
465459
-- means that the inserted value is serialised and (if there is another value
466460
-- with the same key in the write buffer) immediately deserialised again.
467461
--
468-
-- TODO: Should this go into @Internal.Monoidal@ or @Internal.ResolveValue@?
469462
-- TODO: The laws depend on 'SerialiseValue', should we make it a superclass?
463+
-- TODO: should we additionally require Totality (for any input 'RawBytes',
464+
-- resolution should successfully provide a result)? This could reduce the
465+
-- risk of encountering errors during a run merge.
470466
class ResolveValue v where
471467
resolveValue :: Proxy v -> RawBytes -> RawBytes -> RawBytes
472468

@@ -486,13 +482,6 @@ resolveValueAssociativity (serialiseValue -> x) (serialiseValue -> y) (serialise
486482
where
487483
(<+>) = resolveValue (Proxy @v)
488484

489-
-- | Test the __Totality__ law for the 'ResolveValue' class
490-
resolveValueTotality ::
491-
forall v. ResolveValue v
492-
=> Proxy v -> RawBytes -> RawBytes -> Bool
493-
resolveValueTotality _ x y =
494-
resolveValue (Proxy @v) x y `deepseq` True
495-
496485
-- | A helper function to implement 'resolveValue' by operating on the
497486
-- deserialised representation. Note that especially for simple types it
498487
-- should be possible to provide a more efficient implementation by directly
@@ -502,14 +491,10 @@ resolveValueTotality _ x y =
502491
-- for 'resolveValue', but it's probably best to be explicit about instances.
503492
--
504493
-- To satisfy the prerequisites of 'ResolveValue', the function provided to
505-
-- 'resolveDeserialised' should itself satisfy some properties.
506-
--
507-
-- Prerequisites:
494+
-- 'resolveDeserialised' should itself satisfy some properties:
508495
--
509496
-- * [Associativity] The provided function should be associative.
510-
-- * [Total Resolution] The provided function should be total.
511-
-- * [Total Deserialisation] 'deserialiseValue' for @v@ should handle any input
512-
-- 'RawBytes'.
497+
-- * [Totality] The provided function should be total.
513498
resolveDeserialised ::
514499
SerialiseValue v
515500
=> (v -> v -> v) -> Proxy v -> RawBytes -> RawBytes -> RawBytes
@@ -520,5 +505,7 @@ resolve :: ResolveValue v => Proxy v -> Internal.ResolveSerialisedValue
520505
resolve = coerce . resolveValue
521506

522507
-- | Mostly to give an example instance (plus the property tests for it).
523-
instance ResolveValue Word64 where
508+
-- Additionally, this instance for 'Sum' provides a nice monoidal, numerical
509+
-- aggregator.
510+
instance (Num a, SerialiseValue a) => ResolveValue (Sum a) where
524511
resolveValue = resolveDeserialised (+)
Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,28 @@
11
{-# LANGUAGE AllowAmbiguousTypes #-}
2-
{-# LANGUAGE ScopedTypeVariables #-}
3-
{-# LANGUAGE TypeApplications #-}
42

53
module Test.Database.LSMTree.Monoidal (tests) where
64

75
import Control.DeepSeq (NFData)
8-
import Data.Proxy (Proxy (Proxy))
6+
import Data.Monoid (Sum (..))
97
import Data.Word
108
import Database.LSMTree.Extras.Generators ()
11-
import Database.LSMTree.Internal.RawBytes (RawBytes)
129
import Database.LSMTree.Monoidal
1310
import Test.Tasty
1411
import Test.Tasty.QuickCheck
1512

1613
tests :: TestTree
1714
tests = testGroup "Test.Database.LSMTree.Monoidal"
18-
[ testGroup "Word64" (allProperties @Word64 False)
19-
-- TODO: revisit totality (drop requirement or fix @SerialiseValue Word64@)
15+
[ testGroup "Sum Word64" (allProperties @(Sum Word64))
2016
]
2117

2218
allProperties ::
2319
forall v. (Show v, Arbitrary v, NFData v, SerialiseValue v, ResolveValue v)
24-
=> Bool -> [TestTree]
25-
allProperties expectTotality =
20+
=> [TestTree]
21+
allProperties =
2622
[ testProperty "prop_resolveValueValidOutput" $ withMaxSuccess 1000 $
2723
prop_resolveValueValidOutput @v
2824
, testProperty "prop_resolveValueAssociativity" $ withMaxSuccess 1000 $
2925
prop_resolveValueAssociativity @v
30-
, testProperty "prop_resolveValueTotality" $ withMaxSuccess 1000 $ \x y ->
31-
(if expectTotality then id else expectFailure) $
32-
prop_resolveValueTotality @v x y
3326
]
3427

3528
prop_resolveValueValidOutput ::
@@ -45,10 +38,3 @@ prop_resolveValueAssociativity ::
4538
prop_resolveValueAssociativity x y z =
4639
counterexample ("inputs: " <> show (x, y)) $
4740
resolveValueAssociativity x y z
48-
49-
prop_resolveValueTotality ::
50-
forall v. ResolveValue v
51-
=> RawBytes -> RawBytes -> Property
52-
prop_resolveValueTotality x y =
53-
counterexample ("inputs: " <> show (x, y)) $
54-
resolveValueTotality (Proxy @v) x y

0 commit comments

Comments
 (0)