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}