准备可扩展类型系统工作,更合理的JOIN查询优化算法,并行查询

This commit is contained in:
LIm1tZz7 committed 2026-10-03 11:56:13 +08:00
1 parent 9758439b82
commit 274b7fcdcc
75 files changed
+3828 -1789

No files matched your search

+43 -28
View File
@@ -11,6 +11,7 @@ import ChuSQL.Core.Engine.Storage.Memory (MemoryStorage (runMemoryStorage))
import ChuSQL.Core.Engine.Syntax.AST
import ChuSQL.Core.Engine.Syntax.Parser (parseExpression)
import Data.Maybe (mapMaybe)
import qualified Data.HashSet as HS
-- 引擎入口:检查语义、按语句分发执行,另有内存实现与泛型版本。
@@ -86,13 +87,15 @@ runStatementUncheckedM db (Update tbl assigns mWhere) =
rowsResult <- scan tbl
case rowsResult of
Left err -> pure (Left err)
Right rows -> do
newRows <- mapM (updateRowM db table mWhere assigns) rows
case sequence newRows of
Left err -> pure (Left err)
Right rs -> do
result <- replaceAll tbl rs
pure (result >> Right [])
Right rows -> case checkPlan table of
Left err -> pure (Left err)
Right plan -> do
newRows <- mapM (updateRowM db table plan mWhere assigns) rows
case sequence newRows of
Left err -> pure (Left err)
Right rs -> do
result <- replaceAll tbl rs
pure (result >> Right [])
runStatementUncheckedM _ (CreateTable name cols) = do
result <- createTable name cols
pure (result >> Right [])
@@ -156,7 +159,8 @@ buildRows table current cols rows = do
-- | 逐行构造,最后统一查一遍约束
go _ _ [] acc = do
let built = reverse acc
mapM_ (runChecks table) built
plan <- checkPlan table
mapM_ (runChecks plan) built
enforceNotNull (tableCols table) built
enforceUnique (tableCols table) (current ++ built)
Right built
@@ -208,19 +212,29 @@ autoStart (Just (name, _)) rows = case [n | r <- rows, Just (VInt n) <- [lookup
[] -> 1
ns -> maximum ns + 1
-- | 执行 CHECK 约束
runChecks :: Table -> Row -> Either String ()
runChecks table row = mapM_ check (tableCols table)
-- | 一次解析出来的 CHECK 条件
type CheckPlan = [(String, Expr)]
-- | 把表上的 CHECK 文本解析成条件,每语句只解析一次
checkPlan :: Table -> Either String CheckPlan
checkPlan table = mapM parse (mapMaybe checked (tableCols table))
where
-- | 查这一列的 CHECK
check (name, col) = case columnCheck col of
Nothing -> Right ()
Just text -> case parseExpression text of
Left err -> Left ("CHECK on " ++ name ++ ": " ++ firstLine err)
Right e -> case evalCondForRow e row of
Left err -> Left ("CHECK on " ++ name ++ ": " ++ err)
Right True -> Right ()
Right False -> Left ("CHECK on " ++ name ++ " failed")
-- | 只挑出带 CHECK 文本的列
checked (name, col) = (,) name <$> columnCheck col
-- | 解析一列的 CHECK 文本
parse (name, text) = case parseExpression text of
Left err -> Left ("CHECK on " ++ name ++ ": " ++ firstLine err)
Right e -> Right (name, e)
-- | 执行解析好的 CHECK 条件
runChecks :: CheckPlan -> Row -> Either String ()
runChecks plan row = mapM_ check plan
where
-- | 跑一条 CHECK
check (name, e) = case evalCondForRow e row of
Left err -> Left ("CHECK on " ++ name ++ ": " ++ err)
Right True -> Right ()
Right False -> Left ("CHECK on " ++ name ++ " failed")
-- | 只取第一行(报错提示用)
firstLine :: String -> String
@@ -242,18 +256,18 @@ enforceUnique cols rows = mapM_ check targets
where
targets = [(n, c) | (n, c) <- cols, columnUnique c || columnPrimaryKey c]
-- | 查这一列有没有重复值
check (name, _) = go name [] (mapMaybe (lookup name) rows)
check (name, _) = go name HS.empty (mapMaybe (lookup name) rows)
-- | 逐个值比过去
go _ _ [] = Right ()
-- | NULL 不算冲突,跳过
go name seen (VNull : rest) = go name seen rest
go name seen (v : rest)
| v `elem` seen = Left ("UNIQUE: duplicate value in column " ++ name)
| otherwise = go name (v : seen) rest
| HS.member (canonicalValue v) seen = Left ("UNIQUE: duplicate value in column " ++ name)
| otherwise = go name (HS.insert (canonicalValue v) seen) rest
-- | UPDATE 的一行(条件与赋值都可能带子查询)
updateRowM :: (MonadStorage m) => Database -> Table -> Maybe Expr -> [(String, Expr)] -> Row -> m (Either String Row)
updateRowM db table cond asgns row = do
updateRowM :: (MonadStorage m) => Database -> Table -> CheckPlan -> Maybe Expr -> [(String, Expr)] -> Row -> m (Either String Row)
updateRowM db table plan cond asgns row = do
keep <- case cond of
Nothing -> pure (Right True)
Just e -> evalCondForRowM db e row
@@ -262,7 +276,7 @@ updateRowM db table cond asgns row = do
Right False -> pure (Right row)
Right True -> do
row' <- applyUpdatesM db table asgns row
pure (row' >>= \r -> runChecks table r >> Right r)
pure (row' >>= \r -> runChecks plan r >> Right r)
-- | 依次求值、收类型并覆盖列(表达式可以带子查询)
applyUpdatesM :: (MonadStorage m) => Database -> Table -> [(String, Expr)] -> Row -> m (Either String Row)
@@ -316,8 +330,9 @@ alterTable db tbl change = case lookupTable db tbl of
where
-- | 改完再统一查一遍约束
checkAltered (cols, rows) = do
let table = Table tbl cols rows
mapM_ (runChecks table) rows
let table = Table tbl cols rows Nothing
plan <- checkPlan table
mapM_ (runChecks plan) rows
enforceNotNull cols rows
enforceUnique cols rows
Right (cols, rows)
@@ -0,0 +1,31 @@
module ChuSQL.Core.Engine.Algebra.Cost (tableMetaOf, probeCost, pointRows, rangeRows, indexCheaper) where
import ChuSQL.Core.Model
-- 统计与成本估算:行数、列级不同值数与索引探路页数。
-- | 表统计,拿不到就是 Nothing
tableMetaOf :: Database -> String -> Maybe TableMeta
tableMetaOf db t = lookup t db >>= tableMeta
-- | 索引探路的页数:B+Tree 高度
probeCost :: Int -> Int
probeCost rows = max 1 (ceiling (logBase 2 (fromIntegral (max 2 rows) :: Double)))
-- | 等值点查的估算命中行数:行数除以该列不同值数
pointRows :: TableMeta -> String -> Int
pointRows m col = case [d | (c, d, _) <- metaDistinct m, c == col, d > 1] of
(d : _) -> max 1 (rows `div` d)
[] -> rows
where
rows = max 0 (metaRowCount m)
-- | 范围扫描的估算命中行数:没有直方图,按四分之一算
rangeRows :: TableMeta -> Int
rangeRows m = max 0 (metaRowCount m) `div` 4
-- | 有索引且探路加命中比全表扫便宜
indexCheaper :: TableMeta -> String -> Int -> Bool
indexCheaper m col est = col `elem` metaIndexes m && probeCost rows + est < rows
where
rows = max 0 (metaRowCount m)
@@ -1,15 +1,17 @@
module ChuSQL.Core.Engine.Algebra.Eval (evalRelOp, evalRelOpM, evalExprM, evalCondForRowM) where
import ChuSQL.Core.Engine.Algebra.Cost (tableMetaOf)
import ChuSQL.Core.Engine.Algebra.Expr (evalCondForRow, evalExpr)
import ChuSQL.Core.Engine.Algebra.Optimize (optimize, relOpCols)
import ChuSQL.Core.Engine.Algebra.Optimize (optimize)
import ChuSQL.Core.Engine.Algebra.Op
import ChuSQL.Core.Engine.Algebra.Planner (translate)
import ChuSQL.Core.Engine.Algebra.Sort (compareValue, sortRows)
import ChuSQL.Core.Engine.Algebra.Sort (sortRows)
import ChuSQL.Core.Engine.Builtin (aggregateArg, builtinOfExpr, invokeBuiltin, unsupportedAggregate)
import ChuSQL.Core.Model
import ChuSQL.Core.Engine.Storage (IndexResult (..), MonadStorage (..))
import ChuSQL.Core.Engine.Storage.Memory (runMemoryStorage)
import ChuSQL.Core.Engine.Syntax.AST (Expr (..), JoinKind (..), Subquery (..))
import Control.Monad (filterM, foldM)
import Control.Monad (filterM)
import qualified Data.HashMap.Strict as HM
-- 执行:按算子树算出结果行,外层行供相关子查询引用。
@@ -58,17 +60,10 @@ rowsCols :: [Row] -> [String]
rowsCols (r : _) = map fst r
rowsCols [] = []
-- | 参与等值匹配的键:NULL 不参与,整数值的浮点归一
-- | 参与等值匹配的键:NULL 不参与,整值浮点归一
joinKey :: Value -> Maybe Value
joinKey VNull = Nothing
joinKey (VFloat d)
| not (isNaN d)
, not (isInfinite d)
, fromIntegral (rounded :: Integer) == d =
Just (VInt (fromIntegral rounded))
where
rounded = round d :: Integer
joinKey v = Just v
joinKey v = Just (canonicalValue v)
-- | 取一行的连接键
joinKeyOf :: String -> Row -> Maybe Value
@@ -230,57 +225,13 @@ groupRows keys rows = go rows (HM.empty, [])
valuesOf :: Row -> Expr -> [Row] -> Either String [Value]
valuesOf outer e rows = mapM (evalExpr e . (++ outer)) rows
-- | 去掉 NULL
nonNull :: [Value] -> [Value]
nonNull = filter (/= VNull)
-- | 数值相加:整数配整数还是整数
addValue :: Value -> Value -> Either String Value
addValue (VInt a) (VInt b) = Right (VInt (a + b))
addValue (VInt a) (VFloat b) = Right (VFloat (fromIntegral a + b))
addValue (VFloat a) (VInt b) = Right (VFloat (a + fromIntegral b))
addValue (VFloat a) (VFloat b) = Right (VFloat (a + b))
addValue _ _ = Left "type error: expected two numbers"
-- | 能当数用的值
numberOf :: Value -> Either String Double
numberOf (VInt n) = Right (fromIntegral n)
numberOf (VFloat d) = Right d
numberOf _ = Left "type error: expected a number"
-- | 一组值里的极值(NULL 已经先剔掉)
extremeOf :: (Value -> Value -> Value) -> [Value] -> Either String Value
extremeOf _ [] = Right VNull
extremeOf pick (x : xs) = Right (foldl pick x xs)
-- | 一个聚合算子在一组行上的结果
-- | 一个聚合算子在一组行上的结果,统一走内置函数权威
evalAgg :: Row -> Expr -> [Row] -> Either String Value
evalAgg outer agg rows = case agg of
CountAll -> Right (VInt (length rows))
CountOf e -> do
vs <- valuesOf outer e rows
Right (VInt (length (nonNull vs)))
SumOf e -> do
vs <- valuesOf outer e rows
case nonNull vs of
[] -> Right VNull
xs -> foldM addValue (VInt 0) xs
AvgOf e -> do
vs <- valuesOf outer e rows
case nonNull vs of
[] -> Right VNull
xs -> do
total <- foldM addValue (VInt 0) xs
sumOf <- numberOf total
Right (VFloat (sumOf / fromIntegral (length xs)))
MinOf e -> valuesOf outer e rows >>= extremeOf lower . nonNull
MaxOf e -> valuesOf outer e rows >>= extremeOf higher . nonNull
_ -> Left "only COUNT, SUM, AVG, MIN and MAX can be aggregated"
where
-- | 取更小的那个
lower a b = if compareValue b a == LT then b else a
-- | 取更大的那个
higher a b = if compareValue b a == GT then b else a
evalAgg outer agg rows = case builtinOfExpr agg of
Nothing -> Left unsupportedAggregate
Just builtin -> do
vs <- maybe (Right []) (\e -> valuesOf outer e rows) (aggregateArg agg)
invokeBuiltin builtin (length rows) vs
-- | 一组的输出行:分组列 + 各聚合值
oneGroup :: Row -> [String] -> [(String, Expr)] -> ([Value], [Row]) -> Either String Row
@@ -429,8 +380,8 @@ evalRelOpIn db outer (Compute items op) = do
evalRelOpIn db outer (Aggregate keys aggs op) = do
rows <- evalRelOpIn db outer op
pure (rows >>= evalAggregate outer keys aggs)
evalRelOpIn _ _ (Scan mAlias tbl cols) = do
res <- maybe (scan tbl) (scanColumns tbl) cols
evalRelOpIn db _ (Scan mAlias tbl cols) = do
res <- scanTable db tbl cols
pure (fmap (prefixRows mAlias) res)
evalRelOpIn db outer (Lookup mAlias tbl col k) = do
res <- lookupByColumn tbl col k
@@ -487,8 +438,23 @@ scanFiltered ::
String ->
Expr ->
m (Either String [Row])
scanFiltered _ outer mAlias tbl cond = do
res <- scan tbl
scanFiltered db outer mAlias tbl cond = do
res <- scanTable db tbl Nothing
pure $ do
rows <- res
filterM (evalCondForRow cond . (++ outer)) (prefixRows mAlias rows)
-- | 行数到阈值才值得并行分片
parallelRowThreshold :: Int
parallelRowThreshold = 1000
-- | 表够大且存储支持并行时按分片扫,否则顺序扫
scanTable :: (MonadStorage m) => Database -> String -> Maybe [String] -> m (Either String [Row])
scanTable db tbl cols = do
width <- parallelShards
if width > 1 && rows >= parallelRowThreshold
then scanShards width tbl cols
else maybe (scan tbl) (scanColumns tbl) cols
where
-- | 统计里的行数,没有统计算 0
rows = maybe 0 metaRowCount (tableMetaOf db tbl)
@@ -1,8 +1,8 @@
module ChuSQL.Core.Engine.Algebra.Expr (evalExpr, evalCondForRow, colsInExpr, aggregatesIn, hasAggregate, bareColumns, hasSubquery, inValues, threeValuedNot) where
module ChuSQL.Core.Engine.Algebra.Expr (evalExpr, evalCondForRow, colsInExpr, aggregatesIn, hasAggregate, bareColumns, hasSubquery, hasDivision, inValues, threeValuedNot) where
import ChuSQL.Core.Model
import ChuSQL.Core.Engine.Syntax.AST
import Control.Monad (join)
import ChuSQL.Core.Engine.Builtin (Operator (..), executeOperator, inValues, threeValuedNot)
-- 表达式求值:三值逻辑,NULL 参与运算结果仍是 NULL。
@@ -18,22 +18,18 @@ evalExpr (LitBool b) _ = Right (VBool b)
evalExpr (LitDate s) _ = Right (VStr s)
evalExpr (LitTimestamp s) _ = Right (VStr s)
evalExpr (LitBlob s) _ = Right (VStr s)
evalExpr (Add a b) row = liftArith addArith a b row
evalExpr (Sub a b) row = liftArith subArith a b row
evalExpr (Mul a b) row = liftArith mulArith a b row
evalExpr (Div a b) row = liftArith divArith a b row
evalExpr (Add a b) row = liftOperator OpAdd a b row
evalExpr (Sub a b) row = liftOperator OpSub a b row
evalExpr (Mul a b) row = liftOperator OpMul a b row
evalExpr (Div a b) row = liftOperator OpDiv a b row
evalExpr (Neg a) row = do
v <- evalExpr a row
case v of
VNull -> Right VNull
VInt n -> Right (VInt (negate n))
VFloat d -> Right (VFloat (negate d))
_ -> Left "type error: expected a number"
evalExpr (Gt a b) row = liftCompare (> EQ) a b row
evalExpr (Lt a b) row = liftCompare (< EQ) a b row
evalExpr (Eq a b) row = liftCompare (== EQ) a b row
evalExpr (And a b) row = liftBool andValues a b row
evalExpr (Or a b) row = liftBool orValues a b row
executeOperator OpNeg [v]
evalExpr (Gt a b) row = liftOperator OpGt a b row
evalExpr (Lt a b) row = liftOperator OpLt a b row
evalExpr (Eq a b) row = liftOperator OpEq a b row
evalExpr (And a b) row = liftOperator OpAnd a b row
evalExpr (Or a b) row = liftOperator OpOr a b row
evalExpr (IsNull a) row = do
v <- evalExpr a row
Right (VBool (v == VNull))
@@ -54,21 +50,6 @@ evalExpr agg _ = Left ("aggregate functions are not allowed here: " ++ show agg)
subqueryNeedsDatabase :: String
subqueryNeedsDatabase = "subquery needs a database to run"
-- | IN 三值逻辑:命中为真,碰 NULL 得 NULL
inValues :: Value -> [Value] -> Either String Value
inValues VNull _ = Right VNull
inValues v vs = do
results <- mapM (compareValues (== EQ) v) vs
Right $
if VBool True `elem` results
then VBool True
else if VNull `elem` results then VNull else VBool False
-- | NOT 的三值取反
threeValuedNot :: Value -> Value
threeValuedNot (VBool b) = VBool (not b)
threeValuedNot _ = VNull
-- | 表达式用到了哪些列
colsInExpr :: Expr -> [String]
colsInExpr (Col c) = [c]
@@ -95,6 +76,24 @@ colsInExpr (InList a es _) = colsInExpr a ++ concatMap colsInExpr es
colsInExpr (ExistsSub sq _) = subqueryRefs sq
colsInExpr _ = []
-- | 表达式里有没有除法(除法可能报错,不能提前或延后求值)
hasDivision :: Expr -> Bool
hasDivision e = case e of
Div _ _ -> True
Neg a -> hasDivision a
Add a b -> both a b
Sub a b -> both a b
Mul a b -> both a b
Gt a b -> both a b
Lt a b -> both a b
Eq a b -> both a b
And a b -> both a b
Or a b -> both a b
_ -> False
where
-- | 两个子表达式里任意一个有除法
both a b = hasDivision a || hasDivision b
-- | 表达式里有没有子查询
hasSubquery :: Expr -> Bool
hasSubquery e = case e of
@@ -188,107 +187,9 @@ evalCondForRow e row = do
VNull -> Right False
_ -> Left "type error: WHERE condition must be a boolean"
-- | 把二元运算提升到 Either
liftBinOp ::
(Value -> Value -> Either String Value) ->
Expr ->
Expr ->
Row ->
Either String Value
liftBinOp f a b row = join (f <$> evalExpr a row <*> evalExpr b row)
-- | 整数算术与浮点算术成对出现
data Arith = Arith
{ arithInt :: Int -> Int -> Either String Int
, arithFloat :: Double -> Double -> Either String Double
}
-- | 一边是 NULL 就出 NULL,两边都是整数就出整数
liftArith :: Arith -> Expr -> Expr -> Row -> Either String Value
liftArith ops a b row = liftBinOp go a b row
where
-- | NULL 出 NULL,整数配整数走整数算术
go VNull _ = Right VNull
go _ VNull = Right VNull
go (VInt x) (VInt y) = VInt <$> arithInt ops x y
go x y = case (asDouble x, asDouble y) of
(Just p, Just q) -> VFloat <$> arithFloat ops p q
_ -> Left "type error: expected two numbers"
-- | 能当数用的值
asDouble :: Value -> Maybe Double
asDouble (VInt n) = Just (fromIntegral n)
asDouble (VFloat d) = Just d
asDouble _ = Nothing
-- | 加法
addArith :: Arith
addArith = Arith (\x y -> Right (x + y)) (\x y -> Right (x + y))
-- | 减法
subArith :: Arith
subArith = Arith (\x y -> Right (x - y)) (\x y -> Right (x - y))
-- | 乘法
mulArith :: Arith
mulArith = Arith (\x y -> Right (x * y)) (\x y -> Right (x * y))
-- | 整数除法向零截断,溢出返回错误;浮点除法不能除以零
divArith :: Arith
divArith = Arith intDiv floatDiv
where
-- | 整数除法:除零与溢出都报错
intDiv _ 0 = Left "division by zero"
intDiv x y
| x == minBound && y == -1 = Left "integer division overflow"
| otherwise = Right (x `quot` y)
-- | 浮点除法:不能除以零
floatDiv _ 0 = Left "division by zero"
floatDiv x y = Right (x / y)
-- | 比较:任一侧是 NULL 就给 NULL
liftCompare ::
(Ordering -> Bool) ->
Expr ->
Expr ->
Row ->
Either String Value
liftCompare rel a b row = liftBinOp (compareValues rel) a b row
-- | 比较两个值:NULL 给 NULL,其余按数、串、布尔比
compareValues :: (Ordering -> Bool) -> Value -> Value -> Either String Value
compareValues rel x y
| x == VNull || y == VNull = Right VNull
| Just p <- asDouble x, Just q <- asDouble y = Right (VBool (rel (compare p q)))
| (VStr p, VStr q) <- (x, y) = Right (VBool (rel (compare p q)))
| (VBool p, VBool q) <- (x, y) = Right (VBool (rel (compare p q)))
| otherwise = Left "type error: cannot compare these values"
-- | 布尔二元运算
liftBool ::
(Value -> Value -> Either String Value) ->
Expr ->
Expr ->
Row ->
Either String Value
liftBool f = liftBinOp f
-- | 逻辑与的三值真值表
andValues :: Value -> Value -> Either String Value
andValues (VBool False) _ = Right (VBool False)
andValues _ (VBool False) = Right (VBool False)
andValues (VBool True) (VBool True) = Right (VBool True)
andValues VNull (VBool True) = Right VNull
andValues (VBool True) VNull = Right VNull
andValues VNull VNull = Right VNull
andValues _ _ = Left "type error: expected two booleans"
-- | 逻辑或的三值真值表
orValues :: Value -> Value -> Either String Value
orValues (VBool True) _ = Right (VBool True)
orValues _ (VBool True) = Right (VBool True)
orValues (VBool False) (VBool False) = Right (VBool False)
orValues VNull (VBool False) = Right VNull
orValues (VBool False) VNull = Right VNull
orValues VNull VNull = Right VNull
orValues _ _ = Left "type error: expected two booleans"
-- | 二元运算节点统一走运算符权威
liftOperator :: Operator -> Expr -> Expr -> Row -> Either String Value
liftOperator op a b row = do
x <- evalExpr a row
y <- evalExpr b row
executeOperator op [x, y]
@@ -0,0 +1,197 @@
module ChuSQL.Core.Engine.Algebra.JoinOrder (reorderJoins) where
import ChuSQL.Core.Engine.Algebra.Cost (pointRows, rangeRows, tableMetaOf)
import ChuSQL.Core.Engine.Algebra.Expr (colsInExpr, hasDivision)
import ChuSQL.Core.Engine.Algebra.Op (RelOp (..), relOpCols)
import ChuSQL.Core.Engine.Syntax.AST (Expr (..), JoinKind (..))
import ChuSQL.Core.Model
import Data.List (partition)
-- 内连接链顺序优化:按统计估算中间结果,只在行序不可观察处重排。
-- | 重排入口:顶层行序可观察
reorderJoins :: Database -> RelOp -> RelOp
reorderJoins db = pass db False
-- | 往下带“行序是否不可观察”,可观察时不动内连接
pass :: Database -> Bool -> RelOp -> RelOp
pass db _ (Sort spec x) = Sort spec (pass db True x)
pass db _ (Limit n x) = Limit n (pass db False x)
pass db _ (Aggregate keys aggs x) = Aggregate keys aggs (pass db False x)
pass db free (Project cols x) = Project cols (pass db free x)
pass db free (Compute items x) = Compute items (pass db free x)
pass db free (Filter p x) = Filter p (pass db free x)
pass db free (Join kind l r c)
| free
, Just (leaves, conds) <- innerChain (Join kind l r c)
, length leaves > 1
, Just order <- cheaperOrder db leaves conds =
rebuildChain db (map (pass db free) order) conds
| otherwise = Join kind (pass db free l) (pass db free r) c
pass _ _ op = op
-- | 把内连接链拆成叶子与条件,遇到外连接就放弃
innerChain :: RelOp -> Maybe ([RelOp], [Expr])
innerChain (Join InnerJoin l r c) = do
(ls, lcs) <- innerChain l
(rs, rcs) <- innerChain r
pure (ls ++ rs, lcs ++ rcs ++ [c])
innerChain (Join _ _ _ _) = Nothing
innerChain op = Just ([op], [])
-- | 叶子算子的估算行数,拿不到统计就是 Nothing
leafRows :: Database -> RelOp -> Maybe Int
leafRows db op = case op of
Scan _ t _ -> rowCountOf db t
Lookup _ t c _ -> pointRows <$> tableMetaOf db t <*> pure c
Range _ t _ _ _ -> rangeRows <$> tableMetaOf db t
Filter p x -> scaleRows db p x <$> leafRows db x
Project _ x -> leafRows db x
_ -> Nothing
-- | 表统计里的行数
rowCountOf :: Database -> String -> Maybe Int
rowCountOf db t = metaRowCount <$> tableMetaOf db t
-- | 过滤之后的大致行数:等值看不同值数,其他按四分之一
scaleRows :: Database -> Expr -> RelOp -> Int -> Int
scaleRows db p x rows = case p of
Eq (Col k) _ -> case distinctOf db x k of
Just d | d > 1 -> max 1 (rows `div` d)
_ -> quarter
Gt _ _ -> quarter
Lt _ _ -> quarter
And a b -> max 1 (min (scaleRows db a x rows) (scaleRows db b x rows))
_ -> quarter
where
quarter = max 1 (rows `div` 4)
-- | 叶子对应的别名与表名
leafSource :: RelOp -> Maybe (Maybe String, String)
leafSource (Scan a t _) = Just (a, t)
leafSource (Lookup a t _ _) = Just (a, t)
leafSource (Range a t _ _ _) = Just (a, t)
leafSource (Filter _ x) = leafSource x
leafSource (Project _ x) = leafSource x
leafSource _ = Nothing
-- | 某列在叶子统计里的不同值数
distinctOf :: Database -> RelOp -> String -> Maybe Int
distinctOf db leaf col = do
(alias, t) <- leafSource leaf
m <- tableMetaOf db t
num <- lookup (unqualify alias col) [(c, n) | (c, n, _) <- metaDistinct m]
pure num
-- | 若干叶子里某列最大的不同值数
distinctOn :: Database -> [RelOp] -> String -> Maybe Int
distinctOn db leaves col = case [d | leaf <- leaves, Just d <- [distinctOf db leaf col]] of
[] -> Nothing
ds -> Just (maximum ds)
-- | 两侧等值键里较大的不同值数
keyDistinct :: Database -> [RelOp] -> RelOp -> [Expr] -> Maybe Int
keyDistinct db have x conds = case [v | Eq (Col a) (Col b) <- conds, Just v <- [pair a b]] of
(v : _) -> Just v
[] -> Nothing
where
haveCols = concatMap (relOpCols db) have
xCols = relOpCols db x
-- | 这一对列是不是两侧之间的等值键
pair a b
| a `elem` haveCols, b `elem` xCols = bigger (distinctOn db have a) (distinctOf db x b)
| b `elem` haveCols, a `elem` xCols = bigger (distinctOn db have b) (distinctOf db x a)
| otherwise = Nothing
-- | 两个可选统计取大的那个
bigger (Just a) (Just b) = Just (max a b)
bigger (Just a) Nothing = Just a
bigger Nothing (Just b) = Just b
bigger Nothing Nothing = Nothing
-- | 一次连接之后的大致行数
stepRows :: Database -> [RelOp] -> Int -> RelOp -> [Expr] -> Int
stepRows db have acc x conds = case keyDistinct db have x conds of
Just v | v > 1 -> max 1 (acc * rows `div` v)
_ -> acc * rows
where
rows = maybe 1 id (leafRows db x)
-- | 一批叶子按给定顺序连接,累加每一步的中间结果
chainCost :: Database -> [RelOp] -> [Expr] -> Int
chainCost db (l : ls) conds = go [l] (maybe 1 id (leafRows db l)) ls
where
-- | 逐步接上后面的叶子
go _ _ [] = 0
go have acc (x : xs) =
let card = stepRows db have acc x conds
in card + go (have ++ [x]) card xs
chainCost _ [] _ = 0
-- | 贪心扩展一个顺序:每次挑中间结果最小的下一张
growOrder :: Database -> [RelOp] -> [Expr] -> [Int] -> [Int] -> [Int]
growOrder db leaves conds have rest = case rest of
[] -> have
_ ->
let next = pick rest
in growOrder db leaves conds (have ++ [next]) (filter (/= next) rest)
where
-- | 候选里成本最低的那个,同价取序号小的
pick (i : is) = foldl cheaper i is
pick [] = 0
cheaper i j = if costOf j < costOf i then j else i
-- | 这个前缀的估算成本
costOf i = chainCost db [leaves !! k | k <- have ++ [i]] conds
-- | 每个起点贪心一遍,取最便宜的顺序
bestOrder :: Database -> [RelOp] -> [Expr] -> [RelOp]
bestOrder db leaves conds = pick (map start [0 .. n - 1])
where
n = length leaves
-- | 从第 i 张表开始的贪心顺序
start i = [leaves !! k | k <- growOrder db leaves conds [i] (filter (/= i) [0 .. n - 1])]
-- | 成本最低的那个
pick (o : os) = foldl cheaper o os
pick [] = []
cheaper o o' = if cost o' < cost o then o' else o
-- | 一个顺序的估算成本
cost o = chainCost db o conds
-- | 统计齐全、没有除法且确实更省时给出新顺序
cheaperOrder :: Database -> [RelOp] -> [Expr] -> Maybe [RelOp]
cheaperOrder db leaves conds
| any hasDivision conds = Nothing
| any unknown leaves = Nothing
| bestCost < textCost = Just best
| otherwise = Nothing
where
-- | 这个叶子是不是拿不到统计
unknown x = case leafRows db x of
Just _ -> False
Nothing -> True
best = bestOrder db leaves conds
bestCost = chainCost db best conds
textCost = chainCost db leaves conds
-- | 按新顺序重建左深链,条件挂到两侧都齐的那一步
rebuildChain :: Database -> [RelOp] -> [Expr] -> RelOp
rebuildChain db (l : ls) conds = go (relOpCols db l) l ls conds
where
-- | 逐个接上后面的叶子
go _ acc [] rest = applyConds rest acc
go have acc (x : xs) rest =
let have' = have ++ relOpCols db x
(now, later) = partition (\c -> colsInExpr c `subsetOf` have') rest
in go have' (Join InnerJoin acc x (conjOf now)) xs later
-- | 若干条件用 AND 串起来
conjOf [] = LitBool True
conjOf (c : cs) = foldl And c cs
rebuildChain _ [] _ = Unit
-- | 挂不上去的条件套成 Filter
applyConds :: [Expr] -> RelOp -> RelOp
applyConds [] op = op
applyConds (c : cs) op = Filter c (applyConds cs op)
-- | 条件的列是不是都在可用列里
subsetOf :: [String] -> [String] -> Bool
subsetOf need have = all (`elem` have) need
@@ -1,9 +1,9 @@
module ChuSQL.Core.Engine.Algebra.Op (RelOp (..), renderPlan) where
module ChuSQL.Core.Engine.Algebra.Op (RelOp (..), relOpCols, renderPlan) where
import ChuSQL.Core.Model (Value)
import ChuSQL.Core.Model
import ChuSQL.Core.Engine.Syntax.AST (Expr, JoinKind (..), SortDir)
-- 关系代数算子定义与算子树文本渲染。
-- 关系代数算子定义、输出列推导与算子树文本渲染。
data RelOp
= Scan (Maybe String) String (Maybe [String])
@@ -19,6 +19,27 @@ data RelOp
| Join JoinKind RelOp RelOp Expr
deriving (Show, Eq)
-- | 算子会产出哪些列
relOpCols :: Database -> RelOp -> [String]
relOpCols _ Unit = []
relOpCols _ (Compute items _) = map fst items
relOpCols _ (Aggregate keys aggs _) = keys ++ map fst aggs
relOpCols _ (Scan mAlias _ (Just cols)) = map (qualify mAlias) cols
relOpCols db (Scan mAlias tbl Nothing) =
case lookup tbl db of
Nothing -> []
Just t -> map (prefix ++) (colNames t)
where
prefix = maybe "" (++ ".") mAlias
relOpCols db (Lookup mAlias tbl _ _) = relOpCols db (Scan mAlias tbl Nothing)
relOpCols db (Range mAlias tbl _ _ _) = relOpCols db (Scan mAlias tbl Nothing)
relOpCols db (Filter _ x) = relOpCols db x
relOpCols _ (Project ["*"] _) = ["*"]
relOpCols _ (Project cols _) = cols
relOpCols db (Sort _ x) = relOpCols db x
relOpCols db (Limit _ x) = relOpCols db x
relOpCols db (Join _ l r _) = relOpCols db l ++ relOpCols db r
-- | 连接的显示名
kindLabel :: JoinKind -> String
kindLabel InnerJoin = "Join"
@@ -1,33 +1,14 @@
module ChuSQL.Core.Engine.Algebra.Optimize (optimize, pushProject, relOpCols) where
module ChuSQL.Core.Engine.Algebra.Optimize (optimize, pushProject) where
import ChuSQL.Core.Engine.Algebra.Expr (colsInExpr, evalExpr)
import ChuSQL.Core.Engine.Algebra.Op (RelOp (..))
import ChuSQL.Core.Engine.Algebra.Cost (indexCheaper, pointRows, rangeRows, tableMetaOf)
import ChuSQL.Core.Engine.Algebra.Expr (colsInExpr, evalExpr, hasDivision)
import ChuSQL.Core.Engine.Algebra.JoinOrder (reorderJoins)
import ChuSQL.Core.Engine.Algebra.Op (RelOp (..), relOpCols)
import ChuSQL.Core.Model
import ChuSQL.Core.Engine.Syntax.AST (Expr (..), JoinKind (..))
import Data.List (nub)
-- 查询优化:谓词下推、投影下推与常量折叠。
-- | 算子会产出哪些列
relOpCols :: Database -> RelOp -> [String]
relOpCols _ Unit = []
relOpCols _ (Compute items _) = map fst items
relOpCols _ (Aggregate keys aggs _) = keys ++ map fst aggs
relOpCols _ (Scan mAlias _ (Just cols)) = map (qualify mAlias) cols
relOpCols db (Scan mAlias tbl Nothing) =
case lookup tbl db of
Nothing -> []
Just t -> map (prefix ++) (colNames t)
where
prefix = maybe "" (++ ".") mAlias
relOpCols db (Lookup mAlias tbl _ _) = relOpCols db (Scan mAlias tbl Nothing)
relOpCols db (Range mAlias tbl _ _ _) = relOpCols db (Scan mAlias tbl Nothing)
relOpCols db (Filter _ x) = relOpCols db x
relOpCols _ (Project ["*"] _) = ["*"]
relOpCols _ (Project cols _) = cols
relOpCols db (Sort _ x) = relOpCols db x
relOpCols db (Limit _ x) = relOpCols db x
relOpCols db (Join _ l r _) = relOpCols db l ++ relOpCols db r
-- 查询优化:谓词下推、投影下推、常量折叠、扫描方式与连接顺序选择。
-- | need 是否都在 have 里
isSubsetOf :: [String] -> [String] -> Bool
@@ -90,24 +71,6 @@ pushOne _ (Filter p (Sort spec x))
| not (hasDivision p) = Sort spec (Filter p x)
pushOne _ op = op
-- | 除法可能报错,不能提前或延后求值
hasDivision :: Expr -> Bool
hasDivision e = case e of
Div _ _ -> True
Neg a -> hasDivision a
Add a b -> both a b
Sub a b -> both a b
Mul a b -> both a b
Gt a b -> both a b
Lt a b -> both a b
Eq a b -> both a b
And a b -> both a b
Or a b -> both a b
_ -> False
where
-- | 两个子表达式里任意一个有除法
both a b = hasDivision a || hasDivision b
-- | 检查子计划中可能报错的除法
planHasDivision :: RelOp -> Bool
planHasDivision (Filter p x) = hasDivision p || planHasDivision x
@@ -183,24 +146,38 @@ foldConstants (And a b) = foldNode (And (foldConstants a) (foldConstants b))
foldConstants (Or a b) = foldNode (Or (foldConstants a) (foldConstants b))
foldConstants e = e
-- | 点查值不值得改写;没有统计就按老规矩改
worthLookup :: Database -> String -> String -> Bool
worthLookup db t col = case tableMetaOf db t of
Nothing -> True
Just m -> indexCheaper m col (pointRows m col)
-- | 范围扫描值不值得改写;没有统计就按老规矩改
worthRange :: Database -> String -> String -> Bool
worthRange db t col = case tableMetaOf db t of
Nothing -> True
Just m -> indexCheaper m col (rangeRows m)
-- | 单节点重写
rewriteNode :: Database -> RelOp -> RelOp
rewriteNode _ (Filter (Eq (Col k) (LitInt v)) (Scan mAlias t Nothing)) =
Lookup mAlias t (unqualify mAlias k) (VInt v)
rewriteNode _ (Filter (Eq (Col k) (LitStr v)) (Scan mAlias t Nothing)) =
Lookup mAlias t (unqualify mAlias k) (VStr v)
rewriteNode _ (Filter (And (Gt (Col k1) (LitInt lo)) (Lt (Col k2) (LitInt hi))) (Scan mAlias t Nothing))
| k1 == k2 = Range mAlias t (unqualify mAlias k1) (Just (VInt lo, False)) (Just (VInt hi, False))
rewriteNode _ (Filter (And (Gt (Col k1) (LitStr lo)) (Lt (Col k2) (LitStr hi))) (Scan mAlias t Nothing))
| k1 == k2 = Range mAlias t (unqualify mAlias k1) (Just (VStr lo, False)) (Just (VStr hi, False))
rewriteNode _ (Filter (Gt (Col k) (LitInt v)) (Scan mAlias t Nothing)) =
Range mAlias t (unqualify mAlias k) (Just (VInt v, False)) Nothing
rewriteNode _ (Filter (Gt (Col k) (LitStr v)) (Scan mAlias t Nothing)) =
Range mAlias t (unqualify mAlias k) (Just (VStr v, False)) Nothing
rewriteNode _ (Filter (Lt (Col k) (LitInt v)) (Scan mAlias t Nothing)) =
Range mAlias t (unqualify mAlias k) Nothing (Just (VInt v, False))
rewriteNode _ (Filter (Lt (Col k) (LitStr v)) (Scan mAlias t Nothing)) =
Range mAlias t (unqualify mAlias k) Nothing (Just (VStr v, False))
rewriteNode db (Filter (Eq (Col k) (LitInt v)) (Scan mAlias t Nothing))
| worthLookup db t (unqualify mAlias k) = Lookup mAlias t (unqualify mAlias k) (VInt v)
rewriteNode db (Filter (Eq (Col k) (LitStr v)) (Scan mAlias t Nothing))
| worthLookup db t (unqualify mAlias k) = Lookup mAlias t (unqualify mAlias k) (VStr v)
rewriteNode db (Filter (And (Gt (Col k1) (LitInt lo)) (Lt (Col k2) (LitInt hi))) (Scan mAlias t Nothing))
| k1 == k2 && worthRange db t (unqualify mAlias k1) =
Range mAlias t (unqualify mAlias k1) (Just (VInt lo, False)) (Just (VInt hi, False))
rewriteNode db (Filter (And (Gt (Col k1) (LitStr lo)) (Lt (Col k2) (LitStr hi))) (Scan mAlias t Nothing))
| k1 == k2 && worthRange db t (unqualify mAlias k1) =
Range mAlias t (unqualify mAlias k1) (Just (VStr lo, False)) (Just (VStr hi, False))
rewriteNode db (Filter (Gt (Col k) (LitInt v)) (Scan mAlias t Nothing))
| worthRange db t (unqualify mAlias k) = Range mAlias t (unqualify mAlias k) (Just (VInt v, False)) Nothing
rewriteNode db (Filter (Gt (Col k) (LitStr v)) (Scan mAlias t Nothing))
| worthRange db t (unqualify mAlias k) = Range mAlias t (unqualify mAlias k) (Just (VStr v, False)) Nothing
rewriteNode db (Filter (Lt (Col k) (LitInt v)) (Scan mAlias t Nothing))
| worthRange db t (unqualify mAlias k) = Range mAlias t (unqualify mAlias k) Nothing (Just (VInt v, False))
rewriteNode db (Filter (Lt (Col k) (LitStr v)) (Scan mAlias t Nothing))
| worthRange db t (unqualify mAlias k) = Range mAlias t (unqualify mAlias k) Nothing (Just (VStr v, False))
rewriteNode db (Filter p (Join kind l r c))
| not (hasDivision p || planHasDivision (Join kind l r c)) =
case kind of
@@ -235,4 +212,4 @@ optimizePredicates db op =
-- | 优化总入口
optimize :: Database -> RelOp -> RelOp
optimize db op = pushProject db ["*"] (optimizePredicates db op)
optimize db op = pushProject db ["*"] (reorderJoins db (optimizePredicates db op))
@@ -12,19 +12,6 @@ flipOrdering LT = GT
flipOrdering GT = LT
flipOrdering EQ = EQ
-- | 比较两个值:NULL 最小,数值跨整数与小数比较
compareValue :: Value -> Value -> Ordering
compareValue VNull VNull = EQ
compareValue VNull _ = LT
compareValue _ VNull = GT
compareValue (VInt a) (VInt b) = compare a b
compareValue (VFloat a) (VFloat b) = compare a b
compareValue (VInt a) (VFloat b) = compare (fromIntegral a) b
compareValue (VFloat a) (VInt b) = compare a (fromIntegral b)
compareValue (VStr a) (VStr b) = compare a b
compareValue (VBool a) (VBool b) = compare a b
compareValue _ _ = EQ
-- | 取出排序键(按 spec 顺序,每行只查一次)
sortKey :: [(String, SortDir)] -> Row -> [Maybe Value]
sortKey spec row = [lookup col row | (col, _) <- spec]
@@ -0,0 +1,282 @@
module ChuSQL.Core.Engine.Builtin
( Builtin (..)
, builtinNames
, resolveBuiltin
, builtinName
, invokeBuiltin
, builtinOfExpr
, builtinNode
, aggregateArg
, unsupportedAggregate
, Operator (..)
, operatorOfExpr
, operatorSymbol
, executeOperator
, threeValuedNot
, inValues
) where
import ChuSQL.Core.Model (Value (..), compareValue)
import ChuSQL.Core.Engine.Syntax.AST (Expr (..))
import Control.Monad (foldM)
import Data.Char (toLower)
-- 内置函数与运算符的唯一权威:名称解析和求值都只在这里实现一次。
-- | 内置聚合函数
data Builtin = BCountAll | BCount | BSum | BAvg | BMin | BMax
deriving (Show, Eq, Enum, Bounded)
-- | 名字到内置函数的对照表,解析只认这张表
builtinNames :: [(String, Builtin)]
builtinNames =
[ ("count", BCount)
, ("sum", BSum)
, ("avg", BAvg)
, ("min", BMin)
, ("max", BMax)
]
-- | 按名字解析内置函数,名字不分大小写
resolveBuiltin :: String -> Maybe Builtin
resolveBuiltin name = lookup (map toLower name) builtinNames
-- | 内置函数在 SQL 里的名字
builtinName :: Builtin -> String
builtinName BCountAll = "count"
builtinName b = case [n | (n, x) <- builtinNames, x == b] of
(n : _) -> n
[] -> "count"
-- | 在一组上调用内置函数:给出行数与各行的参数值
invokeBuiltin :: Builtin -> Int -> [Value] -> Either String Value
invokeBuiltin BCountAll rows _ = Right (VInt rows)
invokeBuiltin BCount _ vs = Right (VInt (length (nonNull vs)))
invokeBuiltin BSum _ vs = case nonNull vs of
[] -> Right VNull
xs -> foldM addValue (VInt 0) xs
invokeBuiltin BAvg _ vs = case nonNull vs of
[] -> Right VNull
xs -> do
total <- foldM addValue (VInt 0) xs
sumValue <- numberOf total
Right (VFloat (sumValue / fromIntegral (length xs)))
invokeBuiltin BMin _ vs = extremeOf lower (nonNull vs)
invokeBuiltin BMax _ vs = extremeOf higher (nonNull vs)
-- | 去掉 NULL
nonNull :: [Value] -> [Value]
nonNull = filter (/= VNull)
-- | 聚合节点对应的内置函数;其余节点给 Nothing
builtinOfExpr :: Expr -> Maybe Builtin
builtinOfExpr CountAll = Just BCountAll
builtinOfExpr (CountOf _) = Just BCount
builtinOfExpr (SumOf _) = Just BSum
builtinOfExpr (AvgOf _) = Just BAvg
builtinOfExpr (MinOf _) = Just BMin
builtinOfExpr (MaxOf _) = Just BMax
builtinOfExpr _ = Nothing
-- | 内置函数对应的 AST 节点
builtinNode :: Builtin -> Expr -> Expr
builtinNode BCountAll _ = CountAll
builtinNode BCount e = CountOf e
builtinNode BSum e = SumOf e
builtinNode BAvg e = AvgOf e
builtinNode BMin e = MinOf e
builtinNode BMax e = MaxOf e
-- | 聚合调用的参数表达式,COUNT(*) 没有参数
aggregateArg :: Expr -> Maybe Expr
aggregateArg (CountOf x) = Just x
aggregateArg (SumOf x) = Just x
aggregateArg (AvgOf x) = Just x
aggregateArg (MinOf x) = Just x
aggregateArg (MaxOf x) = Just x
aggregateArg _ = Nothing
-- | 只支持这几个聚合函数
unsupportedAggregate :: String
unsupportedAggregate = "only COUNT, SUM, AVG, MIN and MAX can be aggregated"
-- | 数值相加:整数配整数还是整数
addValue :: Value -> Value -> Either String Value
addValue (VInt a) (VInt b) = Right (VInt (a + b))
addValue (VInt a) (VFloat b) = Right (VFloat (fromIntegral a + b))
addValue (VFloat a) (VInt b) = Right (VFloat (a + fromIntegral b))
addValue (VFloat a) (VFloat b) = Right (VFloat (a + b))
addValue _ _ = Left "type error: expected two numbers"
-- | 能当数用的值
numberOf :: Value -> Either String Double
numberOf (VInt n) = Right (fromIntegral n)
numberOf (VFloat d) = Right d
numberOf _ = Left "type error: expected a number"
-- | 一组值里的极值,空组给 NULL
extremeOf :: (Value -> Value -> Value) -> [Value] -> Either String Value
extremeOf _ [] = Right VNull
extremeOf pick (x : xs) = Right (foldl pick x xs)
-- | 取更小的那个
lower :: Value -> Value -> Value
lower a b = if compareValue b a == LT then b else a
-- | 取更大的那个
higher :: Value -> Value -> Value
higher a b = if compareValue b a == GT then b else a
-- | 运算符:AST 里的每个运算节点对应一个
data Operator
= OpAdd
| OpSub
| OpMul
| OpDiv
| OpNeg
| OpGt
| OpLt
| OpEq
| OpAnd
| OpOr
deriving (Show, Eq, Enum, Bounded)
-- | 运算符的 SQL 写法
operatorSymbol :: Operator -> String
operatorSymbol OpAdd = "+"
operatorSymbol OpSub = "-"
operatorSymbol OpMul = "*"
operatorSymbol OpDiv = "/"
operatorSymbol OpNeg = "-"
operatorSymbol OpGt = ">"
operatorSymbol OpLt = "<"
operatorSymbol OpEq = "="
operatorSymbol OpAnd = "AND"
operatorSymbol OpOr = "OR"
-- | 运算节点对应的运算符;其余节点给 Nothing
operatorOfExpr :: Expr -> Maybe Operator
operatorOfExpr (Add _ _) = Just OpAdd
operatorOfExpr (Sub _ _) = Just OpSub
operatorOfExpr (Mul _ _) = Just OpMul
operatorOfExpr (Div _ _) = Just OpDiv
operatorOfExpr (Neg _) = Just OpNeg
operatorOfExpr (Gt _ _) = Just OpGt
operatorOfExpr (Lt _ _) = Just OpLt
operatorOfExpr (Eq _ _) = Just OpEq
operatorOfExpr (And _ _) = Just OpAnd
operatorOfExpr (Or _ _) = Just OpOr
operatorOfExpr _ = Nothing
-- | 运算符求值:算错个数是内部错误
executeOperator :: Operator -> [Value] -> Either String Value
executeOperator op vs = case (op, vs) of
(OpNeg, [x]) -> evalNeg x
(OpGt, [x, y]) -> compareValues (== GT) x y
(OpLt, [x, y]) -> compareValues (< EQ) x y
(OpEq, [x, y]) -> compareValues (== EQ) x y
(OpAnd, [x, y]) -> andValues x y
(OpOr, [x, y]) -> orValues x y
(_, [x, y]) | Just a <- arithOf op -> evalArith a x y
_ -> Left ("internal error: wrong operand count for operator " ++ operatorSymbol op)
-- | 取负
evalNeg :: Value -> Either String Value
evalNeg VNull = Right VNull
evalNeg (VInt n) = Right (VInt (negate n))
evalNeg (VFloat d) = Right (VFloat (negate d))
evalNeg _ = Left "type error: expected a number"
-- | 整数算术与浮点算术成对出现
data Arith = Arith
{ arithInt :: Int -> Int -> Either String Int
, arithFloat :: Double -> Double -> Either String Double
}
-- | 二元算术运算符对应的实现
arithOf :: Operator -> Maybe Arith
arithOf OpAdd = Just (Arith (\x y -> Right (x + y)) (\x y -> Right (x + y)))
arithOf OpSub = Just (Arith (\x y -> Right (x - y)) (\x y -> Right (x - y)))
arithOf OpMul = Just (Arith (\x y -> Right (x * y)) (\x y -> Right (x * y)))
arithOf OpDiv = Just divArith
arithOf _ = Nothing
-- | 一边是 NULL 就出 NULL,两边都是整数就出整数
evalArith :: Arith -> Value -> Value -> Either String Value
evalArith _ VNull _ = Right VNull
evalArith _ _ VNull = Right VNull
evalArith ops (VInt x) (VInt y) = VInt <$> arithInt ops x y
evalArith ops x y = case (asDouble x, asDouble y) of
(Just p, Just q) -> VFloat <$> arithFloat ops p q
_ -> Left "type error: expected two numbers"
-- | 整数除法向零截断,溢出返回错误;浮点除法不能除以零
divArith :: Arith
divArith = Arith intDiv floatDiv
where
-- | 整数除法:除零与溢出都报错
intDiv _ 0 = Left "division by zero"
intDiv x y
| x == minBound && y == -1 = Left "integer division overflow"
| otherwise = Right (x `quot` y)
-- | 浮点除法:不能除以零
floatDiv _ 0 = Left "division by zero"
floatDiv x y = Right (x / y)
-- | 能当数用的值
asDouble :: Value -> Maybe Double
asDouble (VInt n) = Just (fromIntegral n)
asDouble (VFloat d) = Just d
asDouble _ = Nothing
-- | 比较两个值:NULL 给 NULL,其余按全序比
compareValues :: (Ordering -> Bool) -> Value -> Value -> Either String Value
compareValues rel x y
| x == VNull || y == VNull = Right VNull
| comparableValue x y = Right (VBool (rel (compareValue x y)))
| otherwise = Left "type error: cannot compare these values"
-- | 两个值是否落在可以用全序比较的同一族里
comparableValue :: Value -> Value -> Bool
comparableValue (VInt _) (VFloat _) = True
comparableValue (VFloat _) (VInt _) = True
comparableValue (VInt _) (VInt _) = True
comparableValue (VFloat _) (VFloat _) = True
comparableValue (VStr _) (VStr _) = True
comparableValue (VBool _) (VBool _) = True
comparableValue _ _ = False
-- | 逻辑与的三值真值表
andValues :: Value -> Value -> Either String Value
andValues (VBool False) _ = Right (VBool False)
andValues _ (VBool False) = Right (VBool False)
andValues (VBool True) (VBool True) = Right (VBool True)
andValues VNull (VBool True) = Right VNull
andValues (VBool True) VNull = Right VNull
andValues VNull VNull = Right VNull
andValues _ _ = Left "type error: expected two booleans"
-- | 逻辑或的三值真值表
orValues :: Value -> Value -> Either String Value
orValues (VBool True) _ = Right (VBool True)
orValues _ (VBool True) = Right (VBool True)
orValues (VBool False) (VBool False) = Right (VBool False)
orValues VNull (VBool False) = Right VNull
orValues (VBool False) VNull = Right VNull
orValues VNull VNull = Right VNull
orValues _ _ = Left "type error: expected two booleans"
-- | NOT 的三值取反
threeValuedNot :: Value -> Value
threeValuedNot (VBool b) = VBool (not b)
threeValuedNot _ = VNull
-- | IN 三值逻辑:命中为真,碰 NULL 得 NULL
inValues :: Value -> [Value] -> Either String Value
inValues VNull _ = Right VNull
inValues v vs = do
results <- mapM (\x -> executeOperator OpEq [v, x]) vs
Right $
if VBool True `elem` results
then VBool True
else if VNull `elem` results then VNull else VBool False
@@ -0,0 +1,74 @@
module ChuSQL.Core.Engine.Error
( ErrorCategory (..)
, ErrorRule (..)
, errorRules
, errorCategory
, errorCode
, errorLike
, accountErrorCode
) where
import Data.List (isInfixOf)
-- 引擎与存储错误文本的唯一分类表和协议错误码表
-- | 错误大类
data ErrorCategory
= NoDatabaseError
| PermissionError
| CatalogError
| TypeError
| StorageError
| ProtocolError
| UnknownError
deriving (Eq, Show)
-- | 一条识别规则:命中文本片段就算这一类,并给出协议错误码
data ErrorRule = ErrorRule
{ ruleMarker :: String
, ruleCategory :: ErrorCategory
, ruleCode :: String
}
-- | 唯一的错误识别表,顺序即优先级
errorRules :: [ErrorRule]
errorRules =
[ ErrorRule "no database selected" NoDatabaseError "no_database"
, ErrorRule "administrator required" PermissionError "forbidden"
, ErrorRule "unknown table" CatalogError "not_found"
, ErrorRule "unknown column" CatalogError "query_error"
, ErrorRule "unknown database" CatalogError "not_found"
, ErrorRule "unknown role" CatalogError "not_found"
, ErrorRule "unknown account" CatalogError "not_found"
, ErrorRule "unknown index" CatalogError "query_error"
, ErrorRule "unknown user" CatalogError "query_error"
, ErrorRule "permission denied" PermissionError "query_error"
, ErrorRule "already exists" CatalogError "query_error"
, ErrorRule "type error" TypeError "query_error"
, ErrorRule "cannot put this value into" TypeError "query_error"
, ErrorRule "division by zero" TypeError "query_error"
, ErrorRule "overflow" TypeError "query_error"
, ErrorRule "storage" StorageError "query_error"
, ErrorRule "storage library call failed" StorageError "query_error"
, ErrorRule "unsupported protocol" ProtocolError "query_error"
]
-- | 命中的第一条规则
errorLike :: String -> Maybe ErrorRule
errorLike message = case [r | r <- errorRules, ruleMarker r `isInfixOf` message] of
[] -> Nothing
(r : _) -> Just r
-- | 错误文本属于哪一大类
errorCategory :: String -> ErrorCategory
errorCategory message = maybe UnknownError ruleCategory (errorLike message)
-- | 错误文本对应的协议错误码
errorCode :: String -> String
errorCode message = maybe "query_error" ruleCode (errorLike message)
-- | 账号服务对外的错误码
accountErrorCode :: String -> String
accountErrorCode "account already exists" = "conflict"
accountErrorCode "unknown account" = "not_found"
accountErrorCode _ = "storage_error"
@@ -0,0 +1,82 @@
module ChuSQL.Core.Engine.Parallel (
parallelShardLimit,
Pool,
workerPool,
poolRun,
) where
import Control.Concurrent (forkIO)
import Control.Concurrent.MVar (MVar, modifyMVar, modifyMVar_, newEmptyMVar, newMVar, putMVar, takeMVar)
import Control.Exception (SomeException, throwIO, try)
import System.IO.Unsafe (unsafePerformIO)
-- 并行执行:常驻工作线程池,结果按提交顺序收回。
-- | 单次并行扫描最多切几片
parallelShardLimit :: Int
parallelShardLimit = 8
-- | 池:待办队列与唤醒信号
data Pool = Pool
{ poolJobs :: MVar [IO ()]
, poolReady :: MVar ()
}
{-# NOINLINE poolRef #-}
-- | 进程全局的池,按容量复用
poolRef :: MVar (Maybe (Int, Pool))
poolRef = unsafePerformIO (newMVar Nothing)
-- | 按容量取池,不够就新建一个
workerPool :: Int -> IO Pool
workerPool size = modifyMVar poolRef reuse
where
-- | 现有池够大就留着,否则换新的
reuse current = case current of
Just (capacity, pool)
| capacity >= wanted -> pure (current, pool)
_ -> do
pool <- startPool wanted
pure (Just (wanted, pool), pool)
-- | 池的线程数至少一个
wanted = max 1 size
-- | 起 wanted 个常驻线程吃队列
startPool n = do
jobs <- newMVar []
ready <- newMVar ()
sequence_ [forkIO (worker jobs ready) | _ <- [1 .. n]]
pure (Pool jobs ready)
-- | 一个工作线程:被唤醒一次就取一个待办
worker :: MVar [IO ()] -> MVar () -> IO ()
worker jobs ready = do
takeMVar ready
job <- modifyMVar jobs pick
case job of
Nothing -> worker jobs ready
Just act -> act >> worker jobs ready
where
-- | 取队首,空队列就回 Nothing
pick [] = pure ([], Nothing)
pick (j : js) = pure (js, Just j)
-- | 往池里丢一个待办
submit :: Pool -> IO () -> IO ()
submit pool job = do
modifyMVar_ (poolJobs pool) (pure . (++ [job]))
putMVar (poolReady pool) ()
-- | 跑一个动作,结果或异常都填进格子
fillCell :: MVar (Either SomeException a) -> IO a -> IO ()
fillCell cell act = try act >>= putMVar cell
-- | 并发跑一批动作,按输入顺序返回结果与异常
poolRun :: Pool -> [IO a] -> IO [a]
poolRun _ [] = pure []
poolRun pool actions = do
cells <- mapM (const newEmptyMVar) actions
sequence_ [submit pool (fillCell cell act) | (cell, act) <- zip cells actions]
outcomes <- mapM takeMVar cells
mapM (either throwIO pure) outcomes
@@ -243,16 +243,11 @@ comparison db place env a b = do
then Right (InferType CBool)
else Left (place ++ ": both sides of = must have the same type, got " ++ renderType ta ++ " and " ++ renderType tb)
-- | 两类能不能比
-- | 两类能不能比,类型族规则来自 Model
compatible :: InferredType -> InferredType -> Bool
compatible InferNull _ = True
compatible _ InferNull = True
compatible (InferType a) (InferType b)
| numericType a && numericType b = True
| typeClassOf a == typeClassOf b = True
| typeClassOf a == TemporalClass && typeClassOf b == TextClass = True
| typeClassOf b == TemporalClass && typeClassOf a == TextClass = True
| otherwise = False
compatible (InferType a) (InferType b) = comparableTypes a b
-- | 逻辑运算两边的类型检查
boolean :: Database -> String -> Scope -> Expr -> Expr -> Either String InferredType
@@ -35,6 +35,14 @@ class (Monad m) => MonadStorage m where
result <- scan t
pure (fmap (map (\row -> [(c, v) | c <- cols, Just v <- [lookup c row]])) result)
-- \| 本存储能用的并行分片数;不支持并行的存储返回 1
parallelShards :: m Int
parallelShards = pure 1
-- \| 按分片并行扫描并按分片序拼接;默认退回顺序扫描
scanShards :: Int -> String -> Maybe [String] -> m (Either String [Row])
scanShards _ t cols = maybe (scan t) (scanColumns t) cols
-- \| 追加一行
insert :: String -> Row -> m (Either String ())
@@ -28,8 +28,10 @@ module ChuSQL.Core.Engine.Storage.IPC (
import ChuSQL.Core.Model
import ChuSQL.Core.Protocol (Account (..), Request (..), Response (..), SchemaColumn (..), TableInfo (..))
import ChuSQL.Core.Engine.Parallel (parallelShardLimit, poolRun, workerPool)
import ChuSQL.Core.Engine.Storage
import ChuSQL.Core.Engine.Storage.FFI (closeStorage, openStorage, storageRequest)
import Control.Concurrent (getNumCapabilities)
import Control.Concurrent.MVar (MVar, modifyMVar_, newMVar, readMVar)
import Data.Aeson (eitherDecodeStrict, encode)
import Data.ByteString qualified as BS
@@ -166,6 +168,18 @@ doScan t = ask "scan" (ReqScan t) $ \resp -> case resp of
RespRows rows -> Just rows
_ -> Nothing
-- | 发带投影的 Scan
doScanColumns :: String -> [String] -> IO (Either String [Row])
doScanColumns t cols = ask "scan" (ReqScanColumns t cols) $ \resp -> case resp of
RespRows rows -> Just [[(c, v) | c <- cols, Just v <- [lookup c row]] | row <- rows]
_ -> Nothing
-- | 发分片 Scan:只取第 shard 片
doScanShard :: String -> Maybe [String] -> Int -> Int -> IO (Either String [Row])
doScanShard t cols shard shards = ask "scan_shard" (ReqScanShard t cols shard shards) $ \resp -> case resp of
RespRows rows -> Just rows
_ -> Nothing
-- | 发 Insert(索引由存储层维护)
doInsert :: String -> Row -> IO (Either String ())
doInsert t r = ask "insert" (ReqInsert t r) okOnly
@@ -290,9 +304,20 @@ instance MonadStorage IPCStorage where
-- \| 发 Scan
scan t = IPCStorage (doScan t)
-- \| 只取指定列
scanColumns t cols = IPCStorage $ ask "scan" (ReqScanColumns t cols) $ \resp -> case resp of
RespRows rows -> Just [[(c, v) | c <- cols, Just v <- [lookup c row]] | row <- rows]
_ -> Nothing
scanColumns t cols = IPCStorage (doScanColumns t cols)
-- \| 并行分片数:按 RTS 能力数,封顶在 parallelShardLimit
parallelShards = IPCStorage $ do
caps <- getNumCapabilities
pure (max 1 (min parallelShardLimit caps))
-- \| 分片并行扫,再按分片序拼接
scanShards width t cols
| width <= 1 = IPCStorage (maybe (doScan t) (doScanColumns t) cols)
| otherwise = IPCStorage $ do
pool <- workerPool width
parts <- poolRun pool [doScanShard t cols shard width | shard <- [0 .. width - 1]]
pure (fmap concat (sequence parts))
-- \| 发 Insert(索引由存储层自己维护)
insert t r = IPCStorage (doInsert t r)
@@ -341,7 +366,7 @@ instance MonadStorage IPCStorage where
rows <- doScan (tiTable info)
pure
( tiTable info
, Table (tiTable info) (schemaToColumns (tiColumns info)) (either (const []) id rows)
, Table (tiTable info) (schemaToColumns (tiColumns info)) (either (const []) id rows) (Just (metaOf info))
)
-- \| 只问数据字典要结构,设了当前库只留它的表
@@ -354,6 +379,10 @@ instance MonadStorage IPCStorage where
Left _ -> []
Right xs -> concatMap (visibleTables current) xs
-- | 线上表信息转引擎侧统计
metaOf :: TableInfo -> TableMeta
metaOf info = TableMeta (tiRows info) (tiStats info) (tiIndexes info)
-- | 没设库名就照单全收;设了库名只收这个库的表,键名去掉库名前缀
visibleTables :: Maybe String -> TableInfo -> [(String, Table)]
visibleTables current info
@@ -365,4 +394,4 @@ visibleTables current info
where
named = info {tiTable = unqualify current (tiTable info)}
-- | 组装成不带行的表
bare i = (tiTable i, Table (tiTable i) (schemaToColumns (tiColumns i)) [])
bare i = (tiTable i, Table (tiTable i) (schemaToColumns (tiColumns i)) [] (Just (metaOf i)))
@@ -44,7 +44,7 @@ instance MonadStorage MemoryStorage where
createTable name cols = MemoryStorage $ \db ->
if any ((== name) . fst) db
then Right (Left ("table already exists: " ++ name), db)
else Right (Right (), db ++ [(name, Table name cols [])])
else Right (Right (), db ++ [(name, Table name cols [] Nothing)])
-- \| 删表;不存在报错
dropTable name = MemoryStorage $ \db ->
@@ -3,6 +3,7 @@
module ChuSQL.Core.Engine.Syntax.Parser (parseStatement, parseExpression) where
import ChuSQL.Core.Model (Column (..), ColumnType (..), Value (..), plainColumn)
import ChuSQL.Core.Engine.Builtin (Builtin (..), builtinName, builtinNames, builtinNode)
import ChuSQL.Core.Engine.Syntax.AST
import Control.Monad (void)
import Control.Monad.Combinators.Expr (Operator (..), makeExprParser)
@@ -172,18 +173,14 @@ inPredicate = do
subquerySelect :: Parser Subquery
subquerySelect = Subquery <$> selectStatement <*> pure []
-- | 聚合调用:COUNT/SUM/AVG/MIN/MAX
-- | 聚合调用:名称与节点都由内置函数表给出
aggregateCall :: Parser Expr
aggregateCall =
choice
[ CountAll <$ try (keyword "count" *> between (symbol "(") (symbol ")") (symbol "*"))
, CountOf <$> aggArg "count"
, SumOf <$> aggArg "sum"
, AvgOf <$> aggArg "avg"
, MinOf <$> aggArg "min"
, MaxOf <$> aggArg "max"
]
aggregateCall = choice (countAll : map named builtinNames)
where
-- | COUNT(*),没有参数
countAll = CountAll <$ try (keyword (builtinName BCountAll) *> between (symbol "(") (symbol ")") (symbol "*"))
-- | 表里每一个带参数的内置函数
named (name, builtin) = builtinNode builtin <$> aggArg name
-- | 读聚合函数的参数
aggArg name = try (keyword name *> between (symbol "(") (symbol ")") expr)