完成rust缓冲池,服务端部分收尾

This commit is contained in:
LIm1tZz7 committed 2026-09-24 23:56:07 +08:00
1 parent 2a4b446252
commit 7f5bcc289e
18 files changed
+482 -316

No files matched your search

+7 -7
View File
@@ -43,16 +43,16 @@
> 如果找到了较好的Rust+Haskell集成方法也会及时替换 > 如果找到了较好的Rust+Haskell集成方法也会及时替换
# 性能基准 # 性能基准
**测试环境**:Windows / GHC 9.10.3 / `-O2` **测试环境**:Windows / GHC 9.10.3 / `-O2`
**数据规模**:`users`、`orders` 各 500 行,两表配对共 25 万对 **数据规模**:`users`、`orders` 各 500 行,两表配对共 25 万对
**计时方式**:CPU 时间,自适应批量(累计超过 0.3 秒后折算成单次耗时) **计时方式**:CPU 时间;每批至少 8 次迭代、累计超过 0.3 秒后固定批次,取 3 批中位数(避开时钟量化误差)
| 查询 | 结果行数 | 未优化 | 优化后 | 加速 | | 查询 | 结果行数 | 未优化 | 优化后 | 加速 |
| ------------------------------------------ | -------: | -------: | ----------: | --------: | | ------------------------------------------ | -------: | -------: | ----------: | --------: |
| `JOIN` + `WHERE u.age > 90` | 45 | 242.2 ms | **16.6 ms** | **14.6x** | | `JOIN` + `WHERE u.age > 90` | 45 | 156.3 ms | **12.2 ms** | **12.8x** |
| `JOIN`(无 WHERE,只有投影) | 500 | 210.9 ms | 195.3 ms | 1.1x | | `JOIN`(无 WHERE,只有投影) | 500 | 169.9 ms | 146.5 ms | 1.2x |
| `JOIN` + `WHERE` + `ORDER BY` + `LIMIT 10` | 10 | 164.1 ms | **14.2 ms** | **11.6x** | | `JOIN` + `WHERE` + `ORDER BY` + `LIMIT 10` | 10 | 175.8 ms | **13.7 ms** | **12.9x** |
| 单表 `WHERE age > 90`(对照组) | 45 | 0.175 ms | 0.290 ms | 0.6x | | 单表 `WHERE age > 90`(对照组) | 45 | 0.183 ms | 0.275 ms | 0.7x |
> 最近更新:2026-09-22 > 跑法:`cd benchmark/chusql-engine && stack run`(可选参数:`用户行数 订单数 单批目标秒数`,默认 `500 500 0.3`)
> 最近更新:2026-09-24
+33 -8
View File
@@ -7,6 +7,7 @@ import ChuSQL.Model
import ChuSQL.Syntax.Parser (parseStatement) import ChuSQL.Syntax.Parser (parseStatement)
import Control.Exception (evaluate) import Control.Exception (evaluate)
import Data.IORef (IORef, newIORef, readIORef) import Data.IORef (IORef, newIORef, readIORef)
import Data.List (sort)
import System.CPUTime (getCPUTime) import System.CPUTime (getCPUTime)
import System.Environment (getArgs) import System.Environment (getArgs)
import Text.Printf (printf) import Text.Printf (printf)
@@ -79,14 +80,33 @@ timed act = do
t1 <- getCPUTime t1 <- getCPUTime
return (fromIntegral (t1 - t0) / 1e12) return (fromIntegral (t1 - t0) / 1e12)
-- | 加倍批量次数再折算单次 -- | 每批至少跑几次(减少 CPU 时钟量化误差)
minBatch :: Int
minBatch = 8
-- | 取几批中位数
rounds :: Int
rounds = 3
-- | 跑一批 k 次,返回总耗时
timeBatch :: Int -> IO Int -> IO Double
timeBatch k act = timed (mapM_ (const act) [1 .. k])
-- | 中位数
median :: [Double] -> Double
median xs = sort xs !! (length xs `div` 2)
-- | 自适应批量(够长且至少 8 次),取 3 批中位数
benchOp :: Double -> IO Int -> IO (Double, Int) benchOp :: Double -> IO Int -> IO (Double, Int)
benchOp target act = loop 1 benchOp target act = do
k <- pickBatch 1
ts <- mapM (const (timeBatch k act)) [1 .. rounds]
return (median ts / fromIntegral k, k)
where where
-- \| 批次翻倍直到超过目标 -- \| 批次翻倍直到累计超过目标
loop k = do pickBatch k = do
t <- timed (mapM_ (const act) [1 .. k]) t <- timeBatch k act
if t < target then loop (k * 2) else return (t / fromIntegral k, k) if t < target || k < minBatch then pickBatch (k * 2) else return k
-- | 打印一个用例的对比 -- | 打印一个用例的对比
report :: IORef String -> Database -> Double -> String -> IO () report :: IORef String -> Database -> Double -> String -> IO ()
@@ -105,7 +125,12 @@ report saltRef db target sql = do
(tOp * 1000) (tOp * 1000)
speedup speedup
mismatch mismatch
printf " (实测批次:未优化 %d 次/批,优化后 %d 次/批)\n\n" kUn kOp printf
" (%d 次/批 × %d 批取中位数:未优化 %d 次/批,优化后 %d 次/批)\n\n"
minBatch
rounds
kUn
kOp
queries :: [String] queries :: [String]
queries = queries =
@@ -128,7 +153,7 @@ main = do
target = pick 2 (0.3 :: Double) target = pick 2 (0.3 :: Double)
db = [("users", mkUsers n), ("orders", mkOrders n m)] db = [("users", mkUsers n), ("orders", mkOrders n m)]
printf "数据规模:users = %d 行,orders = %d 行\n" n m printf "数据规模:users = %d 行,orders = %d 行\n" n m
printf "计时方式:CPU 时间;每个用例自动加倍批量次数,直到一批累计耗时超过 %.2f 秒,再折算成单次耗时\n" target printf "计时方式:CPU 时间;每批至少 %d 次迭代、累计超过 %.2f 秒后固定批次,取 %d 批中位数\n" minBatch target rounds
printf "(先跑一遍预热,避免把首次构造数据的开销算进去)\n\n" printf "(先跑一遍预热,避免把首次构造数据的开销算进去)\n\n"
saltRef <- newIORef "" saltRef <- newIORef ""
_ <- runOnce saltRef db warmupSql False _ <- runOnce saltRef db warmupSql False
+44 -25
View File
@@ -6,11 +6,12 @@ import ChuSQL.Algebra.Sort (sortRows)
import ChuSQL.Model import ChuSQL.Model
import ChuSQL.Storage (MonadStorage (..)) import ChuSQL.Storage (MonadStorage (..))
import ChuSQL.Syntax.AST (Expr (..)) import ChuSQL.Syntax.AST (Expr (..))
import Control.Monad (filterM) import Control.Monad (filterM, foldM)
-- 执行:按算子树算出结果行(Scan 阶段会加别名前缀)。 -- 执行:按算子树算出结果行(Scan 阶段会加别名前缀)。
-- * 工具 -- * 工具
-- | 只保留清单里的列 -- | 只保留清单里的列
project :: [String] -> Row -> Row project :: [String] -> Row -> Row
project ["*"] row = row project ["*"] row = row
@@ -31,7 +32,21 @@ evalScan db mAlias tbl = do
let prefix = maybe "" (++ ".") mAlias let prefix = maybe "" (++ ".") mAlias
Right (map (addPrefix prefix) (tableRows table)) Right (map (addPrefix prefix) (tableRows table))
-- | 把两边的行按条件配对(边配边筛,不先建整张积表)
filterPairs :: (Row -> Either String Bool) -> [Row] -> [Row] -> Either String [Row]
filterPairs cond lrows rrows = go lrows []
where
go [] acc = Right (reverse acc)
go (l : ls) acc = do
acc' <- foldM (keep l) acc rrows
go ls acc'
keep l acc r = do
let row = l ++ r
ok <- cond row
pure (if ok then row : acc else acc)
-- * 求值 -- * 求值
-- | 纯求值(不需要存储) -- | 纯求值(不需要存储)
evalRelOp :: Database -> RelOp -> Either String [Row] evalRelOp :: Database -> RelOp -> Either String [Row]
evalRelOp db (Scan mAlias tbl) = evalScan db mAlias tbl evalRelOp db (Scan mAlias tbl) = evalScan db mAlias tbl
@@ -53,37 +68,41 @@ evalRelOp db (Limit n op) = do
evalRelOp db (Join left right cond) = do evalRelOp db (Join left right cond) = do
lrows <- evalRelOp db left lrows <- evalRelOp db left
rrows <- evalRelOp db right rrows <- evalRelOp db right
let cross = [l ++ r | l <- lrows, r <- rrows] filterPairs (evalCondForRow cond) lrows rrows
filterM (evalCondForRow cond) cross
-- | 单子求值:Lookup 问存储 -- | 单子求值:Lookup 问存储
evalRelOpM :: (MonadStorage m) => Database -> RelOp -> m (Either String [Row]) evalRelOpM :: (MonadStorage m) => RelOp -> m (Either String [Row])
evalRelOpM db (Scan a t) = pure (evalScan db a t) evalRelOpM (Scan mAlias t) = do
evalRelOpM _ (Lookup t k) = do result <- scan t
pure $ do
rows <- result
case mAlias of
Nothing -> Right rows
Just a -> Right (map (addPrefix (a ++ ".")) rows)
evalRelOpM (Lookup t k) = do
result <- lookupByKey t k result <- lookupByKey t k
pure $ case result of pure $ case result of
Left e -> Left e Left e -> Left e
Right Nothing -> Right [] Right Nothing -> Right []
Right (Just r) -> Right [r] Right (Just row) -> Right [row]
evalRelOpM db (Filter p x) = do evalRelOpM (Filter p x) = do
result <- evalRelOpM db x result <- evalRelOpM x
pure $ do pure $ do
rows <- result rows <- result
filterM (evalCondForRow p) rows filterM (evalCondForRow p) rows
evalRelOpM db (Project cols x) = do evalRelOpM (Project cols x) = do
result <- evalRelOpM db x result <- evalRelOpM x
pure (map (project cols) <$> result) pure (fmap (map (project cols)) result)
evalRelOpM db (Sort specs x) = do evalRelOpM (Sort spec x) = do
result <- evalRelOpM db x result <- evalRelOpM x
pure (sortRows specs <$> result) pure (fmap (sortRows spec) result)
evalRelOpM db (Limit n x) = do evalRelOpM (Limit n x) = do
result <- evalRelOpM db x result <- evalRelOpM x
pure (take n <$> result) pure (fmap (take n) result)
evalRelOpM db (Join l r c) = do evalRelOpM (Join l r cond) = do
ls <- evalRelOpM db l ls <- evalRelOpM l
rs <- evalRelOpM db r rs <- evalRelOpM r
pure $ do pure $ do
leftRows <- ls left <- ls
rightRows <- rs right <- rs
let cross = [x ++ y | x <- leftRows, y <- rightRows] filterPairs (evalCondForRow cond) left right
filterM (evalCondForRow c) cross
+18 -13
View File
@@ -19,21 +19,26 @@ compareValue (VStr a) (VStr b) = compare a b
compareValue (VBool a) (VBool b) = compare a b compareValue (VBool a) (VBool b) = compare a b
compareValue _ _ = EQ compareValue _ _ = EQ
-- | 按排序要求比较两行 -- | 取出排序键(按 spec 顺序,每行只查一次)
compareRows :: [(String, SortDir)] -> Row -> Row -> Ordering sortKey :: [(String, SortDir)] -> Row -> [Maybe Value]
compareRows [] _ _ = EQ sortKey spec row = [lookup col row | (col, _) <- spec]
compareRows ((col, dir) : rest) r1 r2 =
case (lookup col r1, lookup col r2) of -- | 比较两个排序键
compareKeys :: [(String, SortDir)] -> [Maybe Value] -> [Maybe Value] -> Ordering
compareKeys [] _ _ = EQ
compareKeys ((_, dir) : rest) (a : as) (b : bs) =
case (a, b) of
(Just v1, Just v2) -> (Just v1, Just v2) ->
case compareValue v1 v2 of case compareValue v1 v2 of
EQ -> compareRows rest r1 r2 EQ -> compareKeys rest as bs
o -> o -> if dir == Desc then flipOrdering o else o
if dir == Desc _ -> compareKeys rest as bs
then flipOrdering o compareKeys _ _ _ = EQ
else o
_ -> compareRows rest r1 r2
-- | 排序若干行 -- | 排序若干行(先取键再排,避免每次比较都查列)
sortRows :: [(String, SortDir)] -> [Row] -> [Row] sortRows :: [(String, SortDir)] -> [Row] -> [Row]
sortRows [] rows = rows sortRows [] rows = rows
sortRows spec rows = sortBy (compareRows spec) rows sortRows spec rows = map snd (sortBy cmp (map decorate rows))
where
decorate row = (sortKey spec row, row)
cmp (k1, _) (k2, _) = compareKeys spec k1 k2
+17 -16
View File
@@ -14,6 +14,7 @@ import Control.Monad (filterM)
-- 引擎入口:语义检查 + 分发执行,以及内存实现与泛型版本。 -- 引擎入口:语义检查 + 分发执行,以及内存实现与泛型版本。
-- * 对外入口 -- * 对外入口
-- | 跑一条语句(内存实现) -- | 跑一条语句(内存实现)
runStatement :: Database -> Statement -> Either String (Database, [Row]) runStatement :: Database -> Statement -> Either String (Database, [Row])
runStatement db q = do runStatement db q = do
@@ -25,35 +26,36 @@ runStatement db q = do
rowsOf :: Either String (Database, [Row]) -> Either String [Row] rowsOf :: Either String (Database, [Row]) -> Either String [Row]
rowsOf = fmap snd rowsOf = fmap snd
-- | 泛型入口:先检查再执行 -- | 泛型入口:先检查再执行
runStatementM :: (MonadStorage m) => Statement -> m (Either String [Row]) runStatementM :: (MonadStorage m) => Statement -> m (Either String [Row])
runStatementM q = do runStatementM q = do
db <- snapshot db <- snapshot
case check db q of case check db q of
Left err -> pure (Left err) Left err -> pure (Left err)
Right _ -> runStatementUncheckedM q Right _ -> runStatementUncheckedM db q
-- * 分发执行 -- * 分发执行
-- | 按语句类型分发(已检查过) -- | 按语句类型分发(已检查过)
runStatementUncheckedM :: (MonadStorage m) => Statement -> m (Either String [Row]) runStatementUncheckedM :: (MonadStorage m) => Database -> Statement -> m (Either String [Row])
runStatementUncheckedM q@Select{} = do runStatementUncheckedM db q@Select{} =
db <- snapshot
case translate q of case translate q of
Left e -> pure (Left e) Left e -> pure (Left e)
Right relOp -> evalRelOpM db (optimize db relOp) Right relOp -> evalRelOpM (optimize db relOp)
runStatementUncheckedM _ (Insert tbl cols vals) =
runStatementUncheckedM (Insert tbl cols vals) =
case mapM (\e -> evalExpr e []) vals of case mapM (\e -> evalExpr e []) vals of
Left err -> pure (Left err) Left err -> pure (Left err)
Right values Right values
| length cols /= length values -> | length cols /= length values ->
pure (Left "column count does not match value count") pure (Left "column count does not match value count")
| otherwise -> do | otherwise -> do
result <- insert tbl (zip cols values) let row = zip cols values
key = case lookup "id" row of
Just (VInt k) -> Just k
_ -> Nothing
result <- insert tbl row key
pure (result >> Right []) pure (result >> Right [])
runStatementUncheckedM _ (Delete tbl mWhere) = do
runStatementUncheckedM (Delete tbl mWhere) = do
rowsResult <- scan tbl rowsResult <- scan tbl
case rowsResult of case rowsResult of
Left err -> pure (Left err) Left err -> pure (Left err)
@@ -70,8 +72,7 @@ runStatementUncheckedM (Delete tbl mWhere) = do
-- \| 保留 = 条件不成立 -- \| 保留 = 条件不成立
shouldKeep :: Expr -> Row -> Either String Bool shouldKeep :: Expr -> Row -> Either String Bool
shouldKeep e row = not <$> evalCondForRow e row shouldKeep e row = not <$> evalCondForRow e row
runStatementUncheckedM _ (Update tbl assigns mWhere) = do
runStatementUncheckedM (Update tbl assigns mWhere) = do
rowsResult <- scan tbl rowsResult <- scan tbl
case rowsResult of case rowsResult of
Left err -> pure (Left err) Left err -> pure (Left err)
@@ -90,15 +91,15 @@ runStatementUncheckedM (Update tbl assigns mWhere) = do
Nothing -> Right True Nothing -> Right True
Just e -> evalCondForRow e row Just e -> evalCondForRow e row
if keep then applyUpdates asgns row else Right row if keep then applyUpdates asgns row else Right row
runStatementUncheckedM _ (CreateTable name cols) = do
runStatementUncheckedM (CreateTable name cols) = do
result <- createTable name cols result <- createTable name cols
pure (result >> Right []) pure (result >> Right [])
runStatementUncheckedM (DropTable name) = do runStatementUncheckedM _ (DropTable name) = do
result <- dropTable name result <- dropTable name
pure (result >> Right []) pure (result >> Right [])
-- * 更新辅助 -- * 更新辅助
-- | 依次求值并覆盖列 -- | 依次求值并覆盖列
applyUpdates :: [(String, Expr)] -> Row -> Either String Row applyUpdates :: [(String, Expr)] -> Row -> Either String Row
applyUpdates [] row = Right row applyUpdates [] row = Right row
+1 -1
View File
@@ -12,7 +12,7 @@ class (Monad m) => MonadStorage m where
scan :: String -> m (Either String [Row]) scan :: String -> m (Either String [Row])
-- \| 追加一行 -- \| 追加一行
insert :: String -> Row -> m (Either String ()) insert :: String -> Row -> Maybe Int -> m (Either String ())
-- \| 整表替换 -- \| 整表替换
replaceAll :: String -> [Row] -> m (Either String ()) replaceAll :: String -> [Row] -> m (Either String ())
+107 -90
View File
@@ -7,17 +7,19 @@ module ChuSQL.Storage.IPC (
Response (..), Response (..),
SchemaColumn (..), SchemaColumn (..),
sendRequest, sendRequest,
closeConnection,
doListTables, doListTables,
doLookupByKey, doLookupByKey,
doInsertKeyed, doInsertWith,
doDescribeTable, doDescribeTable,
doCreateTable,
doDropTable, doDropTable,
doListCatalog,
) where ) where
import ChuSQL.Model import ChuSQL.Model
import ChuSQL.Storage import ChuSQL.Storage
import Control.Exception (IOException, bracket, try) import Control.Concurrent.MVar (MVar, modifyMVar, modifyMVar_, newMVar)
import Control.Exception (IOException, try)
import Data.Aeson (FromJSON (..), ToJSON (..), eitherDecodeStrict, encode, object, withObject, (.:), (.=)) import Data.Aeson (FromJSON (..), ToJSON (..), eitherDecodeStrict, encode, object, withObject, (.:), (.=))
import Data.Aeson qualified as A import Data.Aeson qualified as A
import Data.Aeson.Key qualified as K import Data.Aeson.Key qualified as K
@@ -25,15 +27,16 @@ import Data.Aeson.KeyMap qualified as KM
import Data.Aeson.Types (Parser) import Data.Aeson.Types (Parser)
import Data.ByteString.Char8 qualified as BSC import Data.ByteString.Char8 qualified as BSC
import Data.ByteString.Lazy qualified as BL import Data.ByteString.Lazy qualified as BL
import Data.List (nub)
import Data.Scientific (floatingOrInteger) import Data.Scientific (floatingOrInteger)
import Data.Text qualified as T import Data.Text qualified as T
import System.Environment (lookupEnv) import System.Environment (lookupEnv)
import System.IO import System.IO
import System.IO.Unsafe (unsafePerformIO)
-- IPC 存储实现:每个存储方法翻成管道上的一条 JSON 请求。 -- IPC 存储实现:每个存储方法翻成管道上的一条 JSON 请求。
-- * 存储实现 -- * 存储实现
-- | 命名管道上的存储 -- | 命名管道上的存储
newtype IPCStorage a = IPCStorage newtype IPCStorage a = IPCStorage
{ runIPCStorage :: IO a { runIPCStorage :: IO a
@@ -54,45 +57,80 @@ instance Monad IPCStorage where
a <- m a <- m
runIPCStorage (k a) runIPCStorage (k a)
-- * 全局连接缓存
-- | 全局句柄缓存:按管道路径缓存,路径变了就换连接。
{-# NOINLINE connectionRef #-}
connectionRef :: MVar (Maybe (FilePath, Handle))
connectionRef = unsafePerformIO (newMVar Nothing)
-- | 打开管道
openPipe :: FilePath -> IO Handle
openPipe path = do
h <- openFile path ReadWriteMode
hSetBuffering h LineBuffering
hSetNewlineMode h noNewlineTranslation
pure h
-- | 关掉缓存里的句柄(没有就什么都不做)
closeCached :: Maybe (FilePath, Handle) -> IO ()
closeCached Nothing = pure ()
closeCached (Just (_, h)) = hClose h
-- | 拿一个可用句柄;路径没变就复用,变了就换。
getConnection :: FilePath -> IO Handle
getConnection path = modifyMVar connectionRef $ \cached -> case cached of
Just (p, h)
| p == path -> pure (Just (p, h), h)
_ -> do
_ <- try (closeCached cached) :: IO (Either IOException ())
h <- openPipe path
pure (Just (path, h), h)
-- | 丢掉缓存的连接(出错后调用,下次会重连)
dropConnection :: IO ()
dropConnection = modifyMVar_ connectionRef $ \cached -> do
_ <- try (closeCached cached) :: IO (Either IOException ())
pure Nothing
-- | 关闭并清空连接。可选,进程退出前调用。
closeConnection :: IO ()
closeConnection = dropConnection
-- * 管道 -- * 管道
-- | 管道路径 -- | 管道路径
pipePath :: IO String pipePath :: IO String
pipePath = do pipePath = do
name <- maybe "chusql-storage" id <$> lookupEnv "CHUSQL_PIPE" name <- maybe "chusql-storage" id <$> lookupEnv "CHUSQL_PIPE"
pure ("\\\\.\\pipe\\" ++ name) pure ("\\\\.\\pipe\\" ++ name)
-- | 开管道跑闭包 -- | 发一条请求读一条响应;管道不通就回 RespError,并丢掉缓存等下次重连。
withPipe :: (Handle -> IO a) -> IO a
withPipe = bracket openPipe hClose
where
-- \| 打开管道句柄
openPipe = do
path <- pipePath
h <- openFile path ReadWriteMode
hSetBuffering h LineBuffering
hSetNewlineMode h noNewlineTranslation
pure h
-- | 发一条请求读一条响应
sendRequest :: Request -> IO Response sendRequest :: Request -> IO Response
sendRequest req = do sendRequest req = do
result <- try (withPipe roundTrip) :: IO (Either IOException Response) path <- pipePath
pure $ case result of result <- try (roundTrip req path) :: IO (Either IOException Response)
Left e -> RespError ("pipe error: " ++ show e) case result of
Right r -> r Left e -> do
where dropConnection
roundTrip h = do pure (RespError ("pipe error: " ++ show e))
BL.hPutStr h (encode req) Right r -> pure r
BSC.hPutStr h "\n"
hFlush h
line <- BSC.hGetLine h
let cleaned = BSC.dropWhileEnd (== '\r') line
case eitherDecodeStrict cleaned of
Left err -> pure (RespError ("decode: " ++ err))
Right r -> pure r
-- | 一条请求-响应往返(复用缓存里的句柄)
roundTrip :: Request -> FilePath -> IO Response
roundTrip req path = do
h <- getConnection path
BL.hPutStr h (encode req)
BSC.hPutStr h "\n"
hFlush h
line <- BSC.hGetLine h
let cleaned = BSC.dropWhileEnd (== '\r') line
case eitherDecodeStrict cleaned of
Left err -> pure (RespError ("decode: " ++ err))
Right r -> pure r
-- * 编解码 -- * 编解码
-- | 一列的 schema -- | 一列的 schema
data SchemaColumn = SchemaColumn data SchemaColumn = SchemaColumn
{ scName :: String { scName :: String
@@ -112,6 +150,7 @@ instance FromJSON SchemaColumn where
pure (SchemaColumn n t) pure (SchemaColumn n t)
-- * 请求 -- * 请求
-- | 请求类型 -- | 请求类型
data Request data Request
= ReqPing = ReqPing
@@ -123,6 +162,7 @@ data Request
| ReqDescribeTable String | ReqDescribeTable String
| ReqCreateTable String [SchemaColumn] | ReqCreateTable String [SchemaColumn]
| ReqDropTable String | ReqDropTable String
| ReqListCatalog
-- | 请求编码 -- | 请求编码
instance ToJSON Request where instance ToJSON Request where
@@ -163,7 +203,7 @@ instance ToJSON Request where
] ]
toJSON (ReqDropTable t) = toJSON (ReqDropTable t) =
object ["method" .= ("drop_table" :: T.Text), "table" .= t] object ["method" .= ("drop_table" :: T.Text), "table" .= t]
toJSON ReqListCatalog = object ["method" .= ("list_catalog" :: T.Text)]
-- | 响应类型 -- | 响应类型
data Response data Response
@@ -173,6 +213,7 @@ data Response
| RespSchema [SchemaColumn] Int | RespSchema [SchemaColumn] Int
| RespOk | RespOk
| RespError String | RespError String
| RespCatalog [(String, [SchemaColumn], Int)]
-- | 响应解码 -- | 响应解码
instance FromJSON Response where instance FromJSON Response where
@@ -188,7 +229,18 @@ instance FromJSON Response where
cols <- o .: "columns" cols <- o .: "columns"
cnt <- o .: "row_count" cnt <- o .: "row_count"
pure (RespSchema cols cnt) pure (RespSchema cols cnt)
"catalog" -> do
xs <- o .: "schemas"
parsed <- mapM parseTableSchema xs
pure (RespCatalog parsed)
other -> fail ("unknown status: " ++ T.unpack other) other -> fail ("unknown status: " ++ T.unpack other)
where
-- \| 解析一条 list_catalog 记录。
parseTableSchema = withObject "TableSchema" $ \o -> do
t <- o .: "table"
cols <- o .: "columns"
cnt <- o .: "row_count"
pure (t, cols, cnt)
-- | 值编码成 JSON -- | 值编码成 JSON
valueToJSON :: Value -> A.Value valueToJSON :: Value -> A.Value
@@ -221,8 +273,8 @@ rowFromJSON (A.Object o) = mapM toPair (KM.toList o)
pure (K.toString k, val) pure (K.toString k, val)
rowFromJSON _ = fail "row must be a JSON object" rowFromJSON _ = fail "row must be a JSON object"
-- * 内部封装 -- * 内部封装
-- | 发 Scan -- | 发 Scan
doScan :: String -> IO (Either String [Row]) doScan :: String -> IO (Either String [Row])
doScan t = do doScan t = do
@@ -232,25 +284,10 @@ doScan t = do
RespError e -> Left e RespError e -> Left e
_ -> Left "unexpected response to scan" _ -> Left "unexpected response to scan"
-- | 发 Insert(带 id 键) -- | 发 Insert,可带索引键。
doInsert :: String -> Row -> IO (Either String ()) doInsertWith :: String -> Row -> Maybe Int -> IO (Either String ())
doInsert t r = do doInsertWith t r mk = do
resp <- sendRequest (ReqInsert t r (rowKey r)) resp <- sendRequest (ReqInsert t r mk)
pure $ case resp of
RespOk -> Right ()
RespError e -> Left e
_ -> Left "unexpected response to insert"
-- | 取行里的 id 作索引键
rowKey :: Row -> Maybe Int
rowKey r = case lookup "id" r of
Just (VInt k) -> Just k
_ -> Nothing
-- | 发 Insert(指定键)
doInsertKeyed :: String -> Row -> Int -> IO (Either String ())
doInsertKeyed t r k = do
resp <- sendRequest (ReqInsert t r (Just k))
pure $ case resp of pure $ case resp of
RespOk -> Right () RespOk -> Right ()
RespError e -> Left e RespError e -> Left e
@@ -274,6 +311,15 @@ doListTables = do
RespError e -> Left e RespError e -> Left e
_ -> Left "unexpected response to list_tables" _ -> Left "unexpected response to list_tables"
-- | 一次拿全库 schema(表名 + 列 + 行数)。
doListCatalog :: IO (Either String [(String, [SchemaColumn], Int)])
doListCatalog = do
resp <- sendRequest ReqListCatalog
pure $ case resp of
RespCatalog xs -> Right xs
RespError e -> Left e
_ -> Left "unexpected response to list_catalog"
-- | 发 LookupByIndex -- | 发 LookupByIndex
doLookupByKey :: String -> Int -> IO (Either String (Maybe Row)) doLookupByKey :: String -> Int -> IO (Either String (Maybe Row))
doLookupByKey t k = do doLookupByKey t k = do
@@ -312,6 +358,7 @@ doDropTable t = do
_ -> Left "unexpected response to drop_table" _ -> Left "unexpected response to drop_table"
-- * 表结构 -- * 表结构
-- | 线上 schema 转本地列 -- | 线上 schema 转本地列
schemaToColumns :: [SchemaColumn] -> [(String, Column)] schemaToColumns :: [SchemaColumn] -> [(String, Column)]
schemaToColumns = map go schemaToColumns = map go
@@ -321,45 +368,15 @@ schemaToColumns = map go
go (SchemaColumn n "bool") = (n, TBool) go (SchemaColumn n "bool") = (n, TBool)
go (SchemaColumn n _) = (n, TStr) go (SchemaColumn n _) = (n, TStr)
-- | 从行推断列和类型
inferColumns :: [Row] -> [(String, Column)]
inferColumns rows = [(n, inferType n) | n <- names]
where
-- \| 出现过的列名
names = nub (concatMap (map fst) rows)
-- \| 按第一个值推断类型
inferType n = case [v | r <- rows, Just v <- [lookup n r]] of
(VInt _ : _) -> TInt
(VStr _ : _) -> TStr
(VBool _ : _) -> TBool
[] -> TStr
-- | 扫一张表拼成 Table
loadTable :: String -> IO Table
loadTable t = do
schemaR <- doDescribeTable t
case schemaR of
Right (cols, _n) -> do
rowsR <- doScan t
case rowsR of
Right rows -> pure (Table t (schemaToColumns cols) rows)
Left _ -> pure (Table t (schemaToColumns cols) [])
Left _ -> do
rowsR <- doScan t
case rowsR of
Right rows -> pure (Table t (inferColumns rows) rows)
Left _ -> pure (Table t [] [])
-- * MonadStorage 实例 -- * MonadStorage 实例
-- | 存储方法全走管道 -- | 存储方法全走管道
instance MonadStorage IPCStorage where instance MonadStorage IPCStorage where
-- \| 发 Scan -- \| 发 Scan
scan t = IPCStorage (doScan t) scan t = IPCStorage (doScan t)
-- \| 发 Insert -- \| 发 Insert
insert t r = IPCStorage (doInsert t r) insert t r mk = IPCStorage (doInsertWith t r mk)
-- \| 发 ReplaceAll -- \| 发 ReplaceAll
replaceAll t rs = IPCStorage (doReplaceAll t rs) replaceAll t rs = IPCStorage (doReplaceAll t rs)
@@ -380,12 +397,12 @@ instance MonadStorage IPCStorage where
-- \| 列表 + 逐表扫描拼库 -- \| 列表 + 逐表扫描拼库
snapshot = IPCStorage $ do snapshot = IPCStorage $ do
result <- doListTables result <- doListCatalog
case result of case result of
Left _ -> pure [] Left _ -> pure []
Right ts -> mapM loadDbEntry ts Right xs -> mapM loadEntry xs
where where
-- \| 加载一个 (表名, 表) 对 -- \| 加载一张表(schema 来自 catalog,行来自 scan)
loadDbEntry t = do loadEntry (t, cols, _n) = do
tbl <- loadTable t rows <- doScan t
pure (t, tbl) pure (t, Table t (schemaToColumns cols) (either (const []) id rows))
+5 -4
View File
@@ -2,10 +2,12 @@ module ChuSQL.Storage.Memory (MemoryStorage (runMemoryStorage)) where
import ChuSQL.Model import ChuSQL.Model
import ChuSQL.Storage import ChuSQL.Storage
import Data.List (find)
-- 内存实现:状态是 Database,错误通道是 Either String。 -- 内存实现:状态是 Database,错误通道是 Either String。
-- * 实例 -- * 实例
-- | 内存存储:Database 上的状态 -- | 内存存储:Database 上的状态
newtype MemoryStorage a = MemoryStorage {runMemoryStorage :: Database -> Either String (a, Database)} newtype MemoryStorage a = MemoryStorage {runMemoryStorage :: Database -> Either String (a, Database)}
@@ -37,7 +39,7 @@ instance MonadStorage MemoryStorage where
Right (Right (tableRows tbl), db) Right (Right (tableRows tbl), db)
-- \| 追加一行 -- \| 追加一行
insert t r = MemoryStorage $ \db -> do insert t r _ = MemoryStorage $ \db -> do
tbl <- lookupTable db t tbl <- lookupTable db t
Right (Right (), replaceTable t tbl{tableRows = tableRows tbl ++ [r]} db) Right (Right (), replaceTable t tbl{tableRows = tableRows tbl ++ [r]} db)
@@ -67,11 +69,10 @@ instance MonadStorage MemoryStorage where
snapshot = MemoryStorage $ \db -> Right (db, db) snapshot = MemoryStorage $ \db -> Right (db, db)
-- * 工具 -- * 工具
-- | 按 id 列找一行 -- | 按 id 列找一行
findRow :: Table -> Int -> Maybe Row findRow :: Table -> Int -> Maybe Row
findRow tbl k = case filter matches (tableRows tbl) of findRow tbl k = find matches (tableRows tbl)
(r : _) -> Just r
[] -> Nothing
where where
-- \| 这行的 id 是否等于 k -- \| 这行的 id 是否等于 k
matches r = lookup "id" r == Just (VInt k) matches r = lookup "id" r == Just (VInt k)
+4
View File
@@ -5,6 +5,7 @@ import ChuSQL.Model (Column (..))
-- 语法树:语句、数据来源、条件表达式、排序方向。 -- 语法树:语句、数据来源、条件表达式、排序方向。
-- * 语句 -- * 语句
-- | 一条语句:查 / 加 / 删 / 改 / 建表 / 删表 -- | 一条语句:查 / 加 / 删 / 改 / 建表 / 删表
data Statement data Statement
= Select = Select
@@ -28,6 +29,7 @@ data FromClause
deriving (Show, Eq) deriving (Show, Eq)
-- * 表达式 -- * 表达式
-- | 条件表达式 -- | 条件表达式
data Expr data Expr
= Col String = Col String
@@ -42,6 +44,7 @@ data Expr
deriving (Show, Eq) deriving (Show, Eq)
-- * 排序 -- * 排序
-- | 排序方向 -- | 排序方向
data SortDir data SortDir
= Asc = Asc
@@ -49,6 +52,7 @@ data SortDir
deriving (Show, Eq) deriving (Show, Eq)
-- * 构造 -- * 构造
-- | 造一个最简单的 SELECT -- | 造一个最简单的 SELECT
makeSelect :: [String] -> String -> Maybe Expr -> Statement makeSelect :: [String] -> String -> Maybe Expr -> Statement
makeSelect cols tbl w = makeSelect cols tbl w =
+55 -18
View File
@@ -11,8 +11,9 @@ import ChuSQL.Storage.IPC (IPCStorage (runIPCStorage), Request (ReqPing), Respon
import ChuSQL.Syntax.AST import ChuSQL.Syntax.AST
import ChuSQL.Syntax.Parser import ChuSQL.Syntax.Parser
import Control.Concurrent (threadDelay) import Control.Concurrent (threadDelay)
import Control.Exception (IOException, bracket, try) import Control.Exception (IOException, bracket, finally, try)
import Data.List (isInfixOf, sortOn) import Data.List (isInfixOf, sortOn)
import Data.Unique (hashUnique, newUnique)
import System.CPUTime (getCPUTime) import System.CPUTime (getCPUTime)
import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getTemporaryDirectory, removePathForcibly) import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getTemporaryDirectory, removePathForcibly)
import System.Environment (getEnvironment, lookupEnv, setEnv, unsetEnv) import System.Environment (getEnvironment, lookupEnv, setEnv, unsetEnv)
@@ -340,7 +341,12 @@ main = hspec $ do
it "returns Left when a value has the wrong type for comparison" $ do it "returns Left when a value has the wrong type for comparison" $ do
runStatement testDB (Insert "users" ["name"] [Col "other"]) runStatement testDB (Insert "users" ["name"] [Col "other"])
`shouldSatisfy` isLeft `shouldSatisfy` isLeft
it "writes id column to index and queries it back" $ do
case parseStatement "INSERT INTO users (id, name, age) VALUES (42, 'Zoe', 20)" >>= runStatement testDB of
Left err -> expectationFailure err
Right (db', _) -> do
rowsOf (parseStatement "SELECT name FROM users WHERE id = 42" >>= runStatement db')
`shouldBe` Right [[("name", VStr "Zoe")]]
describe "ChuSQL.Engine (DELETE)" $ do describe "ChuSQL.Engine (DELETE)" $ do
it "parses DELETE with WHERE" $ do it "parses DELETE with WHERE" $ do
parseStatement "DELETE FROM users WHERE age < 18" parseStatement "DELETE FROM users WHERE age < 18"
@@ -1102,7 +1108,7 @@ main = hspec $ do
it "scan returns rows with all three value types after insert" $ do it "scan returns rows with all three value types after insert" $ do
withTestServer $ \srv -> withServerEnv srv $ do withTestServer $ \srv -> withServerEnv srv $ do
let row = [("id", VInt 7), ("name", VStr "Alice"), ("flag", VBool True)] let row = [("id", VInt 7), ("name", VStr "Alice"), ("flag", VBool True)]
runIPCStorage (insert "users" row) `shouldReturn` Right () runIPCStorage (insert "users" row (Just 7)) `shouldReturn` Right ()
rows <- runIPCStorage (scan "users") rows <- runIPCStorage (scan "users")
(map sortRow <$> rows) `shouldBe` Right [sortRow row] (map sortRow <$> rows) `shouldBe` Right [sortRow row]
@@ -1113,7 +1119,7 @@ main = hspec $ do
it "scan returns all 20 inserted rows in order" $ do it "scan returns all 20 inserted rows in order" $ do
withTestServer $ \srv -> withServerEnv srv $ do withTestServer $ \srv -> withServerEnv srv $ do
mapM_ (\i -> runIPCStorage (insert "many" [("id", VInt i)])) [1 .. 20 :: Int] mapM_ (\i -> runIPCStorage (insert "many" [("id", VInt i)] (Just i))) [1 .. 20 :: Int]
rows <- runIPCStorage (scan "many") rows <- runIPCStorage (scan "many")
fmap length rows `shouldBe` Right 20 fmap length rows `shouldBe` Right 20
fmap (sortOn show . map (lookup "id")) rows fmap (sortOn show . map (lookup "id")) rows
@@ -1121,7 +1127,7 @@ main = hspec $ do
it "replaceAll leaves only the new rows" $ do it "replaceAll leaves only the new rows" $ do
withTestServer $ \srv -> withServerEnv srv $ do withTestServer $ \srv -> withServerEnv srv $ do
runIPCStorage (insert "t" [("id", VInt 1)]) `shouldReturn` Right () runIPCStorage (insert "t" [("id", VInt 1)] (Just 1)) `shouldReturn` Right ()
runIPCStorage (replaceAll "t" [[("id", VInt 9)], [("id", VInt 8)]]) `shouldReturn` Right () runIPCStorage (replaceAll "t" [[("id", VInt 9)], [("id", VInt 8)]]) `shouldReturn` Right ()
rows <- runIPCStorage (scan "t") rows <- runIPCStorage (scan "t")
fmap (sortOn show . map (lookup "id")) rows fmap (sortOn show . map (lookup "id")) rows
@@ -1130,7 +1136,7 @@ main = hspec $ do
it "lookupByKey finds a row inserted with an id over the pipe" $ do it "lookupByKey finds a row inserted with an id over the pipe" $ do
withTestServer $ \srv -> withServerEnv srv $ do withTestServer $ \srv -> withServerEnv srv $ do
let row = [("id", VInt 7), ("name", VStr "Zoe")] let row = [("id", VInt 7), ("name", VStr "Zoe")]
runIPCStorage (insert "keyed" row) `shouldReturn` Right () runIPCStorage (insert "keyed" row (Just 7)) `shouldReturn` Right ()
found <- runIPCStorage (lookupByKey "keyed" 7) found <- runIPCStorage (lookupByKey "keyed" 7)
fmap (fmap sortRow) found `shouldBe` Right (Just (sortRow row)) fmap (fmap sortRow) found `shouldBe` Right (Just (sortRow row))
missing <- runIPCStorage (lookupByKey "keyed" 8) missing <- runIPCStorage (lookupByKey "keyed" 8)
@@ -1138,7 +1144,7 @@ main = hspec $ do
it "replaceAll rebuilds the index over the pipe" $ do it "replaceAll rebuilds the index over the pipe" $ do
withTestServer $ \srv -> withServerEnv srv $ do withTestServer $ \srv -> withServerEnv srv $ do
runIPCStorage (insert "rb" [("id", VInt 1)]) `shouldReturn` Right () runIPCStorage (insert "rb" [("id", VInt 1)] (Just 1)) `shouldReturn` Right ()
runIPCStorage (replaceAll "rb" [[("id", VInt 9), ("name", VStr "Zoe")]]) `shouldReturn` Right () runIPCStorage (replaceAll "rb" [[("id", VInt 9), ("name", VStr "Zoe")]]) `shouldReturn` Right ()
old <- runIPCStorage (lookupByKey "rb" 1) old <- runIPCStorage (lookupByKey "rb" 1)
old `shouldBe` Right Nothing old `shouldBe` Right Nothing
@@ -1148,8 +1154,8 @@ main = hspec $ do
it "snapshot lists tables and scans each one to build the database" $ do it "snapshot lists tables and scans each one to build the database" $ do
withTestServer $ \srv -> withServerEnv srv $ do withTestServer $ \srv -> withServerEnv srv $ do
runIPCStorage (insert "a" [("id", VInt 1)]) `shouldReturn` Right () runIPCStorage (insert "a" [("id", VInt 1)] (Just 1)) `shouldReturn` Right ()
runIPCStorage (insert "b" [("id", VInt 2)]) `shouldReturn` Right () runIPCStorage (insert "b" [("id", VInt 2)] (Just 2)) `shouldReturn` Right ()
db <- runIPCStorage snapshot db <- runIPCStorage snapshot
map fst db `shouldBe` ["a", "b"] map fst db `shouldBe` ["a", "b"]
map (length . tableRows . snd) db `shouldBe` [1, 1] map (length . tableRows . snd) db `shouldBe` [1, 1]
@@ -1157,8 +1163,8 @@ main = hspec $ do
it "doListTables returns the tables that exist" $ do it "doListTables returns the tables that exist" $ do
withTestServer $ \srv -> withServerEnv srv $ do withTestServer $ \srv -> withServerEnv srv $ do
runIPCStorage (insert "t1" [("id", VInt 1)]) `shouldReturn` Right () runIPCStorage (insert "t1" [("id", VInt 1)] (Just 1)) `shouldReturn` Right ()
runIPCStorage (insert "t2" [("id", VInt 2)]) `shouldReturn` Right () runIPCStorage (insert "t2" [("id", VInt 2)] (Just 2)) `shouldReturn` Right ()
doListTables `shouldReturn` Right ["t1", "t2"] doListTables `shouldReturn` Right ["t1", "t2"]
it "rows survive a restart on the same data directory" $ do it "rows survive a restart on the same data directory" $ do
@@ -1168,7 +1174,7 @@ main = hspec $ do
Right bin -> do Right bin -> do
srv1 <- startServer bin srv1 <- startServer bin
let dir = dataDir srv1 let dir = dataDir srv1
withServerEnv srv1 $ runIPCStorage (insert "persist" [("id", VInt 42)]) `shouldReturn` Right () withServerEnv srv1 $ runIPCStorage (insert "persist" [("id", VInt 42)] (Just 42)) `shouldReturn` Right ()
stopServer srv1 stopServer srv1
srv2 <- startServerAt bin dir srv2 <- startServerAt bin dir
rows <- withServerEnv srv2 $ runIPCStorage (scan "persist") rows <- withServerEnv srv2 $ runIPCStorage (scan "persist")
@@ -1180,6 +1186,31 @@ main = hspec $ do
withPipeName "chusql-no-such-server" $ do withPipeName "chusql-no-such-server" $ do
result <- runIPCStorage (scan "users") result <- runIPCStorage (scan "users")
result `shouldSatisfy` isLeft result `shouldSatisfy` isLeft
it "INSERT then SELECT WHERE id = k via IPC" $ do
withTestServer $ \srv -> withServerEnv srv $ do
_ <-
runIPCStorage
( runStatementM
(CreateTable "ipc_idx" [("id", TInt), ("name", TStr)])
)
_ <-
runIPCStorage
( runStatementM
(Insert "ipc_idx" ["id", "name"] [LitInt 42, LitStr "Zoe"])
)
result <-
runIPCStorage
( runStatementM
( Select
{ selectCols = ["name"]
, selectFrom = FromTable Nothing "ipc_idx"
, selectWhere = Just (Eq (Col "id") (LitInt 42))
, selectOrderBy = []
, selectLimit = Nothing
}
)
)
result `shouldBe` Right [[("name", VStr "Zoe")]]
it "parses CREATE TABLE with two columns" $ do it "parses CREATE TABLE with two columns" $ do
parseStatement "CREATE TABLE users (id INT, name TEXT)" parseStatement "CREATE TABLE users (id INT, name TEXT)"
`shouldBe` Right (CreateTable "users" [("id", TInt), ("name", TStr)]) `shouldBe` Right (CreateTable "users" [("id", TInt), ("name", TStr)])
@@ -1280,7 +1311,7 @@ locateServer = do
startServer :: FilePath -> IO IPCServer startServer :: FilePath -> IO IPCServer
startServer bin = do startServer bin = do
stamp <- show <$> getCPUTime stamp <- uniqueStamp
tmp <- getTemporaryDirectory tmp <- getTemporaryDirectory
let dir = tmp </> "chusql-hs-IPC" </> stamp let dir = tmp </> "chusql-hs-IPC" </> stamp
createDirectoryIfMissing True dir createDirectoryIfMissing True dir
@@ -1288,7 +1319,7 @@ startServer bin = do
startServerAt :: FilePath -> FilePath -> IO IPCServer startServerAt :: FilePath -> FilePath -> IO IPCServer
startServerAt bin dir = do startServerAt bin dir = do
stamp <- show <$> getCPUTime stamp <- uniqueStamp
let name = "chusql-hs-test-" ++ stamp let name = "chusql-hs-test-" ++ stamp
parentEnv <- getEnvironment parentEnv <- getEnvironment
(_, _, _, ph) <- (_, _, _, ph) <-
@@ -1302,6 +1333,13 @@ startServerAt bin dir = do
waitUntilReady srv 100 waitUntilReady srv 100
pure srv pure srv
-- | 每次调用都不同的后缀:CPU 时间精度不够,再加上一个唯一编号。
uniqueStamp :: IO String
uniqueStamp = do
cpu <- getCPUTime
u <- hashUnique <$> newUnique
pure (show cpu ++ "-" ++ show u)
waitUntilReady :: IPCServer -> Int -> IO () waitUntilReady :: IPCServer -> Int -> IO ()
waitUntilReady srv tries = do waitUntilReady srv tries = do
probe <- try (withServerEnv srv (sendRequest ReqPing)) :: IO (Either IOException Response) probe <- try (withServerEnv srv (sendRequest ReqPing)) :: IO (Either IOException Response)
@@ -1337,10 +1375,9 @@ withServerEnv srv act = do
oldDir <- lookupEnv "CHUSQL_DATA_DIR" oldDir <- lookupEnv "CHUSQL_DATA_DIR"
setEnv "CHUSQL_PIPE" (pipeName srv) setEnv "CHUSQL_PIPE" (pipeName srv)
setEnv "CHUSQL_DATA_DIR" (dataDir srv) setEnv "CHUSQL_DATA_DIR" (dataDir srv)
r <- act act `finally` do
restore "CHUSQL_PIPE" oldPipe restore "CHUSQL_PIPE" oldPipe
restore "CHUSQL_DATA_DIR" oldDir restore "CHUSQL_DATA_DIR" oldDir
pure r
where where
restore k = maybe (unsetEnv k) (setEnv k) restore k = maybe (unsetEnv k) (setEnv k)
+65 -46
View File
@@ -9,7 +9,7 @@ use chusql_storage::catalog::Catalog;
use chusql_storage::config::{self, Config, Loaded}; use chusql_storage::config::{self, Config, Loaded};
use chusql_storage::heap::HeapTable; use chusql_storage::heap::HeapTable;
use chusql_storage::log; use chusql_storage::log;
use chusql_storage::protocol::{Request, Response, SchemaColumn}; use chusql_storage::protocol::{Request, Response, SchemaColumn, TableSchemaWire};
use chusql_storage::wal::{Wal, WalOp}; use chusql_storage::wal::{Wal, WalOp};
use chusql_storage::{log_debug, log_error, log_info, log_warn}; use chusql_storage::{log_debug, log_error, log_info, log_warn};
use interprocess::local_socket::{ use interprocess::local_socket::{
@@ -28,20 +28,22 @@ struct Server {
cfg: Config, cfg: Config,
tables: Mutex<HashMap<String, Arc<Mutex<HeapTable>>>>, tables: Mutex<HashMap<String, Arc<Mutex<HeapTable>>>>,
write_lock: Mutex<()>, write_lock: Mutex<()>,
catalog: Mutex<Catalog>,
} }
impl Server { impl Server {
/// 新建服务器状态 /// 新建服务器状态
fn new(cfg: Config) -> std::io::Result<Self> { fn new(cfg: Config) -> std::io::Result<Self> {
std::fs::create_dir_all(&cfg.data_dir)?; std::fs::create_dir_all(&cfg.data_dir)?;
let catalog = Catalog::load(cfg.data_dir.join("catalog.json"))?;
Ok(Server { Ok(Server {
cfg, cfg,
tables: Mutex::new(HashMap::new()), tables: Mutex::new(HashMap::new()),
write_lock: Mutex::new(()), write_lock: Mutex::new(()),
catalog: Mutex::new(catalog),
}) })
} }
// 路径 // 路径
/// 表名 -> 数据文件 /// 表名 -> 数据文件
fn table_path(&self, table: &str) -> PathBuf { fn table_path(&self, table: &str) -> PathBuf {
@@ -125,7 +127,9 @@ impl Server {
table: &str, table: &str,
f: impl FnOnce(&mut HeapTable) -> std::io::Result<R>, f: impl FnOnce(&mut HeapTable) -> std::io::Result<R>,
) -> std::io::Result<R> { ) -> std::io::Result<R> {
if !self.table_exists(table) { // 已经在缓存里的表不用再查磁盘(省一次 metadata 系统调用)
let cached = self.tables.lock().unwrap().contains_key(table);
if !cached && !self.table_exists(table) {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::NotFound, std::io::ErrorKind::NotFound,
format!("unknown table: {}", table), format!("unknown table: {}", table),
@@ -143,19 +147,21 @@ impl Server {
None => self.with_table(table, |t| t.insert(row))?, None => self.with_table(table, |t| t.insert(row))?,
Some(k) => self.with_indexed_table(table, |t| t.insert_keyed(row, *k))?, Some(k) => self.with_indexed_table(table, |t| t.insert_keyed(row, *k))?,
} }
let mut c = Catalog::load(self.catalog_path())?;
let row: chusql_storage::protocol::Row = row.clone().into_iter().collect(); let row: chusql_storage::protocol::Row = row.clone().into_iter().collect();
c.record_insert(table, &row); let mut c = self.catalog.lock().unwrap();
c.save(self.catalog_path())?; if c.record_insert(table, &row) {
c.save(self.catalog_path())?;
}
Ok(()) Ok(())
} }
WalOp::ReplaceAll { table, rows } => { WalOp::ReplaceAll { table, rows } => {
self.with_indexed_table(table, |t| t.replace_all(rows))?; self.with_indexed_table(table, |t| t.replace_all(rows))?;
let mut c = Catalog::load(self.catalog_path())?;
let rows: Vec<chusql_storage::protocol::Row> = let rows: Vec<chusql_storage::protocol::Row> =
rows.iter().map(|r| r.clone().into_iter().collect()).collect(); rows.iter().map(|r| r.clone().into_iter().collect()).collect();
c.record_replace_all(table, &rows); let mut c = self.catalog.lock().unwrap();
c.save(self.catalog_path())?; if c.record_replace_all(table, &rows) {
c.save(self.catalog_path())?;
}
Ok(()) Ok(())
} }
} }
@@ -298,6 +304,21 @@ impl Server {
} }
} }
Request::ListCatalog => {
log_debug!(pipe, "list_catalog");
let c = self.catalog.lock().unwrap();
let schemas: Vec<TableSchemaWire> = c
.all_tables()
.into_iter()
.map(|(name, ts)| TableSchemaWire {
table: name.to_string(),
columns: ts.columns.clone(),
row_count: ts.row_count,
})
.collect();
Response::Catalog { schemas }
}
Request::DescribeTable { table } => { Request::DescribeTable { table } => {
log_debug!(pipe, "describe_table table={}", table); log_debug!(pipe, "describe_table table={}", table);
match self.describe_table(&table) { match self.describe_table(&table) {
@@ -317,18 +338,13 @@ impl Server {
Request::CreateTable { table, columns } => { Request::CreateTable { table, columns } => {
log_debug!(pipe, "create_table table={} cols={}", table, columns.len()); log_debug!(pipe, "create_table table={} cols={}", table, columns.len());
let mut c = match Catalog::load(self.catalog_path()) { {
Ok(c) => c, let c = self.catalog.lock().unwrap();
Err(e) => { if c.describe(&table).is_some() {
return Response::Error { return Response::Error {
message: format!("create_table {}: catalog: {}", table, e), message: format!("create_table {}: table already exists", table),
} };
} }
};
if c.describe(&table).is_some() {
return Response::Error {
message: format!("create_table {}: table already exists", table),
};
} }
if let Err(e) = self.with_table(&table, |_t| Ok(())) { if let Err(e) = self.with_table(&table, |_t| Ok(())) {
@@ -337,6 +353,7 @@ impl Server {
}; };
} }
let mut c = self.catalog.lock().unwrap();
if let Err(e) = c.create_table(&table, columns) { if let Err(e) = c.create_table(&table, columns) {
return Response::Error { return Response::Error {
message: format!("create_table {}: {}", table, e), message: format!("create_table {}: {}", table, e),
@@ -347,31 +364,27 @@ impl Server {
message: format!("create_table {}: catalog save: {}", table, e), message: format!("create_table {}: catalog save: {}", table, e),
}; };
} }
Response::Ok Response::Ok
} }
Request::DropTable { table } => { Request::DropTable { table } => {
log_debug!(pipe, "drop_table table={}", table); log_debug!(pipe, "drop_table table={}", table);
let mut c = match Catalog::load(self.catalog_path()) { {
Ok(c) => c, let mut c = self.catalog.lock().unwrap();
Err(e) => return Response::Error { if let Err(e) = c.drop_table(&table) {
message: format!("drop_table {}: catalog: {}", table, e), return Response::Error {
}, message: format!("drop_table {}: {}", table, e),
}; };
if let Err(e) = c.drop_table(&table) { }
return Response::Error { if let Err(e) = c.save(self.catalog_path()) {
message: format!("drop_table {}: {}", table, e), return Response::Error {
}; message: format!("drop_table {}: catalog save: {}", table, e),
} };
if let Err(e) = c.save(self.catalog_path()) { }
return Response::Error {
message: format!("drop_table {}: catalog save: {}", table, e),
};
} }
self.tables.lock().unwrap().remove(&table); self.tables.lock().unwrap().remove(&table);
let _ = std::fs::remove_file(self.table_path(&table)); let _ = std::fs::remove_file(self.table_path(&table));
let _ = std::fs::remove_file(self.index_path(&table)); let _ = std::fs::remove_file(self.index_path(&table));
@@ -384,12 +397,12 @@ impl Server {
/// 表名:catalog 与目录取并集 /// 表名:catalog 与目录取并集
fn list_tables(&self) -> std::io::Result<Vec<String>> { fn list_tables(&self) -> std::io::Result<Vec<String>> {
let mut names = BTreeSet::new(); let mut names = BTreeSet::new();
{
let c = Catalog::load(self.catalog_path())?; let c = self.catalog.lock().unwrap();
for t in c.table_names() { for t in c.table_names() {
names.insert(t); names.insert(t);
}
} }
let dir = &self.cfg.data_dir; let dir = &self.cfg.data_dir;
if dir.exists() { if dir.exists() {
for entry in std::fs::read_dir(dir)? { for entry in std::fs::read_dir(dir)? {
@@ -400,19 +413,17 @@ impl Server {
} }
} }
} }
Ok(names.into_iter().collect()) Ok(names.into_iter().collect())
} }
/// 查一张表的 schema /// 查一张表的 schema
fn describe_table(&self, table: &str) -> std::io::Result<(Vec<SchemaColumn>, u64)> { fn describe_table(&self, table: &str) -> std::io::Result<(Vec<SchemaColumn>, u64)> {
let c = Catalog::load(self.catalog_path())?; let c = self.catalog.lock().unwrap();
if let Some(schema) = c.describe(table) { if let Some(schema) = c.describe(table) {
return Ok((schema.columns.clone(), schema.row_count)); return Ok((schema.columns.clone(), schema.row_count));
} }
if self.tables.lock().unwrap().contains_key(table) drop(c);
|| std::fs::metadata(self.table_path(table)).is_ok() if std::fs::metadata(self.table_path(table)).is_ok() {
{
return Ok((Vec::new(), 0)); return Ok((Vec::new(), 0));
} }
Err(std::io::Error::new( Err(std::io::Error::new(
@@ -422,6 +433,14 @@ impl Server {
} }
} }
impl Drop for Server {
fn drop(&mut self) {
if let Ok(c) = self.catalog.lock() {
let _ = c.save(self.catalog_path());
}
}
}
// 连接 // 连接
/// 处理一条连接 /// 处理一条连接
+28 -41
View File
@@ -250,16 +250,14 @@ impl DiskBTree {
/// 读根页号 /// 读根页号
fn root(&mut self) -> io::Result<Option<PageId>> { fn root(&mut self) -> io::Result<Option<PageId>> {
let meta = self.file.read_page(META_PAGE)?; let r = self.file.with_page(META_PAGE, |p| read_u64(p, OFF_META_ROOT))?;
let r = read_u64(&meta, OFF_META_ROOT);
Ok(if r == 0 { None } else { Some(r) }) Ok(if r == 0 { None } else { Some(r) })
} }
/// 写根页号 /// 写根页号
fn set_root(&mut self, r: Option<PageId>) -> io::Result<()> { fn set_root(&mut self, r: Option<PageId>) -> io::Result<()> {
let mut meta = self.file.read_page(META_PAGE)?; self.file
write_u64(&mut meta, OFF_META_ROOT, r.unwrap_or(0)); .update_page(META_PAGE, |p| write_u64(p, OFF_META_ROOT, r.unwrap_or(0)))
self.file.write_page(&meta)
} }
/// 分配新页 /// 分配新页
@@ -292,8 +290,8 @@ impl DiskBTree {
/// 递归查找 /// 递归查找
fn get_rec(&mut self, page_id: PageId, key: i64) -> io::Result<Option<u64>> { fn get_rec(&mut self, page_id: PageId, key: i64) -> io::Result<Option<u64>> {
let p = self.file.read_page(page_id)?; let order = self.order;
match read_node(&p, self.order) { match self.file.with_page(page_id, |p| read_node(p, order))? {
NodeData::Leaf { keys, values, .. } => { NodeData::Leaf { keys, values, .. } => {
Ok(keys.binary_search(&key).ok().map(|i| values[i])) Ok(keys.binary_search(&key).ok().map(|i| values[i]))
} }
@@ -345,9 +343,10 @@ impl DiskBTree {
key: i64, key: i64,
value: u64, value: u64,
) -> io::Result<(PageId, Option<(i64, PageId)>)> { ) -> io::Result<(PageId, Option<(i64, PageId)>)> {
let p = self.file.read_page(page_id)?; let order = self.order;
let limit = max_keys(self.order); let node = self.file.with_page(page_id, |p| read_node(p, order))?;
match read_node(&p, self.order) { let limit = max_keys(order);
match node {
NodeData::Leaf { mut keys, mut values, next } => { NodeData::Leaf { mut keys, mut values, next } => {
match keys.binary_search(&key) { match keys.binary_search(&key) {
Ok(i) => values[i] = value, Ok(i) => values[i] = value,
@@ -357,9 +356,9 @@ impl DiskBTree {
} }
} }
if keys.len() <= limit { if keys.len() <= limit {
let mut p2 = self.new_page(page_id); self.file.update_page(page_id, |p| {
write_node(&mut p2, &NodeData::Leaf { keys, values, next }, self.order); write_node(p, &NodeData::Leaf { keys, values, next }, order)
self.file.write_page(&p2)?; })?;
Ok((page_id, None)) Ok((page_id, None))
} else { } else {
let mid = keys.len() / 2; let mid = keys.len() / 2;
@@ -372,17 +371,13 @@ impl DiskBTree {
write_node( write_node(
&mut rp, &mut rp,
&NodeData::Leaf { keys: rkeys, values: rvalues, next }, &NodeData::Leaf { keys: rkeys, values: rvalues, next },
self.order, order,
); );
self.file.write_page(&rp)?; self.file.write_page(&rp)?;
let mut lp = self.new_page(page_id); self.file.update_page(page_id, |p| {
write_node( write_node(p, &NodeData::Leaf { keys, values, next: right_id }, order)
&mut lp, })?;
&NodeData::Leaf { keys, values, next: right_id },
self.order,
);
self.file.write_page(&lp)?;
Ok((page_id, Some((sep, right_id)))) Ok((page_id, Some((sep, right_id))))
} }
@@ -394,22 +389,18 @@ impl DiskBTree {
children[i] = new_child; children[i] = new_child;
match split { match split {
None => { None => {
let mut p2 = self.new_page(page_id); self.file.update_page(page_id, |p| {
write_node(&mut p2, &NodeData::Internal { keys, children }, self.order); write_node(p, &NodeData::Internal { keys, children }, order)
self.file.write_page(&p2)?; })?;
Ok((page_id, None)) Ok((page_id, None))
} }
Some((sep, right_id)) => { Some((sep, right_id)) => {
keys.insert(i, sep); keys.insert(i, sep);
children.insert(i + 1, right_id); children.insert(i + 1, right_id);
if keys.len() <= limit { if keys.len() <= limit {
let mut p2 = self.new_page(page_id); self.file.update_page(page_id, |p| {
write_node( write_node(p, &NodeData::Internal { keys, children }, order)
&mut p2, })?;
&NodeData::Internal { keys, children },
self.order,
);
self.file.write_page(&p2)?;
Ok((page_id, None)) Ok((page_id, None))
} else { } else {
let mid = keys.len() / 2; let mid = keys.len() / 2;
@@ -423,17 +414,13 @@ impl DiskBTree {
write_node( write_node(
&mut rp, &mut rp,
&NodeData::Internal { keys: rkeys, children: rchildren }, &NodeData::Internal { keys: rkeys, children: rchildren },
self.order, order,
); );
self.file.write_page(&rp)?; self.file.write_page(&rp)?;
let mut lp = self.new_page(page_id); self.file.update_page(page_id, |p| {
write_node( write_node(p, &NodeData::Internal { keys, children }, order)
&mut lp, })?;
&NodeData::Internal { keys, children },
self.order,
);
self.file.write_page(&lp)?;
Ok((page_id, Some((up, right_id)))) Ok((page_id, Some((up, right_id))))
} }
@@ -454,8 +441,8 @@ impl DiskBTree {
/// 中序遍历 /// 中序遍历
fn walk(&mut self, page_id: PageId, out: &mut Vec<(i64, u64)>) -> io::Result<()> { fn walk(&mut self, page_id: PageId, out: &mut Vec<(i64, u64)>) -> io::Result<()> {
let p = self.file.read_page(page_id)?; let order = self.order;
match read_node(&p, self.order) { match self.file.with_page(page_id, |p| read_node(p, order))? {
NodeData::Leaf { keys, values, .. } => { NodeData::Leaf { keys, values, .. } => {
for i in 0..keys.len() { for i in 0..keys.len() {
out.push((keys[i], values[i])); out.push((keys[i], values[i]));
+14 -2
View File
@@ -39,6 +39,11 @@ impl Catalog {
serde_json::from_slice(&bytes).map_err(io::Error::other) serde_json::from_slice(&bytes).map_err(io::Error::other)
} }
/// 返回所有表的 (表名, schema)。
pub fn all_tables(&self) -> Vec<(&str, &TableSchema)> {
self.tables.iter().map(|(k, v)| (k.as_str(), v)).collect()
}
/// 写回文件 /// 写回文件
pub fn save<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { pub fn save<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
let bytes = serde_json::to_vec_pretty(self).map_err(io::Error::other)?; let bytes = serde_json::to_vec_pretty(self).map_err(io::Error::other)?;
@@ -98,22 +103,27 @@ impl Catalog {
} }
} }
/// 记一次插入:补列 + 行数 +1 /// 记一次插入:补列 + 行数 +1
pub fn record_insert(&mut self, table: &str, row: &Row) { pub fn record_insert(&mut self, table: &str, row: &Row) -> bool {
let entry = self.tables.entry(table.to_string()).or_default(); let entry = self.tables.entry(table.to_string()).or_default();
let mut changed = false;
for (k, v) in row { for (k, v) in row {
if entry.columns.iter().any(|c| &c.name == k) { if entry.columns.iter().any(|c| &c.name == k) {
continue; continue;
} }
if let Some(ty) = infer_type(v) { if let Some(ty) = infer_type(v) {
entry.columns.push(SchemaColumn { name: k.clone(), ty }); entry.columns.push(SchemaColumn { name: k.clone(), ty });
changed = true;
} }
} }
entry.row_count += 1; entry.row_count += 1;
changed
} }
/// 记一次整表替换:列照补,行数重置 /// 记一次整表替换:列照补,行数重置
pub fn record_replace_all(&mut self, table: &str, rows: &[Row]) { pub fn record_replace_all(&mut self, table: &str, rows: &[Row]) -> bool {
let entry = self.tables.entry(table.to_string()).or_default(); let entry = self.tables.entry(table.to_string()).or_default();
let mut changed = false;
for r in rows { for r in rows {
for (k, v) in r { for (k, v) in r {
if entry.columns.iter().any(|c| &c.name == k) { if entry.columns.iter().any(|c| &c.name == k) {
@@ -121,10 +131,12 @@ impl Catalog {
} }
if let Some(ty) = infer_type(v) { if let Some(ty) = infer_type(v) {
entry.columns.push(SchemaColumn { name: k.clone(), ty }); entry.columns.push(SchemaColumn { name: k.clone(), ty });
changed = true;
} }
} }
} }
entry.row_count = rows.len() as u64; entry.row_count = rows.len() as u64;
changed
} }
/// 删表 /// 删表
+1 -1
View File
@@ -11,7 +11,7 @@ use crate::log::Level;
pub const DEFAULT_CONFIG_PATH: &str = "chusql-storage.toml"; pub const DEFAULT_CONFIG_PATH: &str = "chusql-storage.toml";
/// 默认池容量(页数) /// 默认池容量(页数)
pub const DEFAULT_POOL_SIZE: usize = 64; pub const DEFAULT_POOL_SIZE: usize = 1024;
/// 默认页大小 /// 默认页大小
pub const DEFAULT_PAGE_SIZE: usize = 4096; pub const DEFAULT_PAGE_SIZE: usize = 4096;
/// 默认 B+ 树 order /// 默认 B+ 树 order
+19 -12
View File
@@ -177,10 +177,12 @@ impl HeapTable {
let n = self.file.num_pages()?; let n = self.file.num_pages()?;
if n > 0 { if n > 0 {
let last_id = n - 1; let last_id = n - 1;
let mut p = self.file.read_page(last_id)?; let mut slot = None;
if let Some(slot) = HeapPage::insert_tuple(&mut p, &tuple) { self.file.update_page(last_id, |p| {
self.file.write_page(&p)?; slot = HeapPage::insert_tuple(p, &tuple);
return Ok((last_id, slot)); })?;
if let Some(s) = slot {
return Ok((last_id, s));
} }
} }
@@ -215,8 +217,8 @@ impl HeapTable {
/// 按位置读一行 /// 按位置读一行
pub fn read_at(&mut self, page_id: PageId, slot: u16) -> io::Result<Option<Row>> { pub fn read_at(&mut self, page_id: PageId, slot: u16) -> io::Result<Option<Row>> {
let p = self.file.read_page(page_id)?; let bytes = self.file.with_page(page_id, |p| HeapPage::get_tuple(p, slot))?;
match HeapPage::get_tuple(&p, slot) { match bytes {
None => Ok(None), None => Ok(None),
Some(bytes) => { Some(bytes) => {
let row: Row = serde_json::from_slice(&bytes).map_err(io::Error::other)?; let row: Row = serde_json::from_slice(&bytes).map_err(io::Error::other)?;
@@ -230,13 +232,18 @@ impl HeapTable {
let n = self.file.num_pages()?; let n = self.file.num_pages()?;
let mut rows = Vec::new(); let mut rows = Vec::new();
for i in 0..n { for i in 0..n {
let p = self.file.read_page(i)?; let tuples = self.file.with_page(i, |p| {
for slot in HeapPage::iter_slots(&p) { let mut out = Vec::new();
if let Some(bytes) = HeapPage::get_tuple(&p, slot) { for slot in HeapPage::iter_slots(p) {
let row: Row = serde_json::from_slice(&bytes) if let Some(bytes) = HeapPage::get_tuple(p, slot) {
.map_err(io::Error::other)?; out.push(bytes);
rows.push(row); }
} }
out
})?;
for bytes in tuples {
let row: Row = serde_json::from_slice(&bytes).map_err(io::Error::other)?;
rows.push(row);
} }
} }
Ok(rows) Ok(rows)
+53 -31
View File
@@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet};
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write}; use std::io::{self, Read, Seek, SeekFrom, Write};
use std::path::Path; use std::path::Path;
@@ -41,7 +41,8 @@ pub struct PageFile {
page_size: usize, page_size: usize,
capacity: usize, capacity: usize,
cache: HashMap<PageId, Page>, cache: HashMap<PageId, Page>,
order: VecDeque<PageId>, last_used: HashMap<PageId, u64>,
clock: u64,
dirty: HashSet<PageId>, dirty: HashSet<PageId>,
hits: u64, hits: u64,
misses: u64, misses: u64,
@@ -70,7 +71,8 @@ impl PageFile {
page_size, page_size,
capacity, capacity,
cache: HashMap::new(), cache: HashMap::new(),
order: VecDeque::new(), last_used: HashMap::new(),
clock: 0,
dirty: HashSet::new(), dirty: HashSet::new(),
hits: 0, hits: 0,
misses: 0, misses: 0,
@@ -116,26 +118,28 @@ impl PageFile {
/// 标记为最近使用 /// 标记为最近使用
fn touch(&mut self, id: PageId) { fn touch(&mut self, id: PageId) {
if let Some(pos) = self.order.iter().position(|&x| x == id) { self.clock += 1;
self.order.remove(pos); self.last_used.insert(id, self.clock);
}
self.order.push_back(id);
} }
/// 淘汰一个最冷的页 /// 淘汰一个最冷的页
fn evict_one(&mut self) -> io::Result<bool> { fn evict_one(&mut self) -> io::Result<bool> {
while let Some(victim) = self.order.pop_front() { let victim = self
if let Some(p) = self.cache.get(&victim) { .cache
if self.dirty.contains(&victim) { .keys()
let p = p.clone(); .min_by_key(|id| self.last_used.get(id).copied().unwrap_or(0))
self.write_raw(&p)?; .copied();
self.dirty.remove(&victim); let Some(victim) = victim else {
} return Ok(false);
self.cache.remove(&victim); };
return Ok(true); if self.dirty.contains(&victim) {
} let p = self.cache[&victim].clone();
self.write_raw(&p)?;
self.dirty.remove(&victim);
} }
Ok(false) self.cache.remove(&victim);
self.last_used.remove(&victim);
Ok(true)
} }
/// 直接写文件 /// 直接写文件
@@ -166,20 +170,38 @@ impl PageFile {
/// 读一页(走缓存) /// 读一页(走缓存)
pub fn read_page(&mut self, id: PageId) -> io::Result<Page> { pub fn read_page(&mut self, id: PageId) -> io::Result<Page> {
if let Some(p) = self.cache.get(&id) { self.note_access(id)?;
let p = p.clone(); Ok(self.cache[&id].clone())
}
/// 只读地用一页(走缓存,不整页拷贝)
pub fn with_page<R>(&mut self, id: PageId, f: impl FnOnce(&Page) -> R) -> io::Result<R> {
self.note_access(id)?;
Ok(f(&self.cache[&id]))
}
/// 就地改一页(走缓存,不整页拷贝)
pub fn update_page<R>(&mut self, id: PageId, f: impl FnOnce(&mut Page) -> R) -> io::Result<R> {
self.note_access(id)?;
let r = f(self.cache.get_mut(&id).expect("cached above"));
self.dirty.insert(id);
Ok(r)
}
/// 记一次访问:命中加 hits,未命中读进来并记 misses
fn note_access(&mut self, id: PageId) -> io::Result<()> {
if self.cache.contains_key(&id) {
self.hits += 1; self.hits += 1;
self.touch(id); } else {
return Ok(p); self.misses += 1;
let p = self.read_raw(id)?;
if self.cache.len() >= self.capacity {
self.evict_one()?;
}
self.cache.insert(id, p);
} }
self.misses += 1; self.touch(id);
let p = self.read_raw(id)?; Ok(())
if self.cache.len() >= self.capacity {
self.evict_one()?;
}
self.cache.insert(id, p.clone());
self.order.push_back(id);
Ok(p)
} }
/// 写一页(走缓存) /// 写一页(走缓存)
@@ -257,7 +279,7 @@ impl PageFile {
/// 清空文件 /// 清空文件
pub fn truncate(&mut self) -> io::Result<()> { pub fn truncate(&mut self) -> io::Result<()> {
self.cache.clear(); self.cache.clear();
self.order.clear(); self.last_used.clear();
self.dirty.clear(); self.dirty.clear();
self.file.set_len(0)?; self.file.set_len(0)?;
self.file.seek(SeekFrom::Start(0))?; self.file.seek(SeekFrom::Start(0))?;
+10
View File
@@ -43,6 +43,7 @@ pub enum Request {
columns: Vec<SchemaColumn>, columns: Vec<SchemaColumn>,
}, },
DropTable { table: String }, DropTable { table: String },
ListCatalog
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@@ -59,4 +60,13 @@ pub enum Response {
}, },
Ok, Ok,
Error { message: String }, Error { message: String },
Catalog { schemas: Vec<TableSchemaWire> },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TableSchemaWire {
pub table: String,
pub columns: Vec<SchemaColumn>,
#[serde(default)]
pub row_count: u64,
} }
+1 -1
View File
@@ -63,7 +63,7 @@ impl Wal {
.append(true) .append(true)
.open(&self.path)?; .open(&self.path)?;
f.write_all(&bytes)?; f.write_all(&bytes)?;
f.sync_all()?; f.sync_data()?;
Ok(()) Ok(())
} }