子查询去相关
This commit is contained in:
1 parent
d59e65b639
commit
157372ae37
5 files changed
+212
-5
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 比较运算符补全 → M14 ANY / ALL 量词比较 → M15 优化器直方图。
|
||||
- 主线已完成:高级优化器 → 并行查询 → M1 会话隔离 → M2 快照事务 → M3 WAL 与崩溃恢复 → M4 表结构进 WAL → M5 系统表统一命名 → M6 检查点按 LSN 部分截断 → M7 事务写写冲突检测 → M8 事务保存点 → M9 角色继承 → M10 转授权 → M11 FROM 派生表与投影别名 → M12 `WITH` / CTE → M13 比较运算符补全 → M14 ANY / ALL 量词比较 → M15 优化器直方图 → M16 子查询去相关。
|
||||
- 进度与计数见 `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
|
||||
|
||||
import ChuSQL.Core.Engine.Algebra.Cost (tableMetaOf)
|
||||
import ChuSQL.Core.Engine.Algebra.Expr (evalCondForRow, evalExpr, quantifiedCompare)
|
||||
import ChuSQL.Core.Engine.Algebra.Expr (evalCondForRow, evalExpr, hasSubquery, quantifiedCompare, subqueryRefsIn)
|
||||
import ChuSQL.Core.Engine.Algebra.Optimize (optimize)
|
||||
import ChuSQL.Core.Engine.Algebra.Op
|
||||
import ChuSQL.Core.Engine.Algebra.Planner (translate)
|
||||
@@ -12,6 +12,7 @@ import ChuSQL.Core.Engine.Storage (IndexResult (..), MonadStorage (..))
|
||||
import ChuSQL.Core.Engine.Storage.Memory (runMemoryStorage)
|
||||
import ChuSQL.Core.Engine.Syntax.AST (Expr (..), JoinKind (..), Subquery (..))
|
||||
import Control.Monad (filterM)
|
||||
import Data.List (nub)
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
|
||||
-- 执行:按算子树算出结果行,外层行供相关子查询引用。
|
||||
@@ -246,6 +247,53 @@ evalAggregate outer keys aggs rows = do
|
||||
let top = if null keys && null groups then [([], [])] else groups
|
||||
mapM (oneGroup outer keys aggs) top
|
||||
|
||||
-- | 绑定键:子查询引用的外层列在这一行上的取值
|
||||
bindingKey :: [String] -> Row -> String
|
||||
bindingKey refs env = show [(c, lookup c env) | c <- refs]
|
||||
|
||||
-- | 每个绑定留第一行当样本,去掉重复绑定
|
||||
bindingSamples :: [String] -> Row -> [Row] -> [(String, Row)]
|
||||
bindingSamples refs outer rows = go rows HM.empty []
|
||||
where
|
||||
-- | 逐行按绑定键去重
|
||||
go [] _ acc = reverse acc
|
||||
go (r : rs) seen acc =
|
||||
let env = r ++ outer
|
||||
k = bindingKey refs env
|
||||
in case HM.lookup k seen of
|
||||
Just _ -> go rs seen acc
|
||||
Nothing -> go rs (HM.insert k () seen) ((k, env) : acc)
|
||||
|
||||
-- | 每个绑定只换一次子查询,得到绑定到表达式的表
|
||||
bindingsTableM ::
|
||||
(MonadStorage m) =>
|
||||
Database ->
|
||||
Row ->
|
||||
[Expr] ->
|
||||
[Row] ->
|
||||
m (Either String (HM.HashMap String [Expr]))
|
||||
bindingsTableM db outer exprs rows = do
|
||||
built <- mapM swap (bindingSamples refs outer rows)
|
||||
pure (fmap HM.fromList (sequence built))
|
||||
where
|
||||
refs = nub (concatMap subqueryRefsIn exprs)
|
||||
-- | 一个绑定样本上把子查询全换成字面量
|
||||
swap (key, env) = do
|
||||
es <- mapM (\e -> substSubqueries db e env) exprs
|
||||
pure (fmap (\xs -> (key, xs)) (sequence es))
|
||||
|
||||
-- | 取这一行绑定换好的表达式;没有这个绑定就是内部错误
|
||||
boundExprs :: [String] -> Row -> HM.HashMap String [Expr] -> Either String [Expr]
|
||||
boundExprs refs env table = case HM.lookup (bindingKey refs env) table of
|
||||
Just es -> Right es
|
||||
Nothing -> Left "subquery binding is missing"
|
||||
|
||||
-- | 取这一行绑定换好的唯一表达式
|
||||
boundExpr :: [String] -> Row -> HM.HashMap String [Expr] -> Either String Expr
|
||||
boundExpr refs env table = case boundExprs refs env table of
|
||||
Right [x] -> Right x
|
||||
_ -> Left "subquery binding is missing"
|
||||
|
||||
-- | 单子过滤:条件本身是单子求值的
|
||||
filterRowsM :: (Monad m) => (Row -> m (Either String Bool)) -> [Row] -> m (Either String [Row])
|
||||
filterRowsM p = go []
|
||||
@@ -385,9 +433,19 @@ evalRelOpIn db outer (Compute items op) = do
|
||||
rows <- evalRelOpIn db outer op
|
||||
case rows of
|
||||
Left e -> pure (Left e)
|
||||
Right rs -> do
|
||||
Right rs
|
||||
| not (any (hasSubquery . snd) items) -> do
|
||||
computed <- mapM (computeM db outer items) rs
|
||||
pure (sequence computed)
|
||||
| otherwise -> do
|
||||
table <- bindingsTableM db outer (map snd items) rs
|
||||
pure (table >>= \t -> traverse (computeBound t) rs)
|
||||
where
|
||||
refs = nub (concatMap (subqueryRefsIn . snd) items)
|
||||
-- | 用这一行绑定换好的表达式算出一行
|
||||
computeBound table r = do
|
||||
es <- boundExprs refs (r ++ outer) table
|
||||
mapM (\(label, e) -> (,) label <$> evalExpr e (r ++ outer)) (zip (map fst items) es)
|
||||
evalRelOpIn db outer (Aggregate keys aggs op) = do
|
||||
rows <- evalRelOpIn db outer op
|
||||
pure (rows >>= evalAggregate outer keys aggs)
|
||||
@@ -410,7 +468,17 @@ evalRelOpIn db outer (Filter e op) = do
|
||||
rows <- evalRelOpIn db outer op
|
||||
case rows of
|
||||
Left err -> pure (Left err)
|
||||
Right rs -> filterRowsM (\r -> evalCondForRowM db e (r ++ outer)) rs
|
||||
Right rs
|
||||
| not (hasSubquery e) -> filterRowsM (\r -> evalCondForRowM db e (r ++ outer)) rs
|
||||
| otherwise -> do
|
||||
table <- bindingsTableM db outer [e] rs
|
||||
pure (table >>= \t -> filterM (keepBound t) rs)
|
||||
where
|
||||
refs = nub (subqueryRefsIn e)
|
||||
-- | 用这一行绑定换好的条件判定
|
||||
keepBound table r = do
|
||||
e' <- boundExpr refs (r ++ outer) table
|
||||
evalCondForRow e' (r ++ outer)
|
||||
evalRelOpIn db outer (Project cols op) = do
|
||||
rows <- evalRelOpIn db outer op
|
||||
pure (fmap (map (project cols)) rows)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module ChuSQL.Core.Engine.Algebra.Expr (evalExpr, evalCondForRow, colsInExpr, aggregatesIn, hasAggregate, bareColumns, hasSubquery, hasDivision, inValues, threeValuedNot, quantifiedCompare) where
|
||||
module ChuSQL.Core.Engine.Algebra.Expr (evalExpr, evalCondForRow, colsInExpr, subqueryRefsIn, aggregatesIn, hasAggregate, bareColumns, hasSubquery, hasDivision, inValues, threeValuedNot, quantifiedCompare) where
|
||||
|
||||
import ChuSQL.Core.Model
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
@@ -90,6 +90,39 @@ colsInExpr (ExistsSub sq _) = subqueryRefs sq
|
||||
colsInExpr (QuantCmp _ a sq _) = colsInExpr a ++ subqueryRefs sq
|
||||
colsInExpr _ = []
|
||||
|
||||
-- | 表达式里子查询引用的外层列
|
||||
subqueryRefsIn :: Expr -> [String]
|
||||
subqueryRefsIn e = case e of
|
||||
ScalarSub sq -> subqueryRefs sq
|
||||
InSub a sq _ -> subqueryRefsIn a ++ subqueryRefs sq
|
||||
InList a es _ -> concatMap subqueryRefsIn (a : es)
|
||||
ExistsSub sq _ -> subqueryRefs sq
|
||||
QuantCmp _ a sq _ -> subqueryRefsIn a ++ subqueryRefs sq
|
||||
Add a b -> both a b
|
||||
Sub a b -> both a b
|
||||
Mul a b -> both a b
|
||||
Div a b -> both a b
|
||||
Neg a -> subqueryRefsIn a
|
||||
Gt a b -> both a b
|
||||
Lt a b -> both a b
|
||||
Eq a b -> both a b
|
||||
GtE a b -> both a b
|
||||
LtE a b -> both a b
|
||||
NotEq a b -> both a b
|
||||
And a b -> both a b
|
||||
Or a b -> both a b
|
||||
IsNull a -> subqueryRefsIn a
|
||||
IsNotNull a -> subqueryRefsIn a
|
||||
CountOf a -> subqueryRefsIn a
|
||||
SumOf a -> subqueryRefsIn a
|
||||
AvgOf a -> subqueryRefsIn a
|
||||
MinOf a -> subqueryRefsIn a
|
||||
MaxOf a -> subqueryRefsIn a
|
||||
_ -> []
|
||||
where
|
||||
-- | 两个子表达式的外层列拼起来
|
||||
both a b = subqueryRefsIn a ++ subqueryRefsIn b
|
||||
|
||||
-- | 表达式里有没有除法(除法可能报错,不能提前或延后求值)
|
||||
hasDivision :: Expr -> Bool
|
||||
hasDivision e = case e of
|
||||
|
||||
@@ -16,6 +16,7 @@ import ChuSQL.Core.Engine.Parallel (poolRun, workerPool)
|
||||
import ChuSQL.Core.Model
|
||||
import ChuSQL.Core.Engine.Semantic (prepare)
|
||||
import ChuSQL.Core.Engine.Storage (IndexResult (..), MonadStorage (..))
|
||||
import ChuSQL.Core.Engine.Storage.Memory (MemoryStorage (runMemoryStorage))
|
||||
import ChuSQL.Core.Engine.Storage.IPC (IPCStorage (..), closeConnection, defaultEnv, doListTables, envForDatabase, getDatabaseName, localStorageLink, runIPCStorage, setDatabaseName, setStorageLink)
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Engine.Syntax.Parser
|
||||
@@ -1400,6 +1401,24 @@ main = hspec $ do
|
||||
sameResultAsUnoptimized "SELECT name FROM users WHERE id IN (SELECT user_id FROM orders)"
|
||||
sameResultAsUnoptimized "SELECT u.name FROM users u WHERE EXISTS (SELECT * FROM orders o WHERE o.user_id = u.id)"
|
||||
|
||||
describe "ChuSQL.Core.Engine (子查询去相关)" $ do
|
||||
it "runs a non-correlated subquery only once" $ do
|
||||
let (result, scans) = countedRun "SELECT name FROM users WHERE id IN (SELECT user_id FROM orders)"
|
||||
fmap (map (map snd)) result `shouldBe` Right [[VStr "Alice"], [VStr "Bob"]]
|
||||
scanTimes "orders" scans `shouldBe` 1
|
||||
|
||||
it "runs a correlated subquery once per distinct binding" $ do
|
||||
let (result, scans) =
|
||||
countedRun "SELECT o.product FROM orders o WHERE EXISTS (SELECT * FROM users u WHERE u.id = o.user_id)"
|
||||
fmap (map (map snd)) result `shouldBe` Right [[VStr "Book"], [VStr "Pen"], [VStr "Cup"]]
|
||||
scanTimes "users" scans `shouldBe` 2
|
||||
scanTimes "orders" scans `shouldBe` 1
|
||||
|
||||
it "runs a non-correlated subquery in a computed column only once" $ do
|
||||
let (result, scans) = countedRun "SELECT name, (SELECT COUNT(*) FROM orders) FROM users"
|
||||
length (either (const []) id result) `shouldBe` 3
|
||||
scanTimes "orders" scans `shouldBe` 1
|
||||
|
||||
describe "ChuSQL.Core.Engine (聚合与 GROUP BY)" $ do
|
||||
-- sql:在默认库上跑 SQL 取结果行
|
||||
let sql input = parseStatement input >>= rowsOf . runStatement testDB
|
||||
@@ -2926,3 +2945,88 @@ leftSideFilters _ = Nothing
|
||||
planRoot :: RelOp -> RelOp
|
||||
planRoot (Project _ x) = planRoot x
|
||||
planRoot x = x
|
||||
|
||||
-- | 记下扫过哪些表的存储:语义与内存实现一致,只是扫描多记一笔
|
||||
data Counting a = Counting {runCounting :: Database -> Either String (a, Database, [String])}
|
||||
|
||||
instance Functor Counting where
|
||||
fmap f (Counting m) = Counting $ \db -> fmap (\(a, db', log') -> (f a, db', log')) (m db)
|
||||
|
||||
instance Applicative Counting where
|
||||
pure a = Counting $ \db -> Right (a, db, [])
|
||||
Counting mf <*> Counting ma = Counting $ \db -> do
|
||||
(f, db1, l1) <- mf db
|
||||
(a, db2, l2) <- ma db1
|
||||
Right (f a, db2, l1 ++ l2)
|
||||
|
||||
instance Monad Counting where
|
||||
Counting m >>= k = Counting $ \db -> do
|
||||
(a, db1, l1) <- m db
|
||||
(b, db2, l2) <- runCounting (k a) db1
|
||||
Right (b, db2, l1 ++ l2)
|
||||
|
||||
-- | 内存实现的某个动作,转发给 Counting
|
||||
memory :: MemoryStorage a -> Counting a
|
||||
memory m = Counting $ \db -> case runMemoryStorage m db of
|
||||
Left e -> Left e
|
||||
Right (a, db') -> Right (a, db', [])
|
||||
|
||||
-- | 内存实现的扫描
|
||||
memScan :: String -> MemoryStorage (Either String [Row])
|
||||
memScan = scan
|
||||
|
||||
-- | 内存实现的插入
|
||||
memInsert :: String -> Row -> MemoryStorage (Either String ())
|
||||
memInsert = insert
|
||||
|
||||
-- | 内存实现的整表替换
|
||||
memReplaceAll :: String -> [Row] -> MemoryStorage (Either String ())
|
||||
memReplaceAll = replaceAll
|
||||
|
||||
-- | 内存实现的建表
|
||||
memCreateTable :: String -> [(String, Column)] -> MemoryStorage (Either String ())
|
||||
memCreateTable = createTable
|
||||
|
||||
-- | 内存实现的删表
|
||||
memDropTable :: String -> MemoryStorage (Either String ())
|
||||
memDropTable = dropTable
|
||||
|
||||
-- | 内存实现的删列
|
||||
memDropColumn :: String -> String -> MemoryStorage (Either String ())
|
||||
memDropColumn = dropColumn
|
||||
|
||||
-- | 内存实现的换结构
|
||||
memReplaceSchema :: String -> [(String, Column)] -> [Row] -> MemoryStorage (Either String ())
|
||||
memReplaceSchema = replaceSchema
|
||||
|
||||
instance MonadStorage Counting where
|
||||
-- \| 扫描:记一笔表名,其余走内存实现
|
||||
scan t = Counting $ \db -> case runMemoryStorage (memScan t) db of
|
||||
Left e -> Left e
|
||||
Right (r, db') -> Right (r, db', [t])
|
||||
-- \| 插入:走内存实现
|
||||
insert t r = memory (memInsert t r)
|
||||
-- \| 整表替换:走内存实现
|
||||
replaceAll t rs = memory (memReplaceAll t rs)
|
||||
-- \| 建表:走内存实现
|
||||
createTable name cols = memory (memCreateTable name cols)
|
||||
-- \| 删表:走内存实现
|
||||
dropTable name = memory (memDropTable name)
|
||||
-- \| 删列:走内存实现
|
||||
dropColumn name col = memory (memDropColumn name col)
|
||||
-- \| 换结构:走内存实现
|
||||
replaceSchema name cols rows = memory (memReplaceSchema name cols rows)
|
||||
-- \| 快照:把当前库原样交出去
|
||||
snapshot = Counting $ \db -> Right (db, db, [])
|
||||
|
||||
-- | 解析一条 SQL 并在计数存储上跑,返回结果与扫描过的表
|
||||
countedRun :: String -> (Either String [Row], [String])
|
||||
countedRun input = case parseStatement input of
|
||||
Left err -> (Left err, [])
|
||||
Right stmt -> case runCounting (runStatementM stmt) testDB of
|
||||
Left err -> (Left err, [])
|
||||
Right (result, _, scans) -> (result, scans)
|
||||
|
||||
-- | 结果里出现了几张表被扫过几次
|
||||
scanTimes :: String -> [String] -> Int
|
||||
scanTimes t scans = length [name | name <- scans, name == t]
|
||||
@@ -55,6 +55,8 @@ chusql-server
|
||||
|
||||
优化器的成本估算用存储维护的统计:行数、列级不同值数,以及数值列的列级等宽直方图。直方图是 16 个桶加区间下界 / 上界,只统计 `int` 与 `float` 列(NULL 与非数值不进直方图);存储侧随插入累计、随删除递减,值跑出区间就把旧桶按中点并进新区间,删到表空时清空。这些统计随 schema 下沉到引擎,`Cost.rangeRows` 有直方图就按桶与区间的交集估命中行数(端点按闭区间算),没有直方图退回原来的四分之一;`Cost.selectivityOf` 把这个比例交给连接顺序估算与索引改写的成本把关,因此一条覆盖大半张表的范围条件不会再被改写成索引点查。在线协议里每条列统计多出 `lo` / `hi` / `hist` 三个可选字段,缺省即「没有直方图」,老客户端与老字典都能照常读。
|
||||
|
||||
子查询按外层绑定分组求值:`Filter` 与计算列两个算子入口先看条件里有没有子查询,有就按子查询引用的外层列取值给扫描到的行分组,同一绑定只把子查询换一次(`IN` / `EXISTS` / 标量 / 量词四种位置共用),然后各行的条件与表达式都用换好的那份求值。非相关子查询没有外层列,因此整条语句只跑一次子查询;相关子查询按不同绑定各跑一次(外层 3 行但绑定只有 2 种就只跑 2 次)。没有子查询的行仍走原来的逐行求值,JOIN 条件与更深的表达式位置不做分组。
|
||||
|
||||
显式事务在会话层实现:`BEGIN` 把当前数据库整库读成内存快照,事务内的语句都在快照上执行;`SAVEPOINT` 把当前快照压进会话里的保存点栈,`ROLLBACK TO` 退回该点并保留它,`RELEASE` 丢掉该点与其后的保存点;`COMMIT` 先把快照、基线与库里现值按行 `id` 逐键比对,检测到写写冲突就报 `serialization_failure` 并保留事务;没有冲突才把差分用一条 `apply_transaction` 请求交给存储层应用;`ROLLBACK` 直接丢弃快照。因此隔离级别是快照读加原子提交:事务里看不到别的事务未提交或后来提交的改动,提交对外一次性可见,冲突由调用方回滚或重试。
|
||||
|
||||
WAL 负责崩溃恢复:每条记录带自增 LSN,一组写操作与一条提交标记一次落盘;每组提交在数据和字典都刷盘之后把 `wal.checkpoint` 推进到这条提交的 LSN,含义是「到此为止的改动都已落盘」,再按这个边界丢掉日志前缀——所以日志里通常只有当组在飞的记录。进程重启时只重放检查点之后的记录,按标记分组取已提交的组、丢弃没有标记的整组,重放前逐条做幂等检查作为第二道保险,结束后再把检查点推到最后一条提交。LSN 从检查点与文件尾里较大的那个续号,跨重启单调,`SystemStatus` 应答里的 `last_lsn` 报告该检查点 LSN。建表、删表、建索引、删索引与数据改动走同一条 WAL,恢复时一并重放。
|
||||
|
||||
Reference in new issue
Block a user