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  }