156 lines
5.6 KiB
Haskell
156 lines
5.6 KiB
Haskell
module ChuSQL.Semantic (check) where
|
|
|
|
import ChuSQL.Algebra.Expr (colsInExpr)
|
|
import ChuSQL.Model
|
|
import ChuSQL.Syntax.AST
|
|
import Data.List (intercalate, nub)
|
|
|
|
-- 语义检查:表和列在不在、类型对不对,全部在执行前查。
|
|
|
|
-- * 环境
|
|
-- | 给列名加上别名前缀
|
|
prefixColumns :: Maybe String -> [(String, Column)] -> [(String, Column)]
|
|
prefixColumns mAlias cols = [(prefix ++ c, ty) | (c, ty) <- cols]
|
|
where
|
|
prefix = maybe "" (++ ".") mAlias
|
|
|
|
-- | 收集 FROM 能提供的列
|
|
checkFrom :: Database -> FromClause -> Either String [(String, Column)]
|
|
checkFrom db (FromTable mAlias tbl) = do
|
|
t <- lookupTable db tbl
|
|
Right (prefixColumns mAlias (tableCols t))
|
|
checkFrom db (FromJoin left mAlias tbl cond) = do
|
|
lcols <- checkFrom db left
|
|
t <- lookupTable db tbl
|
|
let env = lcols ++ prefixColumns mAlias (tableCols t)
|
|
checkBool "ON" env cond
|
|
Right env
|
|
|
|
|
|
-- * 检查
|
|
-- | 拼“列不存在”的报错
|
|
missingColumn :: String -> String -> [(String, Column)] -> String
|
|
missingColumn place c env =
|
|
"unknown column in " ++ place ++ ": " ++ c ++ hint
|
|
where
|
|
hint
|
|
| null env = ""
|
|
| otherwise = " (available: " ++ intercalate ", " (map fst env) ++ ")"
|
|
|
|
-- | 检查列是否都在环境里
|
|
checkColumns :: String -> [(String, Column)] -> [String] -> Either String ()
|
|
checkColumns place env requested =
|
|
case [c | c <- requested, c /= allColumns, c `notElem` map fst env] of
|
|
[] -> Right ()
|
|
(c : _) -> Left (missingColumn place c env)
|
|
|
|
|
|
-- | 推导表达式的类型
|
|
inferExpr :: String -> [(String, Column)] -> Expr -> Either String Column
|
|
inferExpr place env (Col c) = maybe (Left (missingColumn place c env)) Right (lookup c env)
|
|
inferExpr _ _ (LitInt _) = Right TInt
|
|
inferExpr _ _ (LitStr _) = Right TStr
|
|
inferExpr _ _ (LitBool _) = Right TBool
|
|
inferExpr place env (Gt a b) = operands place env TInt a b
|
|
inferExpr place env (Lt a b) = operands place env TInt a b
|
|
inferExpr place env (And a b) = operands place env TBool a b
|
|
inferExpr place env (Or a b) = operands place env TBool a b
|
|
inferExpr place env (Eq a b) = do
|
|
ta <- inferExpr place env a
|
|
tb <- inferExpr place env b
|
|
if ta == tb
|
|
then Right TBool
|
|
else Left (place ++ ": both sides of = must have the same type, got " ++ show ta ++ " and " ++ show tb)
|
|
|
|
-- | 检查二元运算两边的类型
|
|
operands :: String -> [(String, Column)] -> Column -> Expr -> Expr -> Either String Column
|
|
operands place env want a b = do
|
|
ta <- inferExpr place env a
|
|
tb <- inferExpr place env b
|
|
if ta == want && tb == want
|
|
then Right TBool
|
|
else Left (place ++ ": operator needs " ++ show want ++ " on both sides, got " ++ show ta ++ " and " ++ show tb)
|
|
|
|
-- | 要求条件能算出布尔
|
|
checkBool :: String -> [(String, Column)] -> Expr -> Either String ()
|
|
checkBool place env e = do
|
|
checkColumns place env (colsInExpr e)
|
|
t <- inferExpr place env e
|
|
if t == TBool
|
|
then Right ()
|
|
else Left (place ++ ": condition must be a boolean, got " ++ show t)
|
|
|
|
|
|
-- * 写入语句
|
|
-- | 检查赋值列和类型
|
|
checkTyped :: String -> [(String, Column)] -> Table -> (String, Expr) -> Either String ()
|
|
checkTyped place env t (c, e) = do
|
|
want <- maybe (Left (missingColumn place c (tableCols t))) Right (colType t c)
|
|
got <- inferExpr place env e
|
|
if want == got
|
|
then Right ()
|
|
else Left (place ++ ": column " ++ c ++ " needs " ++ show want ++ ", got " ++ show got)
|
|
|
|
-- | 检查插入值的类型
|
|
checkValue :: Table -> (String, Expr) -> Either String ()
|
|
checkValue = checkTyped "INSERT" []
|
|
|
|
-- | 检查 UPDATE 的赋值
|
|
checkAssign :: Table -> (String, Expr) -> Either String ()
|
|
checkAssign t = checkTyped "UPDATE" (tableCols t) t
|
|
|
|
|
|
-- * 入口
|
|
-- | 按语句类型分派检查
|
|
check :: Database -> Statement -> Either String ()
|
|
check db q = case q of
|
|
Select
|
|
{ selectCols = cols
|
|
, selectFrom = fromC
|
|
, selectWhere = mWhere
|
|
, selectOrderBy = orderBy
|
|
} -> do
|
|
env <- checkFrom db fromC
|
|
checkColumns "SELECT" env cols
|
|
checkColumns "ORDER BY" env (map fst orderBy)
|
|
mapM_ (checkBool "WHERE" env) mWhere
|
|
Insert tbl cols rows -> do
|
|
t <- lookupTable db tbl
|
|
checkColumns "INSERT" (tableCols t) cols
|
|
if null rows
|
|
then Left "INSERT: no values"
|
|
else mapM_ (mapM_ (checkValue t) . zip cols) rows
|
|
Delete tbl mWhere -> do
|
|
t <- lookupTable db tbl
|
|
mapM_ (checkBool "WHERE" (tableCols t)) mWhere
|
|
Update tbl assigns mWhere -> do
|
|
t <- lookupTable db tbl
|
|
checkColumns "UPDATE" (tableCols t) (map fst assigns)
|
|
mapM_ (checkAssign t) assigns
|
|
mapM_ (checkBool "WHERE" (tableCols t)) mWhere
|
|
CreateTable name cols -> do
|
|
if null name
|
|
then Left "CREATE TABLE: empty table name"
|
|
else do
|
|
let names = map fst cols
|
|
if length names /= length (nub names)
|
|
then Left ("CREATE TABLE: duplicate column names in " ++ name)
|
|
else Right ()
|
|
DropTable name ->
|
|
if null name
|
|
then Left "DROP TABLE: empty table name"
|
|
else Right ()
|
|
CreateIndex tbl col -> do
|
|
t <- lookupTable db tbl
|
|
if col `elem` tableCols' t
|
|
then Right ()
|
|
else Left ("CREATE INDEX: unknown column: " ++ col)
|
|
DropIndex tbl col -> do
|
|
t <- lookupTable db tbl
|
|
if col `elem` tableCols' t
|
|
then Right ()
|
|
else Left ("DROP INDEX: unknown column: " ++ col)
|
|
where
|
|
-- \| 表的列名清单
|
|
tableCols' = map fst . tableCols
|