model.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package DB
  2. import (
  3. "errors"
  4. "fmt"
  5. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  6. )
  7. // ============================================================================
  8. // 表基类: 会话绑定 + 头部增删改查
  9. // ============================================================================
  10. //
  11. // 三张公开面孔:
  12. //
  13. // BaseModel 主键表基类 = PrimaryModel (别名) 内嵌 → _id 列 + B+ 树主键索引
  14. // TimeSeriesModel 时序表基类 内嵌 → _ts 列 + 时序索引
  15. //
  16. // 表**必须**内嵌其中之一: 它既是"这是一张本框架的表"的标记 (ParseModel / UseTable
  17. // 对没有基类的结构体直接报 ErrNotModel), 也让对象的"头部"有了增删改查:
  18. //
  19. // type User struct {
  20. // DB.BaseModel
  21. // Name string `vrtx:"maxlen:32"`
  22. // }
  23. //
  24. // tbl, _ := DB.UseTable[User](db)
  25. // u := tbl.New() // 已绑定
  26. // u.Name = "a"
  27. // u.Insert() // 增 (自增主键自动回写 u.ID)
  28. // u.Name = "b"
  29. // u.Update() // 改 (按 u.ID)
  30. // u.Reload() // 查 (按 u.ID 重新读进 u)
  31. // u.Delete() // 删 (按 u.ID)
  32. //
  33. // 为什么要把 sess / self 存在基类里 —— Go 的嵌入不是继承:
  34. //
  35. // 方法被提升到外层结构体之后, 接收者仍然是那个被嵌入的基类值, 它没有任何办法回头
  36. // 拿到"外层对象"。而增删改查必须拿到外层对象 (要序列化整行、要把自增主键写回字段),
  37. // 所以基类只能自己持有它。这两个字段由 Table[T] 在绑定时写入 —— 只有那一层知道 T。
  38. // modelCore 三个公开基类的共同底座, 持有"这个对象属于哪张表"。
  39. //
  40. // 单独拆成一个非导出结构体, 而不是直接塞进 BaseModel:
  41. // - 时序表基类要用同一套增删改查, 但它不该被塞进一个根本用不上的 _id 字段;
  42. // - 增删改查只保留一份实现, 而不是每个基类各写一遍 (也就不会有哪一份忘了改)。
  43. type modelCore struct {
  44. sess session // 所属表 (Table[T] 的类型擦除视图); nil = 未绑定
  45. self any // 外层对象的指针 (*T), 由 Table 写入
  46. pkRowID uint64 // 引擎行号 (pkID); 0 = 未知。对用户隐藏, 只用来精确定位"自己那一行"
  47. }
  48. // session 是 Table[T] 的类型擦除视图: 模型基类只认这一组操作, 不认识 T。
  49. //
  50. // 每个方法内部都会把参数断言回 *T。断言失败只可能是"把 A 表的对象交给了 B 表",
  51. // 所以返回明确错误, 而不是 panic。
  52. type session interface {
  53. insert(obj any) error
  54. update(obj any) error
  55. pkOf(obj any) (uint64, error)
  56. loadInto(pk uint64, obj any) error
  57. reload(obj any) error
  58. deleteByPK(pk uint64) error
  59. deleteByRowID(pkID uint64) error
  60. tableName() string
  61. }
  62. // modelBinder 由内嵌的表基类提供, 让 Table 把"这个对象属于哪张表"写回基类,
  63. // 也让它把"这一行在引擎里的行号"带回对象。
  64. //
  65. // 走接口断言而不是反射: 绑定是每条被读写的记录都要过一遍的路径, 反射在这里是白付的。
  66. // 由于 UseTable 已经强制要求内嵌基类, 这里的断言必然成功 —— 保留失败分支只是为了让
  67. // Table[T] 的绑定路径不依赖"上游一定校验过"这个假设 (少一处只能靠推理保证的不变量)。
  68. type modelBinder interface {
  69. bindTo(s session, self any)
  70. rowID() uint64
  71. setRowID(pkID uint64)
  72. }
  73. // rowID 返回本对象的引擎行号 (pkID); 0 表示未知。
  74. //
  75. // 行号对用户完全隐藏 —— 它是引擎给每一行发的自增内部句柄 (类似 SQLite 的 rowid),
  76. // 不是表结构里的一列, 也不参与查询语义。它存在的唯一理由是"同一个时间戳下可以有多行":
  77. // 时间戳只说得清"落在哪个分段", 只有行号能指认"是分段里的哪一行"。没有它, 同刻的
  78. // 第二行就没法被读 / 改 / 删精确定位 (按时间键只会碰到第一行)。
  79. //
  80. // 由 Table[T] 在 Insert / Find / Select 之后写入本对象。
  81. func (c *modelCore) rowID() uint64 { return c.pkRowID }
  82. // setRowID 由 Table[T] 写入本对象的引擎行号。
  83. func (c *modelCore) setRowID(pkID uint64) { c.pkRowID = pkID }
  84. // rowIDOf / setRowIDOf 是包内工具: 对可能没有内嵌基类的对象安全地读写行号。
  85. func rowIDOf(obj any) uint64 {
  86. if b, ok := obj.(modelBinder); ok {
  87. return b.rowID()
  88. }
  89. return 0
  90. }
  91. func setRowIDOf(obj any, pkID uint64) {
  92. if b, ok := obj.(modelBinder); ok && pkID != 0 {
  93. b.setRowID(pkID)
  94. }
  95. }
  96. // bindTo 由 Table 调用: 写入会话与外层对象指针。
  97. func (c *modelCore) bindTo(s session, self any) {
  98. c.sess = s
  99. c.self = self
  100. }
  101. // Bound 报告本对象是否已绑定到表。
  102. func (c *modelCore) Bound() bool { return c != nil && c.sess != nil }
  103. // ready 检查绑定状态。未绑定时给出"接下来该做什么"的错误, 而不是一句 nil 崩溃。
  104. func (c *modelCore) ready(op string) error {
  105. if !c.Bound() {
  106. return fmt.Errorf("%w: 不能调用 %s (先用 Table.New 创建, 或 Table.Bind 绑定, "+
  107. "或先经过一次 Insert / Find)", ErrModelNotBound, op)
  108. }
  109. return nil
  110. }
  111. // ===== 头部增删改查 =====
  112. // Insert 插入本对象 (增)。主键字段为 0 时向引擎要一个自增号并回写本对象;
  113. // 显式给了主键就原样写入 (重复主键报 core.ErrDuplicatePK)。
  114. func (c *modelCore) Insert() error {
  115. if err := c.ready("Insert"); err != nil {
  116. return err
  117. }
  118. return c.sess.insert(c.self)
  119. }
  120. // Update 按本对象自己的主键更新 (改)。
  121. func (c *modelCore) Update() error {
  122. if err := c.ready("Update"); err != nil {
  123. return err
  124. }
  125. return c.sess.update(c.self)
  126. }
  127. // Delete 删除本对象所在的那一行 (删)。
  128. //
  129. // 优先按引擎行号删 (对象是从表里读出来的 / 插入过, 行号已知), 这样同一时间戳下
  130. // 有多行时删的确实是"自己那一行"; 行号未知时 (外部手工构造的对象) 才退回按可见
  131. // 主键 (_id / _ts) 删。
  132. func (c *modelCore) Delete() error {
  133. if err := c.ready("Delete"); err != nil {
  134. return err
  135. }
  136. if id := c.rowID(); id != 0 {
  137. return c.sess.deleteByRowID(id)
  138. }
  139. pk, err := c.PKErr()
  140. if err != nil {
  141. return err
  142. }
  143. if pk == 0 {
  144. return fmt.Errorf("%w: Delete 需要一个非 0 主键来定位行", ErrZeroPrimaryKey)
  145. }
  146. return c.sess.deleteByPK(pk)
  147. }
  148. // Save 没见过这一行就插入, 见过就更新 —— 不想区分"新对象 / 已有对象"时用它。
  149. //
  150. // "见过"以引擎行号为准 (插入过或从表里读出来的对象都有行号); 行号未知时退回首部的
  151. // 老约定: 可见主键为 0 当新对象插入。
  152. func (c *modelCore) Save() error {
  153. if err := c.ready("Save"); err != nil {
  154. return err
  155. }
  156. if c.rowID() != 0 {
  157. return c.sess.update(c.self)
  158. }
  159. pk, err := c.PKErr()
  160. if err != nil {
  161. return err
  162. }
  163. if pk == 0 {
  164. return c.sess.insert(c.self)
  165. }
  166. return c.sess.update(c.self)
  167. }
  168. // Load 按主键把整行读进本对象 (查)。
  169. //
  170. // 之所以是"填自己"而不是"返回新对象": 头部方法没有类型参数, 拿不到 T, 也就没法
  171. // 返回 *T。要一个新建的、类型安全的对象请用 Table[T].Find。
  172. func (c *modelCore) Load(pk uint64) error {
  173. if err := c.ready("Load"); err != nil {
  174. return err
  175. }
  176. return c.sess.loadInto(pk, c.self)
  177. }
  178. // Reload 重新读一遍本对象那一行 (行被别处改过时用它刷新)。
  179. //
  180. // 走的是 Table.reload: 行号已知就按行号读 —— 时序表里"按时间键读"只会读到该时刻的
  181. // 第一行, 拿到同刻多行中的第二行时那样会读串行。
  182. func (c *modelCore) Reload() error {
  183. if err := c.ready("Reload"); err != nil {
  184. return err
  185. }
  186. return c.sess.reload(c.self)
  187. }
  188. // PK 返回本对象当前的主键值; 取不到 (未绑定 / 非法) 时为 0。
  189. func (c *modelCore) PK() uint64 {
  190. pk, _ := c.PKErr()
  191. return pk
  192. }
  193. // PKErr 同 PK, 但把"未绑定"和"主键非法 (负数等)"的错误也交出来。
  194. func (c *modelCore) PKErr() (uint64, error) {
  195. if err := c.ready("PK"); err != nil {
  196. return 0, err
  197. }
  198. return c.sess.pkOf(c.self)
  199. }
  200. // TableName 返回本对象所属的表名 (未绑定时为空串)。
  201. func (c *modelCore) TableName() string {
  202. if !c.Bound() {
  203. return ""
  204. }
  205. return c.sess.tableName()
  206. }
  207. // ===== 公开基类 =====
  208. // BaseModel 主键表基类 —— 内嵌后自动获得 _id 主键列 (B+ 树主键索引) 和头部增删改查。
  209. //
  210. // type User struct {
  211. // DB.BaseModel
  212. // Name string `vrtx:"maxlen:32,index:idx_name"`
  213. // Age int64 `vrtx:"index:idx_age,type:hash"`
  214. // Score int // 自动 int→int64
  215. // }
  216. //
  217. // 主键可以被 vrtx:"pk" 覆盖成自己的字段 (见 types.go 的 ParseModel 说明):
  218. //
  219. // type Ticket struct {
  220. // DB.BaseModel
  221. // No int64 `vrtx:"pk"` // 主键列变成 No, _id 不再建列
  222. // Name string `vrtx:"maxlen:32"`
  223. // }
  224. type BaseModel struct {
  225. modelCore
  226. ID uint64 `vrtx:"_id"`
  227. }
  228. // PrimaryModel 主键表基类, 与 BaseModel 完全等价。
  229. //
  230. // 用别名而不是再定义一个同构结构体: 主键表基类本来就只有一种物理形态
  231. // (B+ 树主键索引 + 默认 _id 列), 复制一份结构体只会让 ParseModel 多出一个必须
  232. // 等价处理的分支 —— 多一份必须同步维护的"真相"。别名既给了这个名字, 又不引入第二份定义。
  233. type PrimaryModel = BaseModel
  234. // TimeSeriesModel 时序表基类 —— 内嵌后自动获得 _ts 时序键列 (时序索引) 和头部增删改查。
  235. //
  236. // type Sensor struct {
  237. // DB.TimeSeriesModel
  238. // Value float64
  239. // }
  240. //
  241. // 时序键同样可以用 vrtx:"pk" 换成自己的字段, 但注意两条约束:
  242. // - 键列必须是 64 位整数;
  243. // - 键列会被排到首列 (时序索引按行内固定偏移读时间键, 见 types.go)。
  244. //
  245. // 时序键就是"时间戳"本身, 约定用纳秒级 Unix 时间戳。Insert 时若没给 (==0), ORM 会补上
  246. // "写入这一刻"的纳秒时间戳, 而不是行号 —— 时序键只表达时间, 行号另有其人。
  247. //
  248. // type Tick struct {
  249. // DB.TimeSeriesModel
  250. // Stamp int64 `vrtx:"pk"` // 时序键列变成 Stamp, _ts 不再建列
  251. // Price float64
  252. // }
  253. //
  254. // 时序键不是引擎行号。同一个时间戳下允许存在多行 (同一时刻的多个测点 / 多次事件) ——
  255. // 时间键只决定"落在哪个分段", 行号才能指认"是分段里的哪一行"。行号由引擎自动分配,
  256. // 随对象在 Insert / Find / Select 之后带回 (对用户隐藏), 所以同刻多行各自都能被精确
  257. // 地改 / 删。详见 table.go 的 Insert / Update 与 core.Table 的 *ByPK 系列。
  258. type TimeSeriesModel struct {
  259. modelCore
  260. TS uint64 `vrtx:"_ts"`
  261. }
  262. // ModelSpec 表基类自报物理形态 —— 相当于其它语言里的虚函数。
  263. //
  264. // 之所以是接口而不是"基类里写死判断": ParseModel 只负责递归结构体, 主键列叫什么、
  265. // 一级索引是哪种, 由基类自己回答; 于是再加一种基类只需要实现这三个方法。
  266. //
  267. // 方法名非导出: 只允许本包的基类实现, 用户结构体的嵌入不会意外满足它。
  268. type ModelSpec interface {
  269. indexType() page.IndexType // 一级 (行存储) 索引类型
  270. pkColumn() string // 默认主键列名
  271. pkGoField() string // 默认主键列对应的 Go 字段名
  272. }
  273. func (BaseModel) indexType() page.IndexType { return page.IndexTypePrimary }
  274. func (BaseModel) pkColumn() string { return pkColumnName }
  275. func (BaseModel) pkGoField() string { return "ID" }
  276. func (TimeSeriesModel) indexType() page.IndexType { return page.IndexTypeTime }
  277. func (TimeSeriesModel) pkColumn() string { return tsColumnName }
  278. func (TimeSeriesModel) pkGoField() string { return "TS" }
  279. // ===== 错误 =====
  280. var (
  281. // ErrNotModel 类型没有内嵌 DB.BaseModel / DB.PrimaryModel / DB.TimeSeriesModel。
  282. // 表基类既是"这是一张本框架的表"的标记 (ParseModel / UseTable 会因此拒绝该类型),
  283. // 也是对象头部增删改查的来源。
  284. ErrNotModel = errors.New("类型未内嵌表基类")
  285. // ErrModelNotBound 在未绑定到表的对象上调用头部方法。
  286. ErrModelNotBound = errors.New("模型未绑定到表")
  287. // ErrZeroPrimaryKey 头部方法需要对象自带主键来定位行, 但主键为 0。
  288. ErrZeroPrimaryKey = errors.New("主键为 0")
  289. )