- Core engine with MemTable, SST, WAL - B+Tree indexing for SST files - Leveled compaction strategy - Multi-table database management - Schema validation and secondary indexes - Query builder with complex conditions - Web UI with HTMX for data visualization - Command-line tools for diagnostics
156 lines
3.2 KiB
Go
156 lines
3.2 KiB
Go
package btree
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/edsrzf/mmap-go"
|
|
)
|
|
|
|
func TestBTree(t *testing.T) {
|
|
// 1. 创建测试文件
|
|
file, err := os.Create("test.sst")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove("test.sst")
|
|
|
|
// 2. 构建 B+Tree
|
|
builder := NewBuilder(file, 256) // 从 offset 256 开始
|
|
|
|
// 添加 1000 个 key-value
|
|
for i := int64(1); i <= 1000; i++ {
|
|
dataOffset := 1000000 + i*100 // 模拟数据位置
|
|
dataSize := int32(100)
|
|
err := builder.Add(i, dataOffset, dataSize)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// 构建
|
|
rootOffset, err := builder.Build()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("Root offset: %d", rootOffset)
|
|
|
|
// 3. 关闭并重新打开文件
|
|
file.Close()
|
|
|
|
file, err = os.Open("test.sst")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer file.Close()
|
|
|
|
// 4. mmap 映射
|
|
mmapData, err := mmap.Map(file, mmap.RDONLY, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer mmapData.Unmap()
|
|
|
|
// 5. 查询测试
|
|
reader := NewReader(mmapData, rootOffset)
|
|
|
|
// 测试存在的 key
|
|
for i := int64(1); i <= 1000; i++ {
|
|
offset, size, found := reader.Get(i)
|
|
if !found {
|
|
t.Errorf("Key %d not found", i)
|
|
}
|
|
expectedOffset := 1000000 + i*100
|
|
if offset != expectedOffset {
|
|
t.Errorf("Key %d: expected offset %d, got %d", i, expectedOffset, offset)
|
|
}
|
|
if size != 100 {
|
|
t.Errorf("Key %d: expected size 100, got %d", i, size)
|
|
}
|
|
}
|
|
|
|
// 测试不存在的 key
|
|
_, _, found := reader.Get(1001)
|
|
if found {
|
|
t.Error("Key 1001 should not exist")
|
|
}
|
|
|
|
_, _, found = reader.Get(0)
|
|
if found {
|
|
t.Error("Key 0 should not exist")
|
|
}
|
|
|
|
t.Log("All tests passed!")
|
|
}
|
|
|
|
func TestBTreeSerialization(t *testing.T) {
|
|
// 测试节点序列化
|
|
leaf := NewLeafNode()
|
|
leaf.AddData(1, 1000, 100)
|
|
leaf.AddData(2, 2000, 200)
|
|
leaf.AddData(3, 3000, 300)
|
|
|
|
// 序列化
|
|
data := leaf.Marshal()
|
|
if len(data) != NodeSize {
|
|
t.Errorf("Expected size %d, got %d", NodeSize, len(data))
|
|
}
|
|
|
|
// 反序列化
|
|
leaf2 := Unmarshal(data)
|
|
if leaf2 == nil {
|
|
t.Fatal("Unmarshal failed")
|
|
}
|
|
|
|
// 验证
|
|
if leaf2.NodeType != NodeTypeLeaf {
|
|
t.Error("Wrong node type")
|
|
}
|
|
if leaf2.KeyCount != 3 {
|
|
t.Errorf("Expected 3 keys, got %d", leaf2.KeyCount)
|
|
}
|
|
if len(leaf2.Keys) != 3 {
|
|
t.Errorf("Expected 3 keys, got %d", len(leaf2.Keys))
|
|
}
|
|
if leaf2.Keys[0] != 1 || leaf2.Keys[1] != 2 || leaf2.Keys[2] != 3 {
|
|
t.Error("Keys mismatch")
|
|
}
|
|
if leaf2.DataOffsets[0] != 1000 || leaf2.DataOffsets[1] != 2000 || leaf2.DataOffsets[2] != 3000 {
|
|
t.Error("Data offsets mismatch")
|
|
}
|
|
if leaf2.DataSizes[0] != 100 || leaf2.DataSizes[1] != 200 || leaf2.DataSizes[2] != 300 {
|
|
t.Error("Data sizes mismatch")
|
|
}
|
|
|
|
t.Log("Serialization test passed!")
|
|
}
|
|
|
|
func BenchmarkBTreeGet(b *testing.B) {
|
|
// 构建测试数据
|
|
file, _ := os.Create("bench.sst")
|
|
defer os.Remove("bench.sst")
|
|
|
|
builder := NewBuilder(file, 256)
|
|
for i := int64(1); i <= 100000; i++ {
|
|
builder.Add(i, i*100, 100)
|
|
}
|
|
rootOffset, _ := builder.Build()
|
|
file.Close()
|
|
|
|
// mmap
|
|
file, _ = os.Open("bench.sst")
|
|
defer file.Close()
|
|
mmapData, _ := mmap.Map(file, mmap.RDONLY, 0)
|
|
defer mmapData.Unmap()
|
|
|
|
reader := NewReader(mmapData, rootOffset)
|
|
|
|
// 性能测试
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
key := int64(i%100000 + 1)
|
|
reader.Get(key)
|
|
}
|
|
}
|