准备可扩展类型系统工作,更合理的JOIN查询优化算法,并行查询

This commit is contained in:
LIm1tZz7 committed 2026-10-03 11:56:13 +08:00
1 parent 9758439b82
commit 274b7fcdcc
75 files changed
+3828 -1789

No files matched your search

+232 -62
View File
@@ -1,12 +1,11 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Mutex, RwLock};
use crate::catalog::Catalog;
use crate::config::{self, Config, Loaded};
use crate::heap::HeapTable;
use crate::initsql::{self, InitColumn, InitTable};
use crate::heap::{scalar_int, HeapTable};
use crate::log;
use crate::protocol::{
reserved_table, Account, ColumnStatWire, IndexWire, Request, Response, Row, SchemaColumn, TableSchemaWire, USERS_TABLE,
@@ -21,6 +20,7 @@ struct Server {
cfg: Config,
tables: Mutex<HashMap<String, Arc<Mutex<HeapTable>>>>,
write_lock: Mutex<()>,
gate: RwLock<()>,
catalog: Mutex<Catalog>,
wal: Wal,
recovery_required: AtomicBool,
@@ -38,6 +38,7 @@ impl Server {
cfg,
tables: Mutex::new(HashMap::new()),
write_lock: Mutex::new(()),
gate: RwLock::new(()),
catalog: Mutex::new(catalog),
wal,
recovery_required: AtomicBool::new(false),
@@ -127,6 +128,56 @@ impl Server {
f(&mut guard)
}
/// 请求带列名时逐个查字典
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)
}
/// 表不存在就报错
fn with_existing_table<R>(
&self,
@@ -528,6 +579,88 @@ impl Server {
Ok(())
}
/// 账号表是否已登记为系统表
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"))?;
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)
}
}
}
}
/// 系统目录里账号表已登记
fn system_initialized(&self) -> bool {
self.catalog
.lock()
.map(|c| c.describe(USERS_TABLE).is_some_and(|schema| schema.system))
.unwrap_or(false)
}
/// 读账号表;表没登记或数据文件缺失都算错
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"));
}
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) {
let id = accounts.iter().map(|a| a.id).max().unwrap_or(0)
.checked_add(1).ok_or_else(|| Error::other("account id exhausted"))?;
accounts.push(new_account(id, &name, password_hash.unwrap_or_default().to_string())?);
changed = true;
}
}
if !changed {
return Ok(accounts);
}
for row in account_rows(&accounts)? {
HeapTable::encode_row(&row, self.cfg.page_size)?;
}
let op = WalOp::Accounts { accounts: accounts.clone() };
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)
}
/// 账号读写共享串行持久化边界
fn accounts_request(&self, request: Request) -> std::io::Result<Vec<Account>> {
use std::io::{Error, ErrorKind};
@@ -535,31 +668,10 @@ impl Server {
if self.recovery_required.load(Ordering::Acquire) {
return Err(Error::other("storage recovery required"));
}
let initialized = {
let c = self.catalog.lock().map_err(|_| Error::other("catalog lock poisoned"))?;
match c.describe(USERS_TABLE) {
Some(schema) if schema.system => true,
Some(_) => return Err(Error::new(ErrorKind::AlreadyExists, "reserved account table collision")),
None => false,
}
};
if !initialized && (self.table_path(USERS_TABLE).exists() || self.index_path(USERS_TABLE, "id").exists()) {
return Err(Error::new(ErrorKind::AlreadyExists, "reserved account file collision"));
if !self.account_table_ready()? {
return Err(Error::other("system catalog is not initialized; run csql-bootstrap"));
}
let mut accounts: Vec<Account> = if initialized {
if !self.table_path(USERS_TABLE).exists() {
return Err(Error::other("missing account data"));
}
let mut loaded: Vec<Account> = self
.with_existing_table(USERS_TABLE, |t| t.scan())?
.iter()
.map(decode_account_row)
.collect::<std::io::Result<_>>()?;
for account in loaded.iter_mut() {
normalize_account(account);
}
loaded
} else { Vec::new() };
let mut accounts = self.load_accounts()?;
match request {
Request::AccountsList => return Ok(accounts),
Request::AccountCreate { user, password_hash } => {
@@ -609,8 +721,27 @@ impl Server {
Ok(accounts)
}
/// 一条请求翻成一条响应
/// 一条请求翻成一条响应:先过读/写门再分发
fn handle_request(&self, req: Request) -> Response {
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(),
},
}
}
}
/// 持门后分发一条请求
fn dispatch(&self, req: Request) -> Response {
let _ddl_guard = if matches!(req, Request::CreateTable { .. } | Request::DropTable { .. }
| Request::CreateIndex { .. } | Request::DropIndex { .. }) {
match self.write_lock.lock() {
@@ -641,6 +772,13 @@ impl Server {
Err(e) => Response::Error { message: e.to_string() },
}
}
Request::BootstrapSystem { user, password_hash } => {
match self.bootstrap_system(user.as_deref(), password_hash.as_deref()) {
Ok(_) => Response::System { initialized: true },
Err(e) => Response::Error { message: e.to_string() },
}
}
Request::SystemStatus => Response::System { initialized: self.system_initialized() },
Request::Ping => {
log_debug!(request, "ping");
Response::Pong
@@ -650,15 +788,7 @@ impl Server {
log_debug!(request, "scan table={}", table);
let result = (|| {
if let Some(cols) = &columns {
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}")));
}
}
}
self.check_columns(&table, columns.as_deref())?;
self.with_existing_table(&table, |t| t.scan_columns(columns.as_deref()))
})();
@@ -673,6 +803,30 @@ impl Server {
}
}
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),
}
}
}
}
Request::Insert { table, row } => {
log_debug!(request, "insert table={}", table);
let tname = table.clone();
@@ -733,7 +887,7 @@ impl Server {
if !indexed {
return Response::NoIndex;
}
let found = match key.as_i64() {
let found = match scalar_int(&key) {
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)),
@@ -1025,18 +1179,31 @@ fn account_rows(accounts: &[Account]) -> std::io::Result<Vec<Row>> {
]))).collect()
}
/// 按 init.sql 把系统表写进字典
/// 系统库的账号表结构:引导模块按这份定义建表
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: "user".into(), ty: "varchar(64)".into(), nullable: false,
unique: true, ..Default::default() },
SchemaColumn { name: "password_hash".into(), ty: "varchar(256)".into(),
nullable: false, ..Default::default() },
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() },
]
}
/// 把系统表写进字典
fn declare_system_schema(catalog: &mut Catalog, rows: &[Row]) -> std::io::Result<()> {
for table in system_schema() {
let stats: &[Row] = if table.name == USERS_TABLE { rows } else { &[] };
catalog.rebuild_stats(&table.name, stats);
catalog.set_columns(&table.name, table.columns.iter().map(InitColumn::to_schema).collect())?;
catalog.rebuild_stats(&table.name, stats);
catalog.mark_system(&table.name)?;
for index in &table.indexes {
catalog.add_index(&table.name, index)?;
}
}
catalog.rebuild_stats(USERS_TABLE, rows);
catalog.set_columns(USERS_TABLE, users_columns())?;
catalog.rebuild_stats(USERS_TABLE, rows);
catalog.mark_system(USERS_TABLE)?;
catalog.add_index(USERS_TABLE, "user")?;
Ok(())
}
@@ -1110,9 +1277,9 @@ fn account_name(user: &str) -> std::io::Result<String> {
Ok(user.to_ascii_lowercase())
}
/// 校验口令哈希长度
/// 校验口令哈希长度,空串表示免密
fn validate_account_hash(hash: &str) -> std::io::Result<()> {
if hash.is_empty() || hash.len() > 256 {
if hash.len() > 256 {
return Err(std::io::Error::other("invalid account credential"));
}
Ok(())
@@ -1172,15 +1339,6 @@ impl Drop for Server {
/// 别的库一律要显式建(没有默认工作库)。
const SYSTEM_DATABASE: &str = "system";
/// 系统库的表结构来自这份脚本(编译期嵌进来)
const SYSTEM_SCHEMA_SQL: &str = include_str!("../../../scripts/init.sql");
/// 系统库 schema:脚本缺表或多表都按它来
fn system_schema() -> &'static [InitTable] {
static TABLES: std::sync::OnceLock<Vec<InitTable>> = std::sync::OnceLock::new();
TABLES.get_or_init(|| initsql::parse(SYSTEM_SCHEMA_SQL).unwrap_or_else(|e| panic!("scripts/init.sql: {e}")))
}
/// 保留库:不能建、不能删。
fn reserved_database(name: &str) -> bool {
name == SYSTEM_DATABASE
@@ -1322,7 +1480,8 @@ impl Databases {
let request: Request = serde_json::from_value(value).map_err(Error::other)?;
let global = matches!(request, Request::AccountsList | Request::AccountCreate { .. }
| Request::AccountReset { .. } | Request::AccountLogin { .. }
| Request::AccountDrop { .. } | Request::Ping);
| Request::AccountDrop { .. } | Request::BootstrapSystem { .. }
| Request::SystemStatus | Request::Ping);
let server = if global { &self.system }
else if selected.is_empty() { return Err(Error::other("no database selected")); }
else if selected == SYSTEM_DATABASE { &self.system }
@@ -1331,6 +1490,17 @@ impl Databases {
}
}
/// 只读表页的请求可与其它读并发,其余独占
fn concurrent_read(req: &Request) -> bool {
matches!(
req,
Request::Scan { .. }
| Request::ScanShard { .. }
| Request::LookupByIndex { .. }
| Request::RangeByIndex { .. }
)
}
/// 校验库名是否合法
fn valid_database(name: &str) -> bool {
let lower = name.to_ascii_lowercase();