github.com/btcsuite/btcwallet/walletdb@v1.4.2/migration/manager.go (about)

     1  package migration
     2  
     3  import (
     4  	"errors"
     5  	"sort"
     6  
     7  	"github.com/btcsuite/btcwallet/walletdb"
     8  )
     9  
    10  var (
    11  	// ErrReversion is an error returned when an attempt to revert to a
    12  	// previous version is detected. This is done to provide safety to users
    13  	// as some upgrades may not be backwards-compatible.
    14  	ErrReversion = errors.New("reverting to a previous version is not " +
    15  		"supported")
    16  )
    17  
    18  // Version denotes the version number of the database. A migration can be used
    19  // to bring a previous version of the database to a later one.
    20  type Version struct {
    21  	// Number represents the number of this version.
    22  	Number uint32
    23  
    24  	// Migration represents a migration function that modifies the database
    25  	// state. Care must be taken so that consequent migrations build off of
    26  	// the previous one in order to ensure the consistency of the database.
    27  	Migration func(walletdb.ReadWriteBucket) error
    28  }
    29  
    30  // Manager is an interface that exposes the necessary methods needed in order to
    31  // migrate/upgrade a service. Each service (i.e., an implementation of this
    32  // interface) can then use the Upgrade function to perform any required database
    33  // migrations.
    34  type Manager interface {
    35  	// Name returns the name of the service we'll be attempting to upgrade.
    36  	Name() string
    37  
    38  	// Namespace returns the top-level bucket of the service.
    39  	Namespace() walletdb.ReadWriteBucket
    40  
    41  	// CurrentVersion returns the current version of the service's database.
    42  	CurrentVersion(walletdb.ReadBucket) (uint32, error)
    43  
    44  	// SetVersion sets the version of the service's database.
    45  	SetVersion(walletdb.ReadWriteBucket, uint32) error
    46  
    47  	// Versions returns all of the available database versions of the
    48  	// service.
    49  	Versions() []Version
    50  }
    51  
    52  // GetLatestVersion returns the latest version available from the given slice.
    53  func GetLatestVersion(versions []Version) uint32 {
    54  	if len(versions) == 0 {
    55  		return 0
    56  	}
    57  
    58  	// Before determining the latest version number, we'll sort the slice to
    59  	// ensure it reflects the last element.
    60  	sort.Slice(versions, func(i, j int) bool {
    61  		return versions[i].Number < versions[j].Number
    62  	})
    63  
    64  	return versions[len(versions)-1].Number
    65  }
    66  
    67  // VersionsToApply determines which versions should be applied as migrations
    68  // based on the current version.
    69  func VersionsToApply(currentVersion uint32, versions []Version) []Version {
    70  	// Assuming the migration versions are in increasing order, we'll apply
    71  	// any migrations that have a version number lower than our current one.
    72  	var upgradeVersions []Version
    73  	for _, version := range versions {
    74  		if version.Number > currentVersion {
    75  			upgradeVersions = append(upgradeVersions, version)
    76  		}
    77  	}
    78  
    79  	// Before returning, we'll sort the slice by its version number to
    80  	// ensure the migrations are applied in their intended order.
    81  	sort.Slice(upgradeVersions, func(i, j int) bool {
    82  		return upgradeVersions[i].Number < upgradeVersions[j].Number
    83  	})
    84  
    85  	return upgradeVersions
    86  }
    87  
    88  // Upgrade attempts to upgrade a group of services exposed through the Manager
    89  // interface. Each service will go through its available versions and determine
    90  // whether it needs to apply any.
    91  //
    92  // NOTE: In order to guarantee fault-tolerance, each service upgrade should
    93  // happen within the same database transaction.
    94  func Upgrade(mgrs ...Manager) error {
    95  	for _, mgr := range mgrs {
    96  		if err := upgrade(mgr); err != nil {
    97  			return err
    98  		}
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  // upgrade attempts to upgrade a service expose through its implementation of
   105  // the Manager interface. This function will determine whether any new versions
   106  // need to be applied based on the service's current version and latest
   107  // available one.
   108  func upgrade(mgr Manager) error {
   109  	// We'll start by fetching the service's current and latest version.
   110  	ns := mgr.Namespace()
   111  	currentVersion, err := mgr.CurrentVersion(ns)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	versions := mgr.Versions()
   116  	latestVersion := GetLatestVersion(versions)
   117  
   118  	switch {
   119  	// If the current version is greater than the latest, then the service
   120  	// is attempting to revert to a previous version that's possibly
   121  	// backwards-incompatible. To prevent this, we'll return an error
   122  	// indicating so.
   123  	case currentVersion > latestVersion:
   124  		return ErrReversion
   125  
   126  	// If the current version is behind the latest version, we'll need to
   127  	// apply all of the newer versions in order to catch up to the latest.
   128  	case currentVersion < latestVersion:
   129  		versions := VersionsToApply(currentVersion, versions)
   130  		mgrName := mgr.Name()
   131  		ns := mgr.Namespace()
   132  
   133  		for _, version := range versions {
   134  			log.Infof("Applying %v migration #%d", mgrName,
   135  				version.Number)
   136  
   137  			// We'll only run a migration if there is one available
   138  			// for this version.
   139  			if version.Migration != nil {
   140  				err := version.Migration(ns)
   141  				if err != nil {
   142  					log.Errorf("Unable to apply %v "+
   143  						"migration #%d: %v", mgrName,
   144  						version.Number, err)
   145  					return err
   146  				}
   147  			}
   148  		}
   149  
   150  		// With all of the versions applied, we can now reflect the
   151  		// latest version upon the service.
   152  		if err := mgr.SetVersion(ns, latestVersion); err != nil {
   153  			return err
   154  		}
   155  
   156  	// If the current version matches the latest one, there's no upgrade
   157  	// needed and we can safely exit.
   158  	case currentVersion == latestVersion:
   159  	}
   160  
   161  	return nil
   162  }