量词比较

This commit is contained in:
LIm1tZz7 committed 2026-10-03 20:52:02 +08:00
1 parent 9b1c835202
commit 7d96933e32
15 files changed
+185 -15

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 转授权 → M11 FROM 派生表与投影别名 → M12 `WITH` / CTE → M13 比较运算符补全。 - 主线已完成:高级优化器 → 并行查询 → M1 会话隔离 → M2 快照事务 → M3 WAL 与崩溃恢复 → M4 表结构进 WAL → M5 系统表统一命名 → M6 检查点按 LSN 部分截断 → M7 事务写写冲突检测 → M8 事务保存点 → M9 角色继承 → M10 转授权 → M11 FROM 派生表与投影别名 → M12 `WITH` / CTE → M13 比较运算符补全 → M14 ANY / ALL 量词比较。
- 进度与计数见 `agent_credientials/project-plan.txt`,限制与风险见 `agent_credientials/known-issues.txt`。 - 进度与计数见 `agent_credientials/project-plan.txt`,限制与风险见 `agent_credientials/known-issues.txt`。
@@ -1,7 +1,7 @@
module ChuSQL.Core.Engine.Algebra.Eval (evalRelOp, evalRelOpM, evalExprM, evalCondForRowM) where module ChuSQL.Core.Engine.Algebra.Eval (evalRelOp, evalRelOpM, evalExprM, evalCondForRowM) where
import ChuSQL.Core.Engine.Algebra.Cost (tableMetaOf) import ChuSQL.Core.Engine.Algebra.Cost (tableMetaOf)
import ChuSQL.Core.Engine.Algebra.Expr (evalCondForRow, evalExpr) import ChuSQL.Core.Engine.Algebra.Expr (evalCondForRow, evalExpr, quantifiedCompare)
import ChuSQL.Core.Engine.Algebra.Optimize (optimize) import ChuSQL.Core.Engine.Algebra.Optimize (optimize)
import ChuSQL.Core.Engine.Algebra.Op import ChuSQL.Core.Engine.Algebra.Op
import ChuSQL.Core.Engine.Algebra.Planner (translate) import ChuSQL.Core.Engine.Algebra.Planner (translate)
@@ -312,6 +312,14 @@ substSubqueries db e env = case e of
ExistsSub sq negated -> do ExistsSub sq negated -> do
rows <- runSubqueryRows db sq env rows <- runSubqueryRows db sq env
pure (fmap (\rs -> LitBool (if negated then null rs else not (null rs))) rows) pure (fmap (\rs -> LitBool (if negated then null rs else not (null rs))) rows)
QuantCmp op a sq q -> do
lhs <- substSubqueries db a env
rows <- runSubqueryRows db sq env
pure (do
a' <- lhs
rs <- rows
vs <- mapM rowValue rs
Right (quantifiedCompare op a' (map valueLiteral vs) q))
InList a es negated -> do InList a es negated -> do
lhs <- substSubqueries db a env lhs <- substSubqueries db a env
es' <- mapM (\x -> substSubqueries db x env) es es' <- mapM (\x -> substSubqueries db x env) es
@@ -1,8 +1,8 @@
module ChuSQL.Core.Engine.Algebra.Expr (evalExpr, evalCondForRow, colsInExpr, aggregatesIn, hasAggregate, bareColumns, hasSubquery, hasDivision, inValues, threeValuedNot) where module ChuSQL.Core.Engine.Algebra.Expr (evalExpr, evalCondForRow, colsInExpr, aggregatesIn, hasAggregate, bareColumns, hasSubquery, hasDivision, inValues, threeValuedNot, quantifiedCompare) where
import ChuSQL.Core.Model import ChuSQL.Core.Model
import ChuSQL.Core.Engine.Syntax.AST import ChuSQL.Core.Engine.Syntax.AST
import ChuSQL.Core.Engine.Builtin (Operator (..), executeOperator, inValues, threeValuedNot) import ChuSQL.Core.Engine.Builtin (Operator (..), compareNode, executeOperator, inValues, threeValuedNot)
-- 表达式求值:三值逻辑,NULL 参与运算结果仍是 NULL。 -- 表达式求值:三值逻辑,NULL 参与运算结果仍是 NULL。
@@ -47,12 +47,19 @@ evalExpr (InList a es negated) row = do
evalExpr (ScalarSub _) _ = Left subqueryNeedsDatabase evalExpr (ScalarSub _) _ = Left subqueryNeedsDatabase
evalExpr (InSub _ _ _) _ = Left subqueryNeedsDatabase evalExpr (InSub _ _ _) _ = Left subqueryNeedsDatabase
evalExpr (ExistsSub _ _) _ = Left subqueryNeedsDatabase evalExpr (ExistsSub _ _) _ = Left subqueryNeedsDatabase
evalExpr (QuantCmp _ _ _ _) _ = Left subqueryNeedsDatabase
evalExpr agg _ = Left ("aggregate functions are not allowed here: " ++ show agg) evalExpr agg _ = Left ("aggregate functions are not allowed here: " ++ show agg)
-- | 子查询要连上存储才能跑 -- | 子查询要连上存储才能跑
subqueryNeedsDatabase :: String subqueryNeedsDatabase :: String
subqueryNeedsDatabase = "subquery needs a database to run" subqueryNeedsDatabase = "subquery needs a database to run"
-- | 量词比较摊成 AND / OR 链
quantifiedCompare :: CompareOp -> Expr -> [Expr] -> Quantifier -> Expr
quantifiedCompare op lhs vs q = case vs of
[] -> LitBool (q == AllQ)
_ -> foldr1 (if q == AllQ then And else Or) (map (compareNode op lhs) vs)
-- | 表达式用到了哪些列 -- | 表达式用到了哪些列
colsInExpr :: Expr -> [String] colsInExpr :: Expr -> [String]
colsInExpr (Col c) = [c] colsInExpr (Col c) = [c]
@@ -80,6 +87,7 @@ colsInExpr (ScalarSub sq) = subqueryRefs sq
colsInExpr (InSub a sq _) = colsInExpr a ++ subqueryRefs sq colsInExpr (InSub a sq _) = colsInExpr a ++ subqueryRefs sq
colsInExpr (InList a es _) = colsInExpr a ++ concatMap colsInExpr es colsInExpr (InList a es _) = colsInExpr a ++ concatMap colsInExpr es
colsInExpr (ExistsSub sq _) = subqueryRefs sq colsInExpr (ExistsSub sq _) = subqueryRefs sq
colsInExpr (QuantCmp _ a sq _) = colsInExpr a ++ subqueryRefs sq
colsInExpr _ = [] colsInExpr _ = []
-- | 表达式里有没有除法(除法可能报错,不能提前或延后求值) -- | 表达式里有没有除法(除法可能报错,不能提前或延后求值)
@@ -98,6 +106,7 @@ hasDivision e = case e of
NotEq a b -> both a b NotEq a b -> both a b
And a b -> both a b And a b -> both a b
Or a b -> both a b Or a b -> both a b
QuantCmp _ a _ _ -> hasDivision a
_ -> False _ -> False
where where
-- | 两个子表达式里任意一个有除法 -- | 两个子表达式里任意一个有除法
@@ -110,6 +119,7 @@ hasSubquery e = case e of
InSub _ _ _ -> True InSub _ _ _ -> True
InList a es _ -> any hasSubquery (a : es) InList a es _ -> any hasSubquery (a : es)
ExistsSub _ _ -> True ExistsSub _ _ -> True
QuantCmp _ _ _ _ -> True
Add a b -> both a b Add a b -> both a b
Sub a b -> both a b Sub a b -> both a b
Mul a b -> both a b Mul a b -> both a b
@@ -144,6 +154,7 @@ aggregatesIn e = case e of
a@(AvgOf _) -> [a] a@(AvgOf _) -> [a]
a@(MinOf _) -> [a] a@(MinOf _) -> [a]
a@(MaxOf _) -> [a] a@(MaxOf _) -> [a]
QuantCmp _ a _ _ -> aggregatesIn a
Add a b -> both a b Add a b -> both a b
Sub a b -> both a b Sub a b -> both a b
Mul a b -> both a b Mul a b -> both a b
@@ -191,6 +202,7 @@ bareColumns e = case e of
InSub a sq _ -> bareColumns a ++ subqueryRefs sq InSub a sq _ -> bareColumns a ++ subqueryRefs sq
InList a es _ -> bareColumns a ++ concatMap bareColumns es InList a es _ -> bareColumns a ++ concatMap bareColumns es
ExistsSub sq _ -> subqueryRefs sq ExistsSub sq _ -> subqueryRefs sq
QuantCmp _ a sq _ -> bareColumns a ++ subqueryRefs sq
_ -> [] _ -> []
where where
-- | 两个子表达式的裸列拼起来 -- | 两个子表达式的裸列拼起来
@@ -65,6 +65,7 @@ scaleRows db p x rows = case p of
GtE _ _ -> quarter GtE _ _ -> quarter
LtE _ _ -> quarter LtE _ _ -> quarter
NotEq _ _ -> quarter NotEq _ _ -> quarter
QuantCmp _ _ _ _ -> quarter
And a b -> max 1 (min (scaleRows db a x rows) (scaleRows db b x rows)) And a b -> max 1 (min (scaleRows db a x rows) (scaleRows db b x rows))
_ -> quarter _ -> quarter
where where
@@ -147,6 +147,7 @@ foldConstants (Eq a b) = foldNode (Eq (foldConstants a) (foldConstants b))
foldConstants (GtE a b) = foldNode (GtE (foldConstants a) (foldConstants b)) foldConstants (GtE a b) = foldNode (GtE (foldConstants a) (foldConstants b))
foldConstants (LtE a b) = foldNode (LtE (foldConstants a) (foldConstants b)) foldConstants (LtE a b) = foldNode (LtE (foldConstants a) (foldConstants b))
foldConstants (NotEq a b) = foldNode (NotEq (foldConstants a) (foldConstants b)) foldConstants (NotEq a b) = foldNode (NotEq (foldConstants a) (foldConstants b))
foldConstants (QuantCmp op a sq q) = QuantCmp op (foldConstants a) sq q
foldConstants (And a b) = foldNode (And (foldConstants a) (foldConstants b)) foldConstants (And a b) = foldNode (And (foldConstants a) (foldConstants b))
foldConstants (Or a b) = foldNode (Or (foldConstants a) (foldConstants b)) foldConstants (Or a b) = foldNode (Or (foldConstants a) (foldConstants b))
foldConstants e = e foldConstants e = e
@@ -47,6 +47,7 @@ rewriteAggs aggs e = case [label | (label, agg) <- aggs, agg == e] of
GtE a b -> GtE (go a) (go b) GtE a b -> GtE (go a) (go b)
LtE a b -> LtE (go a) (go b) LtE a b -> LtE (go a) (go b)
NotEq a b -> NotEq (go a) (go b) NotEq a b -> NotEq (go a) (go b)
QuantCmp op a sq q -> QuantCmp op (go a) sq q
And a b -> And (go a) (go b) And a b -> And (go a) (go b)
Or a b -> Or (go a) (go b) Or a b -> Or (go a) (go b)
IsNull a -> IsNull (go a) IsNull a -> IsNull (go a)
@@ -11,13 +11,15 @@ module ChuSQL.Core.Engine.Builtin
, Operator (..) , Operator (..)
, operatorOfExpr , operatorOfExpr
, operatorSymbol , operatorSymbol
, operatorOfCompare
, compareNode
, executeOperator , executeOperator
, threeValuedNot , threeValuedNot
, inValues , inValues
) where ) where
import ChuSQL.Core.Model (Value (..), compareValue) import ChuSQL.Core.Model (Value (..), compareValue)
import ChuSQL.Core.Engine.Syntax.AST (Expr (..)) import ChuSQL.Core.Engine.Syntax.AST (CompareOp (..), Expr (..))
import Control.Monad (foldM) import Control.Monad (foldM)
import Data.Char (toLower) import Data.Char (toLower)
@@ -177,6 +179,24 @@ operatorOfExpr (And _ _) = Just OpAnd
operatorOfExpr (Or _ _) = Just OpOr operatorOfExpr (Or _ _) = Just OpOr
operatorOfExpr _ = Nothing operatorOfExpr _ = Nothing
-- | 量词比较里的比较运算符
operatorOfCompare :: CompareOp -> Operator
operatorOfCompare CmpEq = OpEq
operatorOfCompare CmpNe = OpNe
operatorOfCompare CmpGt = OpGt
operatorOfCompare CmpGtE = OpGtE
operatorOfCompare CmpLt = OpLt
operatorOfCompare CmpLtE = OpLtE
-- | 量词比较在字面量上的节点
compareNode :: CompareOp -> Expr -> Expr -> Expr
compareNode CmpEq = Eq
compareNode CmpNe = NotEq
compareNode CmpGt = Gt
compareNode CmpGtE = GtE
compareNode CmpLt = Lt
compareNode CmpLtE = LtE
-- | 运算符求值:算错个数是内部错误 -- | 运算符求值:算错个数是内部错误
executeOperator :: Operator -> [Value] -> Either String Value executeOperator :: Operator -> [Value] -> Either String Value
executeOperator op vs = case (op, vs) of executeOperator op vs = case (op, vs) of
@@ -177,6 +177,19 @@ inferExpr db place env (InList a es _) = do
mapM_ (itemType place ta) ts mapM_ (itemType place ta) ts
Right (InferType CBool) Right (InferType CBool)
inferExpr _ _ _ (ExistsSub _ _) = Right (InferType CBool) inferExpr _ _ _ (ExistsSub _ _) = Right (InferType CBool)
inferExpr db place env (QuantCmp _ a sq q) = do
ta <- inferExpr db place env a
cols <- inferSubqueryOutput db env sq
case cols of
[(_, t)]
| compatible ta t -> Right (InferType CBool)
| otherwise -> Left (place ++ ": " ++ quantLabel q ++ " subquery column has type " ++ renderType t ++ ", expected " ++ renderType ta)
_ -> Left (place ++ ": " ++ quantLabel q ++ " subquery must return exactly one column")
-- | 量词在错误文本里的名字
quantLabel :: Quantifier -> String
quantLabel AnyQ = "ANY"
quantLabel AllQ = "ALL"
-- | IN 列表里的每一项都要跟左边的类型对得上 -- | IN 列表里的每一项都要跟左边的类型对得上
itemType :: String -> InferredType -> InferredType -> Either String () itemType :: String -> InferredType -> InferredType -> Either String ()
@@ -617,6 +630,7 @@ resolveExpr db env outer f e = case e of
InSub a sq negated -> InSub <$> resolveExpr db env outer f a <*> resolveSubquery db (env ++ outer) sq <*> pure negated InSub a sq negated -> InSub <$> resolveExpr db env outer f a <*> resolveSubquery db (env ++ outer) sq <*> pure negated
InList a es negated -> InList <$> resolveExpr db env outer f a <*> mapM (resolveExpr db env outer f) es <*> pure negated InList a es negated -> InList <$> resolveExpr db env outer f a <*> mapM (resolveExpr db env outer f) es <*> pure negated
ExistsSub sq negated -> ExistsSub <$> resolveSubquery db (env ++ outer) sq <*> pure negated ExistsSub sq negated -> ExistsSub <$> resolveSubquery db (env ++ outer) sq <*> pure negated
QuantCmp op a sq q -> QuantCmp op <$> resolveExpr db env outer f a <*> resolveSubquery db (env ++ outer) sq <*> pure q
_ -> Right e _ -> Right e
where where
-- | 递归处理两个子表达式 -- | 递归处理两个子表达式
@@ -1,4 +1,4 @@
module ChuSQL.Core.Engine.Syntax.AST (Statement (..), FromClause (..), JoinKind (..), Expr (..), Subquery (..), SortDir (..), makeSelect) where module ChuSQL.Core.Engine.Syntax.AST (Statement (..), FromClause (..), JoinKind (..), Expr (..), Subquery (..), Quantifier (..), CompareOp (..), SortDir (..), makeSelect) where
import ChuSQL.Core.Model (Column (..), ColumnType, Value (..)) import ChuSQL.Core.Model (Column (..), ColumnType, Value (..))
@@ -88,6 +88,22 @@ data Subquery = Subquery
} }
deriving (Show, Eq) deriving (Show, Eq)
-- | 量词:ANY 至少一行成立,ALL 每一行都成立
data Quantifier
= AnyQ
| AllQ
deriving (Show, Eq)
-- | 量词比较里的比较运算符
data CompareOp
= CmpEq
| CmpNe
| CmpGt
| CmpGtE
| CmpLt
| CmpLtE
deriving (Show, Eq)
-- | 条件表达式:列、字面量、算术、比较、逻辑、聚合与子查询 -- | 条件表达式:列、字面量、算术、比较、逻辑、聚合与子查询
data Expr data Expr
= Col String = Col String
@@ -124,6 +140,7 @@ data Expr
| InSub Expr Subquery Bool | InSub Expr Subquery Bool
| InList Expr [Expr] Bool | InList Expr [Expr] Bool
| ExistsSub Subquery Bool | ExistsSub Subquery Bool
| QuantCmp CompareOp Expr Subquery Quantifier
deriving (Show, Eq) deriving (Show, Eq)
-- | 排序方向:升序或降序 -- | 排序方向:升序或降序
@@ -3,7 +3,7 @@
module ChuSQL.Core.Engine.Syntax.Parser (parseStatement, parseExpression) where module ChuSQL.Core.Engine.Syntax.Parser (parseStatement, parseExpression) where
import ChuSQL.Core.Model (Column (..), ColumnType (..), Value (..), plainColumn) import ChuSQL.Core.Model (Column (..), ColumnType (..), Value (..), plainColumn)
import ChuSQL.Core.Engine.Builtin (Builtin (..), builtinName, builtinNames, builtinNode) import ChuSQL.Core.Engine.Builtin (Builtin (..), builtinName, builtinNames, builtinNode, compareNode)
import ChuSQL.Core.Engine.Syntax.AST import ChuSQL.Core.Engine.Syntax.AST
import Control.Monad (void) import Control.Monad (void)
import Control.Monad.Combinators.Expr (Operator (..), makeExprParser) import Control.Monad.Combinators.Expr (Operator (..), makeExprParser)
@@ -112,14 +112,9 @@ operatorTable =
[ [Prefix (Neg <$ symbol "-")] [ [Prefix (Neg <$ symbol "-")]
, [InfixL (Mul <$ symbol "*"), InfixL (Div <$ symbol "/")] , [InfixL (Mul <$ symbol "*"), InfixL (Div <$ symbol "/")]
, [InfixL (Add <$ symbol "+"), InfixL (Sub <$ symbol "-")] , [InfixL (Add <$ symbol "+"), InfixL (Sub <$ symbol "-")]
, [ InfixN (NotEq <$ try (symbol "<>" <|> symbol "!=")) , [ InfixN comparisonOp ]
, InfixN (GtE <$ try (symbol ">="))
, InfixN (LtE <$ try (symbol "<="))
, InfixN (Gt <$ symbol ">")
, InfixN (Lt <$ symbol "<")
, InfixN (Eq <$ symbol "=")
]
, [ Postfix inPredicate , [ Postfix inPredicate
, Postfix (try quantifiedCmp)
, Postfix (IsNotNull <$ try (keyword "is" *> keyword "not" *> keyword "null")) , Postfix (IsNotNull <$ try (keyword "is" *> keyword "not" *> keyword "null"))
, Postfix (IsNull <$ (keyword "is" *> keyword "null")) , Postfix (IsNull <$ (keyword "is" *> keyword "null"))
] ]
@@ -127,6 +122,37 @@ operatorTable =
, [InfixL (Or <$ keyword "OR")] , [InfixL (Or <$ keyword "OR")]
] ]
-- | 普通比较:读最长的运算符,后面不能是 ANY / ALL
comparisonOp :: Parser (Expr -> Expr -> Expr)
comparisonOp = try $ do
op <- cmpOp
notFollowedBy quantifierKeyword
return (compareNode op)
-- | 量词:ANY 或 ALL
quantifierKeyword :: Parser Quantifier
quantifierKeyword = (AnyQ <$ keyword "any") <|> (AllQ <$ keyword "all")
-- | 比较运算符符号
cmpOp :: Parser CompareOp
cmpOp =
choice
[ CmpNe <$ try (symbol "<>" <|> symbol "!=")
, CmpGtE <$ try (symbol ">=")
, CmpLtE <$ try (symbol "<=")
, CmpGt <$ symbol ">"
, CmpLt <$ symbol "<"
, CmpEq <$ symbol "="
]
-- | 量词比较 ANY / ALL
quantifiedCmp :: Parser (Expr -> Expr)
quantifiedCmp = do
op <- cmpOp
q <- quantifierKeyword
sq <- between (symbol "(") (symbol ")") subquerySelect
return (\e -> QuantCmp op e sq q)
-- | 最小的表达式单位 -- | 最小的表达式单位
atom :: Parser Expr atom :: Parser Expr
atom = atom =
@@ -282,6 +308,8 @@ reservedWords =
, "type" , "type"
, "group" , "group"
, "with" , "with"
, "any"
, "all"
] ]
-- | 别名(不许用保留字) -- | 别名(不许用保留字)
@@ -715,6 +743,7 @@ expandExpr env e = case e of
InSub x sq neg -> (\x' sq' -> InSub x' sq' neg) <$> go x <*> goSub sq InSub x sq neg -> (\x' sq' -> InSub x' sq' neg) <$> go x <*> goSub sq
InList x es neg -> (\x' es' -> InList x' es' neg) <$> go x <*> traverse go es InList x es neg -> (\x' es' -> InList x' es' neg) <$> go x <*> traverse go es
ExistsSub sq neg -> (\sq' -> ExistsSub sq' neg) <$> goSub sq ExistsSub sq neg -> (\sq' -> ExistsSub sq' neg) <$> goSub sq
QuantCmp op x sq q -> (\x' sq' -> QuantCmp op x' sq' q) <$> go x <*> goSub sq
_ -> Right e _ -> Right e
where where
-- | 继续看子表达式 -- | 继续看子表达式
+49
View File
@@ -602,6 +602,55 @@ main = hspec $ do
ids (And (Gt (Col "code") (LitInt 501)) (Lt (Col "code") (LitInt 502))) ids (And (Gt (Col "code") (LitInt 501)) (Lt (Col "code") (LitInt 502)))
`shouldReturn` Right [] `shouldReturn` Right []
describe "ChuSQL.Core.Engine (ANY / ALL)" $ do
-- sql:在默认库上跑 SQL 取结果行
let sql input = parseStatement input >>= rowsOf . runStatement testDB
-- values:只取结果里的值
values input = fmap (map (map snd)) (sql input)
it "parses > ANY into a quantified comparison" $ do
parseStatement "SELECT name FROM users WHERE age > ANY (SELECT age FROM orders)"
`shouldBe` Right (Select ["name"] (FromTable Nothing "users") (Just (QuantCmp CmpGt (Col "age") (Subquery (Select ["age"] (FromTable Nothing "orders") Nothing [] [] Nothing) []) AnyQ)) [] [] Nothing)
it "parses <> ALL into a quantified comparison" $ do
parseStatement "SELECT name FROM users WHERE age <> ALL (SELECT age FROM orders)"
`shouldBe` Right (Select ["name"] (FromTable Nothing "users") (Just (QuantCmp CmpNe (Col "age") (Subquery (Select ["age"] (FromTable Nothing "orders") Nothing [] [] Nothing) []) AllQ)) [] [] Nothing)
it "still reads plain comparisons" $ do
values "SELECT name FROM users WHERE age > 20 ORDER BY name"
`shouldBe` Right [[VStr "Alice"], [VStr "Carol"]]
it "satisfies ANY when at least one row matches" $ do
values "SELECT name FROM users WHERE age > ANY (SELECT age FROM users WHERE name = 'Bob')"
`shouldBe` Right [[VStr "Alice"], [VStr "Carol"]]
values "SELECT name FROM users WHERE age = ANY (SELECT age FROM users WHERE name = 'Bob')"
`shouldBe` Right [[VStr "Bob"]]
it "satisfies ALL only when every row matches" $ do
values "SELECT name FROM users WHERE age >= ALL (SELECT age FROM users)"
`shouldBe` Right [[VStr "Carol"]]
values "SELECT name FROM users WHERE age > ALL (SELECT age FROM users)" `shouldBe` Right []
it "treats an empty subquery as true for ALL and false for ANY" $ do
values "SELECT name FROM users WHERE age > ALL (SELECT age FROM users WHERE name = 'Nobody')"
`shouldBe` Right [[VStr "Alice"], [VStr "Bob"], [VStr "Carol"]]
values "SELECT name FROM users WHERE age > ANY (SELECT age FROM users WHERE name = 'Nobody')"
`shouldBe` Right []
it "keeps NULL in the subquery three-valued" $ do
values "SELECT name FROM users WHERE age > ANY (SELECT NULL)" `shouldBe` Right []
values "SELECT name FROM users WHERE age > ALL (SELECT NULL)" `shouldBe` Right []
it "agrees with IN on the equality form" $ do
sql "SELECT name FROM users WHERE id = ANY (SELECT id FROM orders)"
`shouldBe` sql "SELECT name FROM users WHERE id IN (SELECT id FROM orders)"
it "rejects a multi-column or mistyped quantifier subquery" $ do
isLeft (sql "SELECT name FROM users WHERE age > ANY (SELECT id, name FROM users)")
`shouldBe` True
isLeft (sql "SELECT name FROM users WHERE name > ANY (SELECT id FROM users)")
`shouldBe` True
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
@@ -292,6 +292,7 @@ exprTables expr = case expr of
ScalarSub sub -> statementTables (subqueryStatement sub) ScalarSub sub -> statementTables (subqueryStatement sub)
ExistsSub sub _ -> statementTables (subqueryStatement sub) ExistsSub sub _ -> statementTables (subqueryStatement sub)
InSub value sub _ -> exprTables value ++ statementTables (subqueryStatement sub) InSub value sub _ -> exprTables value ++ statementTables (subqueryStatement sub)
QuantCmp _ value sub _ -> exprTables value ++ statementTables (subqueryStatement sub)
InList value items _ -> concatMap exprTables (value : items) InList value items _ -> concatMap exprTables (value : items)
Add a b -> both a b Add a b -> both a b
Sub a b -> both a b Sub a b -> both a b
+9
View File
@@ -670,6 +670,15 @@ privilegeSpec = describe "server privileges" $ do
authorizeSql service (Ordinary testAccount) "SELECT name FROM users WHERE id >= (SELECT id FROM orders)" authorizeSql service (Ordinary testAccount) "SELECT name FROM users WHERE id >= (SELECT id FROM orders)"
`shouldReturn` Left (PrivilegeError "forbidden" "permission denied: SELECT ON orders") `shouldReturn` Left (PrivilegeError "forbidden" "permission denied: SELECT ON orders")
it "requires the privilege on the table inside a quantifier subquery" $ 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 name FROM users WHERE id > ANY (SELECT id FROM users)"
`shouldReturn` Right ()
authorizeSql service (Ordinary testAccount) "SELECT name FROM users WHERE id > ANY (SELECT id FROM orders)"
`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
+2
View File
@@ -51,6 +51,8 @@ chusql-server
`WITH` 不新增语法树节点:解析器在读到一个 `WITH` 时就把每个 CTE 的名字与语句记进一张环境表,遇到 `FROM` 里出现的 CTE 名(不分大小写,只认前面已定义的)就换成 M11 的派生表节点,主语句与 CTE 内部都能再写 `WITH`。这样展开出来的语句对语义层、执行器与鉴权完全等价于手写派生表:CTE 用列清单改名走派生表的投影别名,来源树与权限覆盖不必另做一套。代价是 CTE 不做物化(引用两次就执行两次)、不支持递归,且前向引用会当成普通表名去查。 `WITH` 不新增语法树节点:解析器在读到一个 `WITH` 时就把每个 CTE 的名字与语句记进一张环境表,遇到 `FROM` 里出现的 CTE 名(不分大小写,只认前面已定义的)就换成 M11 的派生表节点,主语句与 CTE 内部都能再写 `WITH`。这样展开出来的语句对语义层、执行器与鉴权完全等价于手写派生表:CTE 用列清单改名走派生表的投影别名,来源树与权限覆盖不必另做一套。代价是 CTE 不做物化(引用两次就执行两次)、不支持递归,且前向引用会当成普通表名去查。
量词比较走另一个真节点 `QuantCmp`:解析时普通比较运算符先读最长符号,发现后面是 `ANY` / `ALL` 就让给量词解析,因此 `age > ANY (SELECT …)` 与 `age > 25` 共用同一层优先级。求值时先把子查询的行取成字面量,再摊成 `AND`(`ALL`)/ `OR`(`ANY`)链交给既有三值逻辑,于是空子查询 `ANY` 为假、`ALL` 为真,子查询里的 `NULL` 按三值规则传播。节点不带取反位(本方言没有前缀 `NOT`),取反写相反的运算符,如 `<> ALL` 与 `NOT IN` 同义。
显式事务在会话层实现:`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,恢复时一并重放。
+7 -1
View File
@@ -47,7 +47,7 @@ csql -f json -e "SELECT count(*) FROM orders"
查询写法: 查询写法:
- `SELECT` 支持 `WHERE`、`ORDER BY`、`LIMIT`、`JOIN` / `LEFT JOIN`、`GROUP BY` 与聚合,以及标量 / `IN` / `EXISTS` 子查询。 - `SELECT` 支持 `WHERE`、`ORDER BY`、`LIMIT`、`JOIN` / `LEFT JOIN`、`GROUP BY` 与聚合,以及标量 / `IN` / `EXISTS` 子查询和量词比较(`op ANY` / `op ALL`)。
- 来源可以是派生表:`SELECT d.name FROM (SELECT name FROM users) d`。派生表必须带别名,内层不能引用外层列;输出列取裸列名(内层写 `u.id`,外层用 `d.id` 引用)。 - 来源可以是派生表:`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`)。 - 投影列可以起别名:`SELECT count(*) AS total FROM orders`,别名就是结果集的列名。`ORDER BY` 仍按来源列解析,要引用派生表的列得写限定名(如 `ORDER BY d.total`)。
- 鉴权覆盖整棵来源树:查派生表时内层表也要有权限,只有外层引用的表有权限不够。 - 鉴权覆盖整棵来源树:查派生表时内层表也要有权限,只有外层引用的表有权限不够。
@@ -58,6 +58,12 @@ csql -f json -e "SELECT count(*) FROM orders"
- `>`、`>=`、`<`、`<=` 在列上有索引时会改写成索引范围扫描;`>=` / `<=` 是闭区间,端点值本身也在结果里。 - `>`、`>=`、`<`、`<=` 在列上有索引时会改写成索引范围扫描;`>=` / `<=` 是闭区间,端点值本身也在结果里。
- 任一操作数为 `NULL` 时比较结果是 `NULL`,`WHERE` 只保留结果为真的行。 - 任一操作数为 `NULL` 时比较结果是 `NULL`,`WHERE` 只保留结果为真的行。
量词比较:
- `值 op ANY (SELECT 单列 …)` 表示子查询里至少有一行让比较成立;`值 op ALL (…)` 表示每一行都成立。
- `ANY` 的空子查询为假、`ALL` 的空子查询为真;子查询里出现 `NULL` 时按 `AND` / `OR` 的三值规则,条件可能为 `NULL`(`WHERE` 里等同不通过)。
- 取反写相反的运算符即可:`<> ALL` 与 `NOT IN` 同义,`<> ANY` 表示不存在相等的行。
公共表表达式: 公共表表达式:
- `WITH 名字 AS (SELECT …), 另一个 AS (SELECT …) SELECT … FROM 名字` 先定义再引用,名字可以带列清单(`WITH t(who) AS (SELECT name FROM users) …`)。 - `WITH 名字 AS (SELECT …), 另一个 AS (SELECT …) SELECT … FROM 名字` 先定义再引用,名字可以带列清单(`WITH t(who) AS (SELECT name FROM users) …`)。