2026-09-25 14:21:55 +08:00
|
|
|
module Main where
|
|
|
|
|
|
|
|
|
|
import ChuSQL.Engine (runStatementM)
|
|
|
|
|
import ChuSQL.Model (Row, Value (..))
|
|
|
|
|
import ChuSQL.Storage (IndexResult (..))
|
|
|
|
|
import ChuSQL.Storage.IPC (
|
|
|
|
|
IPCStorage (..),
|
|
|
|
|
Request (..),
|
|
|
|
|
Response (..),
|
|
|
|
|
TableInfo (..),
|
|
|
|
|
closeConnection,
|
|
|
|
|
doInsert,
|
|
|
|
|
doListCatalog,
|
|
|
|
|
doLookupByColumn,
|
|
|
|
|
sendRequest,
|
|
|
|
|
)
|
|
|
|
|
import ChuSQL.Syntax.Parser (parseStatement)
|
|
|
|
|
import Control.Concurrent (threadDelay)
|
|
|
|
|
import Control.Exception (SomeException, bracket, evaluate, try)
|
|
|
|
|
import Control.Monad (filterM, unless, when)
|
|
|
|
|
import Data.List (intercalate, isInfixOf, isPrefixOf)
|
|
|
|
|
import GHC.Clock (getMonotonicTime)
|
|
|
|
|
import System.Directory (
|
|
|
|
|
createDirectoryIfMissing,
|
|
|
|
|
doesFileExist,
|
|
|
|
|
getTemporaryDirectory,
|
|
|
|
|
removeDirectoryRecursive,
|
|
|
|
|
)
|
|
|
|
|
import System.Environment (getArgs, getEnvironment, setEnv)
|
|
|
|
|
import System.Exit (exitFailure)
|
|
|
|
|
import System.FilePath ((</>))
|
|
|
|
|
import System.Process (
|
|
|
|
|
CreateProcess (..),
|
|
|
|
|
ProcessHandle,
|
|
|
|
|
StdStream (..),
|
|
|
|
|
createProcess,
|
|
|
|
|
proc,
|
|
|
|
|
terminateProcess,
|
|
|
|
|
waitForProcess,
|
|
|
|
|
)
|
|
|
|
|
import Text.Printf (printf)
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
-- 全链路基准:Haskell 引擎与 Rust 存储进程一起跑,
|
|
|
|
|
-- 语句走完整链路(解析、优化、命名管道、WAL、落盘),
|
|
|
|
|
-- 用墙上时钟计时,结果打印到标准输出。
|
2026-09-25 14:21:55 +08:00
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
-- | 找 Rust 存储进程:优先 release
|
2026-09-25 14:21:55 +08:00
|
|
|
findServer :: FilePath -> IO FilePath
|
|
|
|
|
findServer "" = do
|
|
|
|
|
let dirs =
|
|
|
|
|
[ "chusql-storage/target"
|
|
|
|
|
, "../../chusql-storage/target"
|
|
|
|
|
, "../chusql-storage/target"
|
|
|
|
|
, "target"
|
|
|
|
|
]
|
|
|
|
|
modes = ["release", "debug"]
|
2026-09-25 17:41:51 +08:00
|
|
|
exes = ["chusql-storage.exe", "chusql-storage"]
|
2026-09-25 14:21:55 +08:00
|
|
|
candidates = [d </> m </> e | d <- dirs, m <- modes, e <- exes]
|
|
|
|
|
found <- filterM doesFileExist candidates
|
|
|
|
|
case found of
|
|
|
|
|
(p : _) -> do
|
|
|
|
|
unless ("release" `isInfixOf` p) $
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "!! using the debug storage binary; numbers will be slower, run: cargo build --release"
|
2026-09-25 14:21:55 +08:00
|
|
|
pure p
|
|
|
|
|
[] -> do
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "Rust storage binary not found. Build it first:"
|
2026-09-25 14:21:55 +08:00
|
|
|
putStrLn " cd chusql-storage && cargo build --release"
|
|
|
|
|
exitFailure
|
|
|
|
|
findServer p = do
|
|
|
|
|
ok <- doesFileExist p
|
|
|
|
|
if ok
|
|
|
|
|
then pure p
|
|
|
|
|
else do
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn ("storage binary not found: " ++ p)
|
2026-09-25 14:21:55 +08:00
|
|
|
exitFailure
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
-- | 子进程环境:继承并覆盖 CHUSQL_*
|
2026-09-25 14:21:55 +08:00
|
|
|
childEnv :: [(String, String)] -> IO [(String, String)]
|
|
|
|
|
childEnv extra = do
|
|
|
|
|
base <- getEnvironment
|
|
|
|
|
let stripped = filter (\(k, _) -> not ("CHUSQL_" `isPrefixOf` k)) base
|
|
|
|
|
pure (stripped ++ extra)
|
|
|
|
|
|
|
|
|
|
-- | 起存储进程并等管道可连
|
|
|
|
|
startServer :: FilePath -> String -> FilePath -> IO ProcessHandle
|
|
|
|
|
startServer serverPath pipe dataDir = do
|
|
|
|
|
envs <-
|
|
|
|
|
childEnv
|
|
|
|
|
[ ("CHUSQL_PIPE", pipe)
|
|
|
|
|
, ("CHUSQL_DATA_DIR", dataDir)
|
|
|
|
|
, ("CHUSQL_PAGE_SIZE", "4096")
|
|
|
|
|
, ("CHUSQL_BTREE_ORDER", "4")
|
|
|
|
|
, ("CHUSQL_LOG", "error")
|
|
|
|
|
]
|
|
|
|
|
(_, _, _, ph) <-
|
|
|
|
|
createProcess
|
|
|
|
|
(proc serverPath []) {env = Just envs, std_out = NoStream, std_err = Inherit}
|
|
|
|
|
ready <- waitReady (200 :: Int)
|
|
|
|
|
unless ready $ do
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "storage process was not ready within 5 s"
|
2026-09-25 14:21:55 +08:00
|
|
|
exitFailure
|
|
|
|
|
pure ph
|
|
|
|
|
|
|
|
|
|
-- | 轮询 ping,直到对面应答
|
|
|
|
|
waitReady :: Int -> IO Bool
|
|
|
|
|
waitReady 0 = pure False
|
|
|
|
|
waitReady n = do
|
|
|
|
|
r <- try (sendRequest ReqPing) :: IO (Either SomeException Response)
|
|
|
|
|
case r of
|
|
|
|
|
Right RespPong -> pure True
|
|
|
|
|
_ -> threadDelay 25000 >> waitReady (n - 1)
|
|
|
|
|
|
|
|
|
|
-- | 停存储进程并清掉缓存连接
|
|
|
|
|
stopServer :: ProcessHandle -> FilePath -> IO ()
|
|
|
|
|
stopServer ph dataDir = do
|
|
|
|
|
closeConnection
|
|
|
|
|
terminateProcess ph
|
|
|
|
|
_ <- waitForProcess ph
|
|
|
|
|
_ <- try (removeDirectoryRecursive dataDir) :: IO (Either SomeException ())
|
|
|
|
|
pure ()
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
-- | 量一次墙上耗时并强制求出行数
|
2026-09-25 14:21:55 +08:00
|
|
|
timedRows :: IO (Either String [Row]) -> IO (Double, Either String Int)
|
|
|
|
|
timedRows act = do
|
|
|
|
|
t0 <- getMonotonicTime
|
|
|
|
|
r <- act
|
|
|
|
|
n <- case r of
|
|
|
|
|
Left _ -> pure (-1)
|
|
|
|
|
Right rows -> evaluate (length rows)
|
|
|
|
|
t1 <- getMonotonicTime
|
|
|
|
|
pure (t1 - t0, either Left (const (Right n)) r)
|
|
|
|
|
|
|
|
|
|
-- | 跑一条 SQL 文本(整条链路)
|
|
|
|
|
runSql :: String -> IO (Either String [Row])
|
|
|
|
|
runSql sql = case parseStatement sql of
|
|
|
|
|
Left e -> pure (Left e)
|
|
|
|
|
Right stmt -> runIPCStorage (runStatementM stmt)
|
|
|
|
|
|
|
|
|
|
-- | 打印一行结果
|
|
|
|
|
report :: String -> Either String Int -> Double -> IO ()
|
|
|
|
|
report label result dt = case result of
|
2026-09-25 17:41:51 +08:00
|
|
|
Left e -> printf "%-38s %10s !! %s\n" label "FAILED" e
|
|
|
|
|
Right n -> printf "%-38s %8.1f ms %8d rows\n" label (dt * 1000) n
|
2026-09-25 14:21:55 +08:00
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
-- | 单独量一条可重复的 SQL(先预热)
|
2026-09-25 14:21:55 +08:00
|
|
|
benchSql :: String -> String -> IO (Either String Int)
|
|
|
|
|
benchSql label sql = do
|
2026-09-25 17:41:51 +08:00
|
|
|
_ <- runSql sql
|
2026-09-25 14:21:55 +08:00
|
|
|
(dt, r) <- timedRows (runSql sql)
|
|
|
|
|
report label r dt
|
|
|
|
|
pure r
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
-- | 只跑一次的语句(重跑会报错)
|
2026-09-25 14:21:55 +08:00
|
|
|
benchSqlOnce :: String -> String -> IO (Either String Int)
|
|
|
|
|
benchSqlOnce label sql = do
|
|
|
|
|
(dt, r) <- timedRows (runSql sql)
|
|
|
|
|
report label r dt
|
|
|
|
|
pure r
|
|
|
|
|
|
|
|
|
|
-- | 造一条 INSERT 的 SQL 文本
|
|
|
|
|
userSql :: Int -> String
|
|
|
|
|
userSql i =
|
|
|
|
|
printf "INSERT INTO users (id, name, age) VALUES (%d, 'user%d', %d)" i i (i `mod` 100)
|
|
|
|
|
|
|
|
|
|
-- | 造一条订单的 INSERT
|
|
|
|
|
orderSql :: Int -> Int -> String
|
|
|
|
|
orderSql u j =
|
|
|
|
|
printf
|
|
|
|
|
"INSERT INTO orders (id, user_id, product) VALUES (%d, %d, 'item%d')"
|
|
|
|
|
j
|
|
|
|
|
(1 + (j - 1) `mod` u)
|
|
|
|
|
(j `mod` 50)
|
|
|
|
|
|
|
|
|
|
-- | 批量插入并报平均耗时
|
|
|
|
|
batchInsert :: String -> Int -> (Int -> String) -> IO (Either String ())
|
|
|
|
|
batchInsert label n mkSql = do
|
|
|
|
|
t0 <- getMonotonicTime
|
|
|
|
|
results <- mapM (runSql . mkSql) [1 .. n]
|
|
|
|
|
t1 <- getMonotonicTime
|
|
|
|
|
let total = t1 - t0
|
|
|
|
|
errs = [e | Left e <- results]
|
|
|
|
|
case errs of
|
|
|
|
|
[] -> do
|
|
|
|
|
printf
|
2026-09-25 17:41:51 +08:00
|
|
|
"%-38s %8.1f ms %7.3f ms/row\n"
|
2026-09-25 14:21:55 +08:00
|
|
|
label
|
|
|
|
|
(total * 1000)
|
|
|
|
|
(total * 1000 / fromIntegral n)
|
|
|
|
|
pure (Right ())
|
|
|
|
|
(e : _) -> do
|
2026-09-25 17:41:51 +08:00
|
|
|
printf "%-38s %10s !! %s\n" label "FAILED" e
|
2026-09-25 14:21:55 +08:00
|
|
|
pure (Left e)
|
|
|
|
|
|
|
|
|
|
-- | 一次点查(原始 IPC,不经过引擎)
|
|
|
|
|
benchLookup :: String -> String -> Int -> Int -> IO ()
|
|
|
|
|
benchLookup table column key times = do
|
|
|
|
|
_ <- doLookupByColumn table column key
|
|
|
|
|
t0 <- getMonotonicTime
|
|
|
|
|
results <- mapM (const (doLookupByColumn table column key)) [1 .. times]
|
|
|
|
|
t1 <- getMonotonicTime
|
|
|
|
|
let total = t1 - t0
|
|
|
|
|
hit = length [() | Right (IndexRow (Just _)) <- results]
|
|
|
|
|
printf
|
2026-09-25 17:41:51 +08:00
|
|
|
"%-38s %8.1f ms %7.3f ms/op (hits %d/%d)\n"
|
|
|
|
|
"lookup_by_index (raw IPC, by id)"
|
2026-09-25 14:21:55 +08:00
|
|
|
(total * 1000)
|
|
|
|
|
(total * 1000 / fromIntegral times)
|
|
|
|
|
hit
|
|
|
|
|
times
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
-- | 量 list_catalog(只取结构,不取行)
|
2026-09-25 14:21:55 +08:00
|
|
|
benchListCatalog :: Int -> IO ()
|
|
|
|
|
benchListCatalog n = do
|
|
|
|
|
t0 <- getMonotonicTime
|
|
|
|
|
_ <- mapM (const doListCatalog) [1 .. n]
|
|
|
|
|
t1 <- getMonotonicTime
|
|
|
|
|
let total = t1 - t0
|
|
|
|
|
printf
|
2026-09-25 17:41:51 +08:00
|
|
|
"%-38s %8.1f ms %7.3f ms/op\n"
|
|
|
|
|
"list_catalog (schema only)"
|
2026-09-25 14:21:55 +08:00
|
|
|
(total * 1000)
|
|
|
|
|
(total * 1000 / fromIntegral n)
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
-- | 量原始 IPC 插入(不经过引擎)
|
2026-09-25 14:21:55 +08:00
|
|
|
benchRawInsert :: String -> [Row] -> IO ()
|
|
|
|
|
benchRawInsert table rows = do
|
|
|
|
|
t0 <- getMonotonicTime
|
|
|
|
|
results <- mapM (\r -> doInsert table r) rows
|
|
|
|
|
t1 <- getMonotonicTime
|
|
|
|
|
let total = t1 - t0
|
|
|
|
|
n = length rows
|
|
|
|
|
errs = [e | Left e <- results]
|
|
|
|
|
case errs of
|
|
|
|
|
[] ->
|
|
|
|
|
printf
|
2026-09-25 17:41:51 +08:00
|
|
|
"%-38s %8.1f ms %7.3f ms/row\n"
|
|
|
|
|
("raw IPC insert x " ++ show n)
|
2026-09-25 14:21:55 +08:00
|
|
|
(total * 1000)
|
|
|
|
|
(total * 1000 / fromIntegral n)
|
2026-09-25 17:41:51 +08:00
|
|
|
(e : _) -> printf "%-38s %10s !! %s\n" "raw IPC insert" "FAILED" e
|
2026-09-25 14:21:55 +08:00
|
|
|
|
|
|
|
|
-- | 造一行 users 数据(给原始 IPC 用)
|
|
|
|
|
userRow :: Int -> Row
|
|
|
|
|
userRow i = [("id", VInt i), ("name", VStr ("user" ++ show i)), ("age", VInt (i `mod` 100))]
|
|
|
|
|
|
|
|
|
|
-- | 依次跑一批语句,返回 (耗时秒, 错误清单)
|
|
|
|
|
runBatch :: [String] -> IO (Double, [String])
|
|
|
|
|
runBatch sqls = do
|
|
|
|
|
t0 <- getMonotonicTime
|
|
|
|
|
results <- mapM runSql sqls
|
|
|
|
|
t1 <- getMonotonicTime
|
|
|
|
|
pure (t1 - t0, [e | Left e <- results])
|
|
|
|
|
|
|
|
|
|
-- | 把 1..n 按 size 切成一段段
|
|
|
|
|
chunked :: Int -> Int -> [[Int]]
|
|
|
|
|
chunked size n = go [1 .. n]
|
|
|
|
|
where
|
2026-09-25 17:41:51 +08:00
|
|
|
-- | 每次切下 size 个
|
2026-09-25 14:21:55 +08:00
|
|
|
go [] = []
|
|
|
|
|
go xs = let (a, b) = splitAt size xs in a : go b
|
|
|
|
|
|
|
|
|
|
-- | 一条带多行的 INSERT(一条语句 N 行,只落一次盘)
|
|
|
|
|
multiInsertSql :: [Int] -> String
|
|
|
|
|
multiInsertSql ids =
|
|
|
|
|
"INSERT INTO batch_t (id, name, age) VALUES "
|
|
|
|
|
++ intercalate ", " [printf "(%d, 'b%d', %d)" i i (i `mod` 100) | i <- ids]
|
|
|
|
|
|
|
|
|
|
-- | 打印一张表的统计
|
|
|
|
|
printStats :: TableInfo -> IO ()
|
|
|
|
|
printStats i =
|
|
|
|
|
printf
|
2026-09-25 17:41:51 +08:00
|
|
|
" %-12s %6d rows indexes=%s\n stats=%s\n"
|
2026-09-25 14:21:55 +08:00
|
|
|
(tiTable i)
|
|
|
|
|
(tiRows i)
|
2026-09-25 17:41:51 +08:00
|
|
|
(if null (tiIndexes i) then "none" else unwords (tiIndexes i))
|
2026-09-25 14:21:55 +08:00
|
|
|
( unwords
|
2026-09-25 17:41:51 +08:00
|
|
|
[ n ++ ":" ++ show d ++ (if c then "+ (lower bound)" else "")
|
2026-09-25 14:21:55 +08:00
|
|
|
| (n, d, c) <- tiStats i
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
-- | 主场景
|
|
|
|
|
runScenarios :: Int -> Int -> IO ()
|
|
|
|
|
runScenarios u o = do
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[create tables]"
|
2026-09-25 14:21:55 +08:00
|
|
|
r1 <- benchSqlOnce "CREATE TABLE users" "CREATE TABLE users (id int, name str, age int)"
|
|
|
|
|
r2 <- benchSqlOnce "CREATE TABLE orders" "CREATE TABLE orders (id int, user_id int, product str)"
|
|
|
|
|
case (r1, r2) of
|
|
|
|
|
(Right _, Right _) -> pure ()
|
|
|
|
|
_ -> do
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "table creation failed; stopping here"
|
2026-09-25 14:21:55 +08:00
|
|
|
exitFailure
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[write: every INSERT is a full durable commit]"
|
|
|
|
|
_ <- batchInsert (printf "%d INSERT INTO users" u) u userSql
|
|
|
|
|
_ <- batchInsert (printf "%d INSERT INTO orders" o) o (orderSql u)
|
2026-09-25 14:21:55 +08:00
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[breakdown: same INSERT split in two, both via raw IPC, no engine]"
|
2026-09-25 14:21:55 +08:00
|
|
|
_ <- benchSqlOnce "CREATE TABLE raw_users" "CREATE TABLE raw_users (id int, name str, age int)"
|
|
|
|
|
benchListCatalog 200
|
|
|
|
|
benchRawInsert "raw_users" [userRow i | i <- [1 .. u]]
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[read/query: full statement, including parsing and semantic checks]"
|
2026-09-25 14:21:55 +08:00
|
|
|
_ <- benchSql "SELECT * FROM users" "SELECT * FROM users"
|
|
|
|
|
ageR <- benchSql "SELECT name FROM users WHERE age > 90" "SELECT name FROM users WHERE age > 90"
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[read/query: join]"
|
2026-09-25 14:21:55 +08:00
|
|
|
joinR <-
|
|
|
|
|
benchSql
|
2026-09-25 17:41:51 +08:00
|
|
|
"JOIN + WHERE (hash join)"
|
2026-09-25 14:21:55 +08:00
|
|
|
"SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 90"
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[point lookup]"
|
2026-09-25 14:21:55 +08:00
|
|
|
benchLookup "users" "id" (u `div` 2) 100
|
2026-09-25 17:41:51 +08:00
|
|
|
_ <- benchSql "SELECT ... WHERE id = k (via Lookup)" (printf "SELECT name FROM users WHERE id = %d" (u `div` 2))
|
2026-09-25 14:21:55 +08:00
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[batch insert: one statement with N rows, one durable commit]"
|
2026-09-25 14:21:55 +08:00
|
|
|
_ <- benchSqlOnce "CREATE TABLE batch_t" "CREATE TABLE batch_t (id int, name str, age int)"
|
|
|
|
|
let groups = chunked 100 u
|
|
|
|
|
(bt, batchErrs) <- runBatch (map multiInsertSql groups)
|
|
|
|
|
case batchErrs of
|
|
|
|
|
[] ->
|
|
|
|
|
printf
|
2026-09-25 17:41:51 +08:00
|
|
|
"%-38s %8.1f ms %7.3f ms/row (%d rows/stmt x %d statements)\n"
|
|
|
|
|
"multi-row INSERT"
|
2026-09-25 14:21:55 +08:00
|
|
|
(bt * 1000)
|
|
|
|
|
(bt * 1000 / fromIntegral u)
|
|
|
|
|
(100 :: Int)
|
|
|
|
|
(length groups)
|
2026-09-25 17:41:51 +08:00
|
|
|
(e : _) -> printf "%-38s %10s !! %s\n" "multi-row INSERT" "FAILED" e
|
2026-09-25 14:21:55 +08:00
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[row delete: cost tracks the scan plus one commit, not the row count]"
|
|
|
|
|
_ <- benchSqlOnce "DELETE FROM users WHERE id = k (1 row)" (printf "DELETE FROM users WHERE id = %d" (u `div` 2))
|
|
|
|
|
_ <- benchSqlOnce "DELETE FROM users (all)" "DELETE FROM users"
|
2026-09-25 14:21:55 +08:00
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[secondary index: falls back to scan, then uses the index once built]"
|
2026-09-25 14:21:55 +08:00
|
|
|
_ <- benchSqlOnce "CREATE TABLE items" "CREATE TABLE items (id int, code int)"
|
|
|
|
|
let itemGroups = chunked 100 o
|
|
|
|
|
itemSql ids =
|
|
|
|
|
"INSERT INTO items (id, code) VALUES "
|
|
|
|
|
++ intercalate ", " [printf "(%d, %d)" i (i * 7 + 1) | i <- ids]
|
|
|
|
|
(it, itemErrs) <- runBatch (map itemSql itemGroups)
|
|
|
|
|
case itemErrs of
|
|
|
|
|
[] -> pure ()
|
|
|
|
|
(e : _) -> printf " !! %s\n" e
|
2026-09-25 17:41:51 +08:00
|
|
|
printf " (loaded %d rows into items: %.1f ms)\n" o (it * 1000)
|
2026-09-25 14:21:55 +08:00
|
|
|
let targetCode = (o `div` 2) * 7 + 1
|
|
|
|
|
_ <-
|
|
|
|
|
benchSql
|
2026-09-25 17:41:51 +08:00
|
|
|
"SELECT ... WHERE code = k (no index)"
|
2026-09-25 14:21:55 +08:00
|
|
|
(printf "SELECT id FROM items WHERE code = %d" targetCode)
|
|
|
|
|
_ <- benchSqlOnce "CREATE INDEX ON items (code)" "CREATE INDEX ON items (code)"
|
|
|
|
|
_ <-
|
|
|
|
|
benchSql
|
2026-09-25 17:41:51 +08:00
|
|
|
"SELECT ... WHERE code = k (via index)"
|
2026-09-25 14:21:55 +08:00
|
|
|
(printf "SELECT id FROM items WHERE code = %d" targetCode)
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[catalog stats]"
|
2026-09-25 14:21:55 +08:00
|
|
|
cat <- doListCatalog
|
|
|
|
|
case cat of
|
2026-09-25 17:41:51 +08:00
|
|
|
Left e -> printf " failed to read stats: %s\n" e
|
2026-09-25 14:21:55 +08:00
|
|
|
Right infos -> mapM_ printStats infos
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[checks]"
|
2026-09-25 14:21:55 +08:00
|
|
|
let expectAge = 9 * (u `div` 100) + max 0 (u `mod` 100 - 90)
|
|
|
|
|
printf
|
2026-09-25 17:41:51 +08:00
|
|
|
"age > 90 matched %s (expected %d rows from the data distribution)\n"
|
2026-09-25 14:21:55 +08:00
|
|
|
(either show show ageR)
|
|
|
|
|
expectAge
|
|
|
|
|
when (u == o) $
|
|
|
|
|
printf
|
2026-09-25 17:41:51 +08:00
|
|
|
"JOIN matched %s (one order per user, should equal the line above = %d)\n"
|
2026-09-25 14:21:55 +08:00
|
|
|
(either show show joinR)
|
|
|
|
|
expectAge
|
|
|
|
|
unless (u == o) $
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "(users and orders differ in size; no simple formula, printing only)"
|
|
|
|
|
printf "multi-row INSERT loaded %d rows (expected %d)\n" u u
|
|
|
|
|
printf "secondary index: code = %d should find id = %d\n" targetCode (o `div` 2)
|
2026-09-25 14:21:55 +08:00
|
|
|
|
|
|
|
|
-- | 主入口
|
|
|
|
|
main :: IO ()
|
|
|
|
|
main = do
|
|
|
|
|
args <- getArgs
|
|
|
|
|
let pick i d = if length args > i then args !! i else d
|
|
|
|
|
u = read (pick 0 "200") :: Int
|
|
|
|
|
o = read (pick 1 "200") :: Int
|
|
|
|
|
serverArg = pick 2 ""
|
|
|
|
|
serverPath <- findServer serverArg
|
|
|
|
|
|
|
|
|
|
tmp <- getTemporaryDirectory
|
|
|
|
|
stamp <- getMonotonicTime
|
|
|
|
|
let pipe = "chusql-fullchain-" ++ show (round (stamp * 1e6) :: Int)
|
|
|
|
|
dataDir = tmp </> pipe
|
|
|
|
|
createDirectoryIfMissing True dataDir
|
|
|
|
|
setEnv "CHUSQL_PIPE" pipe
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
printf "full-chain benchmark: Haskell engine <--named pipe--> Rust storage\n"
|
|
|
|
|
printf "storage : %s\n" serverPath
|
|
|
|
|
printf "pipe : %s\n" pipe
|
|
|
|
|
printf "data dir : %s\n" dataDir
|
|
|
|
|
printf "dataset : users = %d rows, orders = %d rows\n" u o
|
|
|
|
|
printf "timing : wall clock (monotonic); every statement runs the full chain\n"
|
2026-09-25 14:21:55 +08:00
|
|
|
|
|
|
|
|
bracket
|
|
|
|
|
(do
|
|
|
|
|
t1 <- getMonotonicTime
|
|
|
|
|
ph <- startServer serverPath pipe dataDir
|
|
|
|
|
t2 <- getMonotonicTime
|
2026-09-25 17:41:51 +08:00
|
|
|
printf "\n[startup] storage ready in %.2f s\n" (t2 - t1)
|
2026-09-25 14:21:55 +08:00
|
|
|
pure ph
|
|
|
|
|
)
|
|
|
|
|
(\ph -> do
|
2026-09-25 17:41:51 +08:00
|
|
|
putStrLn "\n[teardown] stopping storage and removing the data dir"
|
2026-09-25 14:21:55 +08:00
|
|
|
stopServer ph dataDir
|
|
|
|
|
)
|
|
|
|
|
(const (runScenarios u o))
|