github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/dbfactory/file.go (about) 1 // Copyright 2019 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 dbfactory 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "net/url" 22 "os" 23 "path/filepath" 24 "sync" 25 26 "github.com/dolthub/dolt/go/libraries/doltcore/dconfig" 27 "github.com/dolthub/dolt/go/libraries/utils/filesys" 28 "github.com/dolthub/dolt/go/store/datas" 29 "github.com/dolthub/dolt/go/store/nbs" 30 "github.com/dolthub/dolt/go/store/prolly/tree" 31 "github.com/dolthub/dolt/go/store/types" 32 ) 33 34 func init() { 35 // default to chunk journal unless feature flag is set 36 if os.Getenv(dconfig.EnvDisableChunkJournal) != "" { 37 chunkJournalFeatureFlag = false 38 } 39 } 40 41 var chunkJournalFeatureFlag = true 42 43 const ( 44 // DoltDir defines the directory used to hold the dolt repo data within the filesys 45 DoltDir = ".dolt" 46 47 // DataDir is the directory internal to the DoltDir which holds the noms files. 48 DataDir = "noms" 49 50 // StatsDir is the directory in DoltDir that holds the database statistics 51 StatsDir = "stats" 52 53 ChunkJournalParam = "journal" 54 ) 55 56 // DoltDataDir is the directory where noms files will be stored 57 var DoltDataDir = filepath.Join(DoltDir, DataDir) 58 var DoltStatsDir = filepath.Join(DoltDir, StatsDir) 59 60 // FileFactory is a DBFactory implementation for creating local filesys backed databases 61 type FileFactory struct { 62 } 63 64 type singletonDB struct { 65 ddb datas.Database 66 vrw types.ValueReadWriter 67 ns tree.NodeStore 68 } 69 70 var singletonLock = new(sync.Mutex) 71 var singletons = make(map[string]singletonDB) 72 73 func CloseAllLocalDatabases() (err error) { 74 singletonLock.Lock() 75 defer singletonLock.Unlock() 76 for name, s := range singletons { 77 if cerr := s.ddb.Close(); cerr != nil { 78 err = fmt.Errorf("error closing DB %s (%s)", name, cerr) 79 } 80 } 81 return 82 } 83 84 func DeleteFromSingletonCache(path string) error { 85 singletonLock.Lock() 86 defer singletonLock.Unlock() 87 delete(singletons, path) 88 return nil 89 } 90 91 // PrepareDB creates the directory for the DB if it doesn't exist, and returns an error if a file or symlink is at the 92 // path given 93 func (fact FileFactory) PrepareDB(ctx context.Context, nbf *types.NomsBinFormat, u *url.URL, params map[string]interface{}) error { 94 path, err := url.PathUnescape(u.Path) 95 if err != nil { 96 return err 97 } 98 99 path = filepath.FromSlash(path) 100 path = u.Host + path 101 102 info, err := os.Stat(path) 103 104 if os.IsNotExist(err) { 105 return os.MkdirAll(path, os.ModePerm) 106 } 107 108 if err != nil { 109 return err 110 } else if !info.IsDir() { 111 return filesys.ErrIsFile 112 } 113 114 return nil 115 } 116 117 // CreateDB creates a local filesys backed database 118 func (fact FileFactory) CreateDB(ctx context.Context, nbf *types.NomsBinFormat, urlObj *url.URL, params map[string]interface{}) (datas.Database, types.ValueReadWriter, tree.NodeStore, error) { 119 singletonLock.Lock() 120 defer singletonLock.Unlock() 121 122 if s, ok := singletons[urlObj.Path]; ok { 123 return s.ddb, s.vrw, s.ns, nil 124 } 125 126 path, err := url.PathUnescape(urlObj.Path) 127 if err != nil { 128 return nil, nil, nil, err 129 } 130 131 path = filepath.FromSlash(path) 132 path = urlObj.Host + path 133 134 err = validateDir(path) 135 if err != nil { 136 return nil, nil, nil, err 137 } 138 139 var useJournal bool 140 if params != nil { 141 _, useJournal = params[ChunkJournalParam] 142 } 143 144 var newGenSt *nbs.NomsBlockStore 145 q := nbs.NewUnlimitedMemQuotaProvider() 146 if useJournal && chunkJournalFeatureFlag { 147 newGenSt, err = nbs.NewLocalJournalingStore(ctx, nbf.VersionString(), path, q) 148 } else { 149 newGenSt, err = nbs.NewLocalStore(ctx, nbf.VersionString(), path, defaultMemTableSize, q) 150 } 151 152 if err != nil { 153 return nil, nil, nil, err 154 } 155 156 oldgenPath := filepath.Join(path, "oldgen") 157 err = validateDir(oldgenPath) 158 if err != nil { 159 if !errors.Is(err, os.ErrNotExist) { 160 return nil, nil, nil, err 161 } 162 163 err = os.Mkdir(oldgenPath, os.ModePerm) 164 if err != nil && !errors.Is(err, os.ErrExist) { 165 return nil, nil, nil, err 166 } 167 } 168 169 oldGenSt, err := nbs.NewLocalStore(ctx, newGenSt.Version(), oldgenPath, defaultMemTableSize, q) 170 if err != nil { 171 return nil, nil, nil, err 172 } 173 174 ghostGen, err := nbs.NewGhostBlockStore(path) 175 if err != nil { 176 return nil, nil, nil, err 177 } 178 179 st := nbs.NewGenerationalCS(oldGenSt, newGenSt, ghostGen) 180 // metrics? 181 182 vrw := types.NewValueStore(st) 183 ns := tree.NewNodeStore(st) 184 ddb := datas.NewTypesDatabase(vrw, ns) 185 186 singletons[urlObj.Path] = singletonDB{ 187 ddb: ddb, 188 vrw: vrw, 189 ns: ns, 190 } 191 192 return ddb, vrw, ns, nil 193 } 194 195 func validateDir(path string) error { 196 info, err := os.Stat(path) 197 198 if err != nil { 199 return err 200 } else if !info.IsDir() { 201 return filesys.ErrIsFile 202 } 203 204 return nil 205 }