2026-10-03 02:17:46 +08:00
use std ::collections ::{ HashMap , HashSet };
2026-09-24 15:37:55 +08:00
use std ::path ::PathBuf ;
2026-10-01 20:48:38 +08:00
use std ::sync ::atomic ::{ AtomicBool , Ordering };
2026-10-03 02:17:46 +08:00
use std ::sync ::{ Arc , Mutex , RwLock };
2026-09-24 15:37:55 +08:00
2026-10-02 14:53:02 +08:00
use crate ::catalog ::Catalog ;
use crate ::config ::{ self , Config , Loaded };
2026-10-03 02:17:46 +08:00
use crate ::heap ::{ scalar_int , HeapTable };
2026-10-02 14:53:02 +08:00
use crate ::log ;
use crate ::protocol ::{
2026-10-05 00:15:01 +08:00
blocked_table , reserved_table , Account , ColumnStatWire , IndexWire , Request , Response , Row , IDENTITIES_TABLE ,
SchemaColumn , StorageOp , TableSchemaWire , USERS_TABLE , TYPES_TABLE ,
2026-09-25 14:21:55 +08:00
};
2026-10-03 17:23:16 +08:00
use crate ::wal ::{ committed_ops , records_after , Wal , WalOp };
2026-10-02 14:53:02 +08:00
use crate ::{ log_debug , log_error , log_info , log_warn };
2026-09-24 15:37:55 +08:00
2026-10-02 14:53:02 +08:00
// 进程内存储运行时:Haskell 侧经 ffi.rs 调进来。
// request_line 的语义等同原来的「一行 JSON 请求 → 一行 JSON 响应」套接字协议。
2026-09-24 15:37:55 +08:00
2026-09-24 21:28:49 +08:00
struct Server {
cfg : Config ,
tables : Mutex < HashMap < String , Arc < Mutex < HeapTable >>>> ,
write_lock : Mutex < () > ,
2026-10-03 02:17:46 +08:00
gate : RwLock < () > ,
2026-09-24 23:56:07 +08:00
catalog : Mutex < Catalog > ,
2026-09-25 14:21:55 +08:00
wal : Wal ,
2026-10-01 20:48:38 +08:00
recovery_required : AtomicBool ,
2026-09-24 15:37:55 +08:00
}
2026-10-01 20:48:38 +08:00
type TableDescription = ( Vec < SchemaColumn > , u64 , Vec < IndexWire > , Vec < ColumnStatWire > );
2026-09-24 21:28:49 +08:00
impl Server {
2026-10-05 22:22:23 +08:00
/// 读取受保护的对象权限快照
fn object_acl_read ( & self ) -> std ::io ::Result < Vec < Row >> {
if ! self . table_exists ( "__system_object_acl" ) ? { return Ok ( Vec ::new ()); }
let rows = self . with_existing_table ( "__system_object_acl" , | table | table . scan ()) ? ;
let mut payload = String ::new ();
for ( index , row ) in rows . iter (). enumerate () {
if row . get ( "id" ). and_then ( | value | value . as_u64 ()) != Some ( index as u64 + 1 ) {
return Err ( std ::io ::Error ::other ( "invalid object ACL chunk sequence" ));
}
payload . push_str ( row . get ( "payload" ). and_then ( | value | value . as_str ())
. ok_or_else ( || std ::io ::Error ::other ( "invalid object ACL chunk" )) ? );
}
if rows . is_empty () { return Err ( std ::io ::Error ::other ( "empty object ACL snapshot" )); }
Ok ( vec! [ serde_json ::from_value ( serde_json ::json! ({ "id" :1 , "payload" :payload })). map_err ( std ::io ::Error ::other ) ? ])
}
/// 比较并原子替换对象权限快照
fn object_acl_replace ( & self , expected : Option <& str > , payload : & str ) -> std ::io ::Result < () > {
let rows = self . object_acl_read () ? ;
let current = rows . first (). and_then ( | row | row . get ( "payload" )). and_then ( | value | value . as_str ());
if current != expected { return Err ( std ::io ::Error ::other ( "object ACL changed concurrently" )); }
let _ : serde_json ::Value = serde_json ::from_str ( payload ). map_err ( std ::io ::Error ::other ) ? ;
let mut ops = Vec ::new ();
if ! self . table_exists ( "__system_object_acl" ) ? {
ops . push ( WalOp ::CreateTable { table : "__system_object_acl" . into (),
columns : vec ! [ SchemaColumn { name : "id" . into (), ty : "int" . into (), .. Default ::default () },
SchemaColumn { name : "payload" . into (), ty : "str" . into (), .. Default ::default () }], object_id : None });
}
let chars : Vec < char > = payload . chars (). collect ();
let rows = chars . chunks ( 256 ). enumerate (). map ( | ( index , chunk ) | {
serde_json ::from_value ( serde_json ::json! ({ "id" :index + 1 , "payload" :chunk . iter (). collect ::< String > ()}))
. map_err ( std ::io ::Error ::other )
}). collect ::< std ::io ::Result < Vec < Row >>> () ? ;
ops . push ( WalOp ::ReplaceAll { table : "__system_object_acl" . into (), rows });
self . group_via_wal ( & ops )
}
2026-09-24 21:28:49 +08:00
/// 新建服务器状态
fn new ( cfg : Config ) -> std ::io ::Result < Self > {
std ::fs ::create_dir_all ( & cfg . data_dir ) ? ;
2026-09-24 23:56:07 +08:00
let catalog = Catalog ::load ( cfg . data_dir . join ( "catalog.json" )) ? ;
2026-10-05 22:22:23 +08:00
catalog . save ( cfg . data_dir . join ( "catalog.json" )) ? ;
2026-09-25 14:21:55 +08:00
let wal = Wal ::new ( cfg . data_dir . join ( "wal.log" ));
2026-09-24 21:28:49 +08:00
Ok ( Server {
cfg ,
tables : Mutex ::new ( HashMap ::new ()),
write_lock : Mutex ::new (()),
2026-10-03 02:17:46 +08:00
gate : RwLock ::new (()),
2026-09-24 23:56:07 +08:00
catalog : Mutex ::new ( catalog ),
2026-09-25 14:21:55 +08:00
wal ,
2026-10-01 20:48:38 +08:00
recovery_required : AtomicBool ::new ( false ),
2026-09-24 21:28:49 +08:00
})
}
2026-09-24 15:37:55 +08:00
2026-09-24 21:28:49 +08:00
/// 表名 -> 数据文件
fn table_path ( & self , table : & str ) -> PathBuf {
self . cfg . data_dir . join ( format! ( " {} .db" , table ))
}
2026-09-24 15:37:55 +08:00
2026-09-25 17:41:51 +08:00
/// (表名, 列名) 对应哪个索引文件
2026-09-25 14:21:55 +08:00
fn index_path ( & self , table : & str , column : & str ) -> PathBuf {
if column == "id" {
self . cfg . data_dir . join ( format! ( " {} .idx" , table ))
} else {
self . cfg
. data_dir
. join ( format! ( " {} . {} .idx" , table , column ))
}
2026-09-24 21:28:49 +08:00
}
2026-09-24 15:37:55 +08:00
2026-09-24 21:28:49 +08:00
/// 数据字典文件
fn catalog_path ( & self ) -> PathBuf {
self . cfg . data_dir . join ( "catalog.json" )
}
2026-09-24 15:37:55 +08:00
2026-10-01 20:48:38 +08:00
/// 仅数据字典决定表是否存在
fn table_exists ( & self , table : & str ) -> std ::io ::Result < bool > {
let catalog = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
Ok ( catalog . describe ( table ). is_some ())
2026-09-24 21:28:49 +08:00
}
2026-09-25 17:41:51 +08:00
/// 一张表该开哪些索引
2026-09-25 14:21:55 +08:00
fn index_columns ( & self , table : & str ) -> Vec < String > {
let mut cols = vec! [ "id" . to_string ()];
let c = self . catalog . lock (). unwrap ();
if let Some ( s ) = c . describe ( table ) {
for i in & s . indexes {
if ! cols . contains ( & i . column ) {
cols . push ( i . column . clone ());
}
}
}
cols
}
2026-09-24 21:28:49 +08:00
/// 拿表句柄(缓存复用)
fn table_handle ( & self , table : & str ) -> std ::io ::Result < Arc < Mutex < HeapTable >>> {
2026-09-25 14:21:55 +08:00
if let Some ( h ) = self . tables . lock (). unwrap (). get ( table ) {
return Ok ( h . clone ());
}
let columns = self . index_columns ( table );
2026-10-01 20:48:38 +08:00
let hidden = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ?
. describe ( table ). map ( | s | s . dropped_columns . iter (). cloned (). collect ()). unwrap_or_default ();
2026-09-25 14:21:55 +08:00
let paths : Vec < ( String , PathBuf ) > = columns
. iter ()
. map ( | c | ( c . clone (), self . index_path ( table , c )))
. collect ();
2026-09-24 21:28:49 +08:00
let mut map = self . tables . lock (). unwrap ();
if let Some ( h ) = map . get ( table ) {
return Ok ( h . clone ());
}
std ::fs ::create_dir_all ( & self . cfg . data_dir ) ? ;
2026-10-01 20:48:38 +08:00
let mut t = HeapTable ::open_with_indexes (
2026-09-24 21:28:49 +08:00
self . table_path ( table ),
2026-09-25 14:21:55 +08:00
& paths ,
2026-09-24 21:28:49 +08:00
self . cfg . page_size ,
self . cfg . btree_order ,
self . cfg . pool_size ,
) ? ;
2026-10-01 20:48:38 +08:00
t . set_hidden_columns ( hidden );
2026-09-24 21:28:49 +08:00
let h = Arc ::new ( Mutex ::new ( t ));
map . insert ( table . to_string (), h . clone ());
Ok ( h )
}
/// 在表上跑闭包
fn with_table < R > (
& self ,
table : & str ,
f : impl FnOnce ( & mut HeapTable ) -> std ::io ::Result < R > ,
) -> std ::io ::Result < R > {
let h = self . table_handle ( table ) ? ;
let mut guard = h . lock (). unwrap ();
f ( & mut guard )
}
2026-10-03 02:17:46 +08:00
/// 请求带列名时逐个查字典
fn check_columns ( & self , table : & str , columns : Option <& [ String ] > ) -> std ::io ::Result < () > {
let Some ( cols ) = columns else {
return Ok (());
};
let c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
let schema = c
. describe ( table )
. ok_or_else ( || std ::io ::Error ::other ( format! ( "unknown table: {table} " ))) ? ;
for col in cols {
if ! schema . columns . iter (). any ( | c | & c . name == col ) {
return Err ( std ::io ::Error ::other ( format! ( "unknown column: {col} " )));
}
}
Ok (())
}
/// 分片只读扫描:独立句柄读一段连续页,不经表锁
fn scan_shard (
& self ,
table : & str ,
columns : Option <& [ String ] > ,
shard : u32 ,
shards : u32 ,
) -> std ::io ::Result < Vec < Row >> {
if shards == 0 || shard >= shards {
return Err ( std ::io ::Error ::other ( "invalid shard range" ));
}
if ! self . table_exists ( table ) ? {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
format! ( "unknown table: {} " , table ),
));
}
let hidden : HashSet < String > = self
. catalog
. lock ()
. map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ?
. describe ( table )
. map ( | s | s . dropped_columns . iter (). cloned (). collect ())
. unwrap_or_default ();
let mut t = HeapTable ::open ( self . table_path ( table ), self . cfg . page_size , self . cfg . pool_size ) ? ;
t . set_hidden_columns ( hidden );
let n = t . num_pages () ? ;
let chunk = n . div_ceil ( u64 ::from ( shards ));
let from = u64 ::from ( shard ). saturating_mul ( chunk ). min ( n );
let to = u64 ::from ( shard + 1 ). saturating_mul ( chunk ). min ( n );
t . scan_columns_pages ( columns , from , to )
}
2026-09-24 21:28:49 +08:00
/// 表不存在就报错
fn with_existing_table < R > (
& self ,
table : & str ,
f : impl FnOnce ( & mut HeapTable ) -> std ::io ::Result < R > ,
) -> std ::io ::Result < R > {
2026-10-01 20:48:38 +08:00
if ! self . table_exists ( table ) ? {
2026-09-24 21:28:49 +08:00
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
format! ( "unknown table: {} " , table ),
));
}
self . with_table ( table , f )
}
/// 把一条 WAL 操作落到磁盘
fn apply_op ( & self , op : & WalOp ) -> std ::io ::Result < () > {
match op {
2026-10-03 16:49:23 +08:00
WalOp ::Commit => Ok (()),
2026-10-11 17:32:12 +08:00
WalOp ::HideColumn { table , column , schema_version } => {
2026-10-01 20:48:38 +08:00
let mut c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
2026-10-11 17:32:12 +08:00
c . remove_column_version ( table , column , * schema_version ) ? ;
2026-10-01 20:48:38 +08:00
c . save ( self . catalog_path ()) ? ;
drop ( c );
if let Some ( handle ) = self . tables . lock (). map_err ( | _ | std ::io ::Error ::other ( "table lock poisoned" )) ? . get ( table ) {
handle . lock (). map_err ( | _ | std ::io ::Error ::other ( "heap lock poisoned" )) ? . hide_column ( column );
}
match std ::fs ::remove_file ( self . index_path ( table , column )) {
Ok (()) => Ok (()),
Err ( e ) if e . kind () == std ::io ::ErrorKind ::NotFound => Ok (()),
Err ( e ) => Err ( e ),
}
}
WalOp ::Compact { table , rows } => {
self . with_table ( table , | t | {
t . replace_all ( rows ) ? ;
t . flush ()
}) ? ;
let mut c = self . catalog . lock (). unwrap ();
c . mark_compacted ( table ) ? ;
2026-10-11 17:32:12 +08:00
c . rebuild_stats ( table , rows ) ? ;
2026-10-01 20:48:38 +08:00
c . save ( self . catalog_path ())
}
2026-10-05 00:15:01 +08:00
WalOp ::Accounts { accounts , removed , migrate_roles } => {
2026-10-01 20:48:38 +08:00
let rows = account_rows ( accounts ) ? ;
2026-10-05 00:15:01 +08:00
let identities = identity_rows ( accounts ) ? ;
for ( table , records ) in [( USERS_TABLE , & rows ), ( IDENTITIES_TABLE , & identities )] {
self . tables . lock (). map_err ( | _ | std ::io ::Error ::other ( "table lock poisoned" )) ? . remove ( table );
for path in [ self . table_path ( table ), self . index_path ( table , "id" )] {
std ::fs ::File ::create ( path ) ? . sync_all () ? ;
}
self . with_table ( table , | t | { t . replace_all ( records ) ? ; t . flush () }) ? ;
}
let legacy_roles = "__system_roles" ;
let has_roles = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? . describe ( legacy_roles ). is_some ();
if has_roles && * migrate_roles {
self . with_existing_table ( legacy_roles , | t | { t . replace_all ( & []) ? ; t . flush () }) ? ;
}
let mut cleaned = Vec ::new ();
for table in [ "__system_grants" , "__system_grant_options" , "__system_members" ] {
let exists = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? . describe ( table ). is_some ();
if exists && ! removed . is_empty () {
let rows = self . with_existing_table ( table , | t | t . scan ()) ? . into_iter (). filter ( | r |
! [ "role" , "member" ]. iter (). any ( | field | r . get ( * field ). and_then ( serde_json ::Value ::as_str ). is_some_and ( | name | removed . iter (). any ( | old | old == name )))). collect ::< Vec < _ >> ();
self . with_existing_table ( table , | t | { t . replace_all ( & rows ) ? ; t . flush () }) ? ;
cleaned . push (( table , rows ));
}
2026-10-01 20:48:38 +08:00
}
let mut c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
declare_system_schema ( & mut c , & rows ) ? ;
2026-10-11 17:32:12 +08:00
c . rebuild_stats ( IDENTITIES_TABLE , & identities ) ? ;
2026-10-05 00:15:01 +08:00
c . set_columns ( IDENTITIES_TABLE , identity_columns ()) ? ;
2026-10-11 17:32:12 +08:00
c . rebuild_stats ( IDENTITIES_TABLE , & identities ) ? ;
2026-10-05 00:15:01 +08:00
c . mark_system ( IDENTITIES_TABLE ) ? ;
c . add_index ( IDENTITIES_TABLE , "user" ) ? ;
c . identity_high_water = c . identity_high_water . max ( accounts . iter (). map ( | a | a . id ). max (). unwrap_or ( 0 ));
2026-10-11 17:32:12 +08:00
if has_roles && * migrate_roles { c . rebuild_stats ( legacy_roles , & []) ? ; }
for ( table , records ) in cleaned { c . rebuild_stats ( table , & records ) ? ; }
2026-10-01 20:48:38 +08:00
c . save ( self . catalog_path ())
}
2026-09-25 14:21:55 +08:00
WalOp ::Insert { table , row } => {
self . with_table ( table , | t | t . insert_row ( row )) ? ;
2026-09-24 23:56:07 +08:00
let mut c = self . catalog . lock (). unwrap ();
2026-10-11 17:32:12 +08:00
if c . record_insert ( table , row ) ? {
2026-09-24 23:56:07 +08:00
c . save ( self . catalog_path ()) ? ;
}
2026-09-24 21:28:49 +08:00
Ok (())
}
2026-09-25 14:21:55 +08:00
WalOp ::InsertBatch { table , rows } => {
self . with_table ( table , | t | t . insert_rows ( rows )) ? ;
2026-09-24 23:56:07 +08:00
let mut c = self . catalog . lock (). unwrap ();
2026-09-25 14:21:55 +08:00
let mut changed = false ;
for r in rows {
2026-10-11 17:32:12 +08:00
if c . record_insert ( table , r ) ? {
2026-09-25 14:21:55 +08:00
changed = true ;
}
}
if changed {
2026-09-24 23:56:07 +08:00
c . save ( self . catalog_path ()) ? ;
}
2026-09-24 21:28:49 +08:00
Ok (())
}
2026-09-25 14:21:55 +08:00
WalOp ::DeleteKeys { table , keys } => {
let deleted = self . with_table ( table , | t | t . delete_by_keys ( keys )) ? ;
let mut c = self . catalog . lock (). unwrap ();
c . record_delete ( table , & deleted );
Ok (())
}
WalOp ::ReplaceAll { table , rows } => {
self . with_table ( table , | t | t . replace_all ( rows )) ? ;
let mut c = self . catalog . lock (). unwrap ();
2026-10-11 17:32:12 +08:00
c . rebuild_stats ( table , rows ) ? ;
2026-09-25 14:21:55 +08:00
c . save ( self . catalog_path ())
}
2026-10-11 17:32:12 +08:00
WalOp ::DropColumn { table , column , rows , schema_version } => {
2026-09-25 17:41:51 +08:00
self . with_table ( table , | t | {
t . detach_index ( column );
t . replace_all ( rows )
}) ? ;
let mut c = self . catalog . lock (). unwrap ();
2026-10-11 17:32:12 +08:00
c . remove_column_version ( table , column , * schema_version ) ? ;
c . rebuild_stats ( table , rows ) ? ;
2026-09-25 17:41:51 +08:00
c . save ( self . catalog_path ()) ? ;
drop ( c );
let _ = std ::fs ::remove_file ( self . index_path ( table , column ));
Ok (())
}
2026-10-05 22:22:23 +08:00
WalOp ::CreateTable { table , columns , object_id } => {
2026-10-03 17:04:28 +08:00
self . with_table ( table , | _t | Ok (())) ? ;
let mut c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
2026-10-05 22:22:23 +08:00
c . create_table_with_id ( table , columns . clone (), * object_id ) ? ;
if table == TYPES_TABLE || table == "__system_object_acl" { c . mark_system ( table ) ? ; }
2026-10-03 17:04:28 +08:00
c . save ( self . catalog_path ())
}
WalOp ::DropTable { table } => {
let dropped_indexes : Vec < String > = {
let mut c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
let idx = c
. describe ( table )
. map ( | s | s . indexes . iter (). map ( | i | i . column . clone ()). collect ())
. unwrap_or_default ();
if c . describe ( table ). is_some () {
c . drop_table ( table ) ? ;
c . save ( self . catalog_path ()) ? ;
}
idx
};
self . tables . lock (). map_err ( | _ | std ::io ::Error ::other ( "table lock poisoned" )) ? . remove ( table );
let _ = std ::fs ::remove_file ( self . table_path ( table ));
let _ = std ::fs ::remove_file ( self . index_path ( table , "id" ));
for col in dropped_indexes {
let _ = std ::fs ::remove_file ( self . index_path ( table , & col ));
}
Ok (())
}
WalOp ::CreateIndex { table , column } => self . apply_create_index ( table , column ),
WalOp ::DropIndex { table , column } => self . apply_drop_index ( table , column ),
2026-10-11 17:32:12 +08:00
WalOp ::ReplaceSchema { table , columns , rows , schema_version } => {
2026-10-01 20:48:38 +08:00
let old_indexes : Vec < String > = {
let c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
c . describe ( table )
. map ( | s | s . indexes . iter (). map ( | i | i . column . clone ()). collect ())
. unwrap_or_default ()
};
self . with_table ( table , | t | {
for col in & old_indexes {
t . detach_index ( col );
}
t . set_hidden_columns ( Default ::default ());
t . replace_all ( rows )
}) ? ;
let mut c = self . catalog . lock (). unwrap ();
2026-10-11 17:32:12 +08:00
c . set_columns_version ( table , columns . clone (), * schema_version ) ? ;
c . rebuild_stats ( table , rows ) ? ;
2026-10-01 20:48:38 +08:00
c . save ( self . catalog_path ()) ? ;
drop ( c );
for col in old_indexes {
let _ = std ::fs ::remove_file ( self . index_path ( table , & col ));
}
Ok (())
}
2026-09-24 21:28:49 +08:00
}
}
/// 先写 WAL 再改数据
fn write_via_wal ( & self , op : & WalOp ) -> std ::io ::Result < () > {
let _guard = self . write_lock . lock (). unwrap ();
2026-10-01 20:48:38 +08:00
if self . recovery_required . load ( Ordering ::Acquire ) {
return Err ( std ::io ::Error ::other ( "storage recovery required" ));
}
2026-10-05 00:15:01 +08:00
let op = self . normalize_decimal_write ( op ) ? ;
self . validate_op ( & op ) ? ;
self . validate_domains ( & op ) ? ;
self . group_via_wal ( std ::slice ::from_ref ( & op ))
}
/// 写日志前规范化定点数值
fn normalize_decimal_write ( & self , op : & WalOp ) -> std ::io ::Result < WalOp > {
let mut normalized = op . clone ();
let catalog = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
let ( table , rows ) : ( & str , Vec <& mut Row > ) = match & mut normalized {
WalOp ::Insert { table , row } => ( table , vec! [ row ]),
WalOp ::InsertBatch { table , rows } | WalOp ::ReplaceAll { table , rows } => ( table , rows . iter_mut (). collect ()),
WalOp ::CreateTable { columns , .. } => {
for column in columns { decimal_parameters ( & column . ty ) ? ; }
return Ok ( normalized );
}
WalOp ::ReplaceSchema { columns , rows , .. } => {
for column in columns . iter () { decimal_parameters ( & column . ty ) ? ; }
for row in rows { normalize_decimal_row ( row , columns ) ? ; }
return Ok ( normalized );
}
_ => return Ok ( normalized ),
};
if let Some ( schema ) = catalog . describe ( table ) {
for row in rows {
normalize_decimal_row ( row , & schema . columns ) ? ;
}
}
Ok ( normalized )
}
/// 校验命名类型引用与删除依赖
fn validate_domains ( & self , op : & WalOp ) -> std ::io ::Result < () > {
let catalog = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
match op {
WalOp ::CreateTable { columns , .. } | WalOp ::ReplaceSchema { columns , .. } => {
for column in columns {
let mut ty = column . ty . as_str ();
while let Some ( body ) = ty . strip_prefix ( "domain(" ). and_then ( | s | s . strip_suffix ( ')' )) {
let ( name , base ) = body . split_once ( ',' ). ok_or_else ( || std ::io ::Error ::other ( "invalid domain type" )) ? ;
let key = format! ( "__system_domain_ {name} " );
let definition = catalog . describe ( & key ). ok_or_else ( || std ::io ::Error ::other ( format! ( "unknown domain: {name} " ))) ? ;
if ! definition . columns . iter (). any ( | c | c . name == "base" && c . ty == base ) {
return Err ( std ::io ::Error ::other ( format! ( "domain base type mismatch: {name} " )));
}
ty = base ;
}
}
}
WalOp ::DropTable { table } => {
if let Some ( name ) = table . strip_prefix ( "__system_domain_" ) {
let prefix = format! ( "domain( {name} ," );
if catalog . all_tables (). iter (). any ( | ( _ , schema ) | schema . columns . iter (). any ( | c | ! schema . dropped_columns . contains ( & c . name ) && c . ty . contains ( & prefix ))) {
return Err ( std ::io ::Error ::other ( format! ( "domain is still used by a column: {name} " )));
}
}
}
_ => {}
}
Ok (())
2026-10-03 16:49:23 +08:00
}
/// 校验一条操作:命中被删的列就拒绝
fn validate_op ( & self , op : & WalOp ) -> std ::io ::Result < () > {
let c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
let validate = | table : & str , rows : & [ Row ] | -> std ::io ::Result < () > {
let Some ( schema ) = c . describe ( table ) else { return Ok (()); };
if let Some ( name ) = rows . iter (). flat_map ( | r | r . keys ()). find ( | k | schema . dropped_columns . contains ( * k )) {
return Err ( std ::io ::Error ::other ( format! ( "unknown column: {name} " )));
2026-10-01 20:48:38 +08:00
}
2026-10-03 16:49:23 +08:00
Ok (())
};
match op {
WalOp ::Insert { table , row } => validate ( table , std ::slice ::from_ref ( row )) ? ,
WalOp ::InsertBatch { table , rows } | WalOp ::ReplaceAll { table , rows } => validate ( table , rows ) ? ,
2026-10-11 17:32:12 +08:00
WalOp ::HideColumn { table , column , .. }
2026-10-03 16:49:23 +08:00
if ! c . describe ( table ). is_some_and ( | s | s . columns . iter (). any ( | col | & col . name == column )) =>
{
return Err ( std ::io ::Error ::other ( format! ( "unknown column: {column} " )));
}
_ => {}
2026-10-01 20:48:38 +08:00
}
2026-10-03 16:49:23 +08:00
Ok (())
}
/// 事务提交:一组写操作加一个提交标记,一次落盘
///
/// 组里每条都过 `validate_op`,跟单条写一致;调用方 `dispatch` 已经持有
/// `gate.write`,因此整批对外原子可见。崩在提交标记之前,这组记录在恢复
/// 时整组丢弃;崩在标记之后,逐条按「是否已生效」补写。
fn apply_transaction ( & self , ops : & [ StorageOp ]) -> std ::io ::Result < () > {
let _guard = self . write_lock . lock (). unwrap ();
if self . recovery_required . load ( Ordering ::Acquire ) {
return Err ( std ::io ::Error ::other ( "storage recovery required" ));
}
let mut wal_ops = Vec ::with_capacity ( ops . len ());
for op in ops {
let table = match op {
StorageOp ::Upsert { table , .. }
| StorageOp ::Delete { table , .. }
| StorageOp ::Replace { table , .. } => table ,
};
2026-10-03 17:16:26 +08:00
if blocked_table ( table ) {
2026-10-03 16:49:23 +08:00
return Err ( std ::io ::Error ::other ( "reserved system table" ));
}
if table . is_empty () || ! table . chars (). all ( | c | c . is_ascii_alphanumeric () || c == '_' ) {
return Err ( std ::io ::Error ::other ( "invalid table name" ));
}
let wal_op = match op {
StorageOp ::Upsert { table , rows } => WalOp ::InsertBatch {
table : table . clone (),
rows : rows . clone (),
},
StorageOp ::Delete { table , ids } => WalOp ::DeleteKeys {
table : table . clone (),
keys : ids . clone (),
},
StorageOp ::Replace { table , rows } => WalOp ::ReplaceAll {
table : table . clone (),
rows : rows . clone (),
},
};
2026-10-05 00:15:01 +08:00
let wal_op = self . normalize_decimal_write ( & wal_op ) ? ;
2026-10-03 16:49:23 +08:00
self . validate_op ( & wal_op ) ? ;
wal_ops . push ( wal_op );
}
self . group_via_wal ( & wal_ops )
}
2026-10-03 17:23:16 +08:00
/// 一组操作加提交标记落盘,成功后把检查点推到这条提交
///
/// 检查点写在这组数据和字典都刷盘之后,含义是「LSN 到此为止的改动都已落盘」;
/// 写完再按这个边界丢掉日志前缀,中途崩掉只会留下更长的日志,不会少数据。
2026-10-03 16:49:23 +08:00
fn group_via_wal ( & self , ops : & [ WalOp ]) -> std ::io ::Result < () > {
2026-09-24 21:28:49 +08:00
std ::fs ::create_dir_all ( & self . cfg . data_dir ) ? ;
2026-09-25 14:21:55 +08:00
let wal = & self . wal ;
2026-10-05 22:22:23 +08:00
let mut ops = std ::borrow ::Cow ::Borrowed ( ops );
if ops . iter (). any ( | op | matches! ( op , WalOp ::CreateTable { object_id : None , .. })) {
let mut catalog = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
for op in ops . to_mut () {
if let WalOp ::CreateTable { object_id , .. } = op
&& object_id . is_none () {
* object_id = Some ( catalog . reserve_object_id () ? );
}
}
}
2026-10-11 17:32:12 +08:00
if ops . iter (). any ( | op | matches! ( op , WalOp ::HideColumn { .. } | WalOp ::DropColumn { .. } | WalOp ::ReplaceSchema { .. })) {
let mut preview = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? . clone ();
for op in ops . to_mut () {
match op {
WalOp ::HideColumn { table , column , schema_version }
| WalOp ::DropColumn { table , column , schema_version , .. } => {
preview . remove_column ( table , column ) ? ;
* schema_version = Some ( preview . describe ( table ). ok_or_else ( || std ::io ::Error ::other ( "missing schema version" )) ? . schema_version . max ( 1 ));
}
WalOp ::ReplaceSchema { table , columns , schema_version , .. } => {
preview . set_columns ( table , columns . clone ()) ? ;
* schema_version = Some ( preview . describe ( table ). ok_or_else ( || std ::io ::Error ::other ( "missing schema version" )) ? . schema_version . max ( 1 ));
}
WalOp ::CreateTable { table , columns , object_id } => preview . create_table_with_id ( table , columns . clone (), * object_id ) ? ,
WalOp ::DropTable { table } => preview . drop_table ( table ) ? ,
WalOp ::Insert { table , row } => preview . ensure_columns ( table , row ) ? ,
WalOp ::InsertBatch { table , rows } | WalOp ::ReplaceAll { table , rows } => {
for row in rows { preview . ensure_columns ( table , row ) ? ; }
}
_ => {}
}
}
}
2026-10-05 22:22:23 +08:00
let commit_lsn = wal . append_group ( & ops ) ? ;
2026-10-05 00:15:01 +08:00
self . recovery_required . store ( true , Ordering ::Release );
2026-10-05 22:22:23 +08:00
for op in ops . iter () {
2026-10-05 00:15:01 +08:00
self . apply_op ( op ) ? ;
2026-10-01 20:48:38 +08:00
}
2026-10-05 22:22:23 +08:00
for op in ops . iter () {
2026-10-01 20:48:38 +08:00
self . flush_op_tables ( op ) ? ;
2026-09-24 21:28:49 +08:00
}
2026-10-03 16:49:23 +08:00
self . flush_all_tables () ? ;
2026-10-03 17:23:16 +08:00
wal . write_checkpoint ( commit_lsn ) ? ;
wal . truncate_before ( commit_lsn ) ? ;
2026-10-05 00:15:01 +08:00
self . recovery_required . store ( false , Ordering ::Release );
2026-10-03 16:49:23 +08:00
log_debug! ( core , "WAL committed group at LSN {}" , commit_lsn );
Ok (())
}
/// 把打开的表都 fsync 一遍
fn flush_all_tables ( & self ) -> std ::io ::Result < () > {
let handles : Vec < Arc < Mutex < HeapTable >>> = self
. tables
. lock ()
. map_err ( | _ | std ::io ::Error ::other ( "table lock poisoned" )) ?
. values ()
. cloned ()
. collect ();
for handle in handles {
handle . lock (). map_err ( | _ | std ::io ::Error ::other ( "heap lock poisoned" )) ? . flush () ? ;
}
Ok (())
2026-09-24 21:28:49 +08:00
}
/// 把涉及的表写回磁盘
fn flush_op_tables ( & self , op : & WalOp ) -> std ::io ::Result < () > {
2026-09-25 14:21:55 +08:00
let table = match op {
2026-10-01 20:48:38 +08:00
WalOp ::HideColumn { .. } => return Ok (()),
2026-10-03 16:49:23 +08:00
WalOp ::Commit => return Ok (()),
2026-10-03 17:04:28 +08:00
// 删表后不需要写回
WalOp ::DropTable { .. } => return Ok (()),
2026-10-01 20:48:38 +08:00
WalOp ::Accounts { .. } => USERS_TABLE ,
2026-09-25 14:21:55 +08:00
WalOp ::Insert { table , .. }
| WalOp ::InsertBatch { table , .. }
| WalOp ::DeleteKeys { table , .. }
2026-09-25 17:41:51 +08:00
| WalOp ::ReplaceAll { table , .. }
2026-10-01 20:48:38 +08:00
| WalOp ::DropColumn { table , .. }
| WalOp ::Compact { table , .. }
2026-10-03 17:04:28 +08:00
| WalOp ::CreateTable { table , .. }
| WalOp ::CreateIndex { table , .. }
| WalOp ::DropIndex { table , .. }
2026-10-01 20:48:38 +08:00
| WalOp ::ReplaceSchema { table , .. } => table ,
2026-09-25 14:21:55 +08:00
};
self . with_table ( table , | t | t . flush ())
2026-09-24 21:28:49 +08:00
}
2026-10-03 17:23:16 +08:00
/// 重放残留 WAL:检查点之后的已提交组重放,未提交组丢弃
2026-09-24 21:28:49 +08:00
fn recover_wal ( & self ) -> std ::io ::Result < () > {
std ::fs ::create_dir_all ( & self . cfg . data_dir ) ? ;
2026-09-25 14:21:55 +08:00
let wal = & self . wal ;
2026-10-05 00:15:01 +08:00
let checkpoint = wal . read_checkpoint () ? . unwrap_or ( 0 );
2026-10-03 16:49:23 +08:00
let scan = wal . read_all () ? ;
if scan . records . is_empty () {
2026-10-05 00:15:01 +08:00
if wal . path (). exists () {
2026-10-03 16:49:23 +08:00
wal . truncate_to ( scan . good_len ) ? ;
2026-09-24 21:28:49 +08:00
}
2026-10-03 16:49:23 +08:00
return Ok (());
2026-09-24 21:28:49 +08:00
}
2026-10-03 17:23:16 +08:00
// 检查点以内的记录已经落盘,直接按边界截掉
let pending = records_after ( & scan . records , checkpoint );
let records = committed_ops ( & pending );
2026-10-03 16:49:23 +08:00
if records . is_empty () {
log_warn! ( core , "discarding uncommitted WAL records" );
2026-10-03 17:23:16 +08:00
// 边界之后的都是没提交的尾巴,直接丢
wal . truncate_after ( checkpoint ) ? ;
2026-10-03 16:49:23 +08:00
return Ok (());
}
2026-10-03 17:23:16 +08:00
let max_lsn = pending
2026-10-03 16:49:23 +08:00
. iter ()
. rev ()
. find ( | ( _ , op ) | * op == WalOp ::Commit )
. map ( | ( lsn , _ ) | * lsn )
. or_else ( || records . last (). map ( | ( lsn , _ ) | * lsn ))
. unwrap_or ( 0 );
log_warn! ( core , "replaying {} committed WAL records" , records . len ());
for ( _ , op ) in & records {
self . replay_op ( op ) ? ;
self . flush_op_tables ( op ) ? ;
}
self . flush_all_tables () ? ;
wal . write_checkpoint ( max_lsn ) ? ;
2026-10-03 17:23:16 +08:00
wal . truncate_before ( max_lsn ) ? ;
2026-10-03 16:49:23 +08:00
log_info! ( core , "WAL replay complete" );
Ok (())
}
/// 重放一条操作:数据已经生效的不再重复写
fn replay_op ( & self , op : & WalOp ) -> std ::io ::Result < () > {
if self . op_already_applied ( op ) ? {
log_debug! ( core , "WAL record already applied, skipping" );
return Ok (());
}
self . apply_op ( op )
}
/// 这条操作对应的数据是不是已经落盘了
fn op_already_applied ( & self , op : & WalOp ) -> std ::io ::Result < bool > {
match op {
WalOp ::Insert { table , row } => self . applied_rows ( table , std ::slice ::from_ref ( row )),
WalOp ::InsertBatch { table , rows } => self . applied_rows ( table , rows ),
WalOp ::ReplaceAll { table , rows } => {
if self . catalog . lock (). unwrap (). describe ( table ). is_none () {
return Ok ( false );
}
let stored = self . with_table ( table , | t | t . scan ()) ? ;
Ok ( stored == * rows )
}
WalOp ::DeleteKeys { table , keys } => {
if keys . is_empty () {
return Ok ( true );
}
if self . catalog . lock (). unwrap (). describe ( table ). is_none () {
return Ok ( false );
}
let rows = self . with_table ( table , | t | t . scan ()) ? ;
let present = rows . iter (). any ( | row | {
row . get ( "id" )
. and_then ( scalar_int )
. is_some_and ( | id | keys . contains ( & id ))
});
Ok ( ! present )
}
2026-10-11 17:32:12 +08:00
WalOp ::HideColumn { table , column , schema_version } => {
2026-10-03 16:49:23 +08:00
let c = self . catalog . lock (). unwrap ();
2026-10-11 17:32:12 +08:00
Ok ( c . describe ( table ). is_some_and ( | s | ! s . columns . iter (). any ( | col | & col . name == column )
&& schema_version . is_none_or ( | version | s . schema_version == version )))
2026-10-03 16:49:23 +08:00
}
2026-10-05 22:22:23 +08:00
WalOp ::CreateTable { table , object_id , .. } => {
let catalog = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
match catalog . describe ( table ) {
Some ( schema ) if object_id . is_some_and ( | id | id != schema . object_id ) => {
Err ( std ::io ::Error ::other ( "WAL catalog object id mismatch" ))
}
Some ( _ ) => Ok ( true ),
None => Ok ( false ),
}
}
2026-10-03 17:04:28 +08:00
WalOp ::DropTable { table } => Ok ( ! self . table_exists ( table ) ?
&& ! self . table_path ( table ). exists ()
&& ! self . index_path ( table , "id" ). exists ()),
WalOp ::CreateIndex { table , column } => Ok ( self . index_exists ( table , column )),
WalOp ::DropIndex { table , column } => {
Ok ( ! self . index_exists ( table , column ) && ! self . index_path ( table , column ). exists ())
}
2026-10-03 16:49:23 +08:00
_ => Ok ( false ),
}
}
2026-10-03 17:04:28 +08:00
/// 这个列有索引吗
fn index_exists ( & self , table : & str , column : & str ) -> bool {
self . catalog
. lock ()
. unwrap ()
. describe ( table )
. is_some_and ( | s | s . indexes . iter (). any ( | i | i . column == column ))
}
2026-10-03 16:49:23 +08:00
/// 这些行是不是都已经在表里
fn applied_rows ( & self , table : & str , rows : & [ Row ]) -> std ::io ::Result < bool > {
if rows . is_empty () {
return Ok ( true );
}
if self . catalog . lock (). unwrap (). describe ( table ). is_none () {
return Ok ( false );
}
let stored = self . with_table ( table , | t | t . scan ()) ? ;
Ok ( rows . iter (). all ( | row | stored . iter (). any ( | one | one == row )))
2026-09-24 21:28:49 +08:00
}
2026-09-25 17:41:51 +08:00
/// 启动时重建列统计
2026-09-25 14:21:55 +08:00
fn rebuild_stats ( & self ) -> std ::io ::Result < () > {
let names : Vec < String > = self . catalog . lock (). unwrap (). table_names ();
let mut touched = false ;
for table in names {
if ! self . table_path ( & table ). exists () {
continue ;
}
let rows = self . with_table ( & table , | t | t . scan ()) ? ;
let mut c = self . catalog . lock (). unwrap ();
2026-10-11 17:32:12 +08:00
c . rebuild_stats ( & table , & rows ) ? ;
2026-09-25 14:21:55 +08:00
touched = true ;
}
if touched {
let c = self . catalog . lock (). unwrap ();
c . save ( self . catalog_path ()) ? ;
}
Ok (())
}
2026-09-24 21:28:49 +08:00
2026-10-02 14:53:02 +08:00
/// 老数据迁移:类型名规范、缺列补 null
2026-10-01 20:48:38 +08:00
fn migrate_data ( & self ) -> std ::io ::Result < () > {
2026-10-03 17:16:26 +08:00
self . migrate_table_names () ? ;
2026-10-01 20:48:38 +08:00
let names : Vec < String > = self
. catalog
. lock ()
. map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ?
. table_names ();
for table in names {
if ! self . table_path ( & table ). exists () {
continue ;
}
let columns : Vec < SchemaColumn > = match self
. catalog
. lock ()
. map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ?
. describe ( & table )
{
Some ( schema ) => schema . columns . clone (),
None => continue ,
};
let rows = self . with_existing_table ( & table , | t | t . scan ()) ? ;
if rows . iter (). all ( | row | columns . iter (). all ( | c | row . contains_key ( & c . name ))) {
continue ;
}
let filled : Vec < Row > = rows
. into_iter ()
. map ( | mut row | {
for column in & columns {
row . entry ( column . name . clone ()). or_insert ( serde_json ::Value ::Null );
}
row
})
. collect ();
log_info! ( core , "migrating {}: {} rows" , table , filled . len ());
let op = WalOp ::ReplaceAll { table , rows : filled };
self . write_via_wal ( & op ) ? ;
}
let mut catalog = self
. catalog
. lock ()
. map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
2026-10-11 17:32:12 +08:00
if catalog . normalize_types () ? {
2026-10-01 20:48:38 +08:00
catalog . save ( self . catalog_path ()) ? ;
}
Ok (())
}
2026-10-03 17:16:26 +08:00
/// 老目录里的角色与授权表改到内部前缀
fn migrate_table_names ( & self ) -> std ::io ::Result < () > {
for ( from , to ) in LEGACY_TABLE_RENAMES {
let indexes = {
let c = self
. catalog
. lock ()
. map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
match c . describe ( from ) {
Some ( schema ) if c . describe ( to ). is_none () => {
schema . indexes . iter (). map ( | i | i . column . clone ()). collect ::< Vec < String >> ()
}
_ => continue ,
}
};
self . rename_table_files ( from , to , & indexes );
{
let mut c = self
. catalog
. lock ()
. map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
if ! c . rename_table ( from , to ) ? {
continue ;
}
c . save ( self . catalog_path ()) ? ;
}
if let Ok ( mut tables ) = self . tables . lock () {
tables . remove ( from );
}
log_info! ( core , "renamed system table {} to {}" , from , to );
}
Ok (())
}
/// 把一张表的数据文件与索引文件改名
fn rename_table_files ( & self , from : & str , to : & str , indexes : & [ String ]) {
if ! self . table_path ( from ). exists () {
return ;
}
let _ = std ::fs ::rename ( self . table_path ( from ), self . table_path ( to ));
for column in std ::iter ::once ( "id" ). chain ( indexes . iter (). map ( String ::as_str )) {
let _ = std ::fs ::rename ( self . index_path ( from , column ), self . index_path ( to , column ));
}
}
2026-10-03 17:04:28 +08:00
/// 建索引前的检查:内置列、未知表列、重复索引
fn check_create_index ( & self , table : & str , column : & str ) -> std ::io ::Result < () > {
if column == "id" {
2026-09-25 14:21:55 +08:00
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::AlreadyExists ,
2026-09-25 17:41:51 +08:00
"id is the built-in index and cannot be created twice" ,
2026-09-25 14:21:55 +08:00
));
}
2026-10-03 17:04:28 +08:00
let c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
let Some ( schema ) = c . describe ( table ) else {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
format! ( "unknown table: {} " , table ),
));
};
if ! schema . columns . iter (). any ( | c | c . name == column ) {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
format! ( "unknown column: {} " , column ),
));
2026-09-25 14:21:55 +08:00
}
2026-10-03 17:04:28 +08:00
if schema . indexes . iter (). any ( | i | i . column == column ) {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::AlreadyExists ,
format! ( "index on column \" {} \" already exists" , column ),
));
}
Ok (())
}
2026-09-25 14:21:55 +08:00
2026-10-03 17:04:28 +08:00
/// 建索引文件并登记到 catalog
fn apply_create_index ( & self , table : & str , column : & str ) -> std ::io ::Result < () > {
2026-09-25 14:21:55 +08:00
let path = self . index_path ( table , column );
let ( page_size , order , pool ) = ( self . cfg . page_size , self . cfg . btree_order , self . cfg . pool_size );
self . with_table ( table , | t | {
2026-10-01 20:48:38 +08:00
t . build_index ( column , & path , page_size , order , pool ) ? ;
t . flush ()
2026-09-25 14:21:55 +08:00
}) ? ;
let mut c = self . catalog . lock (). unwrap ();
c . add_index ( table , column ) ? ;
c . save ( self . catalog_path ())
}
2026-10-03 17:04:28 +08:00
/// 去索引前的检查:内置列、未知表、索引不存在
fn check_drop_index ( & self , table : & str , column : & str ) -> std ::io ::Result < () > {
2026-09-25 14:21:55 +08:00
if column == "id" {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::InvalidInput ,
2026-09-25 17:41:51 +08:00
"id is the built-in index and cannot be dropped" ,
2026-09-25 14:21:55 +08:00
));
}
2026-10-03 17:04:28 +08:00
let c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
let Some ( schema ) = c . describe ( table ) else {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
format! ( "unknown table: {} " , table ),
));
};
if ! schema . indexes . iter (). any ( | i | i . column == column ) {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
format! ( "no index on column \" {} \" " , column ),
));
}
Ok (())
}
/// 摘掉索引定义并删文件;重放时容忍 catalog 里已没有它
fn apply_drop_index ( & self , table : & str , column : & str ) -> std ::io ::Result < () > {
if self . table_exists ( table ) ? {
self . with_table ( table , | t | {
t . detach_index ( column );
Ok (())
}) ? ;
let mut c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
if c . describe ( table ). is_some_and ( | s | s . indexes . iter (). any ( | i | i . column == column )) {
c . remove_index ( table , column ) ? ;
c . save ( self . catalog_path ()) ? ;
2026-09-25 14:21:55 +08:00
}
}
let _ = std ::fs ::remove_file ( self . index_path ( table , column ));
Ok (())
}
2026-10-01 20:48:38 +08:00
/// 删除列定义并隐藏旧字段,不改写堆页。
2026-09-25 17:41:51 +08:00
fn drop_column ( & self , table : & str , column : & str ) -> std ::io ::Result < () > {
if column == "id" {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::InvalidInput ,
"the built-in id column cannot be dropped" ,
));
}
{
let c = self . catalog . lock (). unwrap ();
let Some ( schema ) = c . describe ( table ) else {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
format! ( "unknown table: {} " , table ),
));
};
if ! schema . columns . iter (). any ( | c | c . name == column ) {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
format! ( "unknown column: {} " , column ),
));
}
}
2026-10-01 20:48:38 +08:00
let op = WalOp ::HideColumn {
2026-09-25 17:41:51 +08:00
table : table . to_string (),
column : column . to_string (),
2026-10-11 17:32:12 +08:00
schema_version : None ,
2026-10-01 20:48:38 +08:00
};
self . write_via_wal ( & op )
}
2026-10-03 21:18:52 +08:00
/// 整理一张表:原地回收页内碎片,有隐藏列时重写堆页
2026-10-01 20:48:38 +08:00
fn compact_table ( & self , table : & str ) -> std ::io ::Result < () > {
2026-10-03 21:18:52 +08:00
let rewrite = {
let c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
c . has_dropped_columns ( table )
};
if ! rewrite {
let reclaimed = self . with_existing_table ( table , | t | {
let n = t . compact_pages () ? ;
t . flush () ? ;
Ok ( n )
}) ? ;
log_info! ( core , "compact {} reclaimed {} bytes" , table , reclaimed );
return Ok (());
}
2026-10-01 20:48:38 +08:00
let rows = self . with_existing_table ( table , | t | t . scan ()) ? ;
let op = WalOp ::Compact {
table : table . to_string (),
2026-09-25 17:41:51 +08:00
rows ,
};
self . write_via_wal ( & op )
}
2026-10-01 20:48:38 +08:00
/// 启动时整理:把还留着隐藏列的表逐张重写
fn compact_tables ( & self ) -> std ::io ::Result < () > {
let tables = {
let c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
c . dropped_tables ()
};
for table in tables {
log_info! ( core , "compacting table {}" , table );
self . compact_table ( & table ) ? ;
}
Ok (())
}
2026-10-03 02:17:46 +08:00
/// 账号表是否已登记为系统表
fn account_table_ready ( & self ) -> std ::io ::Result < bool > {
use std ::io ::{ Error , ErrorKind };
let registered = {
let c = self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? ;
2026-10-05 00:15:01 +08:00
match c . describe ( IDENTITIES_TABLE ) {
Some ( schema ) if ! schema . system => return Err ( Error ::new ( ErrorKind ::AlreadyExists , "reserved identity table collision" )),
None if self . table_path ( IDENTITIES_TABLE ). exists () || self . index_path ( IDENTITIES_TABLE , "id" ). exists () =>
return Err ( Error ::new ( ErrorKind ::AlreadyExists , "reserved identity file collision" )),
_ => {}
}
2026-10-03 02:17:46 +08:00
c . describe ( USERS_TABLE ). map ( | schema | schema . system )
};
match registered {
Some ( true ) => Ok ( true ),
Some ( false ) => Err ( Error ::new ( ErrorKind ::AlreadyExists , "reserved account table collision" )),
None => {
if self . table_path ( USERS_TABLE ). exists () || self . index_path ( USERS_TABLE , "id" ). exists () {
Err ( Error ::new ( ErrorKind ::AlreadyExists , "reserved account file collision" ))
} else {
Ok ( false )
}
}
}
}
2026-10-09 09:27:44 +08:00
/// 读取系统初始化状态与检查点 LSN
fn system_status ( & self ) -> std ::io ::Result < Response > {
let initialized = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ?
. describe ( USERS_TABLE ). is_some_and ( | schema | schema . system );
let last_lsn = self . wal . read_checkpoint () ? . unwrap_or ( 0 );
Ok ( Response ::System { initialized , last_lsn })
2026-10-03 16:49:23 +08:00
}
2026-10-03 02:17:46 +08:00
/// 读账号表;表没登记或数据文件缺失都算错
fn load_accounts ( & self ) -> std ::io ::Result < Vec < Account >> {
use std ::io ::Error ;
if ! self . table_path ( USERS_TABLE ). exists () {
return Err ( Error ::other ( "missing account data" ));
}
2026-10-05 00:15:01 +08:00
let unified = self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . describe ( IDENTITIES_TABLE ). is_some ();
if unified {
let credentials = self . with_existing_table ( USERS_TABLE , | t | t . scan ()) ? ;
let identities = self . with_existing_table ( IDENTITIES_TABLE , | t | t . scan ()) ? ;
return identities . into_iter (). map ( | mut row | {
for field in [ "can_login" , "is_superuser" , "enabled" ] {
if row . get ( field ). and_then ( serde_json ::Value ::as_bool ). is_none () { return Err ( Error ::other ( "invalid identity attributes" )); }
}
let id = row . get ( "id" ). and_then ( serde_json ::Value ::as_i64 ). ok_or_else ( || Error ::other ( "invalid identity record" )) ? ;
let credential = credentials . iter (). find ( | r | r . get ( "id" ). and_then ( serde_json ::Value ::as_i64 ) == Some ( id ))
. ok_or_else ( || Error ::other ( "missing identity credential" )) ? ;
row . insert ( "password_hash" . into (), credential . get ( "password_hash" ). cloned (). ok_or_else ( || Error ::other ( "invalid identity credential" )) ? );
decode_account_row ( & row )
}). collect ();
}
2026-10-03 02:17:46 +08:00
let mut accounts : Vec < Account > = self
. with_existing_table ( USERS_TABLE , | t | t . scan ()) ?
. iter ()
. map ( decode_account_row )
. collect ::< std ::io ::Result < _ >> () ? ;
for account in accounts . iter_mut () {
normalize_account ( account );
}
Ok ( accounts )
}
/// 引导系统目录:建账号表,空表才补管理员
fn bootstrap_system ( & self , user : Option <& str > , password_hash : Option <& str > ) -> std ::io ::Result < Vec < Account >> {
use std ::io ::Error ;
let _guard = self . write_lock . lock (). map_err ( | _ | Error ::other ( "write lock poisoned" )) ? ;
if self . recovery_required . load ( Ordering ::Acquire ) {
return Err ( Error ::other ( "storage recovery required" ));
}
let initialized = self . account_table_ready () ? ;
let mut accounts = if initialized { self . load_accounts () ? } else { Vec ::new () };
let mut changed = ! initialized ;
if let Some ( user ) = user {
let name = account_name ( user ) ? ;
// 表里已经有行就不再补人:引导程序只在空表上播种,
// 免得有人拿 storage 透传绕过账号接口往里塞一个已知口令的账号
if accounts . is_empty () && ! accounts . iter (). any ( | a | a . user == name ) {
2026-10-05 00:15:01 +08:00
let id = accounts . iter (). map ( | a | a . id ). max (). unwrap_or ( 0 ). max ( self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . identity_high_water )
2026-10-03 02:17:46 +08:00
. checked_add ( 1 ). ok_or_else ( || Error ::other ( "account id exhausted" )) ? ;
2026-10-05 00:15:01 +08:00
let mut administrator = new_account ( id , & name , password_hash . unwrap_or_default (). to_string ()) ? ;
administrator . is_superuser = true ;
accounts . push ( administrator );
2026-10-03 02:17:46 +08:00
changed = true ;
}
}
if ! changed {
return Ok ( accounts );
}
for row in account_rows ( & accounts ) ? {
HeapTable ::encode_row ( & row , self . cfg . page_size ) ? ;
}
2026-10-05 00:15:01 +08:00
let op = WalOp ::Accounts { accounts : accounts . clone (), removed : Vec ::new (), migrate_roles : false };
2026-10-03 02:17:46 +08:00
self . recovery_required . store ( true , Ordering ::Release );
self . wal . truncate () ? ;
self . wal . append ( & op ) ? ;
self . apply_op ( & op ) ? ;
self . wal . clear () ? ;
self . recovery_required . store ( false , Ordering ::Release );
Ok ( accounts )
}
2026-10-01 20:48:38 +08:00
/// 账号读写共享串行持久化边界
fn accounts_request ( & self , request : Request ) -> std ::io ::Result < Vec < Account >> {
use std ::io ::{ Error , ErrorKind };
let _guard = self . write_lock . lock (). map_err ( | _ | Error ::other ( "write lock poisoned" )) ? ;
if self . recovery_required . load ( Ordering ::Acquire ) {
return Err ( Error ::other ( "storage recovery required" ));
}
2026-10-03 02:17:46 +08:00
if ! self . account_table_ready () ? {
return Err ( Error ::other ( "system catalog is not initialized; run csql-bootstrap" ));
2026-10-01 20:48:38 +08:00
}
2026-10-03 02:17:46 +08:00
let mut accounts = self . load_accounts () ? ;
2026-10-05 00:15:01 +08:00
let request = match request {
Request ::CatalogManage { command } => {
let target = match command . as_ref () {
Request ::AccountCreate { user , .. } | Request ::AccountReset { user , .. } | Request ::AccountDrop { user }
2026-10-05 22:22:23 +08:00
| Request ::IdentityAlter { user , is_superuser : None , system_catalog_manager : None , allow_sudo_auth : None , .. } => Some ( account_name ( user ) ? ),
2026-10-05 00:15:01 +08:00
Request ::AccountsList => None ,
_ => return Err ( Error ::other ( "superuser required for this catalog operation" )),
};
2026-10-05 22:22:23 +08:00
if accounts . iter (). any ( | a | target . as_ref () == Some ( & a . user ) && ( a . is_superuser || a . system_catalog_manager || a . allow_sudo_auth )) {
2026-10-05 00:15:01 +08:00
return Err ( Error ::other ( "superuser required to manage privileged identities" ));
}
* command
}
other => other ,
};
let mut names = std ::collections ::HashSet ::new ();
let mut ids = std ::collections ::HashSet ::new ();
for account in & mut accounts {
let name = account_name ( & account . user ) ? ;
if ! names . insert ( name . clone ()) || account . id < 1 || ! ids . insert ( account . id ) {
return Err ( Error ::other ( "invalid or duplicate identity record" ));
}
if account . identity_version == 0 { account . user = name ; }
}
let previous = accounts . clone ();
let migrate_roles = matches! ( request , Request ::IdentityInitialize { .. });
if ! migrate_roles && ! matches! ( request , Request ::AccountsList ) {
let legacy_exists = self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . describe ( "__system_roles" ). is_some ();
if legacy_exists && ! self . with_existing_table ( "__system_roles" , | t | t . scan ()) ? . is_empty () {
return Err ( Error ::other ( "identity migration required" ));
}
}
let had_superuser = accounts . iter (). any ( | a | a . enabled && a . can_login && a . is_superuser );
2026-10-01 20:48:38 +08:00
match request {
Request ::AccountsList => return Ok ( accounts ),
2026-10-05 00:15:01 +08:00
Request ::IdentityInitialize { administrator } => {
for account in & mut accounts {
if account . identity_version == 0 {
account . is_superuser = account . user == administrator . to_ascii_lowercase ();
account . identity_version = 1 ;
}
}
let legacy_roles = "__system_roles" ;
let exists = self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . describe ( legacy_roles ). is_some ();
if exists {
for row in self . with_existing_table ( legacy_roles , | t | t . scan ()) ? {
let name = account_name ( row . get ( "name" ). and_then ( serde_json ::Value ::as_str ). ok_or_else ( || Error ::other ( "invalid legacy role" )) ? ) ? ;
if accounts . iter (). any ( | a | a . user == name ) { return Err ( Error ::other ( format! ( "identity name collision: {name} " ))); }
let id = accounts . iter (). map ( | a | a . id ). max (). unwrap_or ( 0 ). max ( self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . identity_high_water )
. checked_add ( 1 ). ok_or_else ( || Error ::other ( "account id exhausted" )) ? ;
let mut role = new_account ( id , & name , String ::new ()) ? ;
role . can_login = false ;
accounts . push ( role );
}
}
let unified = self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . describe ( IDENTITIES_TABLE ). is_some ();
if ! accounts . is_empty () { require_superuser ( & accounts ) ? ; }
2026-10-05 22:22:23 +08:00
let sudo_column = self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . describe ( IDENTITIES_TABLE )
. is_some_and ( | schema | schema . columns . iter (). any ( | column | column . name == "allow_sudo_auth" ));
if unified && sudo_column && previous == accounts { return Ok ( accounts ); }
2026-10-05 00:15:01 +08:00
}
2026-10-01 20:48:38 +08:00
Request ::AccountCreate { user , password_hash } => {
let name = account_name ( & user ) ? ;
2026-10-05 22:22:23 +08:00
if name == "public" { return Err ( Error ::other ( "public is reserved for default privileges" )); }
2026-10-01 20:48:38 +08:00
if accounts . iter (). any ( | a | a . user == name ) {
return Err ( Error ::new ( ErrorKind ::AlreadyExists , "account already exists" ));
}
2026-10-05 00:15:01 +08:00
let id = accounts . iter (). map ( | a | a . id ). max (). unwrap_or ( 0 ). max ( self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . identity_high_water )
2026-10-01 20:48:38 +08:00
. checked_add ( 1 ). ok_or_else ( || Error ::other ( "account id exhausted" )) ? ;
accounts . push ( new_account ( id , & name , password_hash ) ? );
}
2026-10-05 00:15:01 +08:00
Request ::RoleCreate { user } => {
let name = account_name ( & user ) ? ;
2026-10-05 22:22:23 +08:00
if name == "public" { return Err ( Error ::other ( "public is reserved for default privileges" )); }
2026-10-05 00:15:01 +08:00
if accounts . iter (). any ( | a | a . user == name ) { return Err ( Error ::new ( ErrorKind ::AlreadyExists , "identity already exists" )); }
let id = accounts . iter (). map ( | a | a . id ). max (). unwrap_or ( 0 ). max ( self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . identity_high_water )
. checked_add ( 1 ). ok_or_else ( || Error ::other ( "account id exhausted" )) ? ;
let mut role = new_account ( id , & name , String ::new ()) ? ;
role . can_login = false ;
accounts . push ( role );
}
2026-10-05 22:22:23 +08:00
Request ::IdentityAlter { user , can_login , is_superuser , enabled , system_catalog_manager , allow_sudo_auth } => {
2026-10-05 00:15:01 +08:00
let name = account_name ( & user ) ? ;
let account = accounts . iter_mut (). find ( | a | a . user == name ). ok_or_else ( || Error ::new ( ErrorKind ::NotFound , "unknown account" )) ? ;
account . can_login = can_login . unwrap_or ( account . can_login );
account . is_superuser = is_superuser . unwrap_or ( account . is_superuser );
account . enabled = enabled . unwrap_or ( account . enabled );
account . system_catalog_manager = system_catalog_manager . unwrap_or ( account . system_catalog_manager );
2026-10-05 22:22:23 +08:00
account . allow_sudo_auth = allow_sudo_auth . unwrap_or ( account . allow_sudo_auth );
2026-10-05 00:15:01 +08:00
account . revision = account . revision . checked_add ( 1 ). ok_or_else ( || Error ::other ( "account revision exhausted" )) ? ;
if had_superuser { require_superuser ( & accounts ) ? ; }
}
2026-10-01 20:48:38 +08:00
Request ::AccountReset { user , password_hash } => {
let name = account_name ( & user ) ? ;
validate_account_hash ( & password_hash ) ? ;
let account = accounts . iter_mut (). find ( | a | a . user == name )
. ok_or_else ( || Error ::new ( ErrorKind ::NotFound , "unknown account" )) ? ;
account . revision = account . revision . checked_add ( 1 ). ok_or_else ( || Error ::other ( "account revision exhausted" )) ? ;
account . password_hash = password_hash ;
}
Request ::AccountLogin { user , at } => {
let name = account_name ( & user ) ? ;
let stamp = at . unwrap_or_else ( now_stamp );
let account = accounts . iter_mut (). find ( | a | a . user == name )
. ok_or_else ( || Error ::new ( ErrorKind ::NotFound , "unknown account" )) ? ;
2026-10-05 00:15:01 +08:00
if ! account . enabled || ! account . can_login { return Err ( Error ::other ( "identity cannot login" )); }
2026-10-01 20:48:38 +08:00
account . last_login_at = Some ( stamp );
}
Request ::AccountDrop { user } => {
let name = account_name ( & user ) ? ;
let before = accounts . len ();
accounts . retain ( | a | a . user != name );
if accounts . len () == before {
return Err ( Error ::new ( ErrorKind ::NotFound , "unknown account" ));
}
2026-10-05 00:15:01 +08:00
if had_superuser { require_superuser ( & accounts ) ? ; }
2026-10-01 20:48:38 +08:00
}
_ => return Err ( Error ::other ( "unexpected account request" )),
}
2026-10-05 00:15:01 +08:00
for row in account_rows ( & accounts ) ? . into_iter (). chain ( identity_rows ( & accounts ) ? ) {
2026-10-01 20:48:38 +08:00
HeapTable ::encode_row ( & row , self . cfg . page_size ) ? ;
}
2026-10-05 00:15:01 +08:00
let removed = previous . iter (). filter ( | old | ! accounts . iter (). any ( | a | a . user == old . user )). map ( | a | a . user . clone ()). collect ();
let op = WalOp ::Accounts { accounts : accounts . clone (), removed , migrate_roles };
2026-10-01 20:48:38 +08:00
self . recovery_required . store ( true , Ordering ::Release );
self . wal . truncate () ? ;
self . wal . append ( & op ) ? ;
self . apply_op ( & op ) ? ;
self . wal . clear () ? ;
self . recovery_required . store ( false , Ordering ::Release );
Ok ( accounts )
}
2026-10-05 00:15:01 +08:00
/// 初始化并校验内置类型目录
fn bootstrap_types ( & self , types : Vec < Row > ) -> std ::io ::Result < Vec < Row >> {
use std ::io ::Error ;
let _guard = self . write_lock . lock (). map_err ( | _ | Error ::other ( "write lock poisoned" )) ? ;
if ! self . account_table_ready () ? { return Err ( Error ::other ( "system catalog is not initialized; run csql-bootstrap" )); }
require_superuser ( & self . load_accounts () ? ) ? ;
let columns = types_columns ();
let existing = self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . describe ( TYPES_TABLE ). cloned ();
if existing . as_ref (). is_some_and ( | s | ! s . system || s . columns != columns ) { return Err ( Error ::other ( "reserved type catalog collision" )); }
if existing . is_none () && ( self . table_path ( TYPES_TABLE ). exists () || self . index_path ( TYPES_TABLE , "id" ). exists ()) {
return Err ( Error ::other ( "reserved type file collision" ));
}
let mut rows = if existing . is_some () { self . with_existing_table ( TYPES_TABLE , | t | t . scan ()) ? } else { Vec ::new () };
let mut names = std ::collections ::HashSet ::new ();
let mut ids = std ::collections ::HashSet ::new ();
for row in & rows {
let name = type_definition_name ( row ) ? ;
let id = row . get ( "id" ). and_then ( serde_json ::Value ::as_i64 ). ok_or_else ( || Error ::other ( "invalid type id" )) ? ;
if id < 1 || ! ids . insert ( id ) || ! names . insert ( name . to_string ()) { return Err ( Error ::other ( "duplicate type catalog record" )); }
}
let original = rows . clone ();
let mut requested = std ::collections ::HashSet ::new ();
for mut definition in types {
let name = type_definition_name ( & definition ) ? . to_string ();
if definition . len () != 3 || ! requested . insert ( name . clone ()) { return Err ( Error ::other ( "invalid or duplicate preinstalled type" )); }
if let Some ( saved ) = rows . iter (). find ( | row | row . get ( "name" ). and_then ( serde_json ::Value ::as_str ) == Some ( & name )) {
if saved . get ( "base_type" ) != definition . get ( "base_type" ) || saved . get ( "parameterized" ) != definition . get ( "parameterized" ) {
return Err ( Error ::other ( format! ( "preinstalled type definition conflict: {name} " )));
}
} else {
let id = rows . iter (). filter_map ( | r | r . get ( "id" ). and_then ( serde_json ::Value ::as_i64 )). max (). unwrap_or ( 0 )
. checked_add ( 1 ). ok_or_else ( || Error ::other ( "type id exhausted" )) ? ;
definition . insert ( "id" . into (), serde_json ::json! ( id ));
HeapTable ::encode_row ( & definition , self . cfg . page_size ) ? ;
rows . push ( definition );
}
}
if existing . is_some () && original == rows { return Ok ( rows ); }
let mut ops = Vec ::new ();
2026-10-05 22:22:23 +08:00
if existing . is_none () { ops . push ( WalOp ::CreateTable { table : TYPES_TABLE . into (), columns , object_id : None }); }
2026-10-05 00:15:01 +08:00
ops . push ( WalOp ::ReplaceAll { table : TYPES_TABLE . into (), rows : rows . clone () });
self . recovery_required . store ( true , Ordering ::Release );
self . group_via_wal ( & ops ) ? ;
self . recovery_required . store ( false , Ordering ::Release );
Ok ( rows )
}
2026-10-03 02:17:46 +08:00
/// 一条请求翻成一条响应:先过读/写门再分发
2026-09-24 21:28:49 +08:00
fn handle_request ( & self , req : Request ) -> Response {
2026-10-03 02:17:46 +08:00
if concurrent_read ( & req ) {
match self . gate . read () {
Ok ( _read ) => self . dispatch ( req ),
Err ( _ ) => Response ::Error {
message : "storage read gate poisoned" . into (),
},
}
} else {
match self . gate . write () {
Ok ( _write ) => self . dispatch ( req ),
Err ( _ ) => Response ::Error {
message : "storage write gate poisoned" . into (),
},
}
}
}
2026-10-15 22:21:41 +08:00
/// 持库门时复查绑定身份、版本和列定义
2026-10-11 17:32:12 +08:00
fn validate_bound_schema ( & self , table : & str , expected : & crate ::protocol ::BoundSchema ) -> std ::io ::Result < () > {
use std ::io ::Error ;
if self . recovery_required . load ( Ordering ::Acquire ) { return Err ( Error ::other ( "storage recovery required" )); }
if blocked_table ( table ) { return Err ( Error ::other ( "reserved system table" )); }
let catalog = self . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? ;
let actual = catalog . describe ( table ). ok_or_else ( || Error ::other ( format! ( "bound query schema changed: {} " , expected . table ))) ? ;
match ( & expected . database_id , & expected . object_id ) {
( Some ( database ), Some ( object )) => {
if parse_bound_number ( database ) ? != catalog . database_id || parse_bound_number ( object ) ? != actual . object_id {
return Err ( Error ::other ( format! ( "bound query object changed: {} " , expected . table )));
}
}
( None , None ) => {}
_ => return Err ( Error ::other ( "incomplete bound object identity" )),
}
if let Some ( version ) = & expected . schema_version
&& parse_bound_number ( version ) ? != actual . schema_version {
return Err ( Error ::other ( format! ( "bound query schema changed: {} " , expected . table )));
}
if expected . columns != actual . columns {
return Err ( Error ::other ( format! ( "bound query schema changed: {} " , expected . table )));
}
Ok (())
}
2026-10-03 02:17:46 +08:00
/// 持门后分发一条请求
fn dispatch ( & self , req : Request ) -> Response {
2026-10-01 20:48:38 +08:00
if self . recovery_required . load ( Ordering ::Acquire ) && ! matches! ( req , Request ::Ping ) {
return Response ::Error { message : "storage recovery required" . into () };
}
if ! req . valid_columns () {
return Response ::Error { message : "invalid column name" . into () };
}
if let Some ( table ) = req . table () {
2026-10-03 17:16:26 +08:00
if blocked_table ( table ) {
2026-10-01 20:48:38 +08:00
return Response ::Error { message : "reserved system table" . into () };
}
if table . is_empty () || ! table . chars (). all ( | c | c . is_ascii_alphanumeric () || c == '_' ) {
return Response ::Error { message : "invalid table name" . into () };
}
}
2026-09-24 21:28:49 +08:00
match req {
2026-10-15 22:21:41 +08:00
Request ::ValidateWrite { .. } => Response ::Ok ,
2026-10-05 00:15:01 +08:00
Request ::BootstrapTypes { types } => match self . bootstrap_types ( types ) {
Ok ( rows ) => Response ::Rows { rows },
Err ( e ) => Response ::Error { message : e . to_string () },
},
request @ ( Request ::AccountsList | Request ::CatalogManage { .. } | Request ::AccountCreate { .. }
| Request ::IdentityInitialize { .. } | Request ::RoleCreate { .. } | Request ::IdentityAlter { .. }
2026-10-01 20:48:38 +08:00
| Request ::AccountReset { .. } | Request ::AccountLogin { .. }
| Request ::AccountDrop { .. }) => {
match self . accounts_request ( request ) {
Ok ( accounts ) => Response ::Accounts { accounts },
Err ( e ) => Response ::Error { message : e . to_string () },
}
}
2026-10-03 02:17:46 +08:00
Request ::BootstrapSystem { user , password_hash } => {
2026-10-09 09:27:44 +08:00
match self . bootstrap_system ( user . as_deref (), password_hash . as_deref ()). and_then ( | _ | self . system_status ()) {
Ok ( response ) => response ,
2026-10-03 02:17:46 +08:00
Err ( e ) => Response ::Error { message : e . to_string () },
}
}
2026-10-09 09:27:44 +08:00
Request ::SystemStatus => self . system_status (). unwrap_or_else ( | e | Response ::Error { message : e . to_string () }),
2026-09-24 21:28:49 +08:00
Request ::Ping => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "ping" );
2026-09-24 21:28:49 +08:00
Response ::Pong
}
2026-10-03 16:49:23 +08:00
Request ::ApplyTransaction { ops } => {
log_debug! ( request , "apply_transaction ops={}" , ops . len ());
match self . apply_transaction ( & ops ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
log_warn! ( request , "apply_transaction: {}" , e );
Response ::Error {
message : format ! ( "apply_transaction: {e}" ),
}
}
}
}
2026-10-01 20:48:38 +08:00
Request ::Scan { table , columns } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "scan table={}" , table );
2026-09-24 21:28:49 +08:00
2026-10-01 20:48:38 +08:00
let result = ( || {
2026-10-03 02:17:46 +08:00
self . check_columns ( & table , columns . as_deref ()) ? ;
2026-10-01 20:48:38 +08:00
self . with_existing_table ( & table , | t | t . scan_columns ( columns . as_deref ()))
})();
2026-09-24 21:28:49 +08:00
match result {
Ok ( rows ) => Response ::Rows { rows },
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "scan {}: {}" , table , e );
2026-09-24 21:28:49 +08:00
Response ::Error {
message : format ! ( "scan {}: {}" , table , e ),
}
}
}
}
2026-10-03 02:17:46 +08:00
Request ::ScanShard {
table ,
columns ,
shard ,
shards ,
} => {
log_debug! ( request , "scan_shard table={} shard={}/{}" , table , shard , shards );
let result = ( || {
self . check_columns ( & table , columns . as_deref ()) ? ;
self . scan_shard ( & table , columns . as_deref (), shard , shards )
})();
match result {
Ok ( rows ) => Response ::Rows { rows },
Err ( e ) => {
log_warn! ( request , "scan {} shard {}/{}: {}" , table , shard , shards , e );
Response ::Error {
message : format ! ( "scan {}: {}" , table , e ),
}
}
}
}
2026-09-25 14:21:55 +08:00
Request ::Insert { table , row } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "insert table={}" , table );
2026-09-24 21:28:49 +08:00
let tname = table . clone ();
2026-09-25 14:21:55 +08:00
let op = WalOp ::Insert { table , row };
2026-09-24 21:28:49 +08:00
match self . write_via_wal ( & op ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "insert {}: {}" , tname , e );
2026-09-24 21:28:49 +08:00
Response ::Error {
message : format ! ( "insert {}: {}" , tname , e ),
}
}
}
}
2026-09-25 14:21:55 +08:00
Request ::InsertBatch { table , rows } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "insert_batch table={} rows={}" , table , rows . len ());
2026-09-25 14:21:55 +08:00
let tname = table . clone ();
let op = WalOp ::InsertBatch { table , rows };
match self . write_via_wal ( & op ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "insert_batch {}: {}" , tname , e );
2026-09-25 14:21:55 +08:00
Response ::Error {
message : format ! ( "insert_batch {}: {}" , tname , e ),
}
}
}
}
Request ::DeleteKeys { table , keys } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "delete_keys table={} keys={}" , table , keys . len ());
2026-09-25 14:21:55 +08:00
let tname = table . clone ();
let op = WalOp ::DeleteKeys { table , keys };
match self . write_via_wal ( & op ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "delete_keys {}: {}" , tname , e );
2026-09-25 14:21:55 +08:00
Response ::Error {
message : format ! ( "delete_keys {}: {}" , tname , e ),
}
}
}
}
Request ::LookupByIndex { table , column , key } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "lookup_by_index table={} column={} key={}" , table , column , key );
2026-09-25 14:21:55 +08:00
let indexed =
match self . with_existing_table ( & table , | t | Ok ( t . has_index ( & column ))) {
Ok ( v ) => v ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "lookup_by_index {}: {}" , table , e );
2026-09-25 14:21:55 +08:00
return Response ::Error {
message : format ! ( "lookup_by_index {}: {}" , table , e ),
};
}
};
if ! indexed {
return Response ::NoIndex ;
}
2026-10-03 02:17:46 +08:00
let found = match scalar_int ( & key ) {
2026-10-01 20:48:38 +08:00
Some ( k ) => self . with_table ( & table , | t | t . get_all_by_column_key ( & column , k )),
None => match key . as_str () {
Some ( s ) => self . with_table ( & table , | t | t . get_all_by_string_key ( & column , s )),
None => {
return Response ::Error {
message : "lookup_by_index: key must be a number or a string" . into (),
};
}
},
};
match found {
Ok ( rows ) => Response ::Rows { rows },
2026-09-24 21:28:49 +08:00
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "lookup_by_index {}: {}" , table , e );
2026-09-24 21:28:49 +08:00
Response ::Error {
message : format ! ( "lookup_by_index {}: {}" , table , e ),
}
}
}
}
2026-10-01 20:48:38 +08:00
Request ::RangeByIndex { table , column , lo , lo_inclusive , hi , hi_inclusive } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "range_by_index table={} column={}" , table , column );
2026-10-01 20:48:38 +08:00
let indexed =
match self . with_existing_table ( & table , | t | Ok ( t . has_index ( & column ))) {
Ok ( v ) => v ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "range_by_index {}: {}" , table , e );
2026-10-01 20:48:38 +08:00
return Response ::Error {
message : format ! ( "range_by_index {}: {}" , table , e ),
};
}
};
if ! indexed {
return Response ::NoIndex ;
}
let lo = lo . as_ref (). map ( | v | ( v , lo_inclusive ));
let hi = hi . as_ref (). map ( | v | ( v , hi_inclusive ));
match self . with_table ( & table , | t | t . scan_range ( & column , lo , hi )) {
Ok ( Some ( rows )) => Response ::Rows { rows },
Ok ( None ) => Response ::NoIndex ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "range_by_index {}: {}" , table , e );
2026-10-01 20:48:38 +08:00
Response ::Error {
message : format ! ( "range_by_index {}: {}" , table , e ),
}
}
}
}
2026-09-24 21:28:49 +08:00
Request ::ReplaceAll { table , rows } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "replace_all table={} rows={}" , table , rows . len ());
2026-09-24 21:28:49 +08:00
let tname = table . clone ();
let op = WalOp ::ReplaceAll { table , rows };
match self . write_via_wal ( & op ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "replace_all {}: {}" , tname , e );
2026-09-24 21:28:49 +08:00
Response ::Error {
message : format ! ( "replace_all {}: {}" , tname , e ),
}
}
}
}
Request ::ListTables => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "list_tables" );
2026-09-24 21:28:49 +08:00
match self . list_tables () {
Ok ( tables ) => Response ::Tables { tables },
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "list_tables: {}" , e );
2026-09-24 21:28:49 +08:00
Response ::Error {
message : format ! ( "list_tables: {}" , e ),
}
}
}
}
2026-09-24 23:56:07 +08:00
Request ::ListCatalog => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "list_catalog" );
2026-09-24 23:56:07 +08:00
let c = self . catalog . lock (). unwrap ();
let schemas : Vec < TableSchemaWire > = c
. all_tables ()
. into_iter ()
2026-10-03 17:16:26 +08:00
. filter ( | ( name , _ ) | ! blocked_table ( name ))
2026-09-24 23:56:07 +08:00
. map ( | ( name , ts ) | TableSchemaWire {
table : name . to_string (),
2026-10-05 22:22:23 +08:00
database_id : c . database_id ,
object_id : ts . object_id ,
2026-10-11 17:32:12 +08:00
schema_version : ts . schema_version ,
2026-09-24 23:56:07 +08:00
columns : ts . columns . clone (),
row_count : ts . row_count ,
2026-09-25 14:21:55 +08:00
indexes : index_wires ( ts ),
stats : stat_wires ( ts ),
2026-09-24 23:56:07 +08:00
})
. collect ();
Response ::Catalog { schemas }
}
2026-09-24 21:28:49 +08:00
Request ::DescribeTable { table } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "describe_table table={}" , table );
2026-09-24 21:28:49 +08:00
match self . describe_table ( & table ) {
2026-09-25 14:21:55 +08:00
Ok (( cols , count , indexes , stats )) => Response ::Schema {
2026-10-11 17:32:12 +08:00
schema_version : self . catalog . lock (). unwrap (). describe ( & table ). map_or ( 0 , | s | s . schema_version ),
2026-09-25 14:21:55 +08:00
table ,
2026-09-24 21:28:49 +08:00
columns : cols ,
row_count : count ,
2026-09-25 14:21:55 +08:00
indexes ,
stats ,
2026-09-24 21:28:49 +08:00
},
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "describe_table {}: {}" , table , e );
2026-09-24 21:28:49 +08:00
Response ::Error {
message : format ! ( "describe_table {}: {}" , table , e ),
}
}
}
}
Request ::CreateTable { table , columns } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "create_table table={} cols={}" , table , columns . len ());
2026-10-03 17:04:28 +08:00
let exists = match self . table_exists ( & table ) {
Ok ( exists ) => exists ,
Err ( e ) => {
2026-09-24 21:28:49 +08:00
return Response ::Error {
2026-10-03 17:04:28 +08:00
message : format ! ( "create_table {}: {}" , table , e ),
}
}
};
if exists {
return Response ::Error {
message : format ! ( "create_table {}: table already exists" , table ),
};
}
let tname = table . clone ();
2026-10-05 22:22:23 +08:00
let op = WalOp ::CreateTable { table , columns , object_id : None };
2026-10-03 17:04:28 +08:00
match self . write_via_wal ( & op ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
log_warn! ( request , "create_table {}: {}" , tname , e );
Response ::Error {
message : format ! ( "create_table {}: {}" , tname , e ),
}
2026-09-24 21:28:49 +08:00
}
}
}
2026-09-24 23:56:07 +08:00
2026-09-24 21:28:49 +08:00
Request ::DropTable { table } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "drop_table table={}" , table );
2026-10-03 17:04:28 +08:00
let exists = match self . table_exists ( & table ) {
Ok ( exists ) => exists ,
Err ( e ) => {
2026-09-24 23:56:07 +08:00
return Response ::Error {
message : format ! ( "drop_table {}: {}" , table , e ),
2026-10-03 17:04:28 +08:00
}
2026-09-24 23:56:07 +08:00
}
2026-09-25 14:21:55 +08:00
};
2026-10-03 17:04:28 +08:00
if ! exists {
return Response ::Error {
message : format ! ( "drop_table {}: unknown table: {}" , table , table ),
};
}
let tname = table . clone ();
let op = WalOp ::DropTable { table };
match self . write_via_wal ( & op ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
log_warn! ( request , "drop_table {}: {}" , tname , e );
Response ::Error {
message : format ! ( "drop_table {}: {}" , tname , e ),
}
}
2026-09-25 14:21:55 +08:00
}
2026-09-24 21:28:49 +08:00
}
2026-09-25 14:21:55 +08:00
Request ::CreateIndex { table , column } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "create_index table={} column={}" , table , column );
2026-10-03 17:04:28 +08:00
if let Err ( e ) = self . check_create_index ( & table , & column ) {
log_warn! ( request , "create_index {}({}): {}" , table , column , e );
return Response ::Error {
message : format ! ( "create_index {}({}): {}" , table , column , e ),
};
}
let tname = table . clone ();
let op = WalOp ::CreateIndex { table , column };
match self . write_via_wal ( & op ) {
2026-09-25 14:21:55 +08:00
Ok (()) => Response ::Ok ,
Err ( e ) => {
2026-10-03 17:04:28 +08:00
log_warn! ( request , "create_index {}: {}" , tname , e );
2026-09-25 14:21:55 +08:00
Response ::Error {
2026-10-03 17:04:28 +08:00
message : format ! ( "create_index {}: {}" , tname , e ),
2026-09-25 14:21:55 +08:00
}
}
}
}
Request ::DropIndex { table , column } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "drop_index table={} column={}" , table , column );
2026-10-03 17:04:28 +08:00
if let Err ( e ) = self . check_drop_index ( & table , & column ) {
log_warn! ( request , "drop_index {}({}): {}" , table , column , e );
return Response ::Error {
message : format ! ( "drop_index {}({}): {}" , table , column , e ),
};
}
let tname = table . clone ();
let op = WalOp ::DropIndex { table , column };
match self . write_via_wal ( & op ) {
2026-09-25 14:21:55 +08:00
Ok (()) => Response ::Ok ,
Err ( e ) => {
2026-10-03 17:04:28 +08:00
log_warn! ( request , "drop_index {}: {}" , tname , e );
2026-09-25 14:21:55 +08:00
Response ::Error {
2026-10-03 17:04:28 +08:00
message : format ! ( "drop_index {}: {}" , tname , e ),
2026-09-25 14:21:55 +08:00
}
}
}
}
2026-09-25 17:41:51 +08:00
Request ::DropColumn { table , column } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "drop_column table={} column={}" , table , column );
2026-09-25 17:41:51 +08:00
match self . drop_column ( & table , & column ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "drop_column {}({}): {}" , table , column , e );
2026-09-25 17:41:51 +08:00
Response ::Error {
message : format ! ( "drop_column {}({}): {}" , table , column , e ),
}
}
}
}
2026-09-24 21:28:49 +08:00
2026-10-01 20:48:38 +08:00
Request ::Compact { table } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "compact table={}" , table );
2026-10-01 20:48:38 +08:00
match self . compact_table ( & table ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "compact {}: {}" , table , e );
2026-10-01 20:48:38 +08:00
Response ::Error {
message : format ! ( "compact {}: {}" , table , e ),
}
}
}
2026-09-24 23:56:07 +08:00
}
2026-10-01 20:48:38 +08:00
Request ::ReplaceSchema { table , columns , rows } => {
2026-10-02 14:53:02 +08:00
log_debug! ( request , "replace_schema table={} cols={}" , table , columns . len ());
2026-10-01 20:48:38 +08:00
let tname = table . clone ();
2026-10-11 17:32:12 +08:00
let op = WalOp ::ReplaceSchema { table , columns , rows , schema_version : None };
2026-10-01 20:48:38 +08:00
match self . write_via_wal ( & op ) {
Ok (()) => Response ::Ok ,
Err ( e ) => {
2026-10-02 14:53:02 +08:00
log_warn! ( request , "replace_schema {}: {}" , tname , e );
2026-10-01 20:48:38 +08:00
Response ::Error {
message : format ! ( "replace_schema {}: {}" , tname , e ),
}
}
2026-09-24 21:28:49 +08:00
}
}
}
2026-10-01 20:48:38 +08:00
}
2026-10-03 17:16:26 +08:00
/// 列出业务表与授权服务用的三张系统表
2026-10-01 20:48:38 +08:00
fn list_tables ( & self ) -> std ::io ::Result < Vec < String >> {
let c = self . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
2026-10-03 17:16:26 +08:00
Ok ( c . table_names (). into_iter (). filter ( | t | ! blocked_table ( t )). collect ())
2026-09-24 21:28:49 +08:00
}
2026-09-25 17:41:51 +08:00
/// 查一张表的 schema
2026-09-25 14:21:55 +08:00
fn describe_table (
& self ,
table : & str ,
2026-10-01 20:48:38 +08:00
) -> std ::io ::Result < TableDescription > {
2026-09-24 23:56:07 +08:00
let c = self . catalog . lock (). unwrap ();
2026-09-24 21:28:49 +08:00
if let Some ( schema ) = c . describe ( table ) {
2026-09-25 14:21:55 +08:00
return Ok ((
schema . columns . clone (),
schema . row_count ,
index_wires ( schema ),
stat_wires ( schema ),
));
2026-09-24 21:28:49 +08:00
}
2026-09-24 23:56:07 +08:00
drop ( c );
2026-09-24 21:28:49 +08:00
Err ( std ::io ::Error ::new (
2026-09-24 15:37:55 +08:00
std ::io ::ErrorKind ::NotFound ,
format! ( "unknown table: {} " , table ),
2026-09-24 21:28:49 +08:00
))
2026-09-24 15:37:55 +08:00
}
}
2026-10-05 00:15:01 +08:00
/// 构造内置类型目录列定义
fn types_columns () -> Vec < SchemaColumn > {
vec! [
SchemaColumn { name : "id" . into (), ty : "int" . into (), nullable : false , primary_key : true , .. Default ::default () },
SchemaColumn { name : "name" . into (), ty : "varchar(64)" . into (), nullable : false , unique : true , .. Default ::default () },
SchemaColumn { name : "base_type" . into (), ty : "varchar(64)" . into (), nullable : false , .. Default ::default () },
SchemaColumn { name : "parameterized" . into (), ty : "bool" . into (), nullable : false , .. Default ::default () },
]
}
/// 校验类型定义并读取名称
fn type_definition_name ( row : & Row ) -> std ::io ::Result <& str > {
let name = row . get ( "name" ). and_then ( serde_json ::Value ::as_str ). ok_or_else ( || std ::io ::Error ::other ( "invalid type name" )) ? ;
let base = row . get ( "base_type" ). and_then ( serde_json ::Value ::as_str ). ok_or_else ( || std ::io ::Error ::other ( "invalid type definition" )) ? ;
if name . is_empty () || name . len () > 48 || ! name . bytes (). all ( | c | c . is_ascii_lowercase () || c . is_ascii_digit () || c == b '_' )
|| base . is_empty () || base . len () > 64 || row . get ( "parameterized" ). and_then ( serde_json ::Value ::as_bool ). is_none () {
return Err ( std ::io ::Error ::other ( "invalid type definition" ));
}
Ok ( name )
}
2026-10-02 14:53:02 +08:00
/// 账号列表转成行
2026-10-01 20:48:38 +08:00
fn account_rows ( accounts : & [ Account ]) -> std ::io ::Result < Vec < Row >> {
accounts . iter (). map ( | a | Ok ( HashMap ::from ([
( "id" . into (), serde_json ::json! ( a . id )),
( "password_hash" . into (), serde_json ::Value ::String ( a . password_hash . clone ())),
2026-10-05 00:15:01 +08:00
]))). collect ()
}
/// 身份元数据转成行
fn identity_rows ( accounts : & [ Account ]) -> std ::io ::Result < Vec < Row >> {
accounts . iter (). map ( | a | Ok ( HashMap ::from ([
( "id" . into (), serde_json ::json! ( a . id )),
( "user" . into (), serde_json ::Value ::String ( a . user . clone ())),
( "can_login" . into (), serde_json ::json! ( a . can_login )),
( "is_superuser" . into (), serde_json ::json! ( a . is_superuser )),
( "system_catalog_manager" . into (), serde_json ::json! ( a . system_catalog_manager )),
2026-10-05 22:22:23 +08:00
( "allow_sudo_auth" . into (), serde_json ::json! ( a . allow_sudo_auth )),
2026-10-05 00:15:01 +08:00
( "enabled" . into (), serde_json ::json! ( a . enabled )),
( "identity_version" . into (), serde_json ::json! ( a . identity_version )),
2026-10-01 20:48:38 +08:00
( "registered_at" . into (), serde_json ::Value ::String ( a . registered_at . clone ())),
( "last_login_at" . into (), match & a . last_login_at {
Some ( stamp ) => serde_json ::Value ::String ( stamp . clone ()),
None => serde_json ::Value ::Null ,
}),
( "revision" . into (), serde_json ::json! ( a . revision )),
]))). collect ()
}
2026-10-03 02:17:46 +08:00
/// 系统库的账号表结构:引导模块按这份定义建表
fn users_columns () -> Vec < SchemaColumn > {
vec! [
SchemaColumn { name : "id" . into (), ty : "int" . into (), nullable : false ,
auto_increment : true , primary_key : true , .. Default ::default () },
SchemaColumn { name : "password_hash" . into (), ty : "varchar(256)" . into (),
nullable : false , .. Default ::default () },
2026-10-05 00:15:01 +08:00
]
}
/// 系统身份目录结构
fn identity_columns () -> Vec < SchemaColumn > {
vec! [
SchemaColumn { name : "id" . into (), ty : "int" . into (), nullable : false ,
primary_key : true , .. Default ::default () },
SchemaColumn { name : "user" . into (), ty : "varchar(64)" . into (), nullable : false ,
unique : true , .. Default ::default () },
SchemaColumn { name : "can_login" . into (), ty : "bool" . into (), nullable : false , .. Default ::default () },
SchemaColumn { name : "is_superuser" . into (), ty : "bool" . into (), nullable : false , .. Default ::default () },
SchemaColumn { name : "system_catalog_manager" . into (), ty : "bool" . into (), nullable : false , .. Default ::default () },
2026-10-05 22:22:23 +08:00
SchemaColumn { name : "allow_sudo_auth" . into (), ty : "bool" . into (), nullable : false , .. Default ::default () },
2026-10-05 00:15:01 +08:00
SchemaColumn { name : "enabled" . into (), ty : "bool" . into (), nullable : false , .. Default ::default () },
SchemaColumn { name : "identity_version" . into (), ty : "int" . into (), nullable : false , .. Default ::default () },
2026-10-03 02:17:46 +08:00
SchemaColumn { name : "registered_at" . into (), ty : "timestamp" . into (),
nullable : false , .. Default ::default () },
SchemaColumn { name : "last_login_at" . into (), ty : "timestamp" . into (),
.. Default ::default () },
SchemaColumn { name : "revision" . into (), ty : "int" . into (), nullable : false ,
.. Default ::default () },
]
}
/// 把系统表写进字典
2026-10-01 20:48:38 +08:00
fn declare_system_schema ( catalog : & mut Catalog , rows : & [ Row ]) -> std ::io ::Result < () > {
2026-10-11 17:32:12 +08:00
catalog . rebuild_stats ( USERS_TABLE , rows ) ? ;
2026-10-03 02:17:46 +08:00
catalog . set_columns ( USERS_TABLE , users_columns ()) ? ;
2026-10-11 17:32:12 +08:00
catalog . rebuild_stats ( USERS_TABLE , rows ) ? ;
2026-10-03 02:17:46 +08:00
catalog . mark_system ( USERS_TABLE ) ? ;
2026-10-01 20:48:38 +08:00
Ok (())
}
2026-10-02 14:53:02 +08:00
/// 读一行账号(兼容老格式)
2026-10-01 20:48:38 +08:00
fn decode_account_row ( row : & Row ) -> std ::io ::Result < Account > {
if let Some ( encoded ) = row . get ( "account" ). and_then ( serde_json ::Value ::as_str ) {
return serde_json ::from_str ( encoded ). map_err ( | _ | std ::io ::Error ::other ( "invalid account record" ));
}
let id = row . get ( "id" ). and_then ( serde_json ::Value ::as_i64 )
. ok_or_else ( || std ::io ::Error ::other ( "invalid account record" )) ? ;
let user = row . get ( "user" ). and_then ( serde_json ::Value ::as_str )
. ok_or_else ( || std ::io ::Error ::other ( "invalid account record" )) ? ;
let password_hash = row . get ( "password_hash" ). and_then ( serde_json ::Value ::as_str )
. ok_or_else ( || std ::io ::Error ::other ( "invalid account record" )) ? ;
Ok ( Account {
id ,
user : user . to_string (),
password_hash : password_hash . to_string (),
revision : row . get ( "revision" ). and_then ( serde_json ::Value ::as_u64 ). unwrap_or ( 1 ),
registered_at : row . get ( "registered_at" ). and_then ( serde_json ::Value ::as_str ). unwrap_or ( "" ). to_string (),
last_login_at : row . get ( "last_login_at" ). and_then ( serde_json ::Value ::as_str ). map ( str ::to_string ),
2026-10-05 00:15:01 +08:00
can_login : row . get ( "can_login" ). and_then ( serde_json ::Value ::as_bool ). unwrap_or ( true ),
is_superuser : row . get ( "is_superuser" ). and_then ( serde_json ::Value ::as_bool ). unwrap_or ( false ),
system_catalog_manager : row . get ( "system_catalog_manager" ). and_then ( serde_json ::Value ::as_bool ). unwrap_or ( false ),
2026-10-05 22:22:23 +08:00
allow_sudo_auth : match row . get ( "allow_sudo_auth" ) {
None => false ,
Some ( value ) => value . as_bool (). ok_or_else ( || std ::io ::Error ::other ( "invalid sudo authentication attribute" )) ? ,
},
2026-10-05 00:15:01 +08:00
enabled : row . get ( "enabled" ). and_then ( serde_json ::Value ::as_bool ). unwrap_or ( true ),
identity_version : row . get ( "identity_version" ). and_then ( serde_json ::Value ::as_u64 ). unwrap_or ( 0 ) as u8 ,
2026-10-01 20:48:38 +08:00
})
}
/// 老记录没有注册时间就补当前时间
fn normalize_account ( account : & mut Account ) {
if account . registered_at . is_empty () {
account . registered_at = now_stamp ();
}
}
/// 当前 UTC 时间,写法与引擎的 timestamp 一致
fn now_stamp () -> String {
let seconds = std ::time ::SystemTime ::now ()
. duration_since ( std ::time ::UNIX_EPOCH )
. map ( | d | d . as_secs () as i64 )
. unwrap_or ( 0 );
format_timestamp ( seconds )
}
2026-10-02 14:53:02 +08:00
/// Unix 秒转时间戳文本
2026-10-01 20:48:38 +08:00
fn format_timestamp ( seconds : i64 ) -> String {
let days = seconds . div_euclid ( 86_400 );
let rest = seconds . rem_euclid ( 86_400 );
let ( year , month , day ) = civil_from_days ( days );
format! (
" {:04} - {:02} - {:02} {:02} : {:02} : {:02} " ,
year , month , day , rest / 3600 , ( rest % 3600 ) / 60 , rest % 60
)
}
/// 天数 → (年, 月, 日)
fn civil_from_days ( days : i64 ) -> ( i64 , u32 , u32 ) {
let z = days + 719_468 ;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097 ;
let doe = z - era * 146_097 ;
let yoe = ( doe - doe / 1460 + doe / 36_524 - doe / 146_096 ) / 365 ;
let year = yoe + era * 400 ;
let doy = doe - ( 365 * yoe + yoe / 4 - yoe / 100 );
let mp = ( 5 * doy + 2 ) / 153 ;
let day = ( doy - ( 153 * mp + 2 ) / 5 + 1 ) as u32 ;
let month = if mp < 10 { mp + 3 } else { mp - 9 } as u32 ;
( if month <= 2 { year + 1 } else { year }, month , day )
}
2026-10-02 14:53:02 +08:00
/// 规范化并校验账号名
2026-10-01 20:48:38 +08:00
fn account_name ( user : & str ) -> std ::io ::Result < String > {
if user . is_empty () || user . len () > 64 || ! user . bytes (). all ( | c | c . is_ascii_alphanumeric () || b "_.-" . contains ( & c )) {
return Err ( std ::io ::Error ::other ( "invalid account name" ));
}
Ok ( user . to_ascii_lowercase ())
}
2026-10-03 02:17:46 +08:00
/// 校验口令哈希长度,空串表示免密
2026-10-01 20:48:38 +08:00
fn validate_account_hash ( hash : & str ) -> std ::io ::Result < () > {
2026-10-03 02:17:46 +08:00
if hash . len () > 256 {
2026-10-01 20:48:38 +08:00
return Err ( std ::io ::Error ::other ( "invalid account credential" ));
}
Ok (())
}
2026-10-02 14:53:02 +08:00
/// 造一条新账号记录
2026-10-01 20:48:38 +08:00
fn new_account ( id : i64 , user : & str , password_hash : String ) -> std ::io ::Result < Account > {
validate_account_hash ( & password_hash ) ? ;
Ok ( Account {
id ,
user : account_name ( user ) ? ,
password_hash ,
revision : 1 ,
registered_at : now_stamp (),
last_login_at : None ,
2026-10-05 00:15:01 +08:00
can_login : true ,
is_superuser : false ,
system_catalog_manager : false ,
2026-10-05 22:22:23 +08:00
allow_sudo_auth : false ,
2026-10-05 00:15:01 +08:00
enabled : true ,
identity_version : 1 ,
2026-10-01 20:48:38 +08:00
})
}
2026-10-05 00:15:01 +08:00
/// 保留至少一个启用的登录管理员
fn require_superuser ( accounts : & [ Account ]) -> std ::io ::Result < () > {
if accounts . iter (). any ( | a | a . can_login && a . enabled && a . is_superuser ) { Ok (()) }
else { Err ( std ::io ::Error ::other ( "the last enabled login superuser cannot be removed" )) }
}
2026-09-25 17:41:51 +08:00
/// 一张表的索引定义转线上格式
2026-10-02 14:53:02 +08:00
fn index_wires ( schema : & crate ::catalog ::TableSchema ) -> Vec < IndexWire > {
2026-09-25 14:21:55 +08:00
let mut out = vec! [ IndexWire {
column : "id" . to_string (),
}];
for i in & schema . indexes {
if i . column != "id" {
out . push ( IndexWire {
column : i . column . clone (),
});
}
}
out
}
/// 一张表的列统计 -> 线上格式
2026-10-02 14:53:02 +08:00
fn stat_wires ( schema : & crate ::catalog ::TableSchema ) -> Vec < ColumnStatWire > {
2026-09-25 14:21:55 +08:00
schema
. stats
. iter ()
. map ( | ( name , s ) | ColumnStatWire {
name : name . clone (),
distinct : s . distinct ,
capped : s . capped ,
2026-10-03 21:03:17 +08:00
lo : s . lo ,
hi : s . hi ,
hist : s . hist . clone (),
2026-09-25 14:21:55 +08:00
})
. collect ()
}
2026-09-24 23:56:07 +08:00
impl Drop for Server {
2026-10-02 14:53:02 +08:00
/// 析构时保存数据字典
2026-09-24 23:56:07 +08:00
fn drop ( & mut self ) {
if let Ok ( c ) = self . catalog . lock () {
let _ = c . save ( self . catalog_path ());
}
}
}
2026-10-05 00:15:01 +08:00
/// 按列精度规范化一行定点数
fn normalize_decimal_row ( row : & mut Row , columns : & [ SchemaColumn ]) -> std ::io ::Result < () > {
for column in columns {
if let Some (( precision , scale )) = decimal_parameters ( & column . ty ) ?
&& let Some ( value ) = row . get_mut ( & column . name ). filter ( | v | ! v . is_null ()) {
let number = value . as_f64 (). ok_or_else ( || std ::io ::Error ::other ( "invalid decimal value" )) ? ;
let factor = 10 f64 . powi ( scale );
let scaled = number * factor ;
if ! scaled . is_finite () { return Err ( std ::io ::Error ::other ( "decimal value is not finite or exceeds precision" )); }
let rounded = scaled . round_ties_even () / factor ;
if rounded . abs () >= 10 f64 . powi ( precision - scale ) { return Err ( std ::io ::Error ::other ( "decimal value exceeds precision" )); }
* value = serde_json ::json! ( rounded );
}
}
Ok (())
}
/// 解析基础或命名定点类型参数
fn decimal_parameters ( ty : & str ) -> std ::io ::Result < Option < ( i32 , i32 ) >> {
let mut base = ty . trim ();
while let Some ( body ) = base . strip_prefix ( "domain(" ). and_then ( | s | s . strip_suffix ( ')' )) {
base = body . split_once ( ',' ). ok_or_else ( || std ::io ::Error ::other ( "invalid domain type" )) ? . 1. trim ();
}
let normalized = crate ::catalog ::normalize_type ( base );
if normalized == "decimal" { return Ok ( Some (( 10 , 0 ))); }
if ! normalized . starts_with ( "decimal(" ) { return Ok ( None ); }
let error = || std ::io ::Error ::other ( "invalid decimal precision or scale" );
let body = normalized . strip_prefix ( "decimal(" ). and_then ( | s | s . strip_suffix ( ')' )). ok_or_else ( error ) ? ;
let values = body . split ( ',' ). map ( | s | s . trim (). parse ::< i32 > ()). collect ::< Result < Vec < _ > , _ >> (). map_err ( | _ | error ()) ? ;
let ( precision , scale ) = match values . as_slice () { [ p ] => ( * p , 0 ), [ p , s ] => ( * p , * s ), _ => return Err ( error ()) };
if ! ( 1 ..= 308 ). contains ( & precision ) || scale < 0 || scale > precision { return Err ( error ()); }
Ok ( Some (( precision , scale )))
}
2026-10-01 20:48:38 +08:00
/// 系统库名:账号表住在这里,其中的表都是系统表。服务启动时只建它一个,
/// 别的库一律要显式建(没有默认工作库)。
const SYSTEM_DATABASE : & str = "system" ;
2026-10-03 17:16:26 +08:00
/// 老目录里角色与授权表的旧名对照
const LEGACY_TABLE_RENAMES : [( & str , & str ); 3 ] = [
( "sys_roles" , "__system_roles" ),
( "sys_grants" , "__system_grants" ),
( "sys_members" , "__system_members" ),
];
2026-10-01 20:48:38 +08:00
/// 保留库:不能建、不能删。
fn reserved_database ( name : & str ) -> bool {
name == SYSTEM_DATABASE
}
/// 每个库拥有独立的目录、字典、句柄和 WAL。
struct Databases {
root : Config ,
2026-10-03 16:49:23 +08:00
system : Arc < Server > ,
named : Mutex < HashMap < String , Arc < Server >>> ,
2026-10-01 20:48:38 +08:00
}
impl Databases {
2026-10-02 14:53:02 +08:00
/// 装载系统库与已有的具名库
2026-10-01 20:48:38 +08:00
fn new ( cfg : Config ) -> std ::io ::Result < Self > {
// 系统库按需创建:目录、字典与系统表缺失时自动补齐。
// 其余库只在 data/databases/<name>/ 下已存在时才装载,启动不会凭空建库。
2026-10-03 16:49:23 +08:00
let system = Arc ::new ( Self ::open ( & cfg , SYSTEM_DATABASE ) ? );
2026-10-01 20:48:38 +08:00
let root = cfg . data_dir . join ( "databases" );
std ::fs ::create_dir_all ( & root ) ? ;
let canonical_root = std ::fs ::canonicalize ( & root ) ? ;
if canonical_root . parent () != Some ( std ::fs ::canonicalize ( & cfg . data_dir ) ? . as_path ()) {
return Err ( std ::io ::Error ::other ( "database root escapes data directory" ));
}
let mut named = HashMap ::new ();
for entry in std ::fs ::read_dir ( root ) ? {
let entry = entry ? ;
let name = entry . file_name (). to_string_lossy (). to_string ();
if entry . file_type () ? . is_dir () && valid_database ( & name ) && ! reserved_database ( & name )
&& name == name . to_ascii_lowercase () && entry . path (). join ( "catalog.json" ). is_file () {
if std ::fs ::canonicalize ( entry . path ()) ? . parent () != Some ( canonical_root . as_path ()) {
return Err ( std ::io ::Error ::other ( "database directory escapes database root" ));
}
let mut config = cfg . clone ();
config . data_dir = entry . path ();
let server = Server ::new ( config ) ? ;
server . recover_wal () ? ;
server . rebuild_stats () ? ;
server . migrate_data () ? ;
server . compact_tables () ? ;
2026-10-03 16:49:23 +08:00
named . insert ( name , Arc ::new ( server ));
2026-10-01 20:48:38 +08:00
}
}
2026-10-05 22:22:23 +08:00
{
let mut catalog = system . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
let mut ids = HashSet ::new ();
if catalog . database_id != 0 { ids . insert ( catalog . database_id ); }
for server in named . values () {
let id = server . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? . database_id ;
if id != 0 && ! ids . insert ( id ) { return Err ( std ::io ::Error ::other ( "duplicate database object id" )); }
catalog . object_high_water = catalog . object_high_water . max ( id );
}
if catalog . database_id == 0 { catalog . database_id = catalog . reserve_object_id () ? ; }
catalog . save ( system . catalog_path ()) ? ;
}
let databases = Self { root : cfg , system , named : Mutex ::new ( named ) };
{
let named = databases . named . lock (). map_err ( | _ | std ::io ::Error ::other ( "database lock poisoned" )) ? ;
for server in named . values () { databases . assign_database_id ( server ) ? ; }
}
Ok ( databases )
}
/// 为数据库持久化不复用的编号
fn assign_database_id ( & self , server : & Server ) -> std ::io ::Result < () > {
let mut catalog = server . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
if catalog . database_id == 0 {
let mut system = self . system . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? ;
let id = system . reserve_object_id () ? ;
system . save ( self . system . catalog_path ()) ? ;
catalog . database_id = id ;
catalog . save ( server . catalog_path ()) ? ;
}
Ok (())
2026-10-01 20:48:38 +08:00
}
2026-10-02 14:53:02 +08:00
/// 按名字打开或新建一个库
2026-10-01 20:48:38 +08:00
fn open ( cfg : & Config , name : & str ) -> std ::io ::Result < Server > {
let mut config = cfg . clone ();
config . data_dir = cfg . data_dir . join ( name );
let server = Server ::new ( config ) ? ;
if ! server . catalog_path (). is_file () {
server . catalog . lock (). map_err ( | _ | std ::io ::Error ::other ( "catalog lock poisoned" )) ? . save ( server . catalog_path ()) ? ;
}
server . recover_wal () ? ;
server . rebuild_stats () ? ;
server . migrate_data () ? ;
server . compact_tables () ? ;
Ok ( server )
}
2026-10-02 14:53:02 +08:00
/// 库级分发:先处理库管理,再转给选中的库
2026-10-01 20:48:38 +08:00
fn request ( & self , mut value : serde_json ::Value ) -> std ::io ::Result < Response > {
use std ::io ::Error ;
let method = value . get ( "method" ). and_then ( | v | v . as_str ()). unwrap_or ( "" ). to_string ();
2026-10-15 22:21:41 +08:00
if method == "validate_write" && value . get ( "schema_guard" ). is_none () {
return Err ( Error ::other ( "write validation requires a schema guard" ));
2026-10-11 17:32:12 +08:00
}
2026-10-15 22:21:41 +08:00
let guarded_write = if value . get ( "schema_guard" ). is_some () {
match method . as_str () {
"scan" | "scan_shard" | "lookup_by_index" | "range_by_index" | "validate_write" => false ,
"insert" | "insert_batch" | "delete_keys" | "replace_all" | "replace_schema"
| "drop_column" | "drop_table" | "create_index" | "drop_index" => true ,
_ => return Err ( Error ::other ( "schema guard requires a supported table operation" )),
}
} else { false };
2026-10-01 20:48:38 +08:00
let name = match value . get ( "database" ) {
None => String ::new (),
Some ( serde_json ::Value ::String ( s )) => s . to_ascii_lowercase (),
_ => return Err ( Error ::other ( "invalid database name" )),
};
if ! name . is_empty () && ! valid_database ( & name ) { return Err ( Error ::other ( "invalid database name" )); }
let mut named = self . named . lock (). map_err ( | _ | Error ::other ( "database lock poisoned" )) ? ;
match method . as_str () {
2026-10-05 22:22:23 +08:00
"object_acl_read" => {
drop ( named );
let _gate = self . system . gate . read (). map_err ( | _ | Error ::other ( "database gate poisoned" )) ? ;
if self . system . recovery_required . load ( Ordering ::Acquire ) { return Err ( Error ::other ( "recovery required" )); }
return Ok ( Response ::Rows { rows : self . system . object_acl_read () ? });
}
"object_acl_replace" => {
drop ( named );
let _gate = self . system . gate . write (). map_err ( | _ | Error ::other ( "database gate poisoned" )) ? ;
if self . system . recovery_required . load ( Ordering ::Acquire ) { return Err ( Error ::other ( "recovery required" )); }
let expected = match value . get ( "expected" ) {
None | Some ( serde_json ::Value ::Null ) => None ,
Some ( serde_json ::Value ::String ( value )) => Some ( value . as_str ()),
_ => return Err ( Error ::other ( "invalid expected ACL payload" )),
};
let payload = value . get ( "payload" ). and_then ( | value | value . as_str ()). ok_or_else ( || Error ::other ( "missing ACL payload" )) ? ;
self . system . object_acl_replace ( expected , payload ) ? ;
return Ok ( Response ::Ok );
}
"resolve_object" => {
let server = if name == SYSTEM_DATABASE { & self . system }
else { named . get ( & name ). ok_or_else ( || Error ::other ( "unknown database" )) ? };
let _gate = server . gate . read (). map_err ( | _ | Error ::other ( "database gate poisoned" )) ? ;
if server . recovery_required . load ( Ordering ::Acquire ) { return Err ( Error ::other ( "recovery required" )); }
let catalog = server . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? ;
let object_id = match value . get ( "table" ) {
None => None ,
Some ( serde_json ::Value ::String ( table )) if ! blocked_table ( table ) => {
let schema = catalog . describe ( table ). ok_or_else ( || Error ::other ( "unknown table" )) ? ;
if schema . object_id == 0 { return Err ( Error ::other ( "missing catalog object id" )); }
Some ( schema . object_id )
}
_ => return Err ( Error ::other ( "invalid object table" )),
};
return Ok ( Response ::Object { database_id : catalog . database_id , object_id });
}
2026-10-01 20:48:38 +08:00
"all_catalogs" => {
2026-10-03 16:49:23 +08:00
let servers : Vec < ( String , Arc < Server > ) > =
std ::iter ::once (( SYSTEM_DATABASE . to_string (), Arc ::clone ( & self . system )))
. chain ( named . iter (). map ( | ( name , server ) | ( name . clone (), Arc ::clone ( server ))))
. collect ();
drop ( named );
2026-10-01 20:48:38 +08:00
let mut schemas = Vec ::new ();
2026-10-03 16:49:23 +08:00
for ( database , server ) in servers {
2026-10-01 20:48:38 +08:00
match server . handle_request ( Request ::ListCatalog ) {
Response ::Catalog { schemas : entries } => {
for mut entry in entries {
entry . table = format! ( " {database} . {} " , entry . table );
schemas . push ( entry );
}
}
other => return Ok ( other ),
}
}
return Ok ( Response ::Catalog { schemas });
}
"list_databases" => {
let mut databases : Vec < String > = named . keys (). cloned (). collect ();
databases . sort ();
databases . push ( SYSTEM_DATABASE . into ());
return Ok ( Response ::Tables { tables : databases });
}
"create_database" => {
if ! valid_database ( & name ) { return Err ( Error ::other ( "invalid database name" )); }
if reserved_database ( & name ) || named . contains_key ( & name ) { return Err ( Error ::other ( "database already exists" )); }
let mut cfg = self . root . clone ();
cfg . data_dir = cfg . data_dir . join ( "databases" ). join ( & name );
std ::fs ::create_dir ( & cfg . data_dir ) ? ;
let server = Server ::new ( cfg ) ? ;
2026-10-05 22:22:23 +08:00
self . assign_database_id ( & server ) ? ;
2026-10-01 20:48:38 +08:00
server . catalog . lock (). map_err ( | _ | Error ::other ( "catalog lock poisoned" )) ? . save ( server . catalog_path ()) ? ;
2026-10-03 16:49:23 +08:00
named . insert ( name , Arc ::new ( server ));
2026-10-01 20:48:38 +08:00
return Ok ( Response ::Ok );
}
"drop_database" => {
if name == SYSTEM_DATABASE { return Err ( Error ::other ( "cannot drop the system database" )); }
2026-10-03 16:49:23 +08:00
let holder = named . remove ( & name ). ok_or_else ( || Error ::other ( "unknown database" )) ? ;
let server = match Arc ::try_unwrap ( holder ) {
Ok ( server ) => server ,
Err ( holder ) => {
named . insert ( name , holder );
return Err ( Error ::other ( "database is in use" ));
}
};
2026-10-01 20:48:38 +08:00
let root = std ::fs ::canonicalize ( self . root . data_dir . join ( "databases" )) ? ;
let target = std ::fs ::canonicalize ( & server . cfg . data_dir ) ? ;
if target . parent () != Some ( root . as_path ()) || ! target . join ( "catalog.json" ). is_file () {
return Err ( Error ::other ( "invalid database directory" ));
}
let config = server . cfg . clone ();
2026-10-03 16:49:23 +08:00
drop ( server );
2026-10-01 20:48:38 +08:00
let stamp = std ::time ::SystemTime ::now (). duration_since ( std ::time ::UNIX_EPOCH ). map_err ( Error ::other ) ? . as_nanos ();
let tombstone = root . join ( format! ( ".dropped- {name} - {stamp} " ));
if let Err ( error ) = std ::fs ::rename ( & target , & tombstone ) {
2026-10-03 16:49:23 +08:00
named . insert ( name , Arc ::new ( Server ::new ( config ) ? ));
2026-10-01 20:48:38 +08:00
return Err ( error );
}
std ::fs ::remove_dir_all ( & tombstone ). map_err ( | e | Error ::other ( format! ( "database dropped; directory cleanup failed: {e} " ))) ? ;
return Ok ( Response ::Ok );
}
"use_database" => {
return if reserved_database ( & name ) || named . contains_key ( & name ) { Ok ( Response ::Ok ) }
else { Err ( Error ::other ( "unknown database" )) };
}
_ => {}
}
let mut selected = name ;
let qualified = value . get ( "table" ). and_then ( | v | v . as_str ()). map ( str ::to_owned );
if let Some (( database , table )) = qualified . as_deref (). and_then ( | t | t . split_once ( '.' )) {
if ! valid_database ( database ) { return Err ( Error ::other ( "invalid database name" )); }
selected = database . to_ascii_lowercase ();
value [ "table" ] = serde_json ::Value ::String ( table . to_string ());
}
2026-10-11 17:32:12 +08:00
let guard : Option < Vec < crate ::protocol ::BoundSchema >> = value . get ( "schema_guard" )
. map ( | guard | serde_json ::from_value ( guard . clone ()). map_err ( Error ::other )). transpose () ? ;
2026-10-01 20:48:38 +08:00
let request : Request = serde_json ::from_value ( value ). map_err ( Error ::other ) ? ;
2026-10-05 00:15:01 +08:00
let global = matches! ( request , Request ::AccountsList | Request ::CatalogManage { .. } | Request ::AccountCreate { .. }
| Request ::IdentityInitialize { .. } | Request ::RoleCreate { .. } | Request ::IdentityAlter { .. }
2026-10-01 20:48:38 +08:00
| Request ::AccountReset { .. } | Request ::AccountLogin { .. }
2026-10-03 02:17:46 +08:00
| Request ::AccountDrop { .. } | Request ::BootstrapSystem { .. }
2026-10-05 00:15:01 +08:00
| Request ::BootstrapTypes { .. }
2026-10-03 02:17:46 +08:00
| Request ::SystemStatus | Request ::Ping );
2026-10-03 16:49:23 +08:00
if ! global && selected . is_empty () { return Err ( Error ::other ( "no database selected" )); }
let server = if global || selected == SYSTEM_DATABASE { Arc ::clone ( & self . system ) }
else { Arc ::clone ( named . get ( & selected ). ok_or_else ( || Error ::other ( "unknown database" )) ? ) };
2026-10-11 17:32:12 +08:00
if let Some ( bindings ) = guard {
let mut servers = std ::collections ::BTreeMap ::from ([( selected . clone (), Arc ::clone ( & server ))]);
let mut dependencies = Vec ::new ();
for binding in bindings {
let ( database , table ) = binding . table . split_once ( '.' ). unwrap_or (( & selected , & binding . table ));
let database = database . to_ascii_lowercase ();
if ! valid_database ( & database ) { return Err ( Error ::other ( "invalid bound database name" )); }
let dependency = if database == SYSTEM_DATABASE { Arc ::clone ( & self . system ) }
else { Arc ::clone ( named . get ( & database ). ok_or_else ( || Error ::other ( "bound query database changed" )) ? ) };
servers . insert ( database . clone (), dependency );
dependencies . push (( database , table . to_owned (), binding ));
}
if ! dependencies . iter (). any ( | ( database , table , _ ) | database == & selected && Some ( table . as_str ()) == request . table ()) {
2026-10-15 22:21:41 +08:00
return Err ( Error ::other ( "bound table operation has no schema guard" ));
2026-10-11 17:32:12 +08:00
}
drop ( named );
2026-10-15 22:21:41 +08:00
// 全部库按名称取门;写入目标独占,其他依赖共享。
let _gates = servers . iter (). map ( | ( database , server ) | {
if guarded_write && database == & selected {
server . gate . write (). map ( | gate | ( None , Some ( gate )))
. map_err ( | _ | Error ::other ( "storage write gate poisoned" ))
} else {
server . gate . read (). map ( | gate | ( Some ( gate ), None ))
. map_err ( | _ | Error ::other ( "storage read gate poisoned" ))
}
}). collect ::< std ::io ::Result < Vec < _ >>> () ? ;
2026-10-11 17:32:12 +08:00
for ( database , table , binding ) in & dependencies {
servers [ database ]. validate_bound_schema ( table , binding ) ? ;
}
return Ok ( Response ::Guarded { response : Box ::new ( server . dispatch ( request )) });
}
2026-10-03 16:49:23 +08:00
drop ( named );
2026-10-01 20:48:38 +08:00
Ok ( server . handle_request ( request ))
}
}
2026-10-11 17:32:12 +08:00
/// 读取守卫中的精确十进制编号
fn parse_bound_number ( value : & str ) -> std ::io ::Result < u64 > {
if value . is_empty () || value . len () > 20 || ! value . bytes (). all ( | c | c . is_ascii_digit ()) {
return Err ( std ::io ::Error ::other ( "invalid bound catalog number" ));
}
value . parse (). map_err ( std ::io ::Error ::other )
}
2026-10-03 02:17:46 +08:00
/// 只读表页的请求可与其它读并发,其余独占
fn concurrent_read ( req : & Request ) -> bool {
matches! (
req ,
Request ::Scan { .. }
2026-10-15 22:21:41 +08:00
| Request ::ValidateWrite { .. }
2026-10-03 02:17:46 +08:00
| Request ::ScanShard { .. }
| Request ::LookupByIndex { .. }
| Request ::RangeByIndex { .. }
)
}
2026-10-02 14:53:02 +08:00
/// 校验库名是否合法
2026-10-01 20:48:38 +08:00
fn valid_database ( name : & str ) -> bool {
let lower = name . to_ascii_lowercase ();
let device = matches! ( lower . as_str (), "con" | "prn" | "aux" | "nul" )
|| ( lower . len () == 4 && ( lower . starts_with ( "com" ) || lower . starts_with ( "lpt" ))
&& lower . as_bytes ()[ 3 ]. is_ascii_digit ());
! name . is_empty () && name . len () <= 64 && ! device && ! reserved_table ( name )
&& name . bytes (). all ( | c | c . is_ascii_alphanumeric () || c == b '_' )
&& name . as_bytes (). first (). is_some_and ( | c | c . is_ascii_alphabetic () || * c == b '_' )
}
2026-09-24 21:28:49 +08:00
/// 把配置来源打进日志
2026-09-24 15:37:55 +08:00
fn log_config ( loaded : & Loaded ) {
match & loaded . config_path {
Some ( p ) => log_info! ( core , "config file: {}" , p . display ()),
None => log_info! ( core , "config file: none" ),
}
for ( name , value , origin ) in & loaded . origins {
2026-09-24 21:28:49 +08:00
log_info! ( core , " {:<18} {} [{}]" , name , value , origin . describe ());
2026-09-24 15:37:55 +08:00
}
}
2026-10-05 00:15:01 +08:00
/// 获取数据目录的跨进程独占锁
fn lock_data_directory ( cfg : & Config ) -> std ::io ::Result < std ::fs ::File > {
std ::fs ::create_dir_all ( & cfg . data_dir ) ? ;
let file = std ::fs ::OpenOptions ::new (). read ( true ). write ( true ). create ( true ). truncate ( false )
. open ( cfg . data_dir . join ( ".chusql.lock" )) ? ;
file . try_lock (). map_err ( | e | std ::io ::Error ::other ( format! ( "data directory is in use; stop all database processes: {e} " ))) ? ;
Ok ( file )
}
/// 复制系统目录并拒绝链接文件
fn copy_system_directory ( source : & std ::path ::Path , target : & std ::path ::Path ) -> std ::io ::Result < () > {
std ::fs ::create_dir_all ( target ) ? ;
for entry in std ::fs ::read_dir ( source ) ? {
let entry = entry ? ;
let ty = entry . file_type () ? ;
if ty . is_symlink () { return Err ( std ::io ::Error ::other ( "system directory contains a symbolic link" )); }
if ty . is_dir () { copy_system_directory ( & entry . path (), & target . join ( entry . file_name ())) ? ; }
else if ty . is_file () { std ::fs ::copy ( entry . path (), target . join ( entry . file_name ())) ? ; }
else { return Err ( std ::io ::Error ::other ( "unsupported system directory entry" )); }
}
Ok (())
}
/// 构造可重建的系统表结构
fn maintenance_schemas () -> Vec < ( & 'static str , Vec < SchemaColumn > ) > {
let text = | name : & str , size : usize | SchemaColumn { name : name . into (), ty : format ! ( "varchar({size})" ), .. Default ::default () };
vec! [( USERS_TABLE , users_columns ()), ( IDENTITIES_TABLE , identity_columns ()), ( TYPES_TABLE , types_columns ()),
( "__system_roles" , vec! [ text ( "name" , 64 )]),
( "__system_grants" , vec! [ text ( "role" , 64 ), text ( "privilege" , 16 ), text ( "object" , 128 )]),
( "__system_grant_options" , vec! [ text ( "role" , 64 ), text ( "privilege" , 16 ), text ( "object" , 128 )]),
( "__system_members" , vec! [ text ( "role" , 64 ), text ( "member" , 64 )])]
}
/// 验证隔离副本并执行所选恢复策略
fn maintenance_candidate ( cfg : Config , mode : & str , user : & str , hash : & str , types : Vec < Row > ) -> Result < (), String > {
if mode == "reset" {
let seed = Server ::new ( cfg . clone ()). map_err ( | e | e . to_string ()) ? ;
seed . bootstrap_system ( Some ( user ), Some ( hash )). map_err ( | e | e . to_string ()) ? ;
seed . bootstrap_types ( types . clone ()). map_err ( | e | e . to_string ()) ? ;
}
let repair = mode == "repair" ;
let catalog_path = cfg . data_dir . join ( "catalog.json" );
2026-10-05 22:22:23 +08:00
let mut schemas = maintenance_schemas ();
if cfg . data_dir . join ( "__system_object_acl.db" ). is_file () {
schemas . push (( "__system_object_acl" , vec! [ SchemaColumn { name : "id" . into (), ty : "int" . into (), .. Default ::default () },
SchemaColumn { name : "payload" . into (), ty : "str" . into (), .. Default ::default () }]));
}
2026-10-05 00:15:01 +08:00
let mut missing_data = Vec ::new ();
let mut catalog = match Catalog ::load ( & catalog_path ) {
Ok ( catalog ) => catalog ,
Err ( _ ) => {
for entry in std ::fs ::read_dir ( & cfg . data_dir ). map_err ( | e | e . to_string ()) ? {
let entry = entry . map_err ( | e | e . to_string ()) ? ;
if entry . path (). extension (). and_then ( | s | s . to_str ()) == Some ( "db" ) {
let name = entry . path (). file_stem (). map ( | s | s . to_string_lossy (). into_owned ()). unwrap_or_default ();
if ! schemas . iter (). any ( | ( known , _ ) | * known == name ) { return Err ( format! ( "cannot reconstruct unknown system table schema: {name} " )); }
}
}
Catalog ::default ()
}
};
if catalog . all_tables (). is_empty () {
for entry in std ::fs ::read_dir ( & cfg . data_dir ). map_err ( | e | e . to_string ()) ? {
let path = entry . map_err ( | e | e . to_string ()) ? . path ();
if path . extension (). and_then ( | s | s . to_str ()) == Some ( "db" ) {
let name = path . file_stem (). map ( | s | s . to_string_lossy (). into_owned ()). unwrap_or_default ();
if ! schemas . iter (). any ( | ( known , _ ) | * known == name ) { return Err ( format! ( "cannot reconstruct unknown system table schema: {name} " )); }
}
}
}
for ( name , columns ) in & schemas {
let file = cfg . data_dir . join ( format! ( " {name} .db" ));
if let Some ( schema ) = catalog . describe ( name ). filter ( | s | ! file . exists () && s . row_count > 0 ) {
missing_data . push (( * name , schema . row_count ));
}
if repair && ! file . exists () && catalog . describe ( name ). is_none_or ( | s | s . row_count != 0 ) {
return Err ( format! ( "repair would require initializing or recovering data in {name} " ));
}
if catalog . describe ( name ). is_none () { catalog . create_table ( name , columns . clone ()). map_err ( | e | e . to_string ()) ? ; }
else { catalog . set_columns ( name , columns . clone ()). map_err ( | e | e . to_string ()) ? ; }
catalog . mark_system ( name ). map_err ( | e | e . to_string ()) ? ;
}
catalog . save ( & catalog_path ). map_err ( | e | e . to_string ()) ? ;
let server = Server ::new ( cfg ). map_err ( | e | e . to_string ()) ? ;
if repair {
let scan = server . wal . read_all (). map_err ( | e | e . to_string ()) ? ;
let checkpoint = server . wal . read_checkpoint (). map_err ( | e | e . to_string ()) ? . unwrap_or ( 0 );
if ! committed_ops ( & records_after ( & scan . records , checkpoint )). is_empty () { return Err ( "repair would require replaying WAL data" . into ()); }
} else {
server . recover_wal (). map_err ( | e | e . to_string ()) ? ;
}
for ( name , _ ) in & schemas { server . with_table ( name , | t | t . flush ()). map_err ( | e | e . to_string ()) ? ; }
if ! repair {
if mode == "recover" && server . load_accounts (). map_err ( | e | e . to_string ()) ? . is_empty () && hash . is_empty () {
return Err ( "recover requires --password-stdin to initialize a missing administrator" . into ());
}
server . bootstrap_system ( Some ( user ), Some ( hash )). map_err ( | e | e . to_string ()) ? ;
server . accounts_request ( Request ::IdentityInitialize { administrator : user . into () }). map_err ( | e | e . to_string ()) ? ;
server . bootstrap_types ( types . clone ()). map_err ( | e | e . to_string ()) ? ;
}
let accounts = server . load_accounts (). map_err ( | e | e . to_string ()) ? ;
require_superuser ( & accounts ). map_err ( | e | e . to_string ()) ? ;
for ( name , expected ) in missing_data {
let rows = server . with_existing_table ( name , | t | t . scan ()). map_err ( | e | e . to_string ()) ? ;
if ( rows . len () as u64 ) < expected { return Err ( format! ( "WAL cannot reconstruct all missing data in {name} ; restore a backup or use reset" )); }
}
if repair {
let saved = server . with_existing_table ( TYPES_TABLE , | t | t . scan ()). map_err ( | e | e . to_string ()) ? ;
if types . iter (). any ( | wanted | ! saved . iter (). any ( | row | wanted . iter (). all ( | ( key , value ) | row . get ( key ) == Some ( value )))) {
return Err ( "repair would require changing preinstalled type data" . into ());
}
}
for ( name , _ ) in & schemas { server . with_existing_table ( name , | t | t . scan ()). map_err ( | e | e . to_string ()) ? ; }
{
let mut catalog = server . catalog . lock (). map_err ( | _ | "catalog lock poisoned" ) ? ;
catalog . identity_high_water = catalog . identity_high_water . max ( accounts . iter (). map ( | a | a . id ). max (). unwrap_or ( 0 ));
catalog . save ( server . catalog_path ()). map_err ( | e | e . to_string ()) ? ;
}
server . rebuild_stats (). map_err ( | e | e . to_string ()) ? ;
server . flush_all_tables (). map_err ( | e | e . to_string ()) ? ;
Ok (())
}
2026-10-02 14:53:02 +08:00
/// 进程内存储句柄:一个数据目录一份状态,跨线程共享(内部各自加锁)。
pub struct Storage {
databases : Databases ,
2026-10-05 00:15:01 +08:00
_directory_lock : std ::fs ::File ,
2026-10-01 20:48:38 +08:00
}
2026-10-02 14:53:02 +08:00
impl Storage {
2026-10-05 00:15:01 +08:00
/// 离线修复或重建系统目录
pub fn maintenance ( config_path : Option <& str > , request : & str ) -> Result < String , String > {
let loaded = config ::load ( config_path ) ? ;
let cfg = loaded . config ;
let _lock = lock_data_directory ( & cfg ). map_err ( | e | e . to_string ()) ? ;
let input : serde_json ::Value = serde_json ::from_str ( request ). map_err ( | e | e . to_string ()) ? ;
let mode = input . get ( "mode" ). and_then ( | v | v . as_str ()). ok_or ( "missing maintenance mode" ) ? ;
if ! [ "repair" , "recover" , "reset" ]. contains ( & mode ) { return Err ( "invalid maintenance mode" . into ()); }
let user = input . get ( "user" ). and_then ( | v | v . as_str ()). unwrap_or ( "root" );
let hash = input . get ( "password_hash" ). and_then ( | v | v . as_str ()). unwrap_or ( "" );
let types : Vec < Row > = serde_json ::from_value ( input . get ( "types" ). cloned (). ok_or ( "missing type manifest" ) ? ). map_err ( | e | e . to_string ()) ? ;
if types . is_empty () { return Err ( "type manifest must not be empty" . into ()); }
for row in & types { type_definition_name ( row ). map_err ( | e | e . to_string ()) ? ; }
let stamp = std ::time ::SystemTime ::now (). duration_since ( std ::time ::UNIX_EPOCH ). map_err ( | e | e . to_string ()) ? . as_nanos ();
let system = cfg . data_dir . join ( "system" );
if system . is_symlink () { return Err ( "system directory cannot be a symbolic link" . into ()); }
let work = cfg . data_dir . join ( format! ( "bootstrap-work- {stamp} " ));
let backup = cfg . data_dir . join ( format! ( "system-backup- {stamp} " ));
let staged = work . join ( "system" );
std ::fs ::create_dir_all ( & staged ). map_err ( | e | e . to_string ()) ? ;
if mode != "reset" && system . exists () { copy_system_directory ( & system , & staged ). map_err ( | e | e . to_string ()) ? ; }
let mut candidate_cfg = cfg . clone ();
candidate_cfg . data_dir = staged . clone ();
let result = maintenance_candidate ( candidate_cfg , mode , user , hash , types );
if let Err ( err ) = result { return Err ( format! ( " {err} ; original system directory was not changed; candidate: {} . Try csql-bootstrap recover or reset" , work . display ())); }
if mode == "repair" && system . exists () {
for entry in std ::fs ::read_dir ( & system ). map_err ( | e | e . to_string ()) ? {
let entry = entry . map_err ( | e | e . to_string ()) ? ;
if entry . path (). extension (). and_then ( | s | s . to_str ()) == Some ( "db" )
&& std ::fs ::read ( entry . path ()). map_err ( | e | e . to_string ()) ? != std ::fs ::read ( staged . join ( entry . file_name ())). map_err ( | e | e . to_string ()) ? {
return Err ( "repair would modify table data; original system directory was not changed. Use recover or reset" . into ());
}
}
}
if system . exists () { std ::fs ::rename ( & system , & backup ). map_err ( | e | e . to_string ()) ? ; }
if let Err ( err ) = std ::fs ::rename ( & staged , & system ) {
if backup . exists () { std ::fs ::rename ( & backup , & system ). map_err ( | rollback | format! ( "publish failed: {err} ; rollback failed: {rollback} ; backup: {} " , backup . display ())) ? ; }
return Err ( format! ( "cannot publish repaired system directory: {err} " ));
}
serde_json ::to_string ( & serde_json ::json! ({ "status" :"maintenance" , "mode" :mode , "backup" :backup , "candidate" :work ,
"message" :"System maintenance completed. Business databases were preserved." })). map_err ( | e | e . to_string ())
}
2026-10-02 14:53:02 +08:00
/// 打开数据目录并初始化存储
pub fn open ( config_path : Option <& str > ) -> Result < Storage , String > {
let loaded = config ::load ( config_path ) ? ;
2026-10-05 00:15:01 +08:00
let directory_lock = lock_data_directory ( & loaded . config ). map_err ( | e | e . to_string ()) ? ;
2026-10-02 14:53:02 +08:00
log ::init ();
log ::set_level ( loaded . config . log_level );
log_info! ( core , "chusql-core-storage {} starting" , env! ( "CARGO_PKG_VERSION" ));
log_config ( & loaded );
let databases = Databases ::new ( loaded . config ). map_err ( | e | e . to_string ()) ? ;
2026-10-05 00:15:01 +08:00
Ok ( Storage { databases , _directory_lock : directory_lock })
2026-10-02 14:53:02 +08:00
}
/// 一行请求 JSON 换一行响应 JSON
pub fn request_line ( & self , line : & str ) -> String {
let response = match serde_json ::from_str ::< serde_json ::Value > ( line ) {
Ok ( value ) => match self . databases . request ( value ) {
Ok ( response ) => response ,
Err ( e ) => Response ::Error { message : e . to_string () },
},
Err ( e ) => {
log_warn! ( core , "bad request line: {}" , e );
Response ::Error {
message : format ! ( "parse error: {}" , e ),
}
2026-10-01 20:48:38 +08:00
}
2026-10-02 14:53:02 +08:00
};
match serde_json ::to_string ( & response ) {
Ok ( text ) => text ,
Err ( e ) => {
log_error! ( core , "response encode failed: {}" , e );
"{ \" status \" : \" error \" , \" message \" : \" response encode failed \" }" . to_string ()
2026-10-01 20:48:38 +08:00
}
}
}
}