系统表统一命名与迁移
This commit is contained in:
1 parent
4c030e6181
commit
29529444a1
7 files changed
+178
-15
No files matched your search
@@ -202,6 +202,23 @@ impl Catalog {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 表改名到指定名字
|
||||
pub fn rename_table(&mut self, from: &str, to: &str) -> io::Result<bool> {
|
||||
let Some(entry) = self.tables.remove(from) else {
|
||||
return Ok(false);
|
||||
};
|
||||
if self.tables.contains_key(to) {
|
||||
self.tables.insert(from.to_string(), entry);
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::AlreadyExists,
|
||||
format!("table already exists: {}", to),
|
||||
));
|
||||
}
|
||||
self.seen.remove(from);
|
||||
self.tables.insert(to.to_string(), entry);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// ALTER:整列定义换成新的,并清掉二级索引与统计
|
||||
pub fn set_columns(&mut self, table: &str, columns: Vec<SchemaColumn>) -> io::Result<()> {
|
||||
let entry = self
|
||||
|
||||
@@ -229,6 +229,19 @@ pub fn reserved_table(table: &str) -> bool {
|
||||
table.to_ascii_lowercase().starts_with("__system_")
|
||||
}
|
||||
|
||||
/// 服务自己的角色与授权表名
|
||||
pub const PRIVILEGE_TABLES: [&str; 3] = ["__system_roles", "__system_grants", "__system_members"];
|
||||
|
||||
/// 是不是服务自己经请求通道读写的系统表
|
||||
pub fn internal_table(table: &str) -> bool {
|
||||
PRIVILEGE_TABLES.contains(&table.to_ascii_lowercase().as_str())
|
||||
}
|
||||
|
||||
/// 是不是请求通道要拒绝的系统表
|
||||
pub fn blocked_table(table: &str) -> bool {
|
||||
reserved_table(table) && !internal_table(table)
|
||||
}
|
||||
|
||||
/// 没写 column 时按 `id` 算
|
||||
fn default_id_column() -> String {
|
||||
"id".to_string()
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::config::{self, Config, Loaded};
|
||||
use crate::heap::{scalar_int, HeapTable};
|
||||
use crate::log;
|
||||
use crate::protocol::{
|
||||
reserved_table, Account, ColumnStatWire, IndexWire, Request, Response, Row, SchemaColumn, StorageOp,
|
||||
TableSchemaWire, USERS_TABLE,
|
||||
blocked_table, reserved_table, Account, ColumnStatWire, IndexWire, Request, Response, Row,
|
||||
SchemaColumn, StorageOp, TableSchemaWire, USERS_TABLE,
|
||||
};
|
||||
use crate::wal::{committed_ops, Wal, WalOp};
|
||||
use crate::{log_debug, log_error, log_info, log_warn};
|
||||
@@ -386,7 +386,7 @@ impl Server {
|
||||
| StorageOp::Delete { table, .. }
|
||||
| StorageOp::Replace { table, .. } => table,
|
||||
};
|
||||
if reserved_table(table) {
|
||||
if blocked_table(table) {
|
||||
return Err(std::io::Error::other("reserved system table"));
|
||||
}
|
||||
if table.is_empty() || !table.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
|
||||
@@ -604,6 +604,7 @@ impl Server {
|
||||
|
||||
/// 老数据迁移:类型名规范、缺列补 null
|
||||
fn migrate_data(&self) -> std::io::Result<()> {
|
||||
self.migrate_table_names()?;
|
||||
let names: Vec<String> = self
|
||||
.catalog
|
||||
.lock()
|
||||
@@ -649,6 +650,51 @@ impl Server {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 老目录里的角色与授权表改到内部前缀
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// 建索引前的检查:内置列、未知表列、重复索引
|
||||
fn check_create_index(&self, table: &str, column: &str) -> std::io::Result<()> {
|
||||
if column == "id" {
|
||||
@@ -963,7 +1009,7 @@ impl Server {
|
||||
return Response::Error { message: "invalid column name".into() };
|
||||
}
|
||||
if let Some(table) = req.table() {
|
||||
if reserved_table(table) {
|
||||
if blocked_table(table) {
|
||||
return Response::Error { message: "reserved system table".into() };
|
||||
}
|
||||
if table.is_empty() || !table.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
|
||||
@@ -1192,7 +1238,7 @@ impl Server {
|
||||
let schemas: Vec<TableSchemaWire> = c
|
||||
.all_tables()
|
||||
.into_iter()
|
||||
.filter(|(name, _)| !reserved_table(name))
|
||||
.filter(|(name, _)| !blocked_table(name))
|
||||
.map(|(name, ts)| TableSchemaWire {
|
||||
table: name.to_string(),
|
||||
columns: ts.columns.clone(),
|
||||
@@ -1364,10 +1410,10 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
/// 只列出数据字典中的业务表
|
||||
/// 列出业务表与授权服务用的三张系统表
|
||||
fn list_tables(&self) -> std::io::Result<Vec<String>> {
|
||||
let c = self.catalog.lock().map_err(|_| std::io::Error::other("catalog lock poisoned"))?;
|
||||
Ok(c.table_names().into_iter().filter(|t| !reserved_table(t)).collect())
|
||||
Ok(c.table_names().into_iter().filter(|t| !blocked_table(t)).collect())
|
||||
}
|
||||
|
||||
/// 查一张表的 schema
|
||||
@@ -1567,6 +1613,13 @@ impl Drop for Server {
|
||||
/// 别的库一律要显式建(没有默认工作库)。
|
||||
const SYSTEM_DATABASE: &str = "system";
|
||||
|
||||
/// 老目录里角色与授权表的旧名对照
|
||||
const LEGACY_TABLE_RENAMES: [(&str, &str); 3] = [
|
||||
("sys_roles", "__system_roles"),
|
||||
("sys_grants", "__system_grants"),
|
||||
("sys_members", "__system_members"),
|
||||
];
|
||||
|
||||
/// 保留库:不能建、不能删。
|
||||
fn reserved_database(name: &str) -> bool {
|
||||
name == SYSTEM_DATABASE
|
||||
|
||||
@@ -1686,6 +1686,59 @@ fn legacy_catalog_and_partial_rows_are_migrated() -> Result<(), Box<dyn std::err
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 老系统库的角色表启动时改到内部前缀
|
||||
#[test]
|
||||
fn legacy_privilege_tables_are_renamed_on_start() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (server, data) = start_server();
|
||||
let mut c = connect()?;
|
||||
request_ok(&mut c, serde_json::json!({"method":"create_table","database":"system","table":"sys_roles","columns":[{"name":"id","ty":"int"},{"name":"name","ty":"str"}]}))?;
|
||||
request_ok(&mut c, serde_json::json!({"method":"insert","database":"system","table":"sys_roles","row":{"id":1,"name":"admin"}}))?;
|
||||
drop(c);
|
||||
drop(server);
|
||||
|
||||
let system_dir = data.path().join("system");
|
||||
assert!(system_dir.join("sys_roles.db").exists(), "老数据文件要先生成");
|
||||
|
||||
let _restarted = start_server_in(data.path());
|
||||
let mut c = connect()?;
|
||||
|
||||
let described: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"describe_table","database":"system","table":"__system_roles"}"#))?;
|
||||
assert_eq!(described["status"], "schema", "{described}");
|
||||
let rows: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"scan","database":"system","table":"__system_roles"}"#))?;
|
||||
assert_eq!(rows["rows"], serde_json::json!([{"id":1,"name":"admin"}]), "行要跟着改名走");
|
||||
let old: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"describe_table","database":"system","table":"sys_roles"}"#))?;
|
||||
assert_eq!(old["status"], "error", "旧名不该还在:{old}");
|
||||
assert!(system_dir.join("__system_roles.db").exists(), "数据文件要改名");
|
||||
assert!(!system_dir.join("sys_roles.db").exists(), "老数据文件要改掉");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 角色与授权表能被请求通道读到,账号表不能
|
||||
#[test]
|
||||
fn privilege_tables_are_reachable_but_account_table_is_not() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (_server, _data) = start_server();
|
||||
let mut c = connect()?;
|
||||
request_ok(&mut c, serde_json::json!({"method":"create_table","database":"system","table":"__system_roles","columns":[{"name":"id","ty":"int"},{"name":"name","ty":"str"}]}))?;
|
||||
let scanned: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"scan","database":"system","table":"__system_roles"}"#))?;
|
||||
assert_eq!(scanned["status"], "rows", "{scanned}");
|
||||
let denied: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"scan","database":"system","table":"__system_users"}"#))?;
|
||||
assert_eq!(denied["status"], "error", "{denied}");
|
||||
assert!(
|
||||
denied["message"].as_str().unwrap_or("").contains("reserved system table"),
|
||||
"{denied}"
|
||||
);
|
||||
let listed: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"list_tables","database":"system"}"#))?;
|
||||
let tables: Vec<&str> = listed["tables"]
|
||||
.as_array()
|
||||
.ok_or("missing tables")?
|
||||
.iter()
|
||||
.map(|one| one.as_str().unwrap_or(""))
|
||||
.collect();
|
||||
assert!(tables.contains(&"__system_roles"), "授权表要在字典里: {listed}");
|
||||
assert!(!tables.contains(&"__system_users"), "账号表不能露面: {listed}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 账号表落盘类型与索引,登录盖时间戳
|
||||
#[test]
|
||||
fn account_table_uses_typed_columns_and_stamps_login() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
Reference in new issue
Block a user