- 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
268 lines
7.9 KiB
Go
268 lines
7.9 KiB
Go
package srdb
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// UserSchema 用户表 Schema
|
|
var UserSchema = NewSchema("users", []Field{
|
|
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
|
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
|
{Name: "email", Type: FieldTypeString, Indexed: true, Comment: "邮箱"},
|
|
{Name: "description", Type: FieldTypeString, Indexed: false, Comment: "描述"},
|
|
})
|
|
|
|
// LogSchema 日志表 Schema
|
|
var LogSchema = NewSchema("logs", []Field{
|
|
{Name: "level", Type: FieldTypeString, Indexed: true, Comment: "日志级别"},
|
|
{Name: "message", Type: FieldTypeString, Indexed: false, Comment: "日志消息"},
|
|
{Name: "source", Type: FieldTypeString, Indexed: true, Comment: "来源"},
|
|
{Name: "error_code", Type: FieldTypeInt64, Indexed: true, Comment: "错误码"},
|
|
})
|
|
|
|
// OrderSchema 订单表 Schema
|
|
var OrderSchema = NewSchema("orders", []Field{
|
|
{Name: "order_id", Type: FieldTypeString, Indexed: true, Comment: "订单ID"},
|
|
{Name: "user_id", Type: FieldTypeInt64, Indexed: true, Comment: "用户ID"},
|
|
{Name: "amount", Type: FieldTypeFloat, Indexed: true, Comment: "金额"},
|
|
{Name: "status", Type: FieldTypeString, Indexed: true, Comment: "状态"},
|
|
{Name: "paid", Type: FieldTypeBool, Indexed: true, Comment: "是否支付"},
|
|
})
|
|
|
|
func TestSchema(t *testing.T) {
|
|
// 创建 Schema
|
|
schema := NewSchema("test", []Field{
|
|
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
|
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
|
{Name: "score", Type: FieldTypeFloat, Indexed: false, Comment: "分数"},
|
|
})
|
|
|
|
// 测试数据
|
|
data := map[string]any{
|
|
"name": "Alice",
|
|
"age": 25,
|
|
"score": 95.5,
|
|
}
|
|
|
|
// 验证
|
|
err := schema.Validate(data)
|
|
if err != nil {
|
|
t.Errorf("Validation failed: %v", err)
|
|
}
|
|
|
|
// 获取索引字段
|
|
indexedFields := schema.GetIndexedFields()
|
|
if len(indexedFields) != 2 {
|
|
t.Errorf("Expected 2 indexed fields, got %d", len(indexedFields))
|
|
}
|
|
|
|
t.Log("Schema test passed!")
|
|
}
|
|
|
|
func TestSchemaValidation(t *testing.T) {
|
|
schema := NewSchema("test", []Field{
|
|
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
|
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
|
})
|
|
|
|
// 正确的数据
|
|
validData := map[string]any{
|
|
"name": "Bob",
|
|
"age": 30,
|
|
}
|
|
|
|
err := schema.Validate(validData)
|
|
if err != nil {
|
|
t.Errorf("Valid data failed validation: %v", err)
|
|
}
|
|
|
|
// 错误的数据类型
|
|
invalidData := map[string]any{
|
|
"name": "Charlie",
|
|
"age": "thirty", // 应该是 int64
|
|
}
|
|
|
|
err = schema.Validate(invalidData)
|
|
if err == nil {
|
|
t.Error("Invalid data should fail validation")
|
|
}
|
|
|
|
t.Log("Schema validation test passed!")
|
|
}
|
|
|
|
func TestExtractIndexValue(t *testing.T) {
|
|
schema := NewSchema("test", []Field{
|
|
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
|
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
|
})
|
|
|
|
data := map[string]any{
|
|
"name": "David",
|
|
"age": float64(35), // JSON 解析后是 float64
|
|
}
|
|
|
|
// 提取 name
|
|
name, err := schema.ExtractIndexValue("name", data)
|
|
if err != nil {
|
|
t.Errorf("Failed to extract name: %v", err)
|
|
}
|
|
if name != "David" {
|
|
t.Errorf("Expected 'David', got %v", name)
|
|
}
|
|
|
|
// 提取 age (float64 → int64)
|
|
age, err := schema.ExtractIndexValue("age", data)
|
|
if err != nil {
|
|
t.Errorf("Failed to extract age: %v", err)
|
|
}
|
|
if age != int64(35) {
|
|
t.Errorf("Expected 35, got %v", age)
|
|
}
|
|
|
|
t.Log("Extract index value test passed!")
|
|
}
|
|
|
|
func TestPredefinedSchemas(t *testing.T) {
|
|
// 测试 UserSchema
|
|
userData := map[string]any{
|
|
"name": "Alice",
|
|
"age": 25,
|
|
"email": "alice@example.com",
|
|
"description": "Test user",
|
|
}
|
|
|
|
err := UserSchema.Validate(userData)
|
|
if err != nil {
|
|
t.Errorf("UserSchema validation failed: %v", err)
|
|
}
|
|
|
|
// 测试 LogSchema
|
|
logData := map[string]any{
|
|
"level": "ERROR",
|
|
"message": "Something went wrong",
|
|
"source": "api",
|
|
"error_code": 500,
|
|
}
|
|
|
|
err = LogSchema.Validate(logData)
|
|
if err != nil {
|
|
t.Errorf("LogSchema validation failed: %v", err)
|
|
}
|
|
|
|
t.Log("Predefined schemas test passed!")
|
|
}
|
|
|
|
// TestChecksumDeterminism 测试 checksum 的确定性
|
|
func TestChecksumDeterminism(t *testing.T) {
|
|
// 创建相同的 Schema 多次
|
|
for i := 0; i < 10; i++ {
|
|
s1 := NewSchema("users", []Field{
|
|
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
|
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
|
})
|
|
|
|
s2 := NewSchema("users", []Field{
|
|
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
|
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
|
})
|
|
|
|
checksum1, err := s1.ComputeChecksum()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
checksum2, err := s2.ComputeChecksum()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if checksum1 != checksum2 {
|
|
t.Errorf("Iteration %d: checksums should be equal, got %s and %s", i, checksum1, checksum2)
|
|
}
|
|
}
|
|
|
|
t.Log("✅ Checksum is deterministic")
|
|
}
|
|
|
|
// TestChecksumFieldOrderIndependent 测试字段顺序不影响 checksum
|
|
func TestChecksumFieldOrderIndependent(t *testing.T) {
|
|
s1 := NewSchema("users", []Field{
|
|
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
|
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
|
})
|
|
|
|
s2 := NewSchema("users", []Field{
|
|
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
|
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
|
})
|
|
|
|
checksum1, _ := s1.ComputeChecksum()
|
|
checksum2, _ := s2.ComputeChecksum()
|
|
|
|
if checksum1 != checksum2 {
|
|
t.Errorf("Checksums should be equal regardless of field order, got %s and %s", checksum1, checksum2)
|
|
} else {
|
|
t.Logf("✅ Field order does not affect checksum (expected behavior)")
|
|
t.Logf(" checksum: %s", checksum1)
|
|
}
|
|
}
|
|
|
|
// TestChecksumDifferentData 测试不同 Schema 的 checksum 应该不同
|
|
func TestChecksumDifferentData(t *testing.T) {
|
|
s1 := NewSchema("users", []Field{
|
|
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
|
})
|
|
|
|
s2 := NewSchema("users", []Field{
|
|
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, // Indexed 不同
|
|
})
|
|
|
|
checksum1, _ := s1.ComputeChecksum()
|
|
checksum2, _ := s2.ComputeChecksum()
|
|
|
|
if checksum1 == checksum2 {
|
|
t.Error("Different schemas should have different checksums")
|
|
} else {
|
|
t.Log("✅ Different schemas have different checksums")
|
|
}
|
|
}
|
|
|
|
// TestChecksumMultipleFieldOrders 测试多个字段的各种排列组合都产生相同 checksum
|
|
func TestChecksumMultipleFieldOrders(t *testing.T) {
|
|
// 定义 4 个字段
|
|
fieldA := Field{Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "ID"}
|
|
fieldB := Field{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "名称"}
|
|
fieldC := Field{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}
|
|
fieldD := Field{Name: "email", Type: FieldTypeString, Indexed: true, Comment: "邮箱"}
|
|
|
|
// 创建不同顺序的 Schema
|
|
schemas := []*Schema{
|
|
NewSchema("test", []Field{fieldA, fieldB, fieldC, fieldD}), // 原始顺序
|
|
NewSchema("test", []Field{fieldD, fieldC, fieldB, fieldA}), // 完全反转
|
|
NewSchema("test", []Field{fieldB, fieldD, fieldA, fieldC}), // 随机顺序 1
|
|
NewSchema("test", []Field{fieldC, fieldA, fieldD, fieldB}), // 随机顺序 2
|
|
NewSchema("test", []Field{fieldD, fieldA, fieldC, fieldB}), // 随机顺序 3
|
|
}
|
|
|
|
// 计算所有 checksum
|
|
checksums := make([]string, len(schemas))
|
|
for i, s := range schemas {
|
|
checksum, err := s.ComputeChecksum()
|
|
if err != nil {
|
|
t.Fatalf("Failed to compute checksum for schema %d: %v", i, err)
|
|
}
|
|
checksums[i] = checksum
|
|
}
|
|
|
|
// 验证所有 checksum 都相同
|
|
expectedChecksum := checksums[0]
|
|
for i := 1; i < len(checksums); i++ {
|
|
if checksums[i] != expectedChecksum {
|
|
t.Errorf("Schema %d has different checksum: expected %s, got %s", i, expectedChecksum, checksums[i])
|
|
}
|
|
}
|
|
|
|
t.Logf("✅ All %d field permutations produce the same checksum", len(schemas))
|
|
t.Logf(" checksum: %s", expectedChecksum)
|
|
}
|