types.go 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. package DB
  2. import (
  3. "errors"
  4. "fmt"
  5. "math/big"
  6. "reflect"
  7. "sort"
  8. "strconv"
  9. "strings"
  10. "unicode"
  11. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  12. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  13. )
  14. // FieldDef ORM 字段定义
  15. type FieldDef struct {
  16. Name string // Go 字段名
  17. ColName string // 数据库列名 (来自 vrtx tag 或字段名)
  18. FieldType types.FieldType // 存储类型
  19. // Flag 列级标志 (随 schema 落盘, 直接透传给 types.FieldInfo.Flag):
  20. // - 低 5 位: 列级类型参数【按类型解释】——
  21. // FieldType_Decimal 是小数位数 scale (0..types.MaxDecimalScale),
  22. // 来自 `vrtx:"decimal:2"` / `vrtx:"scale:2"`;
  23. // FieldType_String 是行内宽度码, 来自
  24. // `vrtx:"maxlen:N"` (见 maxlenToColumn; N 的上界是
  25. // types.MaxStringSize, 超了在建模阶段就报错);
  26. // - 状态位: types.FlagNullable (来自 `vrtx:"nullable"`);
  27. // types.FlagUnique (来自 `vrtx:"unique"`, 唯一约束; NULL 不参与);
  28. // types.FlagDeleted 由核心层 DropField 置位, 声明侧不设。
  29. //
  30. // Decimal 的保序前提是"同一列 scale 一致", 字符串列的定宽前提是"同一列宽度码
  31. // 一致" —— 两者都必须作为列元数据存下来, 而不能只活在 Go 类型里 (见 types.FieldSize)。
  32. Flag byte
  33. GoKind reflect.Kind // Go 原始类型
  34. // GoPath 反射取值路径 (FieldByIndex 用)。ParseModel 时经 Type.FieldByName
  35. // 一次性解析 (含嵌入提升链); 逐行 structToFieldValues 直接按路径取字段,
  36. // 省掉每行每列一次 FieldByName 的类型级搜索 (批量灌数实测 ~8% CPU)。
  37. // 为 nil 时 (字段不在结构体里 / 提升路径特殊) 调用方退回 findField 老路径。
  38. GoPath []int
  39. Index int // 该字段在 AllFields 中的位置
  40. IsPK bool // 用户声明的主键字段 (vrtx:"pk" / 显式列名 _id)
  41. // ColNameBytes 是 ColName 的 []byte 形态, 建模时预烘焙并与 CoreFields 共享底层数组。
  42. // 逐行 structToFieldValues 用它填 FieldValue 的列名, 省掉每行每列一次
  43. // string→[]byte 堆分配 (列名在建模后不可变, 天然可复用)。
  44. ColNameBytes []byte
  45. }
  46. // Param 列级类型参数 (Flag 低 5 位): Decimal 读作 scale, FieldType_String 读作宽度码。
  47. func (fd FieldDef) Param() byte { return fd.Flag & types.FlagScaleMask }
  48. // Width 该列在行内的实际宽度 (字节)。
  49. //
  50. // 不能写成 types.GetSize(fd.FieldType): 那只是【类型默认】宽度, 对 `maxlen:1000`
  51. // 这种用宽度码声明出来的列会算成 256 —— ORM 侧任何按这个宽度补零/截断的地方, 都会
  52. // 与引擎按 1024 存下来的字节不同源。宽度只认这一处 (转发给 types.FieldSize)。
  53. func (fd FieldDef) Width() uint16 { return types.FieldSize(fd.FieldType, fd.Param()) }
  54. // IndexDef 从结构体标签解析的二级索引定义
  55. type IndexDef struct {
  56. Name string // 索引名 (空=自动生成 idx_表名_首个字段名)
  57. FieldID byte // 首个字段在 CoreFields 中的位置 (单列索引)
  58. FieldIDs []byte // 索引分量在 CoreFields 中的位置, 按键序 (复合索引)
  59. ColName string // 首个字段列名 (用于自动生成索引名)
  60. IndexType page.IndexType // 索引类型 (默认 BTree)
  61. Unique bool // 唯一索引: 同一索引键只允许一行, NULL 分量不参与 (可多行 NULL)
  62. }
  63. // coreFieldIDs 返回 core.CreateIndex 需要的分量列表
  64. func (d IndexDef) coreFieldIDs() []byte {
  65. if len(d.FieldIDs) > 0 {
  66. return d.FieldIDs
  67. }
  68. return []byte{d.FieldID}
  69. }
  70. // 自动索引名的前缀。
  71. //
  72. // 索引元数据链本来就是「每表独有」的 —— TableInfo.FirstIndexID 指向本表自己的
  73. // IndexMeta 链, 所以索引名只需要表内唯一, 表名前缀并不是唯一性所必需的。保留它
  74. // 纯粹是为了维持既有的 pk_<表名> / idx_<表名>_<列名> 命名, 不破坏已引用这两个名字的代码。
  75. //
  76. // 代价: 这两个前缀要占用 core.IndexNameSize 的额度 —— 注意是索引名的, 不是表名的。
  77. // 因为 IndexNameSize(64) 比 TableNameSize(48) 宽出 16 字节, 前缀这笔账只从索引名里出,
  78. // 表名依旧可以用满 48 —— 这正是把索引名单独放宽的目的。
  79. // UseTable 会在建表前把这笔账算清楚 (checkNameBudget), 而不是等到建索引时才报错。
  80. const (
  81. pkIndexPrefix = "pk_" // 主键索引: pk_<表名>
  82. idxIndexPrefix = "idx_" // 自动二级索引: idx_<表名>_<列名>
  83. )
  84. // 主键列默认列名: 引擎的行主键号 (pkID) 固定 8 字节, ORM 用这一列把它镜像进结构体。
  85. // BaseModel / PrimaryModel 默认就是这一列 (TimeSeriesModel 是 _ts), 表可以用 vrtx:"pk"
  86. // 换成自己的列名; 写 `vrtx:"_id"` 等价于声明这个列名作主键 (见 parseField)。
  87. const pkColumnName = "_id"
  88. // 时序表默认的时序键列名。时序键是该表的"主键" —— 它是时序索引的分段键, 决定这一行
  89. // 落在哪个时间段, 也是 ORM 头部 Load/Reload/Delete 默认的定位键。用户写 `vrtx:"pk"`
  90. // 时同样可以换成自己的列。
  91. //
  92. // 注意它【不是】引擎行号 (pkID): 时序索引的 DataPage 里, 行首 8 字节才是 pkID, 时间键
  93. // 是紧跟着的第一个字段。两者之所以必须分开, 是因为同一个时间戳下允许存在多行
  94. // (同一时刻的多个测点 / 多次事件) —— 时间键只能定位到时间段, 行号才能定位到具体那一行。
  95. //
  96. // 取值是纳秒级 Unix 时间戳; 插入时若没给 (==0), ORM 会补上当前纳秒时间 (见 table.go)。
  97. const tsColumnName = "_ts"
  98. // ErrInvalidPrimaryKey 主键定义非法: 类型不是整数、一个结构体声明了多个主键、
  99. // 或同时内嵌了多个表基类。具体原因见错误信息。
  100. var ErrInvalidPrimaryKey = errors.New("主键定义非法")
  101. // indexName 返回索引的最终名字: 显式命名优先, 否则 idx_表名_首个字段名
  102. func (d IndexDef) indexName(tableName string) string {
  103. if d.Name != "" {
  104. return d.Name
  105. }
  106. return idxIndexPrefix + tableName + "_" + d.ColName
  107. }
  108. // ModelInfo 模型元信息
  109. type ModelInfo struct {
  110. Fields []FieldDef // 用户定义字段 (不含自动主键)
  111. AllFields []FieldDef // 全部字段 (含自动主键,按存储顺序)
  112. PKField string // 主键列名 ("_id" 或 "_ts")
  113. PKIndex int // 主键在 AllFields 中的索引
  114. IsTimeSeries bool // 是否时序表
  115. CoreFields []types.FieldInfo // 传给 core 的字段列表 (含自动主键)
  116. CorePKField byte // 主键在 CoreFields 中的索引 (用于 CreateIndex)
  117. StructType reflect.Type // 结构体类型 (用于反射)
  118. PKInStruct bool // 主键字段是否在结构体中 (嵌入基类时有)
  119. Indexes []IndexDef // 从标签解析的二级索引定义
  120. // ColIndex 列名 → AllFields 下标, 建模时预建。逐行 fieldValuesToStruct 靠它
  121. // 直查字段, 省掉旧实现"每行建一张 colMap + 每列一次 string 分配"。
  122. ColIndex map[string]int
  123. }
  124. // goKindToFieldType Go 类型到数据库字段类型的默认映射
  125. //
  126. // 有符号与无符号必须落在不同的类型值上: Uint8..Uint64 是无符号列 (补码大端即其正确序),
  127. // 有符号整数走 Int8/Int16/Int32/Int64 (保序编码)。旧映射把二者都压在 Uint8..Uint64 上,
  128. // 于是 int64 列上的负数跨 0 的范围扫描 / ORDER BY 会静默漏行 —— 详见 types.FieldType_Int64。
  129. var goKindToFieldType = map[reflect.Kind]types.FieldType{
  130. reflect.Int8: types.FieldType_Int8,
  131. reflect.Int16: types.FieldType_Int16,
  132. reflect.Int32: types.FieldType_Int32,
  133. reflect.Int64: types.FieldType_Int64,
  134. reflect.Int: types.FieldType_Int64,
  135. reflect.Uint8: types.FieldType_Uint8,
  136. reflect.Uint16: types.FieldType_Uint16,
  137. reflect.Uint32: types.FieldType_Uint32,
  138. reflect.Uint64: types.FieldType_Uint64,
  139. // 浮点必须映射到浮点列。旧映射 (Float32→Uint32, Float64→Uint64) 是整数列,
  140. // 写入时按整数截断: float64 的 123.75 会安静地变成 123 (金额/比例失真)。
  141. //
  142. // Float32 单独立类型 (FieldType_Float32): 旧做法折进 Float64 让列宽翻倍,
  143. // 现在列宽 == Go 类型宽度, 且 32 位精度是用户自己声明的, 不该被悄悄放宽。
  144. reflect.Float32: types.FieldType_Float32,
  145. reflect.Float64: types.FieldType_Float64,
  146. // bool 单独立类型: 借用 Uint8 时"写 2 也算真", 等值/索引/DISTINCT 会给出多套答案。
  147. reflect.Bool: types.FieldType_Bool,
  148. }
  149. // goTypeToFieldType 按【完整 Go 类型】映射到列类型, 优先于 goKindToFieldType。
  150. //
  151. // 这些类型都不是"Kind 能区分"的: types.UUID 是 [16]byte (Kind=Array), types.Decimal /
  152. // big.Int 是结构体。它们自带语义, 所以字段不用写 tag 就能落成正确的列。
  153. //
  154. // 只列【非指针】类型: 查表前统一解了一层指针 (见 parseField 的 carrierType),
  155. // `*T` 命中 T 的条目即可 —— 指针在这里的语义只是"可为 NULL", 不是另一种列型。
  156. var goTypeToFieldType = map[reflect.Type]types.FieldType{
  157. reflect.TypeOf(types.Decimal{}): types.FieldType_Decimal,
  158. reflect.TypeOf(types.UUID{}): types.FieldType_UUID,
  159. reflect.TypeOf(big.Int{}): types.FieldType_Int128,
  160. // types.Blob 载体: "对象里直接持有流" —— 写侧 Source/Bytes 流式落链 (内存峰值
  161. // ~128KB, 与内容大小无关), 读侧引擎只填回 8B ref, 调 row.Body.Open(table) 按需
  162. // 拿 io.ReadCloser。不声明 tag 即自动推断为 blob 列 (无须 type:blob)。
  163. reflect.TypeOf(types.Blob{}): types.FieldType_Blob,
  164. }
  165. // tagFieldTypes vrtx tag 中 `type:<name>` 可声明的列类型。
  166. //
  167. // 为什么不复用 Go Kind 推断: 同一个 Go 类型可能对应多种列语义 —— string 可以是定长串 /
  168. // UUID 文本 / Decimal 文本, int16 可以是普通整数 / 年份, []byte 可以是普通变长列 /
  169. // 文件大对象。tag 是用户表达"这一列的语义"的地方; 没写 tag 时才退回 Go 类型推断。
  170. //
  171. // 两个约定 (由 TestTagFieldTypesNamesAreCanonical 守):
  172. //
  173. // 1. 名字必须与 migrate.TypeName 的输出逐字一致 —— 那是导出 schema 时写进
  174. // __vrtx_meta 的名字, 对不上的话 "dump 出来照着再声明一遍" 就读不回去了。
  175. // 2. 一个列型只留一个名字, 不收别名 (曾经有 bool/boolean、float32/f32、decimal/numeric
  176. // 三对)。别名看着无害, 实则没有文档、没有测试、也永远不会被 TypeName 吐出来,
  177. // 只是给"同一列型两种写法"开口子。
  178. var tagFieldTypes = map[string]types.FieldType{
  179. "bool": types.FieldType_Bool,
  180. "float32": types.FieldType_Float32,
  181. "float64": types.FieldType_Float64,
  182. "decimal": types.FieldType_Decimal,
  183. "uuid": types.FieldType_UUID,
  184. "year": types.FieldType_Year,
  185. "int128": types.FieldType_Int128,
  186. "blob": types.FieldType_Blob,
  187. }
  188. // tagTypeNames 列出 `type:<name>` 可用的全部名称 (排序, 供报错信息使用)。
  189. //
  190. // 从 map 生成而不是手写一串: 手写的那份已经漏过一次 —— 新增 blob 时没同步, 于是
  191. // "可用类型"的提示里始终看不到 blob, 而报错信息正是用户找答案的地方。
  192. func tagTypeNames() string {
  193. names := make([]string, 0, len(tagFieldTypes))
  194. for n := range tagFieldTypes {
  195. names = append(names, n)
  196. }
  197. sort.Strings(names)
  198. return strings.Join(names, "/")
  199. }
  200. var (
  201. baseModelType = reflect.TypeOf(BaseModel{})
  202. timeSeriesModelType = reflect.TypeOf(TimeSeriesModel{})
  203. )
  204. // ParseModel 解析结构体,提取模型信息
  205. //
  206. // obj 必须是指向结构体的指针 (可以是零值), 且该结构体必须内嵌表基类
  207. // (DB.BaseModel / DB.PrimaryModel / DB.TimeSeriesModel) —— 否则返回 ErrNotModel。
  208. // "内嵌基类"既是"这是一张本框架的表"的标记, 也是头部增删改查的来源, 详见 requireBase。
  209. func ParseModel(obj interface{}) (*ModelInfo, error) {
  210. t := reflect.TypeOf(obj)
  211. if t.Kind() == reflect.Ptr {
  212. t = t.Elem()
  213. }
  214. if t.Kind() != reflect.Struct {
  215. return nil, fmt.Errorf("vrtx: obj must be a struct, got %s", t.Kind())
  216. }
  217. s := newModelScanner()
  218. if err := s.scan(t); err != nil {
  219. return nil, err
  220. }
  221. return s.modelInfo(t)
  222. }
  223. // baseSpecOf 判断一个嵌入字段是不是表基类, 并取出它自报的物理形态。
  224. //
  225. // 只认本包的公开基类, 不做接口通配: 用户自己写的嵌入结构体里也可能嵌着 BaseModel,
  226. // 那种情况应当继续递归展开它内部的字段, 而不是整块当成表基类 (否则它内部声明的字段
  227. // 会凭空消失)。再加一种基类时, 只需要让它实现 ModelSpec 并登记到这里。
  228. func baseSpecOf(t reflect.Type) (ModelSpec, bool) {
  229. if t != baseModelType && t != timeSeriesModelType {
  230. return nil, false
  231. }
  232. // ModelSpec 的三个方法都是值接收者, 零值即可断言。
  233. spec, ok := reflect.Zero(t).Interface().(ModelSpec)
  234. return spec, ok
  235. }
  236. // scannedField 一个已解析的用户字段 (不含基类带来的主键列)。
  237. type scannedField struct {
  238. def FieldDef
  239. idxDef *IndexDef
  240. }
  241. // modelScanner 逐层展开结构体, 收集用户字段并识别表基类 / 显式主键。
  242. //
  243. // 之所以要"先认全、再决定": 主键列既可能来自基类 (_id / _ts), 也可能被 vrtx:"pk"
  244. // 覆盖, 只有把两类候选都看完, 才知道 AllFields 里该放哪一列。
  245. type modelScanner struct {
  246. fields []scannedField
  247. spec ModelSpec // 内嵌的表基类自报的物理形态
  248. baseType reflect.Type // 基类的具体类型 (报错用)
  249. hasPK bool // 用户用 vrtx:"pk" 显式声明了主键
  250. pkField int // 显式主键在 fields 中的下标
  251. }
  252. func newModelScanner() *modelScanner {
  253. return &modelScanner{pkField: -1}
  254. }
  255. // exportName 给出字段名的导出形态 (首字母大写), 仅用于未导出字段的报错提示。
  256. func exportName(name string) string {
  257. r := []rune(name)
  258. if len(r) == 0 {
  259. return name
  260. }
  261. r[0] = unicode.ToUpper(r[0])
  262. return string(r)
  263. }
  264. func (s *modelScanner) scan(t reflect.Type) error {
  265. for i := 0; i < t.NumField(); i++ {
  266. sf := t.Field(i)
  267. if sf.Anonymous {
  268. // 表基类: 记下它自报的物理形态, 不当普通字段展开。
  269. // 它内部的 modelCore / ID 都是运行时状态, 不该变成列。
  270. if spec, ok := baseSpecOf(sf.Type); ok {
  271. if s.spec != nil {
  272. return fmt.Errorf("%w: 一张表只能内嵌一个表基类, 已内嵌 %s, 不能再内嵌 %s "+
  273. "(主键索引与时序索引都是行存储索引, 一张表只能有一个)",
  274. ErrInvalidPrimaryKey, s.baseType.Name(), sf.Type.Name())
  275. }
  276. s.spec, s.baseType = spec, sf.Type
  277. continue
  278. }
  279. // 其他嵌入结构体: 递归展开
  280. if sf.Type.Kind() == reflect.Struct {
  281. if err := s.scan(sf.Type); err != nil {
  282. return err
  283. }
  284. }
  285. continue
  286. }
  287. // 未导出的普通字段在建模阶段就拒: reflect 拿不到 (更谈不上设置) 未导出字段,
  288. // 留它进模型等于把 panic 推迟到首次 Insert 的 structToFieldValues。
  289. // 匿名/嵌入字段不在这里拦 —— 常见写法 (内嵌 DB.BaseModel) 的嵌入名是导出的,
  290. // 未导出嵌入结构体的兼容处理维持原样。
  291. if !sf.IsExported() {
  292. return fmt.Errorf("vrtx: 字段 %s 是未导出字段, ORM 无法通过反射读写它; "+
  293. "请导出为 %s (首字母大写) 或从结构体中移除", sf.Name, exportName(sf.Name))
  294. }
  295. fd, idxDef, err := parseField(sf, len(s.fields))
  296. if err != nil {
  297. return fmt.Errorf("field %s: %w", sf.Name, err)
  298. }
  299. s.fields = append(s.fields, scannedField{def: fd, idxDef: idxDef})
  300. if fd.IsPK {
  301. if err := s.declarePK(sf); err != nil {
  302. return err
  303. }
  304. }
  305. }
  306. return nil
  307. }
  308. // declarePK 登记用户用 `vrtx:"pk"` (或显式列名 _id / _ts) 声明的主键。
  309. //
  310. // 类型必须是 64 位整数: 引擎的主键号 (pkID) 就是 uint64, 而且这一列会被当作 pkID
  311. // 的镜像 (插入后回写、Find 反查都靠它)。窄整数装不下时 setPKValue 会静默截断,
  312. // 字符串 / 浮点 / 切片则根本无法承载 pkID —— 与其留一个查不到的行, 不如直接报错。
  313. //
  314. // 它优先于基类带来的默认主键列: 基类给的是"默认样式", 用户显式声明了就听用户的 ——
  315. // 这正是"每个表自己设计主键"的入口。
  316. func (s *modelScanner) declarePK(sf reflect.StructField) error {
  317. if s.hasPK {
  318. return fmt.Errorf("%w: 字段 %s 与 %s 都声明了主键, 一个表只能有一个主键",
  319. ErrInvalidPrimaryKey, sf.Name, s.fields[s.pkField].def.Name)
  320. }
  321. switch sf.Type.Kind() {
  322. case reflect.Int, reflect.Uint, reflect.Int64, reflect.Uint64:
  323. // 这四种在 goKindToFieldType 里都映射到 8 字节列, 与 pkID 同宽
  324. default:
  325. return fmt.Errorf("%w: 字段 %s 是 %s, 主键只能是 64 位整数 (int/uint/int64/uint64); 字符串不能当主键",
  326. ErrInvalidPrimaryKey, sf.Name, sf.Type)
  327. }
  328. s.hasPK, s.pkField = true, len(s.fields)-1
  329. return nil
  330. }
  331. // modelInfo 把扫描结果组装成 ModelInfo。主键列的落位有两条规则, 都有实际原因:
  332. //
  333. // 1. vrtx:"pk" 覆盖基类的默认列 —— 基类给的是默认样式, 用户声明了就听用户的。
  334. // 2. 主键列固定排在 AllFields / CoreFields 第一位。行内布局是
  335. // [pkID(8B)][schemaVer(1B)][字段...], 而时序索引按固定偏移 9 读第一列当时间键
  336. // (core/index.timeRowInRange、TimeDataPage.FindRowByIndexKey), 所以时序键必须落在
  337. // 第一列。与其要求用户把字段写在基类前面, 不如统一排到最前。
  338. // 行镜像是按列名写入的 (core.serializeRow 会 lookupFieldValue 找列),
  339. // 字段顺序只影响新建表时的列序, 因此这个调整对已有表也安全, 不会把数据写错位。
  340. //
  341. // 进到这里之前必须已经确认内嵌了表基类 (见 requireBase): 没有基类就没有默认键列,
  342. // 一级索引用哪种、行主键号往哪个字段回写都无从谈起。
  343. func (s *modelScanner) modelInfo(t reflect.Type) (*ModelInfo, error) {
  344. if err := s.requireBase(t); err != nil {
  345. return nil, err
  346. }
  347. info := &ModelInfo{StructType: t}
  348. pk := s.primaryKey()
  349. info.AllFields = append(info.AllFields, pk)
  350. info.PKField = pk.ColName
  351. info.PKIndex = 0
  352. info.PKInStruct = true
  353. info.IsTimeSeries = s.spec.indexType() == page.IndexTypeTime
  354. // 每个用户字段落在 AllFields 的位置
  355. pos := make([]int, len(s.fields))
  356. for i, f := range s.fields {
  357. if s.hasPK && i == s.pkField {
  358. // 显式主键: 字段本身仍然留在 Fields 里 (调用方靠它取名 / 取类型),
  359. // 但列定义已经作为主键放进 AllFields, 这里不重复追加。
  360. fd := f.def
  361. fd.Index = 0
  362. info.Fields = append(info.Fields, fd)
  363. continue
  364. }
  365. fd := f.def
  366. fd.Index = len(info.AllFields)
  367. pos[i] = fd.Index
  368. info.Fields = append(info.Fields, fd)
  369. info.AllFields = append(info.AllFields, fd)
  370. }
  371. info.Indexes = s.indexes(pos)
  372. // 反射取值路径一次性解析 (Type.FieldByName 含嵌入提升链)。
  373. // 逐行 structToFieldValues 走 FieldByIndex 快路径, 省掉每行每列一次
  374. // FieldByName 的类型级宽度搜索; 解析不到的 (自动 _id 列等) 留 nil,
  375. // 调用方退回 findField 老路径, 语义不变。
  376. for i := range info.AllFields {
  377. if sf, ok := t.FieldByName(info.AllFields[i].Name); ok {
  378. info.AllFields[i].GoPath = sf.Index
  379. }
  380. }
  381. // 列上的 FlagUnique 只反映【该列 tag 里写了 unique】(由 parseField 置位), 这里
  382. // 【不】按索引分量补齐。原因是补齐会撒谎, 而且会骗到"从 schema 反推 tag"的工具:
  383. // - 复合唯一索引 (index:NAME 多列 + 其中一列写 unique) 约束的是整组列, 给每个
  384. // 分量都打上唯一位, 读起来就成了"这一列单独唯一" —— 恰好是它的反面;
  385. // - 工具照这个位回写 vrtx tag 时, 会给 B 也加上 unique, 而 unique 在 ORM 里
  386. // 是"该列单独建一个唯一索引", 于是凭空多出一条约束。
  387. // 复合键覆盖了哪些列, 由 IndexDef / IndexMeta 的 FieldIDs 表达 (那才是权威)。
  388. // 构建 CoreFields。Flag 必须一并带过去: Decimal 的列级 scale、可空位与唯一位都存在
  389. // 这里, 写入/索引编码都要拿它对齐 (缺失会退化成"按值自带 scale", 破坏同列保序前提)。
  390. // 顺带预烘焙 ColNameBytes (与 FieldName 共享数组) 与 ColIndex —— 逐行转换的热路径
  391. // (structToFieldValues / fieldValuesToStruct) 直接取用, 零分配。
  392. info.CoreFields = make([]types.FieldInfo, len(info.AllFields))
  393. info.ColIndex = make(map[string]int, len(info.AllFields))
  394. for i, f := range info.AllFields {
  395. info.CoreFields[i] = types.FieldInfo{
  396. FieldType: f.FieldType,
  397. FieldName: []byte(f.ColName),
  398. Flag: f.Flag,
  399. }
  400. info.AllFields[i].ColNameBytes = info.CoreFields[i].FieldName
  401. info.ColIndex[f.ColName] = i
  402. }
  403. info.CorePKField = byte(info.PKIndex)
  404. return info, nil
  405. }
  406. // requireBase 强制"表必须内嵌基类"。
  407. //
  408. // 为什么把它做成硬约束, 而不是"没有基类也行、只是没有头部方法":
  409. //
  410. // - 基类是"这是一张本框架的表"的唯一凭据。没有它, ORM 只能靠猜 —— 猜哪一列是行
  411. // 主键号的镜像、一级索引用 Primary 还是 Time; 猜错的代价是一张"插入时不回写主键、
  412. // 按写入值查不到"的表 (旧的 struct{} 兜底分支就是这个下场)。
  413. // - 头部增删改查 (Insert/Update/Delete/Load...) 由基类提供。允许没有基类的表存在,
  414. // 等于允许一半的表用 Table[T] 的 API、另一半的表两种都能用 —— 同一个项目里两套
  415. // 并列的写法, 新人和代码审查都得先判断"这张表属于哪种"。
  416. // - 校验放在建表之前, 所以不会留下半成品: 报错时库里什么都没有。
  417. //
  418. // 迁移 (migrate 包) 是按 SQL 元数据动态建表的, 那时根本没有 Go 结构体可内嵌 ——
  419. // 它走 core 层 API (db.CreateTable), 与本约束无关。
  420. func (s *modelScanner) requireBase(t reflect.Type) error {
  421. if s.spec != nil {
  422. return nil
  423. }
  424. if s.hasPK {
  425. // 单写 vrtx:"pk" 是个很自然的误解: 它看起来"已经声明了主键"。
  426. // 点明它只是"覆盖基类默认键列", 否则用户会以为是别的地方出了错。
  427. return fmt.Errorf("%w: %s 只声明了 vrtx:\"pk\", 但没内嵌表基类 —— "+
  428. "pk 标签是用来覆盖基类默认键列的 (BaseModel 的 _id / TimeSeriesModel 的 _ts), "+
  429. "不能替代基类本身", ErrNotModel, t)
  430. }
  431. return fmt.Errorf("%w: %s 必须内嵌 DB.BaseModel / DB.PrimaryModel (主键表) 或 "+
  432. "DB.TimeSeriesModel (时序表)", ErrNotModel, t)
  433. }
  434. // primaryKey 返回主键列定义: 用户用 vrtx:"pk" 显式声明的优先, 否则用基类自报的默认键列
  435. // (_id / _ts)。两者必有其一 —— 调用前 requireBase 已经保证了基类存在。
  436. func (s *modelScanner) primaryKey() FieldDef {
  437. if s.hasPK {
  438. f := s.fields[s.pkField].def
  439. f.IsPK = true
  440. f.Index = 0
  441. // 主键列必须钉在 FieldType_Uint64 (无符号 64 位) 上, 不能沿用字段自身的有符号类型
  442. // (FieldType_Int*)。两条原因都不是风格问题:
  443. // 1. 这一列是引擎行主键号 pkID (uint64) 的镜像, 主键 B+ 树按它的补码字节序排序;
  444. // 2. 时序表里它就是 _ts —— 时序索引与时间范围
  445. // 过滤都按 int64 补码直接读这 8 个字节, 换成保序编码会让时间轴整体错位。
  446. // 字段被声明成 pk 时值仍经 toUint64 归一化后写入 (getPKValue 已拒绝负数),
  447. // 盘上字节与默认主键完全一致, 因此按 _id 查询/排序的行为不变。
  448. f.FieldType = types.FieldType_Uint64
  449. f.GoKind = reflect.Uint64
  450. return f
  451. }
  452. return FieldDef{
  453. Name: s.spec.pkGoField(),
  454. ColName: s.spec.pkColumn(),
  455. FieldType: types.FieldType_Uint64,
  456. GoKind: reflect.Uint64,
  457. Index: 0,
  458. }
  459. }
  460. // indexes 收集所有标记了 index 的字段的索引定义。
  461. //
  462. // 同一个具名索引 (index:NAME) 被多个字段声明时, 合并成一个复合索引:
  463. // 分量顺序 = 字段在表中的物理顺序。这样无需在 tag 里额外表达键顺序,
  464. // 也正好对上复合索引"最左前缀"的用法 (a=? 或 a=? AND b>?)。
  465. // 未命名的 index 仍各自成单列索引。同名时以第一个声明为准 (含索引类型)。
  466. func (s *modelScanner) indexes(pos []int) []IndexDef {
  467. var out []IndexDef
  468. named := make(map[string]int, len(s.fields))
  469. for i, f := range s.fields {
  470. if f.idxDef == nil {
  471. continue
  472. }
  473. if s.hasPK && i == s.pkField {
  474. continue // 主键列本身就是一级索引, 不再单独建二级索引
  475. }
  476. at := pos[i]
  477. if f.idxDef.Name != "" {
  478. if n, ok := named[f.idxDef.Name]; ok {
  479. // 同名 → 追加为新的索引分量
  480. out[n].FieldIDs = append(out[n].FieldIDs, byte(at))
  481. out[n].Unique = out[n].Unique || f.idxDef.Unique
  482. continue
  483. }
  484. }
  485. d := *f.idxDef
  486. d.FieldID = byte(at)
  487. d.FieldIDs = []byte{byte(at)}
  488. d.ColName = f.def.ColName
  489. out = append(out, d)
  490. if d.Name != "" {
  491. named[d.Name] = len(out) - 1
  492. }
  493. }
  494. return out
  495. }
  496. // parseField 解析单个结构体字段
  497. // 返回: FieldDef, *IndexDef (如果有索引声明), error
  498. func parseField(sf reflect.StructField, idx int) (FieldDef, *IndexDef, error) {
  499. // carrierType 列载体类型: 指针载体 `*T` 的列语义由元素类型 T 决定 —— nil ↔ NULL
  500. // (见 convert.go 的 nil 判定), 非 nil 时按 T 的值写入/读回。解一层指针之后,
  501. // *int64 与 int64 落在同一个列型上, 只是多了"可以整列为空"这一档。
  502. carrierType := sf.Type
  503. if carrierType.Kind() == reflect.Ptr {
  504. carrierType = carrierType.Elem()
  505. }
  506. fd := FieldDef{
  507. Name: sf.Name,
  508. GoKind: carrierType.Kind(),
  509. Index: idx,
  510. }
  511. var idxDef *IndexDef
  512. // 列名: 优先使用 vrtx tag 中的列名
  513. fd.ColName = sf.Name
  514. tag, hasTag := sf.Tag.Lookup("vrtx")
  515. if !hasTag {
  516. tag, hasTag = sf.Tag.Lookup("tsdb") // 向后兼容: 也检查 tsdb 标签
  517. }
  518. if hasTag && tag != "" {
  519. parts := parseTag(tag)
  520. // 分类解析每个 part
  521. maxlen := 0
  522. hasIndex := false
  523. idxName := ""
  524. idxType := page.IndexTypeBTree // 默认 B+树索引
  525. typeSet := false // tag 里显式写了列类型
  526. scaleSet := false // tag 里显式写了 decimal scale
  527. for _, p := range parts {
  528. p = strings.TrimSpace(p)
  529. if p == "" {
  530. continue
  531. }
  532. // 1. 检查是否是索引声明: "index" 或 "index:NAME"
  533. if p == "index" || strings.HasPrefix(p, "index:") {
  534. hasIndex = true
  535. if strings.HasPrefix(p, "index:") {
  536. idxName = p[6:] // 去掉 "index:" 前缀
  537. }
  538. continue
  539. }
  540. // 2. 检查 key:value 选项 (冒号分隔)
  541. if strings.Contains(p, ":") && !strings.HasPrefix(p, "index:") {
  542. kv := strings.SplitN(p, ":", 2)
  543. key, val := kv[0], kv[1]
  544. switch key {
  545. case "type":
  546. // type:hash / type:btree 是索引类型; 其余值是列类型
  547. // (type:decimal / type:uuid / ...), 两者共用一个 key 但命名空间不冲突。
  548. switch val {
  549. case "hash":
  550. idxType = page.IndexTypeHash
  551. case "btree":
  552. idxType = page.IndexTypeBTree
  553. default:
  554. ft, ok := tagFieldTypes[strings.ToLower(strings.TrimSpace(val))]
  555. if !ok {
  556. return fd, nil, fmt.Errorf("未知字段类型 %q (可选: %s)", val, tagTypeNames())
  557. }
  558. fd.FieldType = ft
  559. typeSet = true
  560. }
  561. case "decimal":
  562. // decimal:N 是 `type:decimal,scale:N` 的简写。
  563. n, err := strconv.Atoi(strings.TrimSpace(val))
  564. if err != nil || n < 0 || n > types.MaxDecimalScale {
  565. return fd, nil, fmt.Errorf("decimal 精度必须是 0..%d 的整数, 得到 %q",
  566. types.MaxDecimalScale, val)
  567. }
  568. fd.FieldType = types.FieldType_Decimal
  569. // 只动低 5 位的 scale: 它与 nullable 等状态位共用一个字节, 不能整体覆盖。
  570. fd.Flag = fd.Flag&^types.FlagScaleMask | byte(n)
  571. typeSet, scaleSet = true, true
  572. case "scale":
  573. n, err := strconv.Atoi(strings.TrimSpace(val))
  574. if err != nil || n < 0 || n > types.MaxDecimalScale {
  575. return fd, nil, fmt.Errorf("decimal scale 必须是 0..%d 的整数, 得到 %q",
  576. types.MaxDecimalScale, val)
  577. }
  578. fd.Flag = fd.Flag&^types.FlagScaleMask | byte(n)
  579. scaleSet = true
  580. case "maxlen":
  581. // maxlen 的单位是【字节】(与 types.FieldSize 同口径), 不是字符数:
  582. // 引擎的行布局按字节算, 拿字符数去声明会让 utf8mb4 中文列 (3 字节/字)
  583. // 实际容量只有声明的 1/3。需要按字符数声明时请乘上编码宽度。
  584. if n, err := strconv.Atoi(val); err == nil && n > 0 {
  585. maxlen = n
  586. }
  587. }
  588. continue
  589. }
  590. // 列可空声明: `vrtx:"nullable"` / `vrtx:"null"` —— 允许该列写入 NULL,
  591. // 行内用 NULL 位图记录 (见 types.NullBitmapLen)。未声明即 NOT NULL。
  592. if p == "nullable" || p == "null" {
  593. fd.Flag |= types.FlagNullable
  594. continue
  595. }
  596. // 3. 检查是否是 "unique": 唯一约束。
  597. // 同时打两个标记 —— 列上的 FlagUnique (声明) 与索引上的 Unique (执行者):
  598. // 唯一性判定要一次索引查找, 光有列标记无法执行约束; 而只有索引标记则
  599. // "哪些列被约束"要反查索引元数据才看得见。两者都写, 各司其职。
  600. if p == "unique" {
  601. if idxDef == nil {
  602. idxDef = &IndexDef{}
  603. }
  604. idxDef.Unique = true
  605. fd.Flag |= types.FlagUnique
  606. hasIndex = true
  607. continue
  608. }
  609. // 3.5 检查是否是主键声明: 该字段就是本表主键。
  610. // 时序表嵌了 TimeSeriesModel 时它同样成立 —— 声明的那一列会成为时序键
  611. // (时序索引的分段键), 而基类自带的 _ts 不再建列。
  612. // 类型校验在 declarePK 里做 —— 字符串等非整数类型直接报错。
  613. if p == "pk" {
  614. fd.IsPK = true
  615. continue
  616. }
  617. // 4. 其他: 视为列名
  618. fd.ColName = p
  619. }
  620. // 显式把列名写成 _id / _ts 等价于声明主键: 这两列正是引擎行主键号 (pkID) 的
  621. // 镜像列, 旧实现会把这种字段当普通列 —— 插入时既不填号也不回写, 静默失效。
  622. if fd.ColName == pkColumnName || fd.ColName == tsColumnName {
  623. fd.IsPK = true
  624. }
  625. // 主键列本身就是唯一的 (InsertRowWithID 用 ErrDuplicatePK 兜着), 而 indexes()
  626. // 也不会给主键列建二级索引 —— 留着 FlagUnique 只会让人以为"另有一个唯一索引
  627. // 在约束它", 实际上什么都没建。声明侧直接抹掉这个位。
  628. if fd.IsPK {
  629. fd.Flag &^= types.FlagUnique
  630. }
  631. // 处理 maxlen: 只在没有显式列类型时生效 (显式列类型优先)
  632. if maxlen > 0 && !typeSet {
  633. if sf.Type.Kind() == reflect.String {
  634. ft, code, err := maxlenToColumn(maxlen)
  635. if err != nil {
  636. // 声明了引擎兑现不了的容量: 在 ParseModel 阶段就拒, 不留"表建好了、
  637. // 某天写入才失败"的半成品 (与 validateFieldTypeParam 同一原则)。
  638. return fd, nil, fmt.Errorf("字段 %s: %w", fd.ColName, err)
  639. }
  640. fd.FieldType = ft
  641. // 宽度码写进 Flag 低 5 位: 它就是这一列的定宽依据, 随 schema 落盘,
  642. // 与 decimal 的 scale 共用同一格 (按类型解释, 见 types.FieldSize)。
  643. if code > 0 {
  644. fd.Flag = fd.Flag&^types.FlagScaleMask | code
  645. }
  646. }
  647. // 数值类型忽略 maxlen (用 Go 类型自动推断)
  648. }
  649. // 只写了 scale 就默认这是 decimal 列 (scale 是 decimal 独有的参数)。
  650. if scaleSet && fd.FieldType == 0 && !typeSet {
  651. fd.FieldType = types.FieldType_Decimal
  652. }
  653. // 处理索引声明
  654. if hasIndex {
  655. if idxDef == nil {
  656. idxDef = &IndexDef{}
  657. }
  658. idxDef.Name = idxName
  659. idxDef.IndexType = idxType
  660. }
  661. }
  662. // 字段类型: tag 没显式指定时按 Go 类型推断。
  663. // 先按完整类型 (Decimal/UUID/big.Int 这类自带语义的类型), 再按 Kind。
  664. // 两处都看 carrierType: `*int64` 与 `int64` 推出同一个列型 —— 指针只是多了
  665. // "整列为空"这一档, 不该另立一门列型。
  666. if fd.FieldType == 0 {
  667. if ft, ok := goTypeToFieldType[carrierType]; ok {
  668. fd.FieldType = ft
  669. } else if ft, ok := goKindToFieldType[carrierType.Kind()]; ok {
  670. fd.FieldType = ft
  671. } else if carrierType.Kind() == reflect.String {
  672. fd.FieldType = types.FieldType_String // string 默认 256 字节
  673. } else if carrierType.Kind() == reflect.Slice && carrierType.Elem().Kind() == reflect.Uint8 {
  674. fd.FieldType = types.FieldType_Dynamicbyte
  675. } else {
  676. return fd, nil, fmt.Errorf("unsupported type: %s", sf.Type.String())
  677. }
  678. }
  679. // 类型参数校验: 非法组合在建表前就报错, 不留半成品。
  680. if err := validateFieldTypeParam(fd, sf); err != nil {
  681. return fd, nil, err
  682. }
  683. return fd, idxDef, nil
  684. }
  685. // validateFieldTypeParam 校验"列类型 + Go 类型 + Flag"这个组合是否成立。
  686. //
  687. // 这些检查都发生在 UseTable 建表之前: 让模型一开始就被拒, 比等到插入时才在
  688. // 某一行上报错好 —— 后者会留下"表建好了、数据写不进"的半成品状态。
  689. func validateFieldTypeParam(fd FieldDef, sf reflect.StructField) error {
  690. // 主键列不能声明 NULL: pkID 是行内固定 8 字节, 没有"缺失"这一档。
  691. if fd.IsPK && fd.Flag&types.FlagNullable != 0 {
  692. return fmt.Errorf("主键列 %s 不能声明 nullable", fd.ColName)
  693. }
  694. // 载体类型 = 解一层指针 (与 parseField 的推断口径一致): 下面校验的都是"这一列装
  695. // 什么值", 而 `*T` 装的就是 T, 指针本身只是 NULL 的表示法。
  696. carrier := sf.Type
  697. if carrier.Kind() == reflect.Ptr {
  698. carrier = carrier.Elem()
  699. }
  700. switch fd.FieldType {
  701. case types.FieldType_Decimal:
  702. if scale := fd.Flag & types.FlagScaleMask; scale > types.MaxDecimalScale {
  703. return fmt.Errorf("decimal scale=%d 超过上限 %d", scale, types.MaxDecimalScale)
  704. }
  705. // decimal 必须有可解析的载体, 否则写入时才发现就晚了。
  706. if !decimalCarrier(carrier) {
  707. return fmt.Errorf("decimal 列需要 types.Decimal / string / 数值类型, 不能是 %s", sf.Type)
  708. }
  709. case types.FieldType_UUID:
  710. if !uuidCarrier(carrier) {
  711. return fmt.Errorf("uuid 列需要 types.UUID / [16]byte / string / []byte, 不能是 %s", sf.Type)
  712. }
  713. case types.FieldType_Int128:
  714. if !int128Carrier(carrier) {
  715. return fmt.Errorf("int128 列需要 big.Int / *big.Int / types.Decimal / 整数 / string, 不能是 %s", sf.Type)
  716. }
  717. case types.FieldType_Year:
  718. // 解码载体是 int16, 所以只收整型载体 —— time.Time 能写不能读回, 收它等于埋雷。
  719. switch carrier.Kind() {
  720. case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint16:
  721. default:
  722. return fmt.Errorf("year 列需要整数载体 (int/int16/int32/int64/uint16), 不能是 %s", sf.Type)
  723. }
  724. case types.FieldType_Float32:
  725. // 单精度列只收浮点载体: 装整数没有意义, 且会把"写进去的数被悄悄放宽"这件事
  726. // 藏起来。float64 载体是允许的 —— 引擎在写入时校验它能否被 float32 无损表示。
  727. switch carrier.Kind() {
  728. case reflect.Float32, reflect.Float64:
  729. default:
  730. return fmt.Errorf("float32 列需要 float32/float64 载体, 不能是 %s", sf.Type)
  731. }
  732. case types.FieldType_Float64:
  733. // 双精度列同样只收浮点载体。整数载体是必须拒的: 读回走 setFloatField →
  734. // target.SetInt(int64(val)), 一旦列里出现小数 (例如 Update().Set 写进去的),
  735. // 就会被静默砍掉 —— 正是 Float32 那条分支要防的同一件事。
  736. //
  737. // 这条分支存在的意义主要是"让 tag 可达": float64 字段本来就会自动推断成
  738. // Float64, 所以 type:float64 是显式/对称写法 (也给 float32 载体提供加宽),
  739. // 在此之前报错提示里列着 float32 却搜不到 float64, 读着像 bug。
  740. switch carrier.Kind() {
  741. case reflect.Float32, reflect.Float64:
  742. default:
  743. return fmt.Errorf("float64 列需要 float32/float64 载体, 不能是 %s", sf.Type)
  744. }
  745. case types.FieldType_Blob:
  746. // Blob 认 []byte 与 types.Blob 两种载体 —— 后者是"对象里持有流对象"的
  747. // 流式载体 (写 Source/Bytes 流式落链, 读回只填 8B ref + Open() 拿流)。
  748. // 这里【故意不收 string】。
  749. //
  750. // 读取路径 setFieldFromValue → setBytesField 对 string 载体是按"截断到第一个
  751. // NUL 字节"写的 (那是定长串/文本列的 CHAR(n) 语义)。Blob 装的是二进制,
  752. // 内容里出现 0x00 就会被静默截断 —— 写进去 8 字节读回来 3 字节, 且不报错。
  753. // 允许 string 等于亲手留一个静默丢数据的后门, 所以直接在建模阶段拒掉。
  754. // (要存文本用 Dynamicbyte / maxlen, 那些列的 string 载体语义是明确的。)
  755. if carrier.Kind() == reflect.Slice && carrier.Elem().Kind() == reflect.Uint8 {
  756. return nil // []byte: 一次性载体, 读回整块拉进内存
  757. }
  758. if carrier == reflect.TypeOf(types.Blob{}) {
  759. return nil // types.Blob: 流式载体
  760. }
  761. return fmt.Errorf("blob 列需要 []byte / types.Blob 载体, 不能是 %s (二进制里的 0x00 会被 string 载体静默截断)", sf.Type)
  762. }
  763. return nil
  764. }
  765. // decimalCarrier Decimal 列的 Go 载体: types.Decimal / string / 任意数值。
  766. //
  767. // 三个 carrier 判定收的都是【解过指针的载体类型】(*types.Decimal 传进来的是
  768. // types.Decimal), 所以这里不用再判指针。
  769. func decimalCarrier(t reflect.Type) bool {
  770. if _, ok := goTypeToFieldType[t]; ok {
  771. return true
  772. }
  773. switch t.Kind() {
  774. case reflect.String,
  775. reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
  776. reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
  777. reflect.Float32, reflect.Float64:
  778. return true
  779. }
  780. return false
  781. }
  782. // uuidCarrier UUID 列的 Go 载体: types.UUID / [16]byte / string / []byte。
  783. func uuidCarrier(t reflect.Type) bool {
  784. if t == reflect.TypeOf(types.UUID{}) {
  785. return true
  786. }
  787. if t.Kind() == reflect.String {
  788. return true
  789. }
  790. if t.Kind() == reflect.Array && t.Len() == types.UUIDSize && t.Elem().Kind() == reflect.Uint8 {
  791. return true
  792. }
  793. return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8
  794. }
  795. // int128Carrier Int128 列的 Go 载体: big.Int / *big.Int / types.Decimal / 整数 / string。
  796. func int128Carrier(t reflect.Type) bool {
  797. if _, ok := goTypeToFieldType[t]; ok {
  798. return true
  799. }
  800. switch t.Kind() {
  801. case reflect.String,
  802. reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
  803. reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  804. return true
  805. }
  806. return false
  807. }
  808. // maxlenToColumn 把 `vrtx:"maxlen:N"` (单位: 字节) 映射成 (列类型, 列级宽度码)。
  809. //
  810. // 返回的 code 写进 FieldInfo.Flag 低 5 位, 由引擎按它决定行内宽度 (见 types.FieldSize)。
  811. // code == 0 表示"落动态列"。
  812. //
  813. // 【语义: 向上取到最近的档位】宽度码只有 24 档 (16,24,32,...,2048), 声明 200 字节能拿到
  814. // 224 字节档, 声明 10 只能拿到 16 字节档 —— 宁可贵几字节也不能向下取, 向下取会让合法值
  815. // 写不进去。真正的闸门是【档位宽度】(写入超过档位宽度会报错并报出该列容量), 不是 N 本身。
  816. //
  817. // 超过 types.MaxInlineColumnWidth (2048) 的声明退回动态列 (行内 6 字节堆引用):
  818. // 定长档位的存在意义是"这一列可索引、可定长比较", 而动态列两者都没有 (CreateIndex 会
  819. // 明确拒绝), 所以 2048 是"要索引就别超过它"的分界线。
  820. //
  821. // 【N 还有第二道上界】落动态列不等于"声明多大都行": 动态列的内容整体落在一个堆页里,
  822. // 单值上限是 types.MaxStringSize。声明超过它的列永远兑现不了, 所以这里直接报错, 而不是
  823. // 悄悄建成一条装不下那么多字节的列。这道闸门只能在这里 —— 动态列【不保留声明长度】
  824. // (Flag 低 5 位必须为 0), 一旦过了这层, N 就不在盘上了, 事后再无据可查。
  825. func maxlenToColumn(maxlen int) (types.FieldType, byte, error) {
  826. if err := types.CheckDeclaredStringSize(maxlen); err != nil {
  827. return 0, 0, err
  828. }
  829. code := types.WidthCodeFor(maxlen)
  830. if code == 0 {
  831. return types.FieldType_Dynamicbyte, 0, nil
  832. }
  833. return types.FieldType_String, code, nil
  834. }
  835. // parseTag 解析 vrtx tag: 按逗号分隔
  836. func parseTag(tag string) []string {
  837. var parts []string
  838. current := ""
  839. for _, c := range tag {
  840. if c == ',' {
  841. parts = append(parts, current)
  842. current = ""
  843. } else {
  844. current += string(c)
  845. }
  846. }
  847. if current != "" {
  848. parts = append(parts, current)
  849. }
  850. return parts
  851. }
  852. // GetIndexType 返回该模型应创建的一级索引类型
  853. func (m *ModelInfo) GetIndexType() page.IndexType {
  854. if m.IsTimeSeries {
  855. return page.IndexTypeTime
  856. }
  857. return page.IndexTypePrimary
  858. }