Files
EasyGoAdmin_GoFrame_EleVue/internal/migrate/translate.go
T
2026-10-20 11:37:25 +08:00

183 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package migrate
import (
"regexp"
"strconv"
"strings"
)
// ============================== 类型归一 ==============================
var (
reParen = regexp.MustCompile(`\(([^)]*)\)`) // (11) / (10,2) / ('a','b')
reUnsigned = regexp.MustCompile(`(?i)\bunsigned\b`) // mysql unsigned 修饰
rePgCast = regexp.MustCompile(`(?i)^'(.*)'::[\w\s\[\]]+$`) // pgsql 'x'::character varying
rePgFunc = regexp.MustCompile(`(?i)^(current_timestamp|now|localtime)\(\d*\)$`)
)
// normalizeType 源方言原始列类型转换为中间类型
//
// 同时解析长度、精度与枚举值
func normalizeType(dialect, rawType string) (typ string, length, scale int, enumVals []string) {
t := strings.ToLower(strings.TrimSpace(rawType))
unsigned := reUnsigned.MatchString(t)
t = reUnsigned.ReplaceAllString(t, "")
t = strings.TrimSpace(t)
// 提取括号参数(长度/精度/枚举值)
pargs := ""
if m := reParen.FindStringSubmatch(t); m != nil {
pargs = strings.TrimSpace(m[1])
t = strings.TrimSpace(reParen.ReplaceAllString(t, ""))
}
// 剥离 pgsql 时区/精度修饰
t = strings.TrimSuffix(t, " without time zone")
t = strings.TrimSuffix(t, " with time zone")
// 枚举/集合:括号内为引号列表
if strings.HasPrefix(t, "enum") || strings.HasPrefix(t, "set") || strings.HasPrefix(t, "enum(") {
for _, v := range strings.Split(pargs, ",") {
if v = strings.Trim(strings.TrimSpace(v), "'\""); v != "" {
enumVals = append(enumVals, v)
}
}
if strings.HasPrefix(t, "set") {
return TypeSet, 0, 0, enumVals
}
return TypeEnum, 0, 0, enumVals
}
// decimal/numeric:pargs = "10,2"
if parts := strings.Split(pargs, ","); len(parts) == 2 {
if p, e1 := strconv.Atoi(strings.TrimSpace(parts[0])); e1 == nil {
if s, e2 := strconv.Atoi(strings.TrimSpace(parts[1])); e2 == nil {
return TypeDecimal, p, s, nil
}
}
}
// 单参数括号:varchar(150) / int(11)
if pargs != "" {
if n, err := strconv.Atoi(strings.TrimSpace(strings.Split(pargs, ",")[0])); err == nil {
length = n
}
}
switch t {
case "bool", "boolean", "bit":
return TypeBool, 0, 0, nil
case "tinyint":
// mysql tinyint(1) 惯例为布尔
if length == 1 {
return TypeBool, 0, 0, nil
}
if unsigned {
return TypeSmallint, 0, 0, nil
}
return TypeTinyint, 0, 0, nil
case "smallint", "year":
return TypeSmallint, 0, 0, nil
case "mediumint", "int", "integer":
return TypeInt, 0, 0, nil
case "bigint":
return TypeBigint, 0, 0, nil
case "float", "real", "binary_float":
return TypeFloat, 0, 0, nil
case "double", "double precision", "float8", "binary_double":
return TypeDouble, 0, 0, nil
case "decimal", "numeric", "number", "dec", "fixed":
if length == 0 {
return TypeDecimal, 0, 0, nil
}
return TypeDecimal, length, scale, nil
case "varchar", "character varying", "nvarchar", "varchar2":
return TypeVarchar, length, 0, nil
case "char", "character", "nchar", "char2":
return TypeChar, length, 0, nil
case "tinytext":
return TypeTinytext, 0, 0, nil
case "text", "mediumtext", "longtext", "clob", "ntext", "json", "jsonb", "xml":
if t == "json" || t == "jsonb" {
return TypeJson, 0, 0, nil
}
if t == "mediumtext" {
return TypeMediumText, 0, 0, nil
}
if t == "longtext" {
return TypeLongText, 0, 0, nil
}
return TypeText, 0, 0, nil
case "date":
return TypeDate, 0, 0, nil
case "time":
return TypeTime, 0, 0, nil
case "datetime", "timestamp", "smalldatetime", "datetime2":
return TypeDateTime, 0, 0, nil
case "tinyblob", "blob", "mediumblob", "longblob", "binary", "varbinary",
"bytea", "image", "raw", "long raw":
return TypeBlob, 0, 0, nil
case "uuid":
return TypeChar, 36, 0, nil
}
return TypeOther, 0, 0, nil
}
// cleanDefault 归一默认值表达式
//
// PostgreSQL 剥去类型转换与函数括号,其余方言原样保留
func cleanDefault(dialect string, raw any) string {
if raw == nil {
return ""
}
s := strings.TrimSpace(toString(raw))
if s == "" || strings.EqualFold(s, "null") {
return ""
}
if dialect == "pgsql" {
if m := rePgCast.FindStringSubmatch(s); m != nil {
s = m[1]
} else if rePgFunc.MatchString(s) {
s = "CURRENT_TIMESTAMP"
}
}
if dialect == "mysql" && strings.EqualFold(s, "current_timestamp()") {
s = "CURRENT_TIMESTAMP"
}
return s
}
// toString 将任意默认值转为字符串
// 兼容不同驱动的返回类型差异
func toString(v any) string {
switch t := v.(type) {
case nil:
return ""
case string:
return t
case []byte:
return string(t)
default:
return strings.TrimSpace(strings.TrimSuffix(strval(t), ".0"))
}
}
// strval 数值转字符串
func strval(v any) string {
switch t := v.(type) {
case int64:
return strconv.FormatInt(t, 10)
case float64:
return strconv.FormatFloat(t, 'f', -1, 64)
default:
return strings.TrimSpace(toStr(v))
}
}
// toStr 尝试调用 String 方法转为字符串
func toStr(v any) string {
if s, ok := v.(interface{ String() string }); ok {
return s.String()
}
return ""
}