检查点按 LSN 部分截断

This commit is contained in:
LIm1tZz7 committed 2026-10-03 17:23:16 +08:00
1 parent 29529444a1
commit 484b35a8bc
6 files changed
+216 -26

No files matched your search

+37
View File
@@ -1394,6 +1394,43 @@ fn already_applied_create_table_is_skipped_on_restart() -> Result<(), Box<dyn st
Ok(())
}
/// 检查点跟着每组提交走,LSN 跨重启不回头
#[test]
fn checkpoint_advances_and_lsn_survives_restart() -> Result<(), Box<dyn std::error::Error>> {
let checkpoint = |data: &tempfile::TempDir| -> Result<u64, Box<dyn std::error::Error>> {
let bytes = std::fs::read(data.path().join("databases/main/wal.checkpoint"))?;
Ok(u64::from_le_bytes(bytes[..8].try_into()?))
};
let (server, data) = start_server();
let mut c = connect()?;
request_ok(&mut c, serde_json::json!({"method":"create_table","table":"m6_seq","columns":[{"name":"id","ty":"int"}]}))?;
request_ok(&mut c, serde_json::json!({"method":"insert","table":"m6_seq","row":{"id":1}}))?;
drop(c);
let first = checkpoint(&data)?;
assert!(first > 0, "提交后检查点要前进: {first}");
drop(server);
// 重启后接着写,新的 LSN 必须落在检查点之后
let _server = start_server_in(data.path());
let mut c = connect()?;
request_ok(&mut c, serde_json::json!({"method":"insert","table":"m6_seq","row":{"id":2}}))?;
drop(c);
let second = checkpoint(&data)?;
assert!(second > first, "重启后 LSN 要接着检查点走: {second} vs {first}");
// 数据都在,日志本身是空的:每组提交后就按边界截掉了
let mut c = connect()?;
let rows: serde_json::Value =
serde_json::from_str(&send(&mut c, r#"{"method":"scan","table":"m6_seq"}"#))?;
assert_eq!(rows["rows"].as_array().map(Vec::len), Some(2), "{rows}");
assert_eq!(
std::fs::metadata(data.path().join("databases/main/wal.log"))?.len(),
0
);
Ok(())
}
/// 给第二列建索引后能按那一列查
#[test]
fn create_index_on_secondary_column() {
+79 -1
View File
@@ -1,5 +1,5 @@
use chusql_core_storage::protocol::Row;
use chusql_core_storage::wal::{committed_ops, Wal, WalOp};
use chusql_core_storage::wal::{committed_ops, records_after, Wal, WalOp};
use serde_json::json;
// WAL 测试:追加、读取、清空、截断尾巴、LSN 与提交标记。
@@ -218,3 +218,81 @@ fn checkpoint_round_trips() {
assert_eq!(wal.read_checkpoint().unwrap(), Some(7));
}
/// 按 LSN 截断只丢边界以内的帧
#[test]
fn truncate_before_keeps_newer_records() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("wal.log");
let wal = Wal::new(&path);
let second_op = WalOp::Insert {
table: "t".into(),
row: row(&[("id", json!(2))]),
};
let first = wal
.append_group(&[WalOp::Insert {
table: "t".into(),
row: row(&[("id", json!(1))]),
}])
.unwrap();
let second = wal.append_group(std::slice::from_ref(&second_op)).unwrap();
assert!(second > first);
// 提交号是第一组的最后一条;边界以内的操作与提交标记都该丢掉
wal.truncate_before(first).unwrap();
let kept = wal.read_all().unwrap();
assert!(kept.records.iter().all(|(lsn, _)| *lsn > first));
let ops: Vec<&WalOp> = kept
.records
.iter()
.map(|(_, op)| op)
.filter(|op| **op != WalOp::Commit)
.collect();
assert_eq!(ops, vec![&second_op], "只剩第二组的操作");
// 边界划到最后一条之后,日志就空了
wal.truncate_before(second).unwrap();
assert_eq!(std::fs::metadata(&path).unwrap().len(), 0);
assert!(wal.read_all().unwrap().records.is_empty());
}
/// 日志被截断后 LSN 也不回头
#[test]
fn lsn_continues_from_the_checkpoint() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("wal.log");
let op = WalOp::Insert {
table: "t".into(),
row: row(&[("id", json!(1))]),
};
let first = {
let wal = Wal::new(&path);
let lsn = wal.append_group(std::slice::from_ref(&op)).unwrap();
wal.write_checkpoint(lsn).unwrap();
wal.truncate_before(lsn).unwrap();
lsn
};
// 重新打开:号从检查点续
let wal = Wal::new(&path);
let next = wal.append_group(std::slice::from_ref(&op)).unwrap();
assert!(next > first, "重启后 LSN 要接着检查点走: {next} vs {first}");
}
/// 过滤只留下检查点之后的记录
#[test]
fn records_after_keeps_only_the_tail() {
let records = vec![
(1u64, WalOp::Insert { table: "t".into(), row: row(&[("id", json!(1))]) }),
(2, WalOp::Commit),
(3, WalOp::Insert { table: "t".into(), row: row(&[("id", json!(2))]) }),
(4, WalOp::Commit),
];
let kept = records_after(&records, 2);
let lsn: Vec<u64> = kept.iter().map(|(lsn, _)| *lsn).collect();
assert_eq!(lsn, vec![3, 4]);
assert_eq!(records_after(&records, 4).len(), 0);
assert_eq!(records_after(&records, 0).len(), 4);
}