2026-09-19 19:22:50 +08:00
|
|
|
|
{-# LANGUAGE ImportQualifiedPost #-}
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
module ChuSQL.Syntax.Parser (parseStatement) where
|
2026-09-19 19:22:50 +08:00
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
import ChuSQL.Model (Column (..))
|
2026-09-22 21:26:54 +08:00
|
|
|
|
import ChuSQL.Syntax.AST
|
2026-09-19 19:22:50 +08:00
|
|
|
|
import Control.Monad.Combinators.Expr (Operator (..), makeExprParser)
|
2026-09-19 22:42:22 +08:00
|
|
|
|
import Data.Char (isAlpha, isAlphaNum, toLower)
|
|
|
|
|
|
import Data.List (intercalate)
|
2026-09-19 19:22:50 +08:00
|
|
|
|
import Data.Maybe (fromMaybe)
|
|
|
|
|
|
import Data.Void (Void)
|
|
|
|
|
|
import Text.Megaparsec
|
|
|
|
|
|
import Text.Megaparsec.Char
|
|
|
|
|
|
import Text.Megaparsec.Char.Lexer qualified as Lxr
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- SQL 解析:词法 + 语法,把文本变成语句树。
|
2026-09-24 15:37:55 +08:00
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- * 词法
|
|
|
|
|
|
-- | 解析器类型
|
2026-09-19 19:22:50 +08:00
|
|
|
|
type Parser = Parsec Void String
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 跳过空白
|
2026-09-19 19:22:50 +08:00
|
|
|
|
sc :: Parser ()
|
|
|
|
|
|
sc = Lxr.space space1 empty empty
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 词后面吃掉空白
|
2026-09-19 19:22:50 +08:00
|
|
|
|
lexeme :: Parser a -> Parser a
|
|
|
|
|
|
lexeme = Lxr.lexeme sc
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 读一个符号
|
2026-09-19 19:22:50 +08:00
|
|
|
|
symbol :: String -> Parser String
|
|
|
|
|
|
symbol = Lxr.symbol sc
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
{- | 大小写不敏感的关键字;其后不能紧跟标识符字符,避免把 @selection@ 读成 @select@。
|
|
|
|
|
|
整体用 'try' 包住:否则 @ORDER@ 会被 @keyword "or"@ 吃掉前缀 @OR@ 再报错,导致无法回溯。
|
|
|
|
|
|
-}
|
|
|
|
|
|
-- | 读关键字(不分大小写)
|
2026-09-19 19:22:50 +08:00
|
|
|
|
keyword :: String -> Parser ()
|
|
|
|
|
|
keyword k = lexeme $ try $ do
|
|
|
|
|
|
_ <- string' k
|
|
|
|
|
|
notFollowedBy (satisfy isIdentChar)
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 读一个名字
|
2026-09-19 19:22:50 +08:00
|
|
|
|
identifier :: Parser String
|
|
|
|
|
|
identifier = lexeme $ do
|
|
|
|
|
|
first <- satisfy (\ch -> isAlpha ch || ch == '_')
|
|
|
|
|
|
rest <- many (satisfy isIdentChar)
|
|
|
|
|
|
return (first : rest)
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 名字里允许的字符
|
2026-09-19 19:22:50 +08:00
|
|
|
|
isIdentChar :: Char -> Bool
|
|
|
|
|
|
isIdentChar ch = isAlphaNum ch || ch == '_'
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 读一个整数
|
2026-09-19 19:22:50 +08:00
|
|
|
|
integer :: Parser Int
|
|
|
|
|
|
integer = lexeme Lxr.decimal
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 读一个字符串字面量
|
2026-09-19 19:22:50 +08:00
|
|
|
|
stringLit :: Parser String
|
|
|
|
|
|
stringLit = lexeme $ do
|
|
|
|
|
|
_ <- char '\''
|
|
|
|
|
|
s <- many stringChar
|
|
|
|
|
|
_ <- char '\''
|
|
|
|
|
|
return s
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
{- | 字符串内的一个字符:@''@ 折叠成一个单引号,其余字符(含反斜杠)原样保留。
|
|
|
|
|
|
两个分支都不会在失败时消耗输入,因此可以安全地放进 'many'。
|
|
|
|
|
|
-}
|
|
|
|
|
|
-- | 字符串里的一个字符
|
2026-09-19 19:22:50 +08:00
|
|
|
|
stringChar :: Parser Char
|
|
|
|
|
|
stringChar =
|
|
|
|
|
|
choice
|
|
|
|
|
|
[ '\'' <$ try (string "''")
|
|
|
|
|
|
, satisfy (/= '\'')
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- * 子句
|
|
|
|
|
|
-- | 升序或降序
|
2026-09-19 19:22:50 +08:00
|
|
|
|
sortDir :: Parser SortDir
|
|
|
|
|
|
sortDir = (Desc <$ keyword "desc") <|> (Asc <$ keyword "asc") <|> pure Asc
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | LIMIT 后面的整数
|
2026-09-19 19:22:50 +08:00
|
|
|
|
limitClause :: Parser Int
|
|
|
|
|
|
limitClause = do
|
|
|
|
|
|
keyword "limit"
|
|
|
|
|
|
integer
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 能带表名的列名
|
2026-09-19 22:42:22 +08:00
|
|
|
|
qualifiedName :: Parser String
|
|
|
|
|
|
qualifiedName = do
|
|
|
|
|
|
first <- identifier
|
|
|
|
|
|
rest <- many (symbol "." *> identifier)
|
|
|
|
|
|
return (intercalate "." (first : rest))
|
|
|
|
|
|
|
2026-09-19 19:22:50 +08:00
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- * 表达式
|
|
|
|
|
|
-- | 读一个表达式
|
2026-09-19 19:22:50 +08:00
|
|
|
|
expr :: Parser Expr
|
|
|
|
|
|
expr = makeExprParser atom operatorTable
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
{- | 运算符表。注意 'makeExprParser' 要求各层按优先级从高到低排列:
|
|
|
|
|
|
比较运算符绑定最紧,'AND' 次之,'OR' 最松。
|
|
|
|
|
|
-}
|
|
|
|
|
|
-- | 运算符优先级(从紧到松)
|
2026-09-19 19:22:50 +08:00
|
|
|
|
operatorTable :: [[Operator Parser Expr]]
|
|
|
|
|
|
operatorTable =
|
|
|
|
|
|
[
|
|
|
|
|
|
[ InfixN (Gt <$ symbol ">") -- 最高优先级
|
|
|
|
|
|
, InfixN (Lt <$ symbol "<")
|
|
|
|
|
|
, InfixN (Eq <$ symbol "=")
|
|
|
|
|
|
]
|
|
|
|
|
|
, [InfixL (And <$ keyword "AND")]
|
|
|
|
|
|
, [InfixL (Or <$ keyword "OR")] -- 最低优先级
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 最小的表达式单位
|
2026-09-19 19:22:50 +08:00
|
|
|
|
atom :: Parser Expr
|
|
|
|
|
|
atom =
|
|
|
|
|
|
choice
|
|
|
|
|
|
[ LitInt <$> integer
|
|
|
|
|
|
, LitStr <$> stringLit
|
2026-09-19 22:42:22 +08:00
|
|
|
|
, Col <$> qualifiedName
|
2026-09-19 19:22:50 +08:00
|
|
|
|
, between (symbol "(") (symbol ")") expr
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 一个排序列
|
2026-09-19 19:22:50 +08:00
|
|
|
|
orderItem :: Parser (String, SortDir)
|
|
|
|
|
|
orderItem = do
|
2026-09-19 22:42:22 +08:00
|
|
|
|
col <- qualifiedName
|
2026-09-19 19:22:50 +08:00
|
|
|
|
dir <- sortDir
|
|
|
|
|
|
return (col, dir)
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | ORDER BY 的列表
|
2026-09-19 19:22:50 +08:00
|
|
|
|
orderByClause :: Parser [(String, SortDir)]
|
|
|
|
|
|
orderByClause = do
|
|
|
|
|
|
keyword "order"
|
|
|
|
|
|
keyword "by"
|
|
|
|
|
|
sepBy1 orderItem (symbol ",")
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | SET 里的一条赋值
|
2026-09-19 19:22:50 +08:00
|
|
|
|
assignment :: Parser (String, Expr)
|
|
|
|
|
|
assignment = do
|
|
|
|
|
|
col <- identifier
|
|
|
|
|
|
_ <- symbol "="
|
|
|
|
|
|
e <- expr
|
|
|
|
|
|
return (col, e)
|
|
|
|
|
|
|
2026-09-19 22:42:22 +08:00
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 保留字清单
|
2026-09-19 22:42:22 +08:00
|
|
|
|
reservedWords :: [String]
|
|
|
|
|
|
reservedWords =
|
|
|
|
|
|
[ "select"
|
|
|
|
|
|
, "from"
|
|
|
|
|
|
, "where"
|
|
|
|
|
|
, "order"
|
|
|
|
|
|
, "by"
|
|
|
|
|
|
, "limit"
|
|
|
|
|
|
, "join"
|
|
|
|
|
|
, "on"
|
|
|
|
|
|
, "and"
|
|
|
|
|
|
, "or"
|
|
|
|
|
|
, "asc"
|
|
|
|
|
|
, "desc"
|
|
|
|
|
|
, "insert"
|
|
|
|
|
|
, "into"
|
|
|
|
|
|
, "values"
|
|
|
|
|
|
, "delete"
|
|
|
|
|
|
, "update"
|
|
|
|
|
|
, "set"
|
2026-09-24 21:28:49 +08:00
|
|
|
|
, "create"
|
|
|
|
|
|
, "table"
|
2026-09-25 14:21:55 +08:00
|
|
|
|
, "index"
|
|
|
|
|
|
, "drop"
|
2026-09-19 22:42:22 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
{- | 表别名。别名不能是保留字:否则 @FROM users WHERE age > 18@ 会把 @WHERE@
|
|
|
|
|
|
当成 @users@ 的别名吃掉,后面真正的 WHERE 子句就再也解析不到。
|
|
|
|
|
|
用 'try' 包住,一旦命中保留字就整体回溯,让 'optional' 正常返回 'Nothing'。
|
|
|
|
|
|
-}
|
|
|
|
|
|
-- | 别名(不许用保留字)
|
2026-09-19 22:42:22 +08:00
|
|
|
|
aliasName :: Parser String
|
|
|
|
|
|
aliasName = try $ do
|
|
|
|
|
|
name <- identifier
|
|
|
|
|
|
if map toLower name `elem` reservedWords then empty else return name
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 表名 + 可选别名
|
2026-09-19 22:42:22 +08:00
|
|
|
|
tableRef :: Parser (Maybe String, String)
|
|
|
|
|
|
tableRef = do
|
|
|
|
|
|
tbl <- identifier
|
|
|
|
|
|
mAlias <- optional aliasName
|
|
|
|
|
|
return (mAlias, tbl)
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 一个 JOIN ... ON
|
2026-09-19 22:42:22 +08:00
|
|
|
|
joinClause :: Parser (Maybe String, String, Expr)
|
|
|
|
|
|
joinClause = do
|
|
|
|
|
|
keyword "join"
|
|
|
|
|
|
(mAlias, tbl) <- tableRef
|
|
|
|
|
|
keyword "on"
|
|
|
|
|
|
cond <- expr
|
|
|
|
|
|
return (mAlias, tbl, cond)
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | FROM 子句(可含多个 JOIN)
|
2026-09-19 22:42:22 +08:00
|
|
|
|
fromClause :: Parser FromClause
|
|
|
|
|
|
fromClause = do
|
|
|
|
|
|
(mAlias, tbl) <- tableRef
|
|
|
|
|
|
joins <- many joinClause
|
|
|
|
|
|
return (foldl (\acc (a, t, c) -> FromJoin acc a t c) (FromTable mAlias tbl) joins)
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | SELECT 的列清单
|
2026-09-19 19:22:50 +08:00
|
|
|
|
selectList :: Parser [String]
|
|
|
|
|
|
selectList =
|
|
|
|
|
|
choice
|
|
|
|
|
|
[ ["*"] <$ symbol "*"
|
2026-09-19 22:42:22 +08:00
|
|
|
|
, (:) <$> qualifiedName <*> many (symbol "," *> qualifiedName)
|
2026-09-19 19:22:50 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
|
|
|
|
|
|
-- | 读 CREATE TABLE
|
|
|
|
|
|
createTableStatement :: Parser Statement
|
|
|
|
|
|
createTableStatement = do
|
|
|
|
|
|
keyword "create"
|
|
|
|
|
|
keyword "table"
|
|
|
|
|
|
name <- identifier
|
|
|
|
|
|
cols <- between (symbol "(") (symbol ")") (sepBy1 columnDef (symbol ","))
|
|
|
|
|
|
return (CreateTable name cols)
|
|
|
|
|
|
|
|
|
|
|
|
-- | 读 DROP TABLE
|
|
|
|
|
|
dropTableStatement :: Parser Statement
|
|
|
|
|
|
dropTableStatement = do
|
|
|
|
|
|
keyword "drop"
|
|
|
|
|
|
keyword "table"
|
|
|
|
|
|
name <- identifier
|
|
|
|
|
|
return (DropTable name)
|
|
|
|
|
|
|
2026-09-25 14:21:55 +08:00
|
|
|
|
-- | 读 CREATE INDEX(列名即索引名:一列最多一个索引)
|
|
|
|
|
|
createIndexStatement :: Parser Statement
|
|
|
|
|
|
createIndexStatement = do
|
|
|
|
|
|
keyword "create"
|
|
|
|
|
|
keyword "index"
|
|
|
|
|
|
keyword "on"
|
|
|
|
|
|
tbl <- identifier
|
|
|
|
|
|
col <- between (symbol "(") (symbol ")") identifier
|
|
|
|
|
|
return (CreateIndex tbl col)
|
|
|
|
|
|
|
|
|
|
|
|
-- | 读 DROP INDEX
|
|
|
|
|
|
dropIndexStatement :: Parser Statement
|
|
|
|
|
|
dropIndexStatement = do
|
|
|
|
|
|
keyword "drop"
|
|
|
|
|
|
keyword "index"
|
|
|
|
|
|
keyword "on"
|
|
|
|
|
|
tbl <- identifier
|
|
|
|
|
|
col <- between (symbol "(") (symbol ")") identifier
|
|
|
|
|
|
return (DropIndex tbl col)
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 建表时的一列
|
|
|
|
|
|
columnDef :: Parser (String, Column)
|
|
|
|
|
|
columnDef = do
|
|
|
|
|
|
name <- identifier
|
|
|
|
|
|
ty <- columnType
|
|
|
|
|
|
return (name, ty)
|
|
|
|
|
|
|
|
|
|
|
|
-- | 列类型关键字
|
|
|
|
|
|
columnType :: Parser Column
|
|
|
|
|
|
columnType =
|
|
|
|
|
|
(keyword "integer" >> pure TInt)
|
|
|
|
|
|
<|> (keyword "int" >> pure TInt)
|
|
|
|
|
|
<|> (keyword "varchar" >> pure TStr)
|
|
|
|
|
|
<|> (keyword "text" >> pure TStr)
|
|
|
|
|
|
<|> (keyword "str" >> pure TStr)
|
|
|
|
|
|
<|> (keyword "boolean" >> pure TBool)
|
|
|
|
|
|
<|> (keyword "bool" >> pure TBool)
|
|
|
|
|
|
|
|
|
|
|
|
-- * 语句
|
|
|
|
|
|
-- | 读 SELECT
|
|
|
|
|
|
selectStatement :: Parser Statement
|
|
|
|
|
|
selectStatement = do
|
2026-09-19 22:42:22 +08:00
|
|
|
|
keyword "select"
|
2026-09-19 19:22:50 +08:00
|
|
|
|
cols <- selectList
|
2026-09-19 22:42:22 +08:00
|
|
|
|
keyword "from"
|
|
|
|
|
|
fromC <- fromClause
|
2026-09-19 19:22:50 +08:00
|
|
|
|
mWhere <- optional (keyword "where" *> expr)
|
|
|
|
|
|
orderBy <- fromMaybe [] <$> optional orderByClause
|
|
|
|
|
|
mLimit <- optional limitClause
|
|
|
|
|
|
return
|
|
|
|
|
|
Select
|
|
|
|
|
|
{ selectCols = cols
|
2026-09-19 22:42:22 +08:00
|
|
|
|
, selectFrom = fromC
|
2026-09-19 19:22:50 +08:00
|
|
|
|
, selectWhere = mWhere
|
|
|
|
|
|
, selectOrderBy = orderBy
|
|
|
|
|
|
, selectLimit = mLimit
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-25 14:21:55 +08:00
|
|
|
|
-- | 读 INSERT:`VALUES (...)` 后面可以跟多个括号,一次插多行
|
2026-09-24 21:28:49 +08:00
|
|
|
|
insertStatement :: Parser Statement
|
|
|
|
|
|
insertStatement = do
|
2026-09-19 19:22:50 +08:00
|
|
|
|
keyword "INSERT"
|
|
|
|
|
|
keyword "INTO"
|
|
|
|
|
|
tbl <- identifier
|
|
|
|
|
|
cols <-
|
|
|
|
|
|
between
|
|
|
|
|
|
(symbol "(")
|
|
|
|
|
|
(symbol ")")
|
|
|
|
|
|
(sepBy1 identifier (symbol ","))
|
|
|
|
|
|
keyword "VALUES"
|
2026-09-25 14:21:55 +08:00
|
|
|
|
rows <- sepBy1 valueRow (symbol ",")
|
|
|
|
|
|
return (Insert tbl cols rows)
|
|
|
|
|
|
|
|
|
|
|
|
-- | 一行值:一对括号里的若干字面量
|
|
|
|
|
|
valueRow :: Parser [Expr]
|
|
|
|
|
|
valueRow = between (symbol "(") (symbol ")") (sepBy1 atom (symbol ","))
|
2026-09-19 19:22:50 +08:00
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 读 DELETE
|
|
|
|
|
|
deleteStatement :: Parser Statement
|
|
|
|
|
|
deleteStatement = do
|
2026-09-19 19:22:50 +08:00
|
|
|
|
keyword "DELETE"
|
|
|
|
|
|
keyword "FROM"
|
|
|
|
|
|
tbl <- identifier
|
|
|
|
|
|
mWhere <- optional (keyword "WHERE" *> expr)
|
|
|
|
|
|
return (Delete tbl mWhere)
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- | 读 UPDATE
|
|
|
|
|
|
updateStatement :: Parser Statement
|
|
|
|
|
|
updateStatement = do
|
2026-09-19 19:22:50 +08:00
|
|
|
|
keyword "update"
|
|
|
|
|
|
tbl <- identifier
|
|
|
|
|
|
keyword "set"
|
|
|
|
|
|
assigns <- sepBy1 assignment (symbol ",")
|
|
|
|
|
|
mWhere <- optional (keyword "where" *> expr)
|
|
|
|
|
|
return (Update tbl assigns mWhere)
|
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
|
-- * 入口
|
|
|
|
|
|
-- | 解析总入口
|
|
|
|
|
|
parseStatement :: String -> Either String Statement
|
|
|
|
|
|
parseStatement input =
|
|
|
|
|
|
case runParser (sc *> statementP <* sc <* eof) "<query>" input of
|
2026-09-19 19:22:50 +08:00
|
|
|
|
Left err -> Left (errorBundlePretty err)
|
|
|
|
|
|
Right q -> Right q
|
2026-09-24 21:28:49 +08:00
|
|
|
|
where
|
2026-09-25 14:21:55 +08:00
|
|
|
|
-- \| 依次尝试各种语句。
|
|
|
|
|
|
-- `CREATE` / `DROP` 后面跟的词决定是哪条语句,而 `keyword` 的 `try` 只能回溯到
|
|
|
|
|
|
-- "这个词之前"——`CREATE` 一旦被吃掉就没得退了。所以带 `INDEX` 的那两个各自
|
|
|
|
|
|
-- 用 `try` 包住并排在前面:`CREATE INDEX ...` 先试,退回来再试 `CREATE TABLE ...`。
|
2026-09-24 21:28:49 +08:00
|
|
|
|
statementP =
|
|
|
|
|
|
selectStatement
|
|
|
|
|
|
<|> insertStatement
|
|
|
|
|
|
<|> deleteStatement
|
|
|
|
|
|
<|> updateStatement
|
2026-09-25 14:21:55 +08:00
|
|
|
|
<|> try createIndexStatement
|
|
|
|
|
|
<|> try createTableStatement
|
|
|
|
|
|
<|> try dropIndexStatement
|
2026-09-24 21:28:49 +08:00
|
|
|
|
<|> dropTableStatement
|