packages feed

ipedb-0.1.0.0: src/IpeDb/Eventlog/Index.hs

{-# LANGUAGE OverloadedStrings #-}

module IpeDb.Eventlog.Index (
  -- * High level API for interacting with the database
  withDatabase,
  generateInfoProvDb,
  findOneInfoProv,
  findAllInfoProvs,

  -- * Low level API for interacting with the database
  setupDb,
  setupTables,
  setupIndexing,
  insertInfoProv,
  upsertInfoProvStrings,

  -- * ghc-events specific helpers
  insertInfoProvData,
)
where

import Data.Foldable (traverse_)
import qualified Database.SQLite.Simple as Sqlite
import Database.SQLite.Simple.Types (Only (..))
import qualified GHC.RTS.Events as GhcEvents
import IpeDb.InfoProv as Ipe
import IpeDb.Table as Table
import Data.Text (Text)
import qualified Data.Text as Text

findOneInfoProv :: Sqlite.Connection -> IpeId -> IO (Maybe InfoProv)
findOneInfoProv conn ipeId = do
  r <- Sqlite.query conn findInfoTableQuery (Only ipeId)
  case r of
    [ipe] -> pure $ Just ipe
    _ -> pure $ Nothing

findAllInfoProvs :: Sqlite.Connection -> IO [InfoProv]
findAllInfoProvs conn = do
  Sqlite.query conn findAllInfoTablesQuery ()

generateInfoProvDb :: Sqlite.Connection -> FilePath -> IO ()
generateInfoProvDb conn fp = do
  setupDb conn
  GhcEvents.readEventLogFromFile fp >>= \case
    Left err -> fail err
    Right (GhcEvents.EventLog _h (GhcEvents.Data es)) -> Sqlite.withExclusiveTransaction conn $ do
      insertInfoProvData conn es

setupDb :: Sqlite.Connection -> IO ()
setupDb conn = do
  Sqlite.withExclusiveTransaction conn $ setupTables conn
  setupIndexing conn

withDatabase :: FilePath -> (Sqlite.Connection -> IO a) -> IO a
withDatabase fp act = Sqlite.withConnection fp $ \conn ->
  act conn

-- ----------------------------------------------------------------------------
-- Low Level Sqlite api
-- ----------------------------------------------------------------------------

setupTables :: Sqlite.Connection -> IO ()
setupTables conn = do
  Sqlite.execute_ conn dropStringTableStmt
  Sqlite.execute_ conn dropInfoProvTableStmt
  Sqlite.execute_ conn dropInfoProvTableViewStmt
  Sqlite.execute_ conn stringTableStmt
  Sqlite.execute_ conn infoProvTableStmt
  Sqlite.execute_ conn infoProvTableViewStmt

setupIndexing :: Sqlite.Connection -> IO ()
setupIndexing conn = do
  Sqlite.execute_ conn "PRAGMA synchronous = OFF;"
  Sqlite.execute_ conn "PRAGMA journal_mode = OFF;"
  Sqlite.execute_ conn "PRAGMA temp_store = MEMORY;"
  Sqlite.execute_ conn "PRAGMA locking_mode = EXCLUSIVE;"

insertInfoProv :: Sqlite.Connection -> InfoProv -> IO ()
insertInfoProv conn prov = do
  row <- upsertInfoProvStrings conn prov
  _ <- Sqlite.execute conn insertInfoTableQuery row
  pure ()

upsertInfoProvStrings :: Sqlite.Connection -> InfoProv -> IO InfoProvRow
upsertInfoProvStrings conn prov = do
  let
    (srcLocFile, srcLocRange) = splitGhcSrcLoc prov.srcLoc
  Sqlite.executeMany
    conn
    insertOrIgnoreString
    [ Only prov.tableName
    , Only prov.typeDesc
    , Only prov.label
    , Only prov.moduleName
    , Only srcLocFile
    ]
  [(taId, tyId, labelId, modId, srcLocId)] <-
    Sqlite.query
      conn
      getIpeStrings
      ( prov.typeDesc
      , prov.label
      , prov.moduleName
      , srcLocFile
      , prov.tableName
      )
  pure
    InfoProvRow
      { Table.infoId = prov.infoId
      , Table.tableName = taId
      , Table.closureDesc = prov.closureDesc
      , Table.typeDesc = tyId
      , Table.label = labelId
      , Table.moduleName = modId
      , Table.srcLoc = srcLocId
      , Table.srcLocRange = srcLocRange
      }

splitGhcSrcLoc :: Text -> (Text, Text)
splitGhcSrcLoc srcLoc = Text.break (== ':') srcLoc

-- ----------------------------------------------------------------------------
-- Eventlog processing
-- ----------------------------------------------------------------------------

insertInfoProvData :: (Foldable t) => Sqlite.Connection -> t GhcEvents.Event -> IO ()
insertInfoProvData conn es = traverse_ (processIpeEvents conn) es

processIpeEvents :: Sqlite.Connection -> GhcEvents.Event -> IO ()
processIpeEvents conn ev = case eventInfoToInfoProv (GhcEvents.evSpec ev) of
  Nothing -> pure ()
  Just infoProv -> insertInfoProv conn infoProv

eventInfoToInfoProv :: GhcEvents.EventInfo -> Maybe InfoProv
eventInfoToInfoProv ev = case ev of
  it@GhcEvents.InfoTableProv{} ->
    Just
      InfoProv
        { Ipe.infoId = IpeId $ GhcEvents.itInfo it
        , Ipe.tableName = GhcEvents.itTableName it
        , Ipe.closureDesc = fromIntegral $ GhcEvents.itClosureDesc it
        , Ipe.typeDesc = GhcEvents.itTyDesc it
        , Ipe.label = GhcEvents.itLabel it
        , Ipe.moduleName = GhcEvents.itModule it
        , Ipe.srcLoc = GhcEvents.itSrcLoc it
        }
  _ -> Nothing