前端:优化 Manifest 视图文件显示
- 文件名区域改为左右布局 - 左侧显示文件名(如 000001.sst) - 右侧显示级别标签(如 L0、L1) - 添加级别标签样式,使用主题色背景
This commit is contained in:
162
examples/batch_insert/README.md
Normal file
162
examples/batch_insert/README.md
Normal 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 示例
|
||||
1
examples/batch_insert/data/example1/CURRENT
Normal file
1
examples/batch_insert/data/example1/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/batch_insert/data/example1/MANIFEST-000001
Normal file
BIN
examples/batch_insert/data/example1/MANIFEST-000001
Normal file
Binary file not shown.
22
examples/batch_insert/data/example1/schema.json
Normal file
22
examples/batch_insert/data/example1/schema.json
Normal 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": "年龄"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/batch_insert/data/example1/wal/CURRENT
Normal file
1
examples/batch_insert/data/example1/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
2
|
||||
1
examples/batch_insert/data/example2/CURRENT
Normal file
1
examples/batch_insert/data/example2/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/batch_insert/data/example2/MANIFEST-000001
Normal file
BIN
examples/batch_insert/data/example2/MANIFEST-000001
Normal file
Binary file not shown.
28
examples/batch_insert/data/example2/schema.json
Normal file
28
examples/batch_insert/data/example2/schema.json
Normal 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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/batch_insert/data/example2/wal/CURRENT
Normal file
1
examples/batch_insert/data/example2/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
2
|
||||
1
examples/batch_insert/data/example3/CURRENT
Normal file
1
examples/batch_insert/data/example3/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/batch_insert/data/example3/MANIFEST-000001
Normal file
BIN
examples/batch_insert/data/example3/MANIFEST-000001
Normal file
Binary file not shown.
34
examples/batch_insert/data/example3/schema.json
Normal file
34
examples/batch_insert/data/example3/schema.json
Normal 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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/batch_insert/data/example3/wal/CURRENT
Normal file
1
examples/batch_insert/data/example3/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
2
|
||||
1
examples/batch_insert/data/example4/CURRENT
Normal file
1
examples/batch_insert/data/example4/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/batch_insert/data/example4/MANIFEST-000001
Normal file
BIN
examples/batch_insert/data/example4/MANIFEST-000001
Normal file
Binary file not shown.
34
examples/batch_insert/data/example4/schema.json
Normal file
34
examples/batch_insert/data/example4/schema.json
Normal 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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/batch_insert/data/example4/wal/CURRENT
Normal file
1
examples/batch_insert/data/example4/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
2
|
||||
1
examples/batch_insert/data/example5/CURRENT
Normal file
1
examples/batch_insert/data/example5/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/batch_insert/data/example5/MANIFEST-000001
Normal file
BIN
examples/batch_insert/data/example5/MANIFEST-000001
Normal file
Binary file not shown.
34
examples/batch_insert/data/example5/schema.json
Normal file
34
examples/batch_insert/data/example5/schema.json
Normal 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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/batch_insert/data/example5/wal/CURRENT
Normal file
1
examples/batch_insert/data/example5/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
2
|
||||
1
examples/batch_insert/data/example6/CURRENT
Normal file
1
examples/batch_insert/data/example6/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/batch_insert/data/example6/MANIFEST-000001
Normal file
BIN
examples/batch_insert/data/example6/MANIFEST-000001
Normal file
Binary file not shown.
34
examples/batch_insert/data/example6/schema.json
Normal file
34
examples/batch_insert/data/example6/schema.json
Normal 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": "是否有货"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/batch_insert/data/example6/wal/CURRENT
Normal file
1
examples/batch_insert/data/example6/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
2
|
||||
303
examples/batch_insert/main.go
Normal file
303
examples/batch_insert/main.go
Normal 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"])
|
||||
}
|
||||
98
examples/snake_case_demo/main.go
Normal file
98
examples/snake_case_demo/main.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"code.tczkiot.com/wlw/srdb"
|
||||
)
|
||||
|
||||
// 演示各种驼峰命名自动转换为 snake_case
|
||||
type DemoStruct struct {
|
||||
// 基本转换
|
||||
UserName string `srdb:";comment:用户名"` // -> user_name
|
||||
EmailAddress string `srdb:";comment:邮箱地址"` // -> email_address
|
||||
PhoneNumber string `srdb:";comment:手机号"` // -> phone_number
|
||||
|
||||
// 连续大写字母
|
||||
HTTPEndpoint string `srdb:";comment:HTTP 端点"` // -> http_endpoint
|
||||
URLPath string `srdb:";comment:URL 路径"` // -> url_path
|
||||
XMLParser string `srdb:";comment:XML 解析器"` // -> xml_parser
|
||||
|
||||
// 短命名
|
||||
ID int64 `srdb:";comment:ID"` // -> id
|
||||
|
||||
// 布尔值
|
||||
IsActive bool `srdb:";comment:是否激活"` // -> is_active
|
||||
IsDeleted bool `srdb:";comment:是否删除"` // -> is_deleted
|
||||
|
||||
// 数字混合
|
||||
Address1 string `srdb:";comment:地址1"` // -> address1
|
||||
User2Name string `srdb:";comment:用户2名称"` // -> user2_name
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== snake_case 自动转换演示 ===")
|
||||
|
||||
// 生成 Field 列表
|
||||
fields, err := srdb.StructToFields(DemoStruct{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 打印转换结果
|
||||
fmt.Println("\n字段名转换(驼峰命名 -> snake_case):")
|
||||
fmt.Printf("%-20s -> %-20s %s\n", "Go 字段名", "数据库字段名", "注释")
|
||||
fmt.Println(string(make([]byte, 70)) + "\n" + string(make([]byte, 70)))
|
||||
|
||||
type fieldInfo struct {
|
||||
goName string
|
||||
dbName string
|
||||
comment string
|
||||
}
|
||||
|
||||
fieldMapping := []fieldInfo{
|
||||
{"UserName", "user_name", "用户名"},
|
||||
{"EmailAddress", "email_address", "邮箱地址"},
|
||||
{"PhoneNumber", "phone_number", "手机号"},
|
||||
{"HTTPEndpoint", "http_endpoint", "HTTP 端点"},
|
||||
{"URLPath", "url_path", "URL 路径"},
|
||||
{"XMLParser", "xml_parser", "XML 解析器"},
|
||||
{"ID", "id", "ID"},
|
||||
{"IsActive", "is_active", "是否激活"},
|
||||
{"IsDeleted", "is_deleted", "是否删除"},
|
||||
{"Address1", "address1", "地址1"},
|
||||
{"User2Name", "user2_name", "用户2名称"},
|
||||
}
|
||||
|
||||
for i, field := range fields {
|
||||
if i < len(fieldMapping) {
|
||||
fmt.Printf("%-20s -> %-20s %s\n",
|
||||
fieldMapping[i].goName,
|
||||
field.Name,
|
||||
field.Comment)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证转换
|
||||
fmt.Println("\n=== 转换验证 ===")
|
||||
allCorrect := true
|
||||
for i, field := range fields {
|
||||
if i < len(fieldMapping) {
|
||||
expected := fieldMapping[i].dbName
|
||||
if field.Name != expected {
|
||||
fmt.Printf("❌ %s: 期望 %s, 实际 %s\n",
|
||||
fieldMapping[i].goName, expected, field.Name)
|
||||
allCorrect = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if allCorrect {
|
||||
fmt.Println("✅ 所有字段名转换正确!")
|
||||
}
|
||||
|
||||
// 创建 Schema
|
||||
schema := srdb.NewSchema("demo", fields)
|
||||
fmt.Printf("\n✅ 成功创建 Schema,包含 %d 个字段\n", len(schema.Fields))
|
||||
}
|
||||
153
examples/struct_schema/README.md
Normal file
153
examples/struct_schema/README.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# StructToFields 示例
|
||||
|
||||
这个示例展示如何使用 `StructToFields` 方法从 Go 结构体自动生成 Schema。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 从结构体自动生成 Field 列表
|
||||
- ✅ 支持 struct tags 定义字段属性
|
||||
- ✅ 支持索引标记
|
||||
- ✅ 支持字段注释
|
||||
- ✅ 自动类型映射
|
||||
- ✅ 支持忽略字段
|
||||
|
||||
## Struct Tag 格式
|
||||
|
||||
### srdb tag
|
||||
|
||||
所有配置都在 `srdb` tag 中,使用分号 `;` 分隔:
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
// 基本用法:指定字段名
|
||||
Name string `srdb:"name"`
|
||||
|
||||
// 标记为索引字段
|
||||
Email string `srdb:"email;indexed"`
|
||||
|
||||
// 完整格式:字段名;索引;注释
|
||||
Age int64 `srdb:"age;comment:年龄"`
|
||||
|
||||
// 带索引和注释
|
||||
Phone string `srdb:"phone;indexed;comment:手机号"`
|
||||
|
||||
// 忽略该字段
|
||||
Internal string `srdb:"-"`
|
||||
|
||||
// 不使用 tag,默认使用 snake_case 转换
|
||||
Score float64 // 字段名: score
|
||||
UserID string // 字段名: user_id
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Tag 格式说明
|
||||
|
||||
格式:`srdb:"字段名;选项1;选项2;..."`
|
||||
|
||||
- **字段名**(第一部分):指定数据库中的字段名,省略则自动将结构体字段名转为 snake_case
|
||||
- **indexed**:标记该字段需要建立索引
|
||||
- **comment:注释内容**:字段注释说明
|
||||
|
||||
### 默认字段名转换(snake_case)
|
||||
|
||||
如果不指定字段名,会自动将驼峰命名转换为 snake_case:
|
||||
|
||||
- `UserName` → `user_name`
|
||||
- `EmailAddress` → `email_address`
|
||||
- `IsActive` → `is_active`
|
||||
- `HTTPServer` → `http_server`
|
||||
- `ID` → `id`
|
||||
|
||||
## 类型映射
|
||||
|
||||
| Go 类型 | FieldType |
|
||||
|---------|-----------|
|
||||
| int, int64, int32, int16, int8 | FieldTypeInt64 |
|
||||
| uint, uint64, uint32, uint16, uint8 | FieldTypeInt64 |
|
||||
| string | FieldTypeString |
|
||||
| float64, float32 | FieldTypeFloat |
|
||||
| bool | FieldTypeBool |
|
||||
|
||||
## 完整示例
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"code.tczkiot.com/wlw/srdb"
|
||||
)
|
||||
|
||||
// 定义结构体
|
||||
type User struct {
|
||||
Name string `srdb:"name;indexed;comment:用户名"`
|
||||
Age int64 `srdb:"age;comment:年龄"`
|
||||
Email string `srdb:"email;indexed;comment:邮箱"`
|
||||
Score float64 `srdb:"score;comment:分数"`
|
||||
IsActive bool `srdb:"is_active;comment:是否激活"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 1. 从结构体生成 Field 列表
|
||||
fields, err := srdb.StructToFields(User{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 2. 创建 Schema
|
||||
schema := srdb.NewSchema("users", fields)
|
||||
|
||||
// 3. 创建表
|
||||
table, err := srdb.OpenTable(&srdb.TableOptions{
|
||||
Dir: "./data/users",
|
||||
Name: schema.Name,
|
||||
Fields: schema.Fields,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer table.Close()
|
||||
|
||||
// 4. 插入数据
|
||||
err = table.Insert(map[string]any{
|
||||
"name": "张三",
|
||||
"age": int64(25),
|
||||
"email": "zhangsan@example.com",
|
||||
"score": 95.5,
|
||||
"is_active": true,
|
||||
})
|
||||
|
||||
// 5. 查询数据(自动使用索引)
|
||||
rows, _ := table.Query().Eq("email", "zhangsan@example.com").Rows()
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
data := rows.Row().Data()
|
||||
// 处理数据...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 运行示例
|
||||
|
||||
```bash
|
||||
cd examples/struct_schema
|
||||
go run main.go
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
1. **类型安全**: 使用结构体定义,编译时检查类型
|
||||
2. **简洁**: 不需要手动创建 Field 列表
|
||||
3. **可维护**: 结构体和 Schema 在一起,便于维护
|
||||
4. **灵活**: 支持 tag 自定义字段属性
|
||||
5. **自动索引**: 通过 `indexed` tag 自动创建索引
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 只有导出的字段(首字母大写)会被包含
|
||||
2. 使用 `srdb:"-"` 可以忽略字段
|
||||
3. 如果不指定字段名,默认使用小写的字段名
|
||||
4. 不支持嵌套结构体(需要手动展开)
|
||||
5. 不支持切片、map 等复杂类型
|
||||
1
examples/struct_schema/data/users/CURRENT
Normal file
1
examples/struct_schema/data/users/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/struct_schema/data/users/MANIFEST-000001
Normal file
BIN
examples/struct_schema/data/users/MANIFEST-000001
Normal file
Binary file not shown.
40
examples/struct_schema/data/users/schema.json
Normal file
40
examples/struct_schema/data/users/schema.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"version": 1,
|
||||
"timestamp": 1760013049,
|
||||
"checksum": "fad0aaaa6fc5b94364c0c0b07a7567f71e8bb4f8ed8456c1954cffa7538a6a42",
|
||||
"schema": {
|
||||
"Name": "users",
|
||||
"Fields": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": 2,
|
||||
"Indexed": true,
|
||||
"Comment": "用户名"
|
||||
},
|
||||
{
|
||||
"Name": "age",
|
||||
"Type": 1,
|
||||
"Indexed": false,
|
||||
"Comment": "年龄"
|
||||
},
|
||||
{
|
||||
"Name": "email",
|
||||
"Type": 2,
|
||||
"Indexed": true,
|
||||
"Comment": "邮箱"
|
||||
},
|
||||
{
|
||||
"Name": "score",
|
||||
"Type": 3,
|
||||
"Indexed": false,
|
||||
"Comment": "分数"
|
||||
},
|
||||
{
|
||||
"Name": "is_active",
|
||||
"Type": 4,
|
||||
"Indexed": false,
|
||||
"Comment": "是否激活"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/struct_schema/data/users/wal/CURRENT
Normal file
1
examples/struct_schema/data/users/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
3
|
||||
130
examples/struct_schema/main.go
Normal file
130
examples/struct_schema/main.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"code.tczkiot.com/wlw/srdb"
|
||||
)
|
||||
|
||||
// User 用户结构体
|
||||
// 使用 struct tags 定义 Schema
|
||||
type User struct {
|
||||
Name string `srdb:"name;indexed;comment:用户名"`
|
||||
Age int64 `srdb:"age;comment:年龄"`
|
||||
Email string `srdb:"email;indexed;comment:邮箱"`
|
||||
Score float64 `srdb:"score;comment:分数"`
|
||||
IsActive bool `srdb:"is_active;comment:是否激活"`
|
||||
Internal string `srdb:"-"` // 不会被包含在 Schema 中
|
||||
}
|
||||
|
||||
// Product 产品结构体
|
||||
// 不使用 srdb tag,字段名会自动转为 snake_case
|
||||
type Product struct {
|
||||
ProductID string // 字段名: product_id
|
||||
ProductName string // 字段名: product_name
|
||||
Price int64 // 字段名: price
|
||||
InStock bool // 字段名: in_stock
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 示例 1: 使用结构体创建 Schema
|
||||
fmt.Println("=== 示例 1: 从结构体创建 Schema ===")
|
||||
|
||||
// 从 User 结构体生成 Field 列表
|
||||
fields, err := srdb.StructToFields(User{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 创建 Schema
|
||||
schema := srdb.NewSchema("users", fields)
|
||||
|
||||
// 打印 Schema 信息
|
||||
fmt.Printf("Schema 名称: %s\n", schema.Name)
|
||||
fmt.Printf("字段数量: %d\n", len(schema.Fields))
|
||||
fmt.Println("\n字段列表:")
|
||||
for _, field := range schema.Fields {
|
||||
indexed := ""
|
||||
if field.Indexed {
|
||||
indexed = " [索引]"
|
||||
}
|
||||
fmt.Printf(" - %s (%s)%s: %s\n",
|
||||
field.Name, field.Type.String(), indexed, field.Comment)
|
||||
}
|
||||
|
||||
// 示例 2: 使用 Schema 创建表
|
||||
fmt.Println("\n=== 示例 2: 使用 Schema 创建表 ===")
|
||||
|
||||
table, err := srdb.OpenTable(&srdb.TableOptions{
|
||||
Dir: "./data/users",
|
||||
Name: schema.Name,
|
||||
Fields: schema.Fields,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer table.Close()
|
||||
|
||||
// 插入数据
|
||||
err = table.Insert(map[string]any{
|
||||
"name": "张三",
|
||||
"age": int64(25),
|
||||
"email": "zhangsan@example.com",
|
||||
"score": 95.5,
|
||||
"is_active": true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = table.Insert(map[string]any{
|
||||
"name": "李四",
|
||||
"age": int64(30),
|
||||
"email": "lisi@example.com",
|
||||
"score": 88.0,
|
||||
"is_active": true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ 插入 2 条数据")
|
||||
|
||||
// 查询数据
|
||||
rows, err := table.Query().Eq("email", "zhangsan@example.com").Rows()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
fmt.Println("\n查询结果 (email = zhangsan@example.com):")
|
||||
for rows.Next() {
|
||||
data := rows.Row().Data()
|
||||
fmt.Printf(" 姓名: %s, 年龄: %v, 邮箱: %s, 分数: %v, 激活: %v\n",
|
||||
data["name"], data["age"], data["email"], data["score"], data["is_active"])
|
||||
}
|
||||
|
||||
// 示例 3: 使用默认字段名(snake_case)
|
||||
fmt.Println("\n=== 示例 3: 使用默认字段名(snake_case)===")
|
||||
|
||||
productFields, err := srdb.StructToFields(Product{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Product 字段(使用默认 snake_case 名称):")
|
||||
for _, field := range productFields {
|
||||
fmt.Printf(" - %s (%s)\n", field.Name, field.Type.String())
|
||||
}
|
||||
|
||||
// 示例 4: 获取索引字段
|
||||
fmt.Println("\n=== 示例 4: 获取索引字段 ===")
|
||||
indexedFields := schema.GetIndexedFields()
|
||||
fmt.Printf("User Schema 中的索引字段(共 %d 个):\n", len(indexedFields))
|
||||
for _, field := range indexedFields {
|
||||
fmt.Printf(" - %s: %s\n", field.Name, field.Comment)
|
||||
}
|
||||
|
||||
fmt.Println("\n✓ 所有示例执行成功!")
|
||||
}
|
||||
Reference in New Issue
Block a user