派生表与投影别名
This commit is contained in:
1 parent
a8e85b7ac0
commit
30f1014016
14 files changed
+232
-53
No files matched your search
@@ -28,5 +28,5 @@
|
|||||||
- 改动说明只在交流中记录。
|
- 改动说明只在交流中记录。
|
||||||
|
|
||||||
## 当前重点
|
## 当前重点
|
||||||
- 主线已完成:高级优化器 → 并行查询 → M1 会话隔离 → M2 快照事务 → M3 WAL 与崩溃恢复 → M4 表结构进 WAL → M5 系统表统一命名 → M6 检查点按 LSN 部分截断 → M7 事务写写冲突检测 → M8 事务保存点 → M9 角色继承 → M10 转授权。
|
- 主线已完成:高级优化器 → 并行查询 → M1 会话隔离 → M2 快照事务 → M3 WAL 与崩溃恢复 → M4 表结构进 WAL → M5 系统表统一命名 → M6 检查点按 LSN 部分截断 → M7 事务写写冲突检测 → M8 事务保存点 → M9 角色继承 → M10 转授权 → M11 FROM 派生表与投影别名。
|
||||||
- 进度与计数见 `agent_credientials/project-plan.txt`,限制与风险见 `agent_credientials/known-issues.txt`。
|
- 进度与计数见 `agent_credientials/project-plan.txt`,限制与风险见 `agent_credientials/known-issues.txt`。
|
||||||
@@ -409,6 +409,9 @@ evalRelOpIn db outer (Sort spec op) = do
|
|||||||
evalRelOpIn db outer (Limit n op) = do
|
evalRelOpIn db outer (Limit n op) = do
|
||||||
rows <- evalRelOpIn db outer op
|
rows <- evalRelOpIn db outer op
|
||||||
pure (fmap (take n) rows)
|
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
|
evalRelOpIn db outer (Join kind l r c) = do
|
||||||
lres <- evalRelOpIn db outer l
|
lres <- evalRelOpIn db outer l
|
||||||
case lres of
|
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 (Project cols x) = Project cols (pass db free x)
|
||||||
pass db free (Compute items x) = Compute items (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 (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)
|
pass db free (Join kind l r c)
|
||||||
| free
|
| free
|
||||||
, Just (leaves, conds) <- innerChain (Join kind l r c)
|
, Just (leaves, conds) <- innerChain (Join kind l r c)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ data RelOp
|
|||||||
| Sort [(String, SortDir)] RelOp
|
| Sort [(String, SortDir)] RelOp
|
||||||
| Limit Int RelOp
|
| Limit Int RelOp
|
||||||
| Join JoinKind RelOp RelOp Expr
|
| Join JoinKind RelOp RelOp Expr
|
||||||
|
| Derived (Maybe String) RelOp
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
-- | 算子会产出哪些列
|
-- | 算子会产出哪些列
|
||||||
@@ -39,6 +40,7 @@ relOpCols _ (Project cols _) = cols
|
|||||||
relOpCols db (Sort _ x) = relOpCols db x
|
relOpCols db (Sort _ x) = relOpCols db x
|
||||||
relOpCols db (Limit _ x) = relOpCols db x
|
relOpCols db (Limit _ x) = relOpCols db x
|
||||||
relOpCols db (Join _ l r _) = relOpCols db l ++ relOpCols db r
|
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
|
kindLabel :: JoinKind -> String
|
||||||
@@ -69,5 +71,6 @@ renderPlan = go 0
|
|||||||
Sort s x -> pad ++ "Sort " ++ show s ++ "\n" ++ go (ind + 1) x
|
Sort s x -> pad ++ "Sort " ++ show s ++ "\n" ++ go (ind + 1) x
|
||||||
Limit n x -> pad ++ "Limit " ++ show n ++ "\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
|
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
|
where
|
||||||
pad = replicate (ind * 2) ' '
|
pad = replicate (ind * 2) ' '
|
||||||
@@ -79,6 +79,7 @@ planHasDivision (Compute items x) = any (hasDivision . snd) items || planHasDivi
|
|||||||
planHasDivision (Project _ x) = planHasDivision x
|
planHasDivision (Project _ x) = planHasDivision x
|
||||||
planHasDivision (Sort _ x) = planHasDivision x
|
planHasDivision (Sort _ x) = planHasDivision x
|
||||||
planHasDivision (Limit _ x) = planHasDivision x
|
planHasDivision (Limit _ x) = planHasDivision x
|
||||||
|
planHasDivision (Derived _ x) = planHasDivision x
|
||||||
planHasDivision _ = False
|
planHasDivision _ = False
|
||||||
|
|
||||||
-- | 是否要全部列
|
-- | 是否要全部列
|
||||||
@@ -117,6 +118,7 @@ pushProject db need leaf@(Scan a t _)
|
|||||||
| needsAll need = leaf
|
| needsAll need = leaf
|
||||||
| all (`elem` need) (relOpCols db leaf) = leaf
|
| all (`elem` need) (relOpCols db leaf) = leaf
|
||||||
| otherwise = Scan a t (Just (nub (map (unqualify a) need)))
|
| 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@(Lookup _ _ _ _) = pushProjectLeaf db need leaf
|
||||||
pushProject db need leaf@(Range _ _ _ _ _) = 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 (Sort spec x) = Sort spec (optimizeRelOp db x)
|
||||||
descend (Limit n x) = Limit n (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 (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
|
descend x = x
|
||||||
|
|
||||||
-- | 反复跑到不动点
|
-- | 反复跑到不动点
|
||||||
|
|||||||
@@ -6,18 +6,19 @@ import ChuSQL.Core.Engine.Syntax.AST
|
|||||||
|
|
||||||
-- 计划生成:把语句翻译成算子树。
|
-- 计划生成:把语句翻译成算子树。
|
||||||
|
|
||||||
-- | 把 FROM 子句翻成 Scan / Join
|
-- | FROM 子句翻成关系算子
|
||||||
fromToRelOp :: FromClause -> RelOp
|
fromToRelOp :: FromClause -> Either String RelOp
|
||||||
fromToRelOp FromUnit = Unit
|
fromToRelOp FromUnit = Right Unit
|
||||||
fromToRelOp (FromTable mAlias tbl) = Scan mAlias tbl Nothing
|
fromToRelOp (FromTable mAlias tbl) = Right (Scan mAlias tbl Nothing)
|
||||||
fromToRelOp (FromJoin kind left mAlias tbl cond) =
|
fromToRelOp (FromSubquery mAlias stmt) = Derived mAlias <$> translate stmt
|
||||||
Join kind (fromToRelOp left) (Scan mAlias tbl Nothing) cond
|
fromToRelOp (FromJoin kind left right cond) =
|
||||||
|
Join kind <$> fromToRelOp left <*> fromToRelOp right <*> pure cond
|
||||||
|
|
||||||
-- | 带上 WHERE 的来源
|
-- | 带上 WHERE 的来源
|
||||||
source :: FromClause -> Maybe Expr -> RelOp
|
source :: FromClause -> Maybe Expr -> Either String RelOp
|
||||||
source fromC mWhere = case mWhere of
|
source fromC mWhere = do
|
||||||
Nothing -> fromToRelOp fromC
|
op <- fromToRelOp fromC
|
||||||
Just w -> Filter w (fromToRelOp fromC)
|
Right (maybe op (`Filter` op) mWhere)
|
||||||
|
|
||||||
-- | 给用到的聚合起内部列名,同一个聚合只算一次
|
-- | 给用到的聚合起内部列名,同一个聚合只算一次
|
||||||
nameAggs :: [(String, Expr)] -> [(String, Expr)]
|
nameAggs :: [(String, Expr)] -> [(String, Expr)]
|
||||||
@@ -80,9 +81,11 @@ translate q = case q of
|
|||||||
, selectOrderBy = orderBy
|
, selectOrderBy = orderBy
|
||||||
, selectLimit = mLimit
|
, selectLimit = mLimit
|
||||||
} -> do
|
} -> do
|
||||||
let (plan, _) = grouped groupBy [] (source fromC mWhere)
|
plan0 <- source fromC mWhere
|
||||||
|
let (plan, _) = grouped groupBy [] plan0
|
||||||
Right (Project cols (limited orderBy mLimit plan))
|
Right (Project cols (limited orderBy mLimit plan))
|
||||||
SelectExpr items fromC mWhere groupBy orderBy mLimit -> do
|
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))
|
Right (Compute items' (limited orderBy mLimit plan))
|
||||||
_ -> Left "only SELECT is supported by translate"
|
_ -> Left "only SELECT is supported by translate"
|
||||||
@@ -44,13 +44,21 @@ checkFrom _ _ FromUnit = Right []
|
|||||||
checkFrom db _ (FromTable mAlias tbl) = do
|
checkFrom db _ (FromTable mAlias tbl) = do
|
||||||
(key, t) <- resolveTable db tbl
|
(key, t) <- resolveTable db tbl
|
||||||
Right (prefixColumns (Just (deriveQualifier mAlias key)) (tableCols t))
|
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
|
lcols <- checkFrom db outer left
|
||||||
(key, t) <- resolveTable db tbl
|
rcols <- checkFrom db outer right
|
||||||
let env = lcols ++ prefixColumns (Just (deriveQualifier mAlias key)) (tableCols t)
|
let env = lcols ++ rcols
|
||||||
checkBool db "ON" (Scope env outer) cond
|
checkBool db "ON" (Scope env outer) cond
|
||||||
Right env
|
Right env
|
||||||
|
|
||||||
|
-- | 派生表里 NULL 字面量列没有具体类型,按字符串处理
|
||||||
|
derivedType :: InferredType -> ColumnType
|
||||||
|
derivedType (InferType t) = t
|
||||||
|
derivedType InferNull = CStr
|
||||||
|
|
||||||
-- | 合并两层列,本层优先且不重复
|
-- | 合并两层列,本层优先且不重复
|
||||||
scopedEnv :: [(String, Column)] -> [(String, Column)] -> [(String, Column)]
|
scopedEnv :: [(String, Column)] -> [(String, Column)] -> [(String, Column)]
|
||||||
scopedEnv env outer = env ++ [o | o <- outer, o `notElem` env]
|
scopedEnv env outer = env ++ [o | o <- outer, o `notElem` env]
|
||||||
@@ -638,7 +646,8 @@ statementColumns stmt = case stmt of
|
|||||||
-- | 取 FROM 里出现的列
|
-- | 取 FROM 里出现的列
|
||||||
fromColumns FromUnit = []
|
fromColumns FromUnit = []
|
||||||
fromColumns (FromTable _ _) = []
|
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
|
-- | 取语句的 FROM 子句,没有就是 FromUnit
|
||||||
statementFrom :: Statement -> FromClause
|
statementFrom :: Statement -> FromClause
|
||||||
@@ -692,17 +701,18 @@ resolveSelect _ q = Right q
|
|||||||
-- | 解析投影、条件、排序与连接引用
|
-- | 解析投影、条件、排序与连接引用
|
||||||
resolveQuery :: Database -> [(String, Column)] -> Statement -> [(String, Expr)] -> Either String Statement
|
resolveQuery :: Database -> [(String, Column)] -> Statement -> [(String, Expr)] -> Either String Statement
|
||||||
resolveQuery db outer q items = do
|
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
|
let physical c = case selectFrom q of
|
||||||
FromTable Nothing tbl -> unqualify (Just (deriveQualifier Nothing tbl)) c
|
FromTable Nothing tbl -> unqualify (Just (deriveQualifier Nothing tbl)) c
|
||||||
_ -> c
|
_ -> c
|
||||||
-- | 查一列并换算成物理名
|
-- | 查一列并换算成物理名
|
||||||
name place c = physical . fst <$> scopedColumnAt place env outer c
|
name place c = physical . fst <$> scopedColumnAt place env outer c
|
||||||
-- | 换算一个投影项
|
-- | 换算一个投影项:带别名时保留别名做输出列名
|
||||||
output (_, Col c) = do
|
output (label, Col c) = do
|
||||||
k <- name "SELECT" c
|
k <- name "SELECT" c
|
||||||
pure (k, Col k)
|
pure (if label == c then (k, Col k) else (label, Col k))
|
||||||
output (label, e) = do
|
output (label, e) = do
|
||||||
e' <- resolveExpr db env outer (name "SELECT") e
|
e' <- resolveExpr db env outer (name "SELECT") e
|
||||||
pure (label, e')
|
pure (label, e')
|
||||||
@@ -714,24 +724,68 @@ resolveQuery db outer q items = do
|
|||||||
condition <- mapM (resolveExpr db env outer (name "WHERE")) (selectWhere q)
|
condition <- mapM (resolveExpr db env outer (name "WHERE")) (selectWhere q)
|
||||||
grouping <- mapM (name "GROUP BY") (selectGroupBy q)
|
grouping <- mapM (name "GROUP BY") (selectGroupBy q)
|
||||||
ordering <- mapM (\(c, d) -> (\k -> (k, d)) <$> name "ORDER BY" c) (selectOrderBy 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
|
pure $ case q of
|
||||||
Select{} -> Select (map fst projected) source condition grouping ordering (selectLimit q)
|
Select{} -> Select (map fst projected) source condition grouping ordering (selectLimit q)
|
||||||
_ -> SelectExpr 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 :: Database -> [(String, Column)] -> FromClause -> Either String FromClause
|
||||||
resolveFrom _ _ FromUnit = Right FromUnit
|
resolveFrom _ _ FromUnit = Right FromUnit
|
||||||
resolveFrom db _ (FromTable alias tbl) = do
|
resolveFrom db _ (FromTable alias tbl) = do
|
||||||
(key, _) <- resolveTable db tbl
|
(key, _) <- resolveTable db tbl
|
||||||
Right (FromTable (Just (deriveQualifier alias key)) key)
|
Right (FromTable (Just (deriveQualifier alias key)) key)
|
||||||
resolveFrom db outer fromC@(FromJoin kind left alias tbl cond) = do
|
resolveFrom db _ (FromSubquery mAlias stmt) = do
|
||||||
env <- checkFrom db outer fromC
|
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
|
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
|
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
|
| LeftJoin
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
-- | 数据来源:单表、带连接的来源,或空
|
-- | 数据来源:单表、派生表(子查询,输出列已改成裸列名)、带连接的来源,或空
|
||||||
data FromClause
|
data FromClause
|
||||||
= FromTable (Maybe String) String
|
= FromTable (Maybe String) String
|
||||||
| FromJoin JoinKind FromClause (Maybe String) String Expr
|
| FromSubquery (Maybe String) Statement
|
||||||
|
| FromJoin JoinKind FromClause FromClause Expr
|
||||||
| FromUnit
|
| FromUnit
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
|||||||
@@ -300,21 +300,34 @@ joinKind =
|
|||||||
<|> try (keyword "inner" *> keyword "join" *> return InnerJoin)
|
<|> try (keyword "inner" *> keyword "join" *> return InnerJoin)
|
||||||
<|> (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
|
-- | 一个 JOIN ... ON
|
||||||
joinClause :: Parser (JoinKind, Maybe String, String, Expr)
|
joinClause :: Parser (JoinKind, FromClause, Expr)
|
||||||
joinClause = do
|
joinClause = do
|
||||||
kind <- joinKind
|
kind <- joinKind
|
||||||
(mAlias, tbl) <- tableRef
|
src <- fromSource
|
||||||
keyword "on"
|
keyword "on"
|
||||||
cond <- expr
|
cond <- expr
|
||||||
return (kind, mAlias, tbl, cond)
|
return (kind, src, cond)
|
||||||
|
|
||||||
-- | FROM 子句(可含多个 JOIN)
|
-- | FROM 子句(可含多个 JOIN)
|
||||||
fromClause :: Parser FromClause
|
fromClause :: Parser FromClause
|
||||||
fromClause = do
|
fromClause = do
|
||||||
(mAlias, tbl) <- tableRef
|
src <- fromSource
|
||||||
joins <- many joinClause
|
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 的列清单
|
-- | SELECT 的列清单
|
||||||
selectList :: Parser [(String, Expr)]
|
selectList :: Parser [(String, Expr)]
|
||||||
@@ -324,11 +337,17 @@ selectList =
|
|||||||
, sepBy1 selectItem (symbol ",")
|
, sepBy1 selectItem (symbol ",")
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | 投影标签保留表达式原文
|
-- | 投影项:列名或表达式,可带 AS 别名
|
||||||
selectItem :: Parser (String, Expr)
|
selectItem :: Parser (String, Expr)
|
||||||
selectItem = do
|
selectItem = do
|
||||||
(source, e) <- match expr
|
(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
|
-- | 读 CREATE TABLE
|
||||||
createTableStatement :: Parser Statement
|
createTableStatement :: Parser Statement
|
||||||
|
|||||||
@@ -324,8 +324,7 @@ main = hspec $ do
|
|||||||
FromJoin
|
FromJoin
|
||||||
InnerJoin
|
InnerJoin
|
||||||
(FromTable Nothing "users")
|
(FromTable Nothing "users")
|
||||||
Nothing
|
(FromTable Nothing "orders")
|
||||||
"orders"
|
|
||||||
(Eq (Col "id") (Col "user_id"))
|
(Eq (Col "id") (Col "user_id"))
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectGroupBy = []
|
, selectGroupBy = []
|
||||||
@@ -343,8 +342,7 @@ main = hspec $ do
|
|||||||
FromJoin
|
FromJoin
|
||||||
InnerJoin
|
InnerJoin
|
||||||
(FromTable (Just "u") "users")
|
(FromTable (Just "u") "users")
|
||||||
(Just "o")
|
(FromTable (Just "o") "orders")
|
||||||
"orders"
|
|
||||||
(Eq (Col "u.id") (Col "o.user_id"))
|
(Eq (Col "u.id") (Col "o.user_id"))
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectGroupBy = []
|
, selectGroupBy = []
|
||||||
@@ -362,8 +360,7 @@ main = hspec $ do
|
|||||||
FromJoin
|
FromJoin
|
||||||
LeftJoin
|
LeftJoin
|
||||||
(FromTable (Just "u") "users")
|
(FromTable (Just "u") "users")
|
||||||
(Just "o")
|
(FromTable (Just "o") "orders")
|
||||||
"orders"
|
|
||||||
(Eq (Col "u.id") (Col "o.user_id"))
|
(Eq (Col "u.id") (Col "o.user_id"))
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectGroupBy = []
|
, selectGroupBy = []
|
||||||
@@ -378,11 +375,90 @@ main = hspec $ do
|
|||||||
( FromJoin
|
( FromJoin
|
||||||
LeftJoin
|
LeftJoin
|
||||||
(FromTable (Just "u") "users")
|
(FromTable (Just "u") "users")
|
||||||
(Just "o")
|
(FromTable (Just "o") "orders")
|
||||||
"orders"
|
|
||||||
(Eq (Col "u.id") (Col "o.user_id"))
|
(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
|
describe "ChuSQL.Core.Engine (SELECT)" $ do
|
||||||
-- sql:在默认库上跑 SQL 取结果行
|
-- sql:在默认库上跑 SQL 取结果行
|
||||||
let sql input = parseStatement input >>= rowsOf . runStatement testDB
|
let sql input = parseStatement input >>= rowsOf . runStatement testDB
|
||||||
@@ -991,8 +1067,7 @@ main = hspec $ do
|
|||||||
FromJoin
|
FromJoin
|
||||||
InnerJoin
|
InnerJoin
|
||||||
(FromTable (Just "u") "users")
|
(FromTable (Just "u") "users")
|
||||||
(Just "x")
|
(FromTable (Just "x") "nonexistent")
|
||||||
"nonexistent"
|
|
||||||
(Eq (Col "u.id") (Col "x.id"))
|
(Eq (Col "u.id") (Col "x.id"))
|
||||||
, selectWhere = Nothing
|
, selectWhere = Nothing
|
||||||
, selectGroupBy = []
|
, selectGroupBy = []
|
||||||
|
|||||||
@@ -283,7 +283,8 @@ statementNeedsDatabase stmt = any (not . T.any (== '.')) (statementTables stmt)
|
|||||||
tableRefsOf :: FromClause -> [Text]
|
tableRefsOf :: FromClause -> [Text]
|
||||||
tableRefsOf FromUnit = []
|
tableRefsOf FromUnit = []
|
||||||
tableRefsOf (FromTable _ name) = [T.pack name]
|
tableRefsOf (FromTable _ name) = [T.pack name]
|
||||||
tableRefsOf (FromJoin _ left _ name cond) = tableRefsOf left ++ [T.pack name] ++ exprTables cond
|
tableRefsOf (FromSubquery _ stmt) = statementTables stmt
|
||||||
|
tableRefsOf (FromJoin _ left right cond) = tableRefsOf left ++ tableRefsOf right ++ exprTables cond
|
||||||
|
|
||||||
-- | 表达式里出现的表(子查询是唯一的来路)
|
-- | 表达式里出现的表(子查询是唯一的来路)
|
||||||
exprTables :: Expr -> [Text]
|
exprTables :: Expr -> [Text]
|
||||||
|
|||||||
@@ -643,6 +643,15 @@ privilegeSpec = describe "server privileges" $ do
|
|||||||
Left err -> expectationFailure err
|
Left err -> expectationFailure err
|
||||||
Right stmt -> authorize service root testDatabase stmt `shouldReturn` Right ()
|
Right stmt -> authorize service root testDatabase stmt `shouldReturn` Right ()
|
||||||
|
|
||||||
|
it "requires the privilege on the table inside a derived table" $ withPrivileges $ \service root -> do
|
||||||
|
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||||
|
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" False)
|
||||||
|
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "reader" ["alice"])
|
||||||
|
authorizeSql service (Ordinary testAccount) "SELECT d.name FROM (SELECT name FROM users) d"
|
||||||
|
`shouldReturn` Right ()
|
||||||
|
authorizeSql service (Ordinary testAccount) "SELECT d.name FROM (SELECT product FROM orders) d"
|
||||||
|
`shouldReturn` Left (PrivilegeError "forbidden" "permission denied: SELECT ON orders")
|
||||||
|
|
||||||
it "keeps the role tables on the real storage" $ withIpcStorage $ do
|
it "keeps the role tables on the real storage" $ withIpcStorage $ do
|
||||||
base <- ipcBackend
|
base <- ipcBackend
|
||||||
service <- newPrivileges base
|
service <- newPrivileges base
|
||||||
|
|||||||
@@ -47,12 +47,12 @@ chusql-server
|
|||||||
|
|
||||||
每条连接拥有独立会话状态,例如当前数据库选择不会影响其他连接。不同连接可以同时执行语句:读请求之间可以并行,写请求之间由存储层排队。
|
每条连接拥有独立会话状态,例如当前数据库选择不会影响其他连接。不同连接可以同时执行语句:读请求之间可以并行,写请求之间由存储层排队。
|
||||||
|
|
||||||
语句里的库前缀和表限定符由引擎在语义层解析:存储层只接受精确的表名键,因此 `FROM sales.orders` 与选定 `sales` 后的 `FROM orders` 指向同一张表。
|
语句里的库前缀和表限定符由引擎在语义层解析:存储层只接受精确的表名键,因此 `FROM sales.orders` 与选定 `sales` 后的 `FROM orders` 指向同一张表。FROM 的来源是一棵树:单表、派生表(括号里的子查询,必须带别名)与连接共用同一个 `FromClause`,所以 `JOIN` 两侧都能是派生表。解析派生表时先把内层语句在空作用域里解析成一张“表”——内层不能引用外层列,输出列统一改成裸列名(内层 `u.id` 在外层用 `d.id` 引用),重名直接报错;执行侧对应一个 `Derived` 算子,把内层计划的结果按别名加前缀。派生表不参与连接顺序重排(叶子的行数未知),投影也不下推进去,但内层计划照常做谓词下推与索引改写。投影项的 `AS` 别名与 `SELECT` 的输出列名一起在语义层解析,派生表与最外层查询都按别名暴露列名。
|
||||||
|
|
||||||
显式事务在会话层实现:`BEGIN` 把当前数据库整库读成内存快照,事务内的语句都在快照上执行;`SAVEPOINT` 把当前快照压进会话里的保存点栈,`ROLLBACK TO` 退回该点并保留它,`RELEASE` 丢掉该点与其后的保存点;`COMMIT` 先把快照、基线与库里现值按行 `id` 逐键比对,检测到写写冲突就报 `serialization_failure` 并保留事务;没有冲突才把差分用一条 `apply_transaction` 请求交给存储层应用;`ROLLBACK` 直接丢弃快照。因此隔离级别是快照读加原子提交:事务里看不到别的事务未提交或后来提交的改动,提交对外一次性可见,冲突由调用方回滚或重试。
|
显式事务在会话层实现:`BEGIN` 把当前数据库整库读成内存快照,事务内的语句都在快照上执行;`SAVEPOINT` 把当前快照压进会话里的保存点栈,`ROLLBACK TO` 退回该点并保留它,`RELEASE` 丢掉该点与其后的保存点;`COMMIT` 先把快照、基线与库里现值按行 `id` 逐键比对,检测到写写冲突就报 `serialization_failure` 并保留事务;没有冲突才把差分用一条 `apply_transaction` 请求交给存储层应用;`ROLLBACK` 直接丢弃快照。因此隔离级别是快照读加原子提交:事务里看不到别的事务未提交或后来提交的改动,提交对外一次性可见,冲突由调用方回滚或重试。
|
||||||
|
|
||||||
WAL 负责崩溃恢复:每条记录带自增 LSN,一组写操作与一条提交标记一次落盘;每组提交在数据和字典都刷盘之后把 `wal.checkpoint` 推进到这条提交的 LSN,含义是「到此为止的改动都已落盘」,再按这个边界丢掉日志前缀——所以日志里通常只有当组在飞的记录。进程重启时只重放检查点之后的记录,按标记分组取已提交的组、丢弃没有标记的整组,重放前逐条做幂等检查作为第二道保险,结束后再把检查点推到最后一条提交。LSN 从检查点与文件尾里较大的那个续号,跨重启单调,`SystemStatus` 应答里的 `last_lsn` 报告该检查点 LSN。建表、删表、建索引、删索引与数据改动走同一条 WAL,恢复时一并重放。
|
WAL 负责崩溃恢复:每条记录带自增 LSN,一组写操作与一条提交标记一次落盘;每组提交在数据和字典都刷盘之后把 `wal.checkpoint` 推进到这条提交的 LSN,含义是「到此为止的改动都已落盘」,再按这个边界丢掉日志前缀——所以日志里通常只有当组在飞的记录。进程重启时只重放检查点之后的记录,按标记分组取已提交的组、丢弃没有标记的整组,重放前逐条做幂等检查作为第二道保险,结束后再把检查点推到最后一条提交。LSN 从检查点与文件尾里较大的那个续号,跨重启单调,`SystemStatus` 应答里的 `last_lsn` 报告该检查点 LSN。建表、删表、建索引、删索引与数据改动走同一条 WAL,恢复时一并重放。
|
||||||
|
|
||||||
系统数据库及部分管理操作仅允许管理员访问。系统表统一用 `__system_` 前缀:账号表 `__system_users` 对语句通道隐藏,角色、授权、成员三张表(`__system_roles` / `__system_grants` / `__system_members`)只在 `system` 库里可见,权限服务自身也通过 SQL 读写它们;存储启动时把旧名 `sys_roles` / `sys_grants` / `sys_members` 连同数据与索引文件一起迁移过来。角色支持互相继承:`GRANT 角色 A TO 角色 B` 记一条 B 属于 A 的成员关系,判定权限时沿成员关系算出向上闭包再取并集;写入时拒绝成环与自继承,删角色会清掉它两个方向的成员关系。带 `WITH GRANT OPTION` 的授权记在第四张表 `__system_grant_options` 里(同样的角色、权限、对象三列,单独的列存不下就单独一张表,省掉系统表的结构迁移):普通身份只要手里那条授权带 grant option 就能把同一项权限转授给别的角色,`REVOKE` 仍然只允许管理员,收回授权时连它的 grant option 一起收掉,但不级联收回别人已经转授出去的授权。授权命令由权限服务在会话层之外直接执行,`GRANT` 命令会先按当前库把对象补全成 `库.表` 再落盘,因此判断转授权资格与实际写入用的是同一个对象名。
|
系统数据库及部分管理操作仅允许管理员访问。系统表统一用 `__system_` 前缀:账号表 `__system_users` 对语句通道隐藏,角色、授权、带 option 的授权、成员四张表(`__system_roles` / `__system_grants` / `__system_grant_options` / `__system_members`)只在 `system` 库里可见,权限服务自身也通过 SQL 读写它们;存储启动时把旧名 `sys_roles` / `sys_grants` / `sys_members` 连同数据与索引文件一起迁移过来。角色支持互相继承:`GRANT 角色 A TO 角色 B` 记一条 B 属于 A 的成员关系,判定权限时沿成员关系算出向上闭包再取并集;写入时拒绝成环与自继承,删角色会清掉它两个方向的成员关系。带 `WITH GRANT OPTION` 的授权记在第四张表 `__system_grant_options` 里(同样的角色、权限、对象三列,另开一张表省掉给 `__system_grants` 加列带来的系统表结构迁移):普通身份只要手里那条授权带 grant option 就能把同一项权限转授给别的角色,`REVOKE` 仍然只允许管理员,收回授权时连它的 grant option 一起收掉,但不级联收回别人已经转授出去的授权。授权命令由权限服务在会话层之外直接执行,`GRANT` 命令会先按当前库把对象补全成 `库.表` 再落盘,因此判断转授权资格与实际写入用的是同一个对象名。
|
||||||
|
|
||||||
服务监听地址、端口和请求限制可在 `chusql.toml` 的 `[server]` 分区中配置。
|
服务监听地址、端口和请求限制可在 `chusql.toml` 的 `[server]` 分区中配置。
|
||||||
@@ -45,6 +45,13 @@ csql -f json -e "SELECT count(*) FROM orders"
|
|||||||
- 只有候选唯一时才自动推导前缀;同名的表或列出现在多个候选上会以 `ambiguous table` / `ambiguous column` 报错,需要写全。
|
- 只有候选唯一时才自动推导前缀;同名的表或列出现在多个候选上会以 `ambiguous table` / `ambiguous column` 报错,需要写全。
|
||||||
- `FROM orders AS o` 之后只能用 `o.id`,不能再写 `orders.id`。
|
- `FROM orders AS o` 之后只能用 `o.id`,不能再写 `orders.id`。
|
||||||
|
|
||||||
|
查询写法:
|
||||||
|
|
||||||
|
- `SELECT` 支持 `WHERE`、`ORDER BY`、`LIMIT`、`JOIN` / `LEFT JOIN`、`GROUP BY` 与聚合,以及标量 / `IN` / `EXISTS` 子查询。
|
||||||
|
- 来源可以是派生表:`SELECT d.name FROM (SELECT name FROM users) d`。派生表必须带别名,内层不能引用外层列;输出列取裸列名(内层写 `u.id`,外层用 `d.id` 引用)。
|
||||||
|
- 投影列可以起别名:`SELECT count(*) AS total FROM orders`,别名就是结果集的列名。`ORDER BY` 仍按来源列解析,要引用派生表的列得写限定名(如 `ORDER BY d.total`)。
|
||||||
|
- 鉴权覆盖整棵来源树:查派生表时内层表也要有权限,只有外层引用的表有权限不够。
|
||||||
|
|
||||||
事务:
|
事务:
|
||||||
|
|
||||||
- `BEGIN`(或 `START TRANSACTION`)开始显式事务,`COMMIT` 提交,`ROLLBACK` 回滚。
|
- `BEGIN`(或 `START TRANSACTION`)开始显式事务,`COMMIT` 提交,`ROLLBACK` 回滚。
|
||||||
|
|||||||
Reference in new issue
Block a user