github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/migrate/environment.go (about) 1 // Copyright 2022 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package migrate 16 17 import ( 18 "context" 19 "fmt" 20 "path/filepath" 21 "strings" 22 "time" 23 24 "github.com/dolthub/dolt/go/libraries/doltcore/dbfactory" 25 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 26 "github.com/dolthub/dolt/go/libraries/doltcore/env" 27 "github.com/dolthub/dolt/go/libraries/doltcore/ref" 28 "github.com/dolthub/dolt/go/libraries/utils/earl" 29 "github.com/dolthub/dolt/go/libraries/utils/filesys" 30 "github.com/dolthub/dolt/go/store/datas" 31 "github.com/dolthub/dolt/go/store/types" 32 ) 33 34 const ( 35 doltDir = dbfactory.DoltDir 36 nomsDir = dbfactory.DataDir 37 oldGenDir = "oldgen" 38 39 manifestFile = "manifest" 40 migrationRef = "migration" 41 ) 42 43 var ( 44 targetFormat = types.Format_DOLT 45 migrationMsg = fmt.Sprintf("migrating database to Noms Binary Format %s", targetFormat.VersionString()) 46 ) 47 48 // Environment is a migration environment. 49 type Environment struct { 50 Migration *env.DoltEnv 51 Existing *env.DoltEnv 52 DropConflicts bool 53 } 54 55 // NewEnvironment creates a migration Environment for |existing|. 56 func NewEnvironment(ctx context.Context, existing *env.DoltEnv) (Environment, error) { 57 mfs, err := getMigrateFS(existing.FS) 58 if err != nil { 59 return Environment{}, err 60 } 61 62 if err = initMigrationDB(ctx, existing, existing.FS, mfs); err != nil { 63 return Environment{}, err 64 } 65 66 mdb, err := doltdb.LoadDoltDB(ctx, targetFormat, doltdb.LocalDirDoltDB, mfs) 67 if err != nil { 68 return Environment{}, err 69 } 70 71 config, err := env.LoadDoltCliConfig(env.GetCurrentUserHomeDir, mfs) 72 if err != nil { 73 return Environment{}, err 74 } 75 76 migration := &env.DoltEnv{ 77 Version: existing.Version, 78 Config: config, 79 RepoState: existing.RepoState, 80 DoltDB: mdb, 81 FS: mfs, 82 //urlStr: urlStr, 83 //hdp: hdp, 84 } 85 86 return Environment{ 87 Migration: migration, 88 Existing: existing, 89 }, nil 90 } 91 92 func initMigrationDB(ctx context.Context, existing *env.DoltEnv, src, dest filesys.Filesys) (err error) { 93 base, err := src.Abs(".") 94 if err != nil { 95 return err 96 } 97 98 ierr := src.Iter(doltDir, true, func(path string, size int64, isDir bool) (stop bool) { 99 path, err = filepath.Rel(base, path) 100 if err != nil { 101 stop = true 102 return 103 } 104 105 if isDir { 106 err = dest.MkDirs(path) 107 stop = err != nil 108 return 109 } 110 if strings.Contains(path, nomsDir) { 111 return 112 } 113 114 if err = filesys.CopyFile(path, path, src, dest); err != nil { 115 stop = true 116 return 117 } 118 return 119 }) 120 if ierr != nil { 121 return ierr 122 } 123 if err != nil { 124 return err 125 } 126 127 absPath, err := dest.Abs(filepath.Join(doltDir, nomsDir)) 128 if err != nil { 129 return err 130 } 131 if err = dest.MkDirs(absPath); err != nil { 132 return err 133 } 134 135 u, err := earl.Parse("file://" + filepath.ToSlash(absPath)) 136 if err != nil { 137 return err 138 } 139 140 params := map[string]any{dbfactory.ChunkJournalParam: struct{}{}} 141 ddb, err := doltdb.LoadDoltDBWithParams(ctx, targetFormat, u.String(), dest, params) 142 vrw := ddb.ValueReadWriter() 143 ns := ddb.NodeStore() 144 db := doltdb.HackDatasDatabaseFromDoltDB(ddb) 145 146 // write init commit for migration 147 name, email, err := env.GetNameAndEmail(existing.Config) 148 if err != nil { 149 return err 150 } 151 152 meta, err := datas.NewCommitMeta(name, email, migrationMsg) 153 if err != nil { 154 return err 155 } 156 157 rv, err := doltdb.EmptyRootValue(ctx, vrw, ns) 158 if err != nil { 159 return err 160 } 161 162 ds, err := db.GetDataset(ctx, ref.NewInternalRef(migrationRef).String()) 163 if err != nil { 164 return err 165 } 166 167 _, err = db.Commit(ctx, ds, rv.NomsValue(), datas.CommitOptions{Meta: meta}) 168 return nil 169 } 170 171 // SwapChunkStores atomically swaps the ChunkStores of |menv.Migration| and |menv.Existing|. 172 func SwapChunkStores(ctx context.Context, menv Environment) error { 173 src, dest := menv.Migration.FS, menv.Existing.FS 174 175 absSrc, err := src.Abs(filepath.Join(doltDir, nomsDir)) 176 if err != nil { 177 return err 178 } 179 180 absDest, err := dest.Abs(filepath.Join(doltDir, nomsDir)) 181 if err != nil { 182 return err 183 } 184 185 var cpErr error 186 err = src.Iter(absSrc, true, func(p string, size int64, isDir bool) (stop bool) { 187 if strings.Contains(p, manifestFile) || isDir { 188 return 189 } 190 191 var relPath string 192 if relPath, cpErr = filepath.Rel(absSrc, p); cpErr != nil { 193 stop = true 194 return 195 } 196 197 srcPath := filepath.Join(absSrc, relPath) 198 destPath := filepath.Join(absDest, relPath) 199 200 if cpErr = filesys.CopyFile(srcPath, destPath, src, dest); cpErr != nil { 201 stop = true 202 } 203 return 204 }) 205 if err != nil { 206 return err 207 } 208 if cpErr != nil { 209 return cpErr 210 } 211 212 return swapManifests(ctx, src, dest) 213 } 214 215 func swapManifests(ctx context.Context, src, dest filesys.Filesys) (err error) { 216 // backup the current manifest 217 manifest := filepath.Join(doltDir, nomsDir, manifestFile) 218 bak := filepath.Join(doltDir, nomsDir, manifestFile+".bak") 219 if err = filesys.CopyFile(manifest, bak, dest, dest); err != nil { 220 return err 221 } 222 223 // backup the current oldgen manifest, if one exists 224 gcManifest := filepath.Join(doltDir, nomsDir, oldGenDir, manifestFile) 225 oldGen, _ := dest.Exists(gcManifest) 226 if oldGen { 227 bak = filepath.Join(doltDir, nomsDir, oldGenDir, manifestFile+".bak") 228 if err = filesys.CopyFile(gcManifest, bak, dest, dest); err != nil { 229 return err 230 } 231 } 232 233 // copy manifest to |dest| under temporary name 234 tmp := filepath.Join(doltDir, nomsDir, "temp-manifest") 235 if err = filesys.CopyFile(manifest, tmp, src, dest); err != nil { 236 return err 237 } 238 239 // delete current oldgen manifest 240 if oldGen { 241 if err = dest.Delete(gcManifest, true); err != nil { 242 return err 243 } 244 } 245 246 // atomically swap the manifests 247 return dest.MoveFile(tmp, manifest) 248 // exit immediately! 249 } 250 251 func getMigrateFS(existing filesys.Filesys) (filesys.Filesys, error) { 252 uniq := fmt.Sprintf("dolt_migration_%d", time.Now().UnixNano()) 253 tmpPath := filepath.Join(existing.TempDir(), uniq) 254 if err := existing.MkDirs(tmpPath); err != nil { 255 return nil, err 256 } 257 258 mfs, err := filesys.LocalFilesysWithWorkingDir(tmpPath) 259 if err != nil { 260 return nil, err 261 } 262 263 if err = mfs.MkDirs(doltDir); err != nil { 264 return nil, err 265 } 266 return mfs, nil 267 }