使用React js完成web前端

This commit is contained in:
LIm1tZz7 committed 2026-09-27 22:08:23 +08:00
1 parent 9e8c5f1ef0
commit 7600f0f9ed
130 files changed
+12708 -788

No files matched your search

+69 -88
View File
@@ -40,25 +40,11 @@ import System.Process (
)
import Text.Printf (printf)
-- 全链路基准:Haskell 引擎和 Rust 存储进程**一起启动**,跑真实情景。
--
-- 一条语句真正走过的路:
-- SQL 文本 → 词法/语法分析 → 语义检查 → 关系代数 → 命名管道(JSON 行)
-- → Rust:WAL(fsync)→ 堆表/B+ 树 → 页缓冲池 → 落盘
-- → 结果原路返回
--
-- 计时用**墙上时钟(单调时钟)**,不是 CPU 时间:这条链路大部分工夫花在等 IO
-- 和跨进程往返上,用 CPU 时间会把这些等待整个漏掉。
--
-- 跑法(先把 Rust 那边编译成 release):
-- cd chusql-storage && cargo build --release
-- cd benchmark/fullchain && stack run # 默认 users=orders=200
-- stack run -- 500 500 # 换规模
-- stack run -- 200 200 ../../chusql-storage/target/release/server.exe # 指定存储进程
-- 全链路基准:Haskell 引擎与 Rust 存储进程一起跑,
-- 语句走完整链路(解析、优化、命名管道、WAL、落盘),
-- 用墙上时钟计时,结果打印到标准输出。
-- * 命令行与存储进程
-- | 找 Rust 存储进程:优先 release,其次 debug
-- | 找 Rust 存储进程:优先 release
findServer :: FilePath -> IO FilePath
findServer "" = do
let dirs =
@@ -68,16 +54,16 @@ findServer "" = do
, "target"
]
modes = ["release", "debug"]
exes = ["server.exe", "server"]
exes = ["chusql-storage.exe", "chusql-storage"]
candidates = [d </> m </> e | d <- dirs, m <- modes, e <- exes]
found <- filterM doesFileExist candidates
case found of
(p : _) -> do
unless ("release" `isInfixOf` p) $
putStrLn "!! 找到的是 debug 版存储进程,数字会偏慢;建议 cargo build --release"
putStrLn "!! using the debug storage binary; numbers will be slower, run: cargo build --release"
pure p
[] -> do
putStrLn "找不到 Rust 存储进程。先编译:"
putStrLn "Rust storage binary not found. Build it first:"
putStrLn " cd chusql-storage && cargo build --release"
exitFailure
findServer p = do
@@ -85,10 +71,10 @@ findServer p = do
if ok
then pure p
else do
putStrLn ("指定的存储进程不存在:" ++ p)
putStrLn ("storage binary not found: " ++ p)
exitFailure
-- | 子进程环境:继承当前环境,但把 CHUSQL_* 换成本次专用的
-- | 子进程环境:继承并覆盖 CHUSQL_*
childEnv :: [(String, String)] -> IO [(String, String)]
childEnv extra = do
base <- getEnvironment
@@ -111,7 +97,7 @@ startServer serverPath pipe dataDir = do
(proc serverPath []) {env = Just envs, std_out = NoStream, std_err = Inherit}
ready <- waitReady (200 :: Int)
unless ready $ do
putStrLn "存储进程 5 秒内没就绪"
putStrLn "storage process was not ready within 5 s"
exitFailure
pure ph
@@ -133,9 +119,7 @@ stopServer ph dataDir = do
_ <- try (removeDirectoryRecursive dataDir) :: IO (Either SomeException ())
pure ()
-- * 计时
-- | 量一次墙上耗时,并把结果行数强制出来(Haskell 是惰性的,不强制就等于没测)
-- | 量一次墙上耗时并强制求出行数
timedRows :: IO (Either String [Row]) -> IO (Double, Either String Int)
timedRows act = do
t0 <- getMonotonicTime
@@ -155,26 +139,24 @@ runSql sql = case parseStatement sql of
-- | 打印一行结果
report :: String -> Either String Int -> Double -> IO ()
report label result dt = case result of
Left e -> printf "%-38s %10s !! %s\n" label "失败" e
Right n -> printf "%-38s %8.1f ms %8d 行\n" label (dt * 1000) n
Left e -> printf "%-38s %10s !! %s\n" label "FAILED" e
Right n -> printf "%-38s %8.1f ms %8d rows\n" label (dt * 1000) n
-- | 单独量一条 SQL(先预热一遍):只适合可以重复跑的语句(SELECT)
-- | 单独量一条可重复的 SQL(先预热)
benchSql :: String -> String -> IO (Either String Int)
benchSql label sql = do
_ <- runSql sql -- 预热:连接、页缓存、优化器都先热一次
_ <- runSql sql
(dt, r) <- timedRows (runSql sql)
report label r dt
pure r
-- | 只跑一次的语句(CREATE TABLE 这类重跑会报错的)
-- | 只跑一次的语句(重跑会报错)
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 =
@@ -200,13 +182,13 @@ batchInsert label n mkSql = do
case errs of
[] -> do
printf
"%-38s %8.1f ms %7.3f ms/条\n"
"%-38s %8.1f ms %7.3f ms/row\n"
label
(total * 1000)
(total * 1000 / fromIntegral n)
pure (Right ())
(e : _) -> do
printf "%-38s %10s !! %s\n" label "失败" e
printf "%-38s %10s !! %s\n" label "FAILED" e
pure (Left e)
-- | 一次点查(原始 IPC,不经过引擎)
@@ -219,14 +201,14 @@ benchLookup table column key times = do
let total = t1 - t0
hit = length [() | Right (IndexRow (Just _)) <- results]
printf
"%-38s %8.1f ms %7.3f ms/次(命中 %d/%d)\n"
"lookup_by_index(原始 IPC,按 id)"
"%-38s %8.1f ms %7.3f ms/op (hits %d/%d)\n"
"lookup_by_index (raw IPC, by id)"
(total * 1000)
(total * 1000 / fromIntegral times)
hit
times
-- | 量 list_catalog:现在每条语句都要先来一发(只取结构,不取行)
-- | 量 list_catalog(只取结构,不取行)
benchListCatalog :: Int -> IO ()
benchListCatalog n = do
t0 <- getMonotonicTime
@@ -234,12 +216,12 @@ benchListCatalog n = do
t1 <- getMonotonicTime
let total = t1 - t0
printf
"%-38s %8.1f ms %7.3f ms/次\n"
"list_catalog(取结构)"
"%-38s %8.1f ms %7.3f ms/op\n"
"list_catalog (schema only)"
(total * 1000)
(total * 1000 / fromIntegral n)
-- | 量原始 IPC 插入:不经过引擎,把"协议 + 存储"和"引擎"分开
-- | 量原始 IPC 插入(不经过引擎)
benchRawInsert :: String -> [Row] -> IO ()
benchRawInsert table rows = do
t0 <- getMonotonicTime
@@ -251,11 +233,11 @@ benchRawInsert table rows = do
case errs of
[] ->
printf
"%-38s %8.1f ms %7.3f ms/条\n"
("原始 IPC insert × " ++ show n)
"%-38s %8.1f ms %7.3f ms/row\n"
("raw IPC insert x " ++ show n)
(total * 1000)
(total * 1000 / fromIntegral n)
(e : _) -> printf "%-38s %10s !! %s\n" "原始 IPC insert" "失败" e
(e : _) -> printf "%-38s %10s !! %s\n" "raw IPC insert" "FAILED" e
-- | 造一行 users 数据(给原始 IPC 用)
userRow :: Int -> Row
@@ -273,7 +255,7 @@ runBatch sqls = do
chunked :: Int -> Int -> [[Int]]
chunked size n = go [1 .. n]
where
-- \| 每次切下 size 个
-- | 每次切下 size 个
go [] = []
go xs = let (a, b) = splitAt size xs in a : go b
@@ -287,12 +269,12 @@ multiInsertSql ids =
printStats :: TableInfo -> IO ()
printStats i =
printf
" %-12s %6d 行 索引=%s\n 统计=%s\n"
" %-12s %6d rows indexes=%s\n stats=%s\n"
(tiTable i)
(tiRows i)
(if null (tiIndexes i) then "无" else unwords (tiIndexes i))
(if null (tiIndexes i) then "none" else unwords (tiIndexes i))
( unwords
[ n ++ ":" ++ show d ++ (if c then "+(下界)" else "")
[ n ++ ":" ++ show d ++ (if c then "+ (lower bound)" else "")
| (n, d, c) <- tiStats i
]
)
@@ -300,58 +282,58 @@ printStats i =
-- | 主场景
runScenarios :: Int -> Int -> IO ()
runScenarios u o = do
putStrLn "\n[建表]"
putStrLn "\n[create tables]"
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
putStrLn "建表失败,后面不用跑了"
putStrLn "table creation failed; stopping here"
exitFailure
putStrLn "\n[写入:每条 INSERT 都是一次完整落盘]"
_ <- batchInsert (printf "%d 条 INSERT INTO users" u) u userSql
_ <- batchInsert (printf "%d 条 INSERT INTO orders" o) o (orderSql u)
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)
putStrLn "\n[分解:同一条 INSERT 拆成两半,都用原始 IPC,不经过引擎]"
putStrLn "\n[breakdown: same INSERT split in two, both via raw IPC, no engine]"
_ <- 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]]
putStrLn "\n[读取与查询:整条语句,含引擎侧的解析与语义检查]"
putStrLn "\n[read/query: full statement, including parsing and semantic checks]"
_ <- benchSql "SELECT * FROM users" "SELECT * FROM users"
ageR <- benchSql "SELECT name FROM users WHERE age > 90" "SELECT name FROM users WHERE age > 90"
putStrLn "\n[读取与查询:连接]"
putStrLn "\n[read/query: join]"
joinR <-
benchSql
"JOIN + WHERE(哈希连接)"
"JOIN + WHERE (hash join)"
"SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 90"
putStrLn "\n[点查]"
putStrLn "\n[point lookup]"
benchLookup "users" "id" (u `div` 2) 100
_ <- benchSql "SELECT ... WHERE id = k(走 Lookup)" (printf "SELECT name FROM users WHERE id = %d" (u `div` 2))
_ <- benchSql "SELECT ... WHERE id = k (via Lookup)" (printf "SELECT name FROM users WHERE id = %d" (u `div` 2))
putStrLn "\n[批量插入:一条语句带多行,N 行只落一次盘]"
putStrLn "\n[batch insert: one statement with N rows, one durable commit]"
_ <- 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
"%-38s %8.1f ms %7.3f ms/行(%d 行/条 × %d 条语句)\n"
"多行 INSERT"
"%-38s %8.1f ms %7.3f ms/row (%d rows/stmt x %d statements)\n"
"multi-row INSERT"
(bt * 1000)
(bt * 1000 / fromIntegral u)
(100 :: Int)
(length groups)
(e : _) -> printf "%-38s %10s !! %s\n" "多行 INSERT" "失败" e
(e : _) -> printf "%-38s %10s !! %s\n" "multi-row INSERT" "FAILED" e
putStrLn "\n[行级删除:代价和删几行无关,只和扫描 + 一次落盘有关]"
_ <- benchSqlOnce "DELETE FROM users WHERE id = k(1 行)" (printf "DELETE FROM users WHERE id = %d" (u `div` 2))
_ <- benchSqlOnce "DELETE FROM users(全部)" "DELETE FROM users"
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"
putStrLn "\n[二级索引:没有索引回退扫描,建完索引走索引]"
putStrLn "\n[secondary index: falls back to scan, then uses the index once built]"
_ <- benchSqlOnce "CREATE TABLE items" "CREATE TABLE items (id int, code int)"
let itemGroups = chunked 100 o
itemSql ids =
@@ -361,39 +343,39 @@ runScenarios u o = do
case itemErrs of
[] -> pure ()
(e : _) -> printf " !! %s\n" e
printf " (先把 %d 行灌进 items:%.1f ms)\n" o (it * 1000)
printf " (loaded %d rows into items: %.1f ms)\n" o (it * 1000)
let targetCode = (o `div` 2) * 7 + 1
_ <-
benchSql
"SELECT ... WHERE code = k(无索引)"
"SELECT ... WHERE code = k (no index)"
(printf "SELECT id FROM items WHERE code = %d" targetCode)
_ <- benchSqlOnce "CREATE INDEX ON items (code)" "CREATE INDEX ON items (code)"
_ <-
benchSql
"SELECT ... WHERE code = k(走索引)"
"SELECT ... WHERE code = k (via index)"
(printf "SELECT id FROM items WHERE code = %d" targetCode)
putStrLn "\n[数据字典统计]"
putStrLn "\n[catalog stats]"
cat <- doListCatalog
case cat of
Left e -> printf " 取统计失败:%s\n" e
Left e -> printf " failed to read stats: %s\n" e
Right infos -> mapM_ printStats infos
putStrLn "\n[校验]"
putStrLn "\n[checks]"
let expectAge = 9 * (u `div` 100) + max 0 (u `mod` 100 - 90)
printf
"age > 90 命中 %s(按数据分布应为 %d 行)\n"
"age > 90 matched %s (expected %d rows from the data distribution)\n"
(either show show ageR)
expectAge
when (u == o) $
printf
"JOIN 命中 %s(每个用户恰好 1 张订单,应与上一行相同 = %d)\n"
"JOIN matched %s (one order per user, should equal the line above = %d)\n"
(either show show joinR)
expectAge
unless (u == o) $
putStrLn "(users 与 orders 行数不同,JOIN 行数没有简单公式,只打印不校验)"
printf "多行 INSERT 灌进去 %d 行(应为 %d)\n" u u
printf "二级索引那一行:code = %d 应该查到 id = %d\n" targetCode (o `div` 2)
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)
-- | 主入口
main :: IO ()
@@ -405,7 +387,6 @@ main = do
serverArg = pick 2 ""
serverPath <- findServer serverArg
-- 每次跑用独立的管道名与数据目录,避免和别的进程抢
tmp <- getTemporaryDirectory
stamp <- getMonotonicTime
let pipe = "chusql-fullchain-" ++ show (round (stamp * 1e6) :: Int)
@@ -413,23 +394,23 @@ main = do
createDirectoryIfMissing True dataDir
setEnv "CHUSQL_PIPE" pipe
printf "全链路基准:Haskell 引擎 ←命名管道→ Rust 存储进程\n"
printf "存储进程:%s\n" serverPath
printf "管道:%s\n" pipe
printf "数据目录:%s\n" dataDir
printf "数据规模:users = %d 行,orders = %d 行\n" u o
printf "计时:墙上时钟(单调);每条语句都走完整链路\n"
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"
bracket
(do
t1 <- getMonotonicTime
ph <- startServer serverPath pipe dataDir
t2 <- getMonotonicTime
printf "\n[启动] 存储进程就绪,用时 %.2f s\n" (t2 - t1)
printf "\n[startup] storage ready in %.2f s\n" (t2 - t1)
pure ph
)
(\ph -> do
putStrLn "\n[收尾] 停存储进程、清理数据目录"
putStrLn "\n[teardown] stopping storage and removing the data dir"
stopServer ph dataDir
)
(const (runScenarios u o))