go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/db/migration/suite.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package migration 9 10 import ( 11 "context" 12 "database/sql" 13 "fmt" 14 15 "go.charczuk.com/sdk/db" 16 "go.charczuk.com/sdk/errutil" 17 ) 18 19 // New returns a new suite of groups. 20 func New(options ...SuiteOption) *Suite { 21 var s Suite 22 for _, option := range options { 23 option(&s) 24 } 25 return &s 26 } 27 28 // NewWithGroups returns a new suite from a given list of groups. 29 func NewWithGroups(groups ...*Group) *Suite { 30 return New(OptGroups(groups...)) 31 } 32 33 // NewWithActions returns a new suite, with a new group, made up of given actions. 34 func NewWithActions(actions ...Action) *Suite { 35 return New(OptGroups(NewGroup(OptGroupActions(actions...)))) 36 } 37 38 // SuiteOption is an option for migration Suites 39 type SuiteOption func(s *Suite) 40 41 // OptGroups allows you to add groups to the Suite. If you want, multiple OptGroups can be applied to the same Suite. 42 // They are additive. 43 func OptGroups(groups ...*Group) SuiteOption { 44 return func(s *Suite) { 45 if len(s.Groups) == 0 { 46 s.Groups = groups 47 } else { 48 s.Groups = append(s.Groups, groups...) 49 } 50 } 51 } 52 53 // OptLog sets the suite logger. 54 func OptLog(log db.Logger) SuiteOption { 55 return func(s *Suite) { 56 s.Log = log 57 } 58 } 59 60 // Suite is a migration suite. 61 type Suite struct { 62 Groups []*Group 63 Log db.Logger 64 65 Stats struct { 66 Applied int 67 Skipped int 68 Failed int 69 Total int 70 } 71 } 72 73 // Apply applies the suite. 74 func (s *Suite) Apply(ctx context.Context, c *db.Connection) (err error) { 75 err = s.ApplyTx(ctx, c, nil) 76 return 77 } 78 79 // ApplyTx applies the suite within a given transaction (which can be nil). 80 func (s *Suite) ApplyTx(ctx context.Context, c *db.Connection, tx *sql.Tx) (err error) { 81 defer s.WriteStats(ctx) 82 defer func() { 83 if r := recover(); r != nil { 84 err = errutil.New(r) 85 } 86 }() 87 if c == nil { 88 err = fmt.Errorf("connection unset at migrations apply; cannot continue") 89 return 90 } 91 92 for _, group := range s.Groups { 93 if tx != nil { 94 group.Tx = tx 95 } 96 if err = group.Action(WithSuite(ctx, s), c); err != nil { 97 return 98 } 99 } 100 return 101 } 102 103 // Migration Stats 104 const ( 105 StatApplied = "applied" 106 StatFailed = "failed" 107 StatSkipped = "skipped" 108 StatTotal = "total" 109 ) 110 111 // WriteApplyf writes an applied step message. 112 func (s *Suite) WriteApplyf(ctx context.Context, format string, args ...interface{}) { 113 s.Stats.Applied++ 114 s.Stats.Total++ 115 s.Write(ctx, StatApplied, fmt.Sprintf(format, args...)) 116 } 117 118 // WriteSkipf skips a given step. 119 func (s *Suite) WriteSkipf(ctx context.Context, format string, args ...interface{}) { 120 s.Stats.Skipped++ 121 s.Stats.Total++ 122 s.Write(ctx, StatSkipped, fmt.Sprintf(format, args...)) 123 } 124 125 // WriteErrorf writes an error for a given step. 126 func (s *Suite) WriteErrorf(ctx context.Context, format string, args ...interface{}) { 127 s.Stats.Failed++ 128 s.Stats.Total++ 129 s.Write(ctx, StatFailed, fmt.Sprintf(format, args...)) 130 } 131 132 // WriteError writes an error for a given step. 133 func (s *Suite) WriteError(ctx context.Context, err error) error { 134 s.Stats.Failed++ 135 s.Stats.Total++ 136 s.Write(ctx, StatFailed, fmt.Sprintf("%v", err)) 137 return err 138 } 139 140 // Write writes a message for the suite. 141 func (s *Suite) Write(ctx context.Context, result, body string) { 142 if s.Log == nil { 143 return 144 } 145 s.Log.Output(1, "MIGRATION: "+NewEvent(result, body, GetContextLabels(ctx)...).String()) 146 } 147 148 // WriteStats writes the stats if a logger is configured. 149 func (s *Suite) WriteStats(ctx context.Context) { 150 if s.Log == nil { 151 return 152 } 153 s.Log.Output(1, "MIGRATION STATS: "+NewStatsEvent(s.Stats.Applied, s.Stats.Skipped, s.Stats.Failed, s.Stats.Total).String()) 154 }