添加 Clean 和 Destroy 功能

主要改动:
- Engine: 添加 Clean() 和 Destroy() 方法
- Table: 添加 Clean() 和 Destroy() 方法(不持有 Database 引用)
- Database: 添加 Clean()、CleanTable()、DestroyTable()、Destroy() 方法
- 自动 flush: 添加长时间无写入自动 flush 策略(默认 30 秒)
- WebUI 优化: 优化分页查询性能

新增功能:
- Clean(): 清除数据但保留结构,Engine/Table/Database 仍可用
- Destroy(): 销毁并删除所有文件,对象不可用
- CleanTable(name): 清除指定表的数据
- DestroyTable(name): 销毁指定表并从 Database 中删除
- 自动 flush 监控: 后台定期检查,空闲时自动持久化

代码优化:
- Engine.Close(): 支持 Destroy 后调用,不会 panic
- 二级索引持久化: 在 flush 时自动持久化索引
- WebUI 分页: 预构建字段类型 map,减少 Schema 查询
- 职责分离: Table 不再持有 Database 引用

测试覆盖:
- engine_clean_test.go: Engine Clean/Destroy 测试
- table_clean_test.go: Table Clean/Destroy 测试
- database_clean_test.go: Database Clean/Destroy 测试
- database_table_ops_test.go: Database CleanTable/DestroyTable 测试
This commit is contained in:
2025-10-09 01:05:44 +08:00
parent 23843493b8
commit a4582b9e70
8 changed files with 1413 additions and 123 deletions

244
engine_clean_test.go Normal file
View File

@@ -0,0 +1,244 @@
package srdb
import (
"os"
"testing"
"time"
)
func TestEngineClean(t *testing.T) {
dir := "./test_clean_data"
defer os.RemoveAll(dir)
// 1. 创建 Engine 并插入数据
engine, err := OpenEngine(&EngineOptions{
Dir: dir,
})
if err != nil {
t.Fatal(err)
}
// 插入一些数据
for i := 0; i < 100; i++ {
err := engine.Insert(map[string]any{
"id": i,
"name": "test",
})
if err != nil {
t.Fatal(err)
}
}
// 强制 flush
engine.Flush()
time.Sleep(500 * time.Millisecond)
// 验证数据存在
stats := engine.Stats()
t.Logf("Before Clean: MemTable=%d, SST=%d, Total=%d",
stats.MemTableCount, stats.SSTCount, stats.TotalRows)
if stats.TotalRows == 0 {
t.Errorf("Expected some rows, got 0")
}
// 2. 清除数据
err = engine.Clean()
if err != nil {
t.Fatal(err)
}
// 3. 验证数据已清除
stats = engine.Stats()
t.Logf("After Clean: MemTable=%d, SST=%d, Total=%d",
stats.MemTableCount, stats.SSTCount, stats.TotalRows)
if stats.TotalRows != 0 {
t.Errorf("Expected 0 rows after clean, got %d", stats.TotalRows)
}
// 4. 验证 Engine 仍然可用
err = engine.Insert(map[string]any{
"id": 1,
"name": "after_clean",
})
if err != nil {
t.Fatal(err)
}
stats = engine.Stats()
if stats.TotalRows != 1 {
t.Errorf("Expected 1 row after insert, got %d", stats.TotalRows)
}
engine.Close()
}
func TestEngineDestroy(t *testing.T) {
dir := "./test_destroy_data"
defer os.RemoveAll(dir)
// 1. 创建 Engine 并插入数据
engine, err := OpenEngine(&EngineOptions{
Dir: dir,
})
if err != nil {
t.Fatal(err)
}
// 插入一些数据
for i := 0; i < 50; i++ {
err := engine.Insert(map[string]any{
"id": i,
"name": "test",
})
if err != nil {
t.Fatal(err)
}
}
// 验证数据存在
stats := engine.Stats()
t.Logf("Before Destroy: MemTable=%d, SST=%d, Total=%d",
stats.MemTableCount, stats.SSTCount, stats.TotalRows)
// 2. 销毁 Engine
err = engine.Destroy()
if err != nil {
t.Fatal(err)
}
// 3. 验证数据目录已删除
if _, err := os.Stat(dir); !os.IsNotExist(err) {
t.Errorf("Data directory should be deleted")
}
// 4. 验证 Engine 不可用(尝试插入会失败)
err = engine.Insert(map[string]any{
"id": 1,
"name": "after_destroy",
})
if err == nil {
t.Errorf("Insert should fail after destroy")
}
}
func TestEngineCleanWithSchema(t *testing.T) {
dir := "./test_clean_schema_data"
defer os.RemoveAll(dir)
// 定义 Schema
schema := NewSchema("test", []Field{
{Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "ID"},
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "Name"},
})
// 1. 创建 Engine 并插入数据
engine, err := OpenEngine(&EngineOptions{
Dir: dir,
Schema: schema,
})
if err != nil {
t.Fatal(err)
}
// 创建索引
err = engine.CreateIndex("id")
if err != nil {
t.Fatal(err)
}
// 插入数据
for i := 0; i < 50; i++ {
err := engine.Insert(map[string]any{
"id": int64(i),
"name": "test",
})
if err != nil {
t.Fatal(err)
}
}
// 验证索引存在
indexes := engine.ListIndexes()
if len(indexes) != 1 {
t.Errorf("Expected 1 index, got %d", len(indexes))
}
// 2. 清除数据
err = engine.Clean()
if err != nil {
t.Fatal(err)
}
// 3. 验证数据已清除但 Schema 和索引结构保留
stats := engine.Stats()
if stats.TotalRows != 0 {
t.Errorf("Expected 0 rows after clean, got %d", stats.TotalRows)
}
// 验证可以继续插入Schema 仍然有效)
err = engine.Insert(map[string]any{
"id": int64(100),
"name": "after_clean",
})
if err != nil {
t.Fatal(err)
}
engine.Close()
}
func TestEngineCleanAndReopen(t *testing.T) {
dir := "./test_clean_reopen_data"
defer os.RemoveAll(dir)
// 1. 创建 Engine 并插入数据
engine, err := OpenEngine(&EngineOptions{
Dir: dir,
})
if err != nil {
t.Fatal(err)
}
for i := 0; i < 100; i++ {
engine.Insert(map[string]any{
"id": i,
"name": "test",
})
}
// 2. 清除数据
engine.Clean()
// 3. 关闭并重新打开
engine.Close()
engine2, err := OpenEngine(&EngineOptions{
Dir: dir,
})
if err != nil {
t.Fatal(err)
}
defer engine2.Close()
// 4. 验证数据为空
stats := engine2.Stats()
if stats.TotalRows != 0 {
t.Errorf("Expected 0 rows after reopen, got %d", stats.TotalRows)
}
// 5. 验证可以插入新数据
err = engine2.Insert(map[string]any{
"id": 1,
"name": "new_data",
})
if err != nil {
t.Fatal(err)
}
stats = engine2.Stats()
if stats.TotalRows != 1 {
t.Errorf("Expected 1 row, got %d", stats.TotalRows)
}
}