实现基本关系代数运算,JOIN跨表查询
This commit is contained in:
1 parent
a9ef1ef3e2
commit
cae66f0731
15 files changed
+406
-119
No files matched your search
@@ -10,8 +10,6 @@
|
|||||||
| `chusql-storage/` | Rust | 存储引擎:磁盘IO、B+树、缓冲池、WAL、崩溃恢复、并发控制 |
|
| `chusql-storage/` | Rust | 存储引擎:磁盘IO、B+树、缓冲池、WAL、崩溃恢复、并发控制 |
|
||||||
| `chusql-web/` | 前端 | 浏览器端管理客户端 |
|
| `chusql-web/` | 前端 | 浏览器端管理客户端 |
|
||||||
|
|
||||||
详细架构、模块职责与构建目标见 [ARCHITECTURE.md](ARCHITECTURE.md)。
|
|
||||||
|
|
||||||
# 架构分层
|
# 架构分层
|
||||||
|
|
||||||
## Haskell部分
|
## Haskell部分
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
import ChuSQL.Eval.Executor
|
import ChuSQL.SQLSyntax.Executor
|
||||||
import ChuSQL.Model
|
import ChuSQL.Model
|
||||||
import ChuSQL.Syntax.Ast
|
import ChuSQL.SQLSyntax.AST
|
||||||
import ChuSQL.Syntax.Parser
|
import ChuSQL.SQLSyntax.Parser
|
||||||
|
|
||||||
users :: Table
|
users :: Table
|
||||||
users =
|
users =
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ build-type: Simple
|
|||||||
|
|
||||||
library
|
library
|
||||||
hs-source-dirs: src
|
hs-source-dirs: src
|
||||||
exposed-modules: ChuSQL.Syntax.Ast
|
exposed-modules: ChuSQL.Algebra.Eval
|
||||||
ChuSQL.Syntax.Parser
|
ChuSQL.Algebra.Op
|
||||||
|
ChuSQL.Algebra.Planner
|
||||||
ChuSQL.Model
|
ChuSQL.Model
|
||||||
ChuSQL.Eval.Expr
|
ChuSQL.SQLSyntax.AST
|
||||||
ChuSQL.Eval.Sort
|
ChuSQL.SQLSyntax.Executor
|
||||||
ChuSQL.Eval.Executor
|
ChuSQL.SQLSyntax.Expr
|
||||||
|
ChuSQL.SQLSyntax.Parser
|
||||||
|
ChuSQL.SQLSyntax.Sort
|
||||||
build-depends: base >= 4.14 && < 5
|
build-depends: base >= 4.14 && < 5
|
||||||
, megaparsec
|
, megaparsec
|
||||||
, parser-combinators
|
, parser-combinators
|
||||||
|
|||||||
Whitespace-only changes.
@@ -0,0 +1,39 @@
|
|||||||
|
module ChuSQL.Algebra.Eval where
|
||||||
|
|
||||||
|
import ChuSQL.Algebra.Op
|
||||||
|
import ChuSQL.Model
|
||||||
|
import ChuSQL.SQLSyntax.Expr (evalCondForRow)
|
||||||
|
import ChuSQL.SQLSyntax.Sort (sortRows)
|
||||||
|
import Control.Monad (filterM)
|
||||||
|
|
||||||
|
-- 投影
|
||||||
|
project :: [String] -> Row -> Row
|
||||||
|
project ["*"] row = row
|
||||||
|
project cs row = [(c, v) | (c, v) <- row, c `elem` cs]
|
||||||
|
|
||||||
|
-- 执行关系代数运算
|
||||||
|
addPrefix :: String -> Row -> Row
|
||||||
|
addPrefix prefix = map (\(k, v) -> (prefix ++ k, v))
|
||||||
|
|
||||||
|
evalRelOp :: Database -> RelOp -> Either String [Row]
|
||||||
|
evalRelOp db (Scan mAlias tbl) = do
|
||||||
|
table <- maybe (Left ("unknown table: " ++ tbl)) Right (lookup tbl db)
|
||||||
|
let prefix = maybe "" (++ ".") mAlias
|
||||||
|
Right (map (addPrefix prefix) (tableRows table))
|
||||||
|
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
|
||||||
|
let cross = [l ++ r | l <- lrows, r <- rrows] -- 笛卡尔积
|
||||||
|
filterM (evalCondForRow cond) cross
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
module ChuSQL.Algebra.Op where
|
||||||
|
|
||||||
|
import ChuSQL.SQLSyntax.AST (Expr, SortDir)
|
||||||
|
|
||||||
|
-- 关系代数算子
|
||||||
|
data RelOp
|
||||||
|
= Scan (Maybe String) String
|
||||||
|
| Filter Expr RelOp
|
||||||
|
| Project [String] RelOp
|
||||||
|
| Sort [(String, SortDir)] RelOp
|
||||||
|
| Limit Int RelOp
|
||||||
|
| Join RelOp RelOp Expr
|
||||||
|
deriving (Show, Eq)
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
module ChuSQL.Algebra.Planner where
|
||||||
|
|
||||||
|
import ChuSQL.Algebra.Op
|
||||||
|
import ChuSQL.Model
|
||||||
|
import ChuSQL.SQLSyntax.AST
|
||||||
|
|
||||||
|
-- * 存在性检查
|
||||||
|
checkColumns :: [String] -> [String] -> Either String ()
|
||||||
|
checkColumns available requested =
|
||||||
|
case [c | c <- requested, c /= "*", c `notElem` available] of
|
||||||
|
[] -> Right ()
|
||||||
|
(c : _) -> Left ("unknown column: " ++ c)
|
||||||
|
|
||||||
|
checkFrom :: Database -> FromClause -> Either String ()
|
||||||
|
checkFrom db (FromTable _ tbl) =
|
||||||
|
case lookup tbl db of
|
||||||
|
Just _ -> Right ()
|
||||||
|
Nothing -> Left ("unknown table: " ++ tbl)
|
||||||
|
checkFrom db (FromJoin left _ tbl _) = do
|
||||||
|
_ <- checkFrom db left
|
||||||
|
case lookup tbl db of
|
||||||
|
Just _ -> Right ()
|
||||||
|
Nothing -> Left ("unknown table: " ++ tbl)
|
||||||
|
|
||||||
|
-- | 收集 FromClause 里所有可用的列名
|
||||||
|
availableColumns :: Database -> FromClause -> Either String [String]
|
||||||
|
availableColumns db (FromTable mAlias tbl) = do
|
||||||
|
table <- maybe (Left ("unknown table: " ++ tbl)) Right (lookup tbl db)
|
||||||
|
let prefix = maybe "" (++ ".") mAlias
|
||||||
|
Right (map (prefix ++) (tableCols table))
|
||||||
|
availableColumns db (FromJoin left mAlias tbl _) = do
|
||||||
|
lcols <- availableColumns db left
|
||||||
|
table <- maybe (Left ("unknown table: " ++ tbl)) Right (lookup tbl db)
|
||||||
|
let prefix = maybe "" (++ ".") mAlias
|
||||||
|
Right (lcols ++ map (prefix ++) (tableCols table))
|
||||||
|
|
||||||
|
-- | 检查数据库列明
|
||||||
|
checkColumnsFrom :: Database -> FromClause -> [String] -> Either String ()
|
||||||
|
checkColumnsFrom db fromC requested = do
|
||||||
|
available <- availableColumns db fromC
|
||||||
|
checkColumns available requested
|
||||||
|
|
||||||
|
-- 内部查询语句转执行计划
|
||||||
|
fromToRelOp :: FromClause -> RelOp
|
||||||
|
fromToRelOp (FromTable mAlias tbl) = Scan mAlias tbl
|
||||||
|
fromToRelOp (FromJoin left mAlias tbl cond) =
|
||||||
|
Join (fromToRelOp left) (Scan mAlias tbl) cond
|
||||||
|
|
||||||
|
translate :: Database -> Query -> Either String RelOp
|
||||||
|
translate db q = case q of
|
||||||
|
Select
|
||||||
|
{ selectCols = cols
|
||||||
|
, selectFrom = fromC
|
||||||
|
, selectWhere = mWhere
|
||||||
|
, selectOrderBy = orderBy
|
||||||
|
, selectLimit = mLimit
|
||||||
|
} -> do
|
||||||
|
-- 检查所有表和列
|
||||||
|
_ <- checkFrom db fromC
|
||||||
|
_ <- checkColumnsFrom db fromC cols
|
||||||
|
_ <- checkColumnsFrom db fromC (map fst orderBy)
|
||||||
|
let base = fromToRelOp fromC
|
||||||
|
filtered = case mWhere of
|
||||||
|
Nothing -> base
|
||||||
|
Just w -> Filter w base
|
||||||
|
sorted = case orderBy of
|
||||||
|
[] -> filtered
|
||||||
|
spec -> Sort spec filtered
|
||||||
|
limited = case mLimit of
|
||||||
|
Nothing -> sorted
|
||||||
|
Just n -> Limit n sorted
|
||||||
|
Right (Project cols limited)
|
||||||
|
_ -> Left "only SELECT is supported by translate"
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
module ChuSQL.Syntax.Ast where
|
module ChuSQL.SQLSyntax.AST where
|
||||||
|
|
||||||
-- 查询语句
|
-- 查询语句
|
||||||
data Query
|
data Query
|
||||||
= Select
|
= Select
|
||||||
{ selectCols :: [String]
|
{ selectCols :: [String]
|
||||||
, selectTable :: String
|
, selectFrom :: FromClause
|
||||||
, selectWhere :: Maybe Expr
|
, selectWhere :: Maybe Expr
|
||||||
, selectOrderBy :: [(String, SortDir)]
|
, selectOrderBy :: [(String, SortDir)]
|
||||||
, selectLimit :: Maybe Int
|
, selectLimit :: Maybe Int
|
||||||
@@ -14,6 +14,12 @@ data Query
|
|||||||
| Update String [(String, Expr)] (Maybe Expr)
|
| Update String [(String, Expr)] (Maybe Expr)
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
-- 从句
|
||||||
|
data FromClause
|
||||||
|
= FromTable (Maybe String) String
|
||||||
|
| FromJoin FromClause (Maybe String) String Expr
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
-- 条件表达式
|
-- 条件表达式
|
||||||
data Expr
|
data Expr
|
||||||
= Col String
|
= Col String
|
||||||
@@ -35,7 +41,7 @@ makeSelect :: [String] -> String -> Maybe Expr -> Query
|
|||||||
makeSelect cols tbl w =
|
makeSelect cols tbl w =
|
||||||
Select
|
Select
|
||||||
{ selectCols = cols
|
{ selectCols = cols
|
||||||
, selectTable = tbl
|
, selectFrom = FromTable Nothing tbl
|
||||||
, selectWhere = w
|
, selectWhere = w
|
||||||
, selectOrderBy = []
|
, selectOrderBy = []
|
||||||
, selectLimit = Nothing
|
, selectLimit = Nothing
|
||||||
+9
-38
@@ -1,16 +1,12 @@
|
|||||||
module ChuSQL.Eval.Executor where
|
module ChuSQL.SQLSyntax.Executor where
|
||||||
|
|
||||||
import ChuSQL.Eval.Expr
|
import ChuSQL.Algebra.Eval (evalRelOp)
|
||||||
import ChuSQL.Eval.Sort
|
import ChuSQL.Algebra.Planner (translate)
|
||||||
import ChuSQL.Model
|
import ChuSQL.Model
|
||||||
import ChuSQL.Syntax.Ast
|
import ChuSQL.SQLSyntax.AST
|
||||||
|
import ChuSQL.SQLSyntax.Expr (evalCondForRow, evalExpr)
|
||||||
import Control.Monad (filterM)
|
import Control.Monad (filterM)
|
||||||
|
|
||||||
-- | 带限制的排序
|
|
||||||
applyLimit :: Maybe Int -> [Row] -> [Row]
|
|
||||||
applyLimit Nothing rows = rows
|
|
||||||
applyLimit (Just n) rows = take n rows
|
|
||||||
|
|
||||||
-- 表里加新行
|
-- 表里加新行
|
||||||
updateTable :: String -> (Table -> Table) -> Database -> Either String Database
|
updateTable :: String -> (Table -> Table) -> Database -> Either String Database
|
||||||
updateTable name f db = do
|
updateTable name f db = do
|
||||||
@@ -30,28 +26,10 @@ applyUpdates ((col, e) : rest) row = do
|
|||||||
|
|
||||||
-- 处理查询
|
-- 处理查询
|
||||||
runQuery :: Database -> Query -> Either String (Database, [Row])
|
runQuery :: Database -> Query -> Either String (Database, [Row])
|
||||||
runQuery
|
runQuery db q@Select{} = do
|
||||||
db
|
relOp <- translate db q
|
||||||
Select
|
rows <- evalRelOp db relOp
|
||||||
{ selectCols = cols
|
Right (db, rows)
|
||||||
, selectTable = tbl
|
|
||||||
, selectWhere = mWhere
|
|
||||||
, selectOrderBy = orderBy
|
|
||||||
, selectLimit = mLimit
|
|
||||||
} = do
|
|
||||||
table <- maybe (Left ("unknown table: " ++ tbl)) Right (lookup tbl db)
|
|
||||||
_ <- checkColumns (tableCols table) cols
|
|
||||||
_ <- checkColumns (tableCols table) (map fst orderBy)
|
|
||||||
rows <- case mWhere of
|
|
||||||
Nothing -> Right (tableRows table)
|
|
||||||
Just e -> filterM (evalCondForRow e) (tableRows table)
|
|
||||||
let sorted = sortRows orderBy rows
|
|
||||||
limited = applyLimit mLimit sorted
|
|
||||||
Right (db, map (project cols) limited)
|
|
||||||
where
|
|
||||||
project :: [String] -> Row -> Row
|
|
||||||
project ["*"] row = row
|
|
||||||
project cs row = [(c, v) | (c, v) <- row, c `elem` cs]
|
|
||||||
|
|
||||||
-- 处理插入
|
-- 处理插入
|
||||||
runQuery db (Insert tbl cols vals) = do
|
runQuery db (Insert tbl cols vals) = do
|
||||||
@@ -95,10 +73,3 @@ runQuery db (Update tbl assigns mWhere) = do
|
|||||||
if keep
|
if keep
|
||||||
then applyUpdates asgns row
|
then applyUpdates asgns row
|
||||||
else Right row
|
else Right row
|
||||||
|
|
||||||
-- 显式请求的列必须存在(@*@ 表示全部列),避免静默返回一堆空行
|
|
||||||
checkColumns :: [String] -> [String] -> Either String ()
|
|
||||||
checkColumns available requested =
|
|
||||||
case [c | c <- requested, c /= "*", c `notElem` available] of
|
|
||||||
[] -> Right ()
|
|
||||||
(c : _) -> Left ("unknown column: " ++ c)
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
module ChuSQL.Eval.Expr where
|
module ChuSQL.SQLSyntax.Expr where
|
||||||
|
|
||||||
import ChuSQL.Model
|
import ChuSQL.Model
|
||||||
import ChuSQL.Syntax.Ast
|
import ChuSQL.SQLSyntax.AST
|
||||||
import Control.Monad (join)
|
import Control.Monad (join)
|
||||||
|
|
||||||
-- 表达式求值
|
-- 表达式求值
|
||||||
+73
-11
@@ -1,11 +1,11 @@
|
|||||||
{-# LANGUAGE ImportQualifiedPost #-}
|
{-# LANGUAGE ImportQualifiedPost #-}
|
||||||
|
|
||||||
{- | SQL 语法分析器:把 SQL 文本解析为 'ChuSQL.Syntax.Ast.Query'。
|
{- | SQL 语法分析器:把 SQL 文本解析为 'ChuSQL.SQLSyntax.AST.Query'。
|
||||||
|
|
||||||
词法层负责空白、关键字、标识符、字面量与符号;语法层按
|
词法层负责空白、关键字、标识符、字面量与符号;语法层按
|
||||||
优先级 比较 > AND > OR 自底向上组装表达式。
|
优先级 比较 > AND > OR 自底向上组装表达式。
|
||||||
-}
|
-}
|
||||||
module ChuSQL.Syntax.Parser (
|
module ChuSQL.SQLSyntax.Parser (
|
||||||
Parser,
|
Parser,
|
||||||
parseQuery,
|
parseQuery,
|
||||||
expr,
|
expr,
|
||||||
@@ -14,9 +14,10 @@ module ChuSQL.Syntax.Parser (
|
|||||||
stringLit,
|
stringLit,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ChuSQL.Syntax.Ast
|
import ChuSQL.SQLSyntax.AST
|
||||||
import Control.Monad.Combinators.Expr (Operator (..), makeExprParser)
|
import Control.Monad.Combinators.Expr (Operator (..), makeExprParser)
|
||||||
import Data.Char (isAlpha, isAlphaNum)
|
import Data.Char (isAlpha, isAlphaNum, toLower)
|
||||||
|
import Data.List (intercalate)
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
import Data.Void (Void)
|
import Data.Void (Void)
|
||||||
import Text.Megaparsec
|
import Text.Megaparsec
|
||||||
@@ -88,6 +89,13 @@ limitClause = do
|
|||||||
keyword "limit"
|
keyword "limit"
|
||||||
integer
|
integer
|
||||||
|
|
||||||
|
-- | 解析JOIN和限定列名
|
||||||
|
qualifiedName :: Parser String
|
||||||
|
qualifiedName = do
|
||||||
|
first <- identifier
|
||||||
|
rest <- many (symbol "." *> identifier)
|
||||||
|
return (intercalate "." (first : rest))
|
||||||
|
|
||||||
-- * 表达式层
|
-- * 表达式层
|
||||||
|
|
||||||
-- | 表达式入口,最低优先级。
|
-- | 表达式入口,最低优先级。
|
||||||
@@ -114,7 +122,7 @@ atom =
|
|||||||
choice
|
choice
|
||||||
[ LitInt <$> integer
|
[ LitInt <$> integer
|
||||||
, LitStr <$> stringLit
|
, LitStr <$> stringLit
|
||||||
, Col <$> identifier
|
, Col <$> qualifiedName
|
||||||
, between (symbol "(") (symbol ")") expr
|
, between (symbol "(") (symbol ")") expr
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -123,7 +131,7 @@ atom =
|
|||||||
-- | 显式指定排序的语句
|
-- | 显式指定排序的语句
|
||||||
orderItem :: Parser (String, SortDir)
|
orderItem :: Parser (String, SortDir)
|
||||||
orderItem = do
|
orderItem = do
|
||||||
col <- identifier
|
col <- qualifiedName
|
||||||
dir <- sortDir
|
dir <- sortDir
|
||||||
return (col, dir)
|
return (col, dir)
|
||||||
|
|
||||||
@@ -140,28 +148,82 @@ assignment = do
|
|||||||
e <- expr
|
e <- expr
|
||||||
return (col, e)
|
return (col, e)
|
||||||
|
|
||||||
|
-- | 从句语法
|
||||||
|
|
||||||
|
-- | SQL 保留字,不能用作表别名
|
||||||
|
reservedWords :: [String]
|
||||||
|
reservedWords =
|
||||||
|
[ "select"
|
||||||
|
, "from"
|
||||||
|
, "where"
|
||||||
|
, "order"
|
||||||
|
, "by"
|
||||||
|
, "limit"
|
||||||
|
, "join"
|
||||||
|
, "on"
|
||||||
|
, "and"
|
||||||
|
, "or"
|
||||||
|
, "asc"
|
||||||
|
, "desc"
|
||||||
|
, "insert"
|
||||||
|
, "into"
|
||||||
|
, "values"
|
||||||
|
, "delete"
|
||||||
|
, "update"
|
||||||
|
, "set"
|
||||||
|
]
|
||||||
|
|
||||||
|
{- | 表别名。别名不能是保留字:否则 @FROM users WHERE age > 18@ 会把 @WHERE@
|
||||||
|
当成 @users@ 的别名吃掉,后面真正的 WHERE 子句就再也解析不到。
|
||||||
|
用 'try' 包住,一旦命中保留字就整体回溯,让 'optional' 正常返回 'Nothing'。
|
||||||
|
-}
|
||||||
|
aliasName :: Parser String
|
||||||
|
aliasName = try $ do
|
||||||
|
name <- identifier
|
||||||
|
if map toLower name `elem` reservedWords then empty else return name
|
||||||
|
|
||||||
|
tableRef :: Parser (Maybe String, String)
|
||||||
|
tableRef = do
|
||||||
|
tbl <- identifier
|
||||||
|
mAlias <- optional aliasName
|
||||||
|
return (mAlias, tbl)
|
||||||
|
|
||||||
|
joinClause :: Parser (Maybe String, String, Expr)
|
||||||
|
joinClause = do
|
||||||
|
keyword "join"
|
||||||
|
(mAlias, tbl) <- tableRef
|
||||||
|
keyword "on"
|
||||||
|
cond <- expr
|
||||||
|
return (mAlias, tbl, cond)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
-- | 选择列表:@*@ 或逗号分隔的列名。
|
-- | 选择列表:@*@ 或逗号分隔的列名。
|
||||||
selectList :: Parser [String]
|
selectList :: Parser [String]
|
||||||
selectList =
|
selectList =
|
||||||
choice
|
choice
|
||||||
[ ["*"] <$ symbol "*"
|
[ ["*"] <$ symbol "*"
|
||||||
, (:) <$> identifier <*> many (symbol "," *> identifier)
|
, (:) <$> qualifiedName <*> many (symbol "," *> qualifiedName)
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | @SELECT ... FROM ... [WHERE ...]@。
|
-- | @SELECT ... FROM ... [WHERE ...]@。
|
||||||
selectQuery :: Parser Query
|
selectQuery :: Parser Query
|
||||||
selectQuery = do
|
selectQuery = do
|
||||||
_ <- keyword "select"
|
keyword "select"
|
||||||
cols <- selectList
|
cols <- selectList
|
||||||
_ <- keyword "from"
|
keyword "from"
|
||||||
tableName <- identifier
|
fromC <- fromClause
|
||||||
mWhere <- optional (keyword "where" *> expr)
|
mWhere <- optional (keyword "where" *> expr)
|
||||||
orderBy <- fromMaybe [] <$> optional orderByClause
|
orderBy <- fromMaybe [] <$> optional orderByClause
|
||||||
mLimit <- optional limitClause
|
mLimit <- optional limitClause
|
||||||
return
|
return
|
||||||
Select
|
Select
|
||||||
{ selectCols = cols
|
{ selectCols = cols
|
||||||
, selectTable = tableName
|
, selectFrom = fromC
|
||||||
, selectWhere = mWhere
|
, selectWhere = mWhere
|
||||||
, selectOrderBy = orderBy
|
, selectOrderBy = orderBy
|
||||||
, selectLimit = mLimit
|
, selectLimit = mLimit
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
module ChuSQL.Eval.Sort where
|
module ChuSQL.SQLSyntax.Sort where
|
||||||
|
|
||||||
import ChuSQL.Model
|
import ChuSQL.Model
|
||||||
import ChuSQL.Syntax.Ast
|
import ChuSQL.SQLSyntax.AST
|
||||||
import Data.List (sortBy)
|
import Data.List (sortBy)
|
||||||
|
|
||||||
-- | 反向排序
|
-- | 反向排序
|
||||||
Whitespace-only changes.
+174
-52
@@ -1,37 +1,50 @@
|
|||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
import ChuSQL.Eval.Executor
|
|
||||||
import ChuSQL.Model
|
import ChuSQL.Model
|
||||||
import ChuSQL.Syntax.Ast
|
import ChuSQL.SQLSyntax.AST
|
||||||
import ChuSQL.Syntax.Parser
|
import ChuSQL.SQLSyntax.Executor
|
||||||
|
import ChuSQL.SQLSyntax.Parser
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
-- Test database
|
-- Test database
|
||||||
testDB :: Database
|
-- ============================================================
|
||||||
testDB = [("users", users)]
|
users :: Table
|
||||||
where
|
users =
|
||||||
users =
|
|
||||||
Table
|
Table
|
||||||
{ tableName = "users"
|
{ tableName = "users"
|
||||||
, tableCols = ["name", "age"]
|
, tableCols = ["id", "name", "age"]
|
||||||
, tableRows =
|
, tableRows =
|
||||||
[ [("name", VStr "Alice"), ("age", VInt 25)]
|
[ [("id", VInt 1), ("name", VStr "Alice"), ("age", VInt 25)]
|
||||||
, [("name", VStr "Bob"), ("age", VInt 17)]
|
, [("id", VInt 2), ("name", VStr "Bob"), ("age", VInt 17)]
|
||||||
, [("name", VStr "Carol"), ("age", VInt 30)]
|
, [("id", VInt 3), ("name", VStr "Carol"), ("age", VInt 30)]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orders :: Table
|
||||||
|
orders =
|
||||||
|
Table
|
||||||
|
{ tableName = "orders"
|
||||||
|
, tableCols = ["id", "user_id", "product"]
|
||||||
|
, tableRows =
|
||||||
|
[ [("id", VInt 1), ("user_id", VInt 1), ("product", VStr "Book")]
|
||||||
|
, [("id", VInt 2), ("user_id", VInt 2), ("product", VStr "Pen")]
|
||||||
|
, [("id", VInt 3), ("user_id", VInt 1), ("product", VStr "Cup")]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
testDB :: Database
|
||||||
|
testDB = [("users", users), ("orders", orders)]
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Main
|
||||||
|
-- ============================================================
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = hspec $ do
|
main = hspec $ do
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- AST
|
-- AST
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
describe "ChuSQL.Syntax.Ast" $ do
|
describe "ChuSQL.Syntax.Ast" $ do
|
||||||
it "shows a Select without WHERE" $ do
|
|
||||||
show (makeSelect ["name"] "users" Nothing)
|
|
||||||
`shouldBe` "Select {selectCols = [\"name\"], selectTable = \"users\", \
|
|
||||||
\selectWhere = Nothing, selectOrderBy = [], selectLimit = Nothing}"
|
|
||||||
|
|
||||||
it "equal Selects are equal" $ do
|
it "equal Selects are equal" $ do
|
||||||
makeSelect ["name"] "users" Nothing
|
makeSelect ["name"] "users" Nothing
|
||||||
`shouldBe` makeSelect ["name"] "users" Nothing
|
`shouldBe` makeSelect ["name"] "users" Nothing
|
||||||
@@ -177,13 +190,56 @@ main = hspec $ do
|
|||||||
)
|
)
|
||||||
|
|
||||||
it "rejects trailing junk after a complete query" $ do
|
it "rejects trailing junk after a complete query" $ do
|
||||||
parseQuery "SELECT name FROM users extra"
|
parseQuery "SELECT name FROM users extra junk"
|
||||||
`shouldSatisfy` isLeft
|
`shouldSatisfy` isLeft
|
||||||
|
|
||||||
it "returns Left on missing table name" $ do
|
it "returns Left on missing table name" $ do
|
||||||
parseQuery "SELECT name FROM"
|
parseQuery "SELECT name FROM"
|
||||||
`shouldSatisfy` isLeft
|
`shouldSatisfy` isLeft
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Parser: JOIN
|
||||||
|
-- ============================================================
|
||||||
|
describe "ChuSQL.Syntax.Parser (JOIN)" $ do
|
||||||
|
it "parses a simple JOIN without aliases" $ do
|
||||||
|
parseQuery "SELECT name FROM users JOIN orders ON id = user_id"
|
||||||
|
`shouldBe` Right
|
||||||
|
( Select
|
||||||
|
{ selectCols = ["name"]
|
||||||
|
, selectFrom =
|
||||||
|
FromJoin
|
||||||
|
(FromTable Nothing "users")
|
||||||
|
Nothing
|
||||||
|
"orders"
|
||||||
|
(Eq (Col "id") (Col "user_id"))
|
||||||
|
, selectWhere = Nothing
|
||||||
|
, selectOrderBy = []
|
||||||
|
, selectLimit = Nothing
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it "parses JOIN with aliases" $ do
|
||||||
|
parseQuery "SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id"
|
||||||
|
`shouldBe` Right
|
||||||
|
( Select
|
||||||
|
{ selectCols = ["u.name"]
|
||||||
|
, selectFrom =
|
||||||
|
FromJoin
|
||||||
|
(FromTable (Just "u") "users")
|
||||||
|
(Just "o")
|
||||||
|
"orders"
|
||||||
|
(Eq (Col "u.id") (Col "o.user_id"))
|
||||||
|
, selectWhere = Nothing
|
||||||
|
, selectOrderBy = []
|
||||||
|
, selectLimit = Nothing
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it "parses qualified column names" $ do
|
||||||
|
case parseQuery "SELECT u.name, o.product FROM users u JOIN orders o ON u.id = o.user_id" of
|
||||||
|
Right q -> selectCols q `shouldBe` ["u.name", "o.product"]
|
||||||
|
Left err -> expectationFailure err
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Engine: SELECT
|
-- Engine: SELECT
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
@@ -191,9 +247,9 @@ main = hspec $ do
|
|||||||
it "returns all rows without WHERE" $ do
|
it "returns all rows without WHERE" $ do
|
||||||
rowsOf (runQuery testDB (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery testDB (makeSelect ["*"] "users" Nothing))
|
||||||
`shouldBe` Right
|
`shouldBe` Right
|
||||||
[ [("name", VStr "Alice"), ("age", VInt 25)]
|
[ [("id", VInt 1), ("name", VStr "Alice"), ("age", VInt 25)]
|
||||||
, [("name", VStr "Bob"), ("age", VInt 17)]
|
, [("id", VInt 2), ("name", VStr "Bob"), ("age", VInt 17)]
|
||||||
, [("name", VStr "Carol"), ("age", VInt 30)]
|
, [("id", VInt 3), ("name", VStr "Carol"), ("age", VInt 30)]
|
||||||
]
|
]
|
||||||
|
|
||||||
it "filters with WHERE" $ do
|
it "filters with WHERE" $ do
|
||||||
@@ -261,7 +317,7 @@ main = hspec $ do
|
|||||||
case runQuery testDB (Insert "users" ["name", "age"] [LitStr "Dave", LitInt 22]) of
|
case runQuery testDB (Insert "users" ["name", "age"] [LitStr "Dave", LitInt 22]) of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (db', _) -> do
|
Right (db', _) -> do
|
||||||
rowsOf (runQuery db' (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery db' (makeSelect ["name", "age"] "users" Nothing))
|
||||||
`shouldBe` Right
|
`shouldBe` Right
|
||||||
[ [("name", VStr "Alice"), ("age", VInt 25)]
|
[ [("name", VStr "Alice"), ("age", VInt 25)]
|
||||||
, [("name", VStr "Bob"), ("age", VInt 17)]
|
, [("name", VStr "Bob"), ("age", VInt 17)]
|
||||||
@@ -273,7 +329,7 @@ main = hspec $ do
|
|||||||
case runQuery testDB (Insert "users" ["name", "age"] [LitStr "Dave", LitInt 22]) of
|
case runQuery testDB (Insert "users" ["name", "age"] [LitStr "Dave", LitInt 22]) of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (_, _) -> do
|
Right (_, _) -> do
|
||||||
rowsOf (runQuery testDB (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery testDB (makeSelect ["name"] "users" Nothing))
|
||||||
`shouldSatisfy` \r -> case r of
|
`shouldSatisfy` \r -> case r of
|
||||||
Right rows -> length rows == 3
|
Right rows -> length rows == 3
|
||||||
Left _ -> False
|
Left _ -> False
|
||||||
@@ -310,24 +366,24 @@ main = hspec $ do
|
|||||||
case runQuery testDB (Delete "users" (Just (Lt (Col "age") (LitInt 18)))) of
|
case runQuery testDB (Delete "users" (Just (Lt (Col "age") (LitInt 18)))) of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (db', _) -> do
|
Right (db', _) -> do
|
||||||
rowsOf (runQuery db' (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery db' (makeSelect ["name"] "users" Nothing))
|
||||||
`shouldBe` Right
|
`shouldBe` Right
|
||||||
[ [("name", VStr "Alice"), ("age", VInt 25)]
|
[ [("name", VStr "Alice")]
|
||||||
, [("name", VStr "Carol"), ("age", VInt 30)]
|
, [("name", VStr "Carol")]
|
||||||
]
|
]
|
||||||
|
|
||||||
it "executes DELETE without WHERE, removing all rows" $ do
|
it "executes DELETE without WHERE, removing all rows" $ do
|
||||||
case runQuery testDB (Delete "users" Nothing) of
|
case runQuery testDB (Delete "users" Nothing) of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (db', _) -> do
|
Right (db', _) -> do
|
||||||
rowsOf (runQuery db' (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery db' (makeSelect ["name"] "users" Nothing))
|
||||||
`shouldBe` Right []
|
`shouldBe` Right []
|
||||||
|
|
||||||
it "does not modify the original database" $ do
|
it "does not modify the original database" $ do
|
||||||
case runQuery testDB (Delete "users" Nothing) of
|
case runQuery testDB (Delete "users" Nothing) of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (_, _) -> do
|
Right (_, _) -> do
|
||||||
rowsOf (runQuery testDB (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery testDB (makeSelect ["name"] "users" Nothing))
|
||||||
`shouldSatisfy` \r -> case r of
|
`shouldSatisfy` \r -> case r of
|
||||||
Right rows -> length rows == 3
|
Right rows -> length rows == 3
|
||||||
Left _ -> False
|
Left _ -> False
|
||||||
@@ -380,7 +436,7 @@ main = hspec $ do
|
|||||||
case runQuery testDB (Update "users" [("age", LitInt 99)] (Just (Eq (Col "name") (LitStr "Alice")))) of
|
case runQuery testDB (Update "users" [("age", LitInt 99)] (Just (Eq (Col "name") (LitStr "Alice")))) of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (db', _) -> do
|
Right (db', _) -> do
|
||||||
rowsOf (runQuery db' (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery db' (makeSelect ["name", "age"] "users" Nothing))
|
||||||
`shouldBe` Right
|
`shouldBe` Right
|
||||||
[ [("name", VStr "Alice"), ("age", VInt 99)]
|
[ [("name", VStr "Alice"), ("age", VInt 99)]
|
||||||
, [("name", VStr "Bob"), ("age", VInt 17)]
|
, [("name", VStr "Bob"), ("age", VInt 17)]
|
||||||
@@ -391,7 +447,7 @@ main = hspec $ do
|
|||||||
case runQuery testDB (Update "users" [("age", LitInt 0)] Nothing) of
|
case runQuery testDB (Update "users" [("age", LitInt 0)] Nothing) of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (db', _) -> do
|
Right (db', _) -> do
|
||||||
rowsOf (runQuery db' (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery db' (makeSelect ["name", "age"] "users" Nothing))
|
||||||
`shouldBe` Right
|
`shouldBe` Right
|
||||||
[ [("name", VStr "Alice"), ("age", VInt 0)]
|
[ [("name", VStr "Alice"), ("age", VInt 0)]
|
||||||
, [("name", VStr "Bob"), ("age", VInt 0)]
|
, [("name", VStr "Bob"), ("age", VInt 0)]
|
||||||
@@ -402,7 +458,7 @@ main = hspec $ do
|
|||||||
case runQuery testDB (Update "users" [("age", LitInt 99), ("name", LitStr "X")] Nothing) of
|
case runQuery testDB (Update "users" [("age", LitInt 99), ("name", LitStr "X")] Nothing) of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (db', _) -> do
|
Right (db', _) -> do
|
||||||
rowsOf (runQuery db' (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery db' (makeSelect ["name", "age"] "users" Nothing))
|
||||||
`shouldBe` Right
|
`shouldBe` Right
|
||||||
[ [("name", VStr "X"), ("age", VInt 99)]
|
[ [("name", VStr "X"), ("age", VInt 99)]
|
||||||
, [("name", VStr "X"), ("age", VInt 99)]
|
, [("name", VStr "X"), ("age", VInt 99)]
|
||||||
@@ -413,7 +469,7 @@ main = hspec $ do
|
|||||||
case runQuery testDB (Update "users" [("age", LitInt 0)] Nothing) of
|
case runQuery testDB (Update "users" [("age", LitInt 0)] Nothing) of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (_, _) -> do
|
Right (_, _) -> do
|
||||||
rowsOf (runQuery testDB (makeSelect ["*"] "users" Nothing))
|
rowsOf (runQuery testDB (makeSelect ["name"] "users" Nothing))
|
||||||
`shouldSatisfy` \r -> case r of
|
`shouldSatisfy` \r -> case r of
|
||||||
Right rows -> length rows == 3
|
Right rows -> length rows == 3
|
||||||
Left _ -> False
|
Left _ -> False
|
||||||
@@ -465,7 +521,7 @@ main = hspec $ do
|
|||||||
let q =
|
let q =
|
||||||
Select
|
Select
|
||||||
{ selectCols = ["name"]
|
{ selectCols = ["name"]
|
||||||
, selectTable = "users"
|
, selectFrom = FromTable Nothing "users"
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectOrderBy = [("age", Asc)]
|
, selectOrderBy = [("age", Asc)]
|
||||||
, selectLimit = Nothing
|
, selectLimit = Nothing
|
||||||
@@ -481,7 +537,7 @@ main = hspec $ do
|
|||||||
let q =
|
let q =
|
||||||
Select
|
Select
|
||||||
{ selectCols = ["name"]
|
{ selectCols = ["name"]
|
||||||
, selectTable = "users"
|
, selectFrom = FromTable Nothing "users"
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectOrderBy = [("age", Desc)]
|
, selectOrderBy = [("age", Desc)]
|
||||||
, selectLimit = Nothing
|
, selectLimit = Nothing
|
||||||
@@ -497,7 +553,7 @@ main = hspec $ do
|
|||||||
let q =
|
let q =
|
||||||
Select
|
Select
|
||||||
{ selectCols = ["name"]
|
{ selectCols = ["name"]
|
||||||
, selectTable = "users"
|
, selectFrom = FromTable Nothing "users"
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectOrderBy = [("name", Asc)]
|
, selectOrderBy = [("name", Asc)]
|
||||||
, selectLimit = Nothing
|
, selectLimit = Nothing
|
||||||
@@ -513,7 +569,7 @@ main = hspec $ do
|
|||||||
let q =
|
let q =
|
||||||
Select
|
Select
|
||||||
{ selectCols = ["name"]
|
{ selectCols = ["name"]
|
||||||
, selectTable = "users"
|
, selectFrom = FromTable Nothing "users"
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectOrderBy = [("nope", Asc)]
|
, selectOrderBy = [("nope", Asc)]
|
||||||
, selectLimit = Nothing
|
, selectLimit = Nothing
|
||||||
@@ -545,7 +601,7 @@ main = hspec $ do
|
|||||||
let q =
|
let q =
|
||||||
Select
|
Select
|
||||||
{ selectCols = ["name"]
|
{ selectCols = ["name"]
|
||||||
, selectTable = "users"
|
, selectFrom = FromTable Nothing "users"
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectOrderBy = []
|
, selectOrderBy = []
|
||||||
, selectLimit = Just 2
|
, selectLimit = Just 2
|
||||||
@@ -560,7 +616,7 @@ main = hspec $ do
|
|||||||
let q =
|
let q =
|
||||||
Select
|
Select
|
||||||
{ selectCols = ["name"]
|
{ selectCols = ["name"]
|
||||||
, selectTable = "users"
|
, selectFrom = FromTable Nothing "users"
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectOrderBy = [("age", Desc)]
|
, selectOrderBy = [("age", Desc)]
|
||||||
, selectLimit = Just 2
|
, selectLimit = Just 2
|
||||||
@@ -575,7 +631,7 @@ main = hspec $ do
|
|||||||
let q =
|
let q =
|
||||||
Select
|
Select
|
||||||
{ selectCols = ["name"]
|
{ selectCols = ["name"]
|
||||||
, selectTable = "users"
|
, selectFrom = FromTable Nothing "users"
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectOrderBy = []
|
, selectOrderBy = []
|
||||||
, selectLimit = Just 10
|
, selectLimit = Just 10
|
||||||
@@ -591,7 +647,7 @@ main = hspec $ do
|
|||||||
let q =
|
let q =
|
||||||
Select
|
Select
|
||||||
{ selectCols = ["name"]
|
{ selectCols = ["name"]
|
||||||
, selectTable = "users"
|
, selectFrom = FromTable Nothing "users"
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectOrderBy = []
|
, selectOrderBy = []
|
||||||
, selectLimit = Just 0
|
, selectLimit = Just 0
|
||||||
@@ -603,6 +659,76 @@ main = hspec $ do
|
|||||||
parseQuery "SELECT * FROM users LIMIT -1"
|
parseQuery "SELECT * FROM users LIMIT -1"
|
||||||
`shouldSatisfy` isLeft
|
`shouldSatisfy` isLeft
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Engine: JOIN
|
||||||
|
-- ============================================================
|
||||||
|
describe "ChuSQL.Eval.Engine (JOIN)" $ do
|
||||||
|
it "executes a two-table JOIN with aliases" $ do
|
||||||
|
rowsOf (parseQuery "SELECT u.name, o.product FROM users u JOIN orders o ON u.id = o.user_id" >>= runQuery testDB)
|
||||||
|
`shouldBe` Right
|
||||||
|
[ [("u.name", VStr "Alice"), ("o.product", VStr "Book")]
|
||||||
|
, [("u.name", VStr "Alice"), ("o.product", VStr "Cup")]
|
||||||
|
, [("u.name", VStr "Bob"), ("o.product", VStr "Pen")]
|
||||||
|
]
|
||||||
|
|
||||||
|
it "executes JOIN with WHERE" $ do
|
||||||
|
rowsOf (parseQuery "SELECT u.name, o.product FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 20" >>= runQuery testDB)
|
||||||
|
`shouldBe` Right
|
||||||
|
[ [("u.name", VStr "Alice"), ("o.product", VStr "Book")]
|
||||||
|
, [("u.name", VStr "Alice"), ("o.product", VStr "Cup")]
|
||||||
|
]
|
||||||
|
|
||||||
|
it "executes JOIN with ORDER BY" $ do
|
||||||
|
rowsOf (parseQuery "SELECT u.name, o.product FROM users u JOIN orders o ON u.id = o.user_id ORDER BY o.product DESC" >>= runQuery testDB)
|
||||||
|
`shouldBe` Right
|
||||||
|
[ [("u.name", VStr "Bob"), ("o.product", VStr "Pen")]
|
||||||
|
, [("u.name", VStr "Alice"), ("o.product", VStr "Cup")]
|
||||||
|
, [("u.name", VStr "Alice"), ("o.product", VStr "Book")]
|
||||||
|
]
|
||||||
|
|
||||||
|
it "executes JOIN with LIMIT" $ do
|
||||||
|
rowsOf (parseQuery "SELECT u.name, o.product FROM users u JOIN orders o ON u.id = o.user_id LIMIT 2" >>= runQuery testDB)
|
||||||
|
`shouldBe` Right
|
||||||
|
[ [("u.name", VStr "Alice"), ("o.product", VStr "Book")]
|
||||||
|
, [("u.name", VStr "Alice"), ("o.product", VStr "Cup")]
|
||||||
|
]
|
||||||
|
|
||||||
|
it "returns Left when JOIN table not found" $ do
|
||||||
|
runQuery
|
||||||
|
testDB
|
||||||
|
( Select
|
||||||
|
{ selectCols = ["u.name"]
|
||||||
|
, selectFrom =
|
||||||
|
FromJoin
|
||||||
|
(FromTable (Just "u") "users")
|
||||||
|
(Just "x")
|
||||||
|
"nonexistent"
|
||||||
|
(Eq (Col "u.id") (Col "x.id"))
|
||||||
|
, selectWhere = Nothing
|
||||||
|
, selectOrderBy = []
|
||||||
|
, selectLimit = Nothing
|
||||||
|
}
|
||||||
|
)
|
||||||
|
`shouldSatisfy` isLeft
|
||||||
|
|
||||||
|
it "returns Left when ON references an unknown column" $ do
|
||||||
|
runQuery
|
||||||
|
testDB
|
||||||
|
( Select
|
||||||
|
{ selectCols = ["u.name"]
|
||||||
|
, selectFrom =
|
||||||
|
FromJoin
|
||||||
|
(FromTable (Just "u") "users")
|
||||||
|
(Just "o")
|
||||||
|
"orders"
|
||||||
|
(Eq (Col "u.nope") (Col "o.user_id"))
|
||||||
|
, selectWhere = Nothing
|
||||||
|
, selectOrderBy = []
|
||||||
|
, selectLimit = Nothing
|
||||||
|
}
|
||||||
|
)
|
||||||
|
`shouldSatisfy` isLeft
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- End-to-end
|
-- End-to-end
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
@@ -652,7 +778,7 @@ main = hspec $ do
|
|||||||
case parseQuery "UPDATE users SET age = 99 WHERE name = 'Alice'" >>= runQuery testDB of
|
case parseQuery "UPDATE users SET age = 99 WHERE name = 'Alice'" >>= runQuery testDB of
|
||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right (db', _) -> do
|
Right (db', _) -> do
|
||||||
rowsOf (parseQuery "SELECT * FROM users" >>= runQuery db')
|
rowsOf (parseQuery "SELECT name, age FROM users" >>= runQuery db')
|
||||||
`shouldBe` Right
|
`shouldBe` Right
|
||||||
[ [("name", VStr "Alice"), ("age", VInt 99)]
|
[ [("name", VStr "Alice"), ("age", VInt 99)]
|
||||||
, [("name", VStr "Bob"), ("age", VInt 17)]
|
, [("name", VStr "Bob"), ("age", VInt 17)]
|
||||||
@@ -666,18 +792,6 @@ main = hspec $ do
|
|||||||
, [("name", VStr "Alice")]
|
, [("name", VStr "Alice")]
|
||||||
]
|
]
|
||||||
|
|
||||||
it "ORDER BY then INSERT then SELECT end-to-end" $ do
|
|
||||||
case parseQuery "INSERT INTO users (name, age) VALUES ('Dave', 99)" >>= runQuery testDB of
|
|
||||||
Left err -> expectationFailure err
|
|
||||||
Right (db', _) -> do
|
|
||||||
rowsOf (parseQuery "SELECT name FROM users ORDER BY age DESC" >>= runQuery db')
|
|
||||||
`shouldBe` Right
|
|
||||||
[ [("name", VStr "Dave")]
|
|
||||||
, [("name", VStr "Carol")]
|
|
||||||
, [("name", VStr "Alice")]
|
|
||||||
, [("name", VStr "Bob")]
|
|
||||||
]
|
|
||||||
|
|
||||||
it "ORDER BY with LIMIT end-to-end" $ do
|
it "ORDER BY with LIMIT end-to-end" $ do
|
||||||
rowsOf (parseQuery "SELECT name FROM users ORDER BY age DESC LIMIT 2" >>= runQuery testDB)
|
rowsOf (parseQuery "SELECT name FROM users ORDER BY age DESC LIMIT 2" >>= runQuery testDB)
|
||||||
`shouldBe` Right
|
`shouldBe` Right
|
||||||
@@ -691,6 +805,14 @@ main = hspec $ do
|
|||||||
[ [("name", VStr "Carol")]
|
[ [("name", VStr "Carol")]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
it "JOIN with WHERE and ORDER BY end-to-end" $ do
|
||||||
|
rowsOf (parseQuery "SELECT u.name, o.product FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 15 ORDER BY o.product" >>= runQuery testDB)
|
||||||
|
`shouldBe` Right
|
||||||
|
[ [("u.name", VStr "Alice"), ("o.product", VStr "Book")]
|
||||||
|
, [("u.name", VStr "Alice"), ("o.product", VStr "Cup")]
|
||||||
|
, [("u.name", VStr "Bob"), ("o.product", VStr "Pen")]
|
||||||
|
]
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Helpers
|
-- Helpers
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
### 词法分析
|
### 词法分析
|
||||||
|
|
||||||
词法层与语法层同处 `chusql-engine/src/ChuSQL/Syntax/Parser.hs`,基于 **megaparsec** 实现。解析器类型为
|
词法层与语法层同处 `chusql-engine/src/ChuSQL/SQLSyntax/Parser.hs`,基于 **megaparsec** 实现。解析器类型为
|
||||||
|
|
||||||
```haskell
|
```haskell
|
||||||
type Parser = Parsec Void String
|
type Parser = Parsec Void String
|
||||||
|
|||||||
Reference in new issue
Block a user