派生表与投影别名

This commit is contained in:
LIm1tZz7 committed 2026-10-03 19:49:57 +08:00
1 parent a8e85b7ac0
commit 30f1014016
14 files changed
+232 -53

No files matched your search

@@ -409,6 +409,9 @@ evalRelOpIn db outer (Sort spec op) = do
evalRelOpIn db outer (Limit n op) = do
rows <- evalRelOpIn db outer op
pure (fmap (take n) rows)
evalRelOpIn db outer (Derived mAlias op) = do
rows <- evalRelOpIn db outer op
pure (fmap (prefixRows mAlias) rows)
evalRelOpIn db outer (Join kind l r c) = do
lres <- evalRelOpIn db outer l
case lres of
@@ -21,6 +21,7 @@ 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 (Derived a x) = Derived a (pass db free x)
pass db free (Join kind l r c)
| free
, Just (leaves, conds) <- innerChain (Join kind l r c)
@@ -17,6 +17,7 @@ data RelOp
| Sort [(String, SortDir)] RelOp
| Limit Int RelOp
| Join JoinKind RelOp RelOp Expr
| Derived (Maybe String) RelOp
deriving (Show, Eq)
-- | 算子会产出哪些列
@@ -39,6 +40,7 @@ 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
relOpCols db (Derived mAlias x) = map (qualify mAlias) (relOpCols db x)
-- | 连接的显示名
kindLabel :: JoinKind -> String
@@ -69,5 +71,6 @@ renderPlan = go 0
Sort s x -> pad ++ "Sort " ++ show s ++ "\n" ++ go (ind + 1) x
Limit n x -> pad ++ "Limit " ++ show n ++ "\n" ++ go (ind + 1) x
Join k l r c -> pad ++ kindLabel k ++ " on " ++ show c ++ "\n" ++ go (ind + 1) l ++ "\n" ++ go (ind + 1) r
Derived a x -> pad ++ "Derived " ++ show a ++ "\n" ++ go (ind + 1) x
where
pad = replicate (ind * 2) ' '
@@ -79,6 +79,7 @@ planHasDivision (Compute items x) = any (hasDivision . snd) items || planHasDivi
planHasDivision (Project _ x) = planHasDivision x
planHasDivision (Sort _ x) = planHasDivision x
planHasDivision (Limit _ x) = planHasDivision x
planHasDivision (Derived _ x) = planHasDivision x
planHasDivision _ = False
-- | 是否要全部列
@@ -117,6 +118,7 @@ pushProject db need leaf@(Scan a t _)
| needsAll need = leaf
| all (`elem` need) (relOpCols db leaf) = leaf
| otherwise = Scan a t (Just (nub (map (unqualify a) need)))
pushProject _ _ (Derived a x) = Derived a x
pushProject db need leaf@(Lookup _ _ _ _) = pushProjectLeaf db need leaf
pushProject db need leaf@(Range _ _ _ _ _) = pushProjectLeaf db need leaf
@@ -202,6 +204,7 @@ optimizeRelOp db = rewriteNode db . descend
descend (Sort spec x) = Sort spec (optimizeRelOp db x)
descend (Limit n x) = Limit n (optimizeRelOp db x)
descend (Join kind l r c) = Join kind (optimizeRelOp db l) (optimizeRelOp db r) c
descend (Derived a x) = Derived a (optimizeRelOp db x)
descend x = x
-- | 反复跑到不动点
@@ -6,18 +6,19 @@ import ChuSQL.Core.Engine.Syntax.AST
-- 计划生成:把语句翻译成算子树。
-- | 把 FROM 子句翻成 Scan / Join
fromToRelOp :: FromClause -> RelOp
fromToRelOp FromUnit = Unit
fromToRelOp (FromTable mAlias tbl) = Scan mAlias tbl Nothing
fromToRelOp (FromJoin kind left mAlias tbl cond) =
Join kind (fromToRelOp left) (Scan mAlias tbl Nothing) cond
-- | FROM 子句翻成关系算子
fromToRelOp :: FromClause -> Either String RelOp
fromToRelOp FromUnit = Right Unit
fromToRelOp (FromTable mAlias tbl) = Right (Scan mAlias tbl Nothing)
fromToRelOp (FromSubquery mAlias stmt) = Derived mAlias <$> translate stmt
fromToRelOp (FromJoin kind left right cond) =
Join kind <$> fromToRelOp left <*> fromToRelOp right <*> pure cond
-- | 带上 WHERE 的来源
source :: FromClause -> Maybe Expr -> RelOp
source fromC mWhere = case mWhere of
Nothing -> fromToRelOp fromC
Just w -> Filter w (fromToRelOp fromC)
source :: FromClause -> Maybe Expr -> Either String RelOp
source fromC mWhere = do
op <- fromToRelOp fromC
Right (maybe op (`Filter` op) mWhere)
-- | 给用到的聚合起内部列名,同一个聚合只算一次
nameAggs :: [(String, Expr)] -> [(String, Expr)]
@@ -80,9 +81,11 @@ translate q = case q of
, selectOrderBy = orderBy
, selectLimit = mLimit
} -> do
let (plan, _) = grouped groupBy [] (source fromC mWhere)
plan0 <- source fromC mWhere
let (plan, _) = grouped groupBy [] plan0
Right (Project cols (limited orderBy mLimit plan))
SelectExpr items fromC mWhere groupBy orderBy mLimit -> do
let (plan, items') = grouped groupBy items (source fromC mWhere)
plan0 <- source fromC mWhere
let (plan, items') = grouped groupBy items plan0
Right (Compute items' (limited orderBy mLimit plan))
_ -> Left "only SELECT is supported by translate"
@@ -44,13 +44,21 @@ checkFrom _ _ FromUnit = Right []
checkFrom db _ (FromTable mAlias tbl) = do
(key, t) <- resolveTable db tbl
Right (prefixColumns (Just (deriveQualifier mAlias key)) (tableCols t))
checkFrom db outer (FromJoin _ left mAlias tbl cond) = do
checkFrom db _ (FromSubquery mAlias stmt) = do
out <- inferSubqueryOutput db (Scope [] []) (Subquery stmt [])
Right (prefixColumns mAlias [(name, plainColumn (derivedType t)) | (name, t) <- out])
checkFrom db outer (FromJoin _ left right cond) = do
lcols <- checkFrom db outer left
(key, t) <- resolveTable db tbl
let env = lcols ++ prefixColumns (Just (deriveQualifier mAlias key)) (tableCols t)
rcols <- checkFrom db outer right
let env = lcols ++ rcols
checkBool db "ON" (Scope env outer) cond
Right env
-- | 派生表里 NULL 字面量列没有具体类型,按字符串处理
derivedType :: InferredType -> ColumnType
derivedType (InferType t) = t
derivedType InferNull = CStr
-- | 合并两层列,本层优先且不重复
scopedEnv :: [(String, Column)] -> [(String, Column)] -> [(String, Column)]
scopedEnv env outer = env ++ [o | o <- outer, o `notElem` env]
@@ -638,7 +646,8 @@ statementColumns stmt = case stmt of
-- | 取 FROM 里出现的列
fromColumns FromUnit = []
fromColumns (FromTable _ _) = []
fromColumns (FromJoin _ l _ _ c) = fromColumns l ++ colsInExpr c
fromColumns (FromSubquery _ _) = []
fromColumns (FromJoin _ l r c) = fromColumns l ++ fromColumns r ++ colsInExpr c
-- | 取语句的 FROM 子句,没有就是 FromUnit
statementFrom :: Statement -> FromClause
@@ -692,17 +701,18 @@ resolveSelect _ q = Right q
-- | 解析投影、条件、排序与连接引用
resolveQuery :: Database -> [(String, Column)] -> Statement -> [(String, Expr)] -> Either String Statement
resolveQuery db outer q items = do
env <- checkFrom db outer (selectFrom q)
source <- resolveSource db outer (selectFrom q)
env <- checkFrom db outer source
-- | 单表无别名时把裸列名换成物理列名;带别名时表头保留别名限定名
let physical c = case selectFrom q of
FromTable Nothing tbl -> unqualify (Just (deriveQualifier Nothing tbl)) c
_ -> c
-- | 查一列并换算成物理名
name place c = physical . fst <$> scopedColumnAt place env outer c
-- | 换算一个投影项
output (_, Col c) = do
-- | 换算一个投影项:带别名时保留别名做输出列名
output (label, Col c) = do
k <- name "SELECT" c
pure (k, Col k)
pure (if label == c then (k, Col k) else (label, Col k))
output (label, e) = do
e' <- resolveExpr db env outer (name "SELECT") e
pure (label, e')
@@ -714,24 +724,68 @@ resolveQuery db outer q items = do
condition <- mapM (resolveExpr db env outer (name "WHERE")) (selectWhere q)
grouping <- mapM (name "GROUP BY") (selectGroupBy q)
ordering <- mapM (\(c, d) -> (\k -> (k, d)) <$> name "ORDER BY" c) (selectOrderBy q)
source <- case selectFrom q of
FromTable mAlias tbl -> do
(key, _) <- resolveTable db tbl
Right (FromTable mAlias key)
other -> resolveFrom db outer other
pure $ case q of
Select{} -> Select (map fst projected) source condition grouping ordering (selectLimit q)
_ -> SelectExpr projected source condition grouping ordering (selectLimit q)
-- | 连接两侧取表名末段作别名;ON 可引用外层列
-- | 单表不加限定名,其余来源交给 resolveFrom
resolveSource :: Database -> [(String, Column)] -> FromClause -> Either String FromClause
resolveSource db outer fromC = case fromC of
FromTable mAlias tbl -> do
(key, _) <- resolveTable db tbl
Right (FromTable mAlias key)
other -> resolveFrom db outer other
-- | 连接两侧与派生表都要先解析,再取环境解析 ON
resolveFrom :: Database -> [(String, Column)] -> FromClause -> Either String FromClause
resolveFrom _ _ FromUnit = Right FromUnit
resolveFrom db _ (FromTable alias tbl) = do
(key, _) <- resolveTable db tbl
Right (FromTable (Just (deriveQualifier alias key)) key)
resolveFrom db outer fromC@(FromJoin kind left alias tbl cond) = do
env <- checkFrom db outer fromC
resolveFrom db _ (FromSubquery mAlias stmt) = do
inner <- resolveSubqueryStatement db [] stmt
named <- nameDerivedColumns inner
Right (FromSubquery mAlias named)
resolveFrom db outer fromC@(FromJoin kind left right cond) = do
l <- resolveFrom db outer left
(key, _) <- resolveTable db tbl
r <- resolveFrom db outer right
env <- checkFrom db outer (FromJoin kind l r cond)
c <- resolveExpr db env outer (fmap fst . columnAt "ON" env) cond
pure (FromJoin kind l (Just (deriveQualifier alias key)) key c)
pure (FromJoin kind l r c)
-- | 派生表的输出列名:列取裸名,表达式保留原标签
nameDerivedColumns :: Statement -> Either String Statement
nameDerivedColumns q@Select { selectCols = cols, selectFrom = fromC } = do
let names = map lastTablePart cols
checkDerivedNames names
Right
( SelectExpr
[(n, Col c) | (n, c) <- zip names cols]
fromC
(selectWhere q)
(selectGroupBy q)
(selectOrderBy q)
(selectLimit q)
)
nameDerivedColumns q@SelectExpr { selectItems = items } = do
let names = map derivedItemName items
checkDerivedNames names
Right q { selectItems = zip names (map snd items) }
nameDerivedColumns _ = Left "FROM: subquery must be a SELECT"
-- | 派生表里一个投影项的输出名:没写别名的列取裸名
derivedItemName :: (String, Expr) -> String
derivedItemName (label, Col c)
| label == c = lastTablePart c
| otherwise = label
derivedItemName (label, _) = label
-- | 派生表列名不能重复
checkDerivedNames :: [String] -> Either String ()
checkDerivedNames = go []
where
-- | 逐个查有没有见过
go _ [] = Right ()
go seen (n : ns)
| n `elem` seen = Left ("FROM: duplicate column name in subquery: " ++ n)
| otherwise = go (n : seen) ns
@@ -73,10 +73,11 @@ data JoinKind
| LeftJoin
deriving (Show, Eq)
-- | 数据来源:单表、带连接的来源,或空
-- | 数据来源:单表、派生表(子查询,输出列已改成裸列名)、带连接的来源,或空
data FromClause
= FromTable (Maybe String) String
| FromJoin JoinKind FromClause (Maybe String) String Expr
| FromSubquery (Maybe String) Statement
| FromJoin JoinKind FromClause FromClause Expr
| FromUnit
deriving (Show, Eq)
@@ -300,21 +300,34 @@ joinKind =
<|> try (keyword "inner" *> keyword "join" *> return InnerJoin)
<|> (keyword "join" *> return InnerJoin)
-- | FROM 里的一个来源:表或派生表
fromSource :: Parser FromClause
fromSource = fromSubquerySource <|> (uncurry FromTable <$> tableRef)
-- | 派生表:括号里的一条 SELECT,必须有别名
fromSubquerySource :: Parser FromClause
fromSubquerySource = do
stmt <- between (symbol "(") (symbol ")") selectStatement
mAlias <- optional ((keyword "as" *> aliasName) <|> aliasName)
case mAlias of
Nothing -> fail "FROM subquery needs an alias"
Just name -> return (FromSubquery (Just name) stmt)
-- | 一个 JOIN ... ON
joinClause :: Parser (JoinKind, Maybe String, String, Expr)
joinClause :: Parser (JoinKind, FromClause, Expr)
joinClause = do
kind <- joinKind
(mAlias, tbl) <- tableRef
src <- fromSource
keyword "on"
cond <- expr
return (kind, mAlias, tbl, cond)
return (kind, src, cond)
-- | FROM 子句(可含多个 JOIN)
fromClause :: Parser FromClause
fromClause = do
(mAlias, tbl) <- tableRef
src <- fromSource
joins <- many joinClause
return (foldl (\acc (k, a, t, c) -> FromJoin k acc a t c) (FromTable mAlias tbl) joins)
return (foldl (\acc (k, s, c) -> FromJoin k acc s c) src joins)
-- | SELECT 的列清单
selectList :: Parser [(String, Expr)]
@@ -324,11 +337,17 @@ selectList =
, sepBy1 selectItem (symbol ",")
]
-- | 投影标签保留表达式原文
-- | 投影项:列名或表达式,可带 AS 别名
selectItem :: Parser (String, Expr)
selectItem = do
(source, e) <- match expr
pure (case e of Col c -> (c, e); _ -> (reverse (dropWhile isSpace (reverse source)), e))
mAlias <- optional (try (keyword "as" *> aliasName))
pure (fromMaybe (selectItemLabel source e) mAlias, e)
-- | 投影项的默认标签:列用列名,表达式用原文
selectItemLabel :: String -> Expr -> String
selectItemLabel _ (Col c) = c
selectItemLabel source _ = reverse (dropWhile isSpace (reverse source))
-- | 读 CREATE TABLE
createTableStatement :: Parser Statement
+85 -10
View File
@@ -324,8 +324,7 @@ main = hspec $ do
FromJoin
InnerJoin
(FromTable Nothing "users")
Nothing
"orders"
(FromTable Nothing "orders")
(Eq (Col "id") (Col "user_id"))
, selectWhere = Nothing
, selectGroupBy = []
@@ -343,8 +342,7 @@ main = hspec $ do
FromJoin
InnerJoin
(FromTable (Just "u") "users")
(Just "o")
"orders"
(FromTable (Just "o") "orders")
(Eq (Col "u.id") (Col "o.user_id"))
, selectWhere = Nothing
, selectGroupBy = []
@@ -362,8 +360,7 @@ main = hspec $ do
FromJoin
LeftJoin
(FromTable (Just "u") "users")
(Just "o")
"orders"
(FromTable (Just "o") "orders")
(Eq (Col "u.id") (Col "o.user_id"))
, selectWhere = Nothing
, selectGroupBy = []
@@ -378,11 +375,90 @@ main = hspec $ do
( FromJoin
LeftJoin
(FromTable (Just "u") "users")
(Just "o")
"orders"
(FromTable (Just "o") "orders")
(Eq (Col "u.id") (Col "o.user_id"))
)
describe "ChuSQL.Core.Engine (派生表)" $ do
-- sql:在默认库上跑 SQL 取结果行
let sql input = parseStatement input >>= rowsOf . runStatement testDB
-- values:只取结果里的值
values input = fmap (map (map snd)) (sql input)
it "parses a derived table with an alias" $ do
parseStatement "SELECT d.name FROM (SELECT name FROM users) d"
`shouldBe` Right
( Select
{ selectCols = ["d.name"]
, selectFrom =
FromSubquery
(Just "d")
(Select ["name"] (FromTable Nothing "users") Nothing [] [] Nothing)
, selectWhere = Nothing
, selectGroupBy = []
, selectOrderBy = []
, selectLimit = Nothing
}
)
it "requires an alias for a derived table" $ do
parseStatement "SELECT name FROM (SELECT name FROM users)" `shouldSatisfy` isLeft
it "parses a derived table on the right of a JOIN" $ do
fmap selectFrom (parseStatement "SELECT d.name FROM users u JOIN (SELECT name FROM users) d ON u.name = d.name")
`shouldBe` Right
( FromJoin
InnerJoin
(FromTable (Just "u") "users")
(FromSubquery (Just "d") (Select ["name"] (FromTable Nothing "users") Nothing [] [] Nothing))
(Eq (Col "u.name") (Col "d.name"))
)
it "parses a projection alias" $ do
parseStatement "SELECT count(*) AS total FROM orders"
`shouldBe` Right
( SelectExpr
[("total", CountAll)]
(FromTable Nothing "orders")
Nothing
[]
[]
Nothing
)
it "runs a derived table as a source" $ do
values "SELECT d.name FROM (SELECT name FROM users) d"
`shouldBe` Right [[VStr "Alice"], [VStr "Bob"], [VStr "Carol"]]
it "expands the star of a derived table" $ do
sql "SELECT * FROM (SELECT id, name FROM users) d"
`shouldBe` Right
[ [("d.id", VInt 1), ("d.name", VStr "Alice")]
, [("d.id", VInt 2), ("d.name", VStr "Bob")]
, [("d.id", VInt 3), ("d.name", VStr "Carol")]
]
it "joins a derived table with a table" $ do
values "SELECT o.id FROM (SELECT id FROM users WHERE age > 20) d JOIN orders o ON o.user_id = d.id ORDER BY o.id"
`shouldBe` Right [[VInt 1], [VInt 3]]
it "runs an aggregate inside a derived table" $ do
values "SELECT d.total FROM (SELECT user_id, count(*) AS total FROM orders GROUP BY user_id) d ORDER BY d.total"
`shouldBe` Right [[VInt 1], [VInt 2]]
it "runs a derived table inside a derived table" $ do
values "SELECT inner_d.name FROM (SELECT d.name FROM (SELECT name FROM users) d WHERE d.name = 'Bob') inner_d"
`shouldBe` Right [[VStr "Bob"]]
it "keeps a projection alias outside a derived table" $ do
sql "SELECT count(*) AS total FROM orders" `shouldBe` Right [[("total", VInt 3)]]
it "rejects duplicate column names in a derived table" $ do
sql "SELECT d.name FROM (SELECT name, name FROM users) d" `shouldSatisfy` isLeft
it "does not let a derived table see outer columns" $ do
sql "SELECT d.name FROM users u JOIN (SELECT name FROM users WHERE id = u.id) d ON u.name = d.name"
`shouldSatisfy` isLeft
describe "ChuSQL.Core.Engine (SELECT)" $ do
-- sql:在默认库上跑 SQL 取结果行
let sql input = parseStatement input >>= rowsOf . runStatement testDB
@@ -991,8 +1067,7 @@ main = hspec $ do
FromJoin
InnerJoin
(FromTable (Just "u") "users")
(Just "x")
"nonexistent"
(FromTable (Just "x") "nonexistent")
(Eq (Col "u.id") (Col "x.id"))
, selectWhere = Nothing
, selectGroupBy = []