前端:优化 Manifest 视图文件显示

- 文件名区域改为左右布局
- 左侧显示文件名(如 000001.sst)
- 右侧显示级别标签(如 L0、L1)
- 添加级别标签样式,使用主题色背景
This commit is contained in:
2025-10-09 20:03:53 +08:00
parent c4d79bc54b
commit dd8a534931
43 changed files with 3142 additions and 761 deletions

View File

@@ -0,0 +1,162 @@
# 批量插入示例
这个示例展示了 SRDB 的批量插入功能,支持多种数据类型的插入。
## 功能特性
SRDB 的 `Insert` 方法支持以下输入类型:
1. **单个 map**: `map[string]any`
2. **map 切片**: `[]map[string]any`
3. **单个结构体**: `struct{}`
4. **结构体指针**: `*struct{}`
5. **结构体切片**: `[]struct{}`
6. **结构体指针切片**: `[]*struct{}`
## 运行示例
```bash
cd examples/batch_insert
go run main.go
```
## 示例说明
### 示例 1: 插入单个 map
```go
err = table.Insert(map[string]any{
"name": "Alice",
"age": int64(25),
})
```
最基本的插入方式,适合动态数据。
### 示例 2: 批量插入 map 切片
```go
err = table.Insert([]map[string]any{
{"name": "Alice", "age": int64(25), "email": "alice@example.com"},
{"name": "Bob", "age": int64(30), "email": "bob@example.com"},
{"name": "Charlie", "age": int64(35), "email": "charlie@example.com"},
})
```
批量插入多条数据,提高插入效率。
### 示例 3: 插入单个结构体
```go
type User struct {
Name string `srdb:"name;comment:用户名"`
Age int64 `srdb:"age;comment:年龄"`
Email string `srdb:"email;indexed;comment:邮箱"`
IsActive bool `srdb:"is_active;comment:是否激活"`
}
user := User{
Name: "Alice",
Age: 25,
Email: "alice@example.com",
IsActive: true,
}
err = table.Insert(user)
```
使用结构体插入,提供类型安全和代码可读性。
### 示例 4: 批量插入结构体切片
```go
users := []User{
{Name: "Alice", Age: 25, Email: "alice@example.com", IsActive: true},
{Name: "Bob", Age: 30, Email: "bob@example.com", IsActive: true},
{Name: "Charlie", Age: 35, Email: "charlie@example.com", IsActive: false},
}
err = table.Insert(users)
```
批量插入结构体,适合需要插入大量数据的场景。
### 示例 5: 批量插入结构体指针切片
```go
users := []*User{
{Name: "Alice", Age: 25, Email: "alice@example.com", IsActive: true},
{Name: "Bob", Age: 30, Email: "bob@example.com", IsActive: true},
nil, // nil 指针会被自动跳过
{Name: "Charlie", Age: 35, Email: "charlie@example.com", IsActive: false},
}
err = table.Insert(users)
```
支持指针切片nil 指针会被自动跳过。
### 示例 6: 使用 snake_case 自动转换
```go
type Product struct {
ProductID string `srdb:";comment:产品ID"` // 自动转为 product_id
ProductName string `srdb:";comment:产品名称"` // 自动转为 product_name
Price float64 `srdb:";comment:价格"` // 自动转为 price
InStock bool `srdb:";comment:是否有货"` // 自动转为 in_stock
}
products := []Product{
{ProductID: "P001", ProductName: "Laptop", Price: 999.99, InStock: true},
{ProductID: "P002", ProductName: "Mouse", Price: 29.99, InStock: true},
}
err = table.Insert(products)
```
不指定字段名时,会自动将驼峰命名转换为 snake_case
- `ProductID``product_id`
- `ProductName``product_name`
- `InStock``in_stock`
## Struct Tag 格式
```go
type User struct {
// 完整格式:字段名;索引;注释
Email string `srdb:"email;indexed;comment:邮箱地址"`
// 使用默认字段名snake_case+ 注释
UserName string `srdb:";comment:用户名"` // 自动转为 user_name
// 不使用 tag完全依赖 snake_case 转换
PhoneNumber string // 自动转为 phone_number
// 忽略字段
Internal string `srdb:"-"`
}
```
## 性能优化
批量插入相比逐条插入:
- ✅ 减少函数调用开销
- ✅ 统一类型转换和验证
- ✅ 更清晰的代码逻辑
- ✅ 适合大批量数据导入
## 注意事项
1. **类型匹配**: 确保结构体字段类型与 Schema 定义一致
2. **Schema 验证**: 所有数据都会经过 Schema 验证
3. **nil 处理**: 结构体指针切片中的 nil 会被自动跳过
4. **字段名转换**: 未指定 tag 时自动使用 snake_case 转换
5. **索引更新**: 带索引的字段会自动更新索引
## 相关文档
- [STRUCT_TAG_GUIDE.md](../../STRUCT_TAG_GUIDE.md) - Struct Tag 完整指南
- [SNAKE_CASE_CONVERSION.md](../../SNAKE_CASE_CONVERSION.md) - snake_case 转换规则
- [examples/struct_schema](../struct_schema) - 结构体 Schema 示例

View File

@@ -0,0 +1 @@
MANIFEST-000001

Binary file not shown.

View File

@@ -0,0 +1,22 @@
{
"version": 1,
"timestamp": 1760013696,
"checksum": "343b1f86cfc4ed9471b71b3a63d61b4205b17cf953f4a2698f2d3ebd37540caa",
"schema": {
"Name": "users",
"Fields": [
{
"Name": "name",
"Type": 2,
"Indexed": false,
"Comment": "用户名"
},
{
"Name": "age",
"Type": 1,
"Indexed": false,
"Comment": "年龄"
}
]
}
}

View File

@@ -0,0 +1 @@
2

View File

@@ -0,0 +1 @@
MANIFEST-000001

Binary file not shown.

View File

@@ -0,0 +1,28 @@
{
"version": 1,
"timestamp": 1760013696,
"checksum": "961d723306aa507e4599ad24afe32e0bc30dcac5f9d1aabd1a18128376767a36",
"schema": {
"Name": "users",
"Fields": [
{
"Name": "name",
"Type": 2,
"Indexed": false,
"Comment": ""
},
{
"Name": "age",
"Type": 1,
"Indexed": false,
"Comment": ""
},
{
"Name": "email",
"Type": 2,
"Indexed": true,
"Comment": ""
}
]
}
}

View File

@@ -0,0 +1 @@
2

View File

@@ -0,0 +1 @@
MANIFEST-000001

Binary file not shown.

View File

@@ -0,0 +1,34 @@
{
"version": 1,
"timestamp": 1760013696,
"checksum": "dab23ff2118dc2a790681dcc4e112b3377fd96414a30cdfa0ca98af5d41a271e",
"schema": {
"Name": "users",
"Fields": [
{
"Name": "name",
"Type": 2,
"Indexed": false,
"Comment": ""
},
{
"Name": "age",
"Type": 1,
"Indexed": false,
"Comment": ""
},
{
"Name": "email",
"Type": 2,
"Indexed": false,
"Comment": ""
},
{
"Name": "is_active",
"Type": 4,
"Indexed": false,
"Comment": ""
}
]
}
}

View File

@@ -0,0 +1 @@
2

View File

@@ -0,0 +1 @@
MANIFEST-000001

Binary file not shown.

View File

@@ -0,0 +1,34 @@
{
"version": 1,
"timestamp": 1760013696,
"checksum": "dab23ff2118dc2a790681dcc4e112b3377fd96414a30cdfa0ca98af5d41a271e",
"schema": {
"Name": "users",
"Fields": [
{
"Name": "name",
"Type": 2,
"Indexed": false,
"Comment": ""
},
{
"Name": "age",
"Type": 1,
"Indexed": false,
"Comment": ""
},
{
"Name": "email",
"Type": 2,
"Indexed": false,
"Comment": ""
},
{
"Name": "is_active",
"Type": 4,
"Indexed": false,
"Comment": ""
}
]
}
}

View File

@@ -0,0 +1 @@
2

View File

@@ -0,0 +1 @@
MANIFEST-000001

Binary file not shown.

View File

@@ -0,0 +1,34 @@
{
"version": 1,
"timestamp": 1760013696,
"checksum": "dab23ff2118dc2a790681dcc4e112b3377fd96414a30cdfa0ca98af5d41a271e",
"schema": {
"Name": "users",
"Fields": [
{
"Name": "name",
"Type": 2,
"Indexed": false,
"Comment": ""
},
{
"Name": "age",
"Type": 1,
"Indexed": false,
"Comment": ""
},
{
"Name": "email",
"Type": 2,
"Indexed": false,
"Comment": ""
},
{
"Name": "is_active",
"Type": 4,
"Indexed": false,
"Comment": ""
}
]
}
}

View File

@@ -0,0 +1 @@
2

View File

@@ -0,0 +1 @@
MANIFEST-000001

Binary file not shown.

View File

@@ -0,0 +1,34 @@
{
"version": 1,
"timestamp": 1760013696,
"checksum": "36398ae9f0d772f19a24dd8562fde01665890335416e4951808c6b050d251b43",
"schema": {
"Name": "products",
"Fields": [
{
"Name": "product_id",
"Type": 2,
"Indexed": false,
"Comment": "产品ID"
},
{
"Name": "product_name",
"Type": 2,
"Indexed": false,
"Comment": "产品名称"
},
{
"Name": "price",
"Type": 3,
"Indexed": false,
"Comment": "价格"
},
{
"Name": "in_stock",
"Type": 4,
"Indexed": false,
"Comment": "是否有货"
}
]
}
}

View File

@@ -0,0 +1 @@
2

View File

@@ -0,0 +1,303 @@
package main
import (
"fmt"
"log"
"os"
"time"
"code.tczkiot.com/wlw/srdb"
)
// User 用户结构体
type User struct {
Name string `srdb:"name;comment:用户名"`
Age int64 `srdb:"age;comment:年龄"`
Email string `srdb:"email;indexed;comment:邮箱"`
IsActive bool `srdb:"is_active;comment:是否激活"`
}
// Product 产品结构体(使用默认 snake_case 转换)
type Product struct {
ProductID string `srdb:";comment:产品ID"` // 自动转为 product_id
ProductName string `srdb:";comment:产品名称"` // 自动转为 product_name
Price float64 `srdb:";comment:价格"` // 自动转为 price
InStock bool `srdb:";comment:是否有货"` // 自动转为 in_stock
}
func main() {
fmt.Println("=== SRDB 批量插入示例 ===\n")
// 清理旧数据
os.RemoveAll("./data")
// 示例 1: 插入单个 map
example1()
// 示例 2: 批量插入 map 切片
example2()
// 示例 3: 插入单个结构体
example3()
// 示例 4: 批量插入结构体切片
example4()
// 示例 5: 批量插入结构体指针切片
example5()
// 示例 6: 使用 snake_case 自动转换
example6()
fmt.Println("\n✓ 所有示例执行成功!")
}
func example1() {
fmt.Println("=== 示例 1: 插入单个 map ===")
schema := srdb.NewSchema("users", []srdb.Field{
{Name: "name", Type: srdb.FieldTypeString, Comment: "用户名"},
{Name: "age", Type: srdb.FieldTypeInt64, Comment: "年龄"},
})
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/example1",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 插入单条数据
err = table.Insert(map[string]any{
"name": "Alice",
"age": int64(25),
})
if err != nil {
log.Fatal(err)
}
fmt.Println("✓ 插入 1 条数据")
// 查询
row, _ := table.Get(1)
fmt.Printf(" 查询结果: name=%s, age=%d\n\n", row.Data["name"], row.Data["age"])
}
func example2() {
fmt.Println("=== 示例 2: 批量插入 map 切片 ===")
schema := srdb.NewSchema("users", []srdb.Field{
{Name: "name", Type: srdb.FieldTypeString},
{Name: "age", Type: srdb.FieldTypeInt64},
{Name: "email", Type: srdb.FieldTypeString, Indexed: true},
})
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/example2",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 批量插入
start := time.Now()
err = table.Insert([]map[string]any{
{"name": "Alice", "age": int64(25), "email": "alice@example.com"},
{"name": "Bob", "age": int64(30), "email": "bob@example.com"},
{"name": "Charlie", "age": int64(35), "email": "charlie@example.com"},
{"name": "David", "age": int64(40), "email": "david@example.com"},
{"name": "Eve", "age": int64(45), "email": "eve@example.com"},
})
if err != nil {
log.Fatal(err)
}
elapsed := time.Since(start)
fmt.Printf("✓ 批量插入 5 条数据,耗时: %v\n", elapsed)
// 使用索引查询
rows, _ := table.Query().Eq("email", "bob@example.com").Rows()
defer rows.Close()
if rows.Next() {
row := rows.Row()
data := row.Data()
fmt.Printf(" 索引查询结果: name=%s, email=%s\n\n", data["name"], data["email"])
}
}
func example3() {
fmt.Println("=== 示例 3: 插入单个结构体 ===")
schema := 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},
})
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/example3",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 插入结构体
user := User{
Name: "Alice",
Age: 25,
Email: "alice@example.com",
IsActive: true,
}
err = table.Insert(user)
if err != nil {
log.Fatal(err)
}
fmt.Println("✓ 插入 1 个结构体")
// 查询
row, _ := table.Get(1)
fmt.Printf(" 查询结果: name=%s, age=%d, active=%v\n\n",
row.Data["name"], row.Data["age"], row.Data["is_active"])
}
func example4() {
fmt.Println("=== 示例 4: 批量插入结构体切片 ===")
schema := 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},
})
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/example4",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 批量插入结构体切片
users := []User{
{Name: "Alice", Age: 25, Email: "alice@example.com", IsActive: true},
{Name: "Bob", Age: 30, Email: "bob@example.com", IsActive: true},
{Name: "Charlie", Age: 35, Email: "charlie@example.com", IsActive: false},
}
start := time.Now()
err = table.Insert(users)
if err != nil {
log.Fatal(err)
}
elapsed := time.Since(start)
fmt.Printf("✓ 批量插入 %d 个结构体,耗时: %v\n", len(users), elapsed)
// 查询所有激活用户
rows, _ := table.Query().Eq("is_active", true).Rows()
defer rows.Close()
count := 0
for rows.Next() {
count++
}
fmt.Printf(" 查询结果: 找到 %d 个激活用户\n\n", count)
}
func example5() {
fmt.Println("=== 示例 5: 批量插入结构体指针切片 ===")
schema := 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},
})
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/example5",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 批量插入结构体指针切片
users := []*User{
{Name: "Alice", Age: 25, Email: "alice@example.com", IsActive: true},
{Name: "Bob", Age: 30, Email: "bob@example.com", IsActive: true},
nil, // nil 指针会被自动跳过
{Name: "Charlie", Age: 35, Email: "charlie@example.com", IsActive: false},
}
err = table.Insert(users)
if err != nil {
log.Fatal(err)
}
fmt.Printf("✓ 批量插入 %d 个结构体指针nil 自动跳过)\n", len(users))
// 验证插入数量
row, _ := table.Get(3)
fmt.Printf(" 实际插入: 3 条数据, 最后一条 name=%s\n\n", row.Data["name"])
}
func example6() {
fmt.Println("=== 示例 6: 使用 snake_case 自动转换 ===")
schema := 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: "是否有货"},
})
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/example6",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 结构体字段名是驼峰命名,会自动转为 snake_case
products := []Product{
{ProductID: "P001", ProductName: "Laptop", Price: 999.99, InStock: true},
{ProductID: "P002", ProductName: "Mouse", Price: 29.99, InStock: true},
{ProductID: "P003", ProductName: "Keyboard", Price: 79.99, InStock: false},
}
err = table.Insert(products)
if err != nil {
log.Fatal(err)
}
fmt.Printf("✓ 批量插入 %d 个产品(自动 snake_case 转换)\n", len(products))
// 查询
row, _ := table.Get(1)
fmt.Printf(" 字段名自动转换:\n")
fmt.Printf(" ProductID -> product_id = %s\n", row.Data["product_id"])
fmt.Printf(" ProductName -> product_name = %s\n", row.Data["product_name"])
fmt.Printf(" Price -> price = %.2f\n", row.Data["price"])
fmt.Printf(" InStock -> in_stock = %v\n\n", row.Data["in_stock"])
}