Golang SQL迁移包使用评测

Golang SQL迁移包使用评测 你好!

我一直在寻找一个能够解决我特定问题的迁移包:允许我在迁移过程中运行一些用 Go 实现的复杂逻辑。例如:从外部资源获取数据,执行我无法用纯 SQL 完成的复杂计算。我未能找到这样的包,因此决定自己编写一个来解决这个问题。

我请求大家对我的包进行审查:https://github.com/iamsalnikov/mymigrate

这个包支持 Cobra 命令。理论上,它可以与任何 SQL 数据库配合工作,因为它仅依赖于 Go 标准库中的 sql 包。

如果有人能审查这个包或其部分代码,我将不胜感激。

谢谢!


更多关于Golang SQL迁移包使用评测的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang SQL迁移包使用评测的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


代码审查与使用示例

看了你的 mymigrate 包,这是一个很有价值的工具,特别适合需要在迁移中执行复杂Go逻辑的场景。以下是专业评测和使用示例:

核心优势

  1. 纯Go迁移逻辑:支持在迁移中执行任意Go代码,这是与传统SQL迁移工具的主要区别
  2. 标准库依赖:仅依赖 database/sql,兼容所有SQL数据库
  3. Cobra集成:命令行工具友好,适合现代Go项目结构

代码结构分析

// 你的包结构清晰,以下是一个典型的使用示例:
package main

import (
    "database/sql"
    "fmt"
    "log"
    
    "github.com/iamsalnikov/mymigrate"
    _ "github.com/lib/pq" // PostgreSQL驱动
)

func main() {
    db, err := sql.Open("postgres", "postgres://user:pass@localhost/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 初始化迁移器
    migrator := mymigrate.New(db, "migrations_table")
    
    // 注册迁移
    migrator.AddMigration("20240101000000_init", func(tx *sql.Tx) error {
        // 复杂逻辑示例:从外部API获取数据
        externalData, err := fetchExternalData()
        if err != nil {
            return err
        }
        
        // 执行复杂计算
        processedData := complexCalculation(externalData)
        
        // 创建表
        _, err = tx.Exec(`
            CREATE TABLE IF NOT EXISTS processed_data (
                id SERIAL PRIMARY KEY,
                data JSONB,
                created_at TIMESTAMP DEFAULT NOW()
            )
        `)
        if err != nil {
            return err
        }
        
        // 插入处理后的数据
        for _, item := range processedData {
            _, err = tx.Exec(
                "INSERT INTO processed_data (data) VALUES ($1)",
                item,
            )
            if err != nil {
                return err
            }
        }
        
        return nil
    })
    
    migrator.AddMigration("20240101000001_update_logic", func(tx *sql.Tx) error {
        // 另一个迁移示例:数据转换
        rows, err := tx.Query("SELECT id, data FROM processed_data")
        if err != nil {
            return err
        }
        defer rows.Close()
        
        for rows.Next() {
            var id int
            var data string
            
            if err := rows.Scan(&id, &data); err != nil {
                return err
            }
            
            // 执行Go逻辑处理
            transformed := transformData(data)
            
            _, err = tx.Exec(
                "UPDATE processed_data SET data = $1 WHERE id = $2",
                transformed, id,
            )
            if err != nil {
                return err
            }
        }
        
        return rows.Err()
    })
    
    // 执行迁移
    if err := migrator.Migrate(); err != nil {
        log.Fatal("Migration failed:", err)
    }
    
    fmt.Println("Migrations completed successfully")
}

// 外部数据获取函数
func fetchExternalData() ([]string, error) {
    // 实现从外部API、文件或其他来源获取数据的逻辑
    return []string{"data1", "data2", "data3"}, nil
}

// 复杂计算函数
func complexCalculation(data []string) []string {
    var result []string
    for _, d := range data {
        // 执行无法用SQL实现的复杂逻辑
        result = append(result, "processed_"+d)
    }
    return result
}

// 数据转换函数
func transformData(data string) string {
    // 复杂的Go逻辑处理
    return "transformed_" + data
}

Cobra命令集成示例

// cmd/migrate.go
package cmd

import (
    "database/sql"
    "fmt"
    "log"
    
    "github.com/iamsalnikov/mymigrate"
    "github.com/spf13/cobra"
)

var migrateCmd = &cobra.Command{
    Use:   "migrate",
    Short: "Run database migrations",
    Run: func(cmd *cobra.Command, args []string) {
        db, err := sql.Open("postgres", "postgres://user:pass@localhost/dbname")
        if err != nil {
            log.Fatal(err)
        }
        defer db.Close()
        
        migrator := mymigrate.New(db, "schema_migrations")
        
        // 注册所有迁移
        registerMigrations(migrator)
        
        if err := migrator.Migrate(); err != nil {
            log.Fatal("Migration failed:", err)
        }
        
        fmt.Println("✅ Migrations completed")
    },
}

func registerMigrations(m *mymigrate.Migrator) {
    m.AddMigration("20240101000000_create_users", func(tx *sql.Tx) error {
        // 创建用户表并初始化数据
        _, err := tx.Exec(`
            CREATE TABLE users (
                id UUID PRIMARY KEY,
                email VARCHAR(255) UNIQUE,
                metadata JSONB,
                created_at TIMESTAMP DEFAULT NOW()
            )
        `)
        if err != nil {
            return err
        }
        
        // 从外部服务获取初始用户数据
        users, err := fetchInitialUsers()
        if err != nil {
            return err
        }
        
        for _, user := range users {
            _, err = tx.Exec(
                "INSERT INTO users (id, email, metadata) VALUES ($1, $2, $3)",
                user.ID, user.Email, user.Metadata,
            )
            if err != nil {
                return err
            }
        }
        
        return nil
    })
}

事务处理示例

// 展示事务中的错误处理和回滚
m.AddMigration("20240101000002_complex_transaction", func(tx *sql.Tx) error {
    // 开始复杂操作
    result, err := performComplexOperation()
    if err != nil {
        return err // 自动回滚
    }
    
    // 批量插入
    for i := 0; i < len(result.Items); i += 100 {
        batch := result.Items[i:min(i+100, len(result.Items))]
        
        stmt, err := tx.Prepare("INSERT INTO items (name, value) VALUES ($1, $2)")
        if err != nil {
            return err
        }
        defer stmt.Close()
        
        for _, item := range batch {
            if _, err := stmt.Exec(item.Name, item.Value); err != nil {
                return err
            }
        }
    }
    
    // 更新统计信息
    stats := calculateStatistics(result)
    _, err = tx.Exec(
        "UPDATE system_stats SET last_processed = $1, total_items = $2",
        stats.LastProcessed, stats.TotalItems,
    )
    
    return err
})

测试示例

// migration_test.go
package mymigrate_test

import (
    "database/sql"
    "testing"
    
    "github.com/iamsalnikov/mymigrate"
    _ "github.com/mattn/go-sqlite3"
)

func TestMigrationWithGoLogic(t *testing.T) {
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatal(err)
    }
    defer db.Close()
    
    migrator := mymigrate.New(db, "test_migrations")
    
    var migrationExecuted bool
    
    migrator.AddMigration("test_migration", func(tx *sql.Tx) error {
        // 测试复杂的Go逻辑
        _, err := tx.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)")
        if err != nil {
            return err
        }
        
        // 执行Go计算
        computedValue := computeTestValue()
        
        _, err = tx.Exec("INSERT INTO test (data) VALUES (?)", computedValue)
        if err != nil {
            return err
        }
        
        migrationExecuted = true
        return nil
    })
    
    if err := migrator.Migrate(); err != nil {
        t.Fatalf("Migration failed: %v", err)
    }
    
    if !migrationExecuted {
        t.Error("Migration was not executed")
    }
    
    // 验证数据
    var count int
    err = db.QueryRow("SELECT COUNT(*) FROM test").Scan(&count)
    if err != nil {
        t.Fatal(err)
    }
    
    if count != 1 {
        t.Errorf("Expected 1 row, got %d", count)
    }
}

func computeTestValue() string {
    // 复杂的Go逻辑
    return "computed_value"
}

你的包成功解决了在数据库迁移中执行复杂Go逻辑的需求,设计简洁且实用。事务支持确保了迁移的原子性,Cobra集成使其易于在命令行工具中使用。对于需要数据转换、外部API集成或复杂计算的迁移场景,这是一个很好的解决方案。

回到顶部