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

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

130
CLAUDE.md
View File

@@ -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/` - 展示新增的 ByteRuneDecimal 类型和 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

View File

@@ -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)

View File

@@ -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)

View File

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

Binary file not shown.

View 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": "是否在线"
}
]
}
}

View File

@@ -0,0 +1 @@
2

View 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 (语义清晰)")
}

View File

@@ -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)

View File

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

View File

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

Binary file not shown.

View File

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

View File

@@ -0,0 +1 @@
2

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

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

View File

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

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

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

View 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
✓ 插入用户 bobemail、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
```

View File

@@ -0,0 +1,10 @@
{
"version": 1,
"tables": [
{
"name": "users",
"dir": "users",
"created_at": 1760032030
}
]
}

View File

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

Binary file not shown.

View 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": "是否激活"
}
]
}
}

View File

@@ -0,0 +1 @@
2

View 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
)

View File

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

View 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("✓ 插入用户 bobemail、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:\"-\" # 忽略字段")
}

View 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)
}

View 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)
}

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

929
schema.go

File diff suppressed because it is too large Load Diff

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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},
},
}

View File

@@ -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

View File

@@ -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. 创建 Schemaage 字段要求 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)

View File

@@ -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)