派生表与投影别名
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`。
|
||||
@@ -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
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -283,7 +283,8 @@ statementNeedsDatabase stmt = any (not . T.any (== '.')) (statementTables stmt)
|
||||
tableRefsOf :: FromClause -> [Text]
|
||||
tableRefsOf FromUnit = []
|
||||
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]
|
||||
|
||||
@@ -643,6 +643,15 @@ privilegeSpec = describe "server privileges" $ do
|
||||
Left err -> expectationFailure err
|
||||
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
|
||||
base <- ipcBackend
|
||||
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` 直接丢弃快照。因此隔离级别是快照读加原子提交:事务里看不到别的事务未提交或后来提交的改动,提交对外一次性可见,冲突由调用方回滚或重试。
|
||||
|
||||
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]` 分区中配置。
|
||||
@@ -45,6 +45,13 @@ csql -f json -e "SELECT count(*) FROM orders"
|
||||
- 只有候选唯一时才自动推导前缀;同名的表或列出现在多个候选上会以 `ambiguous table` / `ambiguous column` 报错,需要写全。
|
||||
- `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` 回滚。
|
||||
|
||||
Reference in new issue
Block a user