量词比较
This commit is contained in:
1 parent
9b1c835202
commit
7d96933e32
15 files changed
+185
-15
No files matched your search
@@ -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
|
||||||
-- | 继续看子表达式
|
-- | 继续看子表达式
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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) …`)。
|
||||||
|
|||||||
Reference in new issue
Block a user