主要改动: - 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 测试
245 lines
4.5 KiB
Go
245 lines
4.5 KiB
Go
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)
|
||
}
|
||
}
|