功能:增强 Schema 系统和添加新示例
- 扩展 Schema 支持更多数据类型(Duration、URL、JSON 等) - 优化 SSTable 编码解码性能 - 添加多个新示例程序: - all_types: 展示所有支持的数据类型 - new_types: 演示新增类型的使用 - struct_tags: 展示结构体标签功能 - time_duration: 时间和持续时间处理示例 - 完善测试用例和文档 - 优化代码结构和错误处理
This commit is contained in:
130
CLAUDE.md
130
CLAUDE.md
@@ -131,9 +131,9 @@ database_dir/
|
||||
|
||||
```go
|
||||
schema := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "email", Type: FieldTypeString, Indexed: true, Comment: "邮箱(索引)"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "email", Type: String, Indexed: true, Comment: "邮箱(索引)"},
|
||||
})
|
||||
|
||||
table, _ := db.CreateTable("users", schema)
|
||||
@@ -142,7 +142,112 @@ table, _ := db.CreateTable("users", schema)
|
||||
- Schema 在 `Insert()` 时强制验证类型和必填字段
|
||||
- 索引字段(`Indexed: true`)自动创建二级索引
|
||||
- Schema 持久化到 `table_dir/schema.json`,包含校验和防篡改
|
||||
- 支持的类型:`FieldTypeString`, `FieldTypeInt64`, `FieldTypeBool`, `FieldTypeFloat`
|
||||
- **支持的类型** (17 种,精确映射到 Go 基础类型):
|
||||
- **有符号整数** (5种): `Int`, `Int8`, `Int16`, `Int32`, `Int64`
|
||||
- **无符号整数** (5种): `Uint`, `Uint8`, `Uint16`, `Uint32`, `Uint64`
|
||||
- **浮点数** (2种): `Float32`, `Float64`
|
||||
- **字符串** (1种): `String`
|
||||
- **布尔** (1种): `Bool`
|
||||
- **特殊类型** (3种): `Byte` (独立类型,底层=uint8), `Rune` (独立类型,底层=int32), `Decimal` (高精度十进制,使用 shopspring/decimal)
|
||||
- **Nullable 支持**: 字段可标记为 `Nullable: true`,允许 NULL 值
|
||||
|
||||
### 类型系统详解
|
||||
|
||||
**精确类型映射**:
|
||||
从 v1.x 开始,SRDB 采用精确类型映射策略,每个 Go 基础类型都有对应的 FieldType。这带来以下优势:
|
||||
|
||||
1. **存储优化**: 使用 `uint8` (1 字节) 存储百分比,而不是 `int64` (8 字节)
|
||||
2. **语义明确**: `uint32` 表示设备ID,`float32` 表示传感器读数
|
||||
3. **类型安全**: 编译期和运行期双重类型检查
|
||||
|
||||
**类型转换规则**:
|
||||
|
||||
```go
|
||||
// 1. 相同类型:直接接受
|
||||
{Name: "age", Type: Int32}
|
||||
Insert(map[string]any{"age": int32(25)}) // ✓
|
||||
|
||||
// 2. 兼容类型:自动转换(有符号 ↔ 无符号,需非负)
|
||||
{Name: "count", Type: Int64}
|
||||
Insert(map[string]any{"count": uint32(100)}) // ✓
|
||||
|
||||
// 3. 类型提升:整数 → 浮点
|
||||
{Name: "ratio", Type: Float32}
|
||||
Insert(map[string]any{"ratio": int32(42)}) // ✓ 转为 42.0
|
||||
|
||||
// 4. JSON 兼容:float64 → 整数(需为整数值)
|
||||
{Name: "id", Type: Int64}
|
||||
Insert(map[string]any{"id": float64(123.0)}) // ✓ JSON 反序列化场景
|
||||
|
||||
// 5. 负数 → 无符号:拒绝
|
||||
{Name: "index", Type: Uint32}
|
||||
Insert(map[string]any{"index": int32(-1)}) // ✗ 错误
|
||||
```
|
||||
|
||||
**最佳实践**:
|
||||
|
||||
```go
|
||||
// 推荐:根据数据范围选择合适的类型
|
||||
schema, _ := NewSchema("sensors", []Field{
|
||||
{Name: "device_id", Type: Uint32}, // 0 ~ 42亿
|
||||
{Name: "temperature", Type: Float32}, // 单精度足够
|
||||
{Name: "humidity", Type: Uint8}, // 0-100
|
||||
{Name: "status", Type: Bool}, // 布尔状态
|
||||
})
|
||||
|
||||
// 避免:盲目使用 int64 和 float64
|
||||
schema, _ := NewSchema("sensors_bad", []Field{
|
||||
{Name: "device_id", Type: Int64}, // 浪费 4 字节
|
||||
{Name: "temperature", Type: Float64}, // 浪费 4 字节
|
||||
{Name: "humidity", Type: Int64}, // 浪费 7 字节!
|
||||
{Name: "status", Type: Int64}, // 浪费 7 字节!
|
||||
})
|
||||
```
|
||||
|
||||
**新增类型的使用场景**:
|
||||
|
||||
```go
|
||||
// Byte 类型 - 状态码、标志位
|
||||
{Name: "status_code", Type: Byte, Comment: "HTTP 状态码 (0-255)"}
|
||||
Insert(map[string]any{"status_code": uint8(200)}) // byte 和 uint8 底层相同
|
||||
|
||||
// Rune 类型 - 单个字符
|
||||
{Name: "grade", Type: Rune, Comment: "等级 (S/A/B/C)"}
|
||||
Insert(map[string]any{"grade": rune('A')}) // rune 和 int32 底层相同
|
||||
|
||||
// Decimal 类型 - 金融计算
|
||||
{Name: "amount", Type: Decimal, Comment: "交易金额"}
|
||||
import "github.com/shopspring/decimal"
|
||||
Insert(map[string]any{"amount": decimal.NewFromFloat(123.456)})
|
||||
|
||||
// Nullable 支持 - 可选字段
|
||||
{Name: "email", Type: String, Nullable: true, Comment: "邮箱(可选)"}
|
||||
Insert(map[string]any{"email": nil}) // 允许 NULL
|
||||
Insert(map[string]any{"email": "user@example.com"}) // 或有值
|
||||
```
|
||||
|
||||
**从结构体自动生成 Schema**:
|
||||
|
||||
```go
|
||||
type Sensor struct {
|
||||
DeviceID uint32 `srdb:"device_id;indexed;comment:设备ID"`
|
||||
Temperature float32 `srdb:"temperature;comment:温度"`
|
||||
Humidity uint8 `srdb:"humidity;comment:湿度 0-100"`
|
||||
Online bool `srdb:"online;comment:是否在线"`
|
||||
}
|
||||
|
||||
// 自动映射:
|
||||
// uint32 → Uint32
|
||||
// float32 → Float32
|
||||
// uint8 → Uint8 (也可用 byte)
|
||||
// bool → Bool
|
||||
fields, _ := StructToFields(Sensor{})
|
||||
schema, _ := NewSchema("sensors", fields)
|
||||
```
|
||||
|
||||
**参考示例**:
|
||||
- `examples/all_types/` - 展示所有 17 种类型的基本使用
|
||||
- `examples/new_types/` - 展示新增的 Byte、Rune、Decimal 类型和 Nullable 支持的实际应用场景
|
||||
|
||||
### Query Builder
|
||||
|
||||
@@ -292,12 +397,25 @@ Magic (4B) | Seq (8B) | Time (8B) | FieldCount (2B) |
|
||||
|
||||
- **Schema 是强制的**: 所有表必须定义 Schema,不再支持无 Schema 模式
|
||||
- **索引非自动创建**: 需要在 Schema 中显式标记 `Indexed: true`
|
||||
- **类型严格**: Schema 验证严格,int 和 int64 需要正确匹配
|
||||
- **类型名称简化**:
|
||||
- ⚠️ **重要变更**: 从 v2.0 开始,类型名称已简化,使用简短形式(如 `String` 而非 `FieldTypeString`)
|
||||
- 每个 Go 类型有对应的简短常量(如 `int32` → `Int32`,`string` → `String`)
|
||||
- 插入时类型会自动转换(如 `int` → `int32`),但需要注意负数不能转为无符号类型
|
||||
- **新增类型的使用**:
|
||||
- **Byte**: 虽然底层是 `uint8`,但在 Schema 中作为独立类型,语义更清晰(用于状态码、标志位等)
|
||||
- **Rune**: 虽然底层是 `int32`,但在 Schema 中作为独立类型,用于存储单个 Unicode 字符
|
||||
- **Decimal**: 必须使用 `github.com/shopspring/decimal` 包,用于金融计算等需要精确数值的场景
|
||||
- **Nullable 支持**:
|
||||
- 需要显式标记 `Nullable: true`,默认字段不允许 NULL
|
||||
- NULL 值在 Go 中表示为 `nil`
|
||||
- 读取时需要检查值是否存在且不为 nil
|
||||
- **选择合适的类型大小**:
|
||||
- 避免盲目使用 `Int64`/`Float64`,根据数据范围选择(如百分比用 `Uint8`,状态码用 `Byte`)
|
||||
- 过大的类型浪费存储和内存,影响性能
|
||||
- **Compaction 磁盘占用**: 合并期间旧文件和新文件共存,会暂时增加磁盘使用
|
||||
- **MemTable flush 异步**: 关闭时需要等待 immutable flush 完成
|
||||
- **mmap 虚拟内存**: 可能显示较大的虚拟内存使用(正常,OS 管理,不是实际 RAM)
|
||||
- **无 panic**: 所有 panic 已替换为错误返回,需要正确处理错误
|
||||
- **废弃代码**: `SSTableCompressionNone` 等常量已删除
|
||||
|
||||
## Web UI
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestCompactionBasic(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "value", Type: FieldTypeInt64},
|
||||
{Name: "value", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -226,7 +226,7 @@ func TestCompactionMerge(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "value", Type: FieldTypeString},
|
||||
{Name: "value", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -350,7 +350,7 @@ func BenchmarkCompaction(b *testing.B) {
|
||||
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "value", Type: FieldTypeString},
|
||||
{Name: "value", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
@@ -439,10 +439,10 @@ func TestCompactionQueryOrder(t *testing.T) {
|
||||
|
||||
// 创建 Schema - 包含多个字段以增加数据大小
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "data", Type: FieldTypeString},
|
||||
{Name: "timestamp", Type: FieldTypeInt64},
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "data", Type: String},
|
||||
{Name: "timestamp", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -41,8 +41,8 @@ func TestCreateTable(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
userSchema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: true, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -80,16 +80,16 @@ func TestMultipleTables(t *testing.T) {
|
||||
|
||||
// 创建多个表
|
||||
userSchema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: true, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
orderSchema, err := NewSchema("orders", []Field{
|
||||
{Name: "order_id", Type: FieldTypeString, Indexed: true, Comment: "订单ID"},
|
||||
{Name: "amount", Type: FieldTypeInt64, Indexed: true, Comment: "金额"},
|
||||
{Name: "order_id", Type: String, Indexed: true, Comment: "订单ID"},
|
||||
{Name: "amount", Type: Int64, Indexed: true, Comment: "金额"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -127,8 +127,8 @@ func TestTableOperations(t *testing.T) {
|
||||
|
||||
// 创建表
|
||||
userSchema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: true, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -192,7 +192,7 @@ func TestDropTable(t *testing.T) {
|
||||
|
||||
// 创建表
|
||||
userSchema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -230,8 +230,8 @@ func TestDatabaseRecover(t *testing.T) {
|
||||
}
|
||||
|
||||
userSchema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: true, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -291,8 +291,8 @@ func TestDatabaseClean(t *testing.T) {
|
||||
// 2. 创建多个表并插入数据
|
||||
// 表 1: users
|
||||
usersSchema, err := NewSchema("users", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "User ID"},
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "Name"},
|
||||
{Name: "id", Type: Int64, Indexed: true, Comment: "User ID"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "Name"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -311,8 +311,8 @@ func TestDatabaseClean(t *testing.T) {
|
||||
|
||||
// 表 2: orders
|
||||
ordersSchema, err := NewSchema("orders", []Field{
|
||||
{Name: "order_id", Type: FieldTypeInt64, Indexed: true, Comment: "Order ID"},
|
||||
{Name: "amount", Type: FieldTypeInt64, Indexed: false, Comment: "Amount"},
|
||||
{Name: "order_id", Type: Int64, Indexed: true, Comment: "Order ID"},
|
||||
{Name: "amount", Type: Int64, Indexed: false, Comment: "Amount"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -392,7 +392,7 @@ func TestDatabaseDestroy(t *testing.T) {
|
||||
}
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"},
|
||||
{Name: "id", Type: Int64, Indexed: false, Comment: "ID"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -448,8 +448,8 @@ func TestDatabaseCleanMultipleTables(t *testing.T) {
|
||||
for i := range 5 {
|
||||
tableName := fmt.Sprintf("table%d", i)
|
||||
schema, err := NewSchema(tableName, []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"},
|
||||
{Name: "value", Type: FieldTypeString, Indexed: false, Comment: "Value"},
|
||||
{Name: "id", Type: Int64, Indexed: false, Comment: "ID"},
|
||||
{Name: "value", Type: String, Indexed: false, Comment: "Value"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -524,7 +524,7 @@ func TestDatabaseCleanAndReopen(t *testing.T) {
|
||||
}
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"},
|
||||
{Name: "id", Type: Int64, Indexed: false, Comment: "ID"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -594,8 +594,8 @@ func TestDatabaseCleanTable(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"},
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "Name"},
|
||||
{Name: "id", Type: Int64, Indexed: false, Comment: "ID"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "Name"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -666,7 +666,7 @@ func TestDatabaseDestroyTable(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"},
|
||||
{Name: "id", Type: Int64, Indexed: false, Comment: "ID"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -721,7 +721,7 @@ func TestDatabaseDestroyTableMultiple(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"},
|
||||
{Name: "id", Type: Int64, Indexed: false, Comment: "ID"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
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)
|
||||
|
||||
5
go.mod
5
go.mod
@@ -4,4 +4,7 @@ go 1.24.0
|
||||
|
||||
require github.com/edsrzf/mmap-go v1.1.0
|
||||
|
||||
require golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
require (
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
)
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,4 +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=
|
||||
|
||||
@@ -13,9 +13,9 @@ func TestIndexBTreeBasic(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "city", Type: FieldTypeString},
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "city", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -130,8 +130,8 @@ func TestIndexBTreeLargeDataset(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "category", Type: FieldTypeString},
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "category", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -218,8 +218,8 @@ func TestIndexBTreeBackwardCompatibility(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "status", Type: FieldTypeString},
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "status", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -296,8 +296,8 @@ func TestIndexBTreeIncrementalUpdate(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "tag", Type: FieldTypeString},
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "tag", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestIndexVersionControl(t *testing.T) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
testSchema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "名称"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -76,7 +76,7 @@ func TestIncrementalUpdate(t *testing.T) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
testSchema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "名称"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -150,7 +150,7 @@ func TestNeedsUpdate(t *testing.T) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
testSchema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "名称"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -184,8 +184,8 @@ func TestIndexPersistence(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
testSchema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "名称"},
|
||||
{Name: "age", Type: Int64, Indexed: true, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -266,7 +266,7 @@ func TestIndexDropWithFile(t *testing.T) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
testSchema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "名称"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -306,9 +306,9 @@ func TestIndexQueryIntegration(t *testing.T) {
|
||||
|
||||
// 1. 创建带索引字段的 Schema
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "email", Type: FieldTypeString, Indexed: true}, // email 字段有索引
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false},
|
||||
{Name: "name", Type: String, Indexed: false},
|
||||
{Name: "email", Type: String, Indexed: true}, // email 字段有索引
|
||||
{Name: "age", Type: Int64, Indexed: false},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -469,9 +469,9 @@ func TestIndexPersistenceAcrossRestart(t *testing.T) {
|
||||
// 1. 第一次打开:创建数据和索引
|
||||
{
|
||||
schema, err := NewSchema("products", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "category", Type: FieldTypeString, Indexed: true},
|
||||
{Name: "price", Type: FieldTypeInt64, Indexed: false},
|
||||
{Name: "name", Type: String, Indexed: false},
|
||||
{Name: "category", Type: String, Indexed: true},
|
||||
{Name: "price", Type: Int64, Indexed: false},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -12,8 +12,8 @@ func TestLazyLoadingBasic(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -71,8 +71,8 @@ func TestLazyLoadingVsEagerLoading(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -151,9 +151,9 @@ func TestIndexQueryIsEager(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "email", Type: FieldTypeString, Indexed: true},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "email", Type: String, Indexed: true},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -236,9 +236,9 @@ func TestLazyLoadingWithConditions(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "active", Type: FieldTypeBool},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
{Name: "active", Type: Bool},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -314,8 +314,8 @@ func TestFirstDoesNotLoadAll(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
206
schema_test.go
206
schema_test.go
@@ -17,10 +17,10 @@ func init() {
|
||||
|
||||
// UserSchema 用户表 Schema
|
||||
UserSchema, err = NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "email", Type: FieldTypeString, Indexed: true, Comment: "邮箱"},
|
||||
{Name: "description", Type: FieldTypeString, Indexed: false, Comment: "描述"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "email", Type: String, Indexed: true, Comment: "邮箱"},
|
||||
{Name: "description", Type: String, Indexed: false, Comment: "描述"},
|
||||
})
|
||||
if err != nil {
|
||||
panic("Failed to create UserSchema: " + err.Error())
|
||||
@@ -28,10 +28,10 @@ func init() {
|
||||
|
||||
// LogSchema 日志表 Schema
|
||||
LogSchema, err = NewSchema("logs", []Field{
|
||||
{Name: "level", Type: FieldTypeString, Indexed: true, Comment: "日志级别"},
|
||||
{Name: "message", Type: FieldTypeString, Indexed: false, Comment: "日志消息"},
|
||||
{Name: "source", Type: FieldTypeString, Indexed: true, Comment: "来源"},
|
||||
{Name: "error_code", Type: FieldTypeInt64, Indexed: true, Comment: "错误码"},
|
||||
{Name: "level", Type: String, Indexed: true, Comment: "日志级别"},
|
||||
{Name: "message", Type: String, Indexed: false, Comment: "日志消息"},
|
||||
{Name: "source", Type: String, Indexed: true, Comment: "来源"},
|
||||
{Name: "error_code", Type: Int64, Indexed: true, Comment: "错误码"},
|
||||
})
|
||||
if err != nil {
|
||||
panic("Failed to create LogSchema: " + err.Error())
|
||||
@@ -39,11 +39,11 @@ func init() {
|
||||
|
||||
// OrderSchema 订单表 Schema
|
||||
OrderSchema, err = NewSchema("orders", []Field{
|
||||
{Name: "order_id", Type: FieldTypeString, Indexed: true, Comment: "订单ID"},
|
||||
{Name: "user_id", Type: FieldTypeInt64, Indexed: true, Comment: "用户ID"},
|
||||
{Name: "amount", Type: FieldTypeFloat, Indexed: true, Comment: "金额"},
|
||||
{Name: "status", Type: FieldTypeString, Indexed: true, Comment: "状态"},
|
||||
{Name: "paid", Type: FieldTypeBool, Indexed: true, Comment: "是否支付"},
|
||||
{Name: "order_id", Type: String, Indexed: true, Comment: "订单ID"},
|
||||
{Name: "user_id", Type: Int64, Indexed: true, Comment: "用户ID"},
|
||||
{Name: "amount", Type: Float64, Indexed: true, Comment: "金额"},
|
||||
{Name: "status", Type: String, Indexed: true, Comment: "状态"},
|
||||
{Name: "paid", Type: Bool, Indexed: true, Comment: "是否支付"},
|
||||
})
|
||||
if err != nil {
|
||||
panic("Failed to create OrderSchema: " + err.Error())
|
||||
@@ -53,9 +53,9 @@ func init() {
|
||||
func TestSchema(t *testing.T) {
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "score", Type: FieldTypeFloat, Indexed: false, Comment: "分数"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "名称"},
|
||||
{Name: "age", Type: Int64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "score", Type: Float64, Indexed: false, Comment: "分数"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -85,8 +85,8 @@ func TestSchema(t *testing.T) {
|
||||
|
||||
func TestSchemaValidation(t *testing.T) {
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "名称"},
|
||||
{Name: "age", Type: Int64, Indexed: true, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -130,15 +130,15 @@ func TestNewSchemaValidation(t *testing.T) {
|
||||
name: "Valid schema",
|
||||
schemaName: "users",
|
||||
fields: []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "name", Type: String},
|
||||
},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Empty schema name",
|
||||
schemaName: "",
|
||||
fields: []Field{{Name: "id", Type: FieldTypeInt64}},
|
||||
fields: []Field{{Name: "id", Type: Int64}},
|
||||
shouldError: true,
|
||||
errorMsg: "schema name cannot be empty",
|
||||
},
|
||||
@@ -160,7 +160,7 @@ func TestNewSchemaValidation(t *testing.T) {
|
||||
name: "Empty field name at index 0",
|
||||
schemaName: "users",
|
||||
fields: []Field{
|
||||
{Name: "", Type: FieldTypeInt64},
|
||||
{Name: "", Type: Int64},
|
||||
},
|
||||
shouldError: true,
|
||||
errorMsg: "field at index 0 has empty name",
|
||||
@@ -169,8 +169,8 @@ func TestNewSchemaValidation(t *testing.T) {
|
||||
name: "Empty field name at index 1",
|
||||
schemaName: "users",
|
||||
fields: []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "", Type: FieldTypeString},
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "", Type: String},
|
||||
},
|
||||
shouldError: true,
|
||||
errorMsg: "field at index 1 has empty name",
|
||||
@@ -179,9 +179,9 @@ func TestNewSchemaValidation(t *testing.T) {
|
||||
name: "Duplicate field name",
|
||||
schemaName: "users",
|
||||
fields: []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "id", Type: FieldTypeString}, // Duplicate
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "id", Type: String}, // Duplicate
|
||||
},
|
||||
shouldError: true,
|
||||
errorMsg: "duplicate field name: id",
|
||||
@@ -190,7 +190,7 @@ func TestNewSchemaValidation(t *testing.T) {
|
||||
name: "Valid schema with single field",
|
||||
schemaName: "logs",
|
||||
fields: []Field{
|
||||
{Name: "message", Type: FieldTypeString},
|
||||
{Name: "message", Type: String},
|
||||
},
|
||||
shouldError: false,
|
||||
},
|
||||
@@ -198,9 +198,9 @@ func TestNewSchemaValidation(t *testing.T) {
|
||||
name: "Valid schema with indexed field",
|
||||
schemaName: "users",
|
||||
fields: []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: true},
|
||||
{Name: "email", Type: FieldTypeString, Indexed: true},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "id", Type: Int64, Indexed: true},
|
||||
{Name: "email", Type: String, Indexed: true},
|
||||
{Name: "age", Type: Int64},
|
||||
},
|
||||
shouldError: false,
|
||||
},
|
||||
@@ -208,9 +208,9 @@ func TestNewSchemaValidation(t *testing.T) {
|
||||
name: "Valid schema with comments",
|
||||
schemaName: "products",
|
||||
fields: []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Comment: "产品ID"},
|
||||
{Name: "name", Type: FieldTypeString, Comment: "产品名称"},
|
||||
{Name: "price", Type: FieldTypeFloat, Comment: "价格"},
|
||||
{Name: "id", Type: Int64, Comment: "产品ID"},
|
||||
{Name: "name", Type: String, Comment: "产品名称"},
|
||||
{Name: "price", Type: Float64, Comment: "价格"},
|
||||
},
|
||||
shouldError: false,
|
||||
},
|
||||
@@ -260,10 +260,10 @@ func TestNewSchemaValidation(t *testing.T) {
|
||||
func TestNewSchemaFieldValidation(t *testing.T) {
|
||||
t.Run("Multiple duplicate field names", func(t *testing.T) {
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "id", Type: FieldTypeString}, // First duplicate
|
||||
{Name: "name", Type: FieldTypeString}, // Second duplicate
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "id", Type: String}, // First duplicate
|
||||
{Name: "name", Type: String}, // Second duplicate
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
@@ -284,9 +284,9 @@ func TestNewSchemaFieldValidation(t *testing.T) {
|
||||
t.Run("Case sensitive field names", func(t *testing.T) {
|
||||
// 大小写敏感,ID 和 id 应该是不同的字段
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "ID", Type: FieldTypeInt64},
|
||||
{Name: "Id", Type: FieldTypeInt64},
|
||||
{Name: "id", Type: Int64},
|
||||
{Name: "ID", Type: Int64},
|
||||
{Name: "Id", Type: Int64},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -301,10 +301,10 @@ func TestNewSchemaFieldValidation(t *testing.T) {
|
||||
|
||||
t.Run("Fields with all types", func(t *testing.T) {
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "int_field", Type: FieldTypeInt64},
|
||||
{Name: "string_field", Type: FieldTypeString},
|
||||
{Name: "float_field", Type: FieldTypeFloat},
|
||||
{Name: "bool_field", Type: FieldTypeBool},
|
||||
{Name: "int_field", Type: Int64},
|
||||
{Name: "string_field", Type: String},
|
||||
{Name: "float_field", Type: Float64},
|
||||
{Name: "bool_field", Type: Bool},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -318,10 +318,10 @@ func TestNewSchemaFieldValidation(t *testing.T) {
|
||||
|
||||
// 验证每个字段的类型
|
||||
expectedTypes := map[string]FieldType{
|
||||
"int_field": FieldTypeInt64,
|
||||
"string_field": FieldTypeString,
|
||||
"float_field": FieldTypeFloat,
|
||||
"bool_field": FieldTypeBool,
|
||||
"int_field": Int64,
|
||||
"string_field": String,
|
||||
"float_field": Float64,
|
||||
"bool_field": Bool,
|
||||
}
|
||||
|
||||
for _, field := range schema.Fields {
|
||||
@@ -342,7 +342,7 @@ func TestNewSchemaEdgeCases(t *testing.T) {
|
||||
t.Run("Very long schema name", func(t *testing.T) {
|
||||
longName := strings.Repeat("a", 1000)
|
||||
schema, err := NewSchema(longName, []Field{
|
||||
{Name: "id", Type: FieldTypeInt64},
|
||||
{Name: "id", Type: Int64},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -358,7 +358,7 @@ func TestNewSchemaEdgeCases(t *testing.T) {
|
||||
t.Run("Very long field name", func(t *testing.T) {
|
||||
longFieldName := strings.Repeat("b", 1000)
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: longFieldName, Type: FieldTypeInt64},
|
||||
{Name: longFieldName, Type: Int64},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -376,7 +376,7 @@ func TestNewSchemaEdgeCases(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
fields[i] = Field{
|
||||
Name: strings.Repeat("field", 1) + string(rune('a'+i)),
|
||||
Type: FieldTypeInt64,
|
||||
Type: Int64,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,9 +394,9 @@ func TestNewSchemaEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("Field with special characters", func(t *testing.T) {
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "field_with_underscore", Type: FieldTypeInt64},
|
||||
{Name: "field123", Type: FieldTypeInt64},
|
||||
{Name: "字段名", Type: FieldTypeString}, // 中文字段名
|
||||
{Name: "field_with_underscore", Type: Int64},
|
||||
{Name: "field123", Type: Int64},
|
||||
{Name: "字段名", Type: String}, // 中文字段名
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -414,9 +414,9 @@ func TestNewSchemaEdgeCases(t *testing.T) {
|
||||
func TestNewSchemaConsistency(t *testing.T) {
|
||||
t.Run("Field order preserved", func(t *testing.T) {
|
||||
fields := []Field{
|
||||
{Name: "zebra", Type: FieldTypeString},
|
||||
{Name: "alpha", Type: FieldTypeInt64},
|
||||
{Name: "beta", Type: FieldTypeFloat},
|
||||
{Name: "zebra", Type: String},
|
||||
{Name: "alpha", Type: Int64},
|
||||
{Name: "beta", Type: Float64},
|
||||
}
|
||||
|
||||
schema, err := NewSchema("test", fields)
|
||||
@@ -437,8 +437,8 @@ func TestNewSchemaConsistency(t *testing.T) {
|
||||
|
||||
t.Run("Field properties preserved", func(t *testing.T) {
|
||||
fields := []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "Primary key"},
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "User name"},
|
||||
{Name: "id", Type: Int64, Indexed: true, Comment: "Primary key"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "User name"},
|
||||
}
|
||||
|
||||
schema, err := NewSchema("users", fields)
|
||||
@@ -466,8 +466,8 @@ func TestNewSchemaConsistency(t *testing.T) {
|
||||
|
||||
func TestExtractIndexValue(t *testing.T) {
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "名称"},
|
||||
{Name: "age", Type: Int64, Indexed: true, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -534,16 +534,16 @@ func TestChecksumDeterminism(t *testing.T) {
|
||||
// 创建相同的 Schema 多次
|
||||
for i := range 10 {
|
||||
s1, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s2, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -570,16 +570,16 @@ func TestChecksumDeterminism(t *testing.T) {
|
||||
// TestChecksumFieldOrderIndependent 测试字段顺序不影响 checksum
|
||||
func TestChecksumFieldOrderIndependent(t *testing.T) {
|
||||
s1, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s2, err := NewSchema("users", []Field{
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -599,14 +599,14 @@ func TestChecksumFieldOrderIndependent(t *testing.T) {
|
||||
// TestChecksumDifferentData 测试不同 Schema 的 checksum 应该不同
|
||||
func TestChecksumDifferentData(t *testing.T) {
|
||||
s1, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
|
||||
{Name: "name", Type: String, Indexed: true, Comment: "用户名"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s2, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, // Indexed 不同
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "用户名"}, // Indexed 不同
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -625,10 +625,10 @@ func TestChecksumDifferentData(t *testing.T) {
|
||||
// TestChecksumMultipleFieldOrders 测试多个字段的各种排列组合都产生相同 checksum
|
||||
func TestChecksumMultipleFieldOrders(t *testing.T) {
|
||||
// 定义 4 个字段
|
||||
fieldA := Field{Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "ID"}
|
||||
fieldB := Field{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "名称"}
|
||||
fieldC := Field{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}
|
||||
fieldD := Field{Name: "email", Type: FieldTypeString, Indexed: true, Comment: "邮箱"}
|
||||
fieldA := Field{Name: "id", Type: Int64, Indexed: true, Comment: "ID"}
|
||||
fieldB := Field{Name: "name", Type: String, Indexed: false, Comment: "名称"}
|
||||
fieldC := Field{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"}
|
||||
fieldD := Field{Name: "email", Type: String, Indexed: true, Comment: "邮箱"}
|
||||
|
||||
// 创建不同顺序的 Schema
|
||||
mustNewSchema := func(name string, fields []Field) *Schema {
|
||||
@@ -697,11 +697,11 @@ func TestStructToFields(t *testing.T) {
|
||||
Indexed bool
|
||||
Comment string
|
||||
}{
|
||||
"name": {FieldTypeString, true, "用户名"},
|
||||
"age": {FieldTypeInt64, false, "年龄"},
|
||||
"email": {FieldTypeString, true, "邮箱"},
|
||||
"score": {FieldTypeFloat, false, "分数"},
|
||||
"active": {FieldTypeBool, false, "是否激活"},
|
||||
"name": {String, true, "用户名"},
|
||||
"age": {Int64, false, "年龄"},
|
||||
"email": {String, true, "邮箱"},
|
||||
"score": {Float64, false, "分数"},
|
||||
"active": {Bool, false, "是否激活"},
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
@@ -834,30 +834,32 @@ func TestStructToFieldsAllTypes(t *testing.T) {
|
||||
t.Errorf("Expected 14 fields, got %d", len(fields))
|
||||
}
|
||||
|
||||
// 验证所有整数类型都映射到 FieldTypeInt64
|
||||
intFields := []string{"int", "int64", "int32", "int16", "int8", "uint", "uint64", "uint32", "uint16", "uint8"}
|
||||
for _, name := range intFields {
|
||||
found := false
|
||||
for _, field := range fields {
|
||||
if field.Name == name {
|
||||
found = true
|
||||
if field.Type != FieldTypeInt64 {
|
||||
t.Errorf("Field %s: expected FieldTypeInt64, got %v", name, field.Type)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Field %s not found", name)
|
||||
}
|
||||
// 验证所有类型都精确映射到对应的 FieldType
|
||||
expectedTypes := map[string]FieldType{
|
||||
"int": Int,
|
||||
"int64": Int64,
|
||||
"int32": Int32,
|
||||
"int16": Int16,
|
||||
"int8": Int8,
|
||||
"uint": Uint,
|
||||
"uint64": Uint64,
|
||||
"uint32": Uint32,
|
||||
"uint16": Uint16,
|
||||
"uint8": Uint8,
|
||||
"string": String,
|
||||
"float64": Float64,
|
||||
"float32": Float32,
|
||||
"bool": Bool,
|
||||
}
|
||||
|
||||
// 验证浮点类型
|
||||
for _, field := range fields {
|
||||
if field.Name == "float64" || field.Name == "float32" {
|
||||
if field.Type != FieldTypeFloat {
|
||||
t.Errorf("Field %s: expected FieldTypeFloat, got %v", field.Name, field.Type)
|
||||
}
|
||||
expectedType, exists := expectedTypes[field.Name]
|
||||
if !exists {
|
||||
t.Errorf("Unexpected field: %s", field.Name)
|
||||
continue
|
||||
}
|
||||
if field.Type != expectedType {
|
||||
t.Errorf("Field %s: expected %v, got %v", field.Name, expectedType, field.Type)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
428
sstable.go
428
sstable.go
@@ -9,8 +9,10 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/edsrzf/mmap-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -230,47 +232,101 @@ func encodeSSTableRowBinary(row *SSTableRow, schema *Schema) ([]byte, error) {
|
||||
// writeFieldBinaryValue 写入字段值(二进制格式)
|
||||
func writeFieldBinaryValue(buf *bytes.Buffer, typ FieldType, value any) error {
|
||||
switch typ {
|
||||
case FieldTypeInt64:
|
||||
var v int64
|
||||
switch val := value.(type) {
|
||||
case int:
|
||||
v = int64(val)
|
||||
case int64:
|
||||
v = val
|
||||
case int32:
|
||||
v = int64(val)
|
||||
case int16:
|
||||
v = int64(val)
|
||||
case int8:
|
||||
v = int64(val)
|
||||
case float64:
|
||||
v = int64(val)
|
||||
default:
|
||||
return fmt.Errorf("cannot convert %T to int64", value)
|
||||
// 有符号整数类型
|
||||
case Int:
|
||||
v, ok := value.(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected int, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, int64(v))
|
||||
|
||||
case Int8:
|
||||
v, ok := value.(int8)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected int8, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
case FieldTypeFloat:
|
||||
var v float64
|
||||
switch val := value.(type) {
|
||||
case float64:
|
||||
v = val
|
||||
case float32:
|
||||
v = float64(val)
|
||||
default:
|
||||
return fmt.Errorf("cannot convert %T to float64", value)
|
||||
case Int16:
|
||||
v, ok := value.(int16)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected int16, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
case FieldTypeBool:
|
||||
var b byte
|
||||
if value.(bool) {
|
||||
b = 1
|
||||
case Int32, Rune:
|
||||
// rune 和 int32 底层类型相同(都是 int32)
|
||||
v, ok := value.(int32)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected int32 (or rune), got %T", value)
|
||||
}
|
||||
return buf.WriteByte(b)
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
case FieldTypeString:
|
||||
s := value.(string)
|
||||
case Int64:
|
||||
v, ok := value.(int64)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected int64, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
// 无符号整数类型
|
||||
case Uint:
|
||||
v, ok := value.(uint)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected uint, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, uint64(v))
|
||||
|
||||
case Uint8, Byte:
|
||||
// byte 和 uint8 底层类型相同
|
||||
v, ok := value.(uint8)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected uint8 or byte, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
case Uint16:
|
||||
v, ok := value.(uint16)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected uint16, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
case Uint32:
|
||||
v, ok := value.(uint32)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected uint32, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
case Uint64:
|
||||
v, ok := value.(uint64)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected uint64, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
// 浮点类型
|
||||
case Float32:
|
||||
v, ok := value.(float32)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected float32, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
case Float64:
|
||||
v, ok := value.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected float64, got %T", value)
|
||||
}
|
||||
return binary.Write(buf, binary.LittleEndian, v)
|
||||
|
||||
// 字符串类型
|
||||
case String:
|
||||
s, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected string, got %T", value)
|
||||
}
|
||||
// 写入长度
|
||||
if err := binary.Write(buf, binary.LittleEndian, uint32(len(s))); err != nil {
|
||||
return err
|
||||
@@ -279,6 +335,55 @@ func writeFieldBinaryValue(buf *bytes.Buffer, typ FieldType, value any) error {
|
||||
_, err := buf.WriteString(s)
|
||||
return err
|
||||
|
||||
// 布尔类型
|
||||
case Bool:
|
||||
v, ok := value.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected bool, got %T", value)
|
||||
}
|
||||
var b byte
|
||||
if v {
|
||||
b = 1
|
||||
}
|
||||
return buf.WriteByte(b)
|
||||
|
||||
// Decimal 类型
|
||||
case Decimal:
|
||||
v, ok := value.(decimal.Decimal)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected decimal.Decimal, got %T", value)
|
||||
}
|
||||
// 使用 MarshalBinary 序列化
|
||||
data, err := v.MarshalBinary()
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal decimal: %w", err)
|
||||
}
|
||||
// 写入长度
|
||||
if err := binary.Write(buf, binary.LittleEndian, uint32(len(data))); err != nil {
|
||||
return err
|
||||
}
|
||||
// 写入数据
|
||||
_, err = buf.Write(data)
|
||||
return err
|
||||
|
||||
// 时间类型
|
||||
case Time:
|
||||
v, ok := value.(time.Time)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected time.Time, got %T", value)
|
||||
}
|
||||
// 存储为 Unix 时间戳(秒,int64)
|
||||
return binary.Write(buf, binary.LittleEndian, v.Unix())
|
||||
|
||||
// 时间间隔类型
|
||||
case Duration:
|
||||
v, ok := value.(time.Duration)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected time.Duration, got %T", value)
|
||||
}
|
||||
// 存储为纳秒(int64)
|
||||
return binary.Write(buf, binary.LittleEndian, int64(v))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported field type: %d", typ)
|
||||
}
|
||||
@@ -287,14 +392,65 @@ func writeFieldBinaryValue(buf *bytes.Buffer, typ FieldType, value any) error {
|
||||
// writeFieldZeroValue 写入字段零值
|
||||
func writeFieldZeroValue(buf *bytes.Buffer, typ FieldType) error {
|
||||
switch typ {
|
||||
case FieldTypeInt64:
|
||||
// 有符号整数类型
|
||||
case Int:
|
||||
return binary.Write(buf, binary.LittleEndian, int64(0))
|
||||
case FieldTypeFloat:
|
||||
return binary.Write(buf, binary.LittleEndian, float64(0))
|
||||
case FieldTypeBool:
|
||||
return buf.WriteByte(0)
|
||||
case FieldTypeString:
|
||||
case Int8:
|
||||
return binary.Write(buf, binary.LittleEndian, int8(0))
|
||||
case Int16:
|
||||
return binary.Write(buf, binary.LittleEndian, int16(0))
|
||||
case Int32, Rune:
|
||||
return binary.Write(buf, binary.LittleEndian, int32(0))
|
||||
case Int64:
|
||||
return binary.Write(buf, binary.LittleEndian, int64(0))
|
||||
|
||||
// 无符号整数类型
|
||||
case Uint:
|
||||
return binary.Write(buf, binary.LittleEndian, uint64(0))
|
||||
case Uint8, Byte:
|
||||
return binary.Write(buf, binary.LittleEndian, uint8(0))
|
||||
case Uint16:
|
||||
return binary.Write(buf, binary.LittleEndian, uint16(0))
|
||||
case Uint32:
|
||||
return binary.Write(buf, binary.LittleEndian, uint32(0))
|
||||
case Uint64:
|
||||
return binary.Write(buf, binary.LittleEndian, uint64(0))
|
||||
|
||||
// 浮点类型
|
||||
case Float32:
|
||||
return binary.Write(buf, binary.LittleEndian, float32(0))
|
||||
case Float64:
|
||||
return binary.Write(buf, binary.LittleEndian, float64(0))
|
||||
|
||||
// 字符串类型
|
||||
case String:
|
||||
return binary.Write(buf, binary.LittleEndian, uint32(0))
|
||||
|
||||
// 布尔类型
|
||||
case Bool:
|
||||
return buf.WriteByte(0)
|
||||
|
||||
// Decimal 类型(零值)
|
||||
case Decimal:
|
||||
zero := decimal.Zero
|
||||
data, err := zero.MarshalBinary()
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal zero decimal: %w", err)
|
||||
}
|
||||
if err := binary.Write(buf, binary.LittleEndian, uint32(len(data))); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = buf.Write(data)
|
||||
return err
|
||||
|
||||
// 时间类型(零值:Unix epoch)
|
||||
case Time:
|
||||
return binary.Write(buf, binary.LittleEndian, int64(0))
|
||||
|
||||
// 时间间隔类型(零值)
|
||||
case Duration:
|
||||
return binary.Write(buf, binary.LittleEndian, int64(0))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported field type: %d", typ)
|
||||
}
|
||||
@@ -416,7 +572,58 @@ func decodeSSTableRowBinaryPartial(data []byte, schema *Schema, fields []string)
|
||||
// readFieldBinaryValue 读取字段值(二进制格式)
|
||||
func readFieldBinaryValue(buf *bytes.Reader, typ FieldType, keep bool) (any, error) {
|
||||
switch typ {
|
||||
case FieldTypeInt64:
|
||||
// 有符号整数类型
|
||||
case Int:
|
||||
var v int64
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return int(v), nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Int8:
|
||||
var v int8
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Int16:
|
||||
var v int16
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Int32:
|
||||
var v int32
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Rune:
|
||||
var v int32
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return rune(v), nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Int64:
|
||||
var v int64
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
@@ -426,7 +633,79 @@ func readFieldBinaryValue(buf *bytes.Reader, typ FieldType, keep bool) (any, err
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case FieldTypeFloat:
|
||||
// 无符号整数类型
|
||||
case Uint:
|
||||
var v uint64
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return uint(v), nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Uint8:
|
||||
var v uint8
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Byte:
|
||||
var v uint8
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return byte(v), nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Uint16:
|
||||
var v uint16
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Uint32:
|
||||
var v uint32
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Uint64:
|
||||
var v uint64
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
// 浮点类型
|
||||
case Float32:
|
||||
var v float32
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case Float64:
|
||||
var v float64
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
@@ -436,17 +715,8 @@ func readFieldBinaryValue(buf *bytes.Reader, typ FieldType, keep bool) (any, err
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case FieldTypeBool:
|
||||
b, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return b == 1, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case FieldTypeString:
|
||||
// 字符串类型
|
||||
case String:
|
||||
var length uint32
|
||||
if err := binary.Read(buf, binary.LittleEndian, &length); err != nil {
|
||||
return nil, err
|
||||
@@ -460,6 +730,60 @@ func readFieldBinaryValue(buf *bytes.Reader, typ FieldType, keep bool) (any, err
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
// 布尔类型
|
||||
case Bool:
|
||||
b, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
return b == 1, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
// Decimal 类型
|
||||
case Decimal:
|
||||
var length uint32
|
||||
if err := binary.Read(buf, binary.LittleEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]byte, length)
|
||||
if _, err := buf.Read(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
var d decimal.Decimal
|
||||
if err := d.UnmarshalBinary(data); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal decimal: %w", err)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
// 时间类型
|
||||
case Time:
|
||||
var v int64
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
// 从 Unix 时间戳(秒)转换为 time.Time
|
||||
return time.Unix(v, 0), nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
// 时间间隔类型
|
||||
case Duration:
|
||||
var v int64
|
||||
if err := binary.Read(buf, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep {
|
||||
// 从纳秒转换为 time.Duration
|
||||
return time.Duration(v), nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported field type: %d", typ)
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ func TestSSTable(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -168,7 +168,7 @@ func TestSSTableHeaderSerialization(t *testing.T) {
|
||||
func BenchmarkSSTableGet(b *testing.B) {
|
||||
// 创建 Schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "value", Type: FieldTypeInt64},
|
||||
{Name: "value", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
@@ -212,9 +212,9 @@ func TestSSTableBinaryEncoding(t *testing.T) {
|
||||
schema := &Schema{
|
||||
Name: "users",
|
||||
Fields: []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "email", Type: FieldTypeString},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
{Name: "email", Type: String},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -262,9 +262,9 @@ func TestSSTableEncodingComparison(t *testing.T) {
|
||||
schema := &Schema{
|
||||
Name: "users",
|
||||
Fields: []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "email", Type: FieldTypeString},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
{Name: "email", Type: String},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -303,9 +303,9 @@ func BenchmarkSSTableBinaryEncoding(b *testing.B) {
|
||||
schema := &Schema{
|
||||
Name: "users",
|
||||
Fields: []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "email", Type: FieldTypeString},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
{Name: "email", Type: String},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -351,10 +351,10 @@ func TestSSTablePerFieldCompression(t *testing.T) {
|
||||
schema := &Schema{
|
||||
Name: "users",
|
||||
Fields: []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false},
|
||||
{Name: "email", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "score", Type: FieldTypeFloat, Indexed: false},
|
||||
{Name: "name", Type: String, Indexed: false},
|
||||
{Name: "age", Type: Int64, Indexed: false},
|
||||
{Name: "email", Type: String, Indexed: false},
|
||||
{Name: "score", Type: Float64, Indexed: false},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -437,16 +437,16 @@ func TestSSTablePartialReadingPerformance(t *testing.T) {
|
||||
schema := &Schema{
|
||||
Name: "events",
|
||||
Fields: []Field{
|
||||
{Name: "field1", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field2", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field3", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field4", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field5", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field6", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field7", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field8", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field9", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field10", Type: FieldTypeString, Indexed: false},
|
||||
{Name: "field1", Type: String, Indexed: false},
|
||||
{Name: "field2", Type: String, Indexed: false},
|
||||
{Name: "field3", Type: String, Indexed: false},
|
||||
{Name: "field4", Type: String, Indexed: false},
|
||||
{Name: "field5", Type: String, Indexed: false},
|
||||
{Name: "field6", Type: String, Indexed: false},
|
||||
{Name: "field7", Type: String, Indexed: false},
|
||||
{Name: "field8", Type: String, Indexed: false},
|
||||
{Name: "field9", Type: String, Indexed: false},
|
||||
{Name: "field10", Type: String, Indexed: false},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
42
table.go
42
table.go
@@ -398,8 +398,8 @@ func (t *Table) insertSingle(data map[string]any) error {
|
||||
Data: data,
|
||||
}
|
||||
|
||||
// 3. 序列化
|
||||
rowData, err := json.Marshal(row)
|
||||
// 3. 序列化(使用二进制格式,保留类型信息)
|
||||
rowData, err := encodeSSTableRowBinary(row, t.schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -437,12 +437,12 @@ func (t *Table) Get(seq int64) (*SSTableRow, error) {
|
||||
// 1. 先查 MemTable Manager (Active + Immutables)
|
||||
data, found := t.memtableManager.Get(seq)
|
||||
if found {
|
||||
var row SSTableRow
|
||||
err := json.Unmarshal(data, &row)
|
||||
// 使用二进制解码
|
||||
row, err := decodeSSTableRowBinary(data, t.schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &row, nil
|
||||
return row, nil
|
||||
}
|
||||
|
||||
// 2. 查询 SST 文件
|
||||
@@ -454,24 +454,12 @@ func (t *Table) GetPartial(seq int64, fields []string) (*SSTableRow, error) {
|
||||
// 1. 先查 MemTable Manager (Active + Immutables)
|
||||
data, found := t.memtableManager.Get(seq)
|
||||
if found {
|
||||
var row SSTableRow
|
||||
err := json.Unmarshal(data, &row)
|
||||
// 使用二进制解码(支持部分解码)
|
||||
row, err := decodeSSTableRowBinaryPartial(data, t.schema, fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// MemTable 中的数据已经完全解析,需要手动过滤字段
|
||||
if len(fields) > 0 {
|
||||
filteredData := make(map[string]any)
|
||||
for _, field := range fields {
|
||||
if val, ok := row.Data[field]; ok {
|
||||
filteredData[field] = val
|
||||
}
|
||||
}
|
||||
row.Data = filteredData
|
||||
}
|
||||
|
||||
return &row, nil
|
||||
return row, nil
|
||||
}
|
||||
|
||||
// 2. 查询 SST 文件(按需解码)
|
||||
@@ -505,10 +493,10 @@ func (t *Table) flushImmutable(imm *ImmutableMemTable, walNumber int64) error {
|
||||
var rows []*SSTableRow
|
||||
iter := imm.NewIterator()
|
||||
for iter.Next() {
|
||||
var row SSTableRow
|
||||
err := json.Unmarshal(iter.Value(), &row)
|
||||
// 使用二进制解码
|
||||
row, err := decodeSSTableRowBinary(iter.Value(), t.schema)
|
||||
if err == nil {
|
||||
rows = append(rows, &row)
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,10 +597,10 @@ func (t *Table) recover() error {
|
||||
|
||||
// 重放 WAL 到 Active MemTable
|
||||
for _, entry := range entries {
|
||||
// 验证 Schema
|
||||
var row SSTableRow
|
||||
if err := json.Unmarshal(entry.Data, &row); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal row during recovery (seq=%d): %w", entry.Seq, err)
|
||||
// 使用二进制解码验证 Schema
|
||||
row, err := decodeSSTableRowBinary(entry.Data, t.schema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode row during recovery (seq=%d): %w", entry.Seq, err)
|
||||
}
|
||||
|
||||
// 验证 Schema
|
||||
|
||||
124
table_test.go
124
table_test.go
@@ -19,8 +19,8 @@ func TestTable(t *testing.T) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -84,7 +84,7 @@ func TestTableRecover(t *testing.T) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "value", Type: FieldTypeInt64, Indexed: false, Comment: "值"},
|
||||
{Name: "value", Type: Int64, Indexed: false, Comment: "值"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -150,7 +150,7 @@ func TestTableFlush(t *testing.T) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "data", Type: FieldTypeString, Indexed: false, Comment: "数据"},
|
||||
{Name: "data", Type: String, Indexed: false, Comment: "数据"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -203,7 +203,7 @@ func BenchmarkTableInsert(b *testing.B) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "value", Type: FieldTypeInt64, Indexed: false, Comment: "值"},
|
||||
{Name: "value", Type: Int64, Indexed: false, Comment: "值"},
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
@@ -232,7 +232,7 @@ func BenchmarkTableGet(b *testing.B) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "value", Type: FieldTypeInt64, Indexed: false, Comment: "值"},
|
||||
{Name: "value", Type: Int64, Indexed: false, Comment: "值"},
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
@@ -267,7 +267,7 @@ func TestHighConcurrencyWrite(t *testing.T) {
|
||||
// Note: This test uses []byte payload - we create a minimal schema
|
||||
// Schema validation accepts []byte as it gets JSON-marshaled
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "worker_id", Type: FieldTypeInt64, Indexed: false, Comment: "Worker ID"},
|
||||
{Name: "worker_id", Type: Int64, Indexed: false, Comment: "Worker ID"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -385,7 +385,7 @@ func TestConcurrentReadWrite(t *testing.T) {
|
||||
|
||||
// Note: This test uses []byte data - we create a minimal schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "writer_id", Type: FieldTypeInt64, Indexed: false, Comment: "Writer ID"},
|
||||
{Name: "writer_id", Type: Int64, Indexed: false, Comment: "Writer ID"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -507,7 +507,7 @@ func TestPowerFailureRecovery(t *testing.T) {
|
||||
|
||||
// Note: This test uses []byte data - we create a minimal schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "batch", Type: FieldTypeInt64, Indexed: false, Comment: "Batch number"},
|
||||
{Name: "batch", Type: Int64, Indexed: false, Comment: "Batch number"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -646,7 +646,7 @@ func TestCrashDuringCompaction(t *testing.T) {
|
||||
|
||||
// Note: This test uses []byte data - we create a minimal schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "Index"},
|
||||
{Name: "index", Type: Int64, Indexed: false, Comment: "Index"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -749,7 +749,7 @@ func TestLargeDataIntegrity(t *testing.T) {
|
||||
|
||||
// Note: This test uses []byte data - we create a minimal schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "size", Type: FieldTypeInt64, Indexed: false, Comment: "Size"},
|
||||
{Name: "size", Type: Int64, Indexed: false, Comment: "Size"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -863,7 +863,7 @@ func BenchmarkConcurrentWrites(b *testing.B) {
|
||||
|
||||
// Note: This benchmark uses []byte data - we create a minimal schema
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "timestamp", Type: FieldTypeInt64, Indexed: false, Comment: "Timestamp"},
|
||||
{Name: "timestamp", Type: Int64, Indexed: false, Comment: "Timestamp"},
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
@@ -917,9 +917,9 @@ func TestTableWithCompaction(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "batch", Type: FieldTypeInt64, Indexed: false, Comment: "批次"},
|
||||
{Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "索引"},
|
||||
{Name: "value", Type: FieldTypeString, Indexed: false, Comment: "值"},
|
||||
{Name: "batch", Type: Int64, Indexed: false, Comment: "批次"},
|
||||
{Name: "index", Type: Int64, Indexed: false, Comment: "索引"},
|
||||
{Name: "value", Type: String, Indexed: false, Comment: "值"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1046,9 +1046,9 @@ func TestTableCompactionMerge(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "batch", Type: FieldTypeInt64, Indexed: false, Comment: "批次"},
|
||||
{Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "索引"},
|
||||
{Name: "value", Type: FieldTypeString, Indexed: false, Comment: "值"},
|
||||
{Name: "batch", Type: Int64, Indexed: false, Comment: "批次"},
|
||||
{Name: "index", Type: Int64, Indexed: false, Comment: "索引"},
|
||||
{Name: "value", Type: String, Indexed: false, Comment: "值"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1154,8 +1154,8 @@ func TestTableBackgroundCompaction(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "batch", Type: FieldTypeInt64, Indexed: false, Comment: "批次"},
|
||||
{Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "索引"},
|
||||
{Name: "batch", Type: Int64, Indexed: false, Comment: "批次"},
|
||||
{Name: "index", Type: Int64, Indexed: false, Comment: "索引"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1244,8 +1244,8 @@ func BenchmarkTableWithCompaction(b *testing.B) {
|
||||
tmpDir := b.TempDir()
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "index", Type: FieldTypeInt64, Indexed: false, Comment: "索引"},
|
||||
{Name: "value", Type: FieldTypeString, Indexed: false, Comment: "值"},
|
||||
{Name: "index", Type: Int64, Indexed: false, Comment: "索引"},
|
||||
{Name: "value", Type: String, Indexed: false, Comment: "值"},
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
@@ -1299,9 +1299,9 @@ func TestTableSchemaRecover(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
s, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "email", Type: FieldTypeString, Indexed: false, Comment: "邮箱"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "email", Type: String, Indexed: false, Comment: "邮箱"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1374,8 +1374,8 @@ func TestTableSchemaRecoverInvalid(t *testing.T) {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeString, Indexed: false, Comment: "年龄字符串"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: String, Indexed: false, Comment: "年龄字符串"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1421,8 +1421,8 @@ func TestTableSchemaRecoverInvalid(t *testing.T) {
|
||||
|
||||
// 3. 创建 Schema,age 字段要求 int64
|
||||
s, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1455,8 +1455,8 @@ func TestTableAutoRecoverSchema(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
s, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1550,8 +1550,8 @@ func TestTableSchemaTamperDetection(t *testing.T) {
|
||||
|
||||
// 创建 Schema
|
||||
s, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "用户名"},
|
||||
{Name: "age", Type: Int64, Indexed: false, Comment: "年龄"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1615,8 +1615,8 @@ func TestTableClean(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "ID"},
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "Name"},
|
||||
{Name: "id", Type: Int64, Indexed: true, Comment: "ID"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "Name"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1687,7 +1687,7 @@ func TestTableDestroy(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"},
|
||||
{Name: "id", Type: Int64, Indexed: false, Comment: "ID"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1746,9 +1746,9 @@ func TestTableCleanWithIndex(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "ID"},
|
||||
{Name: "email", Type: FieldTypeString, Indexed: true, Comment: "Email"},
|
||||
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "Name"},
|
||||
{Name: "id", Type: Int64, Indexed: true, Comment: "ID"},
|
||||
{Name: "email", Type: String, Indexed: true, Comment: "Email"},
|
||||
{Name: "name", Type: String, Indexed: false, Comment: "Name"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1835,8 +1835,8 @@ func TestTableCleanAndQuery(t *testing.T) {
|
||||
defer db.Close()
|
||||
|
||||
schema, err := NewSchema("test", []Field{
|
||||
{Name: "id", Type: FieldTypeInt64, Indexed: false, Comment: "ID"},
|
||||
{Name: "status", Type: FieldTypeString, Indexed: false, Comment: "Status"},
|
||||
{Name: "id", Type: Int64, Indexed: false, Comment: "ID"},
|
||||
{Name: "status", Type: String, Indexed: false, Comment: "Status"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1923,8 +1923,8 @@ func TestInsertMap(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1968,8 +1968,8 @@ func TestInsertMapSlice(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2019,9 +2019,9 @@ func TestInsertStruct(t *testing.T) {
|
||||
}
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "email", Type: FieldTypeString},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
{Name: "email", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2077,9 +2077,9 @@ func TestInsertStructPointer(t *testing.T) {
|
||||
}
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "email", Type: FieldTypeString},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
{Name: "email", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2131,8 +2131,8 @@ func TestInsertStructSlice(t *testing.T) {
|
||||
}
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2183,8 +2183,8 @@ func TestInsertStructPointerSlice(t *testing.T) {
|
||||
}
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2237,9 +2237,9 @@ func TestInsertWithSnakeCase(t *testing.T) {
|
||||
}
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "user_name", Type: FieldTypeString, Comment: "用户名"},
|
||||
{Name: "email_address", Type: FieldTypeString},
|
||||
{Name: "is_active", Type: FieldTypeBool},
|
||||
{Name: "user_name", Type: String, Comment: "用户名"},
|
||||
{Name: "email_address", Type: String},
|
||||
{Name: "is_active", Type: Bool},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2292,7 +2292,7 @@ func TestInsertInvalidType(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "name", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2333,7 +2333,7 @@ func TestInsertEmptySlice(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "name", Type: String},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -2370,8 +2370,8 @@ func TestBatchInsertPerformance(t *testing.T) {
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
schema, err := NewSchema("users", []Field{
|
||||
{Name: "name", Type: FieldTypeString},
|
||||
{Name: "age", Type: FieldTypeInt64},
|
||||
{Name: "name", Type: String},
|
||||
{Name: "age", Type: Int64},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -415,7 +415,7 @@ func (ui *WebUI) handleTableData(w http.ResponseWriter, r *http.Request, tableNa
|
||||
|
||||
// 检查字段类型
|
||||
field, err := tableSchema.GetField(k)
|
||||
if err == nil && field.Type == srdb.FieldTypeString {
|
||||
if err == nil && field.Type == srdb.String {
|
||||
// 对字符串字段进行剪裁
|
||||
if str, ok := v.(string); ok {
|
||||
runes := []rune(str)
|
||||
|
||||
Reference in New Issue
Block a user