修bug,完善功能
This commit is contained in:
1 parent
30364b1c4b
commit
6996e19694
125 files changed
+15343
-3746
No files matched your search
@@ -10,7 +10,8 @@ build-type: Simple
|
||||
|
||||
library
|
||||
hs-source-dirs: src
|
||||
exposed-modules: ChuSQL.Core.Engine.Algebra.Cost
|
||||
exposed-modules: ChuSQL.Core.Engine.Domain
|
||||
ChuSQL.Core.Engine.Algebra.Cost
|
||||
ChuSQL.Core.Engine.Algebra.Eval
|
||||
ChuSQL.Core.Engine.Algebra.Expr
|
||||
ChuSQL.Core.Engine.Algebra.JoinOrder
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
module ChuSQL.Core.Engine (runStatement, runStatementM, rowsOf, BoundWrite, bindWrite, executeWriteM) where
|
||||
module ChuSQL.Core.Engine (runStatement, runStatementM, rowsOf, BoundWrite, bindWrite, executeWriteM, validateTableRows) where
|
||||
|
||||
import ChuSQL.Core.Engine.Algebra.Eval (evalCondForRowM, evalExprM)
|
||||
import ChuSQL.Core.Engine.Algebra.Expr (evalCondForRow, evalExpr)
|
||||
import ChuSQL.Core.Engine.Domain
|
||||
import ChuSQL.Core.Engine.Runtime.Dependencies (domainDependencies, queryTables, typeDependencies, validateDependencies)
|
||||
import ChuSQL.Core.Engine.Runtime.Query (bindQuery, executeQueryM)
|
||||
import ChuSQL.Core.Engine.Runtime.Relation (bindWriteColumns, orderRow)
|
||||
import ChuSQL.Core.Engine.Runtime.Dependencies (queryTables, domainDependencies, typeDependencies, validateDependencies)
|
||||
import ChuSQL.Core.Engine.Semantic (prepare, prepareCheckExpression)
|
||||
import ChuSQL.Core.Engine.Storage (MonadStorage (..))
|
||||
import ChuSQL.Core.Engine.Storage.Memory (MemoryStorage (runMemoryStorage))
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Engine.Syntax.Parser (parseExpression)
|
||||
import ChuSQL.Core.Model
|
||||
import Data.HashSet qualified as HS
|
||||
import Data.HashMap.Strict qualified as HM
|
||||
import Data.HashSet qualified as HS
|
||||
import Data.Maybe (mapMaybe)
|
||||
|
||||
-- 引擎入口:检查语义、按语句分发执行,另有内存实现与泛型版本。
|
||||
@@ -69,26 +70,27 @@ bindWrite :: Database -> Statement -> Either String BoundWrite
|
||||
bindWrite db statement = case statement of
|
||||
Select{} -> unsupported
|
||||
SelectDistinct{} -> unsupported
|
||||
_ | null (queryTables statement) -> unsupported
|
||||
| otherwise -> do
|
||||
resolved <- prepare catalog statement >>= bindWriteColumns catalog
|
||||
let sources = queryTables resolved
|
||||
mapM_ (lookupTable catalog) sources
|
||||
let names = sources ++ domainDependencies catalog sources ++ typeDependencies catalog (declaredTypes resolved)
|
||||
dependencies = [(name, table) | (name, table) <- catalog, name `elem` names]
|
||||
action <- bindWriteAction catalog resolved
|
||||
Right (BoundWrite catalog dependencies action)
|
||||
_
|
||||
| null (queryTables statement) -> unsupported
|
||||
| otherwise -> do
|
||||
resolved <- prepare catalog statement >>= bindWriteColumns catalog
|
||||
let sources = queryTables resolved
|
||||
mapM_ (lookupTable catalog) sources
|
||||
let names = sources ++ domainDependencies catalog sources ++ typeDependencies catalog (declaredTypes resolved)
|
||||
dependencies = [(name, table) | (name, table) <- catalog, name `elem` names]
|
||||
action <- bindWriteAction catalog resolved
|
||||
Right (BoundWrite catalog dependencies action)
|
||||
where
|
||||
catalog = [(name, table{tableRows = []}) | (name, table) <- db]
|
||||
unsupported = Left "bound writes require an existing table target"
|
||||
-- | 新声明类型也须保留领域依赖
|
||||
-- \| 新声明类型也须保留领域依赖
|
||||
declaredTypes resolved = case resolved of
|
||||
AddColumn _ (_, column) -> [columnType column]
|
||||
AlterColumnType _ _ ty -> [ty]
|
||||
_ -> []
|
||||
|
||||
-- | 复查结构后执行可复用的绑定写计划
|
||||
executeWriteM :: MonadStorage m => BoundWrite -> m (Either String [Row])
|
||||
executeWriteM :: (MonadStorage m) => BoundWrite -> m (Either String [Row])
|
||||
executeWriteM (BoundWrite catalog dependencies action) = do
|
||||
current <- schema
|
||||
case current >>= validateDependencies dependencies of
|
||||
@@ -99,15 +101,24 @@ executeWriteM (BoundWrite catalog dependencies action) = do
|
||||
bindWriteAction :: Database -> Statement -> Either String WriteAction
|
||||
bindWriteAction catalog statement = case statement of
|
||||
Insert target columns rows -> do
|
||||
table <- lookupTable catalog target
|
||||
checks <- checkPlan table
|
||||
let plan = InsertPlan (bindInsertTargets (tableCols table) columns) (length columns)
|
||||
(autoColumn table) checks (constraintPlan (tableCols table))
|
||||
declared <- lookupTable catalog target
|
||||
columnsEffective <- effectiveColumns catalog (tableCols declared)
|
||||
let table = declared{tableCols = columnsEffective}
|
||||
checks <- checkPlan catalog table
|
||||
let plan =
|
||||
InsertPlan
|
||||
(bindInsertTargets (tableCols table) columns)
|
||||
(length columns)
|
||||
(autoColumn table)
|
||||
checks
|
||||
(constraintPlan (tableCols table))
|
||||
Right (InsertAction target plan rows)
|
||||
Update target assignments condition -> do
|
||||
table <- lookupTable catalog target
|
||||
declared <- lookupTable catalog target
|
||||
columnsEffective <- effectiveColumns catalog (tableCols declared)
|
||||
let table = declared{tableCols = columnsEffective}
|
||||
bound <- bindUpdateTargets (tableCols table) assignments
|
||||
checks <- checkPlan table
|
||||
checks <- checkPlan catalog table
|
||||
let plan = UpdatePlan (map fst (tableCols table)) bound condition checks (constraintPlan (tableCols table))
|
||||
Right (UpdateAction target plan)
|
||||
Delete target condition -> do
|
||||
@@ -116,7 +127,7 @@ bindWriteAction catalog statement = case statement of
|
||||
_ -> Right (StructureAction statement)
|
||||
|
||||
-- | 直接执行已绑定的数据修改或结构动作
|
||||
executeWriteActionM :: MonadStorage m => Database -> WriteAction -> m (Either String [Row])
|
||||
executeWriteActionM :: (MonadStorage m) => Database -> WriteAction -> m (Either String [Row])
|
||||
executeWriteActionM _ (InsertAction target plan rows) = do
|
||||
current <- existingRows target plan
|
||||
case current >>= \current' -> buildRows plan current' rows of
|
||||
@@ -151,14 +162,14 @@ executeWriteActionM catalog (DeleteAction target columns condition) = do
|
||||
result <- replaceAll target kept
|
||||
pure (result >> Right [])
|
||||
where
|
||||
-- | 取整数主键,否则使用原有整表删除路径
|
||||
-- \| 取整数主键,否则使用原有整表删除路径
|
||||
rowId row = case lookup "id" row of
|
||||
Just (VInt key) -> Right key
|
||||
_ -> Left "row has no integer id"
|
||||
executeWriteActionM catalog (StructureAction statement) = runStatementUncheckedM catalog statement
|
||||
|
||||
-- | 绑定并执行查询语句
|
||||
runQueryM :: MonadStorage m => Database -> Statement -> m (Either String [Row])
|
||||
runQueryM :: (MonadStorage m) => Database -> Statement -> m (Either String [Row])
|
||||
runQueryM db q = case bindQuery db q of
|
||||
Left e -> pure (Left e)
|
||||
Right bound -> executeQueryM bound
|
||||
@@ -170,11 +181,19 @@ runStatementUncheckedM db q@Select{} = runQueryM db q
|
||||
runStatementUncheckedM _ Insert{} = pure (Left "INSERT requires a bound write plan")
|
||||
runStatementUncheckedM _ Update{} = pure (Left "UPDATE requires a bound write plan")
|
||||
runStatementUncheckedM _ Delete{} = pure (Left "DELETE requires a bound write plan")
|
||||
runStatementUncheckedM _ (CreateTable name cols) = do
|
||||
result <- createTable name cols
|
||||
pure (result >> Right [])
|
||||
runStatementUncheckedM _ (CreateDomain name base) = do
|
||||
result <- createTable (domainTableName name) [("base", plainColumn base)]
|
||||
runStatementUncheckedM db (CreateTable name cols) = case validateDomainDefaults db cols of
|
||||
Left err -> pure (Left err)
|
||||
Right () -> do
|
||||
result <- guardDomainCreation db (map (columnType . snd) cols) (createTable name cols)
|
||||
pure (result >> Right [])
|
||||
runStatementUncheckedM db (CreateDomain name definition) = case canonicalDomain db name definition of
|
||||
Left err -> pure (Left err)
|
||||
Right normalized -> do
|
||||
let DomainDefinition column _ = normalized
|
||||
result <- guardDomainCreation db [columnType column] (createTable (domainTableName name) (domainColumns normalized))
|
||||
pure (result >> Right [])
|
||||
runStatementUncheckedM _ (AlterDomain name action) = do
|
||||
result <- alterDomain (domainTableName name) (prepareDomainAlter name action)
|
||||
pure (result >> Right [])
|
||||
runStatementUncheckedM _ (DropDomain name) = do
|
||||
result <- dropTable (domainTableName name)
|
||||
@@ -201,7 +220,7 @@ runStatementUncheckedM _ (DropColumn tbl col) = do
|
||||
result <- dropColumn tbl col
|
||||
pure (result >> Right [])
|
||||
runStatementUncheckedM db (AddColumn tbl def) =
|
||||
alterTable db tbl (\table -> addColumnTo def (tableCols table) (tableRows table))
|
||||
alterTable db tbl (\table -> addColumnTo db def (tableCols table) (tableRows table))
|
||||
runStatementUncheckedM db (RenameColumn tbl old new) =
|
||||
alterTable db tbl (\table -> renameColumnIn old new (tableCols table) (tableRows table))
|
||||
runStatementUncheckedM db (AlterColumnType tbl col ty) =
|
||||
@@ -228,12 +247,22 @@ runStatementUncheckedM _ q@DropDatabase{} = runStatementM q
|
||||
runStatementUncheckedM _ q@UseDatabase{} = runStatementM q
|
||||
runStatementUncheckedM _ ShowDatabases = runStatementM ShowDatabases
|
||||
runStatementUncheckedM _ BeginTransaction = pure (Left "BEGIN is executed by the session")
|
||||
runStatementUncheckedM _ BeginSerializableTransaction = pure (Left "BEGIN is executed by the session")
|
||||
runStatementUncheckedM _ CommitTransaction = pure (Left "COMMIT is executed by the session")
|
||||
runStatementUncheckedM _ RollbackTransaction = pure (Left "ROLLBACK is executed by the session")
|
||||
runStatementUncheckedM _ Savepoint{} = pure (Left "SAVEPOINT is executed by the session")
|
||||
runStatementUncheckedM _ RollbackToSavepoint{} = pure (Left "ROLLBACK TO is executed by the session")
|
||||
runStatementUncheckedM _ ReleaseSavepoint{} = pure (Left "RELEASE is executed by the session")
|
||||
|
||||
-- | 建表时在实际发布点复查领域定义
|
||||
guardDomainCreation :: (MonadStorage m) => Database -> [ColumnType] -> m (Either String a) -> m (Either String a)
|
||||
guardDomainCreation db types action
|
||||
| null dependencies = action
|
||||
| otherwise = withWriteSchemaGuard dependencies action
|
||||
where
|
||||
names = typeDependencies db types
|
||||
dependencies = [(name, table) | (name, table) <- db, name `elem` names]
|
||||
|
||||
-- | 只有需要读全表才能判断约束时才扫表
|
||||
existingRows :: (MonadStorage m) => String -> InsertPlan -> m (Either String [Row])
|
||||
existingRows tbl (InsertPlan _ _ auto _ (ConstraintPlan _ _ unique))
|
||||
@@ -317,11 +346,14 @@ autoStart (Just (name, _)) rows = case [n | r <- rows, Just (VInt n) <- [lookup
|
||||
ns -> maximum ns + 1
|
||||
|
||||
-- | 一次解析出来的 CHECK 条件
|
||||
data CheckPlan = CheckPlan [String] [(String, Expr)]
|
||||
data CheckPlan = CheckPlan [String] [(String, Expr)] [(String, Int, Expr)]
|
||||
|
||||
-- | 按 schema 解析并绑定表上全部 CHECK
|
||||
checkPlan :: Table -> Either String CheckPlan
|
||||
checkPlan table = CheckPlan (map fst (tableCols table)) <$> mapM parse (mapMaybe checked (tableCols table))
|
||||
checkPlan :: Database -> Table -> Either String CheckPlan
|
||||
checkPlan db table = do
|
||||
local <- mapM parse (mapMaybe checked (tableCols table))
|
||||
domains <- domainCheckPlan db (tableCols table)
|
||||
Right (CheckPlan (map fst (tableCols table)) local domains)
|
||||
where
|
||||
-- \| 只挑出带 CHECK 文本的列
|
||||
checked (name, col) = (,) name <$> columnCheck col
|
||||
@@ -334,9 +366,10 @@ checkPlan table = CheckPlan (map fst (tableCols table)) <$> mapM parse (mapMaybe
|
||||
|
||||
-- | 执行解析好的 CHECK 条件
|
||||
runChecks :: CheckPlan -> Row -> Either String ()
|
||||
runChecks (CheckPlan columns plan) row = do
|
||||
runChecks (CheckPlan columns plan domains) row = do
|
||||
ordered <- orderRow columns row
|
||||
mapM_ (check ordered) plan
|
||||
runDomainChecks domains ordered
|
||||
where
|
||||
-- \| 跑一条 CHECK
|
||||
check ordered (name, e) = case evalCondForRow e ordered of
|
||||
@@ -344,6 +377,16 @@ runChecks (CheckPlan columns plan) row = do
|
||||
Right True -> Right ()
|
||||
Right False -> Left ("CHECK on " ++ name ++ " failed")
|
||||
|
||||
-- 校验最终行集的领域、检查与唯一约束。
|
||||
validateTableRows :: Database -> Table -> Either String ()
|
||||
validateTableRows db table = do
|
||||
effective <- effectiveColumns db (tableCols table)
|
||||
plan <- checkPlan db table
|
||||
let rows = tableRows table
|
||||
mapM_ (runChecks plan) rows
|
||||
ordered <- mapM (orderRow (map fst effective)) rows
|
||||
enforceConstraints (constraintPlan effective) [] ordered
|
||||
|
||||
-- | 只取第一行(报错提示用)
|
||||
firstLine :: String -> String
|
||||
firstLine = takeWhile (/= '\n')
|
||||
@@ -370,7 +413,7 @@ enforceConstraints (ConstraintPlan columns required unique) current rows = do
|
||||
enforceNotNull :: [(String, Expr)] -> [Row] -> Either String ()
|
||||
enforceNotNull targets rows = mapM_ check targets
|
||||
where
|
||||
-- | 按 schema 槽位检查 NOT NULL
|
||||
-- \| 按 schema 槽位检查 NOT NULL
|
||||
check (name, reference) = do
|
||||
values <- mapM (evalExpr reference) rows
|
||||
if VNull `elem` values
|
||||
@@ -411,7 +454,7 @@ data BoundAssignment = BoundAssignment Int String Column Expr
|
||||
bindUpdateTargets :: [(String, Column)] -> [(String, Expr)] -> Either String [BoundAssignment]
|
||||
bindUpdateTargets columns = mapM bindTarget
|
||||
where
|
||||
-- | 只接受唯一目标并保留列定义
|
||||
-- \| 只接受唯一目标并保留列定义
|
||||
bindTarget (name, expression) = case [(slot, column) | (slot, (key, column)) <- zip [0 ..] columns, key == name] of
|
||||
[(slot, column)] -> Right (BoundAssignment slot name column expression)
|
||||
[] -> Left ("UPDATE: unknown target column: " ++ name)
|
||||
@@ -429,9 +472,11 @@ applyUpdatesM db (assignment@(BoundAssignment _ _ _ e) : rest) row = do
|
||||
-- | 校验目标位置并只覆盖该槽位的值
|
||||
writeAssignment :: BoundAssignment -> Row -> Value -> Either String Row
|
||||
writeAssignment (BoundAssignment slot name column _) row value = case splitAt slot row of
|
||||
(before, (key, _) : after) | slot >= 0, key == name -> do
|
||||
coerced <- coerceValue (columnType column) value
|
||||
Right (before ++ ((name, coerced) : after))
|
||||
(before, (key, _) : after)
|
||||
| slot >= 0
|
||||
, key == name -> do
|
||||
coerced <- coerceValue (columnType column) value
|
||||
Right (before ++ ((name, coerced) : after))
|
||||
(_, []) -> Left ("missing bound column: " ++ name)
|
||||
_ -> Left ("bound column position mismatch: " ++ name)
|
||||
|
||||
@@ -471,20 +516,23 @@ alterTable db tbl change = case lookupTable db tbl of
|
||||
-- \| 改完再统一查一遍约束
|
||||
checkAltered (cols, rows) = do
|
||||
let table = Table tbl cols rows Nothing Nothing Nothing
|
||||
plan <- checkPlan table
|
||||
mapM_ (runChecks plan) rows
|
||||
enforceConstraints (constraintPlan cols) [] rows
|
||||
validateDomainDefaults db cols
|
||||
validateTableRows db table
|
||||
Right (cols, rows)
|
||||
|
||||
-- | ADD COLUMN:老行补默认值或 NULL
|
||||
addColumnTo :: (String, Column) -> [(String, Column)] -> [Row] -> Either String ([(String, Column)], [Row])
|
||||
addColumnTo (name, col) cols rows
|
||||
addColumnTo :: Database -> (String, Column) -> [(String, Column)] -> [Row] -> Either String ([(String, Column)], [Row])
|
||||
addColumnTo db (name, declared) cols rows
|
||||
| name `elem` map fst cols = Left ("ADD COLUMN: column already exists: " ++ name)
|
||||
| otherwise = do
|
||||
values <- fill
|
||||
Right (cols ++ [(name, col)], [r ++ [(name, v)] | (r, v) <- zip rows values])
|
||||
effective <- effectiveColumns db [(name, declared)]
|
||||
values <- case effective of
|
||||
[(_, column)] -> fill column
|
||||
_ -> Left "invalid added column layout"
|
||||
Right (cols ++ [(name, declared)], [r ++ [(name, v)] | (r, v) <- zip rows values])
|
||||
where
|
||||
fill
|
||||
-- \| 使用领域继承属性填充已有行
|
||||
fill col
|
||||
| columnAutoIncrement col = mapM (coerceValue (columnType col) . VInt) [1 .. length rows]
|
||||
| Just d <- columnDefault col = mapM (\_ -> coerceValue (columnType col) d) rows
|
||||
| columnNullable col = mapM (\_ -> coerceValue (columnType col) VNull) rows
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module ChuSQL.Core.Engine.Algebra.Cost (tableMetaOf, probeCost, pointRows, rangeRows, selectivityOf, indexCheaper) where
|
||||
|
||||
import Data.Maybe (fromMaybe)
|
||||
import ChuSQL.Core.Model
|
||||
import Data.Maybe (fromMaybe)
|
||||
|
||||
-- 统计与成本估算:行数、不同值数、列直方图与索引探路页数。
|
||||
|
||||
@@ -33,9 +33,9 @@ estimateRows h lo hi = max 0 (min total (round (sum (zipWith overlap [0 ..] (his
|
||||
where
|
||||
total = sum (histBuckets h)
|
||||
width = if histHigh h > histLow h then (histHigh h - histLow h) / fromIntegral (length (histBuckets h)) else 0
|
||||
lower = fromMaybe (- (1 / 0)) (boundOf lo)
|
||||
lower = fromMaybe (-(1 / 0)) (boundOf lo)
|
||||
upper = fromMaybe (1 / 0) (boundOf hi)
|
||||
-- | 第 i 个桶与区间重叠的行数
|
||||
-- \| 第 i 个桶与区间重叠的行数
|
||||
overlap :: Int -> Int -> Double
|
||||
overlap i count
|
||||
| width <= 0 = if lower <= histLow h && histLow h <= upper then fromIntegral count else 0
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
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, hasSubquery, quantifiedCompare, subquerySlotsIn, subqueryColumns)
|
||||
import ChuSQL.Core.Engine.Algebra.Optimize (optimize)
|
||||
import ChuSQL.Core.Engine.Algebra.Expr (evalCondForRow, evalExpr, hasSubquery, quantifiedCompare, subqueryColumns, subquerySlotsIn)
|
||||
import ChuSQL.Core.Engine.Algebra.Op
|
||||
import ChuSQL.Core.Engine.Algebra.Optimize (optimize)
|
||||
import ChuSQL.Core.Engine.Algebra.Planner (translate)
|
||||
import ChuSQL.Core.Engine.Algebra.Sort (sortRows, sortBoundRows)
|
||||
import ChuSQL.Core.Engine.Algebra.Sort (sortBoundRows, sortRows)
|
||||
import ChuSQL.Core.Engine.Builtin (aggregateArg, builtinOfExpr, inValues, invokeBuiltin, unsupportedAggregate)
|
||||
import ChuSQL.Core.Engine.Runtime.Relation (bindRelOpColumns, orderRow)
|
||||
import ChuSQL.Core.Engine.Builtin (aggregateArg, builtinOfExpr, invokeBuiltin, unsupportedAggregate, inValues)
|
||||
import ChuSQL.Core.Model
|
||||
import ChuSQL.Core.Engine.Storage (IndexResult (..), MonadStorage (..))
|
||||
import ChuSQL.Core.Engine.Storage.Memory (runMemoryStorage)
|
||||
import ChuSQL.Core.Engine.Syntax.AST (Expr (..), JoinKind (..), Subquery (..))
|
||||
import ChuSQL.Core.Model
|
||||
import Control.Monad (filterM)
|
||||
import Control.Monad.Trans.Class (lift)
|
||||
import Control.Monad.Trans.State.Strict (StateT, evalStateT, gets, modify')
|
||||
import Data.Bifunctor (first)
|
||||
import Data.HashMap.Strict qualified as HM
|
||||
import Data.HashSet qualified as HS
|
||||
import Data.List (nub)
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
import qualified Data.HashSet as HS
|
||||
|
||||
-- 执行:按算子树算出结果行,外层行供相关子查询引用。
|
||||
|
||||
@@ -26,7 +28,7 @@ import qualified Data.HashSet as HS
|
||||
type Execution m a = StateT (HM.HashMap String [Row]) m a
|
||||
|
||||
-- | 首次使用时执行,成功结果供后续引用复用
|
||||
materialize :: Monad m => String -> Execution m (Either String [Row]) -> Execution m (Either String [Row])
|
||||
materialize :: (Monad m) => String -> Execution m (Either String [Row]) -> Execution m (Either String [Row])
|
||||
materialize key action = do
|
||||
cached <- gets (HM.lookup key)
|
||||
case cached of
|
||||
@@ -46,7 +48,7 @@ project cs row = [(c, v) | c <- cs, Just v <- [lookup c row]]
|
||||
|
||||
-- | 给列名加别名前缀
|
||||
addPrefix :: Maybe String -> Row -> Row
|
||||
addPrefix mAlias = map (\(k, v) -> (qualify mAlias k, v))
|
||||
addPrefix mAlias = map (first (qualify mAlias))
|
||||
|
||||
-- | 给一批行加别名前缀
|
||||
prefixRows :: Maybe String -> [Row] -> [Row]
|
||||
@@ -74,7 +76,7 @@ rangeCondition mAlias col lo hi = case (lo, hi) of
|
||||
(Nothing, Nothing) -> LitBool True
|
||||
where
|
||||
c = Col (qualify mAlias col)
|
||||
-- | 一个端点的比较,闭端再加上等号
|
||||
-- \| 一个端点的比较,闭端再加上等号
|
||||
bound rel (v, inclusive)
|
||||
| inclusive = Or (rel c (valueLiteral v)) (Eq c (valueLiteral v))
|
||||
| otherwise = rel c (valueLiteral v)
|
||||
@@ -102,10 +104,18 @@ data EquiKeys = EquiKeys
|
||||
-- | 认出 a = b 这种两列等值条件,并且能分清谁在左谁在右
|
||||
equiKeys :: [String] -> [String] -> Expr -> Maybe EquiKeys
|
||||
equiKeys lcols rcols (Eq left right)
|
||||
| Just a <- columnName left, Just b <- columnName right
|
||||
, a /= b, a `elem` lcols, b `elem` rcols = Just (EquiKeys a b)
|
||||
| Just a <- columnName left, Just b <- columnName right
|
||||
, a /= b, b `elem` lcols, a `elem` rcols = Just (EquiKeys b a)
|
||||
| Just a <- columnName left
|
||||
, Just b <- columnName right
|
||||
, a /= b
|
||||
, a `elem` lcols
|
||||
, b `elem` rcols =
|
||||
Just (EquiKeys a b)
|
||||
| Just a <- columnName left
|
||||
, Just b <- columnName right
|
||||
, a /= b
|
||||
, b `elem` lcols
|
||||
, a `elem` rcols =
|
||||
Just (EquiKeys b a)
|
||||
equiKeys _ _ _ = Nothing
|
||||
|
||||
-- | 提取执行列的诊断名称
|
||||
@@ -144,26 +154,32 @@ semiRows kind left right outer leftRows rightRows = do
|
||||
let keys = HS.fromList [canonicalValue value | value <- values, value /= VNull]
|
||||
hasNull = VNull `elem` values
|
||||
hashSafe = all safeNumber values
|
||||
-- | NULL 与数值边界沿用表达式比较规则
|
||||
-- \| NULL 与数值边界沿用表达式比较规则
|
||||
keep row = do
|
||||
value <- evalExpr left (row ++ outer)
|
||||
if value == VNull
|
||||
then Right (kind == NonMatching || kind == NotIn && null values)
|
||||
else if hashSafe && safeNumber value
|
||||
then let found = HS.member (canonicalValue value) keys
|
||||
in Right (case kind of
|
||||
Matching -> found
|
||||
NonMatching -> not found
|
||||
NotIn -> not found && not hasNull)
|
||||
else do
|
||||
result <- inValues value values
|
||||
Right (case kind of
|
||||
Matching -> result == VBool True
|
||||
NonMatching -> result /= VBool True
|
||||
NotIn -> result == VBool False)
|
||||
else
|
||||
if hashSafe && safeNumber value
|
||||
then
|
||||
let found = HS.member (canonicalValue value) keys
|
||||
in Right
|
||||
( case kind of
|
||||
Matching -> found
|
||||
NonMatching -> not found
|
||||
NotIn -> not found && not hasNull
|
||||
)
|
||||
else do
|
||||
result <- inValues value values
|
||||
Right
|
||||
( case kind of
|
||||
Matching -> result == VBool True
|
||||
NonMatching -> result /= VBool True
|
||||
NotIn -> result == VBool False
|
||||
)
|
||||
filterM keep leftRows
|
||||
where
|
||||
-- | 大数混合比较退回既有逐值语义
|
||||
-- \| 大数混合比较退回既有逐值语义
|
||||
safeNumber (VInt value) = abs (toInteger value) <= 9007199254740992
|
||||
safeNumber (VFloat value) = abs value <= 9007199254740992
|
||||
safeNumber _ = True
|
||||
@@ -178,7 +194,7 @@ hashJoin :: JoinKind -> EquiKeys -> [String] -> [Row] -> [Row] -> [Row]
|
||||
hashJoin kind ks ncols lrows rrows = concatMap probe lrows
|
||||
where
|
||||
buckets = HM.fromListWith (flip (++)) [(v, [r]) | r <- rrows, Just v <- [joinKeyOf (ekRight ks) r]]
|
||||
-- | 拿左行的键去桶里找
|
||||
-- \| 拿左行的键去桶里找
|
||||
probe l =
|
||||
let miss = case kind of
|
||||
InnerJoin -> []
|
||||
@@ -204,7 +220,7 @@ filterPairsM ::
|
||||
filterPairsM db outer kind ncols cond lrows rrows = go lrows [] HM.empty
|
||||
where
|
||||
refs = subquerySlotsIn cond
|
||||
-- | 逐左行配对,左连接没配上就补 NULL
|
||||
-- \| 逐左行配对,左连接没配上就补 NULL
|
||||
go [] acc _ = pure (Right (reverse acc))
|
||||
go (l : ls) acc cache = do
|
||||
hits <- keep l rrows [] cache
|
||||
@@ -215,7 +231,7 @@ filterPairsM db outer kind ncols cond lrows rrows = go lrows [] HM.empty
|
||||
(LeftJoin, []) -> [l ++ nullRow]
|
||||
_ -> hs
|
||||
go ls (reverse hs' ++ acc) nextCache
|
||||
-- | 拿一行右行去比条件
|
||||
-- \| 拿一行右行去比条件
|
||||
keep _ [] acc cache = pure (Right (reverse acc, cache))
|
||||
keep l (r : rs) acc cache = do
|
||||
let env = l ++ r ++ outer
|
||||
@@ -259,13 +275,18 @@ probeIndexJoin db outer kind mAlias t ks rcols cond l = case lookup (ekLeft ks)
|
||||
Left err -> pure (Left err)
|
||||
Right ordered -> fmap (fmap Just) (joinRowsIn db outer kind rcols cond [l] ordered)
|
||||
|
||||
-- | 列与键都不是浮点时才敢用索引
|
||||
-- | 仅用已支持且无浮点歧义的索引键
|
||||
indexSafe :: Database -> String -> String -> Value -> Bool
|
||||
indexSafe db t col k = case lookupTable db t of
|
||||
Left _ -> False
|
||||
Right tbl -> case lookup col (tableCols tbl) of
|
||||
Nothing -> False
|
||||
Just c -> not (floatType (columnType c)) && not (floatValue k)
|
||||
Just c -> scalarKey k && not (floatType (columnType c)) && not (floatValue k)
|
||||
where
|
||||
-- \| 只有已实现编码的键可以点查
|
||||
scalarKey VInt{} = True
|
||||
scalarKey VStr{} = True
|
||||
scalarKey _ = False
|
||||
|
||||
-- | 是不是浮点列类型
|
||||
floatType :: ColumnType -> Bool
|
||||
@@ -288,7 +309,7 @@ groupKey outer keys row = mapM (\key -> evalExpr key (row ++ outer)) keys
|
||||
groupRows :: Row -> [Expr] -> [Row] -> Either String [([Value], [Row])]
|
||||
groupRows outer keys rows = go rows (HM.empty, [])
|
||||
where
|
||||
-- | 逐行塞进对应分组,并记住分组首次出现的顺序
|
||||
-- \| 逐行塞进对应分组,并记住分组首次出现的顺序
|
||||
go [] (acc, order) = Right [(k, reverse (HM.lookupDefault [] k acc)) | k <- reverse order]
|
||||
go (r : rs) (acc, order) = do
|
||||
k <- groupKey outer keys r
|
||||
@@ -342,7 +363,7 @@ bindingValue column env = case lookup column env of
|
||||
bindingSamples :: [Expr] -> Row -> [Row] -> Either String [(String, Row)]
|
||||
bindingSamples refs outer rows = go rows HM.empty []
|
||||
where
|
||||
-- | 逐行按绑定键去重
|
||||
-- \| 逐行按绑定键去重
|
||||
go [] _ acc = Right (reverse acc)
|
||||
go (r : rs) seen acc = do
|
||||
let env = r ++ outer
|
||||
@@ -366,7 +387,7 @@ bindingsTableM db outer exprs rows = case bindingSamples refs outer rows of
|
||||
pure (fmap HM.fromList (sequence built))
|
||||
where
|
||||
refs = nub (concatMap subquerySlotsIn exprs)
|
||||
-- | 一个绑定样本上把子查询全换成字面量
|
||||
-- \| 一个绑定样本上把子查询全换成字面量
|
||||
swap (key, env) = do
|
||||
es <- mapM (\e -> substSubqueries db e env) exprs
|
||||
pure (fmap (key,) (sequence es))
|
||||
@@ -391,7 +412,7 @@ boundExpr refs env table = do
|
||||
filterRowsM :: (Monad m) => (Row -> m (Either String Bool)) -> [Row] -> m (Either String [Row])
|
||||
filterRowsM p = go []
|
||||
where
|
||||
-- | 逐行留下通过条件的
|
||||
-- \| 逐行留下通过条件的
|
||||
go acc [] = pure (Right (reverse acc))
|
||||
go acc (r : rs) = do
|
||||
keep <- p r
|
||||
@@ -404,7 +425,7 @@ filterRowsM p = go []
|
||||
computeM :: (MonadStorage m) => Database -> Row -> [(String, Expr)] -> Row -> Execution m (Either String Row)
|
||||
computeM db outer items row = go items []
|
||||
where
|
||||
-- | 逐项求值并攒成一行
|
||||
-- \| 逐项求值并攒成一行
|
||||
go [] acc = pure (Right (reverse acc))
|
||||
go ((label, e) : rest) acc = do
|
||||
v <- evalExprIn db e (row ++ outer)
|
||||
@@ -417,7 +438,7 @@ evalExprM :: (MonadStorage m) => Database -> Expr -> Row -> m (Either String Val
|
||||
evalExprM db e env = evalStateT (evalExprIn db e env) HM.empty
|
||||
|
||||
-- | 在当前执行中求值并共享子查询物化
|
||||
evalExprIn :: MonadStorage m => Database -> Expr -> Row -> Execution m (Either String Value)
|
||||
evalExprIn :: (MonadStorage m) => Database -> Expr -> Row -> Execution m (Either String Value)
|
||||
evalExprIn db e env = do
|
||||
e' <- substSubqueries db e env
|
||||
pure (e' >>= \x -> evalExpr x env)
|
||||
@@ -427,7 +448,7 @@ evalCondForRowM :: (MonadStorage m) => Database -> Expr -> Row -> m (Either Stri
|
||||
evalCondForRowM db e env = evalStateT (evalCondIn db e env) HM.empty
|
||||
|
||||
-- | 在当前执行中判定条件并共享物化
|
||||
evalCondIn :: MonadStorage m => Database -> Expr -> Row -> Execution m (Either String Bool)
|
||||
evalCondIn :: (MonadStorage m) => Database -> Expr -> Row -> Execution m (Either String Bool)
|
||||
evalCondIn db e env = do
|
||||
e' <- substSubqueries db e env
|
||||
pure (e' >>= \x -> evalCondForRow x env)
|
||||
@@ -465,29 +486,35 @@ substSubqueries db e env = case e of
|
||||
InSub a sq negated -> do
|
||||
lhs <- substSubqueries db a env
|
||||
rows <- runSubqueryRows db sq env
|
||||
pure (do
|
||||
a' <- lhs
|
||||
rs <- rows
|
||||
vs <- mapM rowValue rs
|
||||
Right (InList a' (map valueLiteral vs) negated))
|
||||
pure
|
||||
( do
|
||||
a' <- lhs
|
||||
rs <- rows
|
||||
vs <- mapM rowValue rs
|
||||
Right (InList a' (map valueLiteral vs) negated)
|
||||
)
|
||||
ExistsSub sq negated -> do
|
||||
rows <- runSubqueryRows db sq env
|
||||
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))
|
||||
pure
|
||||
( do
|
||||
a' <- lhs
|
||||
rs <- rows
|
||||
vs <- mapM rowValue rs
|
||||
Right (quantifiedCompare op a' (map valueLiteral vs) q)
|
||||
)
|
||||
InList a es negated -> do
|
||||
lhs <- substSubqueries db a env
|
||||
es' <- mapM (\x -> substSubqueries db x env) es
|
||||
pure (do
|
||||
a' <- lhs
|
||||
items <- sequence es'
|
||||
Right (InList a' items negated))
|
||||
pure
|
||||
( do
|
||||
a' <- lhs
|
||||
items <- sequence es'
|
||||
Right (InList a' items negated)
|
||||
)
|
||||
Add a b -> bin Add a b
|
||||
Sub a b -> bin Sub a b
|
||||
Mul a b -> bin Mul a b
|
||||
@@ -510,7 +537,7 @@ substSubqueries db e env = case e of
|
||||
MaxOf a -> un MaxOf a
|
||||
_ -> pure (Right e)
|
||||
where
|
||||
-- | 递归处理两个子表达式
|
||||
-- \| 递归处理两个子表达式
|
||||
bin ctor a b = do
|
||||
ra <- substSubqueries db a env
|
||||
case ra of
|
||||
@@ -518,7 +545,7 @@ substSubqueries db e env = case e of
|
||||
Right a' -> do
|
||||
rb <- substSubqueries db b env
|
||||
pure (ctor a' <$> rb)
|
||||
-- | 递归处理一个子表达式
|
||||
-- \| 递归处理一个子表达式
|
||||
un ctor a = do
|
||||
ra <- substSubqueries db a env
|
||||
pure (ctor <$> ra)
|
||||
@@ -531,14 +558,16 @@ runSubqueryRows db sq outer = case translate (subqueryStatement sq) of
|
||||
Left err -> pure (Left err)
|
||||
Right bindings -> case bindRelOpColumns db (map fst (bindings ++ outer)) (optimize db op) of
|
||||
Left err -> pure (Left err)
|
||||
Right bound -> materialize (show ("subquery", subqueryStatement sq, bindings, fallback))
|
||||
(evalRelOpIn db (bindings ++ outer) bound)
|
||||
Right bound ->
|
||||
materialize
|
||||
(show ("subquery", subqueryStatement sq, bindings, fallback))
|
||||
(evalRelOpIn db (bindings ++ outer) bound)
|
||||
where
|
||||
-- | 原始调用未证明依赖完整,保留整个外层行
|
||||
-- \| 原始调用未证明依赖完整,保留整个外层行
|
||||
fallback = case sq of
|
||||
Subquery{} -> Just outer
|
||||
BoundSubquery{} -> Nothing
|
||||
-- | 给外层物理列补回已解析的限定名
|
||||
-- \| 给外层物理列补回已解析的限定名
|
||||
bind (column, expression) = (,) column <$> bindingReferenceValue expression outer
|
||||
|
||||
-- | 单子求值:Scan/点查/范围查都问存储
|
||||
@@ -567,7 +596,7 @@ evalRelOpIn db outer (Compute items op) = do
|
||||
pure (table >>= \t -> traverse (computeBound t) rs)
|
||||
where
|
||||
refs = nub (concatMap (subquerySlotsIn . 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)
|
||||
@@ -603,7 +632,7 @@ evalRelOpIn db outer (Filter e op) = do
|
||||
pure (table >>= \t -> filterM (keepBound t) rs)
|
||||
where
|
||||
refs = nub (subquerySlotsIn e)
|
||||
-- | 用这一行绑定换好的条件判定
|
||||
-- \| 用这一行绑定换好的条件判定
|
||||
keepBound table r = do
|
||||
e' <- boundExpr refs (r ++ outer) table
|
||||
evalCondForRow e' (r ++ outer)
|
||||
@@ -631,7 +660,7 @@ evalRelOpIn db outer (SemiJoin kind left right l r query) = do
|
||||
Left err -> pure (Left err)
|
||||
Right [] -> pure (Right [])
|
||||
Right leftRows -> do
|
||||
-- | 独立 IN 沿用子查询身份,跨算子复用结果
|
||||
-- \| 独立 IN 沿用子查询身份,跨算子复用结果
|
||||
let key = case query of
|
||||
Just statement -> show ("subquery", statement, [] :: Row, Nothing :: Maybe Row)
|
||||
Nothing -> show ("derived", r, outer)
|
||||
@@ -684,14 +713,14 @@ scanTable db tbl cols = do
|
||||
then lift (scanShards width tbl cols)
|
||||
else lift (maybe (scan tbl) (scanColumns tbl) cols)
|
||||
where
|
||||
-- | 统计里的行数,没有统计算 0
|
||||
-- \| 统计里的行数,没有统计算 0
|
||||
rows = maybe 0 metaRowCount (tableMetaOf db tbl)
|
||||
|
||||
-- | 按输出值去重并保留首次行序
|
||||
distinctRows :: [Row] -> [Row]
|
||||
distinctRows = go HS.empty
|
||||
where
|
||||
-- | 只输出尚未遇到的值组合
|
||||
-- \| 只输出尚未遇到的值组合
|
||||
go _ [] = []
|
||||
go seen (row : rows)
|
||||
| HS.member key seen = go seen rows
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module ChuSQL.Core.Engine.Algebra.Expr (evalExpr, evalCondForRow, colsInExpr, subqueryRefsIn, subquerySlotsIn, subqueryColumns, aggregatesIn, hasAggregate, bareColumns, hasSubquery, hasDivision, inValues, threeValuedNot, quantifiedCompare) where
|
||||
|
||||
import ChuSQL.Core.Model
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Engine.Builtin (Operator (..), compareNode, executeOperator, inValues, threeValuedNot)
|
||||
import ChuSQL.Core.Engine.Runtime.SQL (invokeSQLFunction, constructSQLValue)
|
||||
import ChuSQL.Core.Engine.Runtime.SQL (constructSQLValue, invokeSQLFunction)
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Model
|
||||
|
||||
-- 表达式求值:三值逻辑,NULL 参与运算结果仍是 NULL。
|
||||
|
||||
@@ -59,9 +59,9 @@ evalExpr (InList a es negated) row = do
|
||||
r <- inValues v vs
|
||||
Right (if negated then threeValuedNot r else r)
|
||||
evalExpr (ScalarSub _) _ = Left subqueryNeedsDatabase
|
||||
evalExpr (InSub {}) _ = Left subqueryNeedsDatabase
|
||||
evalExpr (InSub{}) _ = Left subqueryNeedsDatabase
|
||||
evalExpr (ExistsSub _ _) _ = Left subqueryNeedsDatabase
|
||||
evalExpr (QuantCmp {}) _ = Left subqueryNeedsDatabase
|
||||
evalExpr (QuantCmp{}) _ = Left subqueryNeedsDatabase
|
||||
evalExpr agg _ = Left ("aggregate functions are not allowed here: " ++ show agg)
|
||||
|
||||
-- | 子查询要连上存储才能跑
|
||||
@@ -157,9 +157,9 @@ subqueryReferences references e = case e of
|
||||
MaxOf a -> collect a
|
||||
_ -> []
|
||||
where
|
||||
-- | 递归收集当前提取器的引用
|
||||
-- \| 递归收集当前提取器的引用
|
||||
collect = subqueryReferences references
|
||||
-- | 两个子表达式的外层列拼起来
|
||||
-- \| 两个子表达式的外层列拼起来
|
||||
both a b = collect a ++ collect b
|
||||
|
||||
-- | 表达式里有没有除法(除法可能报错,不能提前或延后求值)
|
||||
@@ -185,7 +185,7 @@ hasDivision e = case e of
|
||||
QuantCmp _ a _ _ -> hasDivision a
|
||||
_ -> False
|
||||
where
|
||||
-- | 两个子表达式里任意一个有除法
|
||||
-- \| 两个子表达式里任意一个有除法
|
||||
both a b = hasDivision a || hasDivision b
|
||||
|
||||
-- | 表达式里有没有子查询
|
||||
@@ -196,10 +196,10 @@ hasSubquery e = case e of
|
||||
FunctionCall _ arguments -> any hasSubquery arguments
|
||||
BoundFunction _ _ arguments -> any hasSubquery arguments
|
||||
ScalarSub _ -> True
|
||||
InSub {} -> True
|
||||
InSub{} -> True
|
||||
InList a es _ -> any hasSubquery (a : es)
|
||||
ExistsSub _ _ -> True
|
||||
QuantCmp {} -> True
|
||||
QuantCmp{} -> True
|
||||
Add a b -> both a b
|
||||
Sub a b -> both a b
|
||||
Mul a b -> both a b
|
||||
@@ -222,7 +222,7 @@ hasSubquery e = case e of
|
||||
MaxOf a -> hasSubquery a
|
||||
_ -> False
|
||||
where
|
||||
-- | 两个子表达式里任意一个有
|
||||
-- \| 两个子表达式里任意一个有
|
||||
both a b = hasSubquery a || hasSubquery b
|
||||
|
||||
-- | 表达式里出现的聚合调用(按出现顺序)
|
||||
@@ -258,7 +258,7 @@ aggregatesIn e = case e of
|
||||
IsNotNull a -> aggregatesIn a
|
||||
_ -> []
|
||||
where
|
||||
-- | 两个子表达式的聚合拼起来
|
||||
-- \| 两个子表达式的聚合拼起来
|
||||
both a b = aggregatesIn a ++ aggregatesIn b
|
||||
|
||||
-- | 表达式里有没有聚合调用
|
||||
@@ -297,7 +297,7 @@ bareColumns e = case e of
|
||||
QuantCmp _ a sq _ -> bareColumns a ++ subqueryRefs sq
|
||||
_ -> []
|
||||
where
|
||||
-- | 两个子表达式的裸列拼起来
|
||||
-- \| 两个子表达式的裸列拼起来
|
||||
both a b = bareColumns a ++ bareColumns b
|
||||
|
||||
-- | 判断条件真假:只有 TRUE 通过,NULL 当作不通过
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
module ChuSQL.Core.Engine.Algebra.JoinOrder (reorderJoins) where
|
||||
|
||||
import Data.Maybe (fromMaybe)
|
||||
import ChuSQL.Core.Engine.Algebra.Cost (pointRows, rangeRows, selectivityOf, tableMetaOf)
|
||||
import ChuSQL.Core.Engine.Algebra.Expr (colsInExpr, hasDivision, hasSubquery)
|
||||
import ChuSQL.Core.Engine.Algebra.Op (RelOp (..), relOpCols)
|
||||
import ChuSQL.Core.Engine.Syntax.AST (Expr (..), JoinKind (..))
|
||||
import ChuSQL.Core.Model
|
||||
import Data.List (partition)
|
||||
import Data.Maybe (fromMaybe)
|
||||
|
||||
-- 内连接链顺序优化:按统计估算中间结果,只在行序不可观察处重排。
|
||||
|
||||
@@ -40,7 +40,7 @@ innerChain (Join InnerJoin l r c) = do
|
||||
(ls, lcs) <- innerChain l
|
||||
(rs, rcs) <- innerChain r
|
||||
pure (ls ++ rs, lcs ++ rcs ++ [c])
|
||||
innerChain (Join {}) = Nothing
|
||||
innerChain (Join{}) = Nothing
|
||||
innerChain op = Just ([op], [])
|
||||
|
||||
-- | 叶子算子的估算行数,拿不到统计就是 Nothing
|
||||
@@ -81,7 +81,7 @@ scaleRows db p x rows = case p of
|
||||
GtE _ _ -> quarter
|
||||
LtE _ _ -> quarter
|
||||
NotEq _ _ -> quarter
|
||||
QuantCmp {} -> quarter
|
||||
QuantCmp{} -> quarter
|
||||
And a b -> max 1 (min (scaleRows db a x rows) (scaleRows db b x rows))
|
||||
_ -> quarter
|
||||
where
|
||||
@@ -89,7 +89,8 @@ scaleRows db p x rows = case p of
|
||||
pick k lo hi = case leafSource x of
|
||||
Just (alias, t)
|
||||
| Just m <- tableMetaOf db t
|
||||
, Just s <- selectivityOf m (unqualify alias k) lo hi -> max 1 (round (fromIntegral rows * s))
|
||||
, Just s <- selectivityOf m (unqualify alias k) lo hi ->
|
||||
max 1 (round (fromIntegral rows * s))
|
||||
_ -> quarter
|
||||
|
||||
-- | 叶子对应的别名与表名
|
||||
@@ -103,9 +104,10 @@ leafSource _ = Nothing
|
||||
|
||||
-- | 某列在叶子统计里的不同值数
|
||||
distinctOf :: Database -> RelOp -> String -> Maybe Int
|
||||
distinctOf db leaf col = do (alias, t) <- leafSource leaf
|
||||
m <- tableMetaOf db t
|
||||
lookup (unqualify alias col) [(c, n) | (c, n, _) <- metaDistinct m]
|
||||
distinctOf db leaf col = do
|
||||
(alias, t) <- leafSource leaf
|
||||
m <- tableMetaOf db t
|
||||
lookup (unqualify alias col) [(c, n) | (c, n, _) <- metaDistinct m]
|
||||
|
||||
-- | 若干叶子里某列最大的不同值数
|
||||
distinctOn :: Database -> [RelOp] -> String -> Maybe Int
|
||||
@@ -121,12 +123,12 @@ keyDistinct db have x conds = case [v | Eq (Col a) (Col b) <- conds, Just v <- [
|
||||
where
|
||||
haveCols = concatMap (relOpCols db) have
|
||||
xCols = relOpCols db x
|
||||
-- | 这一对列是不是两侧之间的等值键
|
||||
-- \| 这一对列是不是两侧之间的等值键
|
||||
pair a b
|
||||
| a `elem` haveCols, b `elem` xCols = bigger (distinctOn db have a) (distinctOf db x b)
|
||||
| b `elem` haveCols, a `elem` xCols = bigger (distinctOn db have b) (distinctOf db x a)
|
||||
| otherwise = Nothing
|
||||
-- | 两个可选统计取大的那个
|
||||
-- \| 两个可选统计取大的那个
|
||||
bigger (Just a) (Just b) = Just (max a b)
|
||||
bigger (Just a) Nothing = Just a
|
||||
bigger Nothing (Just b) = Just b
|
||||
@@ -144,7 +146,7 @@ stepRows db have acc x conds = case keyDistinct db have x conds of
|
||||
chainCost :: Database -> [RelOp] -> [Expr] -> Int
|
||||
chainCost db (l : ls) conds = go [l] (fromMaybe 1 (leafRows db l)) ls
|
||||
where
|
||||
-- | 逐步接上后面的叶子
|
||||
-- \| 逐步接上后面的叶子
|
||||
go _ _ [] = 0
|
||||
go have acc (x : xs) =
|
||||
let card = stepRows db have acc x conds
|
||||
@@ -159,11 +161,11 @@ growOrder db leaves conds have rest = case rest of
|
||||
let next = pick rest
|
||||
in growOrder db leaves conds (have ++ [next]) (filter (/= next) rest)
|
||||
where
|
||||
-- | 候选里成本最低的那个,同价取序号小的
|
||||
-- \| 候选里成本最低的那个,同价取序号小的
|
||||
pick (i : is) = foldl cheaper i is
|
||||
pick [] = 0
|
||||
cheaper i j = if costOf j < costOf i then j else i
|
||||
-- | 这个前缀的估算成本
|
||||
-- \| 这个前缀的估算成本
|
||||
costOf i = chainCost db [leaves !! k | k <- have ++ [i]] conds
|
||||
|
||||
-- | 每个起点贪心一遍,取最便宜的顺序
|
||||
@@ -171,13 +173,13 @@ bestOrder :: Database -> [RelOp] -> [Expr] -> [RelOp]
|
||||
bestOrder db leaves conds = pick (map start [0 .. n - 1])
|
||||
where
|
||||
n = length leaves
|
||||
-- | 从第 i 张表开始的贪心顺序
|
||||
-- \| 从第 i 张表开始的贪心顺序
|
||||
start i = [leaves !! k | k <- growOrder db leaves conds [i] (filter (/= i) [0 .. n - 1])]
|
||||
-- | 成本最低的那个
|
||||
-- \| 成本最低的那个
|
||||
pick (o : os) = foldl cheaper o os
|
||||
pick [] = []
|
||||
cheaper o o' = if cost o' < cost o then o' else o
|
||||
-- | 一个顺序的估算成本
|
||||
-- \| 一个顺序的估算成本
|
||||
cost o = chainCost db o conds
|
||||
|
||||
-- | 统计齐全、没有除法且确实更省时给出新顺序
|
||||
@@ -188,7 +190,7 @@ cheaperOrder db leaves conds
|
||||
| bestCost < textCost = Just best
|
||||
| otherwise = Nothing
|
||||
where
|
||||
-- | 这个叶子是不是拿不到统计
|
||||
-- \| 这个叶子是不是拿不到统计
|
||||
unknown x = case leafRows db x of
|
||||
Just _ -> False
|
||||
Nothing -> True
|
||||
@@ -200,13 +202,13 @@ cheaperOrder db leaves conds
|
||||
rebuildChain :: Database -> [RelOp] -> [Expr] -> RelOp
|
||||
rebuildChain db (l : ls) conds = go (relOpCols db l) l ls conds
|
||||
where
|
||||
-- | 逐个接上后面的叶子
|
||||
-- \| 逐个接上后面的叶子
|
||||
go _ acc [] rest = applyConds rest acc
|
||||
go have acc (x : xs) rest =
|
||||
let have' = have ++ relOpCols db x
|
||||
(now, later) = partition (\c -> colsInExpr c `subsetOf` have') rest
|
||||
in go have' (Join InnerJoin acc x (conjOf now)) xs later
|
||||
-- | 若干条件用 AND 串起来
|
||||
-- \| 若干条件用 AND 串起来
|
||||
conjOf [] = LitBool True
|
||||
conjOf (c : cs) = foldl And c cs
|
||||
rebuildChain _ [] _ = Unit
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module ChuSQL.Core.Engine.Algebra.Op (RelOp (..), SemiKind (..), relOpCols, renderPlan) where
|
||||
|
||||
import ChuSQL.Core.Engine.Syntax.AST (Expr, JoinKind (..), SortDir, Statement)
|
||||
import ChuSQL.Core.Model
|
||||
import ChuSQL.Core.Engine.Syntax.AST (Expr, Statement, JoinKind (..), SortDir)
|
||||
|
||||
-- 关系代数算子定义、输出列推导与算子树文本渲染。
|
||||
|
||||
@@ -23,8 +23,8 @@ data RelOp
|
||||
| BoundSort [(Expr, SortDir)] RelOp
|
||||
| Limit Int RelOp
|
||||
| Join JoinKind RelOp RelOp Expr
|
||||
-- | 末项仅保存独立 IN 的物化身份
|
||||
| SemiJoin SemiKind Expr Expr RelOp RelOp (Maybe Statement)
|
||||
| -- | 末项仅保存独立 IN 的物化身份
|
||||
SemiJoin SemiKind Expr Expr RelOp RelOp (Maybe Statement)
|
||||
| Derived (Maybe String) RelOp
|
||||
deriving (Show, Eq)
|
||||
|
||||
@@ -63,7 +63,7 @@ kindLabel LeftJoin = "LeftJoin"
|
||||
renderPlan :: RelOp -> String
|
||||
renderPlan = go 0
|
||||
where
|
||||
-- | 按缩进渲染一个算子
|
||||
-- \| 按缩进渲染一个算子
|
||||
go ind op = case op of
|
||||
Scan a t cols -> pad ++ "Scan " ++ show a ++ " " ++ show t ++ maybe "" ((" columns=" ++) . show) cols
|
||||
Lookup _ t c k -> pad ++ "Lookup " ++ show t ++ "." ++ show c ++ " = " ++ show k
|
||||
@@ -80,15 +80,29 @@ renderPlan = go 0
|
||||
++ "\n"
|
||||
++ go (ind + 1) x
|
||||
Distinct x -> pad ++ "Distinct\n" ++ go (ind + 1) x
|
||||
BoundAggregate keys aggs x -> pad ++ "BoundAggregate keys=" ++ show keys
|
||||
++ " aggs=" ++ show aggs ++ "\n" ++ go (ind + 1) x
|
||||
BoundAggregate keys aggs x ->
|
||||
pad
|
||||
++ "BoundAggregate keys="
|
||||
++ show keys
|
||||
++ " aggs="
|
||||
++ show aggs
|
||||
++ "\n"
|
||||
++ go (ind + 1) x
|
||||
Unit -> pad ++ "Unit"
|
||||
Sort s x -> pad ++ "Sort " ++ show s ++ "\n" ++ go (ind + 1) x
|
||||
BoundSort s x -> pad ++ "BoundSort " ++ 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
|
||||
SemiJoin k left right l r _ -> pad ++ "SemiJoin " ++ show k ++ " keys=" ++ show (left, right)
|
||||
++ "\n" ++ go (ind + 1) l ++ "\n" ++ go (ind + 1) r
|
||||
SemiJoin k left right l r _ ->
|
||||
pad
|
||||
++ "SemiJoin "
|
||||
++ show k
|
||||
++ " keys="
|
||||
++ show (left, right)
|
||||
++ "\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) ' '
|
||||
@@ -5,8 +5,8 @@ import ChuSQL.Core.Engine.Algebra.Expr (colsInExpr, evalExpr, hasDivision)
|
||||
import ChuSQL.Core.Engine.Algebra.JoinOrder (reorderJoins)
|
||||
import ChuSQL.Core.Engine.Algebra.Op (RelOp (..), SemiKind (..), relOpCols)
|
||||
import ChuSQL.Core.Engine.Algebra.Planner (translate)
|
||||
import ChuSQL.Core.Engine.Syntax.AST (Expr (..), FromClause (..), JoinKind (..), Statement (..), Subquery (..))
|
||||
import ChuSQL.Core.Model
|
||||
import ChuSQL.Core.Engine.Syntax.AST (Expr (..), JoinKind (..), Statement (..), FromClause (..), Subquery (..))
|
||||
import Data.List (nub)
|
||||
|
||||
-- 查询优化:谓词下推、投影下推、常量折叠、扫描方式与连接顺序选择。
|
||||
@@ -30,7 +30,7 @@ combineConj (e : es) = Just (foldl And e es)
|
||||
partitionPred :: [String] -> [Expr] -> ([Expr], [Expr])
|
||||
partitionPred have = foldr pick ([], [])
|
||||
where
|
||||
-- | 按列归属收到左边或右边
|
||||
-- \| 按列归属收到左边或右边
|
||||
pick e (yes, no)
|
||||
| colsInExpr e `isSubsetOf` have = (e : yes, no)
|
||||
| otherwise = (yes, e : no)
|
||||
@@ -111,8 +111,13 @@ pushProject db _ (Distinct x) = Distinct (pushProject db (relOpCols db x) x)
|
||||
pushProject db need (Sort spec x) = Sort spec (pushProject db (need ++ map fst spec) x)
|
||||
pushProject db need (Limit n x) = Limit n (pushProject db need x)
|
||||
pushProject db need (SemiJoin kind left right l r query) =
|
||||
SemiJoin kind left right (pushProject db (need ++ colsInExpr left) l)
|
||||
(pushProject db (colsInExpr right) r) query
|
||||
SemiJoin
|
||||
kind
|
||||
left
|
||||
right
|
||||
(pushProject db (need ++ colsInExpr left) l)
|
||||
(pushProject db (colsInExpr right) r)
|
||||
query
|
||||
pushProject db need (Join kind l r c)
|
||||
| needsAll need = Join kind (pushProject db need l) (pushProject db need r) c
|
||||
| otherwise =
|
||||
@@ -128,15 +133,15 @@ pushProject db need leaf@(Scan a t _)
|
||||
| otherwise = Scan a t (Just (nub [column | requested <- need, let column = physicalColumn requested, column `elem` available]))
|
||||
where
|
||||
available = map (unqualify a) (relOpCols db (Scan a t Nothing))
|
||||
-- | 把扫描限定列换成存储物理列名
|
||||
-- \| 把扫描限定列换成存储物理列名
|
||||
physicalColumn column = unqualify (Just (deriveQualifier a t)) (unqualify a column)
|
||||
pushProject db need (Derived a x)
|
||||
| needsAll need || all (`elem` wanted) (relOpCols db x) = Derived a (pushProject db ["*"] x)
|
||||
| otherwise = Derived a (Project wanted (pushProject db wanted x))
|
||||
where
|
||||
wanted = nub (map (unqualify a) need)
|
||||
pushProject db need leaf@(Lookup {}) = pushProjectLeaf db need leaf
|
||||
pushProject db need leaf@(Range {}) = pushProjectLeaf db need leaf
|
||||
pushProject db need leaf@(Lookup{}) = pushProjectLeaf db need leaf
|
||||
pushProject db need leaf@(Range{}) = pushProjectLeaf db need leaf
|
||||
|
||||
-- | 折一个表达式节点
|
||||
foldNode :: Expr -> Expr
|
||||
@@ -186,9 +191,11 @@ worthRange db t col lo hi = case tableMetaOf db t of
|
||||
-- | 将独立 IN 或简单等值 EXISTS 变为半连接
|
||||
semiJoin :: Database -> Expr -> RelOp -> Maybe RelOp
|
||||
semiJoin db (InSub left query negated) source
|
||||
| null (subqueryRefs query), safeKey left
|
||||
| null (subqueryRefs query)
|
||||
, safeKey left
|
||||
, Right right <- translate (subqueryStatement query)
|
||||
, [column] <- relOpCols db right, column /= "*" =
|
||||
, [column] <- relOpCols db right
|
||||
, column /= "*" =
|
||||
Just (SemiJoin (if negated then NotIn else Matching) left (Col column) source right (Just (subqueryStatement query)))
|
||||
semiJoin db (ExistsSub query negated) source
|
||||
| [reference] <- subqueryRefs query
|
||||
@@ -199,8 +206,15 @@ semiJoin db (ExistsSub query negated) source
|
||||
, Just column <- localKey columns reference a b
|
||||
, let physical = if reference `elem` relOpCols db source then reference else reverse (takeWhile (/= '.') (reverse reference))
|
||||
, physical `elem` relOpCols db source =
|
||||
Just (SemiJoin (if negated then NonMatching else Matching) (Col physical) (Col "$semikey") source
|
||||
(Compute (items ++ [("$semikey", Col column)]) scan) Nothing)
|
||||
Just
|
||||
( SemiJoin
|
||||
(if negated then NonMatching else Matching)
|
||||
(Col physical)
|
||||
(Col "$semikey")
|
||||
source
|
||||
(Compute (items ++ [("$semikey", Col column)]) scan)
|
||||
Nothing
|
||||
)
|
||||
semiJoin _ _ _ = Nothing
|
||||
|
||||
-- | 只允许无求值错误的左键
|
||||
@@ -297,7 +311,7 @@ rewriteNode db op = pushOne db op
|
||||
optimizeRelOp :: Database -> RelOp -> RelOp
|
||||
optimizeRelOp db = rewriteNode db . descend
|
||||
where
|
||||
-- | 先递归处理子算子
|
||||
-- \| 先递归处理子算子
|
||||
descend (Filter p x) = Filter p (optimizeRelOp db x)
|
||||
descend (Project cols x) = Project cols (optimizeRelOp db x)
|
||||
descend (Compute items x) = Compute items (optimizeRelOp db x)
|
||||
|
||||
@@ -27,7 +27,7 @@ source fromC mWhere = do
|
||||
nameAggs :: [(String, Expr)] -> [(String, Expr)]
|
||||
nameAggs items = go (1 :: Int) [] (concatMap (aggregatesIn . snd) items)
|
||||
where
|
||||
-- | 逐个聚合起名,重复的不再起名
|
||||
-- \| 逐个聚合起名,重复的不再起名
|
||||
go :: Int -> [(String, Expr)] -> [Expr] -> [(String, Expr)]
|
||||
go _ acc [] = reverse acc
|
||||
go n acc (e : es)
|
||||
@@ -63,7 +63,7 @@ rewriteAggs aggs e = case [label | (label, agg) <- aggs, agg == e] of
|
||||
IsNotNull a -> IsNotNull (go a)
|
||||
other -> other
|
||||
where
|
||||
-- | 递归替换当前查询层的聚合引用
|
||||
-- \| 递归替换当前查询层的聚合引用
|
||||
go = rewriteAggs aggs
|
||||
|
||||
-- | 排序 + 截断
|
||||
@@ -82,10 +82,10 @@ translate q = case q of
|
||||
SelectDistinct inner -> do
|
||||
ordering <- distinctOrdering inner
|
||||
plan <- case inner of
|
||||
s@Select{} -> translate s { selectOrderBy = [], selectLimit = Nothing }
|
||||
s@Select{} -> translate s{selectOrderBy = [], selectLimit = Nothing}
|
||||
_ -> Left "DISTINCT requires SELECT"
|
||||
let labels = case inner of
|
||||
Select { selectItems = items } -> map fst items
|
||||
Select{selectItems = items} -> map fst items
|
||||
_ -> []
|
||||
keys <- mapM (distinctKey labels) ordering
|
||||
namedOrdering <- mapM (columnKey labels) ordering
|
||||
@@ -97,32 +97,38 @@ translate q = case q of
|
||||
Select items fromC mWhere groupBy having orderBy mLimit -> do
|
||||
plan0 <- source fromC mWhere
|
||||
expressions <- mapM (orderExpression items . fst) orderBy
|
||||
let aggs = nameAggs (items ++ [("having", expression) | expression <- maybe [] pure having]
|
||||
++ map ("order",) expressions)
|
||||
let aggs =
|
||||
nameAggs
|
||||
( items
|
||||
++ [("having", expression) | expression <- maybe [] pure having]
|
||||
++ map ("order",) expressions
|
||||
)
|
||||
aggregate = not (null groupBy && null aggs && null having)
|
||||
grouped = if aggregate then Aggregate groupBy aggs plan0 else plan0
|
||||
plan = maybe grouped (\condition -> Filter (rewriteAggs aggs condition) grouped) having
|
||||
items' = [(label, rewriteAggs aggs expression) | (label, expression) <- items]
|
||||
ordering = zip (map (rewriteAggs aggs) expressions) (map snd orderBy)
|
||||
-- | 简单列排序保留可优化的名称算子
|
||||
-- \| 简单列排序保留可优化的名称算子
|
||||
named (Col column, direction) = Just (column, direction)
|
||||
named _ = Nothing
|
||||
-- | 保持普通投影在 LIMIT 之后求值
|
||||
-- \| 保持普通投影在 LIMIT 之后求值
|
||||
projected child
|
||||
| all (\(label, expression) -> expression == Col label) items' = Project (map fst items') child
|
||||
| otherwise = Compute items' child
|
||||
Right (case mapM named ordering of
|
||||
Just spec -> projected (limited spec mLimit plan)
|
||||
Nothing -> orderedProjection items' ordering mLimit plan)
|
||||
Right
|
||||
( case mapM named ordering of
|
||||
Just spec -> projected (limited spec mLimit plan)
|
||||
Nothing -> orderedProjection items' ordering mLimit plan
|
||||
)
|
||||
_ -> Left "only SELECT is supported by translate"
|
||||
where
|
||||
-- | 简单投影的序号映射到来源列
|
||||
-- \| 简单投影的序号映射到来源列
|
||||
columnKey columns (column, direction) = do
|
||||
position <- orderPosition (length columns) column
|
||||
case position of
|
||||
Just slot -> Right (columns !! slot, direction)
|
||||
Nothing -> Left "DISTINCT: sorting requires an output position"
|
||||
-- | 去重后按指定输出位置或唯一名称排序
|
||||
-- \| 去重后按指定输出位置或唯一名称排序
|
||||
distinctKey columns (column, direction) = do
|
||||
position <- orderPosition (length columns) column
|
||||
case position of
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module ChuSQL.Core.Engine.Algebra.Sort (sortRows, sortBoundRows, compareValue) where
|
||||
|
||||
import ChuSQL.Core.Model
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Engine.Algebra.Expr (evalExpr)
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Model
|
||||
import Data.List (sortBy)
|
||||
|
||||
-- 排序:按多列规格排出结果行,每列可升可降。
|
||||
@@ -34,9 +34,9 @@ sortRows :: [(String, SortDir)] -> [Row] -> [Row]
|
||||
sortRows [] rows = rows
|
||||
sortRows spec rows = map snd (sortBy cmp (map decorate rows))
|
||||
where
|
||||
-- | 取键并带上原行
|
||||
-- \| 取键并带上原行
|
||||
decorate row = (sortKey spec row, row)
|
||||
-- | 比较两个带键的行
|
||||
-- \| 比较两个带键的行
|
||||
cmp (k1, _) (k2, _) = compareKeys (map snd spec) k1 k2
|
||||
|
||||
-- | 用绑定槽位取键并稳定排序
|
||||
@@ -47,9 +47,9 @@ sortBoundRows spec outer rows = do
|
||||
Right (map snd (sortBy compareRows decorated))
|
||||
where
|
||||
directions = map snd spec
|
||||
-- | 每行只计算一次绑定排序键
|
||||
-- \| 每行只计算一次绑定排序键
|
||||
decorate row = do
|
||||
keys <- mapM (\(expression, _) -> evalExpr expression (row ++ outer)) spec
|
||||
Right (map Just keys, row)
|
||||
-- | 比较排序键并保留同键行序
|
||||
-- \| 比较排序键并保留同键行序
|
||||
compareRows (left, _) (right, _) = compareKeys directions left right
|
||||
@@ -19,7 +19,8 @@ module ChuSQL.Core.Engine.Builtin (
|
||||
) where
|
||||
|
||||
import ChuSQL.Core.Engine.Syntax.AST (CompareOp (..), Expr (..))
|
||||
import ChuSQL.Core.Model (Value (..), compareValue)
|
||||
import ChuSQL.Core.Model (ColumnType (..), Value (..), compareValue, hasTypeCapability)
|
||||
import ChuSQL.Core.Runtime (TypeCapability (..), builtinTypes, validateValue)
|
||||
import Control.Monad (foldM)
|
||||
import Data.Char (toLower)
|
||||
|
||||
@@ -201,12 +202,12 @@ compareNode CmpLtE = LtE
|
||||
executeOperator :: Operator -> [Value] -> Either String Value
|
||||
executeOperator op vs = case (op, vs) of
|
||||
(OpNeg, [x]) -> evalNeg x
|
||||
(OpGt, [x, y]) -> compareValues (== GT) x y
|
||||
(OpLt, [x, y]) -> compareValues (< EQ) x y
|
||||
(OpEq, [x, y]) -> compareValues (== EQ) x y
|
||||
(OpGtE, [x, y]) -> compareValues (>= EQ) x y
|
||||
(OpLtE, [x, y]) -> compareValues (<= EQ) x y
|
||||
(OpNe, [x, y]) -> compareValues (/= EQ) x y
|
||||
(OpGt, [x, y]) -> compareValues OrdCapability (== GT) x y
|
||||
(OpLt, [x, y]) -> compareValues OrdCapability (< EQ) x y
|
||||
(OpEq, [x, y]) -> compareValues EqCapability (== EQ) x y
|
||||
(OpGtE, [x, y]) -> compareValues OrdCapability (>= EQ) x y
|
||||
(OpLtE, [x, y]) -> compareValues OrdCapability (<= EQ) x y
|
||||
(OpNe, [x, y]) -> compareValues EqCapability (/= EQ) x y
|
||||
(OpAnd, [x, y]) -> andValues x y
|
||||
(OpOr, [x, y]) -> orValues x y
|
||||
(_, [x, y]) | Just a <- arithOf op -> evalArith a x y
|
||||
@@ -262,21 +263,26 @@ asDouble (VFloat d) = Just d
|
||||
asDouble _ = Nothing
|
||||
|
||||
-- | 比较两个值:NULL 给 NULL,其余按全序比
|
||||
compareValues :: (Ordering -> Bool) -> Value -> Value -> Either String Value
|
||||
compareValues rel x y
|
||||
compareValues :: TypeCapability -> (Ordering -> Bool) -> Value -> Value -> Either String Value
|
||||
compareValues capability rel x y
|
||||
| x == VNull || y == VNull = Right VNull
|
||||
| comparableValue x y = Right (VBool (rel (compareValue x y)))
|
||||
| comparableValue capability x y = Right (VBool (rel (compareValue x y)))
|
||||
| otherwise = Left "type error: cannot compare these values"
|
||||
|
||||
-- | 两个值是否落在可以用全序比较的同一族里
|
||||
comparableValue :: Value -> Value -> Bool
|
||||
comparableValue (VInt _) (VFloat _) = True
|
||||
comparableValue (VFloat _) (VInt _) = True
|
||||
comparableValue (VInt _) (VInt _) = True
|
||||
comparableValue (VFloat _) (VFloat _) = True
|
||||
comparableValue (VStr _) (VStr _) = True
|
||||
comparableValue (VBool _) (VBool _) = True
|
||||
comparableValue _ _ = False
|
||||
comparableValue :: TypeCapability -> Value -> Value -> Bool
|
||||
comparableValue _ (VInt _) (VFloat _) = True
|
||||
comparableValue _ (VFloat _) (VInt _) = True
|
||||
comparableValue _ (VInt _) (VInt _) = True
|
||||
comparableValue _ (VFloat _) (VFloat _) = True
|
||||
comparableValue _ (VStr _) (VStr _) = True
|
||||
comparableValue _ (VBool _) (VBool _) = True
|
||||
comparableValue capability (VRuntime left x) (VRuntime right y) =
|
||||
left == right
|
||||
&& hasTypeCapability capability (CRuntime left)
|
||||
&& validateValue builtinTypes left x == Right ()
|
||||
&& validateValue builtinTypes right y == Right ()
|
||||
comparableValue _ _ _ = False
|
||||
|
||||
-- | 逻辑与的三值真值表
|
||||
andValues :: Value -> Value -> Either String Value
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
module ChuSQL.Core.Engine.Domain (
|
||||
domainColumns,
|
||||
domainDefinition,
|
||||
canonicalDomain,
|
||||
effectiveColumns,
|
||||
domainCheckPlan,
|
||||
runDomainChecks,
|
||||
validateDomainDefaults,
|
||||
prepareDomainAlter,
|
||||
) where
|
||||
|
||||
import ChuSQL.Core.Engine.Algebra.Expr (evalExpr)
|
||||
import ChuSQL.Core.Engine.Runtime.Relation (orderRow)
|
||||
import ChuSQL.Core.Engine.Semantic (prepareDomainCheckExpression)
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Engine.Syntax.Parser (parseExpression)
|
||||
import ChuSQL.Core.Model
|
||||
import Control.Applicative ((<|>))
|
||||
import Control.Monad (when)
|
||||
import Data.Char (isAlphaNum, isAscii, toLower)
|
||||
import Data.List (isPrefixOf, nub)
|
||||
import Data.Maybe (fromMaybe)
|
||||
|
||||
-- 领域元数据、约束继承、默认值及结构变更的纯校验。
|
||||
|
||||
-- | 将领域定义编码为目录列
|
||||
domainColumns :: DomainDefinition -> [(String, Column)]
|
||||
domainColumns (DomainDefinition base checks) =
|
||||
("base", base{columnCheck = Nothing})
|
||||
: [("check_" ++ name, (plainColumn CBool){columnCheck = Just text}) | (name, text) <- checks]
|
||||
|
||||
-- | 从新旧目录恢复领域定义
|
||||
domainDefinition :: Table -> Either String DomainDefinition
|
||||
domainDefinition table = do
|
||||
base <- maybe (Left "invalid domain catalog: missing base") Right (lookup "base" (tableCols table))
|
||||
checks <- mapM readCheck [(name, col) | (name, col) <- tableCols table, name /= "base"]
|
||||
let legacy = maybe [] (\text -> [("domain_check_0", text)]) (columnCheck base)
|
||||
Right (DomainDefinition base{columnCheck = Nothing} (legacy ++ checks))
|
||||
where
|
||||
-- \| 恢复一条具名检查条件
|
||||
readCheck (name, col)
|
||||
| "check_" `isPrefixOf` name, Just text <- columnCheck col = Right (drop 6 name, text)
|
||||
| otherwise = Left "invalid domain constraint catalog"
|
||||
|
||||
-- | 为匿名约束命名并验证完整定义
|
||||
canonicalDomain :: Database -> String -> DomainDefinition -> Either String DomainDefinition
|
||||
canonicalDomain db name definition = do
|
||||
normalized <- normalize definition
|
||||
let candidate = install name normalized db
|
||||
validateOwn candidate name
|
||||
Right normalized
|
||||
where
|
||||
-- \| 规范化约束名称并拒绝重复声明
|
||||
normalize (DomainDefinition base checks) = do
|
||||
when
|
||||
(columnAutoIncrement base || columnUnique base || columnPrimaryKey base)
|
||||
(Left "DOMAIN does not support row-set constraints")
|
||||
let named = [map toLower key | (key, _) <- checks, not (null key)]
|
||||
when (length named /= length (nub named)) (Left "duplicate domain constraint name")
|
||||
mapM_ validName named
|
||||
assigned <- assign named 1 checks
|
||||
Right (DomainDefinition base assigned)
|
||||
-- \| 验证约束标识符
|
||||
validName key
|
||||
| null key || length key > 48 || not (all (\c -> isAscii c && (isAlphaNum c || c == '_')) key) =
|
||||
Left "invalid domain constraint name"
|
||||
| otherwise = Right ()
|
||||
-- \| 为未命名约束分配稳定名称
|
||||
assign _ _ [] = Right []
|
||||
assign used index ((key, text) : rest)
|
||||
| null key =
|
||||
let (chosen, next) = fresh used index
|
||||
in ((chosen, text) :) <$> assign (chosen : used) next rest
|
||||
| otherwise = ((map toLower key, text) :) <$> assign used index rest
|
||||
-- \| 跳过已有的自动约束名称
|
||||
fresh used index
|
||||
| chosen `elem` used = fresh used (index + 1)
|
||||
| otherwise = (chosen, index + 1)
|
||||
where
|
||||
chosen = "domain_check_" ++ show (index :: Int)
|
||||
|
||||
-- | 在纯目录中安装候选领域定义
|
||||
install :: String -> DomainDefinition -> Database -> Database
|
||||
install name definition db = (key, table{tableCols = domainColumns definition}) : filter ((/= key) . fst) db
|
||||
where
|
||||
key = domainTableName name
|
||||
table = fromMaybe (Table key [] [] Nothing Nothing Nothing) (lookup key db)
|
||||
|
||||
-- | 沿目录解析领域链并拒绝循环
|
||||
chain :: Database -> ColumnType -> Either String [(String, DomainDefinition)]
|
||||
chain db = go []
|
||||
where
|
||||
-- \| 逐层读取当前定义而非缓存属性
|
||||
go seen (CDomain name _)
|
||||
| name `elem` seen = Left ("cyclic domain definition: " ++ name)
|
||||
| otherwise = do
|
||||
table <- maybe (Left ("unknown domain: " ++ name)) Right (lookup (domainTableName name) db)
|
||||
definition@(DomainDefinition base _) <- domainDefinition table
|
||||
rest <- go (name : seen) (columnType base)
|
||||
Right ((name, definition) : rest)
|
||||
go _ _ = Right []
|
||||
|
||||
-- | 合并列声明与继承的非空和默认值
|
||||
effectiveColumns :: Database -> [(String, Column)] -> Either String [(String, Column)]
|
||||
effectiveColumns db = mapM effective
|
||||
where
|
||||
-- \| 保留最近默认值并收紧可空属性
|
||||
effective (name, column) = do
|
||||
definitions <- chain db (columnType column)
|
||||
let bases = [base | (_, DomainDefinition base _) <- definitions]
|
||||
Right
|
||||
( name
|
||||
, column
|
||||
{ columnNullable = columnNullable column && all columnNullable bases
|
||||
, columnDefault = foldl (<|>) (columnDefault column) (map columnDefault bases)
|
||||
}
|
||||
)
|
||||
|
||||
-- | 绑定领域 VALUE 到独立的单值布局
|
||||
domainCheckPlan :: Database -> [(String, Column)] -> Either String [(String, Int, Expr)]
|
||||
domainCheckPlan db columns = concat <$> mapM checks (zip [0 ..] columns)
|
||||
where
|
||||
-- \| 按基础领域到外层领域绑定条件
|
||||
checks (slot, (_, column)) = do
|
||||
definitions <- chain db (columnType column)
|
||||
mapM
|
||||
(bind slot column)
|
||||
[ (name ++ "." ++ key, text)
|
||||
| (name, DomainDefinition _ constraints) <- reverse definitions
|
||||
, (key, text) <- constraints
|
||||
]
|
||||
-- \| 拒绝聚合及子查询并固定类型槽位
|
||||
bind slot column (name, text) = do
|
||||
expression <- parseExpression text
|
||||
bound <- prepareDomainCheckExpression (columnType column) expression
|
||||
Right (name, slot, bound)
|
||||
|
||||
-- | SQL CHECK 的 UNKNOWN 与 TRUE 都通过
|
||||
runDomainChecks :: [(String, Int, Expr)] -> Row -> Either String ()
|
||||
runDomainChecks plan row = mapM_ check plan
|
||||
where
|
||||
-- \| 在单值作用域执行一条领域约束
|
||||
check (name, slot, expression) = case drop slot row of
|
||||
(_, value) : _ -> do
|
||||
result <- evalExpr expression [("value", value)]
|
||||
case result of
|
||||
VBool True -> Right ()
|
||||
VNull -> Right ()
|
||||
VBool False -> Left ("CHECK on DOMAIN " ++ name ++ " failed")
|
||||
_ -> Left ("CHECK on DOMAIN " ++ name ++ " is not boolean")
|
||||
[] -> Left "missing bound domain value"
|
||||
|
||||
-- | 检查有效默认值是否满足领域约束
|
||||
validateDomainDefaults :: Database -> [(String, Column)] -> Either String ()
|
||||
validateDomainDefaults db columns = do
|
||||
effective <- effectiveColumns db columns
|
||||
mapM_ validate [entry | entry@(_, column) <- effective, CDomain{} <- [columnType column]]
|
||||
where
|
||||
-- \| 默认值不改变已有行但必须可赋值
|
||||
validate entry@(name, column) = case columnDefault column of
|
||||
Nothing -> Right ()
|
||||
Just value -> do
|
||||
coerced <- coerceValue (columnType column) value
|
||||
checkNull name column coerced
|
||||
plan <- domainCheckPlan db [entry]
|
||||
runDomainChecks plan [(name, coerced)]
|
||||
|
||||
-- | 拒绝领域或列要求非空时的 NULL
|
||||
checkNull :: String -> Column -> Value -> Either String ()
|
||||
checkNull name column value
|
||||
| value == VNull && not (columnNullable column) = Left ("DOMAIN NOT NULL: " ++ name)
|
||||
| otherwise = Right ()
|
||||
|
||||
-- | 检查领域本身的定义及继承默认值
|
||||
validateOwn :: Database -> String -> Either String ()
|
||||
validateOwn db name = do
|
||||
table <- maybe (Left ("unknown domain: " ++ name)) Right (lookup (domainTableName name) db)
|
||||
DomainDefinition base _ <- domainDefinition table
|
||||
let columns = [("value", plainColumn (CDomain name (columnType base)))]
|
||||
_ <- domainCheckPlan db columns
|
||||
validateDomainDefaults db columns
|
||||
|
||||
-- | 在完整快照上校验并生成领域替换定义
|
||||
prepareDomainAlter :: String -> DomainAlter -> Database -> Either String [(String, Column)]
|
||||
prepareDomainAlter rawName action db = do
|
||||
table <- maybe (Left ("unknown domain: " ++ name)) Right (lookup (domainTableName name) db)
|
||||
definition <- domainDefinition table >>= change
|
||||
normalized <- canonicalDomain db name definition
|
||||
let candidate = install name normalized db
|
||||
mapM_ (validateDependent candidate) [key | (key, _) <- candidate, isDomainTable key]
|
||||
mapM_ (validateTable candidate) [(key, value) | (key, value) <- candidate, not (isDomainTable key)]
|
||||
Right (domainColumns normalized)
|
||||
where
|
||||
name = map toLower rawName
|
||||
-- \| 修改单个属性且保留其他约束
|
||||
change (DomainDefinition base checks) = case action of
|
||||
DomainDefault value -> Right (DomainDefinition base{columnDefault = value} checks)
|
||||
DomainNullable nullable -> Right (DomainDefinition base{columnNullable = nullable} checks)
|
||||
DomainAddCheck key text -> Right (DomainDefinition base (checks ++ [(fromMaybe "" key, text)]))
|
||||
DomainDropCheck key
|
||||
| map toLower key `elem` map fst checks -> Right (DomainDefinition base (filter ((/= map toLower key) . fst) checks))
|
||||
| otherwise -> Left ("unknown domain constraint: " ++ key)
|
||||
-- \| 判断传递引用是否包含目标领域
|
||||
affected candidate ty = any ((== name) . fst) <$> chain candidate ty
|
||||
-- \| 校验依赖领域的继承默认值
|
||||
validateDependent candidate key = do
|
||||
let domain = drop (length (domainTableName "")) key
|
||||
applies <- affected candidate (CDomain domain CStr)
|
||||
when applies (validateOwn candidate domain)
|
||||
-- \| 只校验受领域变化影响的列与数据
|
||||
validateTable candidate (_, table) = do
|
||||
selected <-
|
||||
mapM
|
||||
( \entry@(_, column) -> do
|
||||
applies <- affected candidate (columnType column)
|
||||
Right [entry | applies]
|
||||
)
|
||||
(tableCols table)
|
||||
let columns = concat selected
|
||||
effective <- effectiveColumns candidate columns
|
||||
validateDomainDefaults candidate columns
|
||||
plan <- domainCheckPlan candidate columns
|
||||
mapM_ (validateRow effective plan) (tableRows table)
|
||||
-- \| 按列布局检查非空与全部继承条件
|
||||
validateRow columns plan row = do
|
||||
selected <- orderRow (map fst columns) row
|
||||
values <-
|
||||
mapM
|
||||
( \((key, column), (_, value)) -> do
|
||||
coerced <- coerceValue (columnType column) value
|
||||
checkNull key column coerced
|
||||
Right (key, coerced)
|
||||
)
|
||||
(zip columns selected)
|
||||
runDomainChecks plan values
|
||||
@@ -33,8 +33,12 @@ data ErrorRule = ErrorRule
|
||||
-- | 唯一的错误识别表,顺序即优先级
|
||||
errorRules :: [ErrorRule]
|
||||
errorRules =
|
||||
[ ErrorRule "no database selected" NoDatabaseError "no_database"
|
||||
[ ErrorRule "deadlock_detected:" StorageError "deadlock_detected"
|
||||
, ErrorRule "lock_timeout:" StorageError "lock_timeout"
|
||||
, ErrorRule "serialization_failure:" CatalogError "serialization_failure"
|
||||
, ErrorRule "no database selected" NoDatabaseError "no_database"
|
||||
, ErrorRule "administrator required" PermissionError "forbidden"
|
||||
, ErrorRule "serialization_failure: domain snapshot changed" CatalogError "serialization_failure"
|
||||
, ErrorRule "bound query object changed" CatalogError "serialization_failure"
|
||||
, ErrorRule "bound query schema changed" CatalogError "serialization_failure"
|
||||
, ErrorRule "unknown table" CatalogError "not_found"
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
module ChuSQL.Core.Engine.Runtime.Binding
|
||||
( Expression (..), TypedExpression, expressionType, bindExpression, evaluateExpression
|
||||
, Plan (..), TypedPlan, planSchema, bindPlan, executePlan
|
||||
) where
|
||||
module ChuSQL.Core.Engine.Runtime.Binding (
|
||||
Expression (..),
|
||||
TypedExpression,
|
||||
expressionType,
|
||||
bindExpression,
|
||||
evaluateExpression,
|
||||
Plan (..),
|
||||
TypedPlan,
|
||||
planSchema,
|
||||
bindPlan,
|
||||
executePlan,
|
||||
) where
|
||||
|
||||
import ChuSQL.Core.Engine.Runtime.Types
|
||||
import ChuSQL.Core.Engine.Runtime.Functions
|
||||
import ChuSQL.Core.Engine.Runtime.Types
|
||||
import Control.Monad (filterM)
|
||||
import Data.List (nub, sort)
|
||||
|
||||
-- 仅依据 schema 绑定表达式与查询,执行已解析的类型和函数身份。
|
||||
|
||||
-- | 表达未绑定的类型化语言
|
||||
data Expression = Literal TypeExpr RuntimeValue | Reference String | Apply String [Expression]
|
||||
| ListLiteral TypeExpr [Expression] | NothingLiteral TypeExpr | JustLiteral Expression
|
||||
| TupleLiteral [Expression] deriving (Show, Eq)
|
||||
data Expression
|
||||
= Literal TypeExpr RuntimeValue
|
||||
| Reference String
|
||||
| Apply String [Expression]
|
||||
| ListLiteral TypeExpr [Expression]
|
||||
| NothingLiteral TypeExpr
|
||||
| JustLiteral Expression
|
||||
| TupleLiteral [Expression]
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | 保存绑定后的表达式节点
|
||||
data ExpressionNode = Constant RuntimeValue | BoundReference Int String | BoundApply FunctionId [TypedExpression]
|
||||
| BoundList [TypedExpression] | BoundNothing | BoundJust TypedExpression | BoundTuple [TypedExpression]
|
||||
data ExpressionNode
|
||||
= Constant RuntimeValue
|
||||
| BoundReference Int String
|
||||
| BoundApply FunctionId [TypedExpression]
|
||||
| BoundList [TypedExpression]
|
||||
| BoundNothing
|
||||
| BoundJust TypedExpression
|
||||
| BoundTuple [TypedExpression]
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | 保存表达式的确定类型
|
||||
@@ -56,9 +76,9 @@ bindExpression types functions env expression = case expression of
|
||||
bound <- mapM bind arguments
|
||||
compound (tupleType (map (typeExpression . expressionType) bound)) (BoundTuple bound)
|
||||
where
|
||||
-- | 绑定当前作用域的子表达式
|
||||
-- \| 绑定当前作用域的子表达式
|
||||
bind = bindExpression types functions env
|
||||
-- | 校验并构造复合表达式
|
||||
-- \| 校验并构造复合表达式
|
||||
compound ty node = do
|
||||
tid <- descriptorId <$> resolveType types ty
|
||||
Right (TypedExpression tid node)
|
||||
@@ -80,16 +100,24 @@ evaluateExpression types functions row (TypedExpression tid node) = do
|
||||
validateValue types tid value
|
||||
Right value
|
||||
where
|
||||
-- | 执行当前行的子表达式
|
||||
-- \| 执行当前行的子表达式
|
||||
evaluate = evaluateExpression types functions row
|
||||
|
||||
-- | 表达未绑定的关系计划
|
||||
data Plan = TableScan String | SingleRow | Projection [(String, Expression)] Plan
|
||||
| Selection Expression Plan deriving (Show, Eq)
|
||||
data Plan
|
||||
= TableScan String
|
||||
| SingleRow
|
||||
| Projection [(String, Expression)] Plan
|
||||
| Selection Expression Plan
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | 保存绑定后的关系节点
|
||||
data PlanNode = BoundScan String | BoundSingleRow | BoundProjection [(String, TypedExpression)] TypedPlan
|
||||
| BoundSelection TypedExpression TypedPlan deriving (Show, Eq)
|
||||
data PlanNode
|
||||
= BoundScan String
|
||||
| BoundSingleRow
|
||||
| BoundProjection [(String, TypedExpression)] TypedPlan
|
||||
| BoundSelection TypedExpression TypedPlan
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | 保存输出 schema 和关系节点
|
||||
data TypedPlan = TypedPlan [(String, TypeId)] PlanNode deriving (Show, Eq)
|
||||
@@ -122,13 +150,13 @@ bindPlan types functions catalog plan = case plan of
|
||||
then Right (TypedPlan (planSchema child) (BoundSelection predicate child))
|
||||
else Left "selection predicate must be Bool"
|
||||
where
|
||||
-- | 绑定子计划
|
||||
-- \| 绑定子计划
|
||||
bind = bindPlan types functions catalog
|
||||
-- | 解析 schema 列的类型身份
|
||||
-- \| 解析 schema 列的类型身份
|
||||
bindColumn (name, ty) = do
|
||||
tid <- descriptorId <$> resolveType types ty
|
||||
Right (name, tid)
|
||||
-- | 绑定投影项
|
||||
-- \| 绑定投影项
|
||||
bindItem env (name, value) = do
|
||||
bound <- bindExpression types functions env value
|
||||
Right (name, bound)
|
||||
@@ -140,8 +168,12 @@ uniqueNames names
|
||||
| otherwise = Left "duplicate schema column"
|
||||
|
||||
-- | 执行绑定计划并校验存储返回值
|
||||
executePlan :: TypeRegistry -> FunctionRegistry -> (String -> Either String [[(String, RuntimeValue)]])
|
||||
-> TypedPlan -> Either String [[(String, RuntimeValue)]]
|
||||
executePlan ::
|
||||
TypeRegistry ->
|
||||
FunctionRegistry ->
|
||||
(String -> Either String [[(String, RuntimeValue)]]) ->
|
||||
TypedPlan ->
|
||||
Either String [[(String, RuntimeValue)]]
|
||||
executePlan types functions scan (TypedPlan output node) = do
|
||||
rows <- case node of
|
||||
BoundScan name -> scan name
|
||||
@@ -155,23 +187,23 @@ executePlan types functions scan (TypedPlan output node) = do
|
||||
mapM_ (validateRow output) rows
|
||||
Right [[(name, value) | (name, _) <- output, Just value <- [lookup name row]] | row <- rows]
|
||||
where
|
||||
-- | 计算一行的投影项
|
||||
-- \| 计算一行的投影项
|
||||
project items row = mapM (projectItem row) items
|
||||
-- | 计算命名输出项
|
||||
-- \| 计算命名输出项
|
||||
projectItem row (name, expression) = do
|
||||
value <- evaluateExpression types functions row expression
|
||||
Right (name, value)
|
||||
-- | 计算选择条件
|
||||
-- \| 计算选择条件
|
||||
keep condition row = do
|
||||
value <- evaluateExpression types functions row condition
|
||||
case value of
|
||||
RBool result -> Right result
|
||||
_ -> Left "bound predicate returned a non-Bool value"
|
||||
-- | 校验存储行的完整 schema
|
||||
-- \| 校验存储行的完整 schema
|
||||
validateRow columns row
|
||||
| sort (map fst row) /= sort (map fst columns) = Left "storage row schema mismatch"
|
||||
| otherwise = mapM_ (validateColumn row) columns
|
||||
-- | 按列名校验返回值的类型
|
||||
-- \| 按列名校验返回值的类型
|
||||
validateColumn row (name, tid) = case lookup name row of
|
||||
Just value -> validateValue types tid value
|
||||
Nothing -> Left ("missing bound column: " ++ name)
|
||||
@@ -1,7 +1,7 @@
|
||||
module ChuSQL.Core.Engine.Runtime.Dependencies (queryTables, domainDependencies, typeDependencies, validateDependencies) where
|
||||
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Model (Database, ColumnType (..), columnType, tableCols, tableIdentity, tableSchemaVersion, domainTableName, lastTablePart)
|
||||
import ChuSQL.Core.Model (ColumnType (..), Database, columnType, domainTableName, lastTablePart, tableCols, tableIdentity, tableSchemaVersion)
|
||||
import Data.List (nub)
|
||||
|
||||
-- 收集读写语句及嵌套表达式的表依赖,供执行期校验目录。
|
||||
@@ -10,7 +10,7 @@ import Data.List (nub)
|
||||
validateDependencies :: Database -> Database -> Either String ()
|
||||
validateDependencies expected current = mapM_ validate expected
|
||||
where
|
||||
-- | 数据和统计变化不使结构绑定失效
|
||||
-- \| 数据和统计变化不使结构绑定失效
|
||||
validate (name, table) = case lookup name current of
|
||||
Just actual | tableIdentity actual /= tableIdentity table -> Left ("bound query object changed: " ++ name)
|
||||
Just actual | tableSchemaVersion actual /= tableSchemaVersion table -> Left ("bound query schema changed: " ++ name)
|
||||
@@ -19,47 +19,67 @@ validateDependencies expected current = mapM_ validate expected
|
||||
|
||||
-- | 收集读写目标、来源及子查询依赖
|
||||
queryTables :: Statement -> [String]
|
||||
queryTables statement = nub (case statement of
|
||||
SelectDistinct child -> queryTables child
|
||||
Select items source condition _ having ordering _ -> sourceTables source
|
||||
++ concatMap (expressionTables . snd) items ++ concatMap expressionTables condition
|
||||
++ concatMap expressionTables having
|
||||
++ concatMap (maybe [] expressionTables . orderKeyExpression . fst) ordering
|
||||
Insert target _ rows -> target : concatMap (concatMap expressionTables) rows
|
||||
Update target assignments condition -> target : concatMap (expressionTables . snd) assignments
|
||||
++ concatMap expressionTables condition
|
||||
Delete target condition -> target : concatMap expressionTables condition
|
||||
DropTable target -> [target]
|
||||
CreateIndex target _ -> [target]
|
||||
DropIndex target _ -> [target]
|
||||
DropColumn target _ -> [target]
|
||||
AddColumn target _ -> [target]
|
||||
RenameColumn target _ _ -> [target]
|
||||
AlterColumnType target _ _ -> [target]
|
||||
AlterColumnDefault target _ _ -> [target]
|
||||
AlterColumnNull target _ _ -> [target]
|
||||
_ -> [])
|
||||
queryTables statement =
|
||||
nub
|
||||
( case statement of
|
||||
SelectDistinct child -> queryTables child
|
||||
Select items source condition _ having ordering _ ->
|
||||
sourceTables source
|
||||
++ concatMap (expressionTables . snd) items
|
||||
++ concatMap expressionTables condition
|
||||
++ concatMap expressionTables having
|
||||
++ concatMap (maybe [] expressionTables . orderKeyExpression . fst) ordering
|
||||
Insert target _ rows -> target : concatMap (concatMap expressionTables) rows
|
||||
Update target assignments condition ->
|
||||
target
|
||||
: concatMap (expressionTables . snd) assignments
|
||||
++ concatMap expressionTables condition
|
||||
Delete target condition -> target : concatMap expressionTables condition
|
||||
DropTable target -> [target]
|
||||
CreateIndex target _ -> [target]
|
||||
DropIndex target _ -> [target]
|
||||
DropColumn target _ -> [target]
|
||||
AddColumn target _ -> [target]
|
||||
RenameColumn target _ _ -> [target]
|
||||
AlterColumnType target _ _ -> [target]
|
||||
AlterColumnDefault target _ _ -> [target]
|
||||
AlterColumnNull target _ _ -> [target]
|
||||
_ -> []
|
||||
)
|
||||
|
||||
-- | 收集来源列依赖的领域定义
|
||||
domainDependencies :: Database -> [String] -> [String]
|
||||
domainDependencies catalog sources = typeDependencies catalog
|
||||
[columnType column | (name, table) <- catalog, name `elem` sources
|
||||
, (_, column) <- tableCols table]
|
||||
domainDependencies catalog sources =
|
||||
typeDependencies
|
||||
catalog
|
||||
[ columnType column
|
||||
| (name, table) <- catalog
|
||||
, name `elem` sources
|
||||
, (_, column) <- tableCols table
|
||||
]
|
||||
|
||||
-- | 收集声明类型依赖的领域目录
|
||||
typeDependencies :: Database -> [ColumnType] -> [String]
|
||||
typeDependencies catalog types = collect [] (concatMap typeDomains types)
|
||||
where
|
||||
-- | 递归展开定义并防止重复访问
|
||||
-- \| 递归展开定义并防止重复访问
|
||||
collect seen [] = seen
|
||||
collect seen (domain : remaining) =
|
||||
let definitions = [(name, table) | (name, table) <- catalog
|
||||
, lastTablePart name == domainTableName domain, name `notElem` seen]
|
||||
let definitions =
|
||||
[ (name, table)
|
||||
| (name, table) <- catalog
|
||||
, lastTablePart name == domainTableName domain
|
||||
, name `notElem` seen
|
||||
]
|
||||
names = map fst definitions
|
||||
nested = [child | (_, table) <- definitions, (_, column) <- tableCols table
|
||||
, child <- typeDomains (columnType column)]
|
||||
in collect (seen ++ names) (remaining ++ nested)
|
||||
-- | 保留嵌套领域的每层依赖
|
||||
nested =
|
||||
[ child
|
||||
| (_, table) <- definitions
|
||||
, (_, column) <- tableCols table
|
||||
, child <- typeDomains (columnType column)
|
||||
]
|
||||
in collect (seen ++ names) (remaining ++ nested)
|
||||
-- \| 保留嵌套领域的每层依赖
|
||||
typeDomains (CDomain name base) = name : typeDomains base
|
||||
typeDomains _ = []
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
module ChuSQL.Core.Engine.Runtime.Functions
|
||||
( FunctionId (..), FunctionSignature (..), FunctionRegistry
|
||||
, builtinFunctions, resolveFunction, resolveNullableFunction, functionSignature, invokeFunction
|
||||
) where
|
||||
module ChuSQL.Core.Engine.Runtime.Functions (
|
||||
FunctionId (..),
|
||||
FunctionSignature (..),
|
||||
FunctionRegistry,
|
||||
builtinFunctions,
|
||||
resolveFunction,
|
||||
resolveNullableFunction,
|
||||
functionSignature,
|
||||
invokeFunction,
|
||||
) where
|
||||
|
||||
import Control.Monad (zipWithM_)
|
||||
import ChuSQL.Core.Engine.Runtime.Types
|
||||
import Control.Monad (zipWithM_)
|
||||
import Data.Char (toLower)
|
||||
|
||||
-- 内建函数的固定身份、签名解析和类型化调用。
|
||||
@@ -14,9 +20,12 @@ newtype FunctionId = FunctionId Int deriving (Show, Eq, Ord)
|
||||
|
||||
-- | 描述已解析的函数签名
|
||||
data FunctionSignature = FunctionSignature
|
||||
{ signatureId :: FunctionId, signatureName :: String
|
||||
, signatureArguments :: [TypeId], signatureResult :: TypeId
|
||||
} deriving (Show, Eq)
|
||||
{ signatureId :: FunctionId
|
||||
, signatureName :: String
|
||||
, signatureArguments :: [TypeId]
|
||||
, signatureResult :: TypeId
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | 保存函数签名和实现
|
||||
newtype FunctionRegistry = FunctionRegistry [(FunctionSignature, [RuntimeValue] -> Either String RuntimeValue)]
|
||||
@@ -27,9 +36,12 @@ builtinFunctions types = do
|
||||
int <- descriptorId <$> resolveType types intType
|
||||
bool <- descriptorId <$> resolveType types boolType
|
||||
string <- descriptorId <$> resolveType types stringType
|
||||
Right (FunctionRegistry
|
||||
[(FunctionSignature (FunctionId 1) "length" [string] int, stringLength)
|
||||
,(FunctionSignature (FunctionId 2) "not" [bool] bool, booleanNot)])
|
||||
Right
|
||||
( FunctionRegistry
|
||||
[ (FunctionSignature (FunctionId 1) "length" [string] int, stringLength)
|
||||
, (FunctionSignature (FunctionId 2) "not" [bool] bool, booleanNot)
|
||||
]
|
||||
)
|
||||
|
||||
-- | 按名称和完整参数类型解析函数
|
||||
resolveFunction :: FunctionRegistry -> String -> [TypeId] -> Either String FunctionSignature
|
||||
@@ -38,14 +50,17 @@ resolveFunction registry name arguments = resolveNullableFunction registry name
|
||||
-- | 按可空 SQL 参数解析确定签名
|
||||
resolveNullableFunction :: FunctionRegistry -> String -> [Maybe TypeId] -> Either String FunctionSignature
|
||||
resolveNullableFunction (FunctionRegistry functions) name arguments =
|
||||
case [sig | (sig, _) <- functions, map toLower (signatureName sig) == map toLower name
|
||||
, length arguments == length (signatureArguments sig)
|
||||
, and (zipWith matches arguments (signatureArguments sig))] of
|
||||
case [ sig
|
||||
| (sig, _) <- functions
|
||||
, map toLower (signatureName sig) == map toLower name
|
||||
, length arguments == length (signatureArguments sig)
|
||||
, and (zipWith matches arguments (signatureArguments sig))
|
||||
] of
|
||||
[sig] -> Right sig
|
||||
[] -> Left ("no matching function signature: " ++ name)
|
||||
_ -> Left ("ambiguous function signature: " ++ name)
|
||||
where
|
||||
-- | 匹配具体参数或未定型 NULL
|
||||
-- \| 匹配具体参数或未定型 NULL
|
||||
matches Nothing _ = True
|
||||
matches (Just supplied) expected = supplied == expected
|
||||
|
||||
@@ -62,7 +77,9 @@ invokeFunction types (FunctionRegistry functions) fid values =
|
||||
[(sig, implementation)]
|
||||
| length values == length (signatureArguments sig) -> do
|
||||
zipWithM_
|
||||
(validateValue types) (signatureArguments sig) values
|
||||
(validateValue types)
|
||||
(signatureArguments sig)
|
||||
values
|
||||
result <- implementation values
|
||||
validateValue types (signatureResult sig) result
|
||||
Right result
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
module ChuSQL.Core.Engine.Runtime.Query
|
||||
( BoundQuery, QueryType (..), queryTypes, bindQuery, executeQueryM
|
||||
) where
|
||||
module ChuSQL.Core.Engine.Runtime.Query (
|
||||
BoundQuery,
|
||||
QueryType (..),
|
||||
queryTypes,
|
||||
bindQuery,
|
||||
executeQueryM,
|
||||
) where
|
||||
|
||||
import ChuSQL.Core.Engine.Algebra.Eval (evalRelOpM)
|
||||
import ChuSQL.Core.Engine.Algebra.Op (RelOp)
|
||||
import ChuSQL.Core.Engine.Algebra.Optimize (optimize)
|
||||
import ChuSQL.Core.Engine.Algebra.Planner (translate)
|
||||
import ChuSQL.Core.Engine.Semantic (prepare, querySchema)
|
||||
import ChuSQL.Core.Engine.Syntax.AST (Statement)
|
||||
import ChuSQL.Core.Engine.Storage (MonadStorage (schema, withSchemaGuard))
|
||||
import ChuSQL.Core.Engine.Runtime.Dependencies (domainDependencies, queryTables, validateDependencies)
|
||||
import ChuSQL.Core.Engine.Runtime.Relation (bindRelOpColumns)
|
||||
import ChuSQL.Core.Engine.Runtime.SQL (sqlType)
|
||||
import ChuSQL.Core.Engine.Runtime.Types (TypeId)
|
||||
import ChuSQL.Core.Engine.Runtime.Relation (bindRelOpColumns)
|
||||
import ChuSQL.Core.Engine.Runtime.Dependencies (queryTables, domainDependencies, validateDependencies)
|
||||
import ChuSQL.Core.Engine.Semantic (prepare, querySchema)
|
||||
import ChuSQL.Core.Engine.Storage (MonadStorage (schema, withSchemaGuard))
|
||||
import ChuSQL.Core.Engine.Syntax.AST (Statement)
|
||||
import ChuSQL.Core.Model
|
||||
|
||||
-- SQL 查询绑定边界:保存解析后的类型、函数身份及输出 schema。
|
||||
|
||||
-- | 保存运行时或兼容列类型
|
||||
data QueryType = RuntimeType TypeId | CompatibilityType ColumnType
|
||||
| DomainType String QueryType | NullType deriving (Show, Eq)
|
||||
data QueryType
|
||||
= RuntimeType TypeId
|
||||
| CompatibilityType ColumnType
|
||||
| DomainType String QueryType
|
||||
| NullType
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | 保存仅由 schema 绑定的执行计划
|
||||
data BoundQuery = BoundQuery Database [String] [(String, Column)] [(String, QueryType)] RelOp
|
||||
@@ -31,7 +39,7 @@ queryTypes (BoundQuery _ _ _ types _) = types
|
||||
-- | 绑定 SQL 查询并优化确定的计划
|
||||
bindQuery :: Database -> Statement -> Either String BoundQuery
|
||||
bindQuery catalog statement = do
|
||||
let schemaOnly = [(name, table {tableRows = []}) | (name, table) <- catalog]
|
||||
let schemaOnly = [(name, table{tableRows = []}) | (name, table) <- catalog]
|
||||
resolved <- prepare schemaOnly statement
|
||||
columns <- querySchema schemaOnly resolved
|
||||
plan <- translate resolved
|
||||
@@ -41,11 +49,11 @@ bindQuery catalog statement = do
|
||||
dependencies = sources ++ domainDependencies schemaOnly sources
|
||||
Right (BoundQuery schemaOnly dependencies columns types bound)
|
||||
where
|
||||
-- | 解析输出列的运行时身份
|
||||
-- \| 解析输出列的运行时身份
|
||||
typeOf (name, column) = do
|
||||
ty <- bindingType (columnType column)
|
||||
Right (name, ty)
|
||||
-- | 保留阶段外类型的兼容表示
|
||||
-- \| 保留阶段外类型的兼容表示
|
||||
bindingType CNull = Right NullType
|
||||
bindingType (CDomain name base) = DomainType name <$> bindingType base
|
||||
bindingType CDate = Right (CompatibilityType CDate)
|
||||
@@ -54,22 +62,24 @@ bindQuery catalog statement = do
|
||||
bindingType ty = RuntimeType <$> sqlType ty
|
||||
|
||||
-- | 执行绑定查询并校验输出类型
|
||||
executeQueryM :: MonadStorage m => BoundQuery -> m (Either String [Row])
|
||||
executeQueryM :: (MonadStorage m) => BoundQuery -> m (Either String [Row])
|
||||
executeQueryM (BoundQuery catalog dependencies columns _ plan) = do
|
||||
current <- schema
|
||||
case current >>= validateDependencies guardedTables of
|
||||
Left err -> pure (Left err)
|
||||
Right () -> do
|
||||
result <- withSchemaGuard guardedTables
|
||||
(evalRelOpM catalog plan)
|
||||
result <-
|
||||
withSchemaGuard
|
||||
guardedTables
|
||||
(evalRelOpM catalog plan)
|
||||
pure (result >>= mapM validateRow)
|
||||
where
|
||||
guardedTables = [(name, table) | (name, table) <- catalog, name `elem` dependencies]
|
||||
-- | 校验完整输出行而不重新解析函数
|
||||
-- \| 校验完整输出行而不重新解析函数
|
||||
validateRow row
|
||||
| map fst row /= map fst columns = Left "bound query output schema mismatch"
|
||||
| otherwise = mapM validateColumn (zip columns row)
|
||||
-- | 校验输出列的物理值
|
||||
-- \| 校验输出列的物理值
|
||||
validateColumn ((name, column), (_, value))
|
||||
| columnType column == CNull, value == VNull = Right (name, value)
|
||||
| valueFits (columnType column) value = Right (name, value)
|
||||
|
||||
@@ -20,14 +20,14 @@ bindWriteColumns db statement = case statement of
|
||||
Delete table <$> mapM (bindSlots columns []) condition
|
||||
_ -> Right statement
|
||||
where
|
||||
-- | 保留写入目标并绑定右侧表达式
|
||||
-- \| 保留写入目标并绑定右侧表达式
|
||||
bindItem columns (name, expression) = (,) name <$> bindSlots columns [] expression
|
||||
|
||||
-- | 按绑定布局排列一行并拒绝缺列
|
||||
orderRow :: [String] -> Row -> Either String Row
|
||||
orderRow columns row = mapM column columns
|
||||
where
|
||||
-- | 保留列名并取得物理值
|
||||
-- \| 保留列名并取得物理值
|
||||
column name = case lookup name row of
|
||||
Just value -> Right (name, value)
|
||||
Nothing -> Left ("missing bound column: " ++ name)
|
||||
@@ -84,8 +84,10 @@ bindSlots columns outer expression = case expression of
|
||||
ColumnSlot slot name -> ColumnSlot <$> rebindSlot columns slot name "unknown local column: " <*> pure name
|
||||
OuterColumnSlot width slot name
|
||||
| width < 0 -> Left ("bound column position mismatch: " ++ name)
|
||||
| otherwise -> OuterColumnSlot (length columns)
|
||||
<$> rebindSlot outer slot name "unknown column: " <*> pure name
|
||||
| otherwise ->
|
||||
OuterColumnSlot (length columns)
|
||||
<$> rebindSlot outer slot name "unknown column: "
|
||||
<*> pure name
|
||||
FunctionCall name arguments -> FunctionCall name <$> mapM bind arguments
|
||||
BoundFunction fid tid arguments -> BoundFunction fid tid <$> mapM bind arguments
|
||||
Construct ty arguments -> Construct ty <$> mapM bind arguments
|
||||
@@ -121,7 +123,7 @@ bindSlots columns outer expression = case expression of
|
||||
bind = bindSlots columns outer
|
||||
-- \| 查找布局中首次出现的列
|
||||
slots names name = [slot | (slot, key) <- zip [0 ..] names, key == name]
|
||||
-- | 保留有效位置,仅允许唯一列重定位
|
||||
-- \| 保留有效位置,仅允许唯一列重定位
|
||||
rebindSlot names slot name missing
|
||||
| slot < 0 = Left ("bound column position mismatch: " ++ name)
|
||||
| take 1 (drop slot names) == [name] = Right slot
|
||||
@@ -133,7 +135,7 @@ bindSlots columns outer expression = case expression of
|
||||
outerReference name = case slots outer name of
|
||||
slot : _ -> Right (OuterColumnSlot (length columns) slot name)
|
||||
[] -> Left ("unknown column: " ++ name)
|
||||
-- | 将子查询自由变量绑定到父输入布局
|
||||
-- \| 将子查询自由变量绑定到父输入布局
|
||||
bindSubquery query = do
|
||||
references <- case query of
|
||||
Subquery _ names -> mapM reference names
|
||||
@@ -141,12 +143,12 @@ bindSlots columns outer expression = case expression of
|
||||
| map fst references == names -> mapM rebind references
|
||||
| otherwise -> Left "bound subquery references mismatch"
|
||||
Right (BoundSubquery (subqueryStatement query) (subqueryRefs query) references)
|
||||
-- | 补回限定名之前先绑定真实物理列
|
||||
-- \| 补回限定名之前先绑定真实物理列
|
||||
reference name = do
|
||||
let physical = if name `elem` (columns ++ outer) then name else reverse (takeWhile (/= '.') (reverse name))
|
||||
boundReference <- bind (Col physical)
|
||||
Right (name, boundReference)
|
||||
-- | 重绑定时保留局部或外层身份
|
||||
-- \| 重绑定时保留局部或外层身份
|
||||
rebind (name, boundReference) = (,) name <$> bind boundReference
|
||||
-- \| 绑定两个操作数
|
||||
binary constructor a b = constructor <$> bind a <*> bind b
|
||||
@@ -1,9 +1,9 @@
|
||||
module ChuSQL.Core.Engine.Runtime.SQL (resolveSQLFunction, sqlResultType, invokeSQLFunction, sqlType, constructSQLValue) where
|
||||
|
||||
import Control.Monad (zipWithM_)
|
||||
import ChuSQL.Core.Engine.Runtime.Functions
|
||||
import ChuSQL.Core.Engine.Runtime.Types
|
||||
import ChuSQL.Core.Model (ColumnType (..), Value (..))
|
||||
import Control.Monad (zipWithM_)
|
||||
|
||||
-- SQL 现有标量表示与已解析运行时函数之间的类型边界。
|
||||
|
||||
@@ -54,7 +54,9 @@ invokeSQLFunction fid expected values = do
|
||||
then Left "function argument count mismatch"
|
||||
else do
|
||||
zipWithM_
|
||||
validateArgument (signatureArguments sig) values
|
||||
validateArgument
|
||||
(signatureArguments sig)
|
||||
values
|
||||
if VNull `elem` values
|
||||
then Right VNull
|
||||
else do
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
module ChuSQL.Core.Engine.Semantic (check, prepare, prepareRowExpression, prepareCheckExpression, querySchema) where
|
||||
|
||||
import Control.Monad (void)
|
||||
module ChuSQL.Core.Engine.Semantic (check, prepare, prepareRowExpression, prepareCheckExpression, prepareDomainCheckExpression, querySchema) where
|
||||
|
||||
import ChuSQL.Core.Engine.Algebra.Expr (bareColumns, colsInExpr, hasAggregate, hasSubquery)
|
||||
import ChuSQL.Core.Engine.Runtime.Functions (FunctionSignature (..), builtinFunctions, functionSignature)
|
||||
import ChuSQL.Core.Engine.Runtime.SQL (resolveSQLFunction, sqlResultType, sqlType)
|
||||
import ChuSQL.Core.Engine.Runtime.Relation (bindSlots)
|
||||
import ChuSQL.Core.Engine.Runtime.SQL (resolveSQLFunction, sqlResultType, sqlType)
|
||||
import ChuSQL.Core.Engine.Runtime.Types
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Engine.Syntax.Parser (parseExpression)
|
||||
import ChuSQL.Core.Model
|
||||
import Control.Monad (void)
|
||||
import Data.Char (isAlphaNum, isAscii, toLower)
|
||||
import Data.List (intercalate, isInfixOf, isPrefixOf, isSuffixOf, nub)
|
||||
|
||||
@@ -46,7 +47,7 @@ data Scope = Scope
|
||||
scopeAt :: String -> Scope -> String -> Either String (String, Column)
|
||||
scopeAt place scope c = search (scopeFrames scope)
|
||||
where
|
||||
-- | 在最近的可见列层停止查找
|
||||
-- \| 在最近的可见列层停止查找
|
||||
search [] = Left (missingColumn place c (concat (scopeFrames scope)))
|
||||
search (columns : rest) = case columnAt place columns c of
|
||||
Right entry -> Right entry
|
||||
@@ -116,7 +117,12 @@ checkGrouping env keys items having
|
||||
| null keys && null having && not (any (hasAggregate . snd) items) = Right ()
|
||||
| otherwise = do
|
||||
checkColumns "GROUP BY" env keys
|
||||
mapM_ (checkRuntimeColumn "GROUP BY" env) keys
|
||||
mapM_
|
||||
( \column -> do
|
||||
(_, definition) <- scopeAt "GROUP BY" env column
|
||||
void (requireHashing "GROUP BY" (InferType (columnType definition)))
|
||||
)
|
||||
keys
|
||||
mapM_ okCol (nub (concatMap (bareColumns . snd) items ++ concatMap bareColumns having))
|
||||
where
|
||||
-- \| 这一列能不能出现在分组查询里
|
||||
@@ -172,12 +178,12 @@ inferExpr db place env (Neg a) = do
|
||||
InferType x
|
||||
| numericType x -> Right t
|
||||
| otherwise -> Left (place ++ ": operator needs TInt on both sides, got TInt and " ++ typeLabel x)
|
||||
inferExpr db place env (Gt a b) = comparison db place env a b
|
||||
inferExpr db place env (Lt a b) = comparison db place env a b
|
||||
inferExpr db place env (Eq a b) = comparison db place env a b
|
||||
inferExpr db place env (GtE a b) = comparison db place env a b
|
||||
inferExpr db place env (LtE a b) = comparison db place env a b
|
||||
inferExpr db place env (NotEq a b) = comparison db place env a b
|
||||
inferExpr db place env (Gt a b) = comparison OrdCapability db place env a b
|
||||
inferExpr db place env (Lt a b) = comparison OrdCapability db place env a b
|
||||
inferExpr db place env (Eq a b) = comparison EqCapability db place env a b
|
||||
inferExpr db place env (GtE a b) = comparison OrdCapability db place env a b
|
||||
inferExpr db place env (LtE a b) = comparison OrdCapability db place env a b
|
||||
inferExpr db place env (NotEq a b) = comparison EqCapability db place env a b
|
||||
inferExpr db place env (And a b) = boolean db place env a b
|
||||
inferExpr db place env (Or a b) = boolean db place env a b
|
||||
inferExpr db place env (IsNull a) = inferExpr db place env a >> Right (InferType CBool)
|
||||
@@ -215,12 +221,15 @@ inferExpr db place env (InList a es _) = do
|
||||
mapM_ (itemType place ta) ts
|
||||
Right (InferType CBool)
|
||||
inferExpr _ _ _ (ExistsSub _ _) = Right (InferType CBool)
|
||||
inferExpr db place env (QuantCmp _ a sq q) = do
|
||||
inferExpr db place env (QuantCmp operator 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)
|
||||
| compatible ta t -> do
|
||||
let capability = if operator `elem` [CmpEq, CmpNe] then EqCapability else OrdCapability
|
||||
mapM_ (requireCapability capability place) [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")
|
||||
|
||||
@@ -241,7 +250,7 @@ inferSubqueryOutput _ _ BoundSubquery{} = Left "execution subquery cannot be use
|
||||
inferSubqueryOutput db outerScope sq = case subqueryStatement sq of
|
||||
SelectDistinct q -> do
|
||||
columns <- inferSubqueryOutput db outerScope sq{subqueryStatement = q}
|
||||
mapM_ (requireOrdering "DISTINCT" . snd) columns
|
||||
mapM_ (requireHashing "DISTINCT" . snd) columns
|
||||
_ <- distinctOrdering q
|
||||
Right columns
|
||||
q@Select{selectItems = items} -> do
|
||||
@@ -295,12 +304,14 @@ numericOperand place want other t = case t of
|
||||
_ -> Left (place ++ ": operator needs " ++ renderType want ++ " on both sides, got " ++ renderType other ++ " and " ++ renderType t)
|
||||
|
||||
-- | 比较两边的类型检查
|
||||
comparison :: Database -> String -> Scope -> Expr -> Expr -> Either String InferredType
|
||||
comparison db place env a b = do
|
||||
comparison :: TypeCapability -> Database -> String -> Scope -> Expr -> Expr -> Either String InferredType
|
||||
comparison capability db place env a b = do
|
||||
ta <- inferExpr db place env a
|
||||
tb <- inferExpr db place env b
|
||||
if compatible ta tb
|
||||
then Right (InferType CBool)
|
||||
then do
|
||||
mapM_ (requireCapability capability place) [ta, tb]
|
||||
Right (InferType CBool)
|
||||
else Left (place ++ ": both sides of = must have the same type, got " ++ renderType ta ++ " and " ++ renderType tb)
|
||||
|
||||
-- | 两类能不能比,类型族规则来自 Model
|
||||
@@ -362,7 +373,8 @@ prepare db q = do
|
||||
let visible = filter (not . isDomainTable . fst) db
|
||||
resolved <- resolveStatement visible typed
|
||||
case resolved of
|
||||
CreateDomain name base -> do
|
||||
CreateDomain name (DomainDefinition column _) -> do
|
||||
let base = columnType column
|
||||
checkDomainName name
|
||||
checkTypeParameters base
|
||||
case base of
|
||||
@@ -373,6 +385,9 @@ prepare db q = do
|
||||
if any ((== domainTableName name) . fst) db
|
||||
then Left ("domain already exists: " ++ name)
|
||||
else Right ()
|
||||
AlterDomain name _ -> do
|
||||
_ <- maybe (Left ("unknown domain: " ++ name)) Right (lookup (domainTableName name) db)
|
||||
Right ()
|
||||
DropDomain name -> do
|
||||
_ <- maybe (Left ("unknown domain: " ++ name)) Right (lookup (domainTableName name) db)
|
||||
if any (any (usesDomain name . columnType . snd) . tableCols . snd) db
|
||||
@@ -418,7 +433,9 @@ ddlTargets statement = case statement of
|
||||
-- | 从 schema 解析列上的命名类型
|
||||
resolveDomainTypes :: Database -> Statement -> Either String Statement
|
||||
resolveDomainTypes db statement = case statement of
|
||||
CreateDomain name base -> CreateDomain name <$> resolveDomainType base
|
||||
CreateDomain name (DomainDefinition column checks) -> do
|
||||
ty <- resolveDomainType (columnType column)
|
||||
Right (CreateDomain name (DomainDefinition column{columnType = ty} checks))
|
||||
CreateTable name cols -> CreateTable name <$> mapM resolveColumnType cols
|
||||
AddColumn name col -> AddColumn name <$> resolveColumnType col
|
||||
AlterColumnType name col ty -> AlterColumnType name col <$> resolveDomainType ty
|
||||
@@ -491,9 +508,11 @@ checkResolvedWith db outer q = case q of
|
||||
else Right ()
|
||||
CreateIndex tbl col -> do
|
||||
t <- lookupTable db tbl
|
||||
if col `elem` tableCols' t
|
||||
then checkRuntimeColumn "CREATE INDEX" (Scope (tableCols t) []) col
|
||||
else Left ("CREATE INDEX: unknown column: " ++ col)
|
||||
case lookup col (tableCols t) of
|
||||
Nothing -> Left ("CREATE INDEX: unknown column: " ++ col)
|
||||
Just column
|
||||
| typeClassOf (columnType column) == RuntimeClass -> Left "CREATE INDEX: runtime index key encoding is not registered"
|
||||
| otherwise -> Right ()
|
||||
DropIndex tbl col -> do
|
||||
t <- lookupTable db tbl
|
||||
if col `elem` tableCols' t
|
||||
@@ -579,10 +598,12 @@ checkResolvedWith db outer q = case q of
|
||||
UseDatabase name -> checkDatabaseName name
|
||||
ShowDatabases -> Right ()
|
||||
CreateDomain{} -> Right ()
|
||||
AlterDomain{} -> Right ()
|
||||
DropDomain{} -> Right ()
|
||||
ShowDomains -> Right ()
|
||||
-- \| 事务控制由会话层执行,语义层不做列检查
|
||||
BeginTransaction -> Right ()
|
||||
BeginSerializableTransaction -> Right ()
|
||||
CommitTransaction -> Right ()
|
||||
RollbackTransaction -> Right ()
|
||||
-- \| 保存点名字只是会话层的记号,不做列检查
|
||||
@@ -660,7 +681,7 @@ checkChecks columns = mapM_ checkOne
|
||||
Just text -> case parseExpression text of
|
||||
Left err -> Left ("CHECK: " ++ firstLine err)
|
||||
Right e -> void (prepareCheckExpression columns e)
|
||||
-- | 只保留解析错误的第一行
|
||||
-- \| 只保留解析错误的第一行
|
||||
firstLine = takeWhile (/= '\n')
|
||||
|
||||
-- | 账号名上限
|
||||
@@ -825,6 +846,21 @@ prepareCheckExpression columns expression = do
|
||||
checkBool [] "CHECK" (Scope columns []) resolved
|
||||
bindSlots (map fst columns) [] resolved
|
||||
|
||||
-- | 在单值作用域规范化领域 VALUE 的大小写
|
||||
prepareDomainCheckExpression :: ColumnType -> Expr -> Either String Expr
|
||||
prepareDomainCheckExpression ty expression = do
|
||||
checkNoAggregate "CHECK" expression
|
||||
if hasSubquery expression then Left "CHECK: subqueries are not allowed here" else Right ()
|
||||
let names = nub ("value" : colsInExpr expression)
|
||||
columns = [(name, plainColumn ty) | name <- names, map toLower name == "value"]
|
||||
resolved <- resolveExpr [] columns [] valueName expression
|
||||
prepareCheckExpression [("value", plainColumn ty)] resolved
|
||||
where
|
||||
-- \| 领域条件只允许引用 VALUE
|
||||
valueName name
|
||||
| map toLower name == "value" = Right "value"
|
||||
| otherwise = Left ("unknown column in DOMAIN CHECK: " ++ name)
|
||||
|
||||
-- | 将推导类型转换为可空参数类型
|
||||
inferredArgument :: InferredType -> Maybe ColumnType
|
||||
inferredArgument InferNull = Nothing
|
||||
@@ -856,8 +892,14 @@ statementColumns stmt = case stmt of
|
||||
SelectDistinct q -> statementColumns q
|
||||
Select items fromC mWhere groupBy having orderBy _ -> do
|
||||
ordering <- mapM (orderExpression items . fst) orderBy
|
||||
Right (concatMap (colsInExpr . snd) items ++ fromColumns fromC
|
||||
++ concatMap colsInExpr mWhere ++ groupBy ++ concatMap colsInExpr having ++ concatMap colsInExpr ordering)
|
||||
Right
|
||||
( concatMap (colsInExpr . snd) items
|
||||
++ fromColumns fromC
|
||||
++ concatMap colsInExpr mWhere
|
||||
++ groupBy
|
||||
++ concatMap colsInExpr having
|
||||
++ concatMap colsInExpr ordering
|
||||
)
|
||||
_ -> Right []
|
||||
where
|
||||
-- \| 取 FROM 里出现的列
|
||||
@@ -909,11 +951,12 @@ nameIn env place c = fst <$> columnAt place env c
|
||||
scopedColumnAt :: String -> [(String, Column)] -> EnclosingScopes -> String -> Either String (String, Column)
|
||||
scopedColumnAt place inner outer c
|
||||
| length (filter (== '.') c) == 1
|
||||
, columns : _ <- filter ownsSource (inner : outer) = columnAt place columns c
|
||||
, columns : _ <- filter ownsSource (inner : outer) =
|
||||
columnAt place columns c
|
||||
| otherwise = scopeAt place (Scope inner outer) c
|
||||
where
|
||||
qualifier = takeWhile (/= '.') c ++ "."
|
||||
-- | 判断当前层是否声明该来源限定名
|
||||
-- \| 判断当前层是否声明该来源限定名
|
||||
ownsSource = any (isPrefixOf qualifier . fst)
|
||||
|
||||
-- | 单表保留原表头,连接使用唯一前缀
|
||||
@@ -927,18 +970,21 @@ checkOrder db env items column = do
|
||||
expression <- orderExpression items column
|
||||
void (inferExpr db "ORDER BY" env expression >>= requireOrdering "ORDER BY")
|
||||
|
||||
-- | 校验列已有排序能力
|
||||
checkRuntimeColumn :: String -> Scope -> String -> Either String ()
|
||||
checkRuntimeColumn place env column = do
|
||||
(_, definition) <- scopeAt place env column
|
||||
void (requireOrdering place (InferType (columnType definition)))
|
||||
|
||||
-- | 拒绝尚未注册能力的复合类型
|
||||
requireOrdering :: String -> InferredType -> Either String InferredType
|
||||
requireOrdering place inferred@(InferType ty)
|
||||
| typeClassOf ty == RuntimeClass = Left (place ++ ": runtime type capability is not registered")
|
||||
-- | 统一检查已注册能力并保留 NULL 传播
|
||||
requireCapability :: TypeCapability -> String -> InferredType -> Either String InferredType
|
||||
requireCapability capability place inferred@(InferType ty)
|
||||
| not (hasTypeCapability capability ty) = Left (place ++ ": type capability is not registered: " ++ show capability)
|
||||
| otherwise = Right inferred
|
||||
requireOrdering _ inferred = Right inferred
|
||||
requireCapability _ _ inferred = Right inferred
|
||||
|
||||
-- | 只允许已提供排序能力的类型
|
||||
requireOrdering :: String -> InferredType -> Either String InferredType
|
||||
requireOrdering = requireCapability OrdCapability
|
||||
|
||||
-- | 分组与去重同时要求相等和哈希能力
|
||||
requireHashing :: String -> InferredType -> Either String InferredType
|
||||
requireHashing place inferred =
|
||||
requireCapability EqCapability place inferred >> requireCapability HashCapability place inferred
|
||||
|
||||
-- | 解析投影、条件、排序与连接引用
|
||||
resolveQuery :: Database -> EnclosingScopes -> Statement -> [(String, Expr)] -> Either String Statement
|
||||
@@ -957,11 +1003,13 @@ resolveQuery db outer q items = do
|
||||
qualifiedReference c =
|
||||
qualifiedReferenceIn db (selectFrom q) c
|
||||
|| ( any
|
||||
(any
|
||||
(\ (key, _)
|
||||
-> length (filter (== '.') key) == 1
|
||||
&& ("." ++ key) `isSuffixOf` c))
|
||||
outer
|
||||
( any
|
||||
( \(key, _) ->
|
||||
length (filter (== '.') key) == 1
|
||||
&& ("." ++ key) `isSuffixOf` c
|
||||
)
|
||||
)
|
||||
outer
|
||||
&& (not (any (elem '.' . fst) db) || any (\(table, _) -> (table ++ ".") `isPrefixOf` c) db)
|
||||
)
|
||||
-- \| 换算一个投影项:带别名时保留别名做输出列名
|
||||
@@ -981,13 +1029,13 @@ resolveQuery db outer q items = do
|
||||
condition <- mapM (resolveExpr db env outer (name "WHERE")) (selectWhere q)
|
||||
grouping <- mapM (name "GROUP BY") (selectGroupBy q)
|
||||
having <- mapM (resolveExpr db env outer (name "HAVING")) (selectHaving q)
|
||||
-- | 整项名称可用输出别名,复合表达式只查来源
|
||||
-- \| 整项名称可用输出别名,复合表达式只查来源
|
||||
let orderKey key@(OrderColumn column)
|
||||
| column `elem` map fst projected = key <$ orderExpression projected key
|
||||
| otherwise = OrderColumn <$> name "ORDER BY" column
|
||||
orderKey key@(OrderPosition _) = key <$ orderPosition (length projected) key
|
||||
orderKey (OrderExpression expression) = OrderExpression <$> resolveExpr db env outer (name "ORDER BY") expression
|
||||
ordering <- mapM (\(key, direction) -> (, direction) <$> orderKey key) (selectOrderBy q)
|
||||
ordering <- mapM (\(key, direction) -> (,direction) <$> orderKey key) (selectOrderBy q)
|
||||
pure (Select projected source condition grouping having ordering (selectLimit q))
|
||||
|
||||
-- | 收集没有别名遮蔽的来源表名
|
||||
|
||||
@@ -118,6 +118,10 @@ class (Monad m) => MonadStorage m where
|
||||
-- \| ALTER 用:整表换列定义与全部行
|
||||
replaceSchema :: String -> [(String, Column)] -> [Row] -> m (Either String ())
|
||||
|
||||
-- | 校验完整快照后原子修改领域定义
|
||||
alterDomain :: String -> (Database -> Either String [(String, Column)]) -> m (Either String ())
|
||||
alterDomain _ _ = pure (Left "atomic domain alteration is unavailable in this storage")
|
||||
|
||||
-- \| 取全库快照(表名 + 列 + 全部行)
|
||||
snapshot :: m (Either String Database)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE ImportQualifiedPost #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module ChuSQL.Core.Engine.Storage.IPC (
|
||||
@@ -29,10 +29,10 @@ module ChuSQL.Core.Engine.Storage.IPC (
|
||||
doDescribeTable,
|
||||
doDropTable,
|
||||
doListCatalog,
|
||||
databaseSnapshot,
|
||||
snapshotDatabase,
|
||||
) where
|
||||
|
||||
import Data.Foldable (for_)
|
||||
import Control.Monad (when)
|
||||
import ChuSQL.Core.Engine.Parallel (parallelShardLimit, poolRun, workerPool)
|
||||
import ChuSQL.Core.Engine.Storage
|
||||
import ChuSQL.Core.Engine.Storage.FFI (closeStorage, openStorage, storageRequest)
|
||||
@@ -41,16 +41,43 @@ import ChuSQL.Core.Protocol (Account (..), Request (..), Response (..), SchemaCo
|
||||
import Control.Concurrent (getNumCapabilities)
|
||||
import Control.Concurrent.MVar (MVar, modifyMVar_, newMVar, putMVar, readMVar, takeMVar, withMVar)
|
||||
import Control.Exception (bracket, mask_)
|
||||
import Control.Monad (when)
|
||||
import Data.Aeson (eitherDecodeStrict, encode)
|
||||
import Data.Aeson qualified as A
|
||||
import Data.Aeson.KeyMap qualified as KM
|
||||
import Data.ByteString qualified as BS
|
||||
import Data.ByteString.Lazy qualified as BL
|
||||
import Data.Foldable (for_)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import System.IO.Unsafe (unsafePerformIO)
|
||||
|
||||
-- IPC 存储实现:把 MonadStorage 的操作变成发给存储库的 JSON 请求。
|
||||
|
||||
-- 复用相同提交点的不可变快照版本。
|
||||
databaseSnapshot :: Maybe (String, Database) -> Env -> IO (Either String (String, Database))
|
||||
databaseSnapshot previous env = do
|
||||
response <- ask env "database_snapshot" (ReqDatabaseSnapshot (fst <$> previous)) Just
|
||||
pure $ case response of
|
||||
Left err -> Left err
|
||||
Right (RespSnapshotUnchanged checkpoint) -> case previous of
|
||||
Just cached | fst cached == checkpoint -> Right cached
|
||||
_ -> Left "snapshot version does not match cached database"
|
||||
Right (RespDomainSnapshot infos rows checkpoint) -> do
|
||||
db <- snapshotDatabase infos rows
|
||||
Right (checkpoint, db)
|
||||
Right (RespError err) -> Left err
|
||||
_ -> Left "unexpected database snapshot response"
|
||||
|
||||
-- 从同一版本的结构和行集构建引擎数据库。
|
||||
snapshotDatabase :: [TableInfo] -> [(String, [Row])] -> Either String Database
|
||||
snapshotDatabase infos rows = mapM load infos
|
||||
where
|
||||
-- 布局、对象身份与数据必须来自同一版本。
|
||||
load info = do
|
||||
values <- maybe (Left "incomplete database snapshot") Right (lookup (tiTable info) rows)
|
||||
let name = tiTable info
|
||||
Right (name, Table name (schemaToColumns (tiColumns info)) values (Just (metaOf info)) (tiIdentity info) (tiSchemaVersion info))
|
||||
|
||||
-- | 一条会话的存储环境:当前库名随会话走,不共享
|
||||
data Env = Env
|
||||
{ envDatabase :: Maybe String
|
||||
@@ -146,15 +173,16 @@ closeConnection = modifyMVar_ linkRef $ \current -> withMVar (snd linkUsers) $ \
|
||||
-- | 发一条请求并解码应答;链路没装就报错,不崩
|
||||
sendRequest :: Request -> IO Response
|
||||
sendRequest req = withStorageLink $ \case
|
||||
Nothing -> pure (RespError "storage not configured")
|
||||
Just link
|
||||
-> do raw <- slRequest link (BL.toStrict (encode req))
|
||||
case raw of
|
||||
Left e -> pure (RespError ("storage error: " ++ e))
|
||||
Right bytes
|
||||
-> case eitherDecodeStrict (BS.dropWhileEnd (== 13) bytes) of
|
||||
Left e -> pure (RespError ("decode error: " ++ e))
|
||||
Right resp -> pure resp
|
||||
Nothing -> pure (RespError "storage not configured")
|
||||
Just link ->
|
||||
do
|
||||
raw <- slRequest link (BL.toStrict (encode req))
|
||||
case raw of
|
||||
Left e -> pure (RespError ("storage error: " ++ e))
|
||||
Right bytes ->
|
||||
case eitherDecodeStrict (BS.dropWhileEnd (== 13) bytes) of
|
||||
Left e -> pure (RespError ("decode error: " ++ e))
|
||||
Right resp -> pure resp
|
||||
|
||||
-- | 原样转发一条载荷给存储库
|
||||
sendRawRequest :: BS.ByteString -> IO (Either String BS.ByteString)
|
||||
@@ -244,22 +272,22 @@ okOnly _ = Nothing
|
||||
-- | 发 Scan
|
||||
doScan :: Env -> String -> IO (Either String [Row])
|
||||
doScan env t = ask env "scan" (ReqScan t) $ \case
|
||||
RespRows rows -> Just rows
|
||||
_ -> Nothing
|
||||
RespRows rows -> Just rows
|
||||
_ -> Nothing
|
||||
|
||||
-- | 发带投影的 Scan
|
||||
doScanColumns :: Env -> String -> [String] -> IO (Either String [Row])
|
||||
doScanColumns env t cols = ask env "scan" (ReqScanColumns t cols) $ \case
|
||||
RespRows rows
|
||||
-> Just
|
||||
[[(c, v) | c <- cols, Just v <- [lookup c row]] | row <- rows]
|
||||
_ -> Nothing
|
||||
RespRows rows ->
|
||||
Just
|
||||
[[(c, v) | c <- cols, Just v <- [lookup c row]] | row <- rows]
|
||||
_ -> Nothing
|
||||
|
||||
-- | 发分片 Scan:只取第 shard 片
|
||||
doScanShard :: Env -> String -> Maybe [String] -> Int -> Int -> IO (Either String [Row])
|
||||
doScanShard env t cols shard shards = ask env "scan_shard" (ReqScanShard t cols shard shards) $ \case
|
||||
RespRows rows -> Just rows
|
||||
_ -> Nothing
|
||||
RespRows rows -> Just rows
|
||||
_ -> Nothing
|
||||
|
||||
-- | 发 Insert(索引由存储层维护)
|
||||
doInsert :: Env -> String -> Row -> IO (Either String ())
|
||||
@@ -276,21 +304,21 @@ doDeleteKeys env t ks = ask env "delete_keys" (ReqDeleteKeys t ks) okOnly
|
||||
-- | 发 ListTables
|
||||
doListTables :: Env -> IO (Either String [String])
|
||||
doListTables env = ask env "list_tables" ReqListTables $ \case
|
||||
RespTables ts -> Just ts
|
||||
_ -> Nothing
|
||||
RespTables ts -> Just ts
|
||||
_ -> Nothing
|
||||
|
||||
-- | 一次拿全库 schema
|
||||
doListCatalog :: Env -> IO (Either String [TableInfo])
|
||||
doListCatalog env = ask env "list_catalog" ReqListCatalog $ \case
|
||||
RespCatalog xs -> Just xs
|
||||
_ -> Nothing
|
||||
RespCatalog xs -> Just xs
|
||||
_ -> Nothing
|
||||
|
||||
-- | 按某一列的索引取一行(键可以是整数或字符串)
|
||||
doLookupByColumn :: Env -> String -> String -> Value -> IO (Either String IndexResult)
|
||||
doLookupByColumn env t c k = ask env "lookup_by_index" (ReqLookupByColumn t c k) $ \case
|
||||
RespRows rows -> Just (IndexRows rows)
|
||||
RespNoIndex -> Just NoIndex
|
||||
_ -> Nothing
|
||||
RespRows rows -> Just (IndexRows rows)
|
||||
RespNoIndex -> Just NoIndex
|
||||
_ -> Nothing
|
||||
|
||||
-- | 范围扫描:没有可用索引就回 Nothing,让上层退回扫描
|
||||
doScanRange ::
|
||||
@@ -301,15 +329,15 @@ doScanRange ::
|
||||
Maybe (Value, Bool) ->
|
||||
IO (Either String (Maybe [Row]))
|
||||
doScanRange env t c lo hi = ask env "range_by_index" (ReqRangeByIndex t c lo hi) $ \case
|
||||
RespRows rows -> Just (Just rows)
|
||||
RespNoIndex -> Just Nothing
|
||||
_ -> Nothing
|
||||
RespRows rows -> Just (Just rows)
|
||||
RespNoIndex -> Just Nothing
|
||||
_ -> Nothing
|
||||
|
||||
-- | 发 DescribeTable
|
||||
doDescribeTable :: Env -> String -> IO (Either String TableInfo)
|
||||
doDescribeTable env t = ask env "describe_table" (ReqDescribeTable t) $ \case
|
||||
RespSchema info -> Just info
|
||||
_ -> Nothing
|
||||
RespSchema info -> Just info
|
||||
_ -> Nothing
|
||||
|
||||
-- | 发 DropTable
|
||||
doDropTable :: Env -> String -> IO (Either String ())
|
||||
@@ -353,7 +381,7 @@ instance MonadStorage IPCStorage where
|
||||
withSchemaGuard dependencies action = IPCStorage $ \env ->
|
||||
runIPCStorageIn action env{envSchemaGuard = Just dependencies}
|
||||
|
||||
-- | 写前握手,旧存储不能先写入后才报错
|
||||
-- \| 写前握手,旧存储不能先写入后才报错
|
||||
withWriteSchemaGuard dependencies action = IPCStorage $ \env -> case dependencies of
|
||||
[] -> pure (Left "bound write requires a target schema guard")
|
||||
(target, _) : _ -> do
|
||||
@@ -374,8 +402,8 @@ instance MonadStorage IPCStorage where
|
||||
|
||||
-- \| 列出现有库
|
||||
listDatabases = IPCStorage $ \env -> ask env "list_databases" (ReqDatabase "list_databases" "") $ \case
|
||||
RespTables names -> Just names
|
||||
_ -> Nothing
|
||||
RespTables names -> Just names
|
||||
_ -> Nothing
|
||||
|
||||
-- \| 发 Scan
|
||||
scan t = IPCStorage (`doScan` t)
|
||||
@@ -430,30 +458,31 @@ instance MonadStorage IPCStorage where
|
||||
-- \| 删一列
|
||||
dropColumn t c = IPCStorage $ \env -> ask env "drop_column" (ReqDropColumn t c) okOnly
|
||||
|
||||
-- \| 列表 + 逐表扫描拼库
|
||||
snapshot = IPCStorage $ \env -> do
|
||||
result <- doListCatalog env
|
||||
case result of
|
||||
-- \| 校验一致快照并复查提交点后发布领域
|
||||
alterDomain name validate = IPCStorage $ \env -> do
|
||||
snapshotResult <- ask env "domain_snapshot" ReqDomainSnapshot $ \case
|
||||
RespDomainSnapshot infos rows checkpoint -> Just (infos, rows, checkpoint)
|
||||
_ -> Nothing
|
||||
case snapshotResult of
|
||||
Left err -> pure (Left err)
|
||||
Right xs -> fmap sequence (mapM (loadEntry env) xs)
|
||||
Right (infos, rows, checkpoint) -> case mapM (loadDomainEntry rows) infos >>= validate of
|
||||
Left err -> pure (Left err)
|
||||
Right cols -> ask env "alter_domain" (ReqAlterDomain name (map toWire cols) checkpoint) okOnly
|
||||
where
|
||||
-- \| 读取表数据并保留扫描错误
|
||||
loadEntry env info = do
|
||||
rows <- doScan env (tiTable info)
|
||||
pure $
|
||||
fmap
|
||||
( \rs ->
|
||||
( tiTable info
|
||||
, Table (tiTable info) (schemaToColumns (tiColumns info)) rs (Just (metaOf info)) (tiIdentity info) (tiSchemaVersion info)
|
||||
)
|
||||
)
|
||||
rows
|
||||
-- \| 保留快照身份并拒绝缺失的表数据
|
||||
loadDomainEntry rows info = do
|
||||
values <- maybe (Left "incomplete domain snapshot") Right (lookup (tiTable info) rows)
|
||||
let key = tiTable info
|
||||
Right (key, Table key (schemaToColumns (tiColumns info)) values (Just (metaOf info)) (tiIdentity info) (tiSchemaVersion info))
|
||||
|
||||
-- 在存储读门内获取同一版本的完整快照。
|
||||
snapshot = IPCStorage $ \env -> fmap (fmap snd) (databaseSnapshot Nothing env)
|
||||
|
||||
-- \| 只问数据字典要结构,设了当前库只留它的表
|
||||
schema = IPCStorage $ \env -> do
|
||||
result <- ask env "all_catalogs" ReqAllCatalog $ \case
|
||||
RespCatalog infos -> Just infos
|
||||
_ -> Nothing
|
||||
RespCatalog infos -> Just infos
|
||||
_ -> Nothing
|
||||
pure (fmap (concatMap (visibleTables (envDatabase env))) result)
|
||||
|
||||
-- | 线上表信息转引擎侧统计
|
||||
|
||||
@@ -30,7 +30,7 @@ instance MonadStorage MemoryStorage where
|
||||
-- \| 纯状态视图在求值期间不受外部修改
|
||||
withSchemaGuard _ action = action
|
||||
|
||||
-- | 纯状态写入不受外部结构变更影响
|
||||
-- \| 纯状态写入不受外部结构变更影响
|
||||
withWriteSchemaGuard _ action = action
|
||||
|
||||
-- \| 全表扫描
|
||||
@@ -81,6 +81,11 @@ instance MonadStorage MemoryStorage where
|
||||
db
|
||||
)
|
||||
|
||||
-- \| 在同一状态转换内校验并发布领域
|
||||
alterDomain name validate = MemoryStorage $ \db -> case validate db of
|
||||
Left err -> Right (Left err, db)
|
||||
Right cols -> runMemoryStorage (replaceSchema name cols []) db
|
||||
|
||||
-- \| 原样返回当前库
|
||||
snapshot = MemoryStorage $ \db -> Right (Right db, db)
|
||||
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
module ChuSQL.Core.Engine.Syntax.AST (Statement (..), FromClause (..), JoinKind (..), Expr (..), Subquery (..), Quantifier (..), CompareOp (..), SortDir (..), OrderKey (..), makeSelect, distinctOrdering, orderPosition, orderExpression, orderKeyExpression) where
|
||||
module ChuSQL.Core.Engine.Syntax.AST (Statement (..), DomainDefinition (..), DomainAlter (..), FromClause (..), JoinKind (..), Expr (..), Subquery (..), Quantifier (..), CompareOp (..), SortDir (..), OrderKey (..), makeSelect, distinctOrdering, orderPosition, orderExpression, orderKeyExpression) where
|
||||
|
||||
import ChuSQL.Core.Model (Column (..), ColumnType, Value (..))
|
||||
import ChuSQL.Core.Engine.Runtime.Types (TypeId, TypeExpr, RuntimeValue)
|
||||
import ChuSQL.Core.Engine.Runtime.Functions (FunctionId)
|
||||
import ChuSQL.Core.Engine.Runtime.Types (RuntimeValue, TypeExpr, TypeId)
|
||||
import ChuSQL.Core.Model (Column (..), ColumnType, Value (..))
|
||||
|
||||
-- 语法树:语句、数据来源、条件表达式、排序方向。
|
||||
|
||||
-- | 领域的基础列属性及具名检查条件
|
||||
data DomainDefinition = DomainDefinition Column [(String, String)] deriving (Show, Eq)
|
||||
|
||||
-- | 领域约束及默认值的结构变更
|
||||
data DomainAlter
|
||||
= DomainDefault (Maybe Value)
|
||||
| DomainNullable Bool
|
||||
| DomainAddCheck (Maybe String) String
|
||||
| DomainDropCheck String
|
||||
deriving (Show, Eq)
|
||||
|
||||
data Statement
|
||||
= Select
|
||||
-- | 输出名称与投影表达式,普通列用 Col
|
||||
{ selectItems :: [(String, Expr)]
|
||||
-- ^ 输出名称与投影表达式,普通列用 Col
|
||||
, selectFrom :: FromClause
|
||||
, selectWhere :: Maybe Expr
|
||||
, selectGroupBy :: [String]
|
||||
@@ -22,7 +33,8 @@ data Statement
|
||||
| Delete String (Maybe Expr)
|
||||
| Update String [(String, Expr)] (Maybe Expr)
|
||||
| CreateTable String [(String, Column)]
|
||||
| CreateDomain String ColumnType
|
||||
| CreateDomain String DomainDefinition
|
||||
| AlterDomain String DomainAlter
|
||||
| DropDomain String
|
||||
| ShowDomains
|
||||
| DropTable String
|
||||
@@ -40,35 +52,37 @@ data Statement
|
||||
| ShowGrants
|
||||
| ShowRoles
|
||||
| DropUser String
|
||||
-- | 新角色:权限的载体,用户靠成员资格继承它的权限
|
||||
| CreateRole String
|
||||
-- | 删角色:连同它的授权与成员一起删
|
||||
| DropRole String
|
||||
-- | GRANT 权限 ON 表 TO 角色,末位表示带 WITH GRANT OPTION
|
||||
| GrantPrivileges [String] String String Bool
|
||||
-- | REVOKE 权限 ON 表 FROM 角色
|
||||
| RevokePrivileges [String] String String
|
||||
| -- | 新角色:权限的载体,用户靠成员资格继承它的权限
|
||||
CreateRole String
|
||||
| -- | 删角色:连同它的授权与成员一起删
|
||||
DropRole String
|
||||
| -- | GRANT 权限 ON 表 TO 角色,末位表示带 WITH GRANT OPTION
|
||||
GrantPrivileges [String] String String Bool
|
||||
| -- | REVOKE 权限 ON 表 FROM 角色
|
||||
RevokePrivileges [String] String String
|
||||
| RevokeGrantOption [String] String String
|
||||
-- | GRANT 角色 TO 用户:给用户加角色
|
||||
| GrantRole String [String]
|
||||
-- | REVOKE <角色> FROM <用户>[, ...]
|
||||
| RevokeRole String [String]
|
||||
| -- | GRANT 角色 TO 用户:给用户加角色
|
||||
GrantRole String [String]
|
||||
| -- | REVOKE <角色> FROM <用户>[, ...]
|
||||
RevokeRole String [String]
|
||||
| CreateDatabase String
|
||||
| DropDatabase String
|
||||
| UseDatabase String
|
||||
| ShowDatabases
|
||||
-- | 开事务:BEGIN / START TRANSACTION
|
||||
| BeginTransaction
|
||||
-- | 提交事务
|
||||
| CommitTransaction
|
||||
-- | 回滚事务
|
||||
| RollbackTransaction
|
||||
-- | 建保存点:SAVEPOINT 名字
|
||||
| Savepoint String
|
||||
-- | 回滚到保存点:ROLLBACK TO [SAVEPOINT] 名字
|
||||
| RollbackToSavepoint String
|
||||
-- | 释放保存点:RELEASE [SAVEPOINT] 名字
|
||||
| ReleaseSavepoint String
|
||||
| -- | 开事务:BEGIN / START TRANSACTION
|
||||
BeginTransaction
|
||||
| -- | 串行化快照事务
|
||||
BeginSerializableTransaction
|
||||
| -- | 提交事务
|
||||
CommitTransaction
|
||||
| -- | 回滚事务
|
||||
RollbackTransaction
|
||||
| -- | 建保存点:SAVEPOINT 名字
|
||||
Savepoint String
|
||||
| -- | 回滚到保存点:ROLLBACK TO [SAVEPOINT] 名字
|
||||
RollbackToSavepoint String
|
||||
| -- | 释放保存点:RELEASE [SAVEPOINT] 名字
|
||||
ReleaseSavepoint String
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | 连接类型:内连接与左外连接
|
||||
@@ -86,15 +100,16 @@ data FromClause
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | 子查询:语句,加上它对外层列的引用(自由变量,语义检查时填)
|
||||
data Subquery = Subquery
|
||||
{ subqueryStatement :: Statement
|
||||
, subqueryRefs :: [String]
|
||||
}
|
||||
data Subquery
|
||||
= Subquery
|
||||
{ subqueryStatement :: Statement
|
||||
, subqueryRefs :: [String]
|
||||
}
|
||||
| BoundSubquery
|
||||
{ subqueryStatement :: Statement
|
||||
, subqueryRefs :: [String]
|
||||
, subquerySlots :: [(String, Expr)]
|
||||
}
|
||||
{ subqueryStatement :: Statement
|
||||
, subqueryRefs :: [String]
|
||||
, subquerySlots :: [(String, Expr)]
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | 量词:ANY 至少一行成立,ALL 每一行都成立
|
||||
@@ -219,9 +234,9 @@ resolveDistinctOrdering :: Statement -> Either String [(OrderKey, SortDir)]
|
||||
resolveDistinctOrdering q = mapM resolve (selectOrderBy q)
|
||||
where
|
||||
items = case q of
|
||||
Select { selectItems = projected } -> projected
|
||||
Select{selectItems = projected} -> projected
|
||||
_ -> []
|
||||
-- | 排序键必须出现在去重结果中
|
||||
-- \| 排序键必须出现在去重结果中
|
||||
resolve (key, direction) = do
|
||||
position <- orderPosition (length items) key
|
||||
case position of
|
||||
@@ -230,7 +245,7 @@ resolveDistinctOrdering q = mapM resolve (selectOrderBy q)
|
||||
[slot] -> Right (OrderPosition slot, direction)
|
||||
[] -> Left "DISTINCT: ORDER BY column must appear in the select list"
|
||||
_ -> Left "DISTINCT: ambiguous ORDER BY column"
|
||||
-- | 名称或完整表达式必须对应去重输出项
|
||||
-- \| 名称或完整表达式必须对应去重输出项
|
||||
matches (OrderColumn column) label expression = label == column || expression == Col column
|
||||
matches (OrderExpression key) _ expression = key == expression
|
||||
matches (OrderPosition _) _ _ = False
|
||||
@@ -1,16 +1,16 @@
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# LANGUAGE ImportQualifiedPost #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
module ChuSQL.Core.Engine.Syntax.Parser (parseStatement, parseExpression) where
|
||||
|
||||
import Data.Functor (($>))
|
||||
import ChuSQL.Core.Model (Column (..), ColumnType (..), Value (..), plainColumn)
|
||||
import ChuSQL.Core.Engine.Builtin (Builtin (..), builtinName, builtinNames, builtinNode, compareNode)
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Engine.Runtime.Types
|
||||
import ChuSQL.Core.Engine.Syntax.AST
|
||||
import ChuSQL.Core.Model (Column (..), ColumnType (..), Value (..), plainColumn)
|
||||
import Control.Monad (void)
|
||||
import Control.Monad.Combinators.Expr (Operator (..), makeExprParser)
|
||||
import Data.Char (isAlpha, isAlphaNum, isHexDigit, isSpace, toLower, toUpper)
|
||||
import Data.Functor (($>))
|
||||
import Data.List (intercalate, isInfixOf)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Void (Void)
|
||||
@@ -115,8 +115,9 @@ operatorTable =
|
||||
[ [Prefix (Neg <$ symbol "-")]
|
||||
, [InfixL (Mul <$ symbol "*"), InfixL (Div <$ symbol "/")]
|
||||
, [InfixL (Add <$ symbol "+"), InfixL (Sub <$ symbol "-")]
|
||||
, [ InfixN comparisonOp ]
|
||||
, [ Postfix inPredicate
|
||||
, [InfixN comparisonOp]
|
||||
,
|
||||
[ Postfix inPredicate
|
||||
, Postfix (try quantifiedCmp)
|
||||
, Postfix (IsNotNull <$ try (keyword "is" *> keyword "not" *> keyword "null"))
|
||||
, Postfix (IsNull <$ (keyword "is" *> keyword "null"))
|
||||
@@ -210,22 +211,29 @@ subquerySelect = Subquery <$> queryStatement <*> pure []
|
||||
-- | 读取带具体类型参数的构造器
|
||||
constructorCall :: Parser Expr
|
||||
constructorCall = do
|
||||
cid <- (TypeConstructorId 5 <$ keyword "list") <|> (TypeConstructorId 6 <$ keyword "maybe")
|
||||
<|> (TypeConstructorId 7 <$ keyword "tuple")
|
||||
cid <-
|
||||
(TypeConstructorId 5 <$ keyword "list")
|
||||
<|> (TypeConstructorId 6 <$ keyword "maybe")
|
||||
<|> (TypeConstructorId 7 <$ keyword "tuple")
|
||||
arguments <- between (symbol "<") (symbol ">") (sepBy runtimeTypeP (symbol ","))
|
||||
values <- between (symbol "(") (symbol ")") (sepBy expr (symbol ","))
|
||||
pure (Construct (TypeApply cid arguments) values)
|
||||
|
||||
-- | 解析运行时逻辑类型语法
|
||||
runtimeTypeP :: Parser TypeExpr
|
||||
runtimeTypeP = choice
|
||||
[ intType <$ keyword "int", doubleType <$ keyword "double", boolType <$ keyword "bool"
|
||||
, stringType <$ keyword "string", stringType <$ keyword "str", stringType <$ keyword "text"
|
||||
, listType <$> between (symbol "[") (symbol "]") runtimeTypeP
|
||||
, listType <$> (keyword "list" *> runtimeTypeP)
|
||||
, maybeType <$> (keyword "maybe" *> runtimeTypeP)
|
||||
, tupleType <$> between (symbol "(") (symbol ")") (sepBy runtimeTypeP (symbol ","))
|
||||
]
|
||||
runtimeTypeP =
|
||||
choice
|
||||
[ intType <$ keyword "int"
|
||||
, doubleType <$ keyword "double"
|
||||
, boolType <$ keyword "bool"
|
||||
, stringType <$ keyword "string"
|
||||
, stringType <$ keyword "str"
|
||||
, stringType <$ keyword "text"
|
||||
, listType <$> between (symbol "[") (symbol "]") runtimeTypeP
|
||||
, listType <$> (keyword "list" *> runtimeTypeP)
|
||||
, maybeType <$> (keyword "maybe" *> runtimeTypeP)
|
||||
, tupleType <$> between (symbol "(") (symbol ")") (sepBy runtimeTypeP (symbol ","))
|
||||
]
|
||||
|
||||
-- | 读取待绑定的标量函数调用
|
||||
scalarFunctionCall :: Parser Expr
|
||||
@@ -238,11 +246,11 @@ scalarFunctionCall = do
|
||||
aggregateCall :: Parser Expr
|
||||
aggregateCall = choice (countAll : map named builtinNames)
|
||||
where
|
||||
-- | COUNT(*),没有参数
|
||||
-- \| COUNT(*),没有参数
|
||||
countAll = CountAll <$ try (keyword (builtinName BCountAll) *> between (symbol "(") (symbol ")") (symbol "*"))
|
||||
-- | 表里每一个带参数的内置函数
|
||||
-- \| 表里每一个带参数的内置函数
|
||||
named (name, builtin) = builtinNode builtin <$> aggArg name
|
||||
-- | 读聚合函数的参数
|
||||
-- \| 读聚合函数的参数
|
||||
aggArg name = try (keyword name *> between (symbol "(") (symbol ")") expr)
|
||||
|
||||
-- | 只认字面量,给 DEFAULT 用
|
||||
@@ -252,14 +260,11 @@ literalValue =
|
||||
[ VNull <$ keyword "null"
|
||||
, VBool True <$ keyword "TRUE"
|
||||
, VBool False <$ keyword "FALSE"
|
||||
, try (negateLit <$> floatLit)
|
||||
, try (symbol "-" *> (VFloat . negate <$> try floatLit <|> VInt . negate <$> integer))
|
||||
, try (VFloat <$> floatLit)
|
||||
, VInt <$> integer
|
||||
, VFloat <$> try floatLit
|
||||
, VStr <$> stringLit
|
||||
]
|
||||
where
|
||||
-- | 负浮点字面量
|
||||
negateLit d = VFloat (negate d)
|
||||
|
||||
-- | 一个排序键:输出序号、列名或表达式
|
||||
orderItem :: Parser (OrderKey, SortDir)
|
||||
@@ -365,8 +370,10 @@ tableRef = do
|
||||
-- | 连接类型:内连接 / 左外连接
|
||||
joinKind :: Parser JoinKind
|
||||
joinKind =
|
||||
try ((keyword "left" *> optional (keyword "outer") *> keyword "join")
|
||||
$> LeftJoin)
|
||||
try
|
||||
( (keyword "left" *> optional (keyword "outer") *> keyword "join")
|
||||
$> LeftJoin
|
||||
)
|
||||
<|> try ((keyword "inner" *> keyword "join") $> InnerJoin)
|
||||
<|> (keyword "join" $> InnerJoin)
|
||||
|
||||
@@ -430,9 +437,10 @@ createTableStatement = do
|
||||
|
||||
-- | 读 DROP TABLE
|
||||
dropTableStatement :: Parser Statement
|
||||
dropTableStatement = do keyword "drop"
|
||||
keyword "table"
|
||||
DropTable <$> tableName
|
||||
dropTableStatement = do
|
||||
keyword "drop"
|
||||
keyword "table"
|
||||
DropTable <$> tableName
|
||||
|
||||
-- | 读 CREATE INDEX(列名即索引名)
|
||||
createIndexStatement :: Parser Statement
|
||||
@@ -491,10 +499,11 @@ alterTableStatement = do
|
||||
|
||||
-- | 读 CREATE USER,口令是明文
|
||||
createUserStatement :: Parser Statement
|
||||
createUserStatement = do keyword "create"
|
||||
keyword "user"
|
||||
name <- userName
|
||||
CreateUser name <$> identifiedBy
|
||||
createUserStatement = do
|
||||
keyword "create"
|
||||
keyword "user"
|
||||
name <- userName
|
||||
CreateUser name <$> identifiedBy
|
||||
|
||||
-- | 读 IDENTIFIED BY '口令'
|
||||
identifiedBy :: Parser String
|
||||
@@ -513,15 +522,19 @@ alterUserStatement = do
|
||||
|
||||
-- | 读身份属性开关
|
||||
identityAttribute :: Parser (String, Bool)
|
||||
identityAttribute = choice
|
||||
[ ("allow_sudo_auth", True) <$ keyword "allow_sudo_auth"
|
||||
, ("allow_sudo_auth", False) <$ keyword "noallow_sudo_auth"
|
||||
, ("system_catalog_manager", True) <$ keyword "system_catalog_manager"
|
||||
, ("system_catalog_manager", False) <$ keyword "nosystem_catalog_manager"
|
||||
, ("login", True) <$ keyword "login", ("login", False) <$ keyword "nologin"
|
||||
, ("superuser", True) <$ keyword "superuser", ("superuser", False) <$ keyword "nosuperuser"
|
||||
, ("enabled", True) <$ keyword "enabled", ("enabled", False) <$ keyword "disabled"
|
||||
]
|
||||
identityAttribute =
|
||||
choice
|
||||
[ ("allow_sudo_auth", True) <$ keyword "allow_sudo_auth"
|
||||
, ("allow_sudo_auth", False) <$ keyword "noallow_sudo_auth"
|
||||
, ("system_catalog_manager", True) <$ keyword "system_catalog_manager"
|
||||
, ("system_catalog_manager", False) <$ keyword "nosystem_catalog_manager"
|
||||
, ("login", True) <$ keyword "login"
|
||||
, ("login", False) <$ keyword "nologin"
|
||||
, ("superuser", True) <$ keyword "superuser"
|
||||
, ("superuser", False) <$ keyword "nosuperuser"
|
||||
, ("enabled", True) <$ keyword "enabled"
|
||||
, ("enabled", False) <$ keyword "disabled"
|
||||
]
|
||||
|
||||
-- | 读 ALTER ROLE 属性或口令
|
||||
alterRoleStatement :: Parser Statement
|
||||
@@ -533,9 +546,10 @@ alterRoleStatement = do
|
||||
|
||||
-- | 读 DROP USER
|
||||
dropUserStatement :: Parser Statement
|
||||
dropUserStatement = do keyword "drop"
|
||||
keyword "user"
|
||||
DropUser <$> userName
|
||||
dropUserStatement = do
|
||||
keyword "drop"
|
||||
keyword "user"
|
||||
DropUser <$> userName
|
||||
|
||||
-- | 读 CREATE ROLE
|
||||
createRoleStatement :: Parser Statement
|
||||
@@ -566,9 +580,11 @@ privilegeName =
|
||||
|
||||
-- | ON 后面的授权对象:* 或一张表
|
||||
grantObject :: Parser String
|
||||
grantObject = try (("database:" ++) <$> (keyword "database" *> identifier))
|
||||
<|> try ((++ ".*") <$> (identifier <* symbol "." <* symbol "*"))
|
||||
<|> ("*" <$ symbol "*") <|> (optional (keyword "table") *> tableName)
|
||||
grantObject =
|
||||
try (("database:" ++) <$> (keyword "database" *> identifier))
|
||||
<|> try ((++ ".*") <$> (identifier <* symbol "." <* symbol "*"))
|
||||
<|> ("*" <$ symbol "*")
|
||||
<|> (optional (keyword "table") *> tableName)
|
||||
|
||||
-- | 可选的 WITH GRANT OPTION
|
||||
grantOption :: Parser Bool
|
||||
@@ -603,6 +619,64 @@ revokeStatement = do
|
||||
, if optionOnly then fail "GRANT OPTION FOR requires object privileges" else RevokeRole <$> userName <*> (keyword "from" *> sepBy1 userName (symbol ","))
|
||||
]
|
||||
|
||||
-- | 读取领域及其独立约束
|
||||
createDomainStatement :: Parser Statement
|
||||
createDomainStatement = do
|
||||
name <- keyword "create" *> keyword "domain" *> identifier
|
||||
ty <- keyword "as" *> columnTypeP
|
||||
modifiers <- many domainConstraint
|
||||
pure (CreateDomain name (foldl (flip ($)) (DomainDefinition (plainColumn ty) []) modifiers))
|
||||
|
||||
-- | 读取领域属性或 CHECK 条件
|
||||
domainConstraint :: Parser (DomainDefinition -> DomainDefinition)
|
||||
domainConstraint =
|
||||
choice
|
||||
[ try (keyword "not" *> keyword "null") $> domainNull False
|
||||
, keyword "null" $> domainNull True
|
||||
, keyword "default" *> (domainDefault . Just <$> literalValue)
|
||||
, do
|
||||
name <- optional (keyword "constraint" *> identifier)
|
||||
text <- keyword "check" *> checkText
|
||||
pure (domainCheck (fromMaybe "" name) text)
|
||||
]
|
||||
|
||||
-- | 设置领域非空声明
|
||||
domainNull :: Bool -> DomainDefinition -> DomainDefinition
|
||||
domainNull nullable (DomainDefinition column checks) = DomainDefinition column{columnNullable = nullable} checks
|
||||
|
||||
-- | 设置领域默认值
|
||||
domainDefault :: Maybe Value -> DomainDefinition -> DomainDefinition
|
||||
domainDefault value (DomainDefinition column checks) = DomainDefinition column{columnDefault = value} checks
|
||||
|
||||
-- | 增加领域检查条件
|
||||
domainCheck :: String -> String -> DomainDefinition -> DomainDefinition
|
||||
domainCheck name text (DomainDefinition column checks) = DomainDefinition column (checks ++ [(name, text)])
|
||||
|
||||
-- | 读取领域约束与默认值变更
|
||||
alterDomainStatement :: Parser Statement
|
||||
alterDomainStatement = do
|
||||
name <- keyword "alter" *> keyword "domain" *> identifier
|
||||
action <-
|
||||
choice
|
||||
[ keyword "set"
|
||||
*> choice
|
||||
[ DomainDefault . Just <$> (keyword "default" *> literalValue)
|
||||
, DomainNullable False <$ (keyword "not" *> keyword "null")
|
||||
]
|
||||
, keyword "drop"
|
||||
*> choice
|
||||
[ DomainDefault Nothing <$ keyword "default"
|
||||
, DomainNullable True <$ (keyword "not" *> keyword "null")
|
||||
, DomainDropCheck <$> (keyword "constraint" *> identifier)
|
||||
]
|
||||
, keyword "add"
|
||||
*> ( DomainAddCheck
|
||||
<$> optional (keyword "constraint" *> identifier)
|
||||
<*> (keyword "check" *> checkText)
|
||||
)
|
||||
]
|
||||
pure (AlterDomain name action)
|
||||
|
||||
-- | 建表时的一列:类型后面跟若干约束
|
||||
columnDef :: Parser (String, Column)
|
||||
columnDef = do
|
||||
@@ -615,18 +689,24 @@ columnDef = do
|
||||
columnConstraint :: Parser (Column -> Column)
|
||||
columnConstraint =
|
||||
choice
|
||||
[ try ((keyword "not" *> keyword "null")
|
||||
$> (\ c -> c {columnNullable = False}))
|
||||
, keyword "null" $> (\ c -> c {columnNullable = True})
|
||||
[ try
|
||||
( (keyword "not" *> keyword "null")
|
||||
$> (\c -> c{columnNullable = False})
|
||||
)
|
||||
, keyword "null" $> (\c -> c{columnNullable = True})
|
||||
, keyword "auto_increment"
|
||||
$>
|
||||
(\ c -> c {columnAutoIncrement = True, columnNullable = False})
|
||||
, try ((keyword "primary" *> keyword "key")
|
||||
$>
|
||||
(\ c
|
||||
-> c {columnPrimaryKey = True, columnUnique = True,
|
||||
columnNullable = False}))
|
||||
, keyword "unique" $> (\ c -> c {columnUnique = True})
|
||||
$> (\c -> c{columnAutoIncrement = True, columnNullable = False})
|
||||
, try
|
||||
( (keyword "primary" *> keyword "key")
|
||||
$> ( \c ->
|
||||
c
|
||||
{ columnPrimaryKey = True
|
||||
, columnUnique = True
|
||||
, columnNullable = False
|
||||
}
|
||||
)
|
||||
)
|
||||
, keyword "unique" $> (\c -> c{columnUnique = True})
|
||||
, keyword "default" *> ((\v c -> c{columnDefault = Just v}) <$> literalValue)
|
||||
, keyword "check" *> ((\s c -> c{columnCheck = Just s}) <$> checkText)
|
||||
]
|
||||
@@ -669,13 +749,14 @@ columnTypeP =
|
||||
-- | 解析复合列的稳定类型身份
|
||||
runtimeColumnType :: Parser ColumnType
|
||||
runtimeColumnType = do
|
||||
expression <- choice
|
||||
[ listType <$> between (symbol "[") (symbol "]") runtimeTypeP
|
||||
, listType <$> (keyword "list" *> runtimeTypeP)
|
||||
, maybeType <$> (keyword "maybe" *> runtimeTypeP)
|
||||
, tupleType <$> between (symbol "(") (symbol ")") (sepBy runtimeTypeP (symbol ","))
|
||||
, stringType <$ keyword "string"
|
||||
]
|
||||
expression <-
|
||||
choice
|
||||
[ listType <$> between (symbol "[") (symbol "]") runtimeTypeP
|
||||
, listType <$> (keyword "list" *> runtimeTypeP)
|
||||
, maybeType <$> (keyword "maybe" *> runtimeTypeP)
|
||||
, tupleType <$> between (symbol "(") (symbol ")") (sepBy runtimeTypeP (symbol ","))
|
||||
, stringType <$ keyword "string"
|
||||
]
|
||||
case resolveType builtinTypes expression of
|
||||
Left err -> fail err
|
||||
Right descriptor -> pure (CRuntime (descriptorId descriptor))
|
||||
@@ -734,7 +815,7 @@ queryStatement = withStatement <|> selectStatement
|
||||
expandWith :: [(String, [String], Statement)] -> Statement -> Either String Statement
|
||||
expandWith defs main = go [] defs
|
||||
where
|
||||
-- | 前面的 CTE 已进环境,接着展开本体
|
||||
-- \| 前面的 CTE 已进环境,接着展开本体
|
||||
go env [] = expandStatement env main
|
||||
go env ((name, cols, body) : rest) = do
|
||||
body' <- expandStatement env body
|
||||
@@ -749,14 +830,15 @@ cteKey = map toLower
|
||||
renameColumns :: String -> [String] -> Statement -> Either String Statement
|
||||
renameColumns _ [] q = Right q
|
||||
renameColumns name cols (SelectDistinct q) = SelectDistinct <$> renameColumns name cols q
|
||||
renameColumns name cols q@Select { selectItems = items } = do
|
||||
renameColumns name cols q@Select{selectItems = items} = do
|
||||
checkCteArity name cols (map snd items)
|
||||
ordering <- if all (\(label, expression) -> expression == Col label) items
|
||||
then Right (selectOrderBy q)
|
||||
else mapM renameOrder (selectOrderBy q)
|
||||
Right q { selectItems = zip cols (map snd items), selectOrderBy = ordering }
|
||||
ordering <-
|
||||
if all (\(columnName, expression) -> expression == Col columnName) items
|
||||
then Right (selectOrderBy q)
|
||||
else mapM renameOrder (selectOrderBy q)
|
||||
Right q{selectItems = zip cols (map snd items), selectOrderBy = ordering}
|
||||
where
|
||||
-- | 列清单同步改写内层排序输出别名
|
||||
-- \| 列清单同步改写内层排序输出别名
|
||||
renameOrder (OrderColumn column, direction) = case [new | ((old, _), new) <- zip items cols, old == column] of
|
||||
[] -> Right (OrderColumn column, direction)
|
||||
[new] -> Right (OrderColumn new, direction)
|
||||
@@ -774,16 +856,16 @@ checkCteArity name cols out
|
||||
expandStatement :: [(String, Statement)] -> Statement -> Either String Statement
|
||||
expandStatement env stmt = case stmt of
|
||||
SelectDistinct q -> SelectDistinct <$> expandStatement env q
|
||||
q@Select { selectFrom = fromC, selectItems = items, selectWhere = mWhere } -> do
|
||||
q@Select{selectFrom = fromC, selectItems = items, selectWhere = mWhere} -> do
|
||||
fromC' <- expandClause env fromC
|
||||
items' <- traverse (\(name, e) -> fmap (name,) (expandExpr env e)) items
|
||||
w <- traverse (expandExpr env) mWhere
|
||||
having <- traverse (expandExpr env) (selectHaving q)
|
||||
ordering <- mapM expandOrder (selectOrderBy q)
|
||||
Right q { selectFrom = fromC', selectItems = items', selectWhere = w, selectHaving = having, selectOrderBy = ordering }
|
||||
Right q{selectFrom = fromC', selectItems = items', selectWhere = w, selectHaving = having, selectOrderBy = ordering}
|
||||
_ -> Right stmt
|
||||
where
|
||||
-- | 展开排序表达式中的 CTE 子查询
|
||||
-- \| 展开排序表达式中的 CTE 子查询
|
||||
expandOrder (OrderExpression expression, direction) = (,) . OrderExpression <$> expandExpr env expression <*> pure direction
|
||||
expandOrder ordering = Right ordering
|
||||
|
||||
@@ -832,10 +914,10 @@ expandExpr env e = case e of
|
||||
QuantCmp op x sq q -> (\x' sq' -> QuantCmp op x' sq' q) <$> go x <*> goSub sq
|
||||
_ -> Right e
|
||||
where
|
||||
-- | 继续看子表达式
|
||||
-- \| 继续看子表达式
|
||||
go = expandExpr env
|
||||
-- | 继续看子查询里的语句
|
||||
goSub sq = (\stmt -> sq { subqueryStatement = stmt }) <$> expandStatement env (subqueryStatement sq)
|
||||
-- \| 继续看子查询里的语句
|
||||
goSub sq = (\stmt -> sq{subqueryStatement = stmt}) <$> expandStatement env (subqueryStatement sq)
|
||||
|
||||
-- | 读 INSERT,可一次插多行
|
||||
insertStatement :: Parser Statement
|
||||
@@ -878,15 +960,18 @@ updateStatement = do
|
||||
-- | 事务控制语句
|
||||
transactionStatement :: Parser Statement
|
||||
transactionStatement =
|
||||
try (BeginTransaction <$ (keyword "start" *> keyword "transaction"))
|
||||
<|> (BeginTransaction <$ (keyword "begin" *> optional (keyword "transaction")))
|
||||
(beginMode =<< (try (keyword "start" *> keyword "transaction") <|> (keyword "begin" *> void (optional (keyword "transaction")))))
|
||||
<|> (CommitTransaction <$ (keyword "commit" *> optional (keyword "transaction")))
|
||||
<|> try (RollbackToSavepoint <$> rollbackToName)
|
||||
<|> (RollbackTransaction <$ (keyword "rollback" *> optional (keyword "transaction")))
|
||||
<|> (Savepoint <$> (keyword "savepoint" *> identifier))
|
||||
<|> (ReleaseSavepoint <$> (keyword "release" *> optional (keyword "savepoint") *> identifier))
|
||||
where
|
||||
-- | ROLLBACK TO [SAVEPOINT] 名字
|
||||
-- 解析可选的串行化隔离级别。
|
||||
beginMode _ = do
|
||||
mode <- optional (keyword "isolation" *> keyword "level" *> keyword "serializable")
|
||||
pure (maybe BeginTransaction (const BeginSerializableTransaction) mode)
|
||||
-- \| ROLLBACK TO [SAVEPOINT] 名字
|
||||
rollbackToName =
|
||||
keyword "rollback" *> keyword "to" *> optional (keyword "savepoint") *> identifier
|
||||
|
||||
@@ -897,7 +982,7 @@ parseStatement input =
|
||||
Left err -> Left (safeParseError input err)
|
||||
Right q -> Right q
|
||||
where
|
||||
-- | 按关键字分派到各语句解析器
|
||||
-- \| 按关键字分派到各语句解析器
|
||||
statementP =
|
||||
withStatement
|
||||
<|> transactionStatement
|
||||
@@ -907,7 +992,8 @@ parseStatement input =
|
||||
<|> (UseDatabase <$> (keyword "use" *> identifier))
|
||||
<|> try (ShowDatabases <$ (keyword "show" *> keyword "databases"))
|
||||
<|> try (ShowDomains <$ (keyword "show" *> keyword "domains"))
|
||||
<|> try (CreateDomain <$> (keyword "create" *> keyword "domain" *> identifier) <*> (keyword "as" *> columnTypeP))
|
||||
<|> try createDomainStatement
|
||||
<|> try alterDomainStatement
|
||||
<|> try (DropDomain <$> (keyword "drop" *> keyword "domain" *> identifier))
|
||||
<|> insertStatement
|
||||
<|> deleteStatement
|
||||
|
||||
@@ -19,49 +19,87 @@ spec = do
|
||||
`shouldBe` True
|
||||
attributeClosure [] ["a"] `shouldBe` ["a"]
|
||||
it "accepts BCNF with a declared key" $ do
|
||||
fmap (map verdict . findings) (analyzeNormalization schema
|
||||
(Declaration [["a"]] [] True (Just True)))
|
||||
fmap
|
||||
(map verdict . findings)
|
||||
( analyzeNormalization
|
||||
schema
|
||||
(Declaration [["a"]] [] True (Just True))
|
||||
)
|
||||
`shouldBe` Right (replicate 4 Satisfied)
|
||||
it "detects partial dependencies of composite keys" $ do
|
||||
fmap (map verdict . findings) (analyzeNormalization schema
|
||||
(Declaration [["a", "b"]] [Dependency ["a"] ["c"]] True (Just True)))
|
||||
fmap
|
||||
(map verdict . findings)
|
||||
( analyzeNormalization
|
||||
schema
|
||||
(Declaration [["a", "b"]] [Dependency ["a"] ["c"]] True (Just True))
|
||||
)
|
||||
`shouldBe` Right [Satisfied, Violated, Violated, Violated]
|
||||
it "detects transitive dependencies and a preserving split" $ do
|
||||
let analysis = analyzeNormalization schema
|
||||
(Declaration [["a"]] [Dependency ["b"] ["c"]] True (Just True))
|
||||
let analysis =
|
||||
analyzeNormalization
|
||||
schema
|
||||
(Declaration [["a"]] [Dependency ["b"] ["c"]] True (Just True))
|
||||
fmap (map verdict . findings) analysis
|
||||
`shouldBe` Right [Satisfied, Satisfied, Violated, Violated]
|
||||
fmap suggestions analysis
|
||||
`shouldBe` Right [Decomposition [["b", "c"], ["a", "b"]] Satisfied Satisfied]
|
||||
it "distinguishes 3NF from BCNF and detects dependency loss" $ do
|
||||
let analysis = analyzeNormalization schema
|
||||
(Declaration [["a", "b"]] [Dependency ["c"] ["b"]] True (Just True))
|
||||
let analysis =
|
||||
analyzeNormalization
|
||||
schema
|
||||
(Declaration [["a", "b"]] [Dependency ["c"] ["b"]] True (Just True))
|
||||
fmap (map verdict . findings) analysis
|
||||
`shouldBe` Right [Satisfied, Satisfied, Satisfied, Violated]
|
||||
fmap suggestions analysis
|
||||
`shouldBe` Right [Decomposition [["b", "c"], ["a", "c"]] Satisfied Violated]
|
||||
it "does not certify incomplete dependencies" $ do
|
||||
fmap (map verdict . findings) (analyzeNormalization schema
|
||||
(Declaration [["a"]] [] False (Just True)))
|
||||
fmap
|
||||
(map verdict . findings)
|
||||
( analyzeNormalization
|
||||
schema
|
||||
(Declaration [["a"]] [] False (Just True))
|
||||
)
|
||||
`shouldBe` Right [Satisfied, Insufficient, Insufficient, Insufficient]
|
||||
it "requires an atomicity declaration" $ do
|
||||
fmap (map verdict . findings) (analyzeNormalization schema
|
||||
(Declaration [] [] True Nothing)) `shouldBe` Right (replicate 4 Insufficient)
|
||||
fmap
|
||||
(map verdict . findings)
|
||||
( analyzeNormalization
|
||||
schema
|
||||
(Declaration [] [] True Nothing)
|
||||
)
|
||||
`shouldBe` Right (replicate 4 Insufficient)
|
||||
it "propagates a declared 1NF violation" $ do
|
||||
fmap (map verdict . findings) (analyzeNormalization schema
|
||||
(Declaration [] [] True (Just False))) `shouldBe` Right (replicate 4 Violated)
|
||||
fmap
|
||||
(map verdict . findings)
|
||||
( analyzeNormalization
|
||||
schema
|
||||
(Declaration [] [] True (Just False))
|
||||
)
|
||||
`shouldBe` Right (replicate 4 Violated)
|
||||
it "rejects unknown attributes" $ do
|
||||
analyzeNormalization schema (Declaration [] [Dependency ["z"] ["a"]] True (Just True))
|
||||
`shouldBe` Left "normalization: unknown or duplicate dependency attributes"
|
||||
it "rejects nonminimal declared candidate keys" $ do
|
||||
analyzeNormalization schema (Declaration [["a", "b"]]
|
||||
[Dependency ["a"] ["b", "c"]] True (Just True))
|
||||
analyzeNormalization
|
||||
schema
|
||||
( Declaration
|
||||
[["a", "b"]]
|
||||
[Dependency ["a"] ["b", "c"]]
|
||||
True
|
||||
(Just True)
|
||||
)
|
||||
`shouldBe` Left "normalization: declared candidate key is not minimal"
|
||||
it "includes the empty candidate key for constant relations" $ do
|
||||
fmap candidateKeys (analyzeNormalization schema
|
||||
(Declaration [] [Dependency [] ["a", "b", "c"]] True (Just True))) `shouldBe` Right [[]]
|
||||
fmap
|
||||
candidateKeys
|
||||
( analyzeNormalization
|
||||
schema
|
||||
(Declaration [] [Dependency [] ["a", "b", "c"]] True (Just True))
|
||||
)
|
||||
`shouldBe` Right [[]]
|
||||
it "rejects excessive exhaustive searches" $ do
|
||||
analyzeNormalization [(show n, TInt) | n <- [1 :: Int .. 13]]
|
||||
analyzeNormalization
|
||||
[(show n, TInt) | n <- [1 :: Int .. 13]]
|
||||
(Declaration [] [] True (Just True))
|
||||
`shouldBe` Left "normalization: exhaustive analysis supports at most 12 columns"
|
||||
where
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+543
-180
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user