1
- -- TODO: Disable this warning once LSMTreeError is split.
2
- {-# OPTIONS_GHC -Wno-incomplete-patterns #-}
3
-
4
1
{- |
5
2
Module : Database.LSMTree.Simple
6
3
Copyright : (c) 2023, Input Output Global, Inc. (IOG)
@@ -126,7 +123,7 @@ module Database.LSMTree.Simple (
126
123
TableClosedError (.. ),
127
124
TableCorruptedError (.. ),
128
125
TableTooLargeError (.. ),
129
- TableNotCompatibleError (.. ),
126
+ TableUnionNotCompatibleError (.. ),
130
127
SnapshotExistsError (.. ),
131
128
SnapshotDoesNotExistError (.. ),
132
129
SnapshotCorruptedError (.. ),
@@ -137,7 +134,7 @@ module Database.LSMTree.Simple (
137
134
) where
138
135
139
136
import Control.Exception.Base (Exception , SomeException (.. ), bracket ,
140
- mapException )
137
+ mapException , assert )
141
138
import Control.Monad (join )
142
139
import Data.Bifunctor (Bifunctor (.. ))
143
140
import Data.Kind (Type )
@@ -150,7 +147,7 @@ import Database.LSMTree.Internal (BlobRefInvalidError (..),
150
147
SnapshotCorruptedError (.. ),
151
148
SnapshotDoesNotExistError (.. ), SnapshotExistsError (.. ),
152
149
SnapshotNotCompatibleError (.. ), TableClosedError (.. ),
153
- TableCorruptedError (.. ), TableNotCompatibleError (.. ),
150
+ TableCorruptedError (.. ), TableUnionNotCompatibleError (.. ),
154
151
TableTooLargeError (.. ))
155
152
import qualified Database.LSMTree.Internal as Internal
156
153
import Database.LSMTree.Internal.Config
@@ -238,10 +235,10 @@ To prevent resource and memory leaks due to asynchronous exceptions,
238
235
it is recommended to use the [bracketed](#bracketed) functions whenever
239
236
possible, and otherwise:
240
237
241
- * Run functions that allocate, use, and release a resource with asynchronous
238
+ * Run functions that allocate and release a resource with asynchronous
242
239
exceptions masked.
243
- * Pair functions that allocate a resource with a masked cleanup function,
244
- e.g., using 'bracket'.
240
+ * Ensure that every use allocate operation is followed by the corresponding release
241
+ operation even in the presence of asynchronous exceptions, e.g., using 'bracket'.
245
242
-}
246
243
247
244
--------------------------------------------------------------------------------
@@ -269,12 +266,18 @@ the table by using 'duplicate' and performing the read operations on the duplica
269
266
However, this requires that the 'duplicate' operation /happens before/ the subsequent
270
267
writes, as it is a race to duplicate concurrently with any writes.
271
268
As this package does not provide any construct for synchronisation or atomic
272
- operations, this ordering of operations must be accomplished by other means.
269
+ operations, this ordering of operations must be accomplished by the user through
270
+ other means.
273
271
274
- An 'Cursor' creates a stable view of a table and can safely be read while
272
+ A 'Cursor' creates a stable view of a table and can safely be read while
275
273
modifying the original table. However, reading the 'next' key\/value pair from
276
274
a cursor locks the view, so concurrent reads on the same cursor block.
277
275
This is because 'next' updates the cursor's current position.
276
+
277
+ Session handles may be used concurrently from multiple Haskell threads,
278
+ but concurrent use of read and write operations may introduce races.
279
+ Specifically, it is a race to use `listSnapshots` and `deleteSnapshots`
280
+ with the same session handle concurrently.
278
281
-}
279
282
280
283
--------------------------------------------------------------------------------
@@ -314,11 +317,11 @@ newtype Session = Session {unSession :: Internal.Session IO HandleIO}
314
317
{- |
315
318
Throws the following exceptions:
316
319
317
- ['SessionDoesNotExistError ']:
320
+ ['SessionDirDoesNotExistError ']:
318
321
If the session directory does not exist.
319
- ['SessionLockedError ']:
322
+ ['SessionDirLockedError ']:
320
323
If the session directory is locked by another process.
321
- ['SessionCorruptedError ']:
324
+ ['SessionDirCorruptedError ']:
322
325
If the session directory is malformed.
323
326
-}
324
327
withSession ::
@@ -338,11 +341,11 @@ withSession dir action = do
338
341
{- |
339
342
Throws the following exceptions:
340
343
341
- ['SessionDoesNotExistError ']:
344
+ ['SessionDirDoesNotExistError ']:
342
345
If the session directory does not exist.
343
- ['SessionLockedError ']:
346
+ ['SessionDirLockedError ']:
344
347
If the session directory is locked by another process.
345
- ['SessionCorruptedError ']:
348
+ ['SessionDirCorruptedError ']:
346
349
If the session directory is malformed.
347
350
-}
348
351
openSession ::
@@ -365,9 +368,7 @@ closeSession = Internal.closeSession . unSession
365
368
-- Tables
366
369
--------------------------------------------------------------------------------
367
370
368
- {- | A table is a handle to an LSM-tree key\/value store.
369
-
370
- Each table is a handle to an individual key\/value store with both in-memory and on-disk parts.
371
+ {- | A table is a handle to an individual LSM-tree key\/value store with both in-memory and on-disk parts.
371
372
372
373
__Warning:__ Tables are ephemeral. Once you close a table, its data is lost forever. To persist tables, use [snapshots](#g:snapshots).
373
374
-}
@@ -382,7 +383,7 @@ data Table k v
382
383
383
384
This function is exception-safe for both synchronous and asynchronous exceptions.
384
385
385
- It is recommended to use this function instead of 'new ' and 'closeTable'.
386
+ It is recommended to use this function instead of 'newTable ' and 'closeTable'.
386
387
387
388
Throws the following exceptions:
388
389
@@ -540,8 +541,9 @@ rangeLookup ::
540
541
Range k ->
541
542
IO (Vector (k , v ))
542
543
rangeLookup (Table table) range =
543
- Internal. rangeLookup const (Internal. serialiseKey <$> range) table $ \ k v ! _b ->
544
- (Internal. deserialiseKey k, Internal. deserialiseValue v)
544
+ Internal. rangeLookup const (Internal. serialiseKey <$> range) table $ \ ! k ! v ! b ->
545
+ assert (null b) $
546
+ (Internal. deserialiseKey k, Internal. deserialiseValue v)
545
547
546
548
--------------------------------------------------------------------------------
547
549
-- Updates
@@ -672,6 +674,9 @@ withDuplicate table =
672
674
673
675
{- | Duplicate a table.
674
676
677
+ The duplicate is an independent copy of the given table.
678
+ The duplicate is unaffected by subsequent updates to the given table and vice versa.
679
+
675
680
__Warning:__ The duplicate must be independently closed using 'closeTable'.
676
681
677
682
Throws the following exceptions:
@@ -697,16 +702,16 @@ This function is exception-safe for both synchronous and asynchronous exceptions
697
702
698
703
It is recommended to use this function instead of 'union' and 'closeTable'.
699
704
700
- __Warning:__ Both input tables must be from the same 'Session' and have the same configuration parameters .
705
+ __Warning:__ Both input tables must be from the same 'Session'.
701
706
702
707
Throws the following exceptions:
703
708
704
709
['SessionClosedError']:
705
710
If the session is closed.
706
711
['TableClosedError']:
707
712
If the table is closed.
708
- ['TableNotCompatibleError ']:
709
- If both tables are not from the same 'Session' or have different configuration parameters .
713
+ ['TableUnionNotCompatibleError ']:
714
+ If both tables are not from the same 'Session'.
710
715
-}
711
716
withUnion ::
712
717
Table k v ->
@@ -726,18 +731,20 @@ withUnions tables =
726
731
727
732
{- | Create a table that contains the union of the entries of the given tables.
728
733
734
+ The union of two tables is left-biased.
735
+
729
736
__Warning:__ The new table must be independently closed using 'closeTable'.
730
737
731
- __Warning:__ Both input tables must be from the same 'Session' and have the same configuration parameters .
738
+ __Warning:__ Both input tables must be from the same 'Session'.
732
739
733
740
Throws the following exceptions:
734
741
735
742
['SessionClosedError']:
736
743
If the session is closed.
737
744
['TableClosedError']:
738
745
If the table is closed.
739
- ['TableNotCompatibleError ']:
740
- If both tables are not from the same 'Session' or have different configuration parameters .
746
+ ['TableUnionNotCompatibleError ']:
747
+ If both tables are not from the same 'Session'.
741
748
-}
742
749
union ::
743
750
Table k v ->
@@ -1008,10 +1015,11 @@ next iterator = do
1008
1015
{- | Read the next batch of table entries from the cursor.
1009
1016
1010
1017
The size of the batch is /at most/ equal to the given number, but may contain fewer entries.
1018
+ In practice, this occurs only when the cursor reaches the end of the table.
1011
1019
1012
1020
The following property holds:
1013
1021
1014
- prop> take n cursor = catMaybes <$> sequence (replicate n (next cursor) )
1022
+ prop> take n cursor = catMaybes <$> replicateM n (next cursor)
1015
1023
1016
1024
Throws the following exceptions:
1017
1025
@@ -1025,8 +1033,9 @@ take ::
1025
1033
Cursor k v ->
1026
1034
IO (Vector (k , v ))
1027
1035
take n (Cursor cursor) =
1028
- Internal. readCursor const n cursor $ \ ! k ! v ! _b ->
1029
- (Internal. deserialiseKey k, Internal. deserialiseValue v)
1036
+ Internal. readCursor const n cursor $ \ ! k ! v ! b ->
1037
+ assert (null b) $
1038
+ (Internal. deserialiseKey k, Internal. deserialiseValue v)
1030
1039
1031
1040
{- | Variant of 'take' that accepts an additional predicate to determine whether or not to continue reading.
1032
1041
@@ -1048,6 +1057,7 @@ takeWhile ::
1048
1057
Cursor k v ->
1049
1058
IO (Vector (k , v ))
1050
1059
takeWhile n p (Cursor cursor) =
1060
+ -- TODO: Rewrite to use a variant of 'readCursorWhile' that works without the maximum batch size.
1051
1061
Internal. readCursorWhile const (p . Internal. deserialiseKey) n cursor $ \ ! k ! v ! _b ->
1052
1062
(Internal. deserialiseKey k, Internal. deserialiseValue v)
1053
1063
0 commit comments