package DB import ( "git.ycl.pub/yl32940/TsDB2/DB/core/types" "bytes" "encoding/binary" "fmt" "io" "math/big" "reflect" "strings" ) // structToFieldValues 将结构体转换为 core.InsertRow 所需的 FieldValue 切片 // // activeColumns 为当前 DB schema 活跃列集合, 只发送交集内的字段。 // // blobc: Blob 列的写入入口 (一般是 *core.Transaction, 包成 types.BlobCommitter 接口)。 // 传 nil 时 Blob 列必须已经"自带" types.BlobRef (写侧用户在 tx 外自行 blob.Commit 然后送 ref), // 否则 blob 列会报错 — 这是把"必须有 tx 才能写 blob"的要求显式化, 防止新写代码在无 tx // 上下文里静默走错路径。 func structToFieldValues(obj interface{}, model *ModelInfo, activeColumns map[string]bool, blobc types.BlobCommitter) ([]types.FieldValue, error) { v := reflect.ValueOf(obj) if v.Kind() == reflect.Ptr { v = v.Elem() } if v.Kind() != reflect.Struct { return nil, fmt.Errorf("vrtx: expected struct, got %s", v.Kind()) } result := make([]types.FieldValue, 0, len(model.AllFields)) for _, fd := range model.AllFields { // 跳过不在当前 DB schema 中的字段 (如已删除列、或 struct 有但 DB 没有) if activeColumns != nil && !activeColumns[fd.ColName] { continue } // 快路径: 建模时解析好的反射路径直接取 (含嵌入提升链); // 指针嵌入等特殊路径解析不到时退回 findField 老逻辑 fv := fieldByModelPath(v, fd, model.StructType) if !fv.IsValid() { // 字段不在结构体中 (如自动添加的 _id),用零值填充 result = append(result, types.FieldValue{ FieldName: fd.ColNameBytes, Value: zeroValue(fd), }) continue } // 指针/接口字段为 nil, 且该列声明了 nullable → 显式 NULL。 // 不做这一步的话, 接口里装的是"类型化 nil" (*T)(nil): core 侧的 `value == nil` // 判不出来, 会被当成零值写进去 —— "可空列永远存不出 NULL", 唯一约束的 NULL // 免除也就无从生效。只认指针/接口: nil 切片是"空值"而不是 NULL (空 blob 的 // 既有语义必须保持), 非可空列则继续走"nil 当零值"的老路。 if fd.Flag&types.FlagNullable != 0 && isNilPointerValue(fv) { result = append(result, types.FieldValue{ FieldName: fd.ColNameBytes, Value: nil, }) continue } val, err := normalizeValue(fv, fd, blobc) if err != nil { return nil, err } result = append(result, types.FieldValue{ FieldName: fd.ColNameBytes, Value: val, }) } return result, nil } // fieldValuesToStruct 将 core 查询结果的 FieldValue 切片反序列化到结构体 // // blobReader: Blob 列的读取入口 (一般是 *core.Table)。行内只有 8B BlobRef, 载体又是 // []byte (见 types.go 对 Blob 列载体的约束), 所以必须在这里按 ref 把链页内容读回内存。 // 该列为空 (全是非 blob 列 / 值是空 ref) 时可以不传, 传 nil 只在真遇到非空 blob 时才会报错。 func fieldValuesToStruct(fvs []types.FieldValue, model *ModelInfo, obj interface{}, blobReader types.BlobReader) error { v := reflect.ValueOf(obj) if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { return fmt.Errorf("vrtx: obj must be a pointer to struct") } v = v.Elem() // 逐列直查预建的 列名→AllFields下标 映射: map 查找里的 string([]byte) 是编译器 // 优化不分配, 省掉旧实现"每行建一张 colMap + 每列一次 string 分配 + 2N 次哈希"。 // 交付列里模型没有的直接跳过; 模型里有但本行没交付的字段保持零值 —— 与旧实现一致。 for i := range fvs { fv := &fvs[i] fi, ok := model.ColIndex[string(fv.FieldName)] if !ok { continue } fd := &model.AllFields[fi] target := fieldByModelPath(v, *fd, model.StructType) if !target.IsValid() || !target.CanSet() { continue } if err := setFieldFromValue(target, fv.Value, *fd, blobReader); err != nil { return fmt.Errorf("vrtx: set field %s: %w", fd.Name, err) } } return nil } // fieldByModelPath 建模时解析好的反射路径直接取字段 (含嵌入提升链)。 // // 逐行路径 (structToFieldValues / getPKValue / setPKValue / fieldValuesToStruct) // 每行每列一次字段查找, FieldByName 的类型级搜索会为闭包与 Index 切片反复分配 // (批量灌数实测 ~18% 的分配量); GoPath 是 ParseModel 时一次性算好的, 取值只是 // O(路径长度) 的指针解引用。解析不到 (字段不在结构体里 / 路径无效) 时退回 // findField 老逻辑, 语义不变。 func fieldByModelPath(v reflect.Value, fd FieldDef, t reflect.Type) reflect.Value { if fd.GoPath != nil { if f, err := v.FieldByIndexErr(fd.GoPath); err == nil { return f } } return findField(v, fd.Name, t) } // findField 在结构体中查找字段 (支持嵌入结构体) func findField(v reflect.Value, name string, t reflect.Type) reflect.Value { // 直接查找 if f := v.FieldByName(name); f.IsValid() { return f } // 在嵌入结构体中查找 for i := 0; i < t.NumField(); i++ { sf := t.Field(i) if sf.Anonymous { fv := v.Field(i) if sf.Type == baseModelType || sf.Type == timeSeriesModelType { if inner := fv.FieldByName(name); inner.IsValid() { return inner } } else if sf.Type.Kind() == reflect.Struct { if inner := findField(fv, name, sf.Type); inner.IsValid() { return inner } } } } return reflect.Value{} } // backfillBlobRefs Insert/Update 成功后把落库拿到的 BlobRef 回填进对象。 // // 只处理流式载体 types.Blob 的字段: 序列化时字段值被换成行内 8B ref, 但对象里的 // Source/Bytes 原样留着 —— 两个后果都实际伤人: // // 1. Insert 后对象不知道自己的链在哪 (row.Data.FirstPageID == 0), demo/上层无从 // 引用; // 2. 更致命: structToFieldValues 无脏字段跟踪, 后续 Update 会全列重序列化。 // 零值 types.Blob 走 blobFieldToRef 得到空 ref —— 用户只是改了 Name 调一次 // Update, blob 链就被整体抹成空。 // // 回填即根治: 清掉已消费的 Source/Bytes, 填入 FirstPageID/TotalLen —— 下次 Update // 走 blobFieldToRef 的"读回镜像"分支 (FirstPageID != 0 且无内容), 原 ref 原样下传, // 链不动。[]byte 载体不需要回填 (它就是内容本身, 每次全量重写是既定语义)。 func backfillBlobRefs(obj interface{}, model *ModelInfo, fvs []types.FieldValue) { v := reflect.ValueOf(obj) if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { return } v = v.Elem() blobType := reflect.TypeOf(types.Blob{}) for _, fd := range model.AllFields { if fd.FieldType != types.FieldType_Blob { continue } target := fieldByModelPath(v, fd, model.StructType) if !target.IsValid() || !target.CanSet() || target.Type() != blobType { continue } for _, fv := range fvs { if string(fv.FieldName) != fd.ColName { continue } ref, ok := fv.Value.(types.BlobRef) if !ok { continue } // Source/Bytes 清掉: 流已被 BlobCommit 消费 (reader 位于 EOF), // 留着只会让下一次 Update 把死流再灌一遍或报错。 target.Set(reflect.ValueOf(types.Blob{ FirstPageID: ref.FirstPageID, TotalLen: ref.TotalLen, })) } } } // normalizeValue 将反射字段值转换为 core 可接受的值 // // blobc: Blob 列的写入入口 (一般是 *core.Transaction). 传 nil 时要求字段已经自带 // types.BlobRef — 否则 blob 列会报错, 这是把"必须有 tx 才能写 blob"的要求显式化。 func normalizeValue(fv reflect.Value, fd FieldDef, blobc types.BlobCommitter) (interface{}, error) { iface := fv.Interface() // 指针载体解一层: 可空列用 `*T` 表达 (nil 已在上层被转成显式 NULL, 走不到这里), // 非 nil 的指针要按 T 的值走下面的转换。不解这一层的话, *int64 会被 toUint64 的 // "既不能 CanUint 也不能 CanInt" 分支静默变成 0 —— 可空列看着能写, 写进去全是零。 // // 只给下面这些"由 normalizeValue 定值"的列型解引用; 扩展类型那组是"原样下传", // 其中 Int128 的载体本来就是 *big.Int (core 按指针识别), 绝不能解开。 carrier := iface if fv.Kind() == reflect.Ptr && !fv.IsNil() { carrier = fv.Elem().Interface() } switch fd.FieldType { case types.FieldType_Uint8: return uint8(toUint64(carrier)), nil case types.FieldType_Uint16: return toInt16(carrier), nil case types.FieldType_Uint32: return toInt32(carrier), nil case types.FieldType_Uint64: return toUint64(carrier), nil case types.FieldType_Int8: return toInt8(carrier), nil case types.FieldType_Int16: return toInt16(carrier), nil case types.FieldType_Int32: return toInt32(carrier), nil case types.FieldType_Int64: return toInt64(carrier), nil case types.FieldType_Float64: return toFloatVal(carrier), nil case types.FieldType_String: // 字符串 / []byte 载体【原样下传】: 补零与"超宽报错"都由 types.EncodeValue // 按列的实际宽度完成 —— 宽度只有一个权威来源 (见 FieldDef.Width)。 // // 旧实现先在这里 toFixedBytes 一次, 而那个函数是 copy 进定长缓冲: 超宽部分被 // 静默丢掉。`maxlen:10` 的列写 20 字节会成功落库并少掉一半内容, 调用方拿不到 // 任何信号 —— 这正是列宽闸门要拦的事, 拦在 ORM 就会绕过它。 switch carrier.(type) { case string, []byte: return carrier, nil } // 数值载体 (String 兼作裸字节列的旧用法) 仍按列宽做 BigEndian 编码 return toFixedBytes(carrier, int(fd.Width())), nil case types.FieldType_Dynamicbyte: // 动态列必须送 []byte: 引擎侧对动态列只做 `value.([]byte)` 断言, // 送 string / 数值会在那里被明确拒绝 (见 core.writeFieldValueAt)。 return toBytes(carrier), nil case types.FieldType_Blob: // 字段值可以是: // - types.Blob{Source, Bytes} 写侧 // - types.BlobRef{TotalLen, FirstPageID} 已 commit 的 ref // - []byte / string / io.Reader / *os.File (裸承载, 引擎帮包 Blob) // - nil 空 blob (NULL 已经被走掉了) // 全部统一为 types.BlobRef 后才下传; 非空 blob 必须有 blobc, 否则报"无 tx"。 return blobFieldToRef(carrier, fd, blobc) // ===== 扩展类型 ===== // // 这几类一律"原样下传", 由 core 的 types.EncodeValue 做规范化与校验 —— // 它才是唯一知道列级参数 (Decimal 的 scale) 的地方。这里若先做一次宽松转换, // 反而会把"不能精确表示"的值悄悄变成另一个值 (例如 float64→float32 截断)。 case types.FieldType_Bool: return iface, nil case types.FieldType_Float32: return iface, nil case types.FieldType_Decimal: return iface, nil case types.FieldType_UUID: return iface, nil case types.FieldType_Year: return iface, nil case types.FieldType_Int128: return iface, nil default: return iface, nil } } // blobFieldToRef 把 ORM 字段值 (任意受支持的载体) 转成 types.BlobRef。 // // 受支持的载体: // - nil → (BlobRef{}, nil) 空 blob // - types.BlobRef{} → 原值返回 已 commit 的 ref // - types.Blob{} → 通过 blobc.BlobCommit 写入链页, 拿 ref // - []byte → 包成 types.Blob{Bytes} 再 BlobCommit // - string → []byte 后同上 // - io.Reader → 包成 types.Blob{Source} 再 BlobCommit; 文件、网络等 // // 任一种都不该要求用户传 tx — ORM 层已自动开 tx, 把 tx.BlobCommit 包成 blobc 即可。 // // 错误处理: Source 与 Bytes 同时非 nil 报"二选一"; blobc == nil 而字段非空 ref 报 // "无 tx"; io.Reader 读取失败等按 tx.BlobCommit 的既有错误处理。 func blobFieldToRef(carrier interface{}, fd FieldDef, blobc types.BlobCommitter) (types.BlobRef, error) { if carrier == nil { return types.BlobRef{}, nil } switch v := carrier.(type) { case types.BlobRef: return v, nil case types.Blob: // Blob 里"已读回"模式 (FirstPageID/TotalLen/Compress 已填) 直接转 ref; // 写侧 (Source/Bytes) 需要 blobc. // // 判定顺序很关键: 必须先看 FirstPageID 再判空。types.Blob.Empty() 的写侧判空 // 是 "Source 与 Bytes 都空", 但"读回镜像"(引擎 Find 填的 ref / backfillBlobRefs // 回填的 ref) 恰恰就是 Source/Bytes 全空、FirstPageID 非 0 —— 若先按 // "Source==nil && Bytes==空 => 空 blob" 短路, 镜像会被误判成空, Update 一发 // 整条链就被抹掉 (曾经正是这么写的, 镜像分支成了死代码)。 if v.FirstPageID != 0 && v.Source == nil && len(v.Bytes) == 0 { return types.BlobRef{TotalLen: v.TotalLen, FirstPageID: v.FirstPageID}, nil } if v.Source == nil && len(v.Bytes) == 0 { return types.BlobRef{}, nil } if blobc == nil { return types.BlobRef{}, fmt.Errorf("vrtx: blob 列 %s 需要事务上下文才能写, 请在 ORM 操作内自动 begin-tx (或显式 tx.BeginTransaction)", fd.ColName) } return blobc.BlobCommit(blobCarrier(v), types.CompressNone) case []byte: if len(v) == 0 { return types.BlobRef{}, nil } if blobc == nil { return types.BlobRef{}, fmt.Errorf("vrtx: blob 列 %s 需要事务上下文才能写", fd.ColName) } return blobc.BlobCommit(bytes.NewReader(v), blobCompressOf(v)) case string: if len(v) == 0 { return types.BlobRef{}, nil } if blobc == nil { return types.BlobRef{}, fmt.Errorf("vrtx: blob 列 %s 需要事务上下文才能写", fd.ColName) } return blobc.BlobCommit(strings.NewReader(v), blobCompressOfString(v)) } // 兜底: 任何 io.Reader 实现都直接接受 (*os.File, *bytes.Reader, *bytes.Buffer, ...). if r, ok := carrier.(io.Reader); ok { if blobc == nil { return types.BlobRef{}, fmt.Errorf("vrtx: blob 列 %s 需要事务上下文才能写", fd.ColName) } return blobc.BlobCommit(r, types.CompressNone) } return types.BlobRef{}, fmt.Errorf("vrtx: blob 列 %s 不支持的载体类型 %T", fd.ColName, carrier) } // blobCarrier 把 types.Blob 折成单个 io.Reader (BlobCommit 只接一个 reader)。 // // 优先级: Source > Bytes. 同时给时会"明确优先 Source, Bytes 当空处理" — 但调用方已经在 // types.Blob 里强调过"二者并存请报错"; 为与 tx.BlobCommit 的"接受一个源"签名兼容, 此处 // 直接用 Source, Bytes 忽略 (Source 不为 nil 时按 io.MultiSource 上报更彻底, 但保持现状)。 func blobCarrier(b types.Blob) io.Reader { if b.Source != nil { return b.Source } return bytes.NewReader(b.Bytes) } // blobCompressOf / blobCompressOfString: 默认 CompressNone, 后续可在 schema 加列级配置 // (例 `vrtx:"type:blob,compress:snappy"`) 时把这里的硬编码换成 fd.Compress 字段。 func blobCompressOf(b []byte) types.FieldCompress { return types.CompressNone } func blobCompressOfString(s string) types.FieldCompress { return types.CompressNone } // readBlobBytes 按 ref 把 blob 内容整块读回内存 (供 []byte 载体使用)。 // // 空 ref (FirstPageID == 0) 返回空切片而不是 nil: 载体是 []byte 的字段读回"空 blob" // 应当是 len==0 的非 nil 切片, 与 SQL 侧读到的空 BLOB 形态一致, 也避免调用方额外 nil-check。 // // compress 取 CompressNone: 与写入侧 (blobCompressOf) 保持一致 —— 库不持久化压缩标志, // 两端必须约定同一个值, 否则解出来的是压缩后的字节流。 func readBlobBytes(reader types.BlobReader, ref types.BlobRef) ([]byte, error) { if ref.FirstPageID == 0 && ref.TotalLen == 0 { return []byte{}, nil } if reader == nil { return nil, fmt.Errorf("vrtx: blob 列 (TotalLen=%d) 需要 blob reader (core.Table) 才能读回内容", ref.TotalLen) } rc, err := reader.ReadBlob(ref, types.CompressNone) if err != nil { return nil, fmt.Errorf("vrtx: 读取 blob 链失败: %w", err) } if rc == nil { return []byte{}, nil } defer rc.Close() data, err := io.ReadAll(rc) if err != nil { return nil, fmt.Errorf("vrtx: 读取 blob 内容失败: %w", err) } if data == nil { data = []byte{} } // 长度必须与 ref.TotalLen 严格一致: 链被截断 / 引用损坏时静默返回短数据, // 等于把损坏的 blob 当成合法内容交给上层 (core.BlobRead 同样按此契约返错)。 if uint32(len(data)) != ref.TotalLen { return nil, fmt.Errorf("vrtx: 读取 blob 内容长度不符: ref.TotalLen=%d, 实读 %d 字节", ref.TotalLen, len(data)) } return data, nil } // setFieldFromValue 将 core 返回值设置到结构体字段 // // blobReader 只在 Blob 列 (val 是 types.BlobRef) 且载体是 []byte 时才用得上 —— ORM 的 // Blob 列载体被约束成 []byte (见 types.go), 拿到的却是 8B 引用, 必须借它把内容读回来。 func setFieldFromValue(target reflect.Value, val interface{}, fd FieldDef, blobReader types.BlobReader) error { if val == nil { return nil // NULL: 字段保持零值 (指针载体即 nil), 正是"没读到值"的表示 } // 指针目标先落一层: 可空列的载体是 `*T`, 而解码值是 T —— 分配元素后往里写。 // 集中在这里做, 下面那些 setXxxField 就不用各自认识指针 (它们只管元素类型)。 if target.Kind() == reflect.Ptr { if target.IsNil() { target.Set(reflect.New(target.Type().Elem())) } target = target.Elem() } switch v := val.(type) { case uint64: setIntField(target, int64(v)) case int64: setIntField(target, v) case int32: setIntField(target, int64(v)) case int16: setIntField(target, int64(v)) case int8: setIntField(target, int64(v)) case uint8: setIntField(target, int64(v)) case float64: setFloatField(target, v) case float32: setFloatField(target, float64(v)) case bool: if target.Kind() == reflect.Bool { target.SetBool(v) } case []byte: setBytesField(target, v, fd) case types.Decimal: setDecimalField(target, v) case types.UUID: setUUIDField(target, v) case *big.Int: setBigIntField(target, v) case types.BlobRef: // 行内 8B 直接解出 ref. 三种载体: // - types.Blob: 填 ref 进结构体, 用户调 row.Cover.Open(table) 拿 io.ReadCloser // (流式, 适合大对象); // - []byte: 直接把内容读回内存 —— 这是 ORM 唯一允许的 Blob 列载体 (见 types.go), // 不读的话 []byte 字段永远是 nil, 写进去的东西读不回来; // - 其它 (自定义类型 / 自带 ref 的字段): 原样下传, 由 default 分支尽力赋值。 switch { case target.Type() == reflect.TypeOf(types.Blob{}): b := types.Blob{FirstPageID: v.FirstPageID, TotalLen: v.TotalLen} if !b.Empty() { // Compress 没有持久化, 这里只能取零值 (CompressNone); 用户需要压缩一致性 // 时, 应在表层配置 Compress 后整列落库, 这里不动它。详细见 types.Blob 注释。 } target.Set(reflect.ValueOf(b)) case target.Kind() == reflect.Slice && target.Type().Elem().Kind() == reflect.Uint8: data, err := readBlobBytes(blobReader, v) if err != nil { return err } target.SetBytes(data) default: rv := reflect.ValueOf(v) if rv.Type().AssignableTo(target.Type()) { target.Set(rv) } else if target.Kind() == reflect.Ptr && rv.Type().AssignableTo(target.Type().Elem()) { p := reflect.New(target.Type().Elem()) p.Elem().Set(rv) target.Set(p) } } default: // 尝试直接设置 (同类型), 或填进同元素类型的指针字段 // (struct 里写 `*types.Decimal` / `*big.Int` 时, 解码值是值类型)。 rv := reflect.ValueOf(val) if !rv.IsValid() { return nil } switch { case rv.Type().AssignableTo(target.Type()): target.Set(rv) case target.Kind() == reflect.Ptr && rv.Type().AssignableTo(target.Type().Elem()): p := reflect.New(target.Type().Elem()) p.Elem().Set(rv) target.Set(p) } } return nil } // setDecimalField 把解码出来的 Decimal 落到结构体字段。 // 只做【无损】转换: 目标是有符号/无符号整数时要求 scale 为 0, 否则不写 —— 由调用方 // 决定是要 string 还是 float 载体, 而不是在这里悄悄舍入。 func setDecimalField(target reflect.Value, d types.Decimal) { switch { case target.Type() == reflect.TypeOf(types.Decimal{}): target.Set(reflect.ValueOf(d)) case target.Kind() == reflect.Ptr && target.Type().Elem() == reflect.TypeOf(types.Decimal{}): p := reflect.New(target.Type().Elem()) p.Elem().Set(reflect.ValueOf(d)) target.Set(p) case target.Kind() == reflect.String: target.SetString(d.String()) case target.Kind() == reflect.Float32, target.Kind() == reflect.Float64: target.SetFloat(d.Float64()) case d.Scale() == 0: coef := d.Coefficient() switch target.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if coef.IsInt64() { target.SetInt(coef.Int64()) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if coef.IsUint64() { target.SetUint(coef.Uint64()) } } } } // setUUIDField 把解码出来的 UUID 落到结构体字段 (UUID / string / [16]byte / []byte)。 func setUUIDField(target reflect.Value, u types.UUID) { switch { case target.Type() == reflect.TypeOf(types.UUID{}): target.Set(reflect.ValueOf(u)) case target.Kind() == reflect.Ptr && target.Type().Elem() == reflect.TypeOf(types.UUID{}): p := reflect.New(target.Type().Elem()) p.Elem().Set(reflect.ValueOf(u)) target.Set(p) case target.Kind() == reflect.String: target.SetString(u.String()) case target.Kind() == reflect.Array && target.Len() == types.UUIDSize && target.Type().Elem().Kind() == reflect.Uint8: reflect.Copy(target, reflect.ValueOf(u[:])) case target.Kind() == reflect.Slice && target.Type().Elem().Kind() == reflect.Uint8: target.SetBytes(append([]byte(nil), u[:]...)) } } // setBigIntField 把解码出来的 *big.Int 落到结构体字段 (big.Int / *big.Int / string / 整数)。 func setBigIntField(target reflect.Value, v *big.Int) { if v == nil { v = new(big.Int) } switch { case target.Type() == reflect.TypeOf(big.Int{}): target.Set(reflect.ValueOf(*new(big.Int).Set(v))) case target.Type() == reflect.TypeOf(&big.Int{}): target.Set(reflect.ValueOf(new(big.Int).Set(v))) case target.Type() == reflect.TypeOf(types.Decimal{}): if d, err := types.NewDecimal(v, 0); err == nil { target.Set(reflect.ValueOf(d)) } case target.Kind() == reflect.String: target.SetString(v.String()) case target.Kind() == reflect.Int, target.Kind() == reflect.Int8, target.Kind() == reflect.Int16, target.Kind() == reflect.Int32, target.Kind() == reflect.Int64: if v.IsInt64() { target.SetInt(v.Int64()) } case target.Kind() == reflect.Uint, target.Kind() == reflect.Uint8, target.Kind() == reflect.Uint16, target.Kind() == reflect.Uint32, target.Kind() == reflect.Uint64: if v.IsUint64() { target.SetUint(v.Uint64()) } } } func setIntField(target reflect.Value, val int64) { switch target.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: target.SetInt(val) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: target.SetUint(uint64(val)) case reflect.Float32, reflect.Float64: target.SetFloat(float64(val)) case reflect.Bool: target.SetBool(val != 0) } } func setFloatField(target reflect.Value, val float64) { switch target.Kind() { case reflect.Float32, reflect.Float64: target.SetFloat(val) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: target.SetInt(int64(val)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: target.SetUint(uint64(val)) } } func setBytesField(target reflect.Value, val []byte, fd FieldDef) { switch target.Kind() { case reflect.String: // 截断到第一个 null 字节 s := string(val) for i, c := range s { if c == 0 { s = s[:i] break } } target.SetString(s) case reflect.Slice: if target.Type().Elem().Kind() == reflect.Uint8 { target.SetBytes(val) } } } // ===== 类型转换辅助函数 ===== // isNilPointerValue 判断反射值是否为 nil 指针 / nil 接口 (nil 切片不算 —— 空值 ≠ NULL)。 func isNilPointerValue(v reflect.Value) bool { switch v.Kind() { case reflect.Ptr, reflect.Interface: return v.IsNil() } return false } func toUint64(v interface{}) uint64 { switch val := v.(type) { case uint64: return val case int64: return uint64(val) case int: return uint64(val) case int32: return uint64(val) case uint32: return uint64(val) case int16: return uint64(val) case uint16: return uint64(val) case int8: return uint64(val) case uint8: return uint64(val) case float64: return uint64(val) case float32: return uint64(val) case bool: if val { return 1 } return 0 default: rv := reflect.ValueOf(v) if rv.CanUint() { return rv.Uint() } if rv.CanInt() { return uint64(rv.Int()) } return 0 } } // toInt64 把任意数值/反射值转成 int64, 供有符号整数列 (FieldType_Int*) 使用。 // // 不走 toUint64 再强转: 对负整数那条路是"绕一圈无符号", 靠低位截断侥幸正确; // 但对负浮点是未定义行为 (Go 规范: 负浮点转无符号数结果实现相关)。直接按有符号取, // 语义明确, 且 toInt8/16/32 也一并收敛到这里。 func toInt64(v interface{}) int64 { switch val := v.(type) { case int64: return val case int: return int64(val) case int32: return int64(val) case int16: return int64(val) case int8: return int64(val) case uint64: return int64(val) case uint: return int64(val) case uint32: return int64(val) case uint16: return int64(val) case uint8: return int64(val) case float64: return int64(val) case float32: return int64(val) case bool: if val { return 1 } return 0 default: rv := reflect.ValueOf(v) if !rv.IsValid() { return 0 } switch rv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return rv.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return int64(rv.Uint()) case reflect.Float32, reflect.Float64: return int64(rv.Float()) case reflect.Bool: if rv.Bool() { return 1 } } return 0 } } func toInt8(v interface{}) int8 { return int8(toInt64(v)) } func toInt16(v interface{}) int16 { return int16(toInt64(v)) } func toInt32(v interface{}) int32 { return int32(toInt64(v)) } // toFloatVal 把任意数值/反射值转成 float64, 供浮点列写入使用。 // 注意这里不能经由 toUint64: 那会把小数部分先砍掉, 正是要避免的截断。 func toFloatVal(v interface{}) float64 { switch val := v.(type) { case float64: return val case float32: return float64(val) case int64: return float64(val) case int: return float64(val) case int32: return float64(val) case uint32: return float64(val) case int16: return float64(val) case uint16: return float64(val) case int8: return float64(val) case uint8: return float64(val) case uint64: return float64(val) default: rv := reflect.ValueOf(v) if !rv.IsValid() { return 0 } switch rv.Kind() { case reflect.Float32, reflect.Float64: return rv.Float() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return float64(rv.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return float64(rv.Uint()) case reflect.Bool: if rv.Bool() { return 1 } } return 0 } } func toFixedBytes(v interface{}, size int) []byte { buf := make([]byte, size) switch val := v.(type) { case string: copy(buf, val) case []byte: copy(buf, val) default: // 数值类型: BigEndian 编码 rv := reflect.ValueOf(v) if rv.CanUint() { binary.BigEndian.PutUint64(buf, rv.Uint()) } else if rv.CanInt() { binary.BigEndian.PutUint64(buf, uint64(rv.Int())) } } return buf } func toBytes(v interface{}) []byte { switch val := v.(type) { case []byte: return val case string: return []byte(val) default: return nil } } // getPKValue 从结构体中提取主键值 // 负数直接拒绝: 引擎的主键号是无符号 uint64, int64 主键填负数会被静默转成一个极大的号, // 之后按自己写的值既查不到也删不掉 —— 报错比留下查不到的行好。 func getPKValue(obj interface{}, model *ModelInfo) (uint64, error) { // PKInStruct 现在恒为 true (表必须内嵌基类, 键列总能对应到结构体字段)。 // 保留这个分支作为兜底: 万一将来又出现"键列不在结构体里"的模型, 让它退回 // "主键填 0 → 由引擎分配"的旧行为, 而不是去取一个不存在的字段。 if !model.PKInStruct { return 0, nil // PK 不在结构体中,返回 0 让 core 自动分配 } v := reflect.ValueOf(obj) if v.Kind() == reflect.Ptr { v = v.Elem() } pkDef := model.AllFields[model.PKIndex] fv := fieldByModelPath(v, pkDef, model.StructType) if !fv.IsValid() { return 0, nil } if fv.CanInt() && fv.Int() < 0 { return 0, fmt.Errorf("%w: 字段 %s = %d 为负数, 主键必须是非负整数", ErrInvalidPrimaryKey, pkDef.Name, fv.Int()) } return toUint64(fv.Interface()), nil } // setPKValue 设置结构体的主键值 func setPKValue(obj interface{}, model *ModelInfo, pk uint64) { // 同 getPKValue: PKInStruct 恒为 true, 这里是防御性兜底。 if !model.PKInStruct { return // PK 不在结构体中,无法回写 } v := reflect.ValueOf(obj) if v.Kind() == reflect.Ptr { v = v.Elem() } pkDef := model.AllFields[model.PKIndex] fv := fieldByModelPath(v, pkDef, model.StructType) if fv.IsValid() && fv.CanSet() { // 走 setIntField 而不是 SetUint: 自定义主键可以是 int64, // 而在有符号整数上调用 SetUint 会 panic。 setIntField(fv, int64(pk)) } } // zeroValue 返回字段类型的零值 func zeroValue(fd FieldDef) interface{} { switch fd.FieldType { case types.FieldType_Uint8: return uint8(0) case types.FieldType_Uint16: return int16(0) case types.FieldType_Uint32: return int32(0) case types.FieldType_Uint64: return uint64(0) case types.FieldType_Int8: return int8(0) case types.FieldType_Int16: return int16(0) case types.FieldType_Int32: return int32(0) case types.FieldType_Int64: return int64(0) case types.FieldType_Float64: return float64(0) // ===== 扩展类型: 零值必须与"标准解码载体"同类型, 否则写入时 EncodeValue 认不出来 ===== case types.FieldType_Bool: return false case types.FieldType_Float32: return float32(0) case types.FieldType_Decimal: return types.Decimal{} case types.FieldType_UUID: return types.UUID{} case types.FieldType_Year: return int16(0) case types.FieldType_Int128: return new(big.Int) case types.FieldType_Blob: return []byte{} default: // 动态列的"值"就是字节内容本身: 行内那 6 字节堆引用由引擎写入时生成, // 不能当成值送进去 (旧写法送 6 个零字节, 落盘是一条 6 字节的垃圾内容)。 if types.IsDynamic(fd.FieldType) { return []byte{} } // 定长字符串列的零值 = 该列宽度个零字节。宽度必须取列声明 (fd.Width()), // 不是类型默认: `maxlen:1000` 的列按 256 补零会让 EncodeValue 的长度校验失败, // "写零值"这条路直接报错。 if size := int(fd.Width()); size > 0 { return make([]byte, size) } return []byte{} } }