package vrtxsql // 值与类型的双向翻译: // - SQL 字面量/driver.Value → 引擎列类型 (写入路径) // - 引擎值 → driver.Value (结果集路径, database/sql 只接受基础类型) // - SQL 类型名 → types.FieldInfo (CREATE TABLE 路径) import ( "database/sql/driver" "fmt" "math" "strconv" "strings" "time" "git.ycl.pub/yl32940/TsDB2/DB/core" "git.ycl.pub/yl32940/TsDB2/DB/core/types" ) // ============================================================================= // driver.Value → 引擎值 (按列类型) // ============================================================================= func driverToEngineValue(fi *types.FieldInfo, v driver.Value) (interface{}, error) { if v == nil { return nil, nil // NULL: 由引擎按 FlagNullable 裁决 } switch fi.FieldType { case types.FieldType_DateTime: // time.Time 原样 (引擎编码层转毫秒); 数值已是毫秒; 字符串走字面量解析, // 其中零时间 (0001-01-01...) 解析出 nil → 返回 (nil, nil) = NULL, // 与 buildFieldValue 的 NULL 语义汇合 (软删除 UPDATE SET deleted_at=零时间 ⇒ NULL)。 switch t := v.(type) { case time.Time: return t, nil case int64: if t < 0 { return nil, fmt.Errorf("时间戳不能为负: %d", t) } return t, nil // 已是毫秒 case uint64: return t, nil // 已是毫秒 case float64: if t < 0 { return nil, fmt.Errorf("时间戳不能为负: %v", t) } return int64(t), nil // 已是毫秒 case string: return parseDateTimeLiteral(t) case []byte: return parseDateTimeLiteral(string(t)) } return nil, typeMismatch(fi, v) case types.FieldType_Uint64: // 旧版驱动把 DATETIME 建成 Uint64 列: 整数之外宽容接受 time.Time / // 时间文本 / 毫秒串 (toDateTimeMillis 统一按毫秒), 保证老表时间读写不回退。 switch v.(type) { case time.Time, string, []byte: return toDateTimeMillis(v) } return toInt64(v) case types.FieldType_Int64, types.FieldType_Uint32, types.FieldType_Int32, types.FieldType_Uint16, types.FieldType_Int16, types.FieldType_Uint8, types.FieldType_Int8, types.FieldType_Year: return toInt64(v) case types.FieldType_Float64, types.FieldType_Float32: return toFloat64(v) case types.FieldType_Bool: switch t := v.(type) { case bool: return t, nil case int64: return t != 0, nil case string: return strings.EqualFold(t, "true") || t == "1", nil } return nil, typeMismatch(fi, v) case types.FieldType_String: switch t := v.(type) { case string: return t, nil case []byte: return string(t), nil } return nil, typeMismatch(fi, v) case types.FieldType_Dynamicbyte: switch t := v.(type) { case []byte: return t, nil case string: return []byte(t), nil } return nil, typeMismatch(fi, v) case types.FieldType_Decimal: switch t := v.(type) { case string: return types.MustParseDecimal(t), nil case []byte: return types.MustParseDecimal(string(t)), nil case float64: return types.MustParseDecimal(strconv.FormatFloat(t, 'f', -1, 64)), nil } return nil, typeMismatch(fi, v) case types.FieldType_UUID: switch t := v.(type) { case string: return types.MustParseUUID(t), nil // 若存在解析入口则用之 case []byte: if len(t) == 16 { var u [16]byte copy(u[:], t) return u, nil } return types.MustParseUUID(string(t)), nil } return nil, typeMismatch(fi, v) case types.FieldType_Blob: switch t := v.(type) { case []byte: return t, nil case string: return []byte(t), nil } return nil, typeMismatch(fi, v) } return nil, typeMismatch(fi, v) } func typeMismatch(fi *types.FieldInfo, v driver.Value) error { return fmt.Errorf("值类型 %T 与列 %q 的类型 %d 不匹配", v, fi.FieldName, fi.FieldType) } func toInt64(v driver.Value) (interface{}, error) { switch t := v.(type) { case int64: return t, nil case float64: return int64(t), nil case bool: if t { return int64(1), nil } return int64(0), nil case string: n, err := strconv.ParseInt(strings.TrimSpace(t), 10, 64) if err == nil { return n, nil } f, ferr := strconv.ParseFloat(strings.TrimSpace(t), 64) if ferr == nil { return int64(f), nil } case []byte: return toInt64(string(t)) case time.Time: return int64(timeToMillis(t)), nil // 时间列: time.Time → 毫秒 } return nil, fmt.Errorf("无法把 %T 转成整数", v) } func toFloat64(v driver.Value) (interface{}, error) { switch t := v.(type) { case float64: return t, nil case int64: return float64(t), nil case string: f, err := strconv.ParseFloat(strings.TrimSpace(t), 64) if err == nil { return f, nil } case []byte: return toFloat64(string(t)) } return nil, fmt.Errorf("无法把 %T 转成浮点", v) } // ============================================================================= // 字面量 → 引擎值 (WHERE/VALUES 里的纯文本数字/字符串) // ============================================================================= // parseLiteralAsType 按列的实际类型解释字面量文本。数字字面量到整型列按 int64, // 到浮点按 float64; 字符串字面量按列类型解析 (数字字符串转数值, 其余原样)。 func parseLiteralAsType(fi *types.FieldInfo, s string) (interface{}, error) { switch fi.FieldType { case types.FieldType_Float64, types.FieldType_Float32: if f, err := strconv.ParseFloat(s, 64); err == nil { return f, nil } case types.FieldType_Uint64: // 整数优先; 兼容旧版 DATETIME 建的 Uint64 列: 时间文本/毫秒串按毫秒解释。 if n, err := strconv.ParseInt(s, 10, 64); err == nil { return n, nil } if f, ferr := strconv.ParseFloat(s, 64); ferr == nil { return int64(f), nil } if ms, terr := parseDateTimeText(s); terr == nil { return ms, nil } case types.FieldType_Int64, types.FieldType_Uint32, types.FieldType_Int32, types.FieldType_Uint16, types.FieldType_Int16, types.FieldType_Uint8, types.FieldType_Int8, types.FieldType_Year: if n, err := strconv.ParseInt(s, 10, 64); err == nil { return n, nil } if f, ferr := strconv.ParseFloat(s, 64); ferr == nil { return int64(f), nil } case types.FieldType_Bool: if b, err := strconv.ParseBool(s); err == nil { return b, nil } case types.FieldType_Decimal: return types.MustParseDecimal(s), nil case types.FieldType_DateTime: return parseDateTimeLiteral(s) default: // 字符串/动态列: 原样 return s, nil } return nil, fmt.Errorf("字面量 %q 无法解释为列 %q 的类型 %d", s, fi.FieldName, fi.FieldType) } // ============================================================================= // 引擎值 → driver.Value // ============================================================================= // blobReaderOf 绑定某张表的 BlobRef 物化入口: 行内 8B 链引用 → 内容字节。 // // compress 固定 CompressNone, 与写入侧严格一致 —— 引擎写路径 (Table.blobFieldRef) // 把驱动/migrate 写入的 []byte / string 内容按 CompressNone 落链 (FieldInfo 不持久化 // 压缩标志, 压缩只是 Blob 载体 types.Blob 的运行时参数; 库内两处读回 — // DB.readBlobBytes 与 migrate 的 VRTX.Scan —— 都是这个取值)。空引用 (空内容, 非 // NULL —— NULL 走行内位图、根本不会以 BlobRef 形态出现) 归一成空切片, Scan 到 // []byte 得到 len==0 而不是 nil。 func blobReaderOf(tbl *core.Table) func(types.BlobRef) ([]byte, error) { return func(ref types.BlobRef) ([]byte, error) { data, err := tbl.BlobRead(ref, types.CompressNone) if err != nil { return nil, err } if data == nil { return []byte{}, nil } return data, nil } } func fieldValueToDriver(v interface{}) (driver.Value, error) { switch t := v.(type) { case nil: return nil, nil case int64: return t, nil case int: return int64(t), nil case uint64: if t > math.MaxInt64 { return nil, fmt.Errorf("uint64 值 %d 超出 database/sql 的 int64 表示范围", t) } return int64(t), nil case uint32: return int64(t), nil case uint16: return int64(t), nil case uint8: return int64(t), nil case float64: return t, nil case float32: return float64(t), nil case bool: return t, nil case string: return []byte(t), nil // 字符串统一以 []byte 出 (database/sql Scan 兼容) case []byte: return t, nil case time.Time: return t, nil case types.BlobRef: // Blob 列行内只有 8B 链引用; 结果集物化由持有表句柄的路径经 blobReaderOf // 解引用 (流式 rows.start / JOIN scanJoinTable, 见 driver.go / join.go) —— // 这一层没有读取入口。仍带着引用走到这里 (聚合 MIN/MAX 命中 blob 列等) // 只能明确报错, 不能静默吐出引用结构或 fmt.Sprint 的杂串。 return nil, fmt.Errorf("BLOB 列的值 (引用 %v) 没有读取入口, 聚合函数不支持 BLOB 列", t) default: // Decimal / UUID 等其余类型: 文本化出 (Scan 到 string 可用) return []byte(fmt.Sprint(v)), nil } } // fieldValueToDriverTyped 同 fieldValueToDriver, 但按列类型做语义转换: // DateTime 列把 uint64 毫秒还原成 time.Time —— 与写入 time.Time 对称。 func fieldValueToDriverTyped(ft types.FieldType, v interface{}) (driver.Value, error) { if ft == types.FieldType_DateTime { switch x := v.(type) { case nil: return nil, nil case time.Time: return x, nil case uint64: return millisToTime(x), nil case int64: if x < 0 { return nil, fmt.Errorf("时间戳不能为负: %d", x) } return millisToTime(uint64(x)), nil } return nil, fmt.Errorf("无法把 %T 作为日期时间返回", v) } return fieldValueToDriver(v) } // ============================================================================= // SQL 类型名 → types.FieldInfo (CREATE TABLE) // ============================================================================= // sqlTypeToFieldInfo 映射 ORM 常见列类型。未覆盖的类型返回明确错误。 func sqlTypeToFieldInfo(c colDef) (types.FieldInfo, error) { fi := types.FieldInfo{FieldName: []byte(c.name)} if !c.notNull { fi.Flag |= types.FlagNullable // SQL 默认可空 } name := c.typeName switch name { case "BIGINT", "INT8": fi.FieldType = types.FieldType_Int64 case "INT", "INTEGER", "INT4", "MEDIUMINT": fi.FieldType = types.FieldType_Int32 case "SMALLINT", "INT2": fi.FieldType = types.FieldType_Int16 case "TINYINT", "INT1": fi.FieldType = types.FieldType_Int8 case "BIGINT UNSIGNED": fi.FieldType = types.FieldType_Uint64 case "INT UNSIGNED", "INTEGER UNSIGNED": fi.FieldType = types.FieldType_Uint32 case "DOUBLE", "DOUBLE PRECISION", "REAL": fi.FieldType = types.FieldType_Float64 case "FLOAT": fi.FieldType = types.FieldType_Float32 case "BOOL", "BOOLEAN": fi.FieldType = types.FieldType_Bool case "VARCHAR", "CHAR", "CHARACTER": w := 32 if c.arg > 0 { w = c.arg } if w > 2048 { return fi, fmt.Errorf("VARCHAR(%d) 超过引擎定长字符串上限 2048, 请用 TEXT (动态列)", w) } fi.FieldType = types.FieldType_String fi.Flag |= types.WidthCodeFor(w) case "TEXT", "LONGTEXT", "MEDIUMTEXT": fi.FieldType = types.FieldType_Dynamicbyte case "BLOB", "LONGBLOB", "MEDIUMBLOB", "BINARY", "VARBINARY": fi.FieldType = types.FieldType_Blob case "DECIMAL", "NUMERIC": fi.FieldType = types.FieldType_Decimal if c.arg >= 0 { fi.Flag |= byte(c.arg) // DECIMAL(p,s) 的 s 落 Flag 低 5 位 } case "DATETIME", "TIMESTAMP": fi.FieldType = types.FieldType_DateTime // 行内 8B 毫秒, 结果路径还原 time.Time case "UUID": fi.FieldType = types.FieldType_UUID default: return fi, fmt.Errorf("不支持的列类型 %q", c.typeName) } return fi, nil } // ============================================================================= // DateTime: driver 值 / 文本 → uint64 毫秒 // ============================================================================= // dateTimeLiteralLayouts parseDateTimeLiteral 尝试的时间文本布局 (按顺序; // 无时区者按本地时区解释)。 var dateTimeLiteralLayouts = []string{ "2006-01-02 15:04:05.999999999", "2006-01-02 15:04:05", "2006-01-02T15:04:05.999999999", "2006-01-02", } // parseDateTimeLiteral 解析 SQL 文本里的日期时间字面量。GORM 会把 time.Time // 族字段的值【内联成 SQL 字符串字面量】而不是 ? 参数, 零值 gorm.DeletedAt // 更是被序列化成 "{0001-01-01 00:00:00 +0000 UTC false}" 这样的垃圾串。 // // 约定: // - 剥掉首尾空白与 {} 包裹; // - 以 "0001-01-01" 开头 = 零时间, 返回 (nil, nil) → 调用方按 NULL 处理 // (GORM 软删除的未删除态正是零 DeletedAt ⇔ deleted_at IS NULL); // - 其余按 dateTimeLiteralLayouts 解析, 成功返回 time.Time (引擎编码层 // 负责 time.Time → uint64 毫秒); // - 全部失败返回含原文的明确错误。 func parseDateTimeLiteral(s string) (interface{}, error) { t := strings.TrimSpace(s) if len(t) >= 2 && t[0] == '{' && t[len(t)-1] == '}' { t = strings.TrimSpace(t[1 : len(t)-1]) } if strings.HasPrefix(t, "0001-01-01") { return nil, nil // 零时间 = NULL } for _, layout := range dateTimeLiteralLayouts { if ts, err := time.ParseInLocation(layout, t, time.Local); err == nil { return ts, nil } } return nil, fmt.Errorf("无法把 %q 解析为日期时间字面量 (支持 'YYYY-MM-DD HH:MM:SS[.小数]'、'YYYY-MM-DDThh:mm:ss[.小数]'、'YYYY-MM-DD'; 0001-01-01 零时间视为 NULL)", s) } // timeLayouts 接受的时间文本格式 (按尝试顺序; 无时区者按本地时区解释)。 var timeLayouts = []string{ time.RFC3339Nano, time.RFC3339, "2006-01-02 15:04:05.999999999", "2006-01-02 15:04:05.999", "2006-01-02 15:04:05", "2006-01-02T15:04:05.999999999", "2006-01-02T15:04:05", "2006-01-02 15:04", "2006-01-02", } // parseTimeFlexible 解析常见时间文本, 成功返回 time.Time。 func parseTimeFlexible(s string) (time.Time, bool) { t := strings.TrimSpace(s) for _, layout := range timeLayouts { if ts, err := time.ParseInLocation(layout, t, time.Local); err == nil { return ts, true } } return time.Time{}, false } // parseDateTimeText 把时间文本或纯毫秒数字解析成引擎 uint64 毫秒。 func parseDateTimeText(s string) (interface{}, error) { t := strings.TrimSpace(s) if ts, ok := parseTimeFlexible(t); ok { return uint64(timeToMillis(ts)), nil } if n, err := strconv.ParseInt(t, 10, 64); err == nil { if n < 0 { return nil, fmt.Errorf("时间戳不能为负: %d", n) } return uint64(n), nil } return nil, fmt.Errorf("无法把 %q 解析为日期时间 (支持 'YYYY-MM-DD HH:MM:SS'、RFC3339 或毫秒数字)", s) } // toDateTimeMillis 把 driver 参数转成引擎 uint64 毫秒 (time.Time/数字/文本)。 func toDateTimeMillis(v driver.Value) (interface{}, error) { if v == nil { return nil, nil } switch t := v.(type) { case time.Time: return uint64(timeToMillis(t)), nil case int64: if t < 0 { return nil, fmt.Errorf("时间戳不能为负: %d", t) } return uint64(t), nil case float64: if t < 0 { return nil, fmt.Errorf("时间戳不能为负: %v", t) } return uint64(t), nil case string: return parseDateTimeText(t) case []byte: return parseDateTimeText(string(t)) } return nil, fmt.Errorf("无法把 %T 转成日期时间", v) }