{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
module Ouroboros.Consensus.Storage.LeiosDB.Impl.SQLite.Connection
(
initialiseLeiosDbFiles
, openRawConnection
, withReadOnlyConn
, closeChecked
, Conn (..)
, mkConn
, finalizeConnStmts
, closeConn
, dbWithWriteTransaction
) where
import Control.Concurrent.Class.MonadSTM.Strict (StrictTVar)
import Control.Exception (throwIO)
import Control.Monad (void)
import Control.Monad.Class.MonadThrow (bracket, onException)
import Control.Tracer (Tracer)
import Data.Foldable (traverse_)
import Data.String (fromString)
import Database.SQLite3
( SQLOpenFlag (..)
, SQLVFS (..)
, open2
)
import qualified Database.SQLite3.Direct as DB
import GHC.Stack (HasCallStack)
import qualified GHC.Stack
import Ouroboros.Consensus.Storage.LeiosDB.Exception
( LeiosDbException (..)
, LeiosDbFailure (..)
)
import Ouroboros.Consensus.Storage.LeiosDB.Impl.SQLite.Primitives
import Ouroboros.Consensus.Storage.LeiosDB.Impl.SQLite.Schema
import Ouroboros.Consensus.Storage.LeiosDB.Impl.SQLite.Statements
import Ouroboros.Consensus.Storage.LeiosDB.Trace (LeiosDbStats (..), TraceLeiosDb (..))
withReadOnlyConn :: HasCallStack => FilePath -> (DB.Database -> IO a) -> IO a
withReadOnlyConn :: forall a. HasCallStack => FilePath -> (Database -> IO a) -> IO a
withReadOnlyConn FilePath
dbPath =
IO Database -> (Database -> IO ()) -> (Database -> IO a) -> IO a
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
forall (m :: * -> *) a b c.
MonadThrow m =>
m a -> (a -> m b) -> (a -> m c) -> m c
bracket (HasCallStack => FilePath -> IO Database
FilePath -> IO Database
openReadOnlyRawConnection FilePath
dbPath) (IO (Either Error ()) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO (Either Error ()) -> IO ())
-> (Database -> IO (Either Error ())) -> Database -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Database -> IO (Either Error ())
DB.close)
openReadOnlyRawConnection :: HasCallStack => FilePath -> IO DB.Database
openReadOnlyRawConnection :: HasCallStack => FilePath -> IO Database
openReadOnlyRawConnection FilePath
dbPath = do
db <- Text -> [SQLOpenFlag] -> SQLVFS -> IO Database
open2 (FilePath -> Text
forall a. IsString a => FilePath -> a
fromString FilePath
dbPath) [SQLOpenFlag
SQLOpenReadOnly] SQLVFS
SQLVFSDefault
dbExec db "pragma mmap_size = 268435500;"
pure db
initialiseLeiosDbFiles :: HasCallStack => FilePath -> FilePath -> IO ()
initialiseLeiosDbFiles :: HasCallStack => FilePath -> FilePath -> IO ()
initialiseLeiosDbFiles FilePath
volPath FilePath
immPath = do
FilePath -> FilePath -> IO ()
initialiseFile FilePath
volPath FilePath
sql_schema_vol
FilePath -> FilePath -> IO ()
initialiseFile FilePath
immPath FilePath
sql_schema_imm
where
initialiseFile :: FilePath -> FilePath -> IO ()
initialiseFile FilePath
path FilePath
ddl =
IO Database -> (Database -> IO ()) -> (Database -> IO ()) -> IO ()
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
forall (m :: * -> *) a b c.
MonadThrow m =>
m a -> (a -> m b) -> (a -> m c) -> m c
bracket
(Text -> [SQLOpenFlag] -> SQLVFS -> IO Database
open2 (FilePath -> Text
forall a. IsString a => FilePath -> a
fromString FilePath
path) [SQLOpenFlag
SQLOpenReadWrite, SQLOpenFlag
SQLOpenCreate] SQLVFS
SQLVFSDefault)
(IO (Either Error ()) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO (Either Error ()) -> IO ())
-> (Database -> IO (Either Error ())) -> Database -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Database -> IO (Either Error ())
DB.close)
((Database -> IO ()) -> IO ()) -> (Database -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Database
db -> do
(Utf8 -> IO ()) -> [Utf8] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ (HasCallStack => Database -> Utf8 -> IO ()
Database -> Utf8 -> IO ()
dbExec Database
db) ([Utf8]
connectionPragmas [Utf8] -> [Utf8] -> [Utf8]
forall a. Semigroup a => a -> a -> a
<> [Utf8]
creationPragmas)
Database -> IO () -> IO ()
forall a. HasCallStack => Database -> IO a -> IO a
dbWithWriteTransactionRaw Database
db (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ HasCallStack => Database -> Utf8 -> IO ()
Database -> Utf8 -> IO ()
dbExec Database
db (FilePath -> Utf8
forall a. IsString a => FilePath -> a
fromString FilePath
ddl)
openRawConnection :: HasCallStack => FilePath -> IO DB.Database
openRawConnection :: HasCallStack => FilePath -> IO Database
openRawConnection FilePath
path = do
db <- Text -> [SQLOpenFlag] -> SQLVFS -> IO Database
open2 (FilePath -> Text
forall a. IsString a => FilePath -> a
fromString FilePath
path) [SQLOpenFlag
SQLOpenReadWrite] SQLVFS
SQLVFSDefault
orCloseOnError db $ traverse_ (dbExec db) connectionPragmas
pure db
connectionPragmas :: [DB.Utf8]
connectionPragmas :: [Utf8]
connectionPragmas =
[
Utf8
"pragma busy_timeout = 1000;"
, Utf8
"pragma synchronous = normal;"
, Utf8
"pragma mmap_size = 268435500;"
,
Utf8
"pragma wal_autocheckpoint = 1000;"
]
creationPragmas :: [DB.Utf8]
creationPragmas :: [Utf8]
creationPragmas =
[
Utf8
"pragma page_size = 4096;"
, Utf8
"pragma journal_mode = WAL;"
]
data Conn = Conn
{ Conn -> Database
conVolDb :: !DB.Database
, Conn -> VolStmts
connVolStmts :: !VolStmts
, Conn -> Tracer IO TraceLeiosDb
connTracer :: !(Tracer IO TraceLeiosDb)
, Conn -> StrictTVar IO LeiosDbStats
connStats :: !(StrictTVar IO LeiosDbStats)
, Conn -> Database
conImmDb :: !DB.Database
, Conn -> ImmStmts
connImmStmts :: !ImmStmts
}
mkConn ::
Tracer IO TraceLeiosDb ->
StrictTVar IO LeiosDbStats ->
DB.Database ->
DB.Database ->
IO Conn
mkConn :: Tracer IO TraceLeiosDb
-> StrictTVar IO LeiosDbStats -> Database -> Database -> IO Conn
mkConn Tracer IO TraceLeiosDb
tracer StrictTVar IO LeiosDbStats
statsVar Database
volDb Database
immDb = do
stmts <- Database -> IO VolStmts
prepareVolStmts Database
volDb
immStmts <- prepareImmStmts immDb `onException` finalizeVolStmts stmts
pure
Conn
{ conVolDb = volDb
, connVolStmts = stmts
, connTracer = tracer
, connStats = statsVar
, conImmDb = immDb
, connImmStmts = immStmts
}
finalizeConnStmts :: Conn -> IO ()
finalizeConnStmts :: Conn -> IO ()
finalizeConnStmts Conn
conn = do
ImmStmts -> IO ()
finalizeImmStmts (Conn -> ImmStmts
connImmStmts Conn
conn)
VolStmts -> IO ()
finalizeVolStmts (Conn -> VolStmts
connVolStmts Conn
conn)
closeConn :: Conn -> IO ()
closeConn :: Conn -> IO ()
closeConn Conn
conn = do
Conn -> IO ()
finalizeConnStmts Conn
conn
HasCallStack => Database -> IO ()
Database -> IO ()
closeChecked (Conn -> Database
conImmDb Conn
conn)
HasCallStack => Database -> IO ()
Database -> IO ()
closeChecked (Conn -> Database
conVolDb Conn
conn)
closeChecked :: HasCallStack => DB.Database -> IO ()
closeChecked :: HasCallStack => Database -> IO ()
closeChecked Database
db =
Database -> IO (Either Error ())
DB.close Database
db IO (Either Error ()) -> (Either Error () -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Left Error
err ->
LeiosDbException -> IO ()
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (LeiosDbException -> IO ()) -> LeiosDbException -> IO ()
forall a b. (a -> b) -> a -> b
$
LeiosDbFailure -> LeiosDbException
LeiosDbException
LeiosDbFailure
{ ldfErrorMessage :: FilePath
ldfErrorMessage = FilePath
"failed to close the connection: " FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> Error -> FilePath
forall a. Show a => a -> FilePath
show Error
err
, ldfCallStack :: FilePath
ldfCallStack = CallStack -> FilePath
GHC.Stack.prettyCallStack CallStack
HasCallStack => CallStack
GHC.Stack.callStack
}
Right () -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
dbWithWriteTransaction :: HasCallStack => Conn -> IO a -> IO a
dbWithWriteTransaction :: forall a. HasCallStack => Conn -> IO a -> IO a
dbWithWriteTransaction Conn{Database
conVolDb :: Conn -> Database
conVolDb :: Database
conVolDb} = Database -> IO a -> IO a
forall a. HasCallStack => Database -> IO a -> IO a
dbWithWriteTransactionRaw Database
conVolDb