| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660 |
- package DB
- import (
- "TsDB2/DB/core/types"
- "encoding/binary"
- "fmt"
- "math/big"
- "reflect"
- )
- // structToFieldValues 将结构体转换为 core.InsertRow 所需的 FieldValue 切片
- // activeColumns 为当前 DB schema 活跃列集合,只发送交集内的字段
- func structToFieldValues(obj interface{}, model *ModelInfo, activeColumns map[string]bool) ([]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
- }
- fv := findField(v, fd.Name, model.StructType)
- if !fv.IsValid() {
- // 字段不在结构体中 (如自动添加的 _id),用零值填充
- result = append(result, types.FieldValue{
- FieldName: []byte(fd.ColName),
- 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: []byte(fd.ColName),
- Value: nil,
- })
- continue
- }
- val := normalizeValue(fv, fd)
- result = append(result, types.FieldValue{
- FieldName: []byte(fd.ColName),
- Value: val,
- })
- }
- return result, nil
- }
- // fieldValuesToStruct 将 core 查询结果的 FieldValue 切片反序列化到结构体
- func fieldValuesToStruct(fvs []types.FieldValue, model *ModelInfo, obj interface{}) 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()
- // 建立列名 → FieldValue 映射
- colMap := make(map[string]types.FieldValue, len(fvs))
- for _, fv := range fvs {
- colMap[string(fv.FieldName)] = fv
- }
- for _, fd := range model.AllFields {
- fv, ok := colMap[fd.ColName]
- if !ok {
- continue
- }
- target := findSettableField(v, fd.Name, model.StructType)
- if !target.IsValid() || !target.CanSet() {
- continue
- }
- if err := setFieldFromValue(target, fv.Value, fd); err != nil {
- return fmt.Errorf("vrtx: set field %s: %w", fd.Name, err)
- }
- }
- return nil
- }
- // 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{}
- }
- // findSettableField 同 findField 但确保返回可设置的字段
- func findSettableField(v reflect.Value, name string, t reflect.Type) reflect.Value {
- return findField(v, name, t)
- }
- // normalizeValue 将反射字段值转换为 core 可接受的值
- func normalizeValue(fv reflect.Value, fd FieldDef) interface{} {
- 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_1byte:
- return uint8(toUint64(carrier))
- case types.FieldType_2byte:
- return toInt16(carrier)
- case types.FieldType_4byte:
- return toInt32(carrier)
- case types.FieldType_8byte:
- return toUint64(carrier)
- case types.FieldType_Int8:
- return toInt8(carrier)
- case types.FieldType_Int16:
- return toInt16(carrier)
- case types.FieldType_Int32:
- return toInt32(carrier)
- case types.FieldType_Int64:
- return toInt64(carrier)
- case types.FieldType_Float64:
- return toFloatVal(carrier)
- case types.FieldType_16byte, types.FieldType_32byte,
- types.FieldType_64byte, types.FieldType_128byte, types.FieldType_256byte:
- // 字符串 / []byte 载体【原样下传】: 补零与"超宽报错"都由 types.EncodeValue
- // 按列的实际宽度完成 —— 宽度只有一个权威来源 (见 FieldDef.Width)。
- //
- // 旧实现先在这里 toFixedBytes 一次, 而那个函数是 copy 进定长缓冲: 超宽部分被
- // 静默丢掉。`maxlen:10` 的列写 20 字节会成功落库并少掉一半内容, 调用方拿不到
- // 任何信号 —— 这正是列宽闸门要拦的事, 拦在 ORM 就会绕过它。
- switch carrier.(type) {
- case string, []byte:
- return carrier
- }
- // 数值载体 (16byte 兼作裸字节列的旧用法) 仍按列宽做 BigEndian 编码
- return toFixedBytes(carrier, int(fd.Width()))
- case types.FieldType_Dynamicbyte, types.FieldType_Blob:
- // 动态列必须送 []byte: 引擎侧对动态列只做 `value.([]byte)` 断言,
- // 送 string / 数值会在那里被明确拒绝 (见 core.writeFieldValueAt)。
- return toBytes(carrier)
- // ===== 扩展类型 =====
- //
- // 这几类一律"原样下传", 由 core 的 types.EncodeValue 做规范化与校验 ——
- // 它才是唯一知道列级参数 (Decimal 的 scale) 的地方。这里若先做一次宽松转换,
- // 反而会把"不能精确表示"的值悄悄变成另一个值 (例如 float64→float32 截断)。
- case types.FieldType_Bool:
- return iface
- case types.FieldType_Float32:
- return iface
- case types.FieldType_Decimal:
- return iface
- case types.FieldType_UUID:
- return iface
- case types.FieldType_Year:
- return iface
- case types.FieldType_Int128:
- return iface
- default:
- return iface
- }
- }
- // setFieldFromValue 将 core 返回值设置到结构体字段
- func setFieldFromValue(target reflect.Value, val interface{}, fd FieldDef) 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)
- 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 := findField(v, pkDef.Name, 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 := findField(v, pkDef.Name, 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_1byte:
- return uint8(0)
- case types.FieldType_2byte:
- return int16(0)
- case types.FieldType_4byte:
- return int32(0)
- case types.FieldType_8byte:
- 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{}
- }
- }
|