功能:增强 Schema 系统和添加新示例

- 扩展 Schema 支持更多数据类型(Duration、URL、JSON 等)
- 优化 SSTable 编码解码性能
- 添加多个新示例程序:
  - all_types: 展示所有支持的数据类型
  - new_types: 演示新增类型的使用
  - struct_tags: 展示结构体标签功能
  - time_duration: 时间和持续时间处理示例
- 完善测试用例和文档
- 优化代码结构和错误处理
This commit is contained in:
2025-10-10 00:20:45 +08:00
parent 6d04487789
commit 77087d36c6
41 changed files with 3008 additions and 452 deletions

View File

@@ -0,0 +1,129 @@
# SRDB 新类型系统示例
本示例展示 SRDB 最新的类型系统特性,包括新增的 **Byte**、**Rune**、**Decimal** 类型以及 **Nullable** 支持。
## 新增特性
### 1. Byte 类型 (FieldTypeByte)
- **用途**: 存储 0-255 范围的小整数
- **适用场景**: HTTP 状态码、标志位、小范围枚举值
- **优势**: 仅占 1 字节,相比 int64 节省 87.5% 空间
### 2. Rune 类型 (FieldTypeRune)
- **用途**: 存储单个 Unicode 字符
- **适用场景**: 等级标识S/A/B/C、单字符代码、Unicode 字符
- **优势**: 语义清晰,支持所有 Unicode 字符
### 3. Decimal 类型 (FieldTypeDecimal)
- **用途**: 高精度十进制数值
- **适用场景**: 金融计算、科学计算、需要精确数值的场景
- **优势**: 无精度损失,避免浮点数误差
- **实现**: 使用 `github.com/shopspring/decimal`
### 4. Nullable 支持
- **用途**: 允许字段值为 NULL
- **适用场景**: 可选字段、区分"未填写"和"空值"
- **使用**: 在 Field 定义中设置 `Nullable: true`
## 完整类型系统
SRDB 现在支持 **17 种**数据类型:
| 类别 | 类型 | 说明 |
|------|------|------|
| 有符号整数 | int, int8, int16, int32, int64 | 5 种 |
| 无符号整数 | uint, uint8, uint16, uint32, uint64 | 5 种 |
| 浮点 | float32, float64 | 2 种 |
| 字符串 | string | 1 种 |
| 布尔 | bool | 1 种 |
| 特殊类型 | byte, rune, decimal | 3 种 |
## 运行示例
```bash
cd examples/new_types
go run main.go
```
## 示例说明
### 示例 1: Byte 类型API 日志)
演示使用 `byte` 类型存储 HTTP 状态码,节省存储空间。
```go
{Name: "status_code", Type: srdb.FieldTypeByte, Comment: "HTTP 状态码"}
```
### 示例 2: Rune 类型(用户等级)
演示使用 `rune` 类型存储等级字符S/A/B/C/D
```go
{Name: "level", Type: srdb.FieldTypeRune, Comment: "等级字符"}
```
### 示例 3: Decimal 类型(金融交易)
演示使用 `decimal` 类型进行精确的金融计算。
```go
{Name: "amount", Type: srdb.FieldTypeDecimal, Comment: "交易金额(高精度)"}
// 使用示例
amount := decimal.NewFromFloat(1234.56789012345)
fee := decimal.NewFromFloat(1.23)
total := amount.Add(fee) // 精确加法,无误差
```
### 示例 4: Nullable 支持(用户资料)
演示可选字段的使用,允许某些字段为 NULL。
```go
{Name: "email", Type: srdb.FieldTypeString, Nullable: true, Comment: "邮箱(可选)"}
// 插入数据时可以为 nil
{"username": "Bob", "email": nil} // email 为 NULL
```
### 示例 5: 完整类型系统
演示所有 17 种类型在同一个表中的使用。
## 类型优势对比
| 场景 | 旧方案 | 新方案 | 优势 |
|------|--------|--------|------|
| HTTP 状态码 | int64 (8 字节) | byte (1 字节) | 节省 87.5% 空间 |
| 等级标识 | string ("S") | rune ('S') | 更精确的语义 |
| 金融金额 | float64 (有误差) | decimal (无误差) | 精确计算 |
| 可选字段 | 空字符串 "" | NULL | 区分未填写和空值 |
## 注意事项
1. **Byte 和 Rune 的底层类型**
- `byte` 在 Go 中等同于 `uint8`
- `rune` 在 Go 中等同于 `int32`
- 但在 SRDB Schema 中作为独立类型,语义更清晰
2. **Decimal 的使用**
- 需要导入 `github.com/shopspring/decimal`
- 创建方式:`decimal.NewFromFloat()`, `decimal.NewFromString()`, `decimal.NewFromInt()`
- 运算方法:`Add()`, `Sub()`, `Mul()`, `Div()`
3. **Nullable 的使用**
- NULL 值在 Go 中表示为 `nil`
- 读取时需要检查值是否存在且不为 nil
- 非 Nullable 字段不允许为 NULL会在验证时报错
## 最佳实践
1. **选择合适的类型**
- 使用最小的整数类型来节省空间(如 uint8 而非 int64
- 金融计算必须使用 decimal避免 float64
- 可选字段使用 Nullable而非空字符串或特殊值
2. **性能优化**
- 小整数类型byte, int8, uint16可减少存储和传输开销
- 索引字段选择合适的类型(如 byte 类型的索引比 string 更高效)
3. **数据完整性**
- 必填字段设置 `Nullable: false`
- 使用类型系统保证数据正确性
- Decimal 类型保证金融数据精确性

View File

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

Binary file not shown.

View File

@@ -0,0 +1,31 @@
{
"version": 1,
"timestamp": 1760030838,
"checksum": "db36b32950014d2d1551c398e67a7c463fd04bdf98be5a9dec64675cdb0882af",
"schema": {
"Name": "api_logs",
"Fields": [
{
"Name": "endpoint",
"Type": 13,
"Indexed": false,
"Nullable": false,
"Comment": "API 端点"
},
{
"Name": "status_code",
"Type": 15,
"Indexed": false,
"Nullable": false,
"Comment": "HTTP 状态码(用 byte 节省空间)"
},
{
"Name": "response_time_ms",
"Type": 8,
"Indexed": false,
"Nullable": false,
"Comment": "响应时间(毫秒)"
}
]
}
}

View File

@@ -0,0 +1 @@
2

15
examples/new_types/go.mod Normal file
View File

@@ -0,0 +1,15 @@
module code.tczkiot.com/wlw/srdb/examples/new_types
go 1.24.0
replace code.tczkiot.com/wlw/srdb => ../..
require (
code.tczkiot.com/wlw/srdb v0.0.0-00010101000000-000000000000
github.com/shopspring/decimal v1.4.0
)
require (
github.com/edsrzf/mmap-go v1.1.0 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
)

View File

@@ -0,0 +1,6 @@
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

378
examples/new_types/main.go Normal file
View File

@@ -0,0 +1,378 @@
package main
import (
"fmt"
"log"
"os"
"code.tczkiot.com/wlw/srdb"
"github.com/shopspring/decimal"
)
func main() {
fmt.Println("=== SRDB 新类型系统示例 ===")
// 清理旧数据
os.RemoveAll("./data")
// 示例 1: Byte 类型 - 适用于状态码、标志位等小整数
fmt.Println("\n=== 示例 1: Byte 类型(状态码)===")
byteExample()
// 示例 2: Rune 类型 - 适用于单个字符、等级标识等
fmt.Println("\n=== 示例 2: Rune 类型(等级字符)===")
runeExample()
// 示例 3: Decimal 类型 - 适用于金融计算、精确数值
fmt.Println("\n=== 示例 3: Decimal 类型(金融数据)===")
decimalExample()
// 示例 4: Nullable 支持 - 允许字段为 NULL
fmt.Println("\n=== 示例 4: Nullable 支持 ===")
nullableExample()
// 示例 5: 完整类型系统 - 展示所有 17 种类型
fmt.Println("\n=== 示例 5: 完整类型系统17 种类型)===")
allTypesExample()
fmt.Println("\n✓ 所有示例执行成功!")
}
// byteExample 演示 Byte 类型的使用
func byteExample() {
// 创建 Schema - 使用 byte 类型存储状态码
schema, err := srdb.NewSchema("api_logs", []srdb.Field{
{Name: "endpoint", Type: srdb.String, Comment: "API 端点"},
{Name: "status_code", Type: srdb.Byte, Comment: "HTTP 状态码(用 byte 节省空间)"},
{Name: "response_time_ms", Type: srdb.Uint16, Comment: "响应时间(毫秒)"},
})
if err != nil {
log.Fatal(err)
}
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/api_logs",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 插入数据 - status_code 使用 byte 类型0-255
logs := []map[string]any{
{"endpoint": "/api/users", "status_code": uint8(200), "response_time_ms": uint16(45)},
{"endpoint": "/api/orders", "status_code": uint8(201), "response_time_ms": uint16(89)},
{"endpoint": "/api/products", "status_code": uint8(255), "response_time_ms": uint16(12)},
{"endpoint": "/api/auth", "status_code": uint8(128), "response_time_ms": uint16(234)},
}
err = table.Insert(logs)
if err != nil {
log.Fatal(err)
}
fmt.Printf("✓ 插入 %d 条 API 日志\n", len(logs))
fmt.Println("类型优势:")
fmt.Println(" - status_code 使用 byte (仅 1 字节,相比 int64 节省 87.5% 空间)")
fmt.Println(" - response_time_ms 使用 uint16 (0-65535ms 范围足够)")
// 查询数据
row, _ := table.Get(1)
fmt.Printf("\n查询结果: endpoint=%s, status_code=%d, response_time=%dms\n",
row.Data["endpoint"], row.Data["status_code"], row.Data["response_time_ms"])
}
// runeExample 演示 Rune 类型的使用
func runeExample() {
// 创建 Schema - 使用 rune 类型存储等级字符
schema, err := srdb.NewSchema("user_levels", []srdb.Field{
{Name: "username", Type: srdb.String, Indexed: true, Comment: "用户名"},
{Name: "level", Type: srdb.Rune, Comment: "等级字符 (S/A/B/C/D)"},
{Name: "score", Type: srdb.Uint32, Comment: "积分"},
})
if err != nil {
log.Fatal(err)
}
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/user_levels",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 插入数据 - level 使用 rune 类型存储单个字符
users := []map[string]any{
{"username": "Alice", "level": rune('S'), "score": uint32(9500)},
{"username": "Bob", "level": rune('A'), "score": uint32(7200)},
{"username": "Charlie", "level": rune('B'), "score": uint32(5800)},
{"username": "David", "level": rune('C'), "score": uint32(3400)},
}
err = table.Insert(users)
if err != nil {
log.Fatal(err)
}
fmt.Printf("✓ 插入 %d 个用户等级数据\n", len(users))
fmt.Println("类型优势:")
fmt.Println(" - level 使用 rune 存储单个字符(语义清晰)")
fmt.Println(" - 支持 Unicode 字符,如中文等级:'甲'、'乙'、'丙'")
// 查询数据
row, _ := table.Get(1)
levelRune := row.Data["level"].(rune)
fmt.Printf("\n查询结果: username=%s, level=%c, score=%d\n",
row.Data["username"], levelRune, row.Data["score"])
}
// decimalExample 演示 Decimal 类型的使用
func decimalExample() {
// 创建 Schema - 使用 decimal 类型存储金融数据
schema, err := srdb.NewSchema("transactions", []srdb.Field{
{Name: "tx_id", Type: srdb.String, Indexed: true, Comment: "交易ID"},
{Name: "amount", Type: srdb.Decimal, Comment: "交易金额(高精度)"},
{Name: "fee", Type: srdb.Decimal, Comment: "手续费(高精度)"},
{Name: "currency", Type: srdb.String, Comment: "货币类型"},
})
if err != nil {
log.Fatal(err)
}
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/transactions",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 插入数据 - amount 和 fee 使用 decimal 类型(无精度损失)
transactions := []map[string]any{
{
"tx_id": "TX001",
"amount": decimal.NewFromFloat(1234.56789012345), // 高精度
"fee": decimal.NewFromFloat(1.23),
"currency": "USD",
},
{
"tx_id": "TX002",
"amount": decimal.RequireFromString("9876.543210987654321"), // 字符串创建,更精确
"fee": decimal.NewFromFloat(9.88),
"currency": "EUR",
},
{
"tx_id": "TX003",
"amount": decimal.NewFromFloat(0.00000001), // 极小值
"fee": decimal.NewFromFloat(0.0000001),
"currency": "BTC",
},
}
err = table.Insert(transactions)
if err != nil {
log.Fatal(err)
}
fmt.Printf("✓ 插入 %d 笔交易\n", len(transactions))
fmt.Println("类型优势:")
fmt.Println(" - decimal 类型无精度损失(使用 shopspring/decimal")
fmt.Println(" - 适合金融计算、科学计算等需要精确数值的场景")
fmt.Println(" - 避免浮点数运算误差(如 0.1 + 0.2 ≠ 0.3")
// 查询数据并进行计算
row, _ := table.Get(1)
amount := row.Data["amount"].(decimal.Decimal)
fee := row.Data["fee"].(decimal.Decimal)
total := amount.Add(fee) // decimal 类型的精确加法
fmt.Printf("\n查询结果: tx_id=%s, currency=%s\n", row.Data["tx_id"], row.Data["currency"])
fmt.Printf(" 金额: %s\n", amount.String())
fmt.Printf(" 手续费: %s\n", fee.String())
fmt.Printf(" 总计: %s (精确计算,无误差)\n", total.String())
}
// nullableExample 演示 Nullable 支持
func nullableExample() {
// 创建 Schema - 某些字段允许为 NULL
schema, err := srdb.NewSchema("user_profiles", []srdb.Field{
{Name: "username", Type: srdb.String, Nullable: false, Comment: "用户名(必填)"},
{Name: "email", Type: srdb.String, Nullable: true, Comment: "邮箱(可选)"},
{Name: "age", Type: srdb.Uint8, Nullable: true, Comment: "年龄(可选)"},
{Name: "bio", Type: srdb.String, Nullable: true, Comment: "个人简介(可选)"},
})
if err != nil {
log.Fatal(err)
}
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/user_profiles",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 插入数据 - 可选字段可以为 nil
profiles := []map[string]any{
{
"username": "Alice",
"email": "alice@example.com",
"age": uint8(25),
"bio": "Hello, I'm Alice!",
},
{
"username": "Bob",
"email": nil, // email 为 NULL
"age": uint8(30),
"bio": "Software Engineer",
},
{
"username": "Charlie",
"email": "charlie@example.com",
"age": nil, // age 为 NULL
"bio": nil, // bio 为 NULL
},
}
err = table.Insert(profiles)
if err != nil {
log.Fatal(err)
}
fmt.Printf("✓ 插入 %d 个用户资料(包含 NULL 值)\n", len(profiles))
fmt.Println("类型优势:")
fmt.Println(" - Nullable 字段可以为 NULL区分'未填写'和'空字符串'")
fmt.Println(" - 非 Nullable 字段必须有值,保证数据完整性")
// 查询数据
for i := 1; i <= 3; i++ {
row, _ := table.Get(int64(i))
fmt.Printf("\n用户 %d: username=%s", i, row.Data["username"])
if email, ok := row.Data["email"]; ok && email != nil {
fmt.Printf(", email=%s", email)
} else {
fmt.Print(", email=NULL")
}
if age, ok := row.Data["age"]; ok && age != nil {
fmt.Printf(", age=%d", age)
} else {
fmt.Print(", age=NULL")
}
}
fmt.Println()
}
// allTypesExample 展示所有 17 种类型
func allTypesExample() {
schema, err := srdb.NewSchema("all_types_demo", []srdb.Field{
// 有符号整数类型 (5 种)
{Name: "f_int", Type: srdb.Int, Comment: "int"},
{Name: "f_int8", Type: srdb.Int8, Comment: "int8"},
{Name: "f_int16", Type: srdb.Int16, Comment: "int16"},
{Name: "f_int32", Type: srdb.Int32, Comment: "int32"},
{Name: "f_int64", Type: srdb.Int64, Comment: "int64"},
// 无符号整数类型 (5 种)
{Name: "f_uint", Type: srdb.Uint, Comment: "uint"},
{Name: "f_uint8", Type: srdb.Uint8, Comment: "uint8"},
{Name: "f_uint16", Type: srdb.Uint16, Comment: "uint16"},
{Name: "f_uint32", Type: srdb.Uint32, Comment: "uint32"},
{Name: "f_uint64", Type: srdb.Uint64, Comment: "uint64"},
// 浮点类型 (2 种)
{Name: "f_float32", Type: srdb.Float32, Comment: "float32"},
{Name: "f_float64", Type: srdb.Float64, Comment: "float64"},
// 字符串类型 (1 种)
{Name: "f_string", Type: srdb.String, Comment: "string"},
// 布尔类型 (1 种)
{Name: "f_bool", Type: srdb.Bool, Comment: "bool"},
// 特殊类型 (3 种)
{Name: "f_byte", Type: srdb.Byte, Comment: "byte (=uint8)"},
{Name: "f_rune", Type: srdb.Rune, Comment: "rune (=int32)"},
{Name: "f_decimal", Type: srdb.Decimal, Comment: "decimal (高精度)"},
})
if err != nil {
log.Fatal(err)
}
table, err := srdb.OpenTable(&srdb.TableOptions{
Dir: "./data/all_types",
Name: schema.Name,
Fields: schema.Fields,
})
if err != nil {
log.Fatal(err)
}
defer table.Close()
// 插入包含所有类型的数据
record := map[string]any{
// 有符号整数
"f_int": int(-12345),
"f_int8": int8(-128),
"f_int16": int16(-32768),
"f_int32": int32(-2147483648),
"f_int64": int64(-9223372036854775808),
// 无符号整数
"f_uint": uint(12345),
"f_uint8": uint8(255),
"f_uint16": uint16(65535),
"f_uint32": uint32(4294967295),
"f_uint64": uint64(18446744073709551615),
// 浮点
"f_float32": float32(3.14159),
"f_float64": float64(2.718281828459045),
// 字符串
"f_string": "Hello, SRDB! 你好!",
// 布尔
"f_bool": true,
// 特殊类型
"f_byte": byte(255),
"f_rune": rune('中'),
"f_decimal": decimal.NewFromFloat(123456.789012345),
}
err = table.Insert(record)
if err != nil {
log.Fatal(err)
}
fmt.Println("✓ 插入包含所有 17 种类型的数据")
fmt.Println("\nSRDB 完整类型系统:")
fmt.Println(" 有符号整数: int, int8, int16, int32, int64 (5 种)")
fmt.Println(" 无符号整数: uint, uint8, uint16, uint32, uint64 (5 种)")
fmt.Println(" 浮点类型: float32, float64 (2 种)")
fmt.Println(" 字符串类型: string (1 种)")
fmt.Println(" 布尔类型: bool (1 种)")
fmt.Println(" 特殊类型: byte, rune, decimal (3 种)")
fmt.Println(" 总计: 17 种类型")
// 查询并验证数据
row, _ := table.Get(1)
fmt.Println("\n数据验证")
fmt.Printf(" f_int=%d, f_int64=%d\n", row.Data["f_int"], row.Data["f_int64"])
fmt.Printf(" f_uint=%d, f_uint64=%d\n", row.Data["f_uint"], row.Data["f_uint64"])
fmt.Printf(" f_float32=%f, f_float64=%f\n", row.Data["f_float32"], row.Data["f_float64"])
fmt.Printf(" f_string=%s\n", row.Data["f_string"])
fmt.Printf(" f_bool=%v\n", row.Data["f_bool"])
fmt.Printf(" f_byte=%d, f_rune=%c\n", row.Data["f_byte"], row.Data["f_rune"])
fmt.Printf(" f_decimal=%s\n", row.Data["f_decimal"].(decimal.Decimal).String())
}