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  }