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 }