功能:增强 Schema 系统和添加新示例
- 扩展 Schema 支持更多数据类型(Duration、URL、JSON 等) - 优化 SSTable 编码解码性能 - 添加多个新示例程序: - all_types: 展示所有支持的数据类型 - new_types: 演示新增类型的使用 - struct_tags: 展示结构体标签功能 - time_duration: 时间和持续时间处理示例 - 完善测试用例和文档 - 优化代码结构和错误处理
This commit is contained in:
1
examples/all_types/data/sensors/CURRENT
Normal file
1
examples/all_types/data/sensors/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/all_types/data/sensors/MANIFEST-000001
Normal file
BIN
examples/all_types/data/sensors/MANIFEST-000001
Normal file
Binary file not shown.
34
examples/all_types/data/sensors/schema.json
Normal file
34
examples/all_types/data/sensors/schema.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"version": 1,
|
||||
"timestamp": 1760028726,
|
||||
"checksum": "89e806ac5fbd5839456b425a2293097529b3edac6360f97afb06a1211d4fd53b",
|
||||
"schema": {
|
||||
"Name": "sensors",
|
||||
"Fields": [
|
||||
{
|
||||
"Name": "device_id",
|
||||
"Type": 9,
|
||||
"Indexed": true,
|
||||
"Comment": "设备ID"
|
||||
},
|
||||
{
|
||||
"Name": "temperature",
|
||||
"Type": 11,
|
||||
"Indexed": false,
|
||||
"Comment": "温度(摄氏度)"
|
||||
},
|
||||
{
|
||||
"Name": "humidity",
|
||||
"Type": 7,
|
||||
"Indexed": false,
|
||||
"Comment": "湿度(0-100)"
|
||||
},
|
||||
{
|
||||
"Name": "online",
|
||||
"Type": 14,
|
||||
"Indexed": false,
|
||||
"Comment": "是否在线"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/all_types/data/sensors/wal/CURRENT
Normal file
1
examples/all_types/data/sensors/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
2
|
||||
98
examples/all_types/main.go
Normal file
98
examples/all_types/main.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"code.tczkiot.com/wlw/srdb"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== SRDB 完整类型系统示例 ===\n")
|
||||
|
||||
// 清理旧数据
|
||||
os.RemoveAll("./data")
|
||||
|
||||
// 示例 1: 展示所有类型
|
||||
fmt.Println("=== 示例 1: 展示所有 14 种支持的类型 ===")
|
||||
showAllTypes()
|
||||
|
||||
// 示例 2: 实际应用场景
|
||||
fmt.Println("\n=== 示例 2: 实际应用 - 物联网传感器数据 ===")
|
||||
sensorDataExample()
|
||||
|
||||
fmt.Println("\n✓ 所有示例执行成功!")
|
||||
}
|
||||
|
||||
func showAllTypes() {
|
||||
// 展示类型映射
|
||||
types := []struct {
|
||||
name string
|
||||
goType string
|
||||
srdbType srdb.FieldType
|
||||
}{
|
||||
{"有符号整数", "int", srdb.Int},
|
||||
{"8位有符号整数", "int8", srdb.Int8},
|
||||
{"16位有符号整数", "int16", srdb.Int16},
|
||||
{"32位有符号整数", "int32", srdb.Int32},
|
||||
{"64位有符号整数", "int64", srdb.Int64},
|
||||
{"无符号整数", "uint", srdb.Uint},
|
||||
{"8位无符号整数", "uint8 (byte)", srdb.Uint8},
|
||||
{"16位无符号整数", "uint16", srdb.Uint16},
|
||||
{"32位无符号整数", "uint32", srdb.Uint32},
|
||||
{"64位无符号整数", "uint64", srdb.Uint64},
|
||||
{"单精度浮点", "float32", srdb.Float32},
|
||||
{"双精度浮点", "float64", srdb.Float64},
|
||||
{"字符串", "string", srdb.String},
|
||||
{"布尔", "bool", srdb.Bool},
|
||||
}
|
||||
|
||||
fmt.Println("SRDB 类型系统(精确映射到 Go 基础类型):\n")
|
||||
for i, t := range types {
|
||||
fmt.Printf("%2d. %-20s %-20s -> %s\n", i+1, t.name, t.goType, t.srdbType.String())
|
||||
}
|
||||
}
|
||||
|
||||
func sensorDataExample() {
|
||||
// 创建 Schema
|
||||
schema, err := srdb.NewSchema("sensors", []srdb.Field{
|
||||
{Name: "device_id", Type: srdb.Uint32, Indexed: true, Comment: "设备ID"},
|
||||
{Name: "temperature", Type: srdb.Float32, Comment: "温度(摄氏度)"},
|
||||
{Name: "humidity", Type: srdb.Uint8, Comment: "湿度(0-100)"},
|
||||
{Name: "online", Type: srdb.Bool, Comment: "是否在线"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
table, err := srdb.OpenTable(&srdb.TableOptions{
|
||||
Dir: "./data/sensors",
|
||||
Name: schema.Name,
|
||||
Fields: schema.Fields,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer table.Close()
|
||||
|
||||
// 插入数据
|
||||
sensors := []map[string]any{
|
||||
{"device_id": uint32(1001), "temperature": float32(23.5), "humidity": uint8(65), "online": true},
|
||||
{"device_id": uint32(1002), "temperature": float32(18.2), "humidity": uint8(72), "online": true},
|
||||
{"device_id": uint32(1003), "temperature": float32(25.8), "humidity": uint8(58), "online": false},
|
||||
}
|
||||
|
||||
err = table.Insert(sensors)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("✓ 插入 %d 个传感器数据\n", len(sensors))
|
||||
fmt.Println("\n类型优势演示:")
|
||||
fmt.Println(" - device_id 使用 uint32 (节省空间,支持 42 亿设备)")
|
||||
fmt.Println(" - temperature 使用 float32 (单精度足够,节省 50% 空间)")
|
||||
fmt.Println(" - humidity 使用 uint8 (0-100 范围,仅需 1 字节)")
|
||||
fmt.Println(" - online 使用 bool (语义清晰)")
|
||||
}
|
||||
@@ -56,8 +56,8 @@ func example1() {
|
||||
fmt.Println("=== 示例 1: 插入单个 map ===")
|
||||
|
||||
schema, err := srdb.NewSchema("users", []srdb.Field{
|
||||
{Name: "name", Type: srdb.FieldTypeString, Comment: "用户名"},
|
||||
{Name: "age", Type: srdb.FieldTypeInt64, Comment: "年龄"},
|
||||
{Name: "name", Type: srdb.String, Comment: "用户名"},
|
||||
{Name: "age", Type: srdb.Int64, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -93,9 +93,9 @@ func example2() {
|
||||
fmt.Println("=== 示例 2: 批量插入 map 切片 ===")
|
||||
|
||||
schema, err := srdb.NewSchema("users", []srdb.Field{
|
||||
{Name: "name", Type: srdb.FieldTypeString},
|
||||
{Name: "age", Type: srdb.FieldTypeInt64},
|
||||
{Name: "email", Type: srdb.FieldTypeString, Indexed: true},
|
||||
{Name: "name", Type: srdb.String},
|
||||
{Name: "age", Type: srdb.Int64},
|
||||
{Name: "email", Type: srdb.String, Indexed: true},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -141,10 +141,10 @@ func example3() {
|
||||
fmt.Println("=== 示例 3: 插入单个结构体 ===")
|
||||
|
||||
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},
|
||||
{Name: "name", Type: srdb.String},
|
||||
{Name: "age", Type: srdb.Int64},
|
||||
{Name: "email", Type: srdb.String},
|
||||
{Name: "is_active", Type: srdb.Bool},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -185,10 +185,10 @@ func example4() {
|
||||
fmt.Println("=== 示例 4: 批量插入结构体切片 ===")
|
||||
|
||||
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},
|
||||
{Name: "name", Type: srdb.String},
|
||||
{Name: "age", Type: srdb.Int64},
|
||||
{Name: "email", Type: srdb.String},
|
||||
{Name: "is_active", Type: srdb.Bool},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -235,10 +235,10 @@ func example5() {
|
||||
fmt.Println("=== 示例 5: 批量插入结构体指针切片 ===")
|
||||
|
||||
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},
|
||||
{Name: "name", Type: srdb.String},
|
||||
{Name: "age", Type: srdb.Int64},
|
||||
{Name: "email", Type: srdb.String},
|
||||
{Name: "is_active", Type: srdb.Bool},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -278,10 +278,10 @@ func example6() {
|
||||
fmt.Println("=== 示例 6: 使用 snake_case 自动转换 ===")
|
||||
|
||||
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: "是否有货"},
|
||||
{Name: "product_id", Type: srdb.String, Comment: "产品ID"},
|
||||
{Name: "product_name", Type: srdb.String, Comment: "产品名称"},
|
||||
{Name: "price", Type: srdb.Float64, Comment: "价格"},
|
||||
{Name: "in_stock", Type: srdb.Bool, Comment: "是否有货"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
129
examples/new_types/README.md
Normal file
129
examples/new_types/README.md
Normal 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 类型保证金融数据精确性
|
||||
1
examples/new_types/data/api_logs/CURRENT
Normal file
1
examples/new_types/data/api_logs/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/new_types/data/api_logs/MANIFEST-000001
Normal file
BIN
examples/new_types/data/api_logs/MANIFEST-000001
Normal file
Binary file not shown.
31
examples/new_types/data/api_logs/schema.json
Normal file
31
examples/new_types/data/api_logs/schema.json
Normal 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": "响应时间(毫秒)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/new_types/data/api_logs/wal/CURRENT
Normal file
1
examples/new_types/data/api_logs/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
2
|
||||
15
examples/new_types/go.mod
Normal file
15
examples/new_types/go.mod
Normal 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
|
||||
)
|
||||
6
examples/new_types/go.sum
Normal file
6
examples/new_types/go.sum
Normal 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
378
examples/new_types/main.go
Normal 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())
|
||||
}
|
||||
132
examples/struct_tags/README.md
Normal file
132
examples/struct_tags/README.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Struct Tags 示例
|
||||
|
||||
本示例展示如何使用 Go struct tags 来定义 SRDB Schema,包括完整的 nullable 支持。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### Struct Tag 格式
|
||||
|
||||
SRDB 支持以下 struct tag 格式:
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
// 基本格式
|
||||
Name string `srdb:"name"`
|
||||
|
||||
// 指定索引
|
||||
Email string `srdb:"email;indexed"`
|
||||
|
||||
// 标记为可空
|
||||
Bio string `srdb:"bio;nullable"`
|
||||
|
||||
// 可空 + 索引
|
||||
Phone string `srdb:"phone;nullable;indexed"`
|
||||
|
||||
// 完整格式
|
||||
Age int64 `srdb:"age;indexed;nullable;comment:用户年龄"`
|
||||
|
||||
// 忽略字段
|
||||
TempData string `srdb:"-"`
|
||||
}
|
||||
```
|
||||
|
||||
### Tag 说明
|
||||
|
||||
- **字段名**: 第一部分指定数据库字段名(可选,默认自动转换为 snake_case)
|
||||
- **indexed**: 标记该字段需要建立索引
|
||||
- **nullable**: 标记该字段允许 NULL 值
|
||||
- **comment**: 指定字段注释
|
||||
- **-**: 忽略该字段(不包含在 Schema 中)
|
||||
|
||||
## 运行示例
|
||||
|
||||
```bash
|
||||
cd examples/struct_tags
|
||||
go run main.go
|
||||
```
|
||||
|
||||
## 示例输出
|
||||
|
||||
```
|
||||
=== SRDB Struct Tags Example ===
|
||||
|
||||
1. 从结构体生成 Schema
|
||||
Schema 名称: users
|
||||
字段数量: 8
|
||||
|
||||
字段详情:
|
||||
- username: Type=string, Indexed=true, Nullable=false, Comment="用户名(索引)"
|
||||
- age: Type=int64, Indexed=false, Nullable=false, Comment="年龄"
|
||||
- email: Type=string, Indexed=false, Nullable=true, Comment="邮箱(可选)"
|
||||
- phone_number: Type=string, Indexed=true, Nullable=true, Comment="手机号(可空且索引)"
|
||||
- bio: Type=string, Indexed=false, Nullable=true, Comment="个人简介(可选)"
|
||||
- avatar: Type=string, Indexed=false, Nullable=true, Comment="头像 URL(可选)"
|
||||
- balance: Type=decimal, Indexed=false, Nullable=true, Comment="账户余额(可空)"
|
||||
- is_active: Type=bool, Indexed=false, Nullable=false, Comment="是否激活"
|
||||
|
||||
2. 创建数据库和表
|
||||
✓ 表创建成功
|
||||
|
||||
3. 插入完整数据
|
||||
✓ 插入用户 alice(完整数据)
|
||||
|
||||
4. 插入部分数据(可选字段为 NULL)
|
||||
✓ 插入用户 bob(email、bio、balance 为 NULL)
|
||||
|
||||
5. 测试必填字段不能为 NULL
|
||||
✓ 符合预期的错误: field username: NULL value not allowed (field is not nullable)
|
||||
|
||||
6. 查询所有用户
|
||||
用户: alice, 邮箱: alice@example.com, 余额: 1000.5
|
||||
用户: bob, 邮箱: <NULL>, 余额: <NULL>
|
||||
|
||||
7. 按索引字段查询(username='alice')
|
||||
找到用户: alice, 年龄: 25
|
||||
|
||||
✅ 所有操作完成!
|
||||
```
|
||||
|
||||
## 自动字段名转换
|
||||
|
||||
如果不指定字段名,会自动将结构体字段名转换为 snake_case:
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
UserName string // -> user_name
|
||||
EmailAddress string // -> email_address
|
||||
IsActive bool // -> is_active
|
||||
HTTPServer string // -> http_server
|
||||
}
|
||||
```
|
||||
|
||||
## Nullable 支持说明
|
||||
|
||||
1. **必填字段**(默认):不能插入 NULL 值,会返回错误
|
||||
2. **可选字段**(nullable=true):可以插入 NULL 值
|
||||
3. **查询结果**:NULL 值会以 `nil` 形式返回
|
||||
4. **验证时机**:在 `Insert()` 时自动验证
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **对可选字段使用 nullable**
|
||||
```go
|
||||
Email string `srdb:"email;nullable"` // ✓ 推荐
|
||||
Email string `srdb:"email"` // ✗ 如果是可选的,应该标记 nullable
|
||||
```
|
||||
|
||||
2. **对查询频繁的字段建立索引**
|
||||
```go
|
||||
Username string `srdb:"username;indexed"` // ✓ 查询键
|
||||
Bio string `srdb:"bio"` // ✓ 不常查询的字段
|
||||
```
|
||||
|
||||
3. **组合使用 nullable 和 indexed**
|
||||
```go
|
||||
Phone string `srdb:"phone;nullable;indexed"` // ✓ 可选但需要索引查询
|
||||
```
|
||||
|
||||
4. **为可选字段标记 nullable**
|
||||
```go
|
||||
Avatar string `srdb:"avatar;nullable"` // ✓ 值类型 + nullable
|
||||
Balance decimal.Decimal `srdb:"balance;nullable"` // ✓ 所有类型都支持 nullable
|
||||
```
|
||||
10
examples/struct_tags/data/database.meta
Normal file
10
examples/struct_tags/data/database.meta
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 1,
|
||||
"tables": [
|
||||
{
|
||||
"name": "users",
|
||||
"dir": "users",
|
||||
"created_at": 1760032030
|
||||
}
|
||||
]
|
||||
}
|
||||
1
examples/struct_tags/data/users/CURRENT
Normal file
1
examples/struct_tags/data/users/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000001
|
||||
BIN
examples/struct_tags/data/users/MANIFEST-000001
Normal file
BIN
examples/struct_tags/data/users/MANIFEST-000001
Normal file
Binary file not shown.
66
examples/struct_tags/data/users/schema.json
Normal file
66
examples/struct_tags/data/users/schema.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"version": 1,
|
||||
"timestamp": 1760032030,
|
||||
"checksum": "1fdebae19ecf3cfcecddfa11ddcfc9c4b6656e1fd2477df8849639c108f4ada1",
|
||||
"schema": {
|
||||
"Name": "users",
|
||||
"Fields": [
|
||||
{
|
||||
"Name": "username",
|
||||
"Type": 13,
|
||||
"Indexed": true,
|
||||
"Nullable": false,
|
||||
"Comment": "用户名(索引)"
|
||||
},
|
||||
{
|
||||
"Name": "age",
|
||||
"Type": 5,
|
||||
"Indexed": false,
|
||||
"Nullable": false,
|
||||
"Comment": "年龄"
|
||||
},
|
||||
{
|
||||
"Name": "email",
|
||||
"Type": 13,
|
||||
"Indexed": false,
|
||||
"Nullable": true,
|
||||
"Comment": "邮箱(可选)"
|
||||
},
|
||||
{
|
||||
"Name": "phone_number",
|
||||
"Type": 13,
|
||||
"Indexed": true,
|
||||
"Nullable": true,
|
||||
"Comment": "手机号(可空且索引)"
|
||||
},
|
||||
{
|
||||
"Name": "bio",
|
||||
"Type": 13,
|
||||
"Indexed": false,
|
||||
"Nullable": true,
|
||||
"Comment": "个人简介(可选)"
|
||||
},
|
||||
{
|
||||
"Name": "avatar",
|
||||
"Type": 13,
|
||||
"Indexed": false,
|
||||
"Nullable": true,
|
||||
"Comment": "头像 URL(可选)"
|
||||
},
|
||||
{
|
||||
"Name": "balance",
|
||||
"Type": 17,
|
||||
"Indexed": false,
|
||||
"Nullable": true,
|
||||
"Comment": "账户余额(可空)"
|
||||
},
|
||||
{
|
||||
"Name": "is_active",
|
||||
"Type": 14,
|
||||
"Indexed": false,
|
||||
"Nullable": false,
|
||||
"Comment": "是否激活"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1
examples/struct_tags/data/users/wal/CURRENT
Normal file
1
examples/struct_tags/data/users/wal/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
2
|
||||
15
examples/struct_tags/go.mod
Normal file
15
examples/struct_tags/go.mod
Normal file
@@ -0,0 +1,15 @@
|
||||
module code.tczkiot.com/wlw/srdb/examples/struct_tags
|
||||
|
||||
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
|
||||
)
|
||||
6
examples/struct_tags/go.sum
Normal file
6
examples/struct_tags/go.sum
Normal 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=
|
||||
176
examples/struct_tags/main.go
Normal file
176
examples/struct_tags/main.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"code.tczkiot.com/wlw/srdb"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// User 用户结构体,展示 struct tag 的完整使用
|
||||
type User struct {
|
||||
// 基本字段(必填)
|
||||
Username string `srdb:"username;indexed;comment:用户名(索引)"`
|
||||
Age int64 `srdb:"age;comment:年龄"`
|
||||
|
||||
// 可选字段(nullable)
|
||||
Email string `srdb:"email;nullable;comment:邮箱(可选)"`
|
||||
PhoneNumber string `srdb:"phone_number;nullable;indexed;comment:手机号(可空且索引)"`
|
||||
Bio string `srdb:"bio;nullable;comment:个人简介(可选)"`
|
||||
Avatar string `srdb:"avatar;nullable;comment:头像 URL(可选)"`
|
||||
|
||||
// 财务字段
|
||||
Balance decimal.Decimal `srdb:"balance;nullable;comment:账户余额(可空)"`
|
||||
|
||||
// 布尔字段
|
||||
IsActive bool `srdb:"is_active;comment:是否激活"`
|
||||
|
||||
// 忽略字段
|
||||
internalData string `srdb:"-"` // 未导出字段会自动忽略
|
||||
TempData string `srdb:"-"` // 使用 "-" 显式忽略导出字段
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== SRDB Struct Tags Example ===\n")
|
||||
|
||||
// 1. 从结构体生成 Schema
|
||||
fmt.Println("1. 从结构体生成 Schema")
|
||||
fields, err := srdb.StructToFields(User{})
|
||||
if err != nil {
|
||||
log.Fatalf("StructToFields failed: %v", err)
|
||||
}
|
||||
|
||||
schema, err := srdb.NewSchema("users", fields)
|
||||
if err != nil {
|
||||
log.Fatalf("NewSchema failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Schema 名称: %s\n", schema.Name)
|
||||
fmt.Printf("字段数量: %d\n\n", len(schema.Fields))
|
||||
|
||||
// 打印所有字段
|
||||
fmt.Println("字段详情:")
|
||||
for _, field := range schema.Fields {
|
||||
fmt.Printf(" - %s: Type=%s, Indexed=%v, Nullable=%v",
|
||||
field.Name, field.Type, field.Indexed, field.Nullable)
|
||||
if field.Comment != "" {
|
||||
fmt.Printf(", Comment=%q", field.Comment)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 2. 创建数据库和表
|
||||
fmt.Println("2. 创建数据库和表")
|
||||
db, err := srdb.Open("./data")
|
||||
if err != nil {
|
||||
log.Fatalf("Open database failed: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
table, err := db.CreateTable("users", schema)
|
||||
if err != nil {
|
||||
log.Fatalf("CreateTable failed: %v", err)
|
||||
}
|
||||
fmt.Println("✓ 表创建成功\n")
|
||||
|
||||
// 3. 插入数据 - 完整数据(所有字段都有值)
|
||||
fmt.Println("3. 插入完整数据")
|
||||
avatar1 := "https://example.com/avatar1.png"
|
||||
err = table.Insert(map[string]any{
|
||||
"username": "alice",
|
||||
"age": int64(25),
|
||||
"email": "alice@example.com",
|
||||
"phone_number": "13800138001",
|
||||
"bio": "Software Engineer",
|
||||
"avatar": avatar1,
|
||||
"balance": decimal.NewFromFloat(1000.50),
|
||||
"is_active": true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Insert failed: %v", err)
|
||||
}
|
||||
fmt.Println("✓ 插入用户 alice(完整数据)")
|
||||
|
||||
// 4. 插入数据 - 部分字段为 NULL
|
||||
fmt.Println("\n4. 插入部分数据(可选字段为 NULL)")
|
||||
err = table.Insert(map[string]any{
|
||||
"username": "bob",
|
||||
"age": int64(30),
|
||||
"email": nil, // NULL 值
|
||||
"bio": nil, // NULL 值
|
||||
"balance": nil, // NULL 值
|
||||
"is_active": true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Insert failed: %v", err)
|
||||
}
|
||||
fmt.Println("✓ 插入用户 bob(email、bio、balance 为 NULL)")
|
||||
|
||||
// 5. 插入数据 - 必填字段不能为 NULL
|
||||
fmt.Println("\n5. 测试必填字段不能为 NULL")
|
||||
err = table.Insert(map[string]any{
|
||||
"username": nil, // 尝试将必填字段设为 NULL
|
||||
"age": int64(28),
|
||||
"is_active": true,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("✓ 符合预期的错误: %v\n", err)
|
||||
} else {
|
||||
log.Fatal("应该返回错误,但成功了!")
|
||||
}
|
||||
|
||||
// 6. 查询所有数据
|
||||
fmt.Println("\n6. 查询所有用户")
|
||||
rows, err := table.Query().Rows()
|
||||
if err != nil {
|
||||
log.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
row := rows.Row()
|
||||
data := row.Data()
|
||||
|
||||
username := data["username"]
|
||||
email := data["email"]
|
||||
balance := data["balance"]
|
||||
|
||||
fmt.Printf(" 用户: %v", username)
|
||||
if email == nil {
|
||||
fmt.Printf(", 邮箱: <NULL>")
|
||||
} else {
|
||||
fmt.Printf(", 邮箱: %v", email)
|
||||
}
|
||||
if balance == nil {
|
||||
fmt.Printf(", 余额: <NULL>")
|
||||
} else {
|
||||
fmt.Printf(", 余额: %v", balance)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// 7. 按索引字段查询
|
||||
fmt.Println("\n7. 按索引字段查询(username='alice')")
|
||||
rows2, err := table.Query().Eq("username", "alice").Rows()
|
||||
if err != nil {
|
||||
log.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows2.Close()
|
||||
|
||||
if rows2.Next() {
|
||||
row := rows2.Row()
|
||||
data := row.Data()
|
||||
fmt.Printf(" 找到用户: %v, 年龄: %v\n", data["username"], data["age"])
|
||||
}
|
||||
|
||||
fmt.Println("\n✅ 所有操作完成!")
|
||||
fmt.Println("\nStruct Tag 使用总结:")
|
||||
fmt.Println(" - srdb:\"name\" # 指定字段名")
|
||||
fmt.Println(" - srdb:\"name;indexed\" # 字段名 + 索引")
|
||||
fmt.Println(" - srdb:\"name;nullable\" # 字段名 + 可空")
|
||||
fmt.Println(" - srdb:\"name;comment:注释\" # 字段名 + 注释")
|
||||
fmt.Println(" - srdb:\"name;indexed;nullable;comment:XX\" # 完整格式")
|
||||
fmt.Println(" - srdb:\"-\" # 忽略字段")
|
||||
}
|
||||
140
examples/time_duration/main.go
Normal file
140
examples/time_duration/main.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"code.tczkiot.com/wlw/srdb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== Testing Time and Duration Types ===\n")
|
||||
|
||||
// 1. 创建 Schema
|
||||
schema, err := srdb.NewSchema("events", []srdb.Field{
|
||||
{Name: "name", Type: srdb.String, Comment: "事件名称"},
|
||||
{Name: "created_at", Type: srdb.Time, Comment: "创建时间"},
|
||||
{Name: "duration", Type: srdb.Duration, Comment: "持续时间"},
|
||||
{Name: "count", Type: srdb.Int64, Comment: "计数"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("创建 Schema 失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ Schema 创建成功")
|
||||
fmt.Printf(" 字段数: %d\n", len(schema.Fields))
|
||||
for _, field := range schema.Fields {
|
||||
fmt.Printf(" - %s: %s (%s)\n", field.Name, field.Type.String(), field.Comment)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 2. 创建数据库和表
|
||||
os.RemoveAll("./test_time_data")
|
||||
db, err := srdb.Open("./test_time_data")
|
||||
if err != nil {
|
||||
log.Fatalf("打开数据库失败: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
db.Close()
|
||||
os.RemoveAll("./test_time_data")
|
||||
}()
|
||||
|
||||
table, err := db.CreateTable("events", schema)
|
||||
if err != nil {
|
||||
log.Fatalf("创建表失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ 表创建成功\n")
|
||||
|
||||
// 3. 插入数据(使用原生类型)
|
||||
now := time.Now()
|
||||
duration := 2 * time.Hour
|
||||
|
||||
err = table.Insert(map[string]any{
|
||||
"name": "event1",
|
||||
"created_at": now,
|
||||
"duration": duration,
|
||||
"count": int64(100),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("插入数据失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ 插入数据成功(使用原生类型)")
|
||||
fmt.Printf(" 时间: %v\n", now)
|
||||
fmt.Printf(" 持续时间: %v\n", duration)
|
||||
fmt.Println()
|
||||
|
||||
// 4. 插入数据(使用字符串格式)
|
||||
err = table.Insert(map[string]any{
|
||||
"name": "event2",
|
||||
"created_at": now.Format(time.RFC3339),
|
||||
"duration": "1h30m",
|
||||
"count": int64(200),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("插入数据失败(字符串格式): %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ 插入数据成功(使用字符串格式)")
|
||||
fmt.Printf(" 时间字符串: %s\n", now.Format(time.RFC3339))
|
||||
fmt.Printf(" 持续时间字符串: 1h30m\n")
|
||||
fmt.Println()
|
||||
|
||||
// 5. 插入数据(使用 int64 格式)
|
||||
err = table.Insert(map[string]any{
|
||||
"name": "event3",
|
||||
"created_at": now.Unix(),
|
||||
"duration": int64(45 * time.Minute),
|
||||
"count": int64(300),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("插入数据失败(int64 格式): %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ 插入数据成功(使用 int64 格式)")
|
||||
fmt.Printf(" Unix 时间戳: %d\n", now.Unix())
|
||||
fmt.Printf(" 持续时间(纳秒): %d\n", int64(45*time.Minute))
|
||||
fmt.Println()
|
||||
|
||||
// 6. 查询数据
|
||||
fmt.Println("6. 查询所有数据")
|
||||
rows, err := table.Query().Rows()
|
||||
if err != nil {
|
||||
log.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
count := 0
|
||||
for rows.Next() {
|
||||
count++
|
||||
row := rows.Row()
|
||||
data := row.Data()
|
||||
|
||||
name := data["name"]
|
||||
createdAt := data["created_at"]
|
||||
dur := data["duration"]
|
||||
cnt := data["count"]
|
||||
|
||||
fmt.Printf(" [%d] 名称: %v\n", count, name)
|
||||
|
||||
// 验证类型
|
||||
if t, ok := createdAt.(time.Time); ok {
|
||||
fmt.Printf(" 时间: %v (类型: time.Time) ✓\n", t.Format(time.RFC3339))
|
||||
} else {
|
||||
fmt.Printf(" 时间: %v (类型: %T) ✗\n", createdAt, createdAt)
|
||||
}
|
||||
|
||||
if d, ok := dur.(time.Duration); ok {
|
||||
fmt.Printf(" 持续时间: %v (类型: time.Duration) ✓\n", d)
|
||||
} else {
|
||||
fmt.Printf(" 持续时间: %v (类型: %T) ✗\n", dur, dur)
|
||||
}
|
||||
|
||||
fmt.Printf(" 计数: %v\n\n", cnt)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ 所有测试完成! 共查询 %d 条记录\n", count)
|
||||
}
|
||||
106
examples/time_duration_simple/main.go
Normal file
106
examples/time_duration_simple/main.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"code.tczkiot.com/wlw/srdb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== Testing Time and Duration Types (Simple) ===\n")
|
||||
|
||||
// 1. 创建 Schema
|
||||
schema, err := srdb.NewSchema("events", []srdb.Field{
|
||||
{Name: "name", Type: srdb.String, Comment: "事件名称"},
|
||||
{Name: "created_at", Type: srdb.Time, Comment: "创建时间"},
|
||||
{Name: "duration", Type: srdb.Duration, Comment: "持续时间"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("创建 Schema 失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ Schema 创建成功")
|
||||
for _, field := range schema.Fields {
|
||||
fmt.Printf(" - %s: %s\n", field.Name, field.Type.String())
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// 2. 创建数据库和表
|
||||
os.RemoveAll("./test_data")
|
||||
db, err := srdb.Open("./test_data")
|
||||
if err != nil {
|
||||
log.Fatalf("打开数据库失败: %v", err)
|
||||
}
|
||||
|
||||
table, err := db.CreateTable("events", schema)
|
||||
if err != nil {
|
||||
log.Fatalf("创建表失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ 表创建成功\n")
|
||||
|
||||
// 3. 插入数据
|
||||
now := time.Now()
|
||||
duration := 2 * time.Hour
|
||||
|
||||
err = table.Insert(map[string]any{
|
||||
"name": "event1",
|
||||
"created_at": now,
|
||||
"duration": duration,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("插入数据失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ 插入数据成功")
|
||||
fmt.Printf(" 时间: %v\n", now.Format(time.RFC3339))
|
||||
fmt.Printf(" 持续时间: %v\n\n", duration)
|
||||
|
||||
// 4. 立即查询(从 MemTable)
|
||||
fmt.Println("4. 查询数据(从 MemTable)")
|
||||
rows, err := table.Query().Rows()
|
||||
if err != nil {
|
||||
log.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
success := true
|
||||
for rows.Next() {
|
||||
row := rows.Row()
|
||||
data := row.Data()
|
||||
|
||||
name := data["name"]
|
||||
createdAt := data["created_at"]
|
||||
dur := data["duration"]
|
||||
|
||||
fmt.Printf(" 名称: %v\n", name)
|
||||
|
||||
// 验证类型
|
||||
if t, ok := createdAt.(time.Time); ok {
|
||||
fmt.Printf(" 时间: %v (类型: time.Time) ✓\n", t.Format(time.RFC3339))
|
||||
} else {
|
||||
fmt.Printf(" 时间: %v (类型: %T) ✗ FAILED\n", createdAt, createdAt)
|
||||
success = false
|
||||
}
|
||||
|
||||
if d, ok := dur.(time.Duration); ok {
|
||||
fmt.Printf(" 持续时间: %v (类型: time.Duration) ✓\n", d)
|
||||
} else {
|
||||
fmt.Printf(" 持续时间: %v (类型: %T) ✗ FAILED\n", dur, dur)
|
||||
success = false
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
fmt.Println("\n✅ 测试通过! Time 和 Duration 类型正确保留")
|
||||
} else {
|
||||
fmt.Println("\n❌ 测试失败! 类型未正确保留")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 快速退出,不等待清理
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -24,20 +24,20 @@ func StartWebUI(dbPath string, addr string) {
|
||||
|
||||
// 创建示例 Schema
|
||||
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"},
|
||||
{Name: "name", Type: srdb.String, Indexed: true, Comment: "User name"},
|
||||
{Name: "email", Type: srdb.String, Indexed: false, Comment: "Email address"},
|
||||
{Name: "age", Type: srdb.Int64, Indexed: false, Comment: "Age"},
|
||||
{Name: "city", Type: srdb.String, Indexed: false, Comment: "City"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
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"},
|
||||
{Name: "product_name", Type: srdb.String, Indexed: true, Comment: "Product name"},
|
||||
{Name: "price", Type: srdb.Float64, Indexed: false, Comment: "Price"},
|
||||
{Name: "quantity", Type: srdb.Int64, Indexed: false, Comment: "Quantity"},
|
||||
{Name: "category", Type: srdb.String, Indexed: false, Comment: "Category"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -140,10 +140,10 @@ func autoInsertData(db *srdb.Database) {
|
||||
|
||||
if !hasLogs {
|
||||
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"},
|
||||
{Name: "group", Type: srdb.String, Indexed: true, Comment: "Log group (A-E)"},
|
||||
{Name: "timestamp", Type: srdb.String, Indexed: false, Comment: "Timestamp"},
|
||||
{Name: "data", Type: srdb.String, Indexed: false, Comment: "Random data"},
|
||||
{Name: "size_bytes", Type: srdb.Int64, Indexed: false, Comment: "Data size in bytes"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
Reference in New Issue
Block a user