@@ -19,6 +19,7 @@ module Database.LSMTree.Model.Session (
1919 , withSomeTable
2020 , TableID
2121 , tableID
22+ , isUnionDescendant
2223 , Model. size
2324 -- ** Constraints
2425 , C
@@ -76,8 +77,13 @@ module Database.LSMTree.Model.Session (
7677 -- * Multiple writable tables
7778 , duplicate
7879 -- * Table union
80+ , IsUnionDescendant (.. )
7981 , union
8082 , unions
83+ , UnionDebt (.. )
84+ , remainingUnionDebt
85+ , UnionCredits (.. )
86+ , supplyUnionCredits
8187 ) where
8288
8389import Control.Monad (forM , when )
@@ -95,7 +101,8 @@ import Data.Maybe (fromJust)
95101import qualified Data.Vector as V
96102import Data.Word
97103import Database.LSMTree.Common (SerialiseKey (.. ),
98- SerialiseValue (.. ), SnapshotLabel , SnapshotName )
104+ SerialiseValue (.. ), SnapshotLabel , SnapshotName ,
105+ UnionCredits (.. ), UnionDebt (.. ))
99106import Database.LSMTree.Model.Table (LookupResult (.. ),
100107 QueryResult (.. ), Range (.. ), ResolveSerialisedValue (.. ),
101108 Update (.. ), getResolve , noResolve )
@@ -125,6 +132,10 @@ initModel = Model {
125132-- | We conservatively model blob reference invalidation: each update after
126133-- acquiring a blob reference will invalidate it. We use 'UpdateCounter' to
127134-- track updates.
135+ --
136+ -- Supplying union credits is also considered an update, though this can only
137+ -- invalidate a blob reference that is associated with a (descendant of a) union
138+ -- table.
128139newtype UpdateCounter = UpdateCounter Word64
129140 deriving stock (Show , Eq , Ord )
130141 deriving newtype (Num )
@@ -300,8 +311,9 @@ type TableID = Int
300311--
301312
302313data Table k v b = Table {
303- tableID :: TableID
304- , config :: TableConfig
314+ tableID :: TableID
315+ , config :: TableConfig
316+ , isUnionDescendant :: IsUnionDescendant
305317 }
306318 deriving stock Show
307319
@@ -319,7 +331,7 @@ new ::
319331 forall k v b m . (MonadState Model m , C k v b )
320332 => TableConfig
321333 -> m (Table k v b )
322- new config = newTableWith config Model. empty
334+ new config = newTableWith config IsNotUnionDescendant Model. empty
323335
324336-- |
325337--
@@ -354,12 +366,14 @@ guardTableIsOpen Table{..} =
354366newTableWith ::
355367 (MonadState Model m , C k v b )
356368 => TableConfig
369+ -> IsUnionDescendant
357370 -> Model. Table k v b
358371 -> m (Table k v b )
359- newTableWith config tbl = state $ \ Model {.. } ->
372+ newTableWith config isUnionDescendant tbl = state $ \ Model {.. } ->
360373 let table = Table {
361374 tableID = nextID
362375 , config
376+ , isUnionDescendant
363377 }
364378 someTable = toSomeTable tbl
365379 tables' = Map. insert nextID (0 , someTable) tables
@@ -549,10 +563,11 @@ invalidateBlobRefs Table{..} = do
549563-------------------------------------------------------------------------------}
550564
551565data Snapshot = Snapshot
552- { snapshotConfig :: TableConfig
553- , snapshotLabel :: SnapshotLabel
554- , snapshotTable :: SomeTable
555- , snapshotCorrupted :: Bool
566+ { snapshotConfig :: TableConfig
567+ , snapshotLabel :: SnapshotLabel
568+ , snapshotTable :: SomeTable
569+ , snapshotIsUnionDescendant :: IsUnionDescendant
570+ , snapshotCorrupted :: Bool
556571 }
557572 deriving stock Show
558573
@@ -570,8 +585,13 @@ createSnapshot label name t@Table{..} = do
570585 snaps <- gets snapshots
571586 when (Map. member name snaps) $
572587 throwError ErrSnapshotExists
588+ let snap =
589+ Snapshot
590+ config label
591+ (toSomeTable $ Model. snapshot table)
592+ isUnionDescendant False
573593 modify (\ m -> m {
574- snapshots = Map. insert name ( Snapshot config label (toSomeTable $ Model. snapshot table) False ) (snapshots m)
594+ snapshots = Map. insert name snap (snapshots m)
575595 })
576596
577597openSnapshot ::
@@ -588,7 +608,7 @@ openSnapshot label name = do
588608 case Map. lookup name snaps of
589609 Nothing ->
590610 throwError ErrSnapshotDoesNotExist
591- Just (Snapshot conf label' tbl corrupted) -> do
611+ Just (Snapshot conf label' tbl snapshotIsUnion corrupted) -> do
592612 when corrupted $
593613 throwError DefaultErrSnapshotCorrupted
594614 when (label /= label') $
@@ -603,7 +623,7 @@ openSnapshot label name = do
603623 -- test setup, and so we use @error@ instead of @throwError@.
604624 error " openSnapshot: snapshot opened at wrong type"
605625 Just table' ->
606- newTableWith conf table'
626+ newTableWith conf snapshotIsUnion table'
607627
608628-- To match the implementation of the real table, this should not corrupt the
609629-- snapshot if there are _no non-empty files_; however, since there are no such
@@ -618,7 +638,7 @@ corruptSnapshot name = do
618638 then throwError ErrSnapshotDoesNotExist
619639 else modify $ \ m -> m {snapshots = Map. adjust corruptSnapshotEntry name snapshots}
620640 where
621- corruptSnapshotEntry (Snapshot c l t _) = Snapshot c l t True
641+ corruptSnapshotEntry (Snapshot c l t u _) = Snapshot c l t u True
622642
623643deleteSnapshot ::
624644 (MonadState Model m , MonadError Err m )
@@ -652,7 +672,7 @@ duplicate ::
652672 -> m (Table k v b )
653673duplicate t@ Table {.. } = do
654674 table <- snd <$> guardTableIsOpen t
655- newTableWith config $ Model. duplicate table
675+ newTableWith config isUnionDescendant $ Model. duplicate table
656676
657677{- ------------------------------------------------------------------------------
658678 Cursor
@@ -733,6 +753,14 @@ guardCursorIsOpen Cursor{..} =
733753 Table union
734754-------------------------------------------------------------------------------}
735755
756+ -- Is this a (descendant of a) union table?
757+ --
758+ -- This is important for invalidating blob references: if a table is a
759+ -- (descendant of a) union table, then 'supplyUnionCredits' can invalidate blob
760+ -- references.
761+ data IsUnionDescendant = IsUnionDescendant | IsNotUnionDescendant
762+ deriving stock (Show , Eq )
763+
736764union ::
737765 ( MonadState Model m
738766 , MonadError Err m
@@ -745,7 +773,7 @@ union ::
745773union r th1 th2 = do
746774 (_, t1) <- guardTableIsOpen th1
747775 (_, t2) <- guardTableIsOpen th2
748- newTableWith TableConfig $ Model. union r t1 t2
776+ newTableWith TableConfig IsUnionDescendant $ Model. union r t1 t2
749777
750778unions ::
751779 ( MonadState Model m
@@ -759,4 +787,68 @@ unions r tables = do
759787 tables' <- forM tables $ \ table -> do
760788 (_, table') <- guardTableIsOpen table
761789 pure table'
762- newTableWith TableConfig $ Model. unions r tables'
790+ newTableWith TableConfig IsUnionDescendant $ Model. unions r tables'
791+
792+ -- | The model can not accurately predict union debt without considerable
793+ -- knowledge about the implementation of /real/ tables. Therefore the model
794+ -- considers unions to be finished right away, and the resulting debt will
795+ -- always be 0.
796+ remainingUnionDebt ::
797+ ( MonadState Model m
798+ , MonadError Err m
799+ , C k v b
800+ )
801+ => Table k v b
802+ -> m UnionDebt
803+ remainingUnionDebt t = do
804+ (_updc, _table) <- guardTableIsOpen t
805+ pure (UnionDebt 0 )
806+
807+ -- | The union debt is always 0, so supplying union credits has no effect on the
808+ -- tables, except for invalidating its blob references in some cases.
809+ --
810+ -- In the /real/ implementation, blob references can be associated with a run in
811+ -- a regular level, or in a union level. In the former case, only updates can
812+ -- invalidate the blob reference. In the latter case, only supplying union
813+ -- credits can invalidate the blob reference.
814+ --
815+ -- Without considerable knowledge about the /real/ implementation, the model can
816+ -- not /always/ accurately predict which of two cases a blob reference belongs
817+ -- to. However, there is one case where a table is guaranteed not to contain a
818+ -- union level: if the table is /not/ a (descendant of a) union table.
819+ --
820+ -- There is another caveat: without considerable knowledge about the real
821+ -- implementation, the model can not accurately predict after how many supplied
822+ -- union credits /real/ blob references are invalidated. Therefore, we model
823+ -- invalidation conservatively in a similar way to 'updates': any supply of
824+ -- @>=1@ union credits is enough to invalidate union blob references.
825+ --
826+ -- To summarise, 'supplyUnionCredits' will invalidate blob references associated
827+ -- with the input table if:
828+ --
829+ -- * The table is a (descendant of a) union table
830+ --
831+ -- * The number of supplied union credits is at least 1.
832+ --
833+ -- TODO: in the real implementation, supplying union credits can invalidate blob
834+ -- references for other tables if they share merging runs in their union levels.
835+ -- For example, supplying union credits to a duplicate of a (descendant of a)
836+ -- union table can invalidate blob references for both the original and
837+ -- duplicate table.
838+ supplyUnionCredits ::
839+ ( MonadState Model m
840+ , MonadError Err m
841+ , C k v b
842+ )
843+ => Table k v b
844+ -> UnionCredits
845+ -> m UnionCredits
846+ supplyUnionCredits t@ Table {.. } c@ (UnionCredits credits)
847+ | credits <= 0 = pure c
848+ | otherwise = do
849+ (updc, table) <- guardTableIsOpen t
850+ when (isUnionDescendant == IsUnionDescendant ) $
851+ modify (\ m -> m {
852+ tables = Map. insert tableID (updc + 1 , toSomeTable table) (tables m)
853+ })
854+ pure c
0 commit comments