package migrate import ( "context" "fmt" "github.com/gogf/gf/v2/database/gdb" ) // ============================== 数据库连接 ============================== // openConn 按 DSN 动态建立连接并探活 // // 通过 gdb.New 注册独立命名节点,不触碰 config.yaml 中的默认连接 func openConn(ctx context.Context, node string, dsn DSN) (gdb.DB, error) { link, err := buildLink(dsn) if err != nil { return nil, err } db, err := gdb.New(gdb.ConfigNode{ Name: node, Type: dsn.Type, Link: link, }) if err != nil { return nil, fmt.Errorf("连接 %s 库失败: %v", node, err) } if _, err = db.Query(ctx, "SELECT 1"); err != nil { return nil, fmt.Errorf("%s 库探活失败: %v", node, err) } return db, nil } // ensureDatabase 目标库不存在时先创建 // // MySQL、PostgreSQL、SQL Server 经维护库建库;Oracle 与 SQLite 无需预建 func ensureDatabase(ctx context.Context, dsn DSN) error { var maintainDB, createSQL, checkSQL string switch dsn.Type { case "mysql": maintainDB, checkSQL = "", "SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?" createSQL = "CREATE DATABASE `" + dsn.Database + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" case "pgsql": maintainDB, checkSQL = "postgres", "SELECT 1 FROM pg_database WHERE datname = ?" createSQL = `CREATE DATABASE "` + dsn.Database + `"` case "mssql": maintainDB, checkSQL = "master", "SELECT 1 FROM sys.databases WHERE name = ?" createSQL = "CREATE DATABASE [" + dsn.Database + "]" default: return nil // oracle/sqlite 无需预建 } checkDSN := dsn checkDSN.Database = maintainDB link, err := buildLink(checkDSN) if err != nil { return err } maintain, err := gdb.New(gdb.ConfigNode{Name: "migrate_maintain", Type: dsn.Type, Link: link}) if err != nil { return fmt.Errorf("连接 %s 维护库失败: %v", dsn.Type, err) } defer maintain.Close(ctx) row, err := maintain.Query(ctx, checkSQL, dsn.Database) if err != nil { return fmt.Errorf("检查目标库是否存在失败: %v", err) } if len(row) > 0 { return nil // 已存在 } if _, err = maintain.Exec(ctx, createSQL); err != nil { return fmt.Errorf("创建目标库 %s 失败: %v", dsn.Database, err) } return nil } // buildLink 将 DSN 转换为 gdb link 串 // // 各方言按驱动约定格式组装,对齐配置示例 func buildLink(dsn DSN) (string, error) { switch dsn.Type { case "mysql": return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=Local", dsn.Username, dsn.Password, dsn.Host, dsn.Port, dsn.Database), nil case "pgsql": return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?sslmode=disable", dsn.Username, dsn.Password, dsn.Host, dsn.Port, dsn.Database), nil case "mssql": return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?encrypt=disable", dsn.Username, dsn.Password, dsn.Host, dsn.Port, dsn.Database), nil case "oracle": return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dsn.Username, dsn.Password, dsn.Host, dsn.Port, dsn.Database), nil case "sqlite": if dsn.Database == "" { return "", fmt.Errorf("sqlite 需以 -src-database/-dst-database 指定数据库文件路径") } return fmt.Sprintf("sqlite::@file(%s)", dsn.Database), nil } return "", fmt.Errorf("不支持的数据库类型: %s", dsn.Type) } // tableExists 判断目标库中同名表是否已存在 func tableExists(ctx context.Context, db gdb.DB, table string) (bool, error) { tables, err := db.Tables(ctx) if err != nil { return false, err } for _, t := range tables { if t == table { return true, nil } } return false, nil }