Skip to content

Commit cc51280

Browse files
authored
Merge pull request #632 from IntersectMBO/wenkokke/mkSnapshotName
fix: replace mkSnapshotName that returns Maybe with toSnapshotName that errors
2 parents 9c94a0d + ffaaf11 commit cc51280

File tree

10 files changed

+105
-57
lines changed

10 files changed

+105
-57
lines changed

bench/macro/lsm-tree-bench-wp8.hs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,7 @@ doSetup' gopts opts = do
411411

412412
hasBlockIO <- FsIO.ioHasBlockIO hasFS FS.defaultIOCtxParams
413413

414-
name <- maybe (fail "invalid snapshot name") return $
415-
LSM.mkSnapshotName "bench"
414+
let name = LSM.toSnapshotName "bench"
416415

417416
LSM.withSession (mkTracer gopts) hasFS hasBlockIO (FS.mkFsPath []) $ \session -> do
418417
tbl <- LSM.new @IO @K @V @B session (mkTableConfigSetup gopts opts benchTableConfig)
@@ -574,8 +573,7 @@ doRun gopts opts = do
574573

575574
hasBlockIO <- FsIO.ioHasBlockIO hasFS FS.defaultIOCtxParams
576575

577-
name <- maybe (fail "invalid snapshot name") return $
578-
LSM.mkSnapshotName "bench"
576+
let name = LSM.toSnapshotName "bench"
579577

580578
LSM.withSession (mkTracer gopts) hasFS hasBlockIO (FS.mkFsPath []) $ \session ->
581579
withLatencyHandle $ \h -> do

src/Database/LSMTree.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
module Database.LSMTree (
1111
-- * Exceptions
1212
Common.LSMTreeError (..)
13+
, Common.InvalidSnapshotNameError (..)
1314

1415
-- * Tracing
1516
, Common.LSMTreeTrace (..)
@@ -67,7 +68,8 @@ module Database.LSMTree (
6768

6869
-- * Durability (snapshots)
6970
, SnapshotName
70-
, Common.mkSnapshotName
71+
, Common.isValidSnapshotName
72+
, Common.toSnapshotName
7173
, Common.SnapshotLabel (..)
7274
, createSnapshot
7375
, openSnapshot

src/Database/LSMTree/Common.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module Database.LSMTree.Common (
33
IOLike
44
-- * Exceptions
55
, Internal.LSMTreeError (..)
6+
, Internal.InvalidSnapshotNameError (..)
67
-- * Tracing
78
, Internal.LSMTreeTrace (..)
89
, Internal.TableTrace (..)
@@ -23,7 +24,8 @@ module Database.LSMTree.Common (
2324
, listSnapshots
2425
-- ** Snapshot names
2526
, Internal.SnapshotName
26-
, Internal.mkSnapshotName
27+
, Internal.toSnapshotName
28+
, Internal.isValidSnapshotName
2729
-- * Blob references
2830
, BlobRef (..)
2931
-- * Table configuration

src/Database/LSMTree/Internal.hs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,15 +1394,14 @@ listSnapshots sesh = do
13941394
snaps <- mapM (checkSnapshot hfs root) $ Set.toList contents
13951395
pure $ catMaybes snaps
13961396
where
1397-
checkSnapshot hfs root s =
1398-
case Paths.mkSnapshotName s of
1399-
Nothing -> pure Nothing
1400-
Just snap -> do
1401-
-- check that it is a directory
1402-
b <- FS.doesDirectoryExist hfs
1403-
(Paths.getNamedSnapshotDir $ Paths.namedSnapshotDir root snap)
1404-
if b then pure $ Just snap
1405-
else pure $ Nothing
1397+
checkSnapshot hfs root s = do
1398+
-- TODO: rethrow 'ErrSnapshotNameInvalid' as 'ErrSnapshotDirCorrupted'
1399+
let snap = Paths.toSnapshotName s
1400+
-- check that it is a directory
1401+
b <- FS.doesDirectoryExist hfs
1402+
(Paths.getNamedSnapshotDir $ Paths.namedSnapshotDir root snap)
1403+
if b then pure $ Just snap
1404+
else pure $ Nothing
14061405

14071406
{-------------------------------------------------------------------------------
14081407
Multiple writable tables

src/Database/LSMTree/Internal/Paths.hs

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ module Database.LSMTree.Internal.Paths (
1616
, tableBlobPath
1717
-- * Snapshot name
1818
, SnapshotName
19-
, mkSnapshotName
19+
, isValidSnapshotName
20+
, InvalidSnapshotNameError (..)
21+
, toSnapshotName
2022
-- * Run paths
2123
, RunFsPaths (..)
2224
, pathsForRunFiles
@@ -52,10 +54,10 @@ module Database.LSMTree.Internal.Paths (
5254

5355
import Control.Applicative (Applicative (..))
5456
import Control.DeepSeq (NFData (..))
57+
import Control.Exception.Base (Exception, throw)
5558
import qualified Data.ByteString.Char8 as BS
5659
import Data.Foldable (toList)
5760
import qualified Data.Map as Map
58-
import Data.Maybe (fromMaybe)
5961
import Data.String (IsString (..))
6062
import Data.Traversable (for)
6163
import qualified Database.LSMTree.Internal.CRC32C as CRC
@@ -92,7 +94,7 @@ snapshotsDir (SessionRoot dir) = dir </> mkFsPath ["snapshots"]
9294
newtype NamedSnapshotDir = NamedSnapshotDir { getNamedSnapshotDir :: FsPath }
9395

9496
namedSnapshotDir :: SessionRoot -> SnapshotName -> NamedSnapshotDir
95-
namedSnapshotDir root (MkSnapshotName name) =
97+
namedSnapshotDir root (SnapshotName name) =
9698
NamedSnapshotDir (snapshotsDir root </> mkFsPath [name])
9799

98100
newtype SnapshotMetaDataFile = SnapshotMetaDataFile FsPath
@@ -111,47 +113,90 @@ snapshotMetaDataChecksumFile (NamedSnapshotDir dir) =
111113
Snapshot name
112114
-------------------------------------------------------------------------------}
113115

114-
newtype SnapshotName = MkSnapshotName FilePath
116+
newtype SnapshotName = SnapshotName FilePath
115117
deriving stock (Eq, Ord)
116118

117119
instance Show SnapshotName where
118-
showsPrec d (MkSnapshotName p) = showsPrec d p
120+
showsPrec d (SnapshotName p) = showsPrec d p
119121

120-
-- | This instance uses 'mkSnapshotName', so all the restrictions on snap shot names apply here too. An invalid snapshot name will lead to an error.
122+
-- | The given string must satsify 'isValidSnapshotName'.
123+
-- Otherwise, 'fromString' throws an 'InvalidSnapshotNameError'.
121124
instance IsString SnapshotName where
122-
fromString s = fromMaybe bad (mkSnapshotName s)
123-
where
124-
bad = error ("SnapshotName.fromString: invalid name " ++ show s)
125+
fromString :: String -> SnapshotName
126+
fromString = toSnapshotName
125127

126-
-- | Create snapshot name.
128+
data InvalidSnapshotNameError
129+
= ErrSnapshotNameInvalid !String
130+
deriving stock (Show)
131+
deriving anyclass (Exception)
132+
133+
-- | Check if a 'String' would be a valid snapshot name.
134+
--
135+
-- Snapshot names consist of lowercase characters, digits, dashes @-@,
136+
-- and underscores @_@, and must be between 1 and 64 characters long.
137+
-- >>> isValidSnapshotName "main"
138+
-- True
127139
--
128-
-- The name may consist of lowercase characters, digits, dashes @-@ and underscores @_@.
129-
-- It must be non-empty and less than 65 characters long.
130-
-- It may not be a special filepath name.
140+
-- >>> isValidSnapshotName "temporary-123-test_"
141+
-- True
131142
--
132-
-- >>> mkSnapshotName "main"
133-
-- Just "main"
143+
-- >>> isValidSnapshotName "UPPER"
144+
-- False
145+
-- >>> isValidSnapshotName "dir/dot.exe"
146+
-- False
147+
-- >>> isValidSnapshotName ".."
148+
-- False
149+
-- >>> isValidSnapshotName "\\"
150+
-- False
151+
-- >>> isValidSnapshotName ""
152+
-- False
153+
-- >>> isValidSnapshotName (replicate 100 'a')
154+
-- False
134155
--
135-
-- >>> mkSnapshotName "temporary-123-test_"
136-
-- Just "temporary-123-test_"
156+
-- Snapshot names must be valid directory on both POSIX and Windows.
157+
-- This rules out the following reserved file and directory names on Windows:
137158
--
138-
-- >>> map mkSnapshotName ["UPPER", "dir/dot.exe", "..", "\\", "com1", "", replicate 100 'a']
139-
-- [Nothing,Nothing,Nothing,Nothing,Nothing,Nothing,Nothing]
159+
-- >>> isValidSnapshotName "con"
160+
-- False
161+
-- >>> isValidSnapshotName "prn"
162+
-- False
163+
-- >>> isValidSnapshotName "aux"
164+
-- False
165+
-- >>> isValidSnapshotName "nul"
166+
-- False
167+
-- >>> isValidSnapshotName "com1" -- "com2", "com3", etc.
168+
-- False
169+
-- >>> isValidSnapshotName "lpt1" -- "lpt2", "lpt3", etc.
170+
-- False
140171
--
141-
mkSnapshotName :: String -> Maybe SnapshotName
142-
mkSnapshotName s
143-
| all isValid s
144-
, len > 0
145-
, len < 65
146-
, System.FilePath.Posix.isValid s
147-
, System.FilePath.Windows.isValid s
148-
= Just (MkSnapshotName s)
149-
150-
| otherwise
151-
= Nothing
172+
-- See, e.g., [the VBA docs for the "Bad file name or number" error](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/bad-file-name-or-number-error-52).
173+
isValidSnapshotName :: String -> Bool
174+
isValidSnapshotName str =
175+
and [ all isValidChar str
176+
, strLength >= 1
177+
, strLength <= 64
178+
, System.FilePath.Posix.isValid str
179+
, System.FilePath.Windows.isValid str
180+
]
152181
where
153-
len = length s
154-
isValid c = ('a' <= c && c <= 'z') || ('0' <= c && c <= '9' ) || c `elem` "-_"
182+
strLength :: Int
183+
strLength = length str
184+
isValidChar :: Char -> Bool
185+
isValidChar c = ('a' <= c && c <= 'z') || ('0' <= c && c <= '9' ) || c `elem` "-_"
186+
187+
-- | Create snapshot name.
188+
--
189+
-- The given string must satsify 'isValidSnapshotName'.
190+
--
191+
-- Throws the following exceptions:
192+
--
193+
-- ['InvalidSnapshotNameError']:
194+
-- If the given string is not a valid snapshot name.
195+
--
196+
toSnapshotName :: String -> SnapshotName
197+
toSnapshotName str
198+
| isValidSnapshotName str = SnapshotName str
199+
| otherwise = throw (ErrSnapshotNameInvalid str)
155200

156201
{-------------------------------------------------------------------------------
157202
Table paths

src/Database/LSMTree/Monoidal.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
module Database.LSMTree.Monoidal (
2626
-- * Exceptions
2727
Common.LSMTreeError (..)
28+
, Common.InvalidSnapshotNameError (..)
2829

2930
-- * Tracing
3031
, Common.LSMTreeTrace (..)
@@ -84,7 +85,8 @@ module Database.LSMTree.Monoidal (
8485

8586
-- * Durability (snapshots)
8687
, SnapshotName
87-
, Common.mkSnapshotName
88+
, Common.toSnapshotName
89+
, Common.isValidSnapshotName
8890
, Common.SnapshotLabel (..)
8991
, createSnapshot
9092
, openSnapshot

src/Database/LSMTree/Normal.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
module Database.LSMTree.Normal (
2525
-- * Exceptions
2626
Common.LSMTreeError (..)
27+
, Common.InvalidSnapshotNameError (..)
2728

2829
-- * Tracing
2930
, Common.LSMTreeTrace (..)
@@ -85,7 +86,8 @@ module Database.LSMTree.Normal (
8586

8687
-- * Durability (snapshots)
8788
, SnapshotName
88-
, Common.mkSnapshotName
89+
, Common.toSnapshotName
90+
, Common.isValidSnapshotName
8991
, Common.SnapshotLabel (..)
9092
, createSnapshot
9193
, openSnapshot

test/Test/Database/LSMTree/Class.hs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@ import Data.Foldable (toList)
1313
import Data.Functor.Compose (Compose (..))
1414
import qualified Data.List as List
1515
import qualified Data.List.NonEmpty as NE
16-
import Data.Maybe (fromMaybe)
1716
import qualified Data.Proxy as Proxy
1817
import qualified Data.Vector as V
1918
import qualified Data.Vector.Algorithms.Merge as VA
2019
import Data.Word (Word64)
2120
import qualified Database.LSMTree as R
2221
import Database.LSMTree.Class
23-
import Database.LSMTree.Common (mkSnapshotName)
22+
import Database.LSMTree.Common (toSnapshotName)
2423
import Database.LSMTree.Extras.Generators ()
2524
import qualified Database.LSMTree.Model.IO as ModelIO
2625
import qualified System.FS.API as FS
@@ -682,7 +681,7 @@ prop_snapshotNoChanges h ups ups' testKeys = ioProperty $ do
682681

683682
res <- lookupsWithBlobs tbl1 ses $ V.fromList testKeys
684683

685-
let name = fromMaybe (error "invalid name") $ mkSnapshotName "foo"
684+
let name = toSnapshotName "foo"
686685

687686
createSnapshot label name tbl1
688687
updates tbl1 (V.fromList ups')
@@ -701,7 +700,7 @@ prop_snapshotNoChanges2 :: forall h.
701700
-> [(Key, Update Value Blob)] -> [Key] -> Property
702701
prop_snapshotNoChanges2 h ups ups' testKeys = ioProperty $ do
703702
withSessionAndTableNew h ups $ \sess tbl0 -> do
704-
let name = fromMaybe (error "invalid name") $ mkSnapshotName "foo"
703+
let name = toSnapshotName "foo"
705704
createSnapshot label name tbl0
706705

707706
withTableFromSnapshot @h sess label name $ \tbl1 ->

test/Test/Database/LSMTree/Internal/Snapshot/FS.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import Control.Monad.Class.MonadThrow
88
import Control.Monad.IOSim (runSimOrThrow)
99
import Control.Tracer
1010
import Data.Bifunctor (Bifunctor (..))
11-
import Data.Maybe (fromJust)
1211
import qualified Data.Vector as V
1312
import Data.Word
1413
import Database.LSMTree.Extras (showPowersOf10)
@@ -216,7 +215,7 @@ prop_flipSnapshotBit (Positive (Small bufferSize)) es pickFileBit =
216215
resolve (SerialisedValue x) (SerialisedValue y) =
217216
SerialisedValue (x <> y)
218217

219-
snapName = fromJust $ mkSnapshotName "snap"
218+
snapName = toSnapshotName "snap"
220219
snapLabel = SnapshotLabel "label"
221220

222221
createSnap t =

test/Test/Database/LSMTree/StateMachine.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ import Data.List.NonEmpty (NonEmpty (..))
7878
import qualified Data.List.NonEmpty as NE
7979
import Data.Map.Strict (Map)
8080
import qualified Data.Map.Strict as Map
81-
import Data.Maybe (catMaybes, fromJust, fromMaybe, isJust)
81+
import Data.Maybe (catMaybes, fromMaybe, isJust)
8282
import Data.Monoid (First (..))
8383
import Data.Primitive.MutVar
8484
import Data.Set (Set)
@@ -1866,7 +1866,7 @@ arbitraryActionWithVars _ label ctx (ModelState st _stats) =
18661866
then Left snapshotname -- used
18671867
else Right snapshotname -- unused
18681868
| name <- ["snap1", "snap2", "snap3" ]
1869-
, let snapshotname = fromJust (R.mkSnapshotName name)
1869+
, let snapshotname = R.toSnapshotName name
18701870
]
18711871

18721872
genActionsSession :: [(Int, Gen (Any (LockstepAction (ModelState h))))]

0 commit comments

Comments
 (0)