package main<>> import ( "fmt" "keven/cdb/pkg/borm" ) // 数据模型(borm 标签声明主键与关联) type User struct { ID uint64 `borm:"pk"` Name string Email string Age int } func main() { // 打开(或创建)加密数据库 db, err := borm.Open("app.db", &borm.Config{ SecretKey: []byte("0123456789abcdef0123456789abcdef"), BufferCapacity: 100, WALMaxSize: 1 << 20, MemoryBudget: 1 << 30, // 可选:内存驻留上限(0=不限) // StorageMode: borm.StorageModeDisk, // 默认:磁盘数据 + 内存索引,数据集可远超 RAM // StorageMode: borm.StorageModeMemory, // 全量内存驻留(旧行为,内存受限场景慎选) }) if err != nil { panic(err) } defer db.Close() // ---------- 写入(写事务,原子提交) ---------- err = db.Transaction(func(tx *borm.Tx) error { for i := 1; i <= 100; i++ { u := User{ID: uint64(i), Name: fmt.Sprintf("user_%d", i), Email: fmt.Sprintf("user%d@a.com", i), Age: 18 + i%50} if err := tx.Model(&User{}).Create(&u); err != nil { return err } } return nil }) if err != nil { panic(err) } // ---------- 查询(读会话) ---------- // 条件 + 排序 + 分页 var users []User err = borm.Model(db, &User{}). Where("Age", ">=", 30). Where("Name", "in", []any{"user_1", "user_2"}). OrderBy("Age", false). Offset(5). Limit(10). Find(&users) if err != nil { panic(err) } // 聚合 count, _ := borm.Model(db, &User{}).Count() maxAge, _ := borm.Model(db, &User{}).Max("Age") // 单条(dest 传结构体指针 或 切片指针,未命中返回 ErrRecordNotFound) var one User _ = borm.Model(db, &User{}).Where("ID", "==", 1).First(&one) // ---------- 更新 / 删除(写事务) ---------- _ = db.Transaction(func(tx *borm.Tx) error { if err := tx.Model(&User{}).Where("ID", "==", 1). Updates(map[string]any{"Age": 99, "Name": "admin"}); err != nil { return err } return tx.Model(&User{}).Where("ID", "<", 10).Delete() }) _ = count _ = maxAge fmt.Println("ok") }数据关联(Preload)
type User struct { ID uint64 `borm:"pk"` Name string Profile *Profile `borm:"rel:hasOne,through:Profile.UserID"` Posts []*Post `borm:"rel:hasMany,through:Post.UserID"` Tags []*Tag `borm:"rel:manyToMany,through:UserTag"` } type Profile struct { ID uint64 `borm:"pk"` UserID uint64 // FK → User.ID(与标签 through 字段对应) Email string } var users []User err := borm.Model(db, &User{}). Where("ID", "<", 10). Preload("Profile", "Posts", "Tags"). // 加载 hasOne / hasMany / 多对多 Find(&users)API 总览
顶层
API说明borm.Open(path, cfg) (*DB, error)打开/创建加密数据库,自动崩溃恢复db.Close()刷盘并关闭db.Transaction(fn) error写事务;fn 返回 nil 原子提交,否则整体回滚borm.Model(db, &T{}) *Tx创建查询会话(GORM 风格链式入口)
链式条件
方法说明Where(field, op, value)条件子句(AND),操作符大小写不敏感OrWhere(field, op, value)条件子句(OR)And(fn) / Or(fn)闭包逻辑过滤(func(record any) bool)WhereIn / WhereNotInIN / NOT INWhereLike / WhereEqLIKE 模糊 / 等值快捷Between(field, lo, hi)范围条件OrderBy(field, desc)排序Limit(n) / Offset(n)分页Distinct()去重
终结操作
方法说明Find(&slice)条件查询First(field, value, &obj)单条查询Pluck(field, &slice)单列查询Payload(&slice, args...)在 Find 后手动触发关联加载(可按 Tag 过滤/加条件)Count() (int, error)计数Exists() (bool, error)存在性判断Sum/Max/Min/Avg(field)聚合FindOrCreate(&obj)查无则插Create(&obj)写入(需写事务)Update(field, val) / Updates(map)更新(需写事务)Delete()删除匹配记录(需写事务,墓碑落盘)Preload("Rel1", "Rel2")关联加载
支持操作符与字段类型
== != > < >= <= in not_in between,以及 Like 模糊匹配(数值/字符串归一化比较)。字符串与 []byte 支持全部比较算子(字节序比较),bool 支持 == != in not_in。
支持字段类型:整型(uint8~uint64 / int8~int64)、string、bool、float32/float64、time.Time、[]byte;嵌套结构体与除 uint8 以外的切片/指针类型会返回 ErrUnsupportedType。
索引(borm:"index")
在结构体字段上加 borm:"index" 标签即为该字段建立 B+ 树索引(主键 borm:"pk" 自动建索引):
type Order struct { ID uint64 `borm:"pk"` // 主键,自动索引 User int `borm:"index"` // 二级索引 Price float64 `borm:"index"` // 二级索引 }任意「已索引字段」上的 == in > >= < <= between 查询都会走 B+ 树快路径; != not_in like 无界、且 B+ 树无剪枝收益,仍走内存全表扫描(语义完全一致)。
存储架构
cdb/ └── pkg/borm/ ├── api.go # Open/Close/Transaction/Stats,内存预算 O(1) 守护 ├── datastore.go # DataStore 抽象:memDataStore(内存模式)/ diskDataStore(磁盘模式) ├── crypto_io.go # 记录级 AES-256-GCM 加/解密 + 主文件/WAL 加载回路 ├── archival.go # 非阻塞后台归档 worker + 版本守卫 + 快照原子替换 ├── chain.go # 链式 API:条件拼装 + Create/Find/First/Count ├── chain_2.go # 聚合 + Update/Delete/FindOrCreate ├── chain_pk.go # B+ 树快路径候选收集(主键 / 二级索引字段) ├── chain_relation.go # Preload 关联加载(hasOne/hasMany/多对多) ├── rel_meta.go # 关联标签解析(rel:xxx,through:FK) ├── evaluator.go # 内存条件求值器(替代 SQL 解析) ├── schema.go # 结构体反射 + 字段映射缓存 ├── mvcc.go # MVCC 生/灭账本 + 快照锚点(可见性判据 aliveAt,零子句 Count/Exists 快路径) ├── index.go # 索引值编码(保序)+ B+ 树索引构建 ├── metrics.go # 读写计数 + 慢查询采样日志(SlowQueryThreshold) ├── lock.go # 文件锁(并发 Open 互斥) ├── config.go # 全局配置(含 StorageMode) ├── errors.go # 统一错误定义 ├── tx/ # 事务视图(读写会话状态) ├── storage/ # 磁盘 I/O、页面布局、变长整型、COW 缓冲 ├── wal/ # WAL 与检查点 └── crypto/ # AES-256-GCM + KDF行记录格式(精简二进制)
[NullBitmap(1B)] [FieldCount(1B)] [Varint 偏移序列] [PayloadData(紧凑二进制)]<>>
Config.StorageMode 选择数据的驻留位置,两种模式 API 完全一致(同一个 DataStore 抽象对外,上层无差别):
模式值数据驻留内存驻留适用场景磁盘(默认)StorageModeDiskbase + WAL(记录级 AES-GCM)仅 B+ 索引 + key→偏移 引用表 + 存活标记数据集可远超 RAM,冷读命中 OS 页缓存内存StorageModeMemorybase + WAL(同格式,供重启恢复)全量 memTable(所有行常驻)低延迟、小数据集(几十 MB 内)
go
db, err := borm.Open("app.db", &borm.Config{
SecretKey: []byte("0123456789abcdef0123456789abcdef"),
StorageMode: borm.StorageModeDisk, // 默认;改 StorageModeMemory 即内存模式
})
关键点:
# 核心稳定性(并发读写隔离、崩溃恢复、P0 六项、45000 条 save/load)
go test -count=1 -timeout 120s ./tests/
# DSL 全场景 + 关联加载
go test -count=1 -timeout 120s ./tests/dsl/
# 20 表 x 10 万条基准
go test -count=1 -timeout 600s ./tests/benchmark/
# 长时间混合读写 soak(默认 5 分钟 / 每表 5 万行,全部读写算子覆盖)
cd tests && rm -f soak_long_running.db* && \
timeout 600 go test -count=1 -timeout 540s -run TestLongRunningSoakMixedOps -v .
# 五维度综合性能评估:写/读/更新/删除/聚合 12 个 Benchmark
cd tests/benchmark && rm -f bm_*.db* && \
timeout 300 go test -count=1 -timeout 240s -run='^$' \
-bench 'BenchmarkEval_' -benchmem -benchtime=1000x .
# 综合报告:单库固定数据集,输出 Markdown 对照表 + 关键加速比
cd tests/benchmark && rm -f bm_report.db* && \
timeout 300 go test -count=1 -timeout 240s -run '^TestEvalReport$' -v .
# 百万行压测报告(单轮约 5 分钟)
cd tests/benchmark && rm -f bm_report1m.db* && \
timeout 900 go test -count=1 -timeout 840s -run '^TestEvalReport1M$' -v .
