From 6d0448778934d55bc0d5fc3eadc25f4543e2b1fe Mon Sep 17 00:00:00 2001 From: bourdon Date: Thu, 9 Oct 2025 21:47:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=EF=BC=9A=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=92=8C=E4=BC=98=E5=8C=96=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化 schema 测试用例,增加边界条件测试 - 完善 table、index、database 等模块的测试 - 改进测试数据清理和错误处理 - 更新示例程序以使用最新 API - 增强测试覆盖率和可靠性 --- compaction_test.go | 20 +- database_test.go | 70 ++++- examples/batch_insert/main.go | 32 +- examples/snake_case_demo/main.go | 5 +- examples/struct_schema/main.go | 5 +- examples/webui/commands/webui.go | 16 +- index_btree_test.go | 28 +- index_test.go | 41 ++- query_lazy_test.go | 25 +- schema.go | 33 ++- schema_test.go | 484 ++++++++++++++++++++++++++++--- sstable_test.go | 10 +- table.go | 5 +- table_test.go | 170 ++++++++--- 14 files changed, 812 insertions(+), 132 deletions(-) diff --git a/compaction_test.go b/compaction_test.go index 42b35c5..372b1ba 100644 --- a/compaction_test.go +++ b/compaction_test.go @@ -20,9 +20,12 @@ func TestCompactionBasic(t *testing.T) { } // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "value", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } // 创建 VersionSet versionSet, err := NewVersionSet(manifestDir) @@ -222,9 +225,12 @@ func TestCompactionMerge(t *testing.T) { } // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "value", Type: FieldTypeString}, }) + if err != nil { + t.Fatal(err) + } // 创建 VersionSet versionSet, err := NewVersionSet(manifestDir) @@ -343,9 +349,12 @@ func BenchmarkCompaction(b *testing.B) { } // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "value", Type: FieldTypeString}, }) + if err != nil { + b.Fatal(err) + } // 创建 VersionSet versionSet, err := NewVersionSet(manifestDir) @@ -429,12 +438,15 @@ func TestCompactionQueryOrder(t *testing.T) { tmpDir := t.TempDir() // 创建 Schema - 包含多个字段以增加数据大小 - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64}, {Name: "name", Type: FieldTypeString}, {Name: "data", Type: FieldTypeString}, {Name: "timestamp", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } // 打开 Table (使用较小的 MemTable 触发频繁 flush) table, err := OpenTable(&TableOptions{ diff --git a/database_test.go b/database_test.go index 3506315..92d1141 100644 --- a/database_test.go +++ b/database_test.go @@ -40,10 +40,13 @@ func TestCreateTable(t *testing.T) { defer db.Close() // 创建 Schema - userSchema := NewSchema("users", []Field{ + userSchema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } // 创建表 usersTable, err := db.CreateTable("users", userSchema) @@ -76,15 +79,21 @@ func TestMultipleTables(t *testing.T) { defer db.Close() // 创建多个表 - userSchema := NewSchema("users", []Field{ + userSchema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } - orderSchema := NewSchema("orders", []Field{ + orderSchema, err := NewSchema("orders", []Field{ {Name: "order_id", Type: FieldTypeString, Indexed: true, Comment: "订单ID"}, {Name: "amount", Type: FieldTypeInt64, Indexed: true, Comment: "金额"}, }) + if err != nil { + t.Fatal(err) + } _, err = db.CreateTable("users", userSchema) if err != nil { @@ -117,10 +126,13 @@ func TestTableOperations(t *testing.T) { defer db.Close() // 创建表 - userSchema := NewSchema("users", []Field{ + userSchema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } usersTable, err := db.CreateTable("users", userSchema) if err != nil { @@ -179,9 +191,12 @@ func TestDropTable(t *testing.T) { defer db.Close() // 创建表 - userSchema := NewSchema("users", []Field{ + userSchema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, }) + if err != nil { + t.Fatal(err) + } _, err = db.CreateTable("users", userSchema) if err != nil { @@ -214,10 +229,13 @@ func TestDatabaseRecover(t *testing.T) { t.Fatalf("Open failed: %v", err) } - userSchema := NewSchema("users", []Field{ + userSchema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } usersTable, err := db1.CreateTable("users", userSchema) if err != nil { @@ -272,10 +290,13 @@ func TestDatabaseClean(t *testing.T) { // 2. 创建多个表并插入数据 // 表 1: users - usersSchema := NewSchema("users", []Field{ + usersSchema, err := NewSchema("users", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "User ID"}, {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "Name"}, }) + if err != nil { + t.Fatal(err) + } usersTable, err := db.CreateTable("users", usersSchema) if err != nil { t.Fatal(err) @@ -289,10 +310,13 @@ func TestDatabaseClean(t *testing.T) { } // 表 2: orders - ordersSchema := NewSchema("orders", []Field{ + ordersSchema, err := NewSchema("orders", []Field{ {Name: "order_id", Type: FieldTypeInt64, Indexed: true, Comment: "Order ID"}, {Name: "amount", Type: FieldTypeInt64, Indexed: false, Comment: "Amount"}, }) + if err != nil { + t.Fatal(err) + } ordersTable, err := db.CreateTable("orders", ordersSchema) if err != nil { t.Fatal(err) @@ -367,9 +391,12 @@ func TestDatabaseDestroy(t *testing.T) { t.Fatal(err) } - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"}, }) + if err != nil { + t.Fatal(err) + } table, err := db.CreateTable("test", schema) if err != nil { t.Fatal(err) @@ -420,10 +447,13 @@ func TestDatabaseCleanMultipleTables(t *testing.T) { // 创建 5 个表 for i := range 5 { tableName := fmt.Sprintf("table%d", i) - schema := NewSchema(tableName, []Field{ + schema, err := NewSchema(tableName, []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"}, {Name: "value", Type: FieldTypeString, Indexed: false, Comment: "Value"}, }) + if err != nil { + t.Fatal(err) + } table, err := db.CreateTable(tableName, schema) if err != nil { @@ -493,9 +523,12 @@ func TestDatabaseCleanAndReopen(t *testing.T) { t.Fatal(err) } - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"}, }) + if err != nil { + t.Fatal(err) + } table, err := db.CreateTable("test", schema) if err != nil { t.Fatal(err) @@ -560,10 +593,13 @@ func TestDatabaseCleanTable(t *testing.T) { } defer db.Close() - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"}, {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "Name"}, }) + if err != nil { + t.Fatal(err) + } table, err := db.CreateTable("users", schema) if err != nil { @@ -629,9 +665,12 @@ func TestDatabaseDestroyTable(t *testing.T) { } defer db.Close() - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"}, }) + if err != nil { + t.Fatal(err) + } table, err := db.CreateTable("test", schema) if err != nil { @@ -681,9 +720,12 @@ func TestDatabaseDestroyTableMultiple(t *testing.T) { } defer db.Close() - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"}, }) + if err != nil { + t.Fatal(err) + } // 创建 3 个表 for i := 1; i <= 3; i++ { diff --git a/examples/batch_insert/main.go b/examples/batch_insert/main.go index 4af9dfd..b6b8b51 100644 --- a/examples/batch_insert/main.go +++ b/examples/batch_insert/main.go @@ -26,7 +26,7 @@ type Product struct { } func main() { - fmt.Println("=== SRDB 批量插入示例 ===\n") + fmt.Println("=== SRDB 批量插入示例 ===") // 清理旧数据 os.RemoveAll("./data") @@ -55,10 +55,13 @@ func main() { func example1() { fmt.Println("=== 示例 1: 插入单个 map ===") - schema := srdb.NewSchema("users", []srdb.Field{ + schema, err := srdb.NewSchema("users", []srdb.Field{ {Name: "name", Type: srdb.FieldTypeString, Comment: "用户名"}, {Name: "age", Type: srdb.FieldTypeInt64, Comment: "年龄"}, }) + if err != nil { + log.Fatal(err) + } table, err := srdb.OpenTable(&srdb.TableOptions{ Dir: "./data/example1", @@ -89,11 +92,14 @@ func example1() { func example2() { fmt.Println("=== 示例 2: 批量插入 map 切片 ===") - schema := srdb.NewSchema("users", []srdb.Field{ + schema, err := srdb.NewSchema("users", []srdb.Field{ {Name: "name", Type: srdb.FieldTypeString}, {Name: "age", Type: srdb.FieldTypeInt64}, {Name: "email", Type: srdb.FieldTypeString, Indexed: true}, }) + if err != nil { + log.Fatal(err) + } table, err := srdb.OpenTable(&srdb.TableOptions{ Dir: "./data/example2", @@ -134,12 +140,15 @@ func example2() { func example3() { fmt.Println("=== 示例 3: 插入单个结构体 ===") - schema := srdb.NewSchema("users", []srdb.Field{ + schema, err := srdb.NewSchema("users", []srdb.Field{ {Name: "name", Type: srdb.FieldTypeString}, {Name: "age", Type: srdb.FieldTypeInt64}, {Name: "email", Type: srdb.FieldTypeString}, {Name: "is_active", Type: srdb.FieldTypeBool}, }) + if err != nil { + log.Fatal(err) + } table, err := srdb.OpenTable(&srdb.TableOptions{ Dir: "./data/example3", @@ -175,12 +184,15 @@ func example3() { func example4() { fmt.Println("=== 示例 4: 批量插入结构体切片 ===") - schema := srdb.NewSchema("users", []srdb.Field{ + schema, err := srdb.NewSchema("users", []srdb.Field{ {Name: "name", Type: srdb.FieldTypeString}, {Name: "age", Type: srdb.FieldTypeInt64}, {Name: "email", Type: srdb.FieldTypeString}, {Name: "is_active", Type: srdb.FieldTypeBool}, }) + if err != nil { + log.Fatal(err) + } table, err := srdb.OpenTable(&srdb.TableOptions{ Dir: "./data/example4", @@ -222,12 +234,15 @@ func example4() { func example5() { fmt.Println("=== 示例 5: 批量插入结构体指针切片 ===") - schema := srdb.NewSchema("users", []srdb.Field{ + schema, err := srdb.NewSchema("users", []srdb.Field{ {Name: "name", Type: srdb.FieldTypeString}, {Name: "age", Type: srdb.FieldTypeInt64}, {Name: "email", Type: srdb.FieldTypeString}, {Name: "is_active", Type: srdb.FieldTypeBool}, }) + if err != nil { + log.Fatal(err) + } table, err := srdb.OpenTable(&srdb.TableOptions{ Dir: "./data/example5", @@ -262,12 +277,15 @@ func example5() { func example6() { fmt.Println("=== 示例 6: 使用 snake_case 自动转换 ===") - schema := srdb.NewSchema("products", []srdb.Field{ + schema, err := srdb.NewSchema("products", []srdb.Field{ {Name: "product_id", Type: srdb.FieldTypeString, Comment: "产品ID"}, {Name: "product_name", Type: srdb.FieldTypeString, Comment: "产品名称"}, {Name: "price", Type: srdb.FieldTypeFloat, Comment: "价格"}, {Name: "in_stock", Type: srdb.FieldTypeBool, Comment: "是否有货"}, }) + if err != nil { + log.Fatal(err) + } table, err := srdb.OpenTable(&srdb.TableOptions{ Dir: "./data/example6", diff --git a/examples/snake_case_demo/main.go b/examples/snake_case_demo/main.go index 8313f01..3fb68e2 100644 --- a/examples/snake_case_demo/main.go +++ b/examples/snake_case_demo/main.go @@ -93,6 +93,9 @@ func main() { } // 创建 Schema - schema := srdb.NewSchema("demo", fields) + schema, err := srdb.NewSchema("demo", fields) + if err != nil { + log.Fatal(err) + } fmt.Printf("\n✅ 成功创建 Schema,包含 %d 个字段\n", len(schema.Fields)) } diff --git a/examples/struct_schema/main.go b/examples/struct_schema/main.go index e9290fa..d22a74e 100644 --- a/examples/struct_schema/main.go +++ b/examples/struct_schema/main.go @@ -38,7 +38,10 @@ func main() { } // 创建 Schema - schema := srdb.NewSchema("users", fields) + schema, err := srdb.NewSchema("users", fields) + if err != nil { + log.Fatal(err) + } // 打印 Schema 信息 fmt.Printf("Schema 名称: %s\n", schema.Name) diff --git a/examples/webui/commands/webui.go b/examples/webui/commands/webui.go index 8b0dcff..c6ec267 100644 --- a/examples/webui/commands/webui.go +++ b/examples/webui/commands/webui.go @@ -23,19 +23,25 @@ func StartWebUI(dbPath string, addr string) { defer db.Close() // 创建示例 Schema - userSchema := srdb.NewSchema("users", []srdb.Field{ + userSchema, err := srdb.NewSchema("users", []srdb.Field{ {Name: "name", Type: srdb.FieldTypeString, Indexed: true, Comment: "User name"}, {Name: "email", Type: srdb.FieldTypeString, Indexed: false, Comment: "Email address"}, {Name: "age", Type: srdb.FieldTypeInt64, Indexed: false, Comment: "Age"}, {Name: "city", Type: srdb.FieldTypeString, Indexed: false, Comment: "City"}, }) + if err != nil { + log.Fatal(err) + } - productSchema := srdb.NewSchema("products", []srdb.Field{ + productSchema, err := srdb.NewSchema("products", []srdb.Field{ {Name: "product_name", Type: srdb.FieldTypeString, Indexed: true, Comment: "Product name"}, {Name: "price", Type: srdb.FieldTypeFloat, Indexed: false, Comment: "Price"}, {Name: "quantity", Type: srdb.FieldTypeInt64, Indexed: false, Comment: "Quantity"}, {Name: "category", Type: srdb.FieldTypeString, Indexed: false, Comment: "Category"}, }) + if err != nil { + log.Fatal(err) + } // 创建表(如果不存在) tables := db.ListTables() @@ -133,14 +139,16 @@ func autoInsertData(db *srdb.Database) { hasLogs := slices.Contains(tables, "logs") if !hasLogs { - logsSchema := srdb.NewSchema("logs", []srdb.Field{ + logsSchema, err := srdb.NewSchema("logs", []srdb.Field{ {Name: "group", Type: srdb.FieldTypeString, Indexed: true, Comment: "Log group (A-E)"}, {Name: "timestamp", Type: srdb.FieldTypeString, Indexed: false, Comment: "Timestamp"}, {Name: "data", Type: srdb.FieldTypeString, Indexed: false, Comment: "Random data"}, {Name: "size_bytes", Type: srdb.FieldTypeInt64, Indexed: false, Comment: "Data size in bytes"}, }) + if err != nil { + log.Fatal(err) + } - var err error logsTable, err = db.CreateTable("logs", logsSchema) if err != nil { log.Printf("Failed to create logs table: %v", err) diff --git a/index_btree_test.go b/index_btree_test.go index 9cc65d9..0435b62 100644 --- a/index_btree_test.go +++ b/index_btree_test.go @@ -12,18 +12,21 @@ func TestIndexBTreeBasic(t *testing.T) { tmpDir := t.TempDir() // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64}, {Name: "name", Type: FieldTypeString}, {Name: "city", Type: FieldTypeString}, }) + if err != nil { + t.Fatal(err) + } // 创建索引管理器 mgr := NewIndexManager(tmpDir, schema) defer mgr.Close() // 创建索引 - err := mgr.CreateIndex("city") + err = mgr.CreateIndex("city") if err != nil { t.Fatalf("Failed to create index: %v", err) } @@ -126,17 +129,20 @@ func TestIndexBTreeLargeDataset(t *testing.T) { tmpDir := t.TempDir() // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64}, {Name: "category", Type: FieldTypeString}, }) + if err != nil { + t.Fatal(err) + } // 创建索引管理器 mgr := NewIndexManager(tmpDir, schema) defer mgr.Close() // 创建索引 - err := mgr.CreateIndex("category") + err = mgr.CreateIndex("category") if err != nil { t.Fatalf("Failed to create index: %v", err) } @@ -211,16 +217,19 @@ func TestIndexBTreeBackwardCompatibility(t *testing.T) { tmpDir := t.TempDir() // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64}, {Name: "status", Type: FieldTypeString}, }) + if err != nil { + t.Fatal(err) + } // 1. 创建索引管理器并用旧方式(通过先禁用新格式)创建索引 mgr := NewIndexManager(tmpDir, schema) // 创建索引 - err := mgr.CreateIndex("status") + err = mgr.CreateIndex("status") if err != nil { t.Fatalf("Failed to create index: %v", err) } @@ -286,17 +295,20 @@ func TestIndexBTreeIncrementalUpdate(t *testing.T) { tmpDir := t.TempDir() // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64}, {Name: "tag", Type: FieldTypeString}, }) + if err != nil { + t.Fatal(err) + } // 创建索引管理器 mgr := NewIndexManager(tmpDir, schema) defer mgr.Close() // 创建索引 - err := mgr.CreateIndex("tag") + err = mgr.CreateIndex("tag") if err != nil { t.Fatalf("Failed to create index: %v", err) } diff --git a/index_test.go b/index_test.go index 7257ac8..dbbe771 100644 --- a/index_test.go +++ b/index_test.go @@ -11,9 +11,12 @@ func TestIndexVersionControl(t *testing.T) { os.MkdirAll(dir, 0755) defer os.RemoveAll(dir) - testSchema := NewSchema("test", []Field{ + testSchema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"}, }) + if err != nil { + t.Fatal(err) + } // 1. 创建索引管理器 mgr := NewIndexManager(dir, testSchema) @@ -72,9 +75,12 @@ func TestIncrementalUpdate(t *testing.T) { os.MkdirAll(dir, 0755) defer os.RemoveAll(dir) - testSchema := NewSchema("test", []Field{ + testSchema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"}, }) + if err != nil { + t.Fatal(err) + } // 1. 创建索引并添加初始数据 mgr := NewIndexManager(dir, testSchema) @@ -103,7 +109,7 @@ func TestIncrementalUpdate(t *testing.T) { } // 3. 增量更新 - err := idx.IncrementalUpdate(getData, 3, 5) + err = idx.IncrementalUpdate(getData, 3, 5) if err != nil { t.Fatal(err) } @@ -143,9 +149,12 @@ func TestNeedsUpdate(t *testing.T) { os.MkdirAll(dir, 0755) defer os.RemoveAll(dir) - testSchema := NewSchema("test", []Field{ + testSchema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"}, }) + if err != nil { + t.Fatal(err) + } mgr := NewIndexManager(dir, testSchema) mgr.CreateIndex("name") @@ -174,16 +183,19 @@ func TestIndexPersistence(t *testing.T) { defer os.RemoveAll(dir) // 创建 Schema - testSchema := NewSchema("test", []Field{ + testSchema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"}, {Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } // 1. 创建索引管理器 mgr := NewIndexManager(dir, testSchema) // 2. 创建索引 - err := mgr.CreateIndex("name") + err = mgr.CreateIndex("name") if err != nil { t.Fatal(err) } @@ -253,9 +265,12 @@ func TestIndexDropWithFile(t *testing.T) { os.MkdirAll(dir, 0755) defer os.RemoveAll(dir) - testSchema := NewSchema("test", []Field{ + testSchema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"}, }) + if err != nil { + t.Fatal(err) + } mgr := NewIndexManager(dir, testSchema) @@ -272,7 +287,7 @@ func TestIndexDropWithFile(t *testing.T) { } // 删除索引 - err := mgr.DropIndex("name") + err = mgr.DropIndex("name") if err != nil { t.Fatal(err) } @@ -290,11 +305,14 @@ func TestIndexQueryIntegration(t *testing.T) { tmpDir := t.TempDir() // 1. 创建带索引字段的 Schema - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: false}, {Name: "email", Type: FieldTypeString, Indexed: true}, // email 字段有索引 {Name: "age", Type: FieldTypeInt64, Indexed: false}, }) + if err != nil { + t.Fatal(err) + } // 2. 打开表 table, err := OpenTable(&TableOptions{ @@ -450,11 +468,14 @@ func TestIndexPersistenceAcrossRestart(t *testing.T) { // 1. 第一次打开:创建数据和索引 { - schema := NewSchema("products", []Field{ + schema, err := NewSchema("products", []Field{ {Name: "name", Type: FieldTypeString, Indexed: false}, {Name: "category", Type: FieldTypeString, Indexed: true}, {Name: "price", Type: FieldTypeInt64, Indexed: false}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, diff --git a/query_lazy_test.go b/query_lazy_test.go index 8b779b8..f07838a 100644 --- a/query_lazy_test.go +++ b/query_lazy_test.go @@ -11,10 +11,13 @@ func TestLazyLoadingBasic(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestLazyLoadingBasic") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -67,10 +70,13 @@ func TestLazyLoadingVsEagerLoading(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestLazyLoadingVsEagerLoading") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -144,11 +150,14 @@ func TestIndexQueryIsEager(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestIndexQueryIsEager") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "email", Type: FieldTypeString, Indexed: true}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -226,11 +235,14 @@ func TestLazyLoadingWithConditions(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestLazyLoadingWithConditions") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, {Name: "active", Type: FieldTypeBool}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -301,10 +313,13 @@ func TestFirstDoesNotLoadAll(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestFirstDoesNotLoadAll") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, diff --git a/schema.go b/schema.go index b31a107..cc840e0 100644 --- a/schema.go +++ b/schema.go @@ -50,11 +50,40 @@ type Schema struct { } // NewSchema 创建 Schema -func NewSchema(name string, fields []Field) *Schema { +// 参数: +// - name: Schema 名称,不能为空 +// - fields: 字段列表,至少需要 1 个字段 +// +// 返回: +// - *Schema: Schema 实例 +// - error: 错误信息 +func NewSchema(name string, fields []Field) (*Schema, error) { + // 验证 name + if name == "" { + return nil, NewError(ErrCodeSchemaInvalid, fmt.Errorf("schema name cannot be empty")) + } + + // 验证 fields 数量 + if len(fields) == 0 { + return nil, NewError(ErrCodeSchemaInvalid, fmt.Errorf("schema must have at least one field")) + } + + // 验证字段名不能为空且不能重复 + fieldNames := make(map[string]bool) + for i, field := range fields { + if field.Name == "" { + return nil, NewError(ErrCodeSchemaInvalid, fmt.Errorf("field at index %d has empty name", i)) + } + if fieldNames[field.Name] { + return nil, NewError(ErrCodeSchemaInvalid, fmt.Errorf("duplicate field name: %s", field.Name)) + } + fieldNames[field.Name] = true + } + return &Schema{ Name: name, Fields: fields, - } + }, nil } // StructToFields 从 Go 结构体生成 Field 列表 diff --git a/schema_test.go b/schema_test.go index cf7d13b..252db9d 100644 --- a/schema_test.go +++ b/schema_test.go @@ -1,41 +1,65 @@ package srdb import ( + "strings" "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: "描述"}, -}) +// Package-level test schemas +var ( + UserSchema *Schema + LogSchema *Schema + OrderSchema *Schema +) -// 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: "错误码"}, -}) +func init() { + var err error -// 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: "是否支付"}, -}) + // UserSchema 用户表 Schema + UserSchema, err = 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: "描述"}, + }) + if err != nil { + panic("Failed to create UserSchema: " + err.Error()) + } + + // LogSchema 日志表 Schema + LogSchema, err = 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: "错误码"}, + }) + if err != nil { + panic("Failed to create LogSchema: " + err.Error()) + } + + // OrderSchema 订单表 Schema + OrderSchema, err = 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: "是否支付"}, + }) + if err != nil { + panic("Failed to create OrderSchema: " + err.Error()) + } +} func TestSchema(t *testing.T) { // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"}, {Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"}, {Name: "score", Type: FieldTypeFloat, Indexed: false, Comment: "分数"}, }) + if err != nil { + t.Fatal(err) + } // 测试数据 data := map[string]any{ @@ -45,7 +69,7 @@ func TestSchema(t *testing.T) { } // 验证 - err := schema.Validate(data) + err = schema.Validate(data) if err != nil { t.Errorf("Validation failed: %v", err) } @@ -60,10 +84,13 @@ func TestSchema(t *testing.T) { } func TestSchemaValidation(t *testing.T) { - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"}, {Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } // 正确的数据 validData := map[string]any{ @@ -71,7 +98,7 @@ func TestSchemaValidation(t *testing.T) { "age": 30, } - err := schema.Validate(validData) + err = schema.Validate(validData) if err != nil { t.Errorf("Valid data failed validation: %v", err) } @@ -90,11 +117,361 @@ func TestSchemaValidation(t *testing.T) { t.Log("Schema validation test passed!") } +// TestNewSchemaValidation 测试 NewSchema 的各种验证场景 +func TestNewSchemaValidation(t *testing.T) { + tests := []struct { + name string + schemaName string + fields []Field + shouldError bool + errorMsg string + }{ + { + name: "Valid schema", + schemaName: "users", + fields: []Field{ + {Name: "id", Type: FieldTypeInt64}, + {Name: "name", Type: FieldTypeString}, + }, + shouldError: false, + }, + { + name: "Empty schema name", + schemaName: "", + fields: []Field{{Name: "id", Type: FieldTypeInt64}}, + shouldError: true, + errorMsg: "schema name cannot be empty", + }, + { + name: "Empty fields array", + schemaName: "users", + fields: []Field{}, + shouldError: true, + errorMsg: "schema must have at least one field", + }, + { + name: "Nil fields array", + schemaName: "users", + fields: nil, + shouldError: true, + errorMsg: "schema must have at least one field", + }, + { + name: "Empty field name at index 0", + schemaName: "users", + fields: []Field{ + {Name: "", Type: FieldTypeInt64}, + }, + shouldError: true, + errorMsg: "field at index 0 has empty name", + }, + { + name: "Empty field name at index 1", + schemaName: "users", + fields: []Field{ + {Name: "id", Type: FieldTypeInt64}, + {Name: "", Type: FieldTypeString}, + }, + shouldError: true, + errorMsg: "field at index 1 has empty name", + }, + { + name: "Duplicate field name", + schemaName: "users", + fields: []Field{ + {Name: "id", Type: FieldTypeInt64}, + {Name: "name", Type: FieldTypeString}, + {Name: "id", Type: FieldTypeString}, // Duplicate + }, + shouldError: true, + errorMsg: "duplicate field name: id", + }, + { + name: "Valid schema with single field", + schemaName: "logs", + fields: []Field{ + {Name: "message", Type: FieldTypeString}, + }, + shouldError: false, + }, + { + name: "Valid schema with indexed field", + schemaName: "users", + fields: []Field{ + {Name: "id", Type: FieldTypeInt64, Indexed: true}, + {Name: "email", Type: FieldTypeString, Indexed: true}, + {Name: "age", Type: FieldTypeInt64}, + }, + shouldError: false, + }, + { + name: "Valid schema with comments", + schemaName: "products", + fields: []Field{ + {Name: "id", Type: FieldTypeInt64, Comment: "产品ID"}, + {Name: "name", Type: FieldTypeString, Comment: "产品名称"}, + {Name: "price", Type: FieldTypeFloat, Comment: "价格"}, + }, + shouldError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + schema, err := NewSchema(tt.schemaName, tt.fields) + + if tt.shouldError { + if err == nil { + t.Errorf("Expected error but got none") + return + } + + if !strings.Contains(err.Error(), tt.errorMsg) { + t.Errorf("Expected error to contain %q, got %q", tt.errorMsg, err.Error()) + } + + // 验证错误码是 ErrCodeSchemaInvalid + if GetErrorCode(err) != ErrCodeSchemaInvalid { + t.Errorf("Expected error code %d, got %d", ErrCodeSchemaInvalid, GetErrorCode(err)) + } + } else { + if err != nil { + t.Errorf("Expected no error but got: %v", err) + return + } + + // 验证返回的 schema 是正确的 + if schema == nil { + t.Errorf("Expected schema, got nil") + return + } + if schema.Name != tt.schemaName { + t.Errorf("Expected schema name %q, got %q", tt.schemaName, schema.Name) + } + if len(schema.Fields) != len(tt.fields) { + t.Errorf("Expected %d fields, got %d", len(tt.fields), len(schema.Fields)) + } + } + }) + } +} + +// TestNewSchemaFieldValidation 测试字段级别的验证 +func TestNewSchemaFieldValidation(t *testing.T) { + t.Run("Multiple duplicate field names", func(t *testing.T) { + schema, err := NewSchema("test", []Field{ + {Name: "id", Type: FieldTypeInt64}, + {Name: "name", Type: FieldTypeString}, + {Name: "id", Type: FieldTypeString}, // First duplicate + {Name: "name", Type: FieldTypeString}, // Second duplicate + }) + + if err == nil { + t.Errorf("Expected error for duplicate field names") + return + } + + // 应该在第一个重复处就停止 + if !strings.Contains(err.Error(), "duplicate field name: id") { + t.Errorf("Expected error about duplicate field 'id', got: %v", err) + } + + if schema != nil { + t.Errorf("Expected nil schema on error, got %+v", schema) + } + }) + + t.Run("Case sensitive field names", func(t *testing.T) { + // 大小写敏感,ID 和 id 应该是不同的字段 + schema, err := NewSchema("test", []Field{ + {Name: "id", Type: FieldTypeInt64}, + {Name: "ID", Type: FieldTypeInt64}, + {Name: "Id", Type: FieldTypeInt64}, + }) + + if err != nil { + t.Errorf("Expected no error for case-sensitive field names, got: %v", err) + return + } + + if len(schema.Fields) != 3 { + t.Errorf("Expected 3 fields (case sensitive), got %d", len(schema.Fields)) + } + }) + + t.Run("Fields with all types", func(t *testing.T) { + schema, err := NewSchema("test", []Field{ + {Name: "int_field", Type: FieldTypeInt64}, + {Name: "string_field", Type: FieldTypeString}, + {Name: "float_field", Type: FieldTypeFloat}, + {Name: "bool_field", Type: FieldTypeBool}, + }) + + if err != nil { + t.Errorf("Expected no error, got: %v", err) + return + } + + if len(schema.Fields) != 4 { + t.Errorf("Expected 4 fields, got %d", len(schema.Fields)) + } + + // 验证每个字段的类型 + expectedTypes := map[string]FieldType{ + "int_field": FieldTypeInt64, + "string_field": FieldTypeString, + "float_field": FieldTypeFloat, + "bool_field": FieldTypeBool, + } + + for _, field := range schema.Fields { + expectedType, exists := expectedTypes[field.Name] + if !exists { + t.Errorf("Unexpected field name: %s", field.Name) + continue + } + if field.Type != expectedType { + t.Errorf("Field %s: expected type %v, got %v", field.Name, expectedType, field.Type) + } + } + }) +} + +// TestNewSchemaEdgeCases 测试边界情况 +func TestNewSchemaEdgeCases(t *testing.T) { + t.Run("Very long schema name", func(t *testing.T) { + longName := strings.Repeat("a", 1000) + schema, err := NewSchema(longName, []Field{ + {Name: "id", Type: FieldTypeInt64}, + }) + + if err != nil { + t.Errorf("Expected no error for long schema name, got: %v", err) + return + } + + if schema.Name != longName { + t.Errorf("Expected schema name to be preserved") + } + }) + + t.Run("Very long field name", func(t *testing.T) { + longFieldName := strings.Repeat("b", 1000) + schema, err := NewSchema("test", []Field{ + {Name: longFieldName, Type: FieldTypeInt64}, + }) + + if err != nil { + t.Errorf("Expected no error for long field name, got: %v", err) + return + } + + if schema.Fields[0].Name != longFieldName { + t.Errorf("Expected field name to be preserved") + } + }) + + t.Run("Many fields", func(t *testing.T) { + fields := make([]Field, 100) + for i := 0; i < 100; i++ { + fields[i] = Field{ + Name: strings.Repeat("field", 1) + string(rune('a'+i)), + Type: FieldTypeInt64, + } + } + + schema, err := NewSchema("test", fields) + + if err != nil { + t.Errorf("Expected no error for many fields, got: %v", err) + return + } + + if len(schema.Fields) != 100 { + t.Errorf("Expected 100 fields, got %d", len(schema.Fields)) + } + }) + + t.Run("Field with special characters", func(t *testing.T) { + schema, err := NewSchema("test", []Field{ + {Name: "field_with_underscore", Type: FieldTypeInt64}, + {Name: "field123", Type: FieldTypeInt64}, + {Name: "字段名", Type: FieldTypeString}, // 中文字段名 + }) + + if err != nil { + t.Errorf("Expected no error for special characters, got: %v", err) + return + } + + if len(schema.Fields) != 3 { + t.Errorf("Expected 3 fields with special characters, got %d", len(schema.Fields)) + } + }) +} + +// TestNewSchemaConsistency 测试创建后的一致性 +func TestNewSchemaConsistency(t *testing.T) { + t.Run("Field order preserved", func(t *testing.T) { + fields := []Field{ + {Name: "zebra", Type: FieldTypeString}, + {Name: "alpha", Type: FieldTypeInt64}, + {Name: "beta", Type: FieldTypeFloat}, + } + + schema, err := NewSchema("test", fields) + + if err != nil { + t.Errorf("Expected no error, got: %v", err) + return + } + + // 字段顺序应该保持不变 + for i, field := range schema.Fields { + if field.Name != fields[i].Name { + t.Errorf("Field order not preserved at index %d: expected %s, got %s", + i, fields[i].Name, field.Name) + } + } + }) + + t.Run("Field properties preserved", func(t *testing.T) { + fields := []Field{ + {Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "Primary key"}, + {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "User name"}, + } + + schema, err := NewSchema("users", fields) + + if err != nil { + t.Errorf("Expected no error, got: %v", err) + return + } + + // 验证所有属性都被保留 + if schema.Fields[0].Indexed != true { + t.Errorf("Expected field 0 to be indexed") + } + if schema.Fields[1].Indexed != false { + t.Errorf("Expected field 1 to not be indexed") + } + if schema.Fields[0].Comment != "Primary key" { + t.Errorf("Expected field 0 comment to be preserved") + } + if schema.Fields[1].Comment != "User name" { + t.Errorf("Expected field 1 comment to be preserved") + } + }) +} + func TestExtractIndexValue(t *testing.T) { - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"}, {Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } data := map[string]any{ "name": "David", @@ -156,15 +533,21 @@ func TestPredefinedSchemas(t *testing.T) { func TestChecksumDeterminism(t *testing.T) { // 创建相同的 Schema 多次 for i := range 10 { - s1 := NewSchema("users", []Field{ + s1, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } - s2 := NewSchema("users", []Field{ + s2, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } checksum1, err := s1.ComputeChecksum() if err != nil { @@ -186,15 +569,21 @@ func TestChecksumDeterminism(t *testing.T) { // TestChecksumFieldOrderIndependent 测试字段顺序不影响 checksum func TestChecksumFieldOrderIndependent(t *testing.T) { - s1 := NewSchema("users", []Field{ + s1, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } - s2 := NewSchema("users", []Field{ + s2, err := NewSchema("users", []Field{ {Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}, {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, }) + if err != nil { + t.Fatal(err) + } checksum1, _ := s1.ComputeChecksum() checksum2, _ := s2.ComputeChecksum() @@ -209,13 +598,19 @@ func TestChecksumFieldOrderIndependent(t *testing.T) { // TestChecksumDifferentData 测试不同 Schema 的 checksum 应该不同 func TestChecksumDifferentData(t *testing.T) { - s1 := NewSchema("users", []Field{ + s1, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"}, }) + if err != nil { + t.Fatal(err) + } - s2 := NewSchema("users", []Field{ + s2, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, // Indexed 不同 }) + if err != nil { + t.Fatal(err) + } checksum1, _ := s1.ComputeChecksum() checksum2, _ := s2.ComputeChecksum() @@ -236,12 +631,20 @@ func TestChecksumMultipleFieldOrders(t *testing.T) { fieldD := Field{Name: "email", Type: FieldTypeString, Indexed: true, Comment: "邮箱"} // 创建不同顺序的 Schema + mustNewSchema := func(name string, fields []Field) *Schema { + s, err := NewSchema(name, fields) + if err != nil { + t.Fatalf("Failed to create schema: %v", err) + } + return s + } + 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 + mustNewSchema("test", []Field{fieldA, fieldB, fieldC, fieldD}), // 原始顺序 + mustNewSchema("test", []Field{fieldD, fieldC, fieldB, fieldA}), // 完全反转 + mustNewSchema("test", []Field{fieldB, fieldD, fieldA, fieldC}), // 随机顺序 1 + mustNewSchema("test", []Field{fieldC, fieldA, fieldD, fieldB}), // 随机顺序 2 + mustNewSchema("test", []Field{fieldD, fieldA, fieldC, fieldB}), // 随机顺序 3 } // 计算所有 checksum @@ -478,7 +881,10 @@ func TestStructToFieldsWithSchema(t *testing.T) { } // 创建 Schema - schema := NewSchema("customers", fields) + schema, err := NewSchema("customers", fields) + if err != nil { + t.Fatal(err) + } // 验证 Schema if schema.Name != "customers" { diff --git a/sstable_test.go b/sstable_test.go index d87088c..bcf95d7 100644 --- a/sstable_test.go +++ b/sstable_test.go @@ -16,10 +16,13 @@ func TestSSTable(t *testing.T) { defer os.Remove("test.sst") // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } // 2. 写入数据 writer := NewSSTableWriter(file, schema) @@ -164,9 +167,12 @@ func TestSSTableHeaderSerialization(t *testing.T) { func BenchmarkSSTableGet(b *testing.B) { // 创建 Schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "value", Type: FieldTypeInt64}, }) + if err != nil { + b.Fatal(err) + } // 创建测试文件 file, _ := os.Create("bench.sst") diff --git a/table.go b/table.go index 5dc0a2f..ad68bff 100644 --- a/table.go +++ b/table.go @@ -80,7 +80,10 @@ func OpenTable(opts *TableOptions) (*Table, error) { var sch *Schema if opts.Name != "" && len(opts.Fields) > 0 { // 从 Name 和 Fields 创建 Schema - sch = NewSchema(opts.Name, opts.Fields) + sch, err = NewSchema(opts.Name, opts.Fields) + if err != nil { + return nil, fmt.Errorf("create schema: %w", err) + } // 保存到磁盘(带校验和) schemaPath := filepath.Join(opts.Dir, "schema.json") schemaFile, err := NewSchemaFile(sch) diff --git a/table_test.go b/table_test.go index 8ba5ff9..e662f0b 100644 --- a/table_test.go +++ b/table_test.go @@ -18,10 +18,13 @@ func TestTable(t *testing.T) { os.RemoveAll(dir) defer os.RemoveAll(dir) - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: dir, @@ -80,9 +83,12 @@ func TestTableRecover(t *testing.T) { os.RemoveAll(dir) defer os.RemoveAll(dir) - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "value", Type: FieldTypeInt64, Indexed: false, Comment: "值"}, }) + if err != nil { + t.Fatal(err) + } // 1. 创建引擎并插入数据 table, err := OpenTable(&TableOptions{ @@ -143,9 +149,12 @@ func TestTableFlush(t *testing.T) { os.RemoveAll(dir) defer os.RemoveAll(dir) - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "data", Type: FieldTypeString, Indexed: false, Comment: "数据"}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: dir, @@ -193,9 +202,12 @@ func BenchmarkTableInsert(b *testing.B) { os.RemoveAll(dir) defer os.RemoveAll(dir) - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "value", Type: FieldTypeInt64, Indexed: false, Comment: "值"}, }) + if err != nil { + b.Fatal(err) + } table, _ := OpenTable(&TableOptions{ Dir: dir, @@ -219,9 +231,12 @@ func BenchmarkTableGet(b *testing.B) { os.RemoveAll(dir) defer os.RemoveAll(dir) - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "value", Type: FieldTypeInt64, Indexed: false, Comment: "值"}, }) + if err != nil { + b.Fatal(err) + } table, _ := OpenTable(&TableOptions{ Dir: dir, @@ -251,9 +266,12 @@ func TestHighConcurrencyWrite(t *testing.T) { // Note: This test uses []byte payload - we create a minimal schema // Schema validation accepts []byte as it gets JSON-marshaled - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "worker_id", Type: FieldTypeInt64, Indexed: false, Comment: "Worker ID"}, }) + if err != nil { + t.Fatal(err) + } opts := &TableOptions{ Dir: tmpDir, @@ -366,9 +384,12 @@ func TestConcurrentReadWrite(t *testing.T) { tmpDir := t.TempDir() // Note: This test uses []byte data - we create a minimal schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "writer_id", Type: FieldTypeInt64, Indexed: false, Comment: "Writer ID"}, }) + if err != nil { + t.Fatal(err) + } opts := &TableOptions{ Dir: tmpDir, @@ -485,9 +506,12 @@ func TestPowerFailureRecovery(t *testing.T) { t.Log("=== 阶段 1: 写入数据 ===") // Note: This test uses []byte data - we create a minimal schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "batch", Type: FieldTypeInt64, Indexed: false, Comment: "Batch number"}, }) + if err != nil { + t.Fatal(err) + } opts := &TableOptions{ Dir: tmpDir, @@ -621,9 +645,12 @@ func TestCrashDuringCompaction(t *testing.T) { tmpDir := t.TempDir() // Note: This test uses []byte data - we create a minimal schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "Index"}, }) + if err != nil { + t.Fatal(err) + } opts := &TableOptions{ Dir: tmpDir, @@ -721,9 +748,12 @@ func TestLargeDataIntegrity(t *testing.T) { tmpDir := t.TempDir() // Note: This test uses []byte data - we create a minimal schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "size", Type: FieldTypeInt64, Indexed: false, Comment: "Size"}, }) + if err != nil { + t.Fatal(err) + } opts := &TableOptions{ Dir: tmpDir, @@ -832,9 +862,12 @@ func BenchmarkConcurrentWrites(b *testing.B) { tmpDir := b.TempDir() // Note: This benchmark uses []byte data - we create a minimal schema - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "timestamp", Type: FieldTypeInt64, Indexed: false, Comment: "Timestamp"}, }) + if err != nil { + b.Fatal(err) + } opts := &TableOptions{ Dir: tmpDir, @@ -883,11 +916,14 @@ func TestTableWithCompaction(t *testing.T) { // 创建临时目录 tmpDir := t.TempDir() - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "batch", Type: FieldTypeInt64, Indexed: false, Comment: "批次"}, {Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "索引"}, {Name: "value", Type: FieldTypeString, Indexed: false, Comment: "值"}, }) + if err != nil { + t.Fatal(err) + } // 打开 Table opts := &TableOptions{ @@ -1009,11 +1045,14 @@ func TestTableWithCompaction(t *testing.T) { func TestTableCompactionMerge(t *testing.T) { tmpDir := t.TempDir() - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "batch", Type: FieldTypeInt64, Indexed: false, Comment: "批次"}, {Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "索引"}, {Name: "value", Type: FieldTypeString, Indexed: false, Comment: "值"}, }) + if err != nil { + t.Fatal(err) + } opts := &TableOptions{ Dir: tmpDir, @@ -1114,10 +1153,13 @@ func TestTableBackgroundCompaction(t *testing.T) { tmpDir := t.TempDir() - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "batch", Type: FieldTypeInt64, Indexed: false, Comment: "批次"}, {Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "索引"}, }) + if err != nil { + t.Fatal(err) + } opts := &TableOptions{ Dir: tmpDir, @@ -1201,10 +1243,13 @@ func TestTableBackgroundCompaction(t *testing.T) { func BenchmarkTableWithCompaction(b *testing.B) { tmpDir := b.TempDir() - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "索引"}, {Name: "value", Type: FieldTypeString, Indexed: false, Comment: "值"}, }) + if err != nil { + b.Fatal(err) + } opts := &TableOptions{ Dir: tmpDir, @@ -1253,11 +1298,14 @@ func TestTableSchemaRecover(t *testing.T) { defer os.RemoveAll(dir) // 创建 Schema - s := NewSchema("users", []Field{ + s, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}, {Name: "email", Type: FieldTypeString, Indexed: false, Comment: "邮箱"}, }) + if err != nil { + t.Fatal(err) + } // 1. 创建引擎并插入数据(带 Schema) table, err := OpenTable(&TableOptions{ @@ -1325,10 +1373,13 @@ func TestTableSchemaRecoverInvalid(t *testing.T) { os.RemoveAll(dir) defer os.RemoveAll(dir) - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, {Name: "age", Type: FieldTypeString, Indexed: false, Comment: "年龄字符串"}, }) + if err != nil { + t.Fatal(err) + } // 1. 先不带 Schema 插入一些数据 table, err := OpenTable(&TableOptions{ @@ -1369,10 +1420,13 @@ func TestTableSchemaRecoverInvalid(t *testing.T) { } // 3. 创建 Schema,age 字段要求 int64 - s := NewSchema("users", []Field{ + s, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } // 4. 尝试用 Schema 打开引擎,应该失败 table2, err := OpenTable(&TableOptions{ @@ -1400,10 +1454,13 @@ func TestTableAutoRecoverSchema(t *testing.T) { defer os.RemoveAll(dir) // 创建 Schema - s := NewSchema("users", []Field{ + s, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } // 1. 创建引擎并提供 Schema(会保存到磁盘) table1, err := OpenTable(&TableOptions{ @@ -1492,10 +1549,13 @@ func TestTableSchemaTamperDetection(t *testing.T) { defer os.RemoveAll(dir) // 创建 Schema - s := NewSchema("users", []Field{ + s, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, {Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}, }) + if err != nil { + t.Fatal(err) + } // 1. 创建引擎并保存 Schema table1, err := OpenTable(&TableOptions{ @@ -1554,10 +1614,13 @@ func TestTableClean(t *testing.T) { } defer db.Close() - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "ID"}, {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "Name"}, }) + if err != nil { + t.Fatal(err) + } table, err := db.CreateTable("users", schema) if err != nil { @@ -1623,9 +1686,12 @@ func TestTableDestroy(t *testing.T) { } defer db.Close() - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"}, }) + if err != nil { + t.Fatal(err) + } table, err := db.CreateTable("test", schema) if err != nil { @@ -1679,11 +1745,14 @@ func TestTableCleanWithIndex(t *testing.T) { } defer db.Close() - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "ID"}, {Name: "email", Type: FieldTypeString, Indexed: true, Comment: "Email"}, {Name: "name", Type: FieldTypeString, Indexed: false, Comment: "Name"}, }) + if err != nil { + t.Fatal(err) + } table, err := db.CreateTable("users", schema) if err != nil { @@ -1765,10 +1834,13 @@ func TestTableCleanAndQuery(t *testing.T) { } defer db.Close() - schema := NewSchema("test", []Field{ + schema, err := NewSchema("test", []Field{ {Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"}, {Name: "status", Type: FieldTypeString, Indexed: false, Comment: "Status"}, }) + if err != nil { + t.Fatal(err) + } table, err := db.CreateTable("test", schema) if err != nil { @@ -1850,10 +1922,13 @@ func TestInsertMap(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestInsertMap") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -1892,10 +1967,13 @@ func TestInsertMapSlice(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestInsertMapSlice") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -1940,11 +2018,14 @@ func TestInsertStruct(t *testing.T) { Email string `srdb:"email"` } - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, {Name: "email", Type: FieldTypeString}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -1995,11 +2076,14 @@ func TestInsertStructPointer(t *testing.T) { Email string `srdb:"email"` } - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, {Name: "email", Type: FieldTypeString}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -2046,10 +2130,13 @@ func TestInsertStructSlice(t *testing.T) { Age int64 `srdb:"age"` } - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -2095,10 +2182,13 @@ func TestInsertStructPointerSlice(t *testing.T) { Age int64 `srdb:"age"` } - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -2146,11 +2236,14 @@ func TestInsertWithSnakeCase(t *testing.T) { IsActive bool // 应该自动转为 is_active } - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "user_name", Type: FieldTypeString, Comment: "用户名"}, {Name: "email_address", Type: FieldTypeString}, {Name: "is_active", Type: FieldTypeBool}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -2198,9 +2291,12 @@ func TestInsertInvalidType(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestInsertInvalidType") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -2236,9 +2332,12 @@ func TestInsertEmptySlice(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestInsertEmptySlice") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir, @@ -2270,10 +2369,13 @@ func TestBatchInsertPerformance(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "TestBatchInsertPerformance") defer os.RemoveAll(tmpDir) - schema := NewSchema("users", []Field{ + schema, err := NewSchema("users", []Field{ {Name: "name", Type: FieldTypeString}, {Name: "age", Type: FieldTypeInt64}, }) + if err != nil { + t.Fatal(err) + } table, err := OpenTable(&TableOptions{ Dir: tmpDir,