115 lines
3.5 KiB
Go
115 lines
3.5 KiB
Go
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
|
|
}
|