派生表与投影别名

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

+1 -1
View File
@@ -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
+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 = []
+2 -1
View File
@@ -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]
+9
View File
@@ -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
+2 -2
View File
@@ -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]` 分区中配置。
+7
View File
@@ -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` 回滚。