Files
ChuBlog/chusql-engine/src/ChuSQL/Algebra/Eval.hs
T

148 lines
4.9 KiB
Haskell

module ChuSQL.Algebra.Eval (evalRelOp, evalRelOpM) where
import ChuSQL.Algebra.Expr (evalCondForRow)
import ChuSQL.Algebra.Op
import ChuSQL.Algebra.Sort (sortRows)
import ChuSQL.Model
import ChuSQL.Storage (IndexResult (..), MonadStorage (..))
import ChuSQL.Syntax.AST (Expr (..))
import Control.Monad (filterM, foldM)
import qualified Data.HashMap.Strict as HM
-- 执行:按算子树算出结果行(Scan 阶段会加别名前缀)。
-- | 只保留清单里的列
project :: [String] -> Row -> Row
project ["*"] row = row
project cs row = [(c, v) | (c, v) <- row, c `elem` cs]
-- | 给列名加别名前缀
addPrefix :: Maybe String -> Row -> Row
addPrefix mAlias = map (\(k, v) -> (qualify mAlias k, v))
-- | 扫描一张表
evalScan :: Database -> Maybe String -> String -> Either String [Row]
evalScan db mAlias tbl = do
table <- lookupTable db tbl
Right (map (addPrefix mAlias) (tableRows table))
-- | 点查退化成扫描时用的条件
lookupCondition :: Maybe String -> String -> Int -> Expr
lookupCondition mAlias col k = Eq (Col (qualify mAlias col)) (LitInt k)
data EquiKeys = EquiKeys
{ ekLeft :: String
, ekRight :: String
}
-- | 一批行的列名(空批给空清单,此时一律退回嵌套循环)
rowsCols :: [Row] -> [String]
rowsCols (r : _) = map fst r
rowsCols [] = []
-- | 认出 a = b 这种两列等值条件,并且能分清谁在左谁在右
equiKeys :: [Row] -> [Row] -> Expr -> Maybe EquiKeys
equiKeys lrows rrows (Eq (Col a) (Col b))
| a /= b, a `elem` lcols, b `elem` rcols = Just (EquiKeys a b)
| a /= b, b `elem` lcols, a `elem` rcols = Just (EquiKeys b a)
where
lcols = rowsCols lrows
rcols = rowsCols rrows
equiKeys _ _ _ = Nothing
-- | 哈希连接,输出顺序同嵌套循环
hashJoin :: EquiKeys -> [Row] -> [Row] -> [Row]
hashJoin keys lrows rrows = concatMap probe lrows
where
buckets =
HM.fromListWith
(flip (++))
[(v, [r]) | r <- rrows, Just v <- [lookup (ekRight keys) r]]
probe l = case lookup (ekLeft keys) l of
Nothing -> []
Just v -> [l ++ r | r <- HM.lookupDefault [] v buckets]
-- | 把两边的行按条件配对(边配边筛,不先建整张积表)
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)
-- | 一次连接:能认出等值键就走哈希,否则退回嵌套循环
joinRows :: Expr -> [Row] -> [Row] -> Either String [Row]
joinRows cond lrows rrows = case equiKeys lrows rrows cond of
Just keys -> Right (hashJoin keys lrows rrows)
Nothing -> filterPairs (evalCondForRow cond) lrows rrows
-- | 纯求值(不需要存储)
evalRelOp :: Database -> RelOp -> Either String [Row]
evalRelOp db (Scan mAlias tbl) = evalScan db mAlias tbl
evalRelOp db (Lookup mAlias tbl col k) = do
rows <- evalScan db mAlias tbl
filterM (evalCondForRow (lookupCondition mAlias col k)) rows
evalRelOp db (Filter e op) = do
rows <- evalRelOp db op
filterM (evalCondForRow e) rows
evalRelOp db (Project cols op) = do
rows <- evalRelOp db op
Right (map (project cols) rows)
evalRelOp db (Sort spec op) = do
rows <- evalRelOp db op
Right (sortRows spec rows)
evalRelOp db (Limit n op) = do
rows <- evalRelOp db op
Right (take n rows)
evalRelOp db (Join left right cond) = do
lrows <- evalRelOp db left
rrows <- evalRelOp db right
joinRows cond lrows rrows
-- | 单子求值:点查先问存储有无索引
evalRelOpM :: (MonadStorage m) => RelOp -> m (Either String [Row])
evalRelOpM (Scan mAlias t) = do
result <- scan t
pure $ do
rows <- result
case mAlias of
Nothing -> Right rows
Just _ -> Right (map (addPrefix mAlias) rows)
evalRelOpM (Lookup mAlias t col k) = do
result <- lookupByColumn t col k
case result of
Left e -> pure (Left e)
Right NoIndex -> do
rows <- evalRelOpM (Scan mAlias t)
pure $ do
rs <- rows
filterM (evalCondForRow (lookupCondition mAlias col k)) rs
Right (IndexRow Nothing) -> pure (Right [])
Right (IndexRow (Just row)) -> pure (Right [addPrefix mAlias row])
evalRelOpM (Filter p x) = do
result <- evalRelOpM x
pure $ do
rows <- result
filterM (evalCondForRow p) rows
evalRelOpM (Project cols x) = do
result <- evalRelOpM x
pure (fmap (map (project cols)) result)
evalRelOpM (Sort spec x) = do
result <- evalRelOpM x
pure (fmap (sortRows spec) result)
evalRelOpM (Limit n x) = do
result <- evalRelOpM x
pure (fmap (take n) result)
evalRelOpM (Join l r cond) = do
ls <- evalRelOpM l
rs <- evalRelOpM r
pure $ do
left <- ls
right <- rs
joinRows cond left right