migration_sql.go

  1package goose
  2
  3import (
  4	"context"
  5	"database/sql"
  6	"fmt"
  7	"regexp"
  8)
  9
 10// Run a migration specified in raw SQL.
 11//
 12// Sections of the script can be annotated with a special comment,
 13// starting with "-- +goose" to specify whether the section should
 14// be applied during an Up or Down migration
 15//
 16// All statements following an Up or Down annotation are grouped together
 17// until another direction annotation is found.
 18func runSQLMigration(
 19	ctx context.Context,
 20	db *sql.DB,
 21	statements []string,
 22	useTx bool,
 23	v int64,
 24	direction bool,
 25	noVersioning bool,
 26) error {
 27	if useTx {
 28		// TRANSACTION.
 29
 30		verboseInfo("Begin transaction")
 31
 32		tx, err := db.BeginTx(ctx, nil)
 33		if err != nil {
 34			return fmt.Errorf("failed to begin transaction: %w", err)
 35		}
 36
 37		for _, query := range statements {
 38			verboseInfo("Executing statement: %s\n", clearStatement(query))
 39			if _, err := tx.ExecContext(ctx, query); err != nil {
 40				verboseInfo("Rollback transaction")
 41				_ = tx.Rollback()
 42				return fmt.Errorf("failed to execute SQL query %q: %w", clearStatement(query), err)
 43			}
 44		}
 45
 46		if !noVersioning {
 47			if direction {
 48				if err := store.InsertVersion(ctx, tx, TableName(), v); err != nil {
 49					verboseInfo("Rollback transaction")
 50					_ = tx.Rollback()
 51					return fmt.Errorf("failed to insert new goose version: %w", err)
 52				}
 53			} else {
 54				if err := store.DeleteVersion(ctx, tx, TableName(), v); err != nil {
 55					verboseInfo("Rollback transaction")
 56					_ = tx.Rollback()
 57					return fmt.Errorf("failed to delete goose version: %w", err)
 58				}
 59			}
 60		}
 61
 62		verboseInfo("Commit transaction")
 63		if err := tx.Commit(); err != nil {
 64			return fmt.Errorf("failed to commit transaction: %w", err)
 65		}
 66
 67		return nil
 68	}
 69
 70	// NO TRANSACTION.
 71	for _, query := range statements {
 72		verboseInfo("Executing statement: %s", clearStatement(query))
 73		if _, err := db.ExecContext(ctx, query); err != nil {
 74			return fmt.Errorf("failed to execute SQL query %q: %w", clearStatement(query), err)
 75		}
 76	}
 77	if !noVersioning {
 78		if direction {
 79			if err := store.InsertVersionNoTx(ctx, db, TableName(), v); err != nil {
 80				return fmt.Errorf("failed to insert new goose version: %w", err)
 81			}
 82		} else {
 83			if err := store.DeleteVersionNoTx(ctx, db, TableName(), v); err != nil {
 84				return fmt.Errorf("failed to delete goose version: %w", err)
 85			}
 86		}
 87	}
 88
 89	return nil
 90}
 91
 92const (
 93	grayColor  = "\033[90m"
 94	resetColor = "\033[00m"
 95)
 96
 97func verboseInfo(s string, args ...interface{}) {
 98	if verbose {
 99		if noColor {
100			log.Printf(s, args...)
101		} else {
102			log.Printf(grayColor+s+resetColor, args...)
103		}
104	}
105}
106
107var (
108	matchSQLComments = regexp.MustCompile(`(?m)^--.*$[\r\n]*`)
109	matchEmptyEOL    = regexp.MustCompile(`(?m)^$[\r\n]*`) // TODO: Duplicate
110)
111
112func clearStatement(s string) string {
113	s = matchSQLComments.ReplaceAllString(s, ``)
114	return matchEmptyEOL.ReplaceAllString(s, ``)
115}