srtree-db-0.1.3.0: app/Backfill.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Backfill the @enode_parent@ reverse index for pre-existing databases.
--
-- The @enode_parent@ table is populated during import and eqsat write-through,
-- but databases created before this feature was added will have an empty table.
-- This command scans all @(eid, enode_key)@ pairs, parses each key to extract
-- children, and inserts the reverse-index rows.
module Backfill
( BackfillOpts
, backfillParser
, runBackfill
) where
import Control.Monad (forM_, foldM)
import qualified Data.IntMap as IntMap
import qualified Data.Text as T
import Options.Applicative
import Database.SQLite3 (Database, open, close)
import Algorithm.EqSat.Storage.Backend (SqlBackend, SqlValue(..), execDb, queryDb, runDb, sqlToInt, sqlToText)
import Algorithm.EqSat.Storage.SQLite () -- SqlBackend Database instance
import Algorithm.EqSat.Storage.Types (parseEnodeKey)
import Algorithm.EqSat.Egraph (ENode(..))
data BackfillOpts = BackfillOpts
{ backfillDb :: String
} deriving (Show)
backfillParser :: Parser BackfillOpts
backfillParser = BackfillOpts
<$> strOption (long "db" <> metavar "FILE" <> help "SQLite database file")
runBackfill :: BackfillOpts -> IO ()
runBackfill (BackfillOpts dbFile) = do
putStrLn $ "Backfilling enode_parent for " ++ dbFile ++ "..."
db <- open (T.pack dbFile)
-- Ensure the table exists
execDb db
"CREATE TABLE IF NOT EXISTS enode_parent (\
\ child_eid INTEGER NOT NULL,\
\ enode_key TEXT NOT NULL,\
\ parent_eid INTEGER NOT NULL,\
\ PRIMARY KEY (child_eid, enode_key))"
-- Count existing rows
existing <- countRows db "enode_parent"
putStrLn $ " Existing enode_parent rows: " ++ show existing
-- Scan all (eid, enode_key) pairs
rows <- queryDb db "SELECT eid, enode_key FROM eclass_node" []
let pairs = [ (sqlToInt eid, sqlToText key) | [eid, key] <- rows ]
putStrLn $ " Total eclass_node pairs: " ++ show (length pairs)
-- For each pair, parse the key to extract children and insert parent rows
execDb db "BEGIN"
count <- foldM (\acc (eid, key) -> do
case parseEnodeKey (T.unpack key) of
Nothing -> pure acc
Just en -> do
let children = allChildren en
forM_ children $ \childEid ->
runDb db "INSERT OR IGNORE INTO enode_parent (child_eid, enode_key, parent_eid) VALUES (?, ?, ?)"
[ SqlInteger (fromIntegral childEid)
, SqlText key
, SqlInteger (fromIntegral eid) ]
pure (acc + length children)) 0 pairs
execDb db "COMMIT"
putStrLn $ " Inserted " ++ show count ++ " enode_parent rows"
-- Count final rows
final <- countRows db "enode_parent"
putStrLn $ " Final enode_parent rows: " ++ show final
close db
putStrLn "Done."
countRows :: SqlBackend db => db -> String -> IO Int
countRows db table = do
rows <- queryDb db ("SELECT COUNT(*) FROM " <> T.pack table) []
pure $ case rows of
[[n]] -> sqlToInt n
_ -> 0
allChildren :: ENode -> [Int]
allChildren (EUni _ c) = [c]
allChildren (EBin _ l r) = [l, r]
allChildren (ENAry _ m) = map fst $ IntMap.toList m
allChildren _ = []