2026-09-24 15:37:55 +08:00
|
|
|
use chusql_storage::catalog::Catalog;
|
|
|
|
|
use chusql_storage::protocol::{ColumnType, Row};
|
|
|
|
|
use serde_json::json;
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
// 数据字典测试:补列、去重、未知表。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-24 15:37:55 +08:00
|
|
|
fn row(pairs: &[(&str, serde_json::Value)]) -> Row {
|
|
|
|
|
let mut m = Row::new();
|
|
|
|
|
for (k, v) in pairs {
|
|
|
|
|
m.insert((*k).to_string(), v.clone());
|
|
|
|
|
}
|
|
|
|
|
m
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 空文件得到空字典
|
2026-09-24 15:37:55 +08:00
|
|
|
#[test]
|
|
|
|
|
fn empty_file_gives_empty_catalog() {
|
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
|
|
|
let path = dir.path().join("catalog.json");
|
|
|
|
|
let c = Catalog::load(&path).unwrap();
|
|
|
|
|
assert_eq!(c.table_names().len(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 补列之后能查到 schema
|
2026-09-24 15:37:55 +08:00
|
|
|
#[test]
|
|
|
|
|
fn ensure_then_describe() {
|
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
|
|
|
let path = dir.path().join("catalog.json");
|
|
|
|
|
|
|
|
|
|
let mut c = Catalog::load(&path).unwrap();
|
|
|
|
|
c.ensure_columns("users", &row(&[("id", json!(1)), ("name", json!("Alice"))]));
|
|
|
|
|
c.save(&path).unwrap();
|
|
|
|
|
|
|
|
|
|
let c2 = Catalog::load(&path).unwrap();
|
|
|
|
|
let s = c2.describe("users").unwrap();
|
|
|
|
|
assert_eq!(s.columns.len(), 2);
|
|
|
|
|
let names: Vec<&str> = s.columns.iter().map(|c| c.name.as_str()).collect();
|
|
|
|
|
assert!(names.contains(&"id"));
|
|
|
|
|
assert!(names.contains(&"name"));
|
|
|
|
|
let id_col = s.columns.iter().find(|c| c.name == "id").unwrap();
|
|
|
|
|
assert_eq!(id_col.ty, ColumnType::Int);
|
|
|
|
|
let name_col = s.columns.iter().find(|c| c.name == "name").unwrap();
|
|
|
|
|
assert_eq!(name_col.ty, ColumnType::Str);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 再插入不会重复列
|
2026-09-24 15:37:55 +08:00
|
|
|
#[test]
|
|
|
|
|
fn second_insert_does_not_duplicate_columns() {
|
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
|
|
|
let path = dir.path().join("catalog.json");
|
|
|
|
|
|
|
|
|
|
let mut c = Catalog::load(&path).unwrap();
|
|
|
|
|
c.ensure_columns("users", &row(&[("id", json!(1))]));
|
|
|
|
|
c.ensure_columns("users", &row(&[("id", json!(2))]));
|
|
|
|
|
assert_eq!(c.describe("users").unwrap().columns.len(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 未知表返回 None
|
2026-09-24 15:37:55 +08:00
|
|
|
#[test]
|
|
|
|
|
fn describe_unknown_returns_none() {
|
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
|
|
|
let path = dir.path().join("catalog.json");
|
|
|
|
|
let c = Catalog::load(&path).unwrap();
|
|
|
|
|
assert!(c.describe("nope").is_none());
|
|
|
|
|
}
|
2026-09-25 17:41:51 +08:00
|
|
|
|
|
|
|
|
/// 删列同时清掉统计与索引
|
|
|
|
|
#[test]
|
|
|
|
|
fn remove_column_drops_definition_stats_and_index() {
|
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
|
|
|
let path = dir.path().join("catalog.json");
|
|
|
|
|
|
|
|
|
|
let mut c = Catalog::load(&path).unwrap();
|
|
|
|
|
c.ensure_columns("t", &row(&[("id", json!(1)), ("code", json!(5))]));
|
|
|
|
|
c.add_index("t", "code").unwrap();
|
|
|
|
|
c.rebuild_stats("t", &[row(&[("id", json!(1)), ("code", json!(5))])]);
|
|
|
|
|
|
|
|
|
|
let removed = c.remove_column("t", "code").unwrap();
|
|
|
|
|
assert!(removed, "first remove should hit the column");
|
|
|
|
|
let s = c.describe("t").unwrap();
|
|
|
|
|
assert_eq!(s.columns.len(), 1);
|
|
|
|
|
assert_eq!(s.columns[0].name, "id");
|
|
|
|
|
assert!(s.indexes.is_empty(), "index on code should be gone");
|
|
|
|
|
assert!(s.stats.get("code").is_none(), "stats for code should be gone");
|
|
|
|
|
|
|
|
|
|
let again = c.remove_column("t", "code").unwrap();
|
|
|
|
|
assert!(!again);
|
|
|
|
|
|
|
|
|
|
assert!(c.remove_column("nope", "code").is_err());
|
|
|
|
|
}
|