2026-09-25 17:41:51 +08:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
module ChuSQL.Server.Backend (
|
2026-09-25 17:41:51 +08:00
|
|
|
StatementResult (..),
|
|
|
|
|
Backend (..),
|
|
|
|
|
columnsFromStatement,
|
2026-10-01 20:48:38 +08:00
|
|
|
currentStamp,
|
|
|
|
|
exprTables,
|
|
|
|
|
inDatabase,
|
|
|
|
|
internalTable,
|
|
|
|
|
grantsTable,
|
2026-09-25 17:41:51 +08:00
|
|
|
ipcBackend,
|
|
|
|
|
memoryBackend,
|
2026-10-01 20:48:38 +08:00
|
|
|
membersTable,
|
|
|
|
|
rolesTable,
|
|
|
|
|
statementNeedsDatabase,
|
|
|
|
|
statementTables,
|
|
|
|
|
systemDatabaseName,
|
2026-09-25 17:41:51 +08:00
|
|
|
tableInfoOf,
|
2026-10-01 20:48:38 +08:00
|
|
|
tableRefsOf,
|
2026-09-25 17:41:51 +08:00
|
|
|
) where
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
import ChuSQL.Core.Engine (runStatement, runStatementM)
|
|
|
|
|
import ChuSQL.Core.Model
|
|
|
|
|
import ChuSQL.Core.Engine.Semantic (prepare)
|
|
|
|
|
import ChuSQL.Core.Engine.Storage (MonadStorage (schema))
|
|
|
|
|
import ChuSQL.Core.Engine.Storage.IPC (
|
2026-10-01 20:48:38 +08:00
|
|
|
Account (..),
|
2026-09-25 17:41:51 +08:00
|
|
|
IPCStorage (..),
|
|
|
|
|
Request (..),
|
|
|
|
|
Response (..),
|
|
|
|
|
SchemaColumn (..),
|
|
|
|
|
TableInfo (..),
|
2026-10-02 14:53:02 +08:00
|
|
|
sendRawRequest,
|
2026-09-25 17:41:51 +08:00
|
|
|
sendRequest,
|
|
|
|
|
)
|
2026-10-02 14:53:02 +08:00
|
|
|
import ChuSQL.Core.Engine.Syntax.AST
|
|
|
|
|
import ChuSQL.Core.Engine.Syntax.Parser (parseStatement)
|
2026-09-25 17:41:51 +08:00
|
|
|
import Control.Concurrent.MVar (MVar, modifyMVar, newMVar, readMVar, withMVar)
|
|
|
|
|
import Data.List (nub)
|
2026-10-01 20:48:38 +08:00
|
|
|
import Data.Text (Text)
|
|
|
|
|
import Data.Time (getCurrentTime)
|
|
|
|
|
import Data.Time.Format (defaultTimeLocale, formatTime)
|
|
|
|
|
import qualified Data.Aeson as A
|
|
|
|
|
import qualified Data.ByteString.Lazy.Char8 as BL
|
|
|
|
|
import qualified Data.Text as T
|
2026-09-25 17:41:51 +08:00
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- 后端抽象:跑语句、取数据字典与账号,分 IPC(真存储)和内存(测试)两套实现。
|
2026-09-25 17:41:51 +08:00
|
|
|
|
|
|
|
|
data StatementResult = StatementResult
|
|
|
|
|
{ srColumns :: [String]
|
|
|
|
|
, srRows :: [Row]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data Backend = Backend
|
|
|
|
|
{ beStatement :: String -> IO (Either String StatementResult)
|
|
|
|
|
, beCatalog :: IO (Either String [TableInfo])
|
|
|
|
|
, bePing :: IO Bool
|
2026-10-01 20:48:38 +08:00
|
|
|
, beAccounts :: Request -> IO (Either String [Account])
|
|
|
|
|
, beWithDatabase :: String -> Backend
|
|
|
|
|
, beDatabases :: IO (Either String [String])
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 原样转发一条存储请求
|
|
|
|
|
, beStorage :: A.Value -> IO (Either String A.Value)
|
2026-09-25 17:41:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-- | 按给定列名取列,固定列顺序
|
|
|
|
|
columnsFromStatement :: Database -> Statement -> [String]
|
2026-10-01 20:48:38 +08:00
|
|
|
columnsFromStatement db stmt = case prepare db stmt of
|
|
|
|
|
Right (Select cols _ _ _ _ _) -> cols
|
|
|
|
|
Right (SelectExpr items _ _ _ _ _) -> map fst items
|
2026-09-25 17:41:51 +08:00
|
|
|
_ -> []
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 走本地存储链路的后端
|
2026-09-25 17:41:51 +08:00
|
|
|
ipcBackend :: IO Backend
|
|
|
|
|
ipcBackend = do
|
|
|
|
|
lock <- newMVar ()
|
2026-10-01 20:48:38 +08:00
|
|
|
let backend current = Backend
|
|
|
|
|
{ beStatement = \sql -> withMVar lock $ \_ -> runIpc current sql
|
2026-09-25 17:41:51 +08:00
|
|
|
, beCatalog = withMVar lock $ \_ -> do
|
2026-10-01 20:48:38 +08:00
|
|
|
response <- sendRequest (ReqInDatabase current ReqListCatalog)
|
|
|
|
|
pure $ case response of
|
|
|
|
|
RespCatalog infos -> Right infos
|
|
|
|
|
RespError err -> Left err
|
|
|
|
|
_ -> Left "unexpected catalog response"
|
2026-09-25 17:41:51 +08:00
|
|
|
, bePing = withMVar lock $ \_ -> do
|
|
|
|
|
resp <- sendRequest ReqPing
|
|
|
|
|
pure (case resp of RespPong -> True; _ -> False)
|
2026-10-01 20:48:38 +08:00
|
|
|
, beAccounts = \req -> withMVar lock $ \_ -> do
|
|
|
|
|
response <- sendRequest req
|
|
|
|
|
pure $ case response of
|
|
|
|
|
RespAccounts accounts -> Right accounts
|
|
|
|
|
RespError err -> Left err
|
|
|
|
|
_ -> Left "unexpected account response"
|
|
|
|
|
, beWithDatabase = backend
|
|
|
|
|
, beDatabases = withMVar lock $ \_ -> do
|
|
|
|
|
response <- sendRequest (ReqDatabase "list_databases" "")
|
|
|
|
|
pure $ case response of
|
|
|
|
|
RespTables names -> Right names
|
|
|
|
|
RespError err -> Left err
|
|
|
|
|
_ -> Left "unexpected database response"
|
2026-10-02 14:53:02 +08:00
|
|
|
, beStorage = \payload -> withMVar lock $ \_ -> runRaw payload
|
2026-09-25 17:41:51 +08:00
|
|
|
}
|
2026-10-01 20:48:38 +08:00
|
|
|
-- 没有默认库:没选库时 current 是空串,任何请求都得先选库
|
|
|
|
|
pure (backend "")
|
2026-09-25 17:41:51 +08:00
|
|
|
where
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 转发一条存储请求并解析响应
|
|
|
|
|
runRaw :: A.Value -> IO (Either String A.Value)
|
|
|
|
|
runRaw payload = do
|
|
|
|
|
response <- sendRawRequest (BL.toStrict (A.encode payload))
|
|
|
|
|
pure $ case response of
|
|
|
|
|
Left err -> Left err
|
|
|
|
|
Right raw -> case A.eitherDecodeStrict raw of
|
|
|
|
|
Left err -> Left ("storage response is not valid JSON: " ++ err)
|
|
|
|
|
Right value -> Right value
|
|
|
|
|
|
|
|
|
|
-- | 走存储链路跑一条语句
|
2026-10-01 20:48:38 +08:00
|
|
|
runIpc :: String -> String -> IO (Either String StatementResult)
|
|
|
|
|
runIpc current sql = case fmap (inDatabase current) (parseStatement sql) of
|
2026-09-25 17:41:51 +08:00
|
|
|
Left e -> pure (Left e)
|
|
|
|
|
Right stmt -> do
|
|
|
|
|
result <- runIPCStorage (runStatementM stmt)
|
|
|
|
|
case result of
|
|
|
|
|
Left e -> pure (Left e)
|
|
|
|
|
Right rows -> do
|
|
|
|
|
cols <- case rows of
|
|
|
|
|
[] -> do
|
|
|
|
|
db <- runIPCStorage (schema :: IPCStorage Database)
|
|
|
|
|
pure (columnsFromStatement db stmt)
|
|
|
|
|
(r : _) -> pure (map fst r)
|
|
|
|
|
pure (Right (StatementResult cols rows))
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 测试用内存后端,状态是一个 Database
|
2026-10-01 20:48:38 +08:00
|
|
|
memoryBackend :: String -> MVar Database -> Backend
|
|
|
|
|
memoryBackend = memoryBackendWith False
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 构造内存后端,Bool 决定普通库还是系统库视角
|
2026-10-01 20:48:38 +08:00
|
|
|
memoryBackendWith :: Bool -> String -> MVar Database -> Backend
|
|
|
|
|
memoryBackendWith trusted name ref =
|
2026-09-25 17:41:51 +08:00
|
|
|
Backend
|
2026-10-01 20:48:38 +08:00
|
|
|
{ beStatement = \sql -> case parseStatement sql of
|
|
|
|
|
Left e -> pure (Left e)
|
|
|
|
|
-- 内存夹具也有"库"的概念:USE 只能切到它有的两个名字(自己 + system),
|
|
|
|
|
-- 与 beDatabases 口径一致;引擎的内存存储不实现库管理,所以拦在这里。
|
|
|
|
|
Right (UseDatabase target)
|
|
|
|
|
| target == name || target == systemDatabaseName -> pure (Right (StatementResult [] []))
|
|
|
|
|
| otherwise -> pure (Left ("unknown database: " ++ target))
|
|
|
|
|
Right stmt -> modifyMVar ref $ \db -> case runStatement (scoped db) stmt of
|
2026-09-25 17:41:51 +08:00
|
|
|
Left e -> pure (db, Left e)
|
2026-10-01 20:48:38 +08:00
|
|
|
Right (db', rows)
|
|
|
|
|
| any (hidden . fst) db' -> pure (db, Left "reserved system table")
|
|
|
|
|
| otherwise -> pure (filter (hidden . fst) db ++ db', Right (StatementResult (colsOf (scoped db) stmt rows) rows))
|
2026-09-25 17:41:51 +08:00
|
|
|
, beCatalog = do
|
|
|
|
|
db <- readMVar ref
|
2026-10-01 20:48:38 +08:00
|
|
|
pure (Right (map tableInfoOf (filter (not . internalTable . fst) db)))
|
2026-09-25 17:41:51 +08:00
|
|
|
, bePing = pure True
|
2026-10-01 20:48:38 +08:00
|
|
|
, beAccounts = \req -> do
|
|
|
|
|
stamp <- currentStamp
|
|
|
|
|
modifyMVar ref $ \db -> case memoryAccounts db stamp req of
|
|
|
|
|
Left err -> pure (db, Left err)
|
|
|
|
|
Right accounts -> pure ((usersTable, Table usersTable [("account", TStr)]
|
2026-10-03 02:17:46 +08:00
|
|
|
[[("account", VStr (BL.unpack (A.encode a))) ] | a <- accounts] Nothing) : filter ((/= usersTable) . fst) db, Right accounts)
|
2026-10-01 20:48:38 +08:00
|
|
|
, beWithDatabase = withDatabase
|
|
|
|
|
, beDatabases = pure (Right (nub [name, systemDatabaseName]))
|
2026-10-02 14:53:02 +08:00
|
|
|
, beStorage = \_ -> pure (Left "storage is not available in this backend")
|
2026-09-25 17:41:51 +08:00
|
|
|
}
|
|
|
|
|
where
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 按视角过滤库里的表
|
2026-10-01 20:48:38 +08:00
|
|
|
scoped = if trusted then filter (not . reserved . fst) else visible
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 该视角下要隐藏的表
|
2026-10-01 20:48:38 +08:00
|
|
|
hidden = if trusted then reserved else internalTable
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 取结果列名,空结果回落到解析
|
2026-09-25 17:41:51 +08:00
|
|
|
colsOf db stmt rows = case rows of
|
|
|
|
|
[] -> columnsFromStatement db stmt
|
|
|
|
|
(r : _) -> map fst r
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 按目标库名挑视角
|
2026-10-01 20:48:38 +08:00
|
|
|
withDatabase target
|
|
|
|
|
| target == systemDatabaseName = memoryBackendWith True name ref
|
|
|
|
|
| target == name = memoryBackendWith False name ref
|
|
|
|
|
| null target = noDatabaseBackend (memoryBackendWith False name ref)
|
|
|
|
|
| otherwise = unavailableDatabase ("unknown database: " ++ target) (memoryBackendWith False name ref)
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 把后端的语句与字典都改成同一条错误
|
2026-10-01 20:48:38 +08:00
|
|
|
unavailableDatabase :: String -> Backend -> Backend
|
|
|
|
|
unavailableDatabase message backend = backend
|
|
|
|
|
{ beStatement = \_ -> pure (Left message)
|
|
|
|
|
, beCatalog = pure (Left message)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 未选库时的视角:只放行不碰表的语句
|
2026-10-01 20:48:38 +08:00
|
|
|
noDatabaseBackend :: Backend -> Backend
|
|
|
|
|
noDatabaseBackend backend = backend
|
|
|
|
|
{ beStatement = \sql -> case parseStatement sql of
|
|
|
|
|
Left e -> pure (Left e)
|
|
|
|
|
Right stmt
|
|
|
|
|
| statementNeedsDatabase stmt -> pure (Left "no database selected")
|
|
|
|
|
| otherwise -> beStatement backend sql
|
|
|
|
|
, beCatalog = pure (Left "no database selected")
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 给语句里的裸表名补上当前库前缀,子查询里的表名也一起改
|
2026-10-01 20:48:38 +08:00
|
|
|
inDatabase :: String -> Statement -> Statement
|
|
|
|
|
inDatabase current = walk
|
|
|
|
|
where
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 递归改写语句里的表名
|
2026-10-01 20:48:38 +08:00
|
|
|
walk :: Statement -> Statement
|
|
|
|
|
walk stmt = case stmt of
|
|
|
|
|
q@Select{} ->
|
|
|
|
|
q
|
|
|
|
|
{ selectFrom = source (selectFrom q)
|
|
|
|
|
, selectWhere = fmap expr (selectWhere q)
|
|
|
|
|
}
|
|
|
|
|
q@SelectExpr{} ->
|
|
|
|
|
q
|
|
|
|
|
{ selectItems = [(label, expr e) | (label, e) <- selectItems q]
|
|
|
|
|
, selectFrom = source (selectFrom q)
|
|
|
|
|
, selectWhere = fmap expr (selectWhere q)
|
|
|
|
|
}
|
|
|
|
|
Insert t cols rows -> Insert (table t) cols (map (map expr) rows)
|
|
|
|
|
Delete t cond -> Delete (table t) (fmap expr cond)
|
|
|
|
|
Update t assigns cond -> Update (table t) [(c, expr e) | (c, e) <- assigns] (fmap expr cond)
|
|
|
|
|
CreateTable t cols -> CreateTable (table t) cols
|
|
|
|
|
DropTable t -> DropTable (table t)
|
|
|
|
|
CreateIndex t c -> CreateIndex (table t) c
|
|
|
|
|
DropIndex t c -> DropIndex (table t) c
|
|
|
|
|
DropColumn t c -> DropColumn (table t) c
|
|
|
|
|
AddColumn t def -> AddColumn (table t) def
|
|
|
|
|
RenameColumn t old new -> RenameColumn (table t) old new
|
|
|
|
|
AlterColumnType t c ty -> AlterColumnType (table t) c ty
|
|
|
|
|
AlterColumnDefault t c v -> AlterColumnDefault (table t) c v
|
|
|
|
|
AlterColumnNull t c b -> AlterColumnNull (table t) c b
|
|
|
|
|
other -> other
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 给单个表名补库前缀,已限定或没选库就原样
|
2026-10-01 20:48:38 +08:00
|
|
|
table t | '.' `elem` t || null current = t
|
|
|
|
|
| otherwise = current ++ "." ++ t
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 改写 FROM 子句
|
2026-10-01 20:48:38 +08:00
|
|
|
source FromUnit = FromUnit
|
|
|
|
|
source (FromTable a t) = FromTable a (table t)
|
|
|
|
|
source (FromJoin k l a t e) = FromJoin k (source l) a (table t) (expr e)
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 改写表达式里的子查询
|
2026-10-01 20:48:38 +08:00
|
|
|
expr (ScalarSub sq) = ScalarSub (sub sq)
|
|
|
|
|
expr (ExistsSub sq neg) = ExistsSub (sub sq) neg
|
|
|
|
|
expr (InSub a sq neg) = InSub (expr a) (sub sq) neg
|
|
|
|
|
expr (InList a items neg) = InList (expr a) (map expr items) neg
|
|
|
|
|
expr (Add a b) = Add (expr a) (expr b)
|
|
|
|
|
expr (Sub a b) = Sub (expr a) (expr b)
|
|
|
|
|
expr (Mul a b) = Mul (expr a) (expr b)
|
|
|
|
|
expr (Div a b) = Div (expr a) (expr b)
|
|
|
|
|
expr (Neg a) = Neg (expr a)
|
|
|
|
|
expr (Gt a b) = Gt (expr a) (expr b)
|
|
|
|
|
expr (Lt a b) = Lt (expr a) (expr b)
|
|
|
|
|
expr (Eq a b) = Eq (expr a) (expr b)
|
|
|
|
|
expr (And a b) = And (expr a) (expr b)
|
|
|
|
|
expr (Or a b) = Or (expr a) (expr b)
|
|
|
|
|
expr (IsNull a) = IsNull (expr a)
|
|
|
|
|
expr (IsNotNull a) = IsNotNull (expr a)
|
|
|
|
|
expr (CountOf a) = CountOf (expr a)
|
|
|
|
|
expr (SumOf a) = SumOf (expr a)
|
|
|
|
|
expr (AvgOf a) = AvgOf (expr a)
|
|
|
|
|
expr (MinOf a) = MinOf (expr a)
|
|
|
|
|
expr (MaxOf a) = MaxOf (expr a)
|
|
|
|
|
expr other = other
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 改写一个子查询
|
2026-10-01 20:48:38 +08:00
|
|
|
sub sq = sq {subqueryStatement = walk (subqueryStatement sq)}
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 语句里出现的表
|
2026-10-01 20:48:38 +08:00
|
|
|
statementTables :: Statement -> [Text]
|
|
|
|
|
statementTables stmt = nub (case stmt of
|
|
|
|
|
Select{} ->
|
|
|
|
|
tableRefsOf (selectFrom stmt)
|
|
|
|
|
++ concatMap exprTables (maybe [] (: []) (selectWhere stmt))
|
|
|
|
|
SelectExpr{} ->
|
|
|
|
|
tableRefsOf (selectFrom stmt)
|
|
|
|
|
++ concatMap exprTables (maybe [] (: []) (selectWhere stmt) ++ map snd (selectItems stmt))
|
|
|
|
|
Insert table _ rows -> T.pack table : concatMap exprTables (concat rows)
|
|
|
|
|
Update table assigns cond -> T.pack table : concatMap exprTables (map snd assigns ++ maybe [] (: []) cond)
|
|
|
|
|
Delete table cond -> T.pack table : concatMap exprTables (maybe [] (: []) cond)
|
|
|
|
|
CreateTable table _ -> [T.pack table]
|
|
|
|
|
DropTable table -> [T.pack table]
|
|
|
|
|
CreateIndex table _ -> [T.pack table]
|
|
|
|
|
DropIndex table _ -> [T.pack table]
|
|
|
|
|
DropColumn table _ -> [T.pack table]
|
|
|
|
|
AddColumn table _ -> [T.pack table]
|
|
|
|
|
RenameColumn table _ _ -> [T.pack table]
|
|
|
|
|
AlterColumnType table _ _ -> [T.pack table]
|
|
|
|
|
AlterColumnDefault table _ _ -> [T.pack table]
|
|
|
|
|
AlterColumnNull table _ _ -> [T.pack table]
|
|
|
|
|
_ -> [])
|
|
|
|
|
|
|
|
|
|
-- | 语句需不需要一个当前库:出现的表里有没写库名的就必须要
|
|
|
|
|
statementNeedsDatabase :: Statement -> Bool
|
|
|
|
|
statementNeedsDatabase stmt = any (not . T.any (== '.')) (statementTables stmt)
|
|
|
|
|
|
|
|
|
|
-- | FROM 子句里的所有表
|
|
|
|
|
tableRefsOf :: FromClause -> [Text]
|
|
|
|
|
tableRefsOf FromUnit = []
|
|
|
|
|
tableRefsOf (FromTable _ name) = [T.pack name]
|
|
|
|
|
tableRefsOf (FromJoin _ left _ name cond) = tableRefsOf left ++ [T.pack name] ++ exprTables cond
|
|
|
|
|
|
|
|
|
|
-- | 表达式里出现的表(子查询是唯一的来路)
|
|
|
|
|
exprTables :: Expr -> [Text]
|
|
|
|
|
exprTables expr = case expr of
|
|
|
|
|
ScalarSub sub -> statementTables (subqueryStatement sub)
|
|
|
|
|
ExistsSub sub _ -> statementTables (subqueryStatement sub)
|
|
|
|
|
InSub value sub _ -> exprTables value ++ statementTables (subqueryStatement sub)
|
|
|
|
|
InList value items _ -> concatMap exprTables (value : items)
|
|
|
|
|
Add a b -> both a b
|
|
|
|
|
Sub a b -> both a b
|
|
|
|
|
Mul a b -> both a b
|
|
|
|
|
Div a b -> both a b
|
|
|
|
|
Gt a b -> both a b
|
|
|
|
|
Lt a b -> both a b
|
|
|
|
|
Eq a b -> both a b
|
|
|
|
|
And a b -> both a b
|
|
|
|
|
Or a b -> both a b
|
|
|
|
|
Neg a -> exprTables a
|
|
|
|
|
IsNull a -> exprTables a
|
|
|
|
|
IsNotNull a -> exprTables a
|
|
|
|
|
CountOf a -> exprTables a
|
|
|
|
|
SumOf a -> exprTables a
|
|
|
|
|
AvgOf a -> exprTables a
|
|
|
|
|
MinOf a -> exprTables a
|
|
|
|
|
MaxOf a -> exprTables a
|
|
|
|
|
_ -> []
|
|
|
|
|
where
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 两侧都取表名
|
2026-10-01 20:48:38 +08:00
|
|
|
both a b = exprTables a ++ exprTables b
|
|
|
|
|
|
2026-09-25 17:41:51 +08:00
|
|
|
-- | 一张表的线上信息(内存实现:没有索引,统计现算)
|
|
|
|
|
tableInfoOf :: (String, Table) -> TableInfo
|
|
|
|
|
tableInfoOf (name, tbl) =
|
|
|
|
|
TableInfo
|
|
|
|
|
{ tiTable = name
|
2026-10-01 20:48:38 +08:00
|
|
|
, tiColumns = [wireColumn c ty | (c, ty) <- tableCols tbl]
|
2026-09-25 17:41:51 +08:00
|
|
|
, tiRows = length (tableRows tbl)
|
|
|
|
|
, tiIndexes = []
|
|
|
|
|
, tiStats = [(c, distinctOf c, False) | (c, _) <- tableCols tbl]
|
|
|
|
|
}
|
|
|
|
|
where
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 一列的不同值个数
|
2026-09-25 17:41:51 +08:00
|
|
|
distinctOf c = length (nub [v | r <- tableRows tbl, Just v <- [lookup c r]])
|
|
|
|
|
|
|
|
|
|
-- | 本地列类型转线上字符串(和 IPC 那边同一套写法)
|
|
|
|
|
wireType :: Column -> String
|
2026-10-01 20:48:38 +08:00
|
|
|
wireType = typeName . columnType
|
|
|
|
|
|
|
|
|
|
-- | 本地列转线上 schema
|
|
|
|
|
wireColumn :: String -> Column -> SchemaColumn
|
|
|
|
|
wireColumn name col =
|
|
|
|
|
SchemaColumn
|
|
|
|
|
{ scName = name
|
|
|
|
|
, scType = wireType col
|
|
|
|
|
, scNullable = columnNullable col
|
|
|
|
|
, scDefault = columnDefault col
|
|
|
|
|
, scAutoIncrement = columnAutoIncrement col
|
|
|
|
|
, scPrimaryKey = columnPrimaryKey col
|
|
|
|
|
, scUnique = columnUnique col
|
|
|
|
|
, scCheck = columnCheck col
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 系统库名,账号、角色与授权表住在这里
|
2026-10-01 20:48:38 +08:00
|
|
|
systemDatabaseName :: String
|
|
|
|
|
systemDatabaseName = "system"
|
|
|
|
|
|
|
|
|
|
usersTable :: String
|
2026-10-03 02:17:46 +08:00
|
|
|
usersTable = "__system_users"
|
2026-10-01 20:48:38 +08:00
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 角色与授权表的名字,不带内部前缀
|
2026-10-01 20:48:38 +08:00
|
|
|
rolesTable :: String
|
|
|
|
|
rolesTable = "sys_roles"
|
|
|
|
|
|
|
|
|
|
grantsTable :: String
|
|
|
|
|
grantsTable = "sys_grants"
|
|
|
|
|
|
|
|
|
|
membersTable :: String
|
|
|
|
|
membersTable = "sys_members"
|
|
|
|
|
|
|
|
|
|
internalNames :: [String]
|
|
|
|
|
internalNames = [rolesTable, grantsTable, membersTable]
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 名字是否带内部表前缀
|
2026-10-01 20:48:38 +08:00
|
|
|
reserved :: String -> Bool
|
2026-10-03 02:17:46 +08:00
|
|
|
reserved = T.isPrefixOf "__system_" . T.toLower . T.pack
|
2026-10-01 20:48:38 +08:00
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 是不是服务自己的内部表
|
2026-10-01 20:48:38 +08:00
|
|
|
internalTable :: String -> Bool
|
|
|
|
|
internalTable name = reserved name || T.unpack (T.toLower (T.pack name)) `elem` internalNames
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 去掉内部表
|
2026-10-01 20:48:38 +08:00
|
|
|
visible :: Database -> Database
|
|
|
|
|
visible = filter (not . internalTable . fst)
|
|
|
|
|
|
|
|
|
|
-- | 当前 UTC 时间,写法与引擎的 timestamp 一致
|
|
|
|
|
currentStamp :: IO T.Text
|
|
|
|
|
currentStamp = T.pack . formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S" <$> getCurrentTime
|
|
|
|
|
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 在内存账号表上执行一条账号请求
|
2026-10-01 20:48:38 +08:00
|
|
|
memoryAccounts :: Database -> T.Text -> Request -> Either String [Account]
|
|
|
|
|
memoryAccounts db stamp req = do
|
|
|
|
|
accounts <- case lookup usersTable db of
|
|
|
|
|
Nothing -> Right []
|
|
|
|
|
Just table -> mapM decodeAccount (tableRows table)
|
|
|
|
|
case req of
|
|
|
|
|
ReqAccountsList -> Right accounts
|
|
|
|
|
ReqAccountCreate u h
|
|
|
|
|
| any ((== T.toLower u) . accountUser) accounts -> Left "account already exists"
|
|
|
|
|
| otherwise -> Right (accounts ++ [Account (1 + maximum (0 : map accountId accounts)) (T.toLower u) h 1 stamp Nothing])
|
|
|
|
|
ReqAccountReset u h -> case filter ((== T.toLower u) . accountUser) accounts of
|
|
|
|
|
[_] -> Right [if accountUser x == T.toLower u then x{accountHash = h, accountRevision = accountRevision x + 1} else x | x <- accounts]
|
|
|
|
|
_ -> Left "unknown account"
|
|
|
|
|
ReqAccountLogin u at -> case filter ((== T.toLower u) . accountUser) accounts of
|
|
|
|
|
[_] -> Right [if accountUser x == T.toLower u then x{accountLastLoginAt = at} else x | x <- accounts]
|
|
|
|
|
_ -> Left "unknown account"
|
|
|
|
|
ReqAccountDrop u -> case filter ((== T.toLower u) . accountUser) accounts of
|
|
|
|
|
[] -> Left "unknown account"
|
|
|
|
|
_ -> Right [x | x <- accounts, accountUser x /= T.toLower u]
|
|
|
|
|
_ -> Left "invalid account request"
|
|
|
|
|
where
|
2026-10-02 14:53:02 +08:00
|
|
|
-- | 把一行解回账号
|
2026-10-01 20:48:38 +08:00
|
|
|
decodeAccount row = case lookup "account" row of
|
|
|
|
|
Just (VStr encoded) -> A.eitherDecode (BL.pack encoded)
|
|
|
|
|
_ -> Left "invalid account record"
|