github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/database_provider.go (about)

     1  // Copyright 2021 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 sqle
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"path/filepath"
    22  	"sort"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/dolthub/go-mysql-server/sql"
    27  
    28  	"github.com/dolthub/dolt/go/cmd/dolt/cli"
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/dbfactory"
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    31  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    32  	"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
    33  	"github.com/dolthub/dolt/go/libraries/doltcore/ref"
    34  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    35  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/clusterdb"
    36  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dfunctions"
    37  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dprocedures"
    38  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
    39  	"github.com/dolthub/dolt/go/libraries/doltcore/sqlserver"
    40  	"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
    41  	"github.com/dolthub/dolt/go/libraries/utils/concurrentmap"
    42  	"github.com/dolthub/dolt/go/libraries/utils/filesys"
    43  	"github.com/dolthub/dolt/go/libraries/utils/lockutil"
    44  	"github.com/dolthub/dolt/go/store/datas"
    45  	"github.com/dolthub/dolt/go/store/types"
    46  )
    47  
    48  type DoltDatabaseProvider struct {
    49  	// dbLocations maps a database name to its file system root
    50  	dbLocations        map[string]filesys.Filesys
    51  	databases          map[string]dsess.SqlDatabase
    52  	functions          map[string]sql.Function
    53  	tableFunctions     map[string]sql.TableFunction
    54  	externalProcedures sql.ExternalStoredProcedureRegistry
    55  	InitDatabaseHooks  []InitDatabaseHook
    56  	DropDatabaseHooks  []DropDatabaseHook
    57  	mu                 *sync.RWMutex
    58  
    59  	droppedDatabaseManager *droppedDatabaseManager
    60  
    61  	defaultBranch string
    62  	fs            filesys.Filesys
    63  	remoteDialer  dbfactory.GRPCDialProvider // TODO: why isn't this a method defined on the remote object
    64  
    65  	dbFactoryUrl string
    66  	isStandby    *bool
    67  }
    68  
    69  var _ sql.DatabaseProvider = (*DoltDatabaseProvider)(nil)
    70  var _ sql.FunctionProvider = (*DoltDatabaseProvider)(nil)
    71  var _ sql.MutableDatabaseProvider = (*DoltDatabaseProvider)(nil)
    72  var _ sql.CollatedDatabaseProvider = (*DoltDatabaseProvider)(nil)
    73  var _ sql.ExternalStoredProcedureProvider = (*DoltDatabaseProvider)(nil)
    74  var _ sql.TableFunctionProvider = (*DoltDatabaseProvider)(nil)
    75  var _ dsess.DoltDatabaseProvider = (*DoltDatabaseProvider)(nil)
    76  
    77  func (p *DoltDatabaseProvider) DefaultBranch() string {
    78  	return p.defaultBranch
    79  }
    80  
    81  func (p *DoltDatabaseProvider) WithTableFunctions(fns ...sql.TableFunction) (sql.TableFunctionProvider, error) {
    82  	funcs := make(map[string]sql.TableFunction)
    83  	for _, fn := range fns {
    84  		funcs[strings.ToLower(fn.Name())] = fn
    85  	}
    86  	cp := *p
    87  	cp.tableFunctions = funcs
    88  	return &cp, nil
    89  }
    90  
    91  // NewDoltDatabaseProvider returns a new provider, initialized without any databases, along with any
    92  // errors that occurred while trying to create the database provider.
    93  func NewDoltDatabaseProvider(defaultBranch string, fs filesys.Filesys) (*DoltDatabaseProvider, error) {
    94  	return NewDoltDatabaseProviderWithDatabases(defaultBranch, fs, nil, nil)
    95  }
    96  
    97  // NewDoltDatabaseProviderWithDatabase returns a new provider, initialized with one database at the
    98  // specified location, and any error that occurred along the way.
    99  func NewDoltDatabaseProviderWithDatabase(defaultBranch string, fs filesys.Filesys, database dsess.SqlDatabase, dbLocation filesys.Filesys) (*DoltDatabaseProvider, error) {
   100  	return NewDoltDatabaseProviderWithDatabases(defaultBranch, fs, []dsess.SqlDatabase{database}, []filesys.Filesys{dbLocation})
   101  }
   102  
   103  // NewDoltDatabaseProviderWithDatabases returns a new provider, initialized with the specified databases,
   104  // at the specified locations. For every database specified, there must be a corresponding filesystem
   105  // specified that represents where the database is located. If the number of specified databases is not the
   106  // same as the number of specified locations, an error is returned.
   107  func NewDoltDatabaseProviderWithDatabases(defaultBranch string, fs filesys.Filesys, databases []dsess.SqlDatabase, locations []filesys.Filesys) (*DoltDatabaseProvider, error) {
   108  	if len(databases) != len(locations) {
   109  		return nil, fmt.Errorf("unable to create DoltDatabaseProvider: "+
   110  			"incorrect number of databases (%d) and database locations (%d) specified", len(databases), len(locations))
   111  	}
   112  
   113  	dbs := make(map[string]dsess.SqlDatabase, len(databases))
   114  	for _, db := range databases {
   115  		dbs[strings.ToLower(db.Name())] = db
   116  	}
   117  
   118  	dbLocations := make(map[string]filesys.Filesys, len(locations))
   119  	for i, dbLocation := range locations {
   120  		dbLocations[strings.ToLower(databases[i].Name())] = dbLocation
   121  	}
   122  
   123  	funcs := make(map[string]sql.Function, len(dfunctions.DoltFunctions))
   124  	for _, fn := range dfunctions.DoltFunctions {
   125  		funcs[strings.ToLower(fn.FunctionName())] = fn
   126  	}
   127  
   128  	externalProcedures := sql.NewExternalStoredProcedureRegistry()
   129  	for _, esp := range dprocedures.DoltProcedures {
   130  		externalProcedures.Register(esp)
   131  	}
   132  
   133  	// If the specified |fs| is an in mem file system, default to using the InMemDoltDB dbFactoryUrl so that all
   134  	// databases are created with the same file system type.
   135  	dbFactoryUrl := doltdb.LocalDirDoltDB
   136  	if _, ok := fs.(*filesys.InMemFS); ok {
   137  		dbFactoryUrl = doltdb.InMemDoltDB
   138  	}
   139  
   140  	return &DoltDatabaseProvider{
   141  		dbLocations:            dbLocations,
   142  		databases:              dbs,
   143  		functions:              funcs,
   144  		externalProcedures:     externalProcedures,
   145  		mu:                     &sync.RWMutex{},
   146  		fs:                     fs,
   147  		defaultBranch:          defaultBranch,
   148  		dbFactoryUrl:           dbFactoryUrl,
   149  		InitDatabaseHooks:      []InitDatabaseHook{ConfigureReplicationDatabaseHook},
   150  		isStandby:              new(bool),
   151  		droppedDatabaseManager: newDroppedDatabaseManager(fs),
   152  	}, nil
   153  }
   154  
   155  // WithFunctions returns a copy of this provider with the functions given. Any previous functions are removed.
   156  func (p *DoltDatabaseProvider) WithFunctions(fns []sql.Function) *DoltDatabaseProvider {
   157  	funcs := make(map[string]sql.Function, len(dfunctions.DoltFunctions))
   158  	for _, fn := range fns {
   159  		funcs[strings.ToLower(fn.FunctionName())] = fn
   160  	}
   161  	cp := *p
   162  	cp.functions = funcs
   163  	return &cp
   164  }
   165  
   166  // WithDbFactoryUrl returns a copy of this provider with the DbFactoryUrl set as provided.
   167  // The URL is used when creating new databases.
   168  // See doltdb.InMemDoltDB, doltdb.LocalDirDoltDB
   169  func (p *DoltDatabaseProvider) WithDbFactoryUrl(url string) *DoltDatabaseProvider {
   170  	cp := *p
   171  	cp.dbFactoryUrl = url
   172  	return &cp
   173  }
   174  
   175  // WithRemoteDialer returns a copy of this provider with the dialer provided
   176  func (p *DoltDatabaseProvider) WithRemoteDialer(provider dbfactory.GRPCDialProvider) *DoltDatabaseProvider {
   177  	cp := *p
   178  	cp.remoteDialer = provider
   179  	return &cp
   180  }
   181  
   182  func (p *DoltDatabaseProvider) FileSystem() filesys.Filesys {
   183  	return p.fs
   184  }
   185  
   186  // SetIsStandby sets whether this provider is set to standby |true|. Standbys return every dolt database as a read only
   187  // database. Set back to |false| to get read-write behavior from dolt databases again.
   188  func (p *DoltDatabaseProvider) SetIsStandby(standby bool) {
   189  	p.mu.Lock()
   190  	defer p.mu.Unlock()
   191  	*p.isStandby = standby
   192  }
   193  
   194  // FileSystemForDatabase returns a filesystem, with the working directory set to the root directory
   195  // of the requested database. If the requested database isn't found, a database not found error
   196  // is returned.
   197  func (p *DoltDatabaseProvider) FileSystemForDatabase(dbname string) (filesys.Filesys, error) {
   198  	p.mu.Lock()
   199  	defer p.mu.Unlock()
   200  
   201  	baseName, _ := dsess.SplitRevisionDbName(dbname)
   202  
   203  	dbLocation, ok := p.dbLocations[strings.ToLower(baseName)]
   204  	if !ok {
   205  		return nil, sql.ErrDatabaseNotFound.New(dbname)
   206  	}
   207  
   208  	return dbLocation, nil
   209  }
   210  
   211  // Database implements the sql.DatabaseProvider interface
   212  func (p *DoltDatabaseProvider) Database(ctx *sql.Context, name string) (sql.Database, error) {
   213  	database, b, err := p.SessionDatabase(ctx, name)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	if !b {
   219  		return nil, sql.ErrDatabaseNotFound.New(name)
   220  	}
   221  
   222  	overriddenSchemaValue, err := getOverriddenSchemaValue(ctx)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	// If a schema override is set, ensure we're using a ReadOnlyDatabase
   228  	if overriddenSchemaValue != "" {
   229  		// TODO: It would be nice if we could set a "read-only reason" for the read only database and let people know
   230  		//       that the database is read-only because of the @@dolt_override_schema setting and that customers need
   231  		//       to unset that session variable to get a write query to work. Otherwise it may be confusing why a
   232  		//       write query isn't working.
   233  		if _, ok := database.(ReadOnlyDatabase); !ok {
   234  			readWriteDatabase, ok := database.(Database)
   235  			if !ok {
   236  				return nil, fmt.Errorf("expected an instance of sqle.Database, but found: %T", database)
   237  			}
   238  			return ReadOnlyDatabase{readWriteDatabase}, nil
   239  		}
   240  	}
   241  
   242  	return database, nil
   243  }
   244  
   245  func wrapForStandby(db dsess.SqlDatabase, standby bool) dsess.SqlDatabase {
   246  	if !standby {
   247  		return db
   248  	}
   249  	if _, ok := db.(ReadOnlyDatabase); ok {
   250  		return db
   251  	}
   252  	if db, ok := db.(Database); ok {
   253  		// :-/. Hopefully it's not too sliced.
   254  		return ReadOnlyDatabase{db}
   255  	}
   256  	return db
   257  }
   258  
   259  // attemptCloneReplica attempts to clone a database from the configured replication remote URL template, returning an error
   260  // if it cannot be found
   261  // TODO: distinct error for not found v. others
   262  func (p *DoltDatabaseProvider) attemptCloneReplica(ctx *sql.Context, dbName string) error {
   263  	// TODO: these need some reworking, they don't make total sense together
   264  	_, readReplicaRemoteName, _ := sql.SystemVariables.GetGlobal(dsess.ReadReplicaRemote)
   265  	if readReplicaRemoteName == "" {
   266  		// not a read replica DB
   267  		return nil
   268  	}
   269  
   270  	remoteName := readReplicaRemoteName.(string)
   271  
   272  	// TODO: error handling when not set
   273  	_, remoteUrlTemplate, _ := sql.SystemVariables.GetGlobal(dsess.ReplicationRemoteURLTemplate)
   274  	if remoteUrlTemplate == "" {
   275  		return nil
   276  	}
   277  
   278  	urlTemplate, ok := remoteUrlTemplate.(string)
   279  	if !ok {
   280  		return nil
   281  	}
   282  
   283  	// TODO: url sanitize
   284  	// TODO: SQL identifiers aren't case sensitive, but URLs are, need a plan for this
   285  	remoteUrl := strings.Replace(urlTemplate, dsess.URLTemplateDatabasePlaceholder, dbName, -1)
   286  
   287  	// TODO: remote params for AWS, others
   288  	// TODO: this needs to be robust in the face of the DB not having the default branch
   289  	// TODO: this treats every database not found error as a clone error, need to tighten
   290  	err := p.CloneDatabaseFromRemote(ctx, dbName, p.defaultBranch, remoteName, remoteUrl, -1, nil)
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	return nil
   296  }
   297  
   298  func (p *DoltDatabaseProvider) HasDatabase(ctx *sql.Context, name string) bool {
   299  	_, err := p.Database(ctx, name)
   300  	if err != nil && !sql.ErrDatabaseNotFound.Is(err) {
   301  		ctx.GetLogger().Warnf("Error getting database %s: %s", name, err.Error())
   302  	}
   303  	return err == nil
   304  }
   305  
   306  func (p *DoltDatabaseProvider) AllDatabases(ctx *sql.Context) (all []sql.Database) {
   307  	currentDb := ctx.GetCurrentDatabase()
   308  	_, currRev := dsess.SplitRevisionDbName(currentDb)
   309  
   310  	p.mu.RLock()
   311  	showBranches, _ := dsess.GetBooleanSystemVar(ctx, dsess.ShowBranchDatabases)
   312  
   313  	all = make([]sql.Database, 0, len(p.databases))
   314  	for _, db := range p.databases {
   315  		all = append(all, db)
   316  
   317  		if showBranches && db.Name() != clusterdb.DoltClusterDbName {
   318  			revisionDbs, err := p.allRevisionDbs(ctx, db)
   319  			if err != nil {
   320  				// TODO: this interface is wrong, needs to return errors
   321  				ctx.GetLogger().Warnf("error fetching revision databases: %s", err.Error())
   322  				continue
   323  			}
   324  			all = append(all, revisionDbs...)
   325  		}
   326  	}
   327  	p.mu.RUnlock()
   328  
   329  	// If there's a revision database in use, include it in the list (but don't double-count)
   330  	if currRev != "" && !showBranches {
   331  		rdb, ok, err := p.databaseForRevision(ctx, currentDb, currentDb)
   332  		if err != nil || !ok {
   333  			// TODO: this interface is wrong, needs to return errors
   334  			ctx.GetLogger().Warnf("error fetching revision databases: %s", err.Error())
   335  		} else {
   336  			all = append(all, rdb)
   337  		}
   338  	}
   339  
   340  	// Because we store databases in a map, sort to get a consistent ordering
   341  	sort.Slice(all, func(i, j int) bool {
   342  		return strings.ToLower(all[i].Name()) < strings.ToLower(all[j].Name())
   343  	})
   344  
   345  	return all
   346  }
   347  
   348  // DoltDatabases implements the dsess.DoltDatabaseProvider interface
   349  func (p *DoltDatabaseProvider) DoltDatabases() []dsess.SqlDatabase {
   350  	p.mu.RLock()
   351  	defer p.mu.RUnlock()
   352  
   353  	dbs := make([]dsess.SqlDatabase, len(p.databases))
   354  	i := 0
   355  	for _, db := range p.databases {
   356  		dbs[i] = db
   357  		i++
   358  	}
   359  
   360  	sort.Slice(dbs, func(i, j int) bool {
   361  		return strings.ToLower(dbs[i].Name()) < strings.ToLower(dbs[j].Name())
   362  	})
   363  
   364  	return dbs
   365  }
   366  
   367  // allRevisionDbs returns all revision dbs for the database given
   368  func (p *DoltDatabaseProvider) allRevisionDbs(ctx *sql.Context, db dsess.SqlDatabase) ([]sql.Database, error) {
   369  	branches, err := db.DbData().Ddb.GetBranches(ctx)
   370  	if err != nil {
   371  		return nil, err
   372  	}
   373  
   374  	revDbs := make([]sql.Database, len(branches))
   375  	for i, branch := range branches {
   376  		revisionQualifiedName := fmt.Sprintf("%s/%s", db.Name(), branch.GetPath())
   377  		revDb, ok, err := p.databaseForRevision(ctx, revisionQualifiedName, revisionQualifiedName)
   378  		if err != nil {
   379  			return nil, err
   380  		}
   381  		if !ok {
   382  			return nil, fmt.Errorf("cannot get revision database for %s/%s", db.Name(), branch.GetPath())
   383  		}
   384  		revDbs[i] = revDb
   385  	}
   386  
   387  	return revDbs, nil
   388  }
   389  
   390  func (p *DoltDatabaseProvider) GetRemoteDB(ctx context.Context, format *types.NomsBinFormat, r env.Remote, withCaching bool) (*doltdb.DoltDB, error) {
   391  	if withCaching {
   392  		return r.GetRemoteDB(ctx, format, p.remoteDialer)
   393  	}
   394  	return r.GetRemoteDBWithoutCaching(ctx, format, p.remoteDialer)
   395  }
   396  
   397  func (p *DoltDatabaseProvider) CreateDatabase(ctx *sql.Context, name string) error {
   398  	return p.CreateCollatedDatabase(ctx, name, sql.Collation_Default)
   399  }
   400  
   401  func (p *DoltDatabaseProvider) CreateCollatedDatabase(ctx *sql.Context, name string, collation sql.CollationID) (err error) {
   402  	p.mu.Lock()
   403  	defer p.mu.Unlock()
   404  
   405  	exists, isDir := p.fs.Exists(name)
   406  	if exists && isDir {
   407  		return sql.ErrDatabaseExists.New(name)
   408  	} else if exists {
   409  		return fmt.Errorf("Cannot create DB, file exists at %s", name)
   410  	}
   411  
   412  	err = p.fs.MkDirs(name)
   413  	if err != nil {
   414  		return err
   415  	}
   416  	defer func() {
   417  		// We do not want to leave this directory behind if we do not
   418  		// successfully create the database.
   419  		if err != nil {
   420  			p.fs.Delete(name, true /* force / recursive */)
   421  		}
   422  	}()
   423  
   424  	newFs, err := p.fs.WithWorkingDir(name)
   425  	if err != nil {
   426  		return err
   427  	}
   428  
   429  	// TODO: fill in version appropriately
   430  	sess := dsess.DSessFromSess(ctx.Session)
   431  	newEnv := env.Load(ctx, env.GetCurrentUserHomeDir, newFs, p.dbFactoryUrl, "TODO")
   432  
   433  	newDbStorageFormat := types.Format_Default
   434  	err = newEnv.InitRepo(ctx, newDbStorageFormat, sess.Username(), sess.Email(), p.defaultBranch)
   435  	if err != nil {
   436  		return err
   437  	}
   438  
   439  	// Set the collation
   440  	if collation != sql.Collation_Default {
   441  		workingRoot, err := newEnv.WorkingRoot(ctx)
   442  		if err != nil {
   443  			return err
   444  		}
   445  		newRoot, err := workingRoot.SetCollation(ctx, schema.Collation(collation))
   446  		if err != nil {
   447  			return err
   448  		}
   449  		// As this is a newly created database, we set both the working and staged roots to the same root value
   450  		if err = newEnv.UpdateWorkingRoot(ctx, newRoot); err != nil {
   451  			return err
   452  		}
   453  		if err = newEnv.UpdateStagedRoot(ctx, newRoot); err != nil {
   454  			return err
   455  		}
   456  	}
   457  
   458  	return p.registerNewDatabase(ctx, name, newEnv)
   459  }
   460  
   461  type InitDatabaseHook func(ctx *sql.Context, pro *DoltDatabaseProvider, name string, env *env.DoltEnv, db dsess.SqlDatabase) error
   462  type DropDatabaseHook func(ctx *sql.Context, name string)
   463  
   464  // ConfigureReplicationDatabaseHook sets up the hooks to push to a remote to replicate a newly created database.
   465  // TODO: consider the replication heads / all heads setting
   466  func ConfigureReplicationDatabaseHook(ctx *sql.Context, p *DoltDatabaseProvider, name string, newEnv *env.DoltEnv, _ dsess.SqlDatabase) error {
   467  	_, replicationRemoteName, _ := sql.SystemVariables.GetGlobal(dsess.ReplicateToRemote)
   468  	if replicationRemoteName == "" {
   469  		return nil
   470  	}
   471  
   472  	remoteName, ok := replicationRemoteName.(string)
   473  	if !ok {
   474  		return nil
   475  	}
   476  
   477  	_, remoteUrlTemplate, _ := sql.SystemVariables.GetGlobal(dsess.ReplicationRemoteURLTemplate)
   478  	if remoteUrlTemplate == "" {
   479  		return nil
   480  	}
   481  
   482  	urlTemplate, ok := remoteUrlTemplate.(string)
   483  	if !ok {
   484  		return nil
   485  	}
   486  
   487  	// TODO: url sanitize name
   488  	remoteUrl := strings.Replace(urlTemplate, dsess.URLTemplateDatabasePlaceholder, name, -1)
   489  
   490  	// TODO: params for AWS, others that need them
   491  	r := env.NewRemote(remoteName, remoteUrl, nil)
   492  	err := r.Prepare(ctx, newEnv.DoltDB.Format(), p.remoteDialer)
   493  	if err != nil {
   494  		return err
   495  	}
   496  
   497  	err = newEnv.AddRemote(r)
   498  	if err != env.ErrRemoteAlreadyExists && err != nil {
   499  		return err
   500  	}
   501  
   502  	// TODO: get background threads from the engine
   503  	commitHooks, err := GetCommitHooks(ctx, sql.NewBackgroundThreads(), newEnv, cli.CliErr)
   504  	if err != nil {
   505  		return err
   506  	}
   507  
   508  	newEnv.DoltDB.SetCommitHooks(ctx, commitHooks)
   509  
   510  	// After setting hooks on the newly created DB, we need to do the first push manually
   511  	branchRef := ref.NewBranchRef(p.defaultBranch)
   512  	return newEnv.DoltDB.ExecuteCommitHooks(ctx, branchRef.String())
   513  }
   514  
   515  // CloneDatabaseFromRemote implements DoltDatabaseProvider interface
   516  func (p *DoltDatabaseProvider) CloneDatabaseFromRemote(
   517  	ctx *sql.Context,
   518  	dbName, branch, remoteName, remoteUrl string,
   519  	depth int,
   520  	remoteParams map[string]string,
   521  ) error {
   522  	p.mu.Lock()
   523  	defer p.mu.Unlock()
   524  
   525  	exists, isDir := p.fs.Exists(dbName)
   526  	if exists && isDir {
   527  		return sql.ErrDatabaseExists.New(dbName)
   528  	} else if exists {
   529  		return fmt.Errorf("cannot create DB, file exists at %s", dbName)
   530  	}
   531  
   532  	err := p.cloneDatabaseFromRemote(ctx, dbName, remoteName, branch, remoteUrl, depth, remoteParams)
   533  	if err != nil {
   534  		// Make a best effort to clean up any artifacts on disk from a failed clone
   535  		// before we return the error
   536  		exists, _ := p.fs.Exists(dbName)
   537  		if exists {
   538  			deleteErr := p.fs.Delete(dbName, true)
   539  			if deleteErr != nil {
   540  				err = fmt.Errorf("%s: unable to clean up failed clone in directory '%s'", err.Error(), dbName)
   541  			}
   542  		}
   543  		return err
   544  	}
   545  
   546  	return nil
   547  }
   548  
   549  // cloneDatabaseFromRemote encapsulates the inner logic for cloning a database so that if any error
   550  // is returned by this function, the caller can capture the error and safely clean up the failed
   551  // clone directory before returning the error to the user. This function should not be used directly;
   552  // use CloneDatabaseFromRemote instead.
   553  func (p *DoltDatabaseProvider) cloneDatabaseFromRemote(
   554  	ctx *sql.Context,
   555  	dbName, remoteName, branch, remoteUrl string,
   556  	depth int,
   557  	remoteParams map[string]string,
   558  ) error {
   559  	if p.remoteDialer == nil {
   560  		return fmt.Errorf("unable to clone remote database; no remote dialer configured")
   561  	}
   562  
   563  	r := env.NewRemote(remoteName, remoteUrl, remoteParams)
   564  	srcDB, err := r.GetRemoteDB(ctx, types.Format_Default, p.remoteDialer)
   565  	if err != nil {
   566  		return err
   567  	}
   568  
   569  	dEnv, err := actions.EnvForClone(ctx, srcDB.ValueReadWriter().Format(), r, dbName, p.fs, "VERSION", env.GetCurrentUserHomeDir)
   570  	if err != nil {
   571  		return err
   572  	}
   573  
   574  	err = actions.CloneRemote(ctx, srcDB, remoteName, branch, false, depth, dEnv)
   575  	if err != nil {
   576  		return err
   577  	}
   578  
   579  	err = dEnv.RepoStateWriter().UpdateBranch(dEnv.RepoState.CWBHeadRef().GetPath(), env.BranchConfig{
   580  		Merge:  dEnv.RepoState.Head,
   581  		Remote: remoteName,
   582  	})
   583  
   584  	return p.registerNewDatabase(ctx, dbName, dEnv)
   585  }
   586  
   587  // DropDatabase implements the sql.MutableDatabaseProvider interface
   588  func (p *DoltDatabaseProvider) DropDatabase(ctx *sql.Context, name string) error {
   589  	_, revision := dsess.SplitRevisionDbName(name)
   590  	if revision != "" {
   591  		return fmt.Errorf("unable to drop revision database: %s", name)
   592  	}
   593  
   594  	p.mu.Lock()
   595  	defer p.mu.Unlock()
   596  
   597  	// get the case-sensitive name for case-sensitive file systems
   598  	dbKey := formatDbMapKeyName(name)
   599  	db := p.databases[dbKey]
   600  
   601  	var database *doltdb.DoltDB
   602  	if ddb, ok := db.(Database); ok {
   603  		database = ddb.ddb
   604  	} else {
   605  		return fmt.Errorf("unable to drop database: %s", name)
   606  	}
   607  
   608  	err := database.Close()
   609  	if err != nil {
   610  		return err
   611  	}
   612  
   613  	// get location of database that's being dropped
   614  	dbLoc := p.dbLocations[dbKey]
   615  	if dbLoc == nil {
   616  		return sql.ErrDatabaseNotFound.New(db.Name())
   617  	}
   618  
   619  	dropDbLoc, err := dbLoc.Abs("")
   620  	if err != nil {
   621  		return err
   622  	}
   623  
   624  	// If this database is re-created, we don't want to return any cached results.
   625  	err = dbfactory.DeleteFromSingletonCache(filepath.ToSlash(dropDbLoc + "/.dolt/noms"))
   626  	if err != nil {
   627  		return err
   628  	}
   629  	err = dbfactory.DeleteFromSingletonCache(filepath.ToSlash(dropDbLoc + "/.dolt/stats/.dolt/noms"))
   630  	if err != nil {
   631  		return err
   632  	}
   633  
   634  	err = p.droppedDatabaseManager.DropDatabase(ctx, name, dropDbLoc)
   635  	if err != nil {
   636  		return err
   637  	}
   638  
   639  	for _, dropHook := range p.DropDatabaseHooks {
   640  		// For symmetry with InitDatabaseHook and the names we see in
   641  		// MultiEnv initialization, we use `name` here, not `dbKey`.
   642  		dropHook(ctx, name)
   643  	}
   644  
   645  	// We not only have to delete tracking metadata for this database, but also for any derivative
   646  	// ones we've stored as a result of USE or connection strings
   647  	derivativeNamePrefix := strings.ToLower(dbKey + dsess.DbRevisionDelimiter)
   648  	for dbName := range p.databases {
   649  		if strings.HasPrefix(strings.ToLower(dbName), derivativeNamePrefix) {
   650  			delete(p.databases, dbName)
   651  		}
   652  	}
   653  	delete(p.databases, dbKey)
   654  
   655  	return p.invalidateDbStateInAllSessions(ctx, name)
   656  }
   657  
   658  func (p *DoltDatabaseProvider) ListDroppedDatabases(ctx *sql.Context) ([]string, error) {
   659  	p.mu.Lock()
   660  	defer p.mu.Unlock()
   661  
   662  	return p.droppedDatabaseManager.ListDroppedDatabases(ctx)
   663  }
   664  
   665  func (p *DoltDatabaseProvider) DbFactoryUrl() string {
   666  	return p.dbFactoryUrl
   667  }
   668  
   669  func (p *DoltDatabaseProvider) UndropDatabase(ctx *sql.Context, name string) (err error) {
   670  	p.mu.Lock()
   671  	defer p.mu.Unlock()
   672  
   673  	newFs, exactCaseName, err := p.droppedDatabaseManager.UndropDatabase(ctx, name)
   674  	if err != nil {
   675  		return err
   676  	}
   677  
   678  	newEnv := env.Load(ctx, env.GetCurrentUserHomeDir, newFs, p.dbFactoryUrl, "TODO")
   679  	return p.registerNewDatabase(ctx, exactCaseName, newEnv)
   680  }
   681  
   682  // PurgeDroppedDatabases permanently deletes all dropped databases that have been stashed away in case they need
   683  // to be restored. Use caution with this operation – it is not reversible!
   684  func (p *DoltDatabaseProvider) PurgeDroppedDatabases(ctx *sql.Context) error {
   685  	p.mu.Lock()
   686  	defer p.mu.Unlock()
   687  
   688  	return p.droppedDatabaseManager.PurgeAllDroppedDatabases(ctx)
   689  }
   690  
   691  // registerNewDatabase registers the specified DoltEnv, |newEnv|, as a new database named |name|. This
   692  // function is responsible for instantiating the new Database instance and updating the tracking metadata
   693  // in this provider. If any problems are encountered while registering the new database, an error is returned.
   694  func (p *DoltDatabaseProvider) registerNewDatabase(ctx *sql.Context, name string, newEnv *env.DoltEnv) (err error) {
   695  	// This method MUST be called with the provider's mutex locked
   696  	if err = lockutil.AssertRWMutexIsLocked(p.mu); err != nil {
   697  		return fmt.Errorf("unable to register new database without database provider mutex being locked")
   698  	}
   699  
   700  	fkChecks, err := ctx.GetSessionVariable(ctx, "foreign_key_checks")
   701  	if err != nil {
   702  		return err
   703  	}
   704  
   705  	opts := editor.Options{
   706  		Deaf: newEnv.DbEaFactory(),
   707  		// TODO: this doesn't seem right, why is this getting set in the constructor to the DB
   708  		ForeignKeyChecksDisabled: fkChecks.(int8) == 0,
   709  	}
   710  
   711  	db, err := NewDatabase(ctx, name, newEnv.DbData(), opts)
   712  	if err != nil {
   713  		return err
   714  	}
   715  
   716  	// If we have any initialization hooks, invoke them, until any error is returned.
   717  	// By default, this will be ConfigureReplicationDatabaseHook, which will set up
   718  	// replication for the new database if a remote url template is set.
   719  	for _, initHook := range p.InitDatabaseHooks {
   720  		err = initHook(ctx, p, name, newEnv, db)
   721  		if err != nil {
   722  			return err
   723  		}
   724  	}
   725  
   726  	mrEnv, err := env.MultiEnvForSingleEnv(ctx, newEnv)
   727  	if err != nil {
   728  		return err
   729  	}
   730  	dbs, err := ApplyReplicationConfig(ctx, sql.NewBackgroundThreads(), mrEnv, cli.CliErr, db)
   731  	if err != nil {
   732  		return err
   733  	}
   734  
   735  	formattedName := formatDbMapKeyName(db.Name())
   736  	p.databases[formattedName] = dbs[0]
   737  	p.dbLocations[formattedName] = newEnv.FS
   738  	return nil
   739  }
   740  
   741  // invalidateDbStateInAllSessions removes the db state for this database from every session. This is necessary when a
   742  // database is dropped, so that other sessions don't use stale db state.
   743  func (p *DoltDatabaseProvider) invalidateDbStateInAllSessions(ctx *sql.Context, name string) error {
   744  	// Remove the db state from the current session
   745  	err := dsess.DSessFromSess(ctx.Session).RemoveDbState(ctx, name)
   746  	if err != nil {
   747  		return err
   748  	}
   749  
   750  	// If we have a running server, remove it from other sessions as well
   751  	runningServer := sqlserver.GetRunningServer()
   752  	if runningServer != nil {
   753  		sessionManager := runningServer.SessionManager()
   754  		err := sessionManager.Iter(func(session sql.Session) (bool, error) {
   755  			dsess, ok := session.(*dsess.DoltSession)
   756  			if !ok {
   757  				return false, fmt.Errorf("unexpected session type: %T", session)
   758  			}
   759  
   760  			// We need to invalidate this database state for EVERY session, even if other sessions aren't actively
   761  			// using this database, since they could still reference it with a db-qualified table name.
   762  			err := dsess.RemoveDbState(ctx, name)
   763  			if err != nil {
   764  				return true, err
   765  			}
   766  			return false, nil
   767  		})
   768  		if err != nil {
   769  			return err
   770  		}
   771  	}
   772  
   773  	return nil
   774  }
   775  
   776  func (p *DoltDatabaseProvider) databaseForRevision(ctx *sql.Context, revisionQualifiedName string, requestedName string) (dsess.SqlDatabase, bool, error) {
   777  	if !strings.Contains(revisionQualifiedName, dsess.DbRevisionDelimiter) {
   778  		return nil, false, nil
   779  	}
   780  
   781  	parts := strings.SplitN(revisionQualifiedName, dsess.DbRevisionDelimiter, 2)
   782  	baseName, rev := parts[0], parts[1]
   783  
   784  	// Look in the session cache for this DB before doing any IO to figure out what's being asked for
   785  	sess := dsess.DSessFromSess(ctx.Session)
   786  	dbCache := sess.DatabaseCache(ctx)
   787  	db, ok := dbCache.GetCachedRevisionDb(revisionQualifiedName, requestedName)
   788  	if ok {
   789  		return db, true, nil
   790  	}
   791  
   792  	p.mu.RLock()
   793  	srcDb, ok := p.databases[formatDbMapKeyName(baseName)]
   794  	p.mu.RUnlock()
   795  	if !ok {
   796  		return nil, false, nil
   797  	}
   798  
   799  	dbType, resolvedRevSpec, err := revisionDbType(ctx, srcDb, rev)
   800  	if err != nil {
   801  		return nil, false, err
   802  	}
   803  
   804  	switch dbType {
   805  	case dsess.RevisionTypeBranch:
   806  		// fetch the upstream head if this is a replicated db
   807  		replicaDb, ok := srcDb.(ReadReplicaDatabase)
   808  		if ok && replicaDb.ValidReplicaState(ctx) {
   809  			// TODO move this out of analysis phase, should only happen at read time, when the transaction begins (like is
   810  			//  the case with a branch that already exists locally)
   811  			err := p.ensureReplicaHeadExists(ctx, resolvedRevSpec, replicaDb)
   812  			if err != nil {
   813  				return nil, false, err
   814  			}
   815  		}
   816  
   817  		db, err := revisionDbForBranch(ctx, srcDb, resolvedRevSpec, requestedName)
   818  		// preserve original user case in the case of not found
   819  		if sql.ErrDatabaseNotFound.Is(err) {
   820  			return nil, false, sql.ErrDatabaseNotFound.New(revisionQualifiedName)
   821  		} else if err != nil {
   822  			return nil, false, err
   823  		}
   824  
   825  		dbCache.CacheRevisionDb(db)
   826  		return db, true, nil
   827  	case dsess.RevisionTypeTag:
   828  		// TODO: this should be an interface, not a struct
   829  		replicaDb, ok := srcDb.(ReadReplicaDatabase)
   830  
   831  		if ok {
   832  			srcDb = replicaDb.Database
   833  		}
   834  
   835  		srcDb, ok = srcDb.(Database)
   836  		if !ok {
   837  			return nil, false, nil
   838  		}
   839  
   840  		db, err := revisionDbForTag(ctx, srcDb.(Database), resolvedRevSpec, requestedName)
   841  		if err != nil {
   842  			return nil, false, err
   843  		}
   844  
   845  		dbCache.CacheRevisionDb(db)
   846  		return db, true, nil
   847  	case dsess.RevisionTypeCommit:
   848  		// TODO: this should be an interface, not a struct
   849  		replicaDb, ok := srcDb.(ReadReplicaDatabase)
   850  		if ok {
   851  			srcDb = replicaDb.Database
   852  		}
   853  
   854  		srcDb, ok = srcDb.(Database)
   855  		if !ok {
   856  			return nil, false, nil
   857  		}
   858  
   859  		db, err := revisionDbForCommit(ctx, srcDb.(Database), rev, requestedName)
   860  		if err != nil {
   861  			return nil, false, err
   862  		}
   863  
   864  		dbCache.CacheRevisionDb(db)
   865  		return db, true, nil
   866  	case dsess.RevisionTypeNone:
   867  		// Returning an error with the fully qualified db name here is our only opportunity to do so in some cases (such
   868  		// as when a branch is deleted by another client)
   869  		return nil, false, sql.ErrDatabaseNotFound.New(revisionQualifiedName)
   870  	default:
   871  		return nil, false, fmt.Errorf("unrecognized revision type for revision spec %s", rev)
   872  	}
   873  }
   874  
   875  // revisionDbType returns the type of revision spec given for the database given, and the resolved revision spec
   876  func revisionDbType(ctx *sql.Context, srcDb dsess.SqlDatabase, revSpec string) (revType dsess.RevisionType, resolvedRevSpec string, err error) {
   877  	resolvedRevSpec, err = resolveAncestorSpec(ctx, revSpec, srcDb.DbData().Ddb)
   878  	if err != nil {
   879  		return dsess.RevisionTypeNone, "", err
   880  	}
   881  
   882  	caseSensitiveBranchName, isBranch, err := isBranch(ctx, srcDb, resolvedRevSpec)
   883  	if err != nil {
   884  		return dsess.RevisionTypeNone, "", err
   885  	}
   886  
   887  	if isBranch {
   888  		return dsess.RevisionTypeBranch, caseSensitiveBranchName, nil
   889  	}
   890  
   891  	isTag, err := isTag(ctx, srcDb, resolvedRevSpec)
   892  	if err != nil {
   893  		return dsess.RevisionTypeNone, "", err
   894  	}
   895  
   896  	if isTag {
   897  		return dsess.RevisionTypeTag, resolvedRevSpec, nil
   898  	}
   899  
   900  	if doltdb.IsValidCommitHash(resolvedRevSpec) {
   901  		// IsValidCommitHash just checks a regex, we need to see if the commit actually exists
   902  		valid, err := isValidCommitHash(ctx, srcDb, resolvedRevSpec)
   903  		if err != nil {
   904  			return 0, "", err
   905  		}
   906  
   907  		if valid {
   908  			return dsess.RevisionTypeCommit, resolvedRevSpec, nil
   909  		}
   910  	}
   911  
   912  	return dsess.RevisionTypeNone, "", nil
   913  }
   914  
   915  func isValidCommitHash(ctx *sql.Context, db dsess.SqlDatabase, commitHash string) (bool, error) {
   916  	cs, err := doltdb.NewCommitSpec(commitHash)
   917  	if err != nil {
   918  		return false, err
   919  	}
   920  
   921  	for _, ddb := range db.DoltDatabases() {
   922  		_, err = ddb.Resolve(ctx, cs, nil)
   923  		if errors.Is(err, datas.ErrCommitNotFound) {
   924  			continue
   925  		} else if err != nil {
   926  			return false, err
   927  		}
   928  
   929  		return true, nil
   930  	}
   931  
   932  	return false, nil
   933  }
   934  
   935  func initialDbState(ctx context.Context, db dsess.SqlDatabase, branch string) (dsess.InitialDbState, error) {
   936  	rsr := db.DbData().Rsr
   937  	ddb := db.DbData().Ddb
   938  
   939  	var r ref.DoltRef
   940  	if len(branch) > 0 {
   941  		r = ref.NewBranchRef(branch)
   942  	} else {
   943  		var err error
   944  		r, err = rsr.CWBHeadRef()
   945  		if err != nil {
   946  			return dsess.InitialDbState{}, err
   947  		}
   948  	}
   949  
   950  	var retainedErr error
   951  
   952  	headCommit, err := ddb.ResolveCommitRef(ctx, r)
   953  	if err == doltdb.ErrBranchNotFound {
   954  		retainedErr = err
   955  		err = nil
   956  	}
   957  	if err != nil {
   958  		return dsess.InitialDbState{}, err
   959  	}
   960  
   961  	var ws *doltdb.WorkingSet
   962  	if retainedErr == nil {
   963  		workingSetRef, err := ref.WorkingSetRefForHead(r)
   964  		if err != nil {
   965  			return dsess.InitialDbState{}, err
   966  		}
   967  
   968  		ws, err = db.DbData().Ddb.ResolveWorkingSet(ctx, workingSetRef)
   969  		if err != nil {
   970  			return dsess.InitialDbState{}, err
   971  		}
   972  	}
   973  
   974  	remotes, err := rsr.GetRemotes()
   975  	if err != nil {
   976  		return dsess.InitialDbState{}, err
   977  	}
   978  
   979  	backups, err := rsr.GetBackups()
   980  	if err != nil {
   981  		return dsess.InitialDbState{}, err
   982  	}
   983  
   984  	branches, err := rsr.GetBranches()
   985  	if err != nil {
   986  		return dsess.InitialDbState{}, err
   987  	}
   988  
   989  	return dsess.InitialDbState{
   990  		Db:         db,
   991  		HeadCommit: headCommit,
   992  		WorkingSet: ws,
   993  		DbData:     db.DbData(),
   994  		Remotes:    remotes,
   995  		Branches:   branches,
   996  		Backups:    backups,
   997  		Err:        retainedErr,
   998  	}, nil
   999  }
  1000  
  1001  func initialStateForRevisionDb(ctx *sql.Context, db dsess.SqlDatabase) (dsess.InitialDbState, error) {
  1002  	switch db.RevisionType() {
  1003  	case dsess.RevisionTypeBranch:
  1004  		init, err := initialStateForBranchDb(ctx, db)
  1005  		// preserve original user case in the case of not found
  1006  		if sql.ErrDatabaseNotFound.Is(err) {
  1007  			return dsess.InitialDbState{}, sql.ErrDatabaseNotFound.New(db.Name())
  1008  		} else if err != nil {
  1009  			return dsess.InitialDbState{}, err
  1010  		}
  1011  
  1012  		return init, nil
  1013  	case dsess.RevisionTypeTag:
  1014  		// TODO: this should be an interface, not a struct
  1015  		replicaDb, ok := db.(ReadReplicaDatabase)
  1016  
  1017  		if ok {
  1018  			db = replicaDb.Database
  1019  		}
  1020  
  1021  		db, ok = db.(ReadOnlyDatabase)
  1022  		if !ok {
  1023  			return dsess.InitialDbState{}, fmt.Errorf("expected a ReadOnlyDatabase, got %T", db)
  1024  		}
  1025  
  1026  		init, err := initialStateForTagDb(ctx, db.(ReadOnlyDatabase))
  1027  		if err != nil {
  1028  			return dsess.InitialDbState{}, err
  1029  		}
  1030  
  1031  		return init, nil
  1032  	case dsess.RevisionTypeCommit:
  1033  		// TODO: this should be an interface, not a struct
  1034  		replicaDb, ok := db.(ReadReplicaDatabase)
  1035  		if ok {
  1036  			db = replicaDb.Database
  1037  		}
  1038  
  1039  		db, ok = db.(ReadOnlyDatabase)
  1040  		if !ok {
  1041  			return dsess.InitialDbState{}, fmt.Errorf("expected a ReadOnlyDatabase, got %T", db)
  1042  		}
  1043  
  1044  		init, err := initialStateForCommit(ctx, db.(ReadOnlyDatabase))
  1045  		if err != nil {
  1046  			return dsess.InitialDbState{}, err
  1047  		}
  1048  		return init, nil
  1049  	default:
  1050  		return dsess.InitialDbState{}, fmt.Errorf("unrecognized revision type for revision spec %s: %v", db.Revision(), db.RevisionType())
  1051  	}
  1052  }
  1053  
  1054  // databaseForClone returns a newly cloned database if read replication is enabled and a remote DB exists, or an error
  1055  // otherwise
  1056  func (p *DoltDatabaseProvider) databaseForClone(ctx *sql.Context, revDB string) (dsess.SqlDatabase, error) {
  1057  	if !readReplicationActive(ctx) {
  1058  		return nil, nil
  1059  	}
  1060  
  1061  	var dbName string
  1062  	if strings.Contains(revDB, dsess.DbRevisionDelimiter) {
  1063  		parts := strings.SplitN(revDB, dsess.DbRevisionDelimiter, 2)
  1064  		dbName = parts[0]
  1065  	} else {
  1066  		dbName = revDB
  1067  	}
  1068  
  1069  	err := p.attemptCloneReplica(ctx, dbName)
  1070  	if err != nil {
  1071  		ctx.GetLogger().Warnf("couldn't clone database %s: %s", dbName, err.Error())
  1072  		return nil, nil
  1073  	}
  1074  
  1075  	// This database needs to be added to the transaction
  1076  	// TODO: we should probably do all this pulling on transaction start, rather than pulling automatically when the
  1077  	//  DB is first referenced
  1078  	tx, ok := ctx.GetTransaction().(*dsess.DoltTransaction)
  1079  	if ok {
  1080  		db := p.databases[dbName]
  1081  		err = tx.AddDb(ctx, db)
  1082  		if err != nil {
  1083  			return nil, err
  1084  		}
  1085  	}
  1086  
  1087  	// now that the database has been cloned, retry the Database call
  1088  	database, err := p.Database(ctx, revDB)
  1089  	return database.(dsess.SqlDatabase), err
  1090  }
  1091  
  1092  // TODO: figure out the right contract: which variables must be set? What happens if they aren't all set?
  1093  func readReplicationActive(ctx *sql.Context) bool {
  1094  	_, readReplicaRemoteName, _ := sql.SystemVariables.GetGlobal(dsess.ReadReplicaRemote)
  1095  	if readReplicaRemoteName == "" {
  1096  		return false
  1097  	}
  1098  
  1099  	_, remoteUrlTemplate, _ := sql.SystemVariables.GetGlobal(dsess.ReplicationRemoteURLTemplate)
  1100  	if remoteUrlTemplate == "" {
  1101  		return false
  1102  	}
  1103  
  1104  	return true
  1105  }
  1106  
  1107  // resolveAncestorSpec resolves the specified revSpec to a specific commit hash if it contains an ancestor reference
  1108  // such as ~ or ^. If no ancestor reference is present, the specified revSpec is returned as is. If any unexpected
  1109  // problems are encountered, an error is returned.
  1110  func resolveAncestorSpec(ctx *sql.Context, revSpec string, ddb *doltdb.DoltDB) (string, error) {
  1111  	refname, ancestorSpec, err := doltdb.SplitAncestorSpec(revSpec)
  1112  	if err != nil {
  1113  		return "", err
  1114  	}
  1115  	if ancestorSpec == nil || ancestorSpec.SpecStr == "" {
  1116  		return revSpec, nil
  1117  	}
  1118  
  1119  	ref, err := ddb.GetRefByNameInsensitive(ctx, refname)
  1120  	if err != nil {
  1121  		return "", err
  1122  	}
  1123  
  1124  	cm, err := ddb.ResolveCommitRef(ctx, ref)
  1125  	if err != nil {
  1126  		return "", err
  1127  	}
  1128  
  1129  	optCmt, err := cm.GetAncestor(ctx, ancestorSpec)
  1130  	if err != nil {
  1131  		return "", err
  1132  	}
  1133  	ok := false
  1134  	cm, ok = optCmt.ToCommit()
  1135  	if !ok {
  1136  		return "", doltdb.ErrGhostCommitEncountered
  1137  	}
  1138  
  1139  	hash, err := cm.HashOf()
  1140  	if err != nil {
  1141  		return "", err
  1142  	}
  1143  
  1144  	return hash.String(), nil
  1145  }
  1146  
  1147  // BaseDatabase returns the base database for the specified database name. Meant for informational purposes when
  1148  // managing the session initialization only. Use SessionDatabase for normal database retrieval.
  1149  func (p *DoltDatabaseProvider) BaseDatabase(ctx *sql.Context, name string) (dsess.SqlDatabase, bool) {
  1150  	baseName := name
  1151  	isRevisionDbName := strings.Contains(name, dsess.DbRevisionDelimiter)
  1152  
  1153  	if isRevisionDbName {
  1154  		parts := strings.SplitN(name, dsess.DbRevisionDelimiter, 2)
  1155  		baseName = parts[0]
  1156  	}
  1157  
  1158  	var ok bool
  1159  	p.mu.RLock()
  1160  	db, ok := p.databases[strings.ToLower(baseName)]
  1161  	p.mu.RUnlock()
  1162  
  1163  	return db, ok
  1164  }
  1165  
  1166  // SessionDatabase implements dsess.SessionDatabaseProvider
  1167  func (p *DoltDatabaseProvider) SessionDatabase(ctx *sql.Context, name string) (dsess.SqlDatabase, bool, error) {
  1168  	baseName := name
  1169  	isRevisionDbName := strings.Contains(name, dsess.DbRevisionDelimiter)
  1170  
  1171  	if isRevisionDbName {
  1172  		// TODO: formalize and enforce this rule (can't allow DBs with / in the name)
  1173  		// TODO: some connectors will take issue with the /, we need other mechanisms to support them
  1174  		parts := strings.SplitN(name, dsess.DbRevisionDelimiter, 2)
  1175  		baseName = parts[0]
  1176  	}
  1177  
  1178  	var ok bool
  1179  	p.mu.RLock()
  1180  	db, ok := p.databases[strings.ToLower(baseName)]
  1181  	standby := *p.isStandby
  1182  	p.mu.RUnlock()
  1183  
  1184  	// If the database doesn't exist and this is a read replica, attempt to clone it from the remote
  1185  	if !ok {
  1186  		var err error
  1187  		db, err = p.databaseForClone(ctx, strings.ToLower(baseName))
  1188  
  1189  		if err != nil {
  1190  			return nil, false, err
  1191  		}
  1192  
  1193  		if db == nil {
  1194  			return nil, false, nil
  1195  		}
  1196  	}
  1197  
  1198  	// Some DB implementations don't support addressing by versioned names, so return directly if we have one of those
  1199  	if !db.Versioned() {
  1200  		return wrapForStandby(db, standby), true, nil
  1201  	}
  1202  
  1203  	// Convert to a revision database before returning. If we got a non-qualified name, convert it to a qualified name
  1204  	// using the session's current head
  1205  	revisionQualifiedName := name
  1206  	usingDefaultBranch := false
  1207  	head := ""
  1208  	sess := dsess.DSessFromSess(ctx.Session)
  1209  	if !isRevisionDbName {
  1210  		var err error
  1211  		head, ok, err = sess.CurrentHead(ctx, baseName)
  1212  		if err != nil {
  1213  			return nil, false, err
  1214  		}
  1215  
  1216  		// A newly created session may not have any info on current head stored yet, in which case we get the default
  1217  		// branch for the db itself instead.
  1218  		if !ok {
  1219  			usingDefaultBranch = true
  1220  
  1221  			head, err = dsess.DefaultHead(baseName, db)
  1222  			if err != nil {
  1223  				return nil, false, err
  1224  			}
  1225  		}
  1226  
  1227  		revisionQualifiedName = baseName + dsess.DbRevisionDelimiter + head
  1228  	}
  1229  
  1230  	db, ok, err := p.databaseForRevision(ctx, revisionQualifiedName, name)
  1231  	if err != nil {
  1232  		if sql.ErrDatabaseNotFound.Is(err) && usingDefaultBranch {
  1233  			// We can return a better error message here in some cases
  1234  			// TODO: this better error message doesn't always get returned to clients because the code path is doesn't
  1235  			//  return an error, only a boolean result (HasDB)
  1236  			return nil, false, fmt.Errorf("cannot resolve default branch head for database '%s': '%s'", baseName, head)
  1237  		} else {
  1238  			return nil, false, err
  1239  		}
  1240  	}
  1241  
  1242  	if !ok {
  1243  		return nil, false, nil
  1244  	}
  1245  
  1246  	return wrapForStandby(db, standby), true, nil
  1247  }
  1248  
  1249  // Function implements the FunctionProvider interface
  1250  func (p *DoltDatabaseProvider) Function(_ *sql.Context, name string) (sql.Function, error) {
  1251  	fn, ok := p.functions[strings.ToLower(name)]
  1252  	if !ok {
  1253  		return nil, sql.ErrFunctionNotFound.New(name)
  1254  	}
  1255  	return fn, nil
  1256  }
  1257  
  1258  func (p *DoltDatabaseProvider) Register(d sql.ExternalStoredProcedureDetails) {
  1259  	p.externalProcedures.Register(d)
  1260  }
  1261  
  1262  // ExternalStoredProcedure implements the sql.ExternalStoredProcedureProvider interface
  1263  func (p *DoltDatabaseProvider) ExternalStoredProcedure(_ *sql.Context, name string, numOfParams int) (*sql.ExternalStoredProcedureDetails, error) {
  1264  	return p.externalProcedures.LookupByNameAndParamCount(name, numOfParams)
  1265  }
  1266  
  1267  // ExternalStoredProcedures implements the sql.ExternalStoredProcedureProvider interface
  1268  func (p *DoltDatabaseProvider) ExternalStoredProcedures(_ *sql.Context, name string) ([]sql.ExternalStoredProcedureDetails, error) {
  1269  	return p.externalProcedures.LookupByName(name)
  1270  }
  1271  
  1272  // TableFunction implements the sql.TableFunctionProvider interface
  1273  func (p *DoltDatabaseProvider) TableFunction(_ *sql.Context, name string) (sql.TableFunction, error) {
  1274  	// TODO: Clean this up and store table functions in a map, similar to regular functions.
  1275  	switch strings.ToLower(name) {
  1276  	case "dolt_diff":
  1277  		return &DiffTableFunction{}, nil
  1278  	case "dolt_diff_stat":
  1279  		return &DiffStatTableFunction{}, nil
  1280  	case "dolt_diff_summary":
  1281  		return &DiffSummaryTableFunction{}, nil
  1282  	case "dolt_log":
  1283  		return &LogTableFunction{}, nil
  1284  	case "dolt_patch":
  1285  		return &PatchTableFunction{}, nil
  1286  	case "dolt_schema_diff":
  1287  		return &SchemaDiffTableFunction{}, nil
  1288  	case "dolt_reflog":
  1289  		return &ReflogTableFunction{}, nil
  1290  	case "dolt_query_diff":
  1291  		return &QueryDiffTableFunction{}, nil
  1292  	}
  1293  
  1294  	if fun, ok := p.tableFunctions[name]; ok {
  1295  		return fun, nil
  1296  	}
  1297  
  1298  	return nil, sql.ErrTableFunctionNotFound.New(name)
  1299  }
  1300  
  1301  // ensureReplicaHeadExists tries to pull the latest version of a remote branch. Will fail if the branch
  1302  // does not exist on the ReadReplicaDatabase's remote.
  1303  func (p *DoltDatabaseProvider) ensureReplicaHeadExists(ctx *sql.Context, branch string, db ReadReplicaDatabase) error {
  1304  	return db.CreateLocalBranchFromRemote(ctx, ref.NewBranchRef(branch))
  1305  }
  1306  
  1307  // isBranch returns whether a branch with the given name is in scope for the database given
  1308  func isBranch(ctx context.Context, db dsess.SqlDatabase, branchName string) (string, bool, error) {
  1309  	ddbs := db.DoltDatabases()
  1310  
  1311  	brName, branchExists, err := isLocalBranch(ctx, ddbs, branchName)
  1312  	if err != nil {
  1313  		return "", false, err
  1314  	}
  1315  	if branchExists {
  1316  		return brName, true, nil
  1317  	}
  1318  
  1319  	brName, branchExists, err = isRemoteBranch(ctx, ddbs, branchName)
  1320  	if err != nil {
  1321  		return "", false, err
  1322  	}
  1323  	if branchExists {
  1324  		return brName, true, nil
  1325  	}
  1326  
  1327  	return "", false, nil
  1328  }
  1329  
  1330  func isLocalBranch(ctx context.Context, ddbs []*doltdb.DoltDB, branchName string) (string, bool, error) {
  1331  	for _, ddb := range ddbs {
  1332  		brName, branchExists, err := ddb.HasBranch(ctx, branchName)
  1333  		if err != nil {
  1334  			return "", false, err
  1335  		}
  1336  
  1337  		if branchExists {
  1338  			return brName, true, nil
  1339  		}
  1340  	}
  1341  
  1342  	return "", false, nil
  1343  }
  1344  
  1345  // isRemoteBranch returns whether the given branch name is a remote branch on any of the databases provided.
  1346  func isRemoteBranch(ctx context.Context, ddbs []*doltdb.DoltDB, branchName string) (string, bool, error) {
  1347  	for _, ddb := range ddbs {
  1348  		bn, branchExists, _, err := ddb.HasRemoteTrackingBranch(ctx, branchName)
  1349  		if err != nil {
  1350  			return "", false, err
  1351  		}
  1352  
  1353  		if branchExists {
  1354  			return bn, true, nil
  1355  		}
  1356  	}
  1357  
  1358  	return "", false, nil
  1359  }
  1360  
  1361  // isTag returns whether a tag with the given name is in scope for the database given
  1362  func isTag(ctx context.Context, db dsess.SqlDatabase, tagName string) (bool, error) {
  1363  	ddbs := db.DoltDatabases()
  1364  
  1365  	for _, ddb := range ddbs {
  1366  		tagExists, err := ddb.HasTag(ctx, tagName)
  1367  		if err != nil {
  1368  			return false, err
  1369  		}
  1370  
  1371  		if tagExists {
  1372  			return true, nil
  1373  		}
  1374  	}
  1375  
  1376  	return false, nil
  1377  }
  1378  
  1379  // revisionDbForBranch returns a new database that is tied to the branch named by revSpec
  1380  func revisionDbForBranch(ctx context.Context, srcDb dsess.SqlDatabase, revSpec string, requestedName string) (dsess.SqlDatabase, error) {
  1381  	static := staticRepoState{
  1382  		branch:          ref.NewBranchRef(revSpec),
  1383  		RepoStateWriter: srcDb.DbData().Rsw,
  1384  		RepoStateReader: srcDb.DbData().Rsr,
  1385  	}
  1386  
  1387  	return srcDb.WithBranchRevision(requestedName, dsess.SessionDatabaseBranchSpec{
  1388  		RepoState: static,
  1389  		Branch:    revSpec,
  1390  	})
  1391  }
  1392  
  1393  func initialStateForBranchDb(ctx *sql.Context, srcDb dsess.SqlDatabase) (dsess.InitialDbState, error) {
  1394  	revSpec := srcDb.Revision()
  1395  
  1396  	// TODO: this may be a disabled transaction, need to kill those
  1397  	rootHash, err := dsess.TransactionRoot(ctx, srcDb)
  1398  	if err != nil {
  1399  		return dsess.InitialDbState{}, err
  1400  	}
  1401  
  1402  	branch := ref.NewBranchRef(revSpec)
  1403  	cm, err := srcDb.DbData().Ddb.ResolveCommitRefAtRoot(ctx, branch, rootHash)
  1404  	if err != nil {
  1405  		return dsess.InitialDbState{}, err
  1406  	}
  1407  
  1408  	wsRef, err := ref.WorkingSetRefForHead(branch)
  1409  	if err != nil {
  1410  		return dsess.InitialDbState{}, err
  1411  	}
  1412  
  1413  	ws, err := srcDb.DbData().Ddb.ResolveWorkingSetAtRoot(ctx, wsRef, rootHash)
  1414  	if err != nil {
  1415  		return dsess.InitialDbState{}, err
  1416  	}
  1417  
  1418  	static := staticRepoState{
  1419  		branch:          branch,
  1420  		RepoStateWriter: srcDb.DbData().Rsw,
  1421  		RepoStateReader: srcDb.DbData().Rsr,
  1422  	}
  1423  
  1424  	remotes, err := static.GetRemotes()
  1425  	if err != nil {
  1426  		return dsess.InitialDbState{}, err
  1427  	}
  1428  
  1429  	branches, err := static.GetBranches()
  1430  	if err != nil {
  1431  		return dsess.InitialDbState{}, err
  1432  	}
  1433  
  1434  	backups, err := static.GetBackups()
  1435  	if err != nil {
  1436  		return dsess.InitialDbState{}, err
  1437  	}
  1438  
  1439  	init := dsess.InitialDbState{
  1440  		Db:         srcDb,
  1441  		HeadCommit: cm,
  1442  		WorkingSet: ws,
  1443  		DbData: env.DbData{
  1444  			Ddb: srcDb.DbData().Ddb,
  1445  			Rsw: static,
  1446  			Rsr: static,
  1447  		},
  1448  		Remotes:  remotes,
  1449  		Branches: branches,
  1450  		Backups:  backups,
  1451  	}
  1452  
  1453  	return init, nil
  1454  }
  1455  
  1456  func revisionDbForTag(ctx context.Context, srcDb Database, revSpec string, requestedName string) (ReadOnlyDatabase, error) {
  1457  	baseName, _ := dsess.SplitRevisionDbName(srcDb.Name())
  1458  	return ReadOnlyDatabase{Database: Database{
  1459  		baseName:      baseName,
  1460  		requestedName: requestedName,
  1461  		ddb:           srcDb.DbData().Ddb,
  1462  		rsw:           srcDb.DbData().Rsw,
  1463  		rsr:           srcDb.DbData().Rsr,
  1464  		editOpts:      srcDb.editOpts,
  1465  		revision:      revSpec,
  1466  		revType:       dsess.RevisionTypeTag,
  1467  	}}, nil
  1468  }
  1469  
  1470  func initialStateForTagDb(ctx context.Context, srcDb ReadOnlyDatabase) (dsess.InitialDbState, error) {
  1471  	revSpec := srcDb.Revision()
  1472  	tag := ref.NewTagRef(revSpec)
  1473  
  1474  	cm, err := srcDb.DbData().Ddb.ResolveCommitRef(ctx, tag)
  1475  	if err != nil {
  1476  		return dsess.InitialDbState{}, err
  1477  	}
  1478  
  1479  	init := dsess.InitialDbState{
  1480  		Db:         srcDb,
  1481  		HeadCommit: cm,
  1482  		ReadOnly:   true,
  1483  		DbData: env.DbData{
  1484  			Ddb: srcDb.DbData().Ddb,
  1485  			Rsw: srcDb.DbData().Rsw,
  1486  			Rsr: srcDb.DbData().Rsr,
  1487  		},
  1488  		Remotes: concurrentmap.New[string, env.Remote](),
  1489  		// todo: should we initialize
  1490  		//  - Remotes
  1491  		//  - Branches
  1492  		//  - Backups
  1493  		//  - ReadReplicas
  1494  	}
  1495  
  1496  	return init, nil
  1497  }
  1498  
  1499  func revisionDbForCommit(ctx context.Context, srcDb Database, revSpec string, requestedName string) (ReadOnlyDatabase, error) {
  1500  	baseName, _ := dsess.SplitRevisionDbName(srcDb.Name())
  1501  	return ReadOnlyDatabase{Database: Database{
  1502  		baseName:      baseName,
  1503  		requestedName: requestedName,
  1504  		ddb:           srcDb.DbData().Ddb,
  1505  		rsw:           srcDb.DbData().Rsw,
  1506  		rsr:           srcDb.DbData().Rsr,
  1507  		editOpts:      srcDb.editOpts,
  1508  		revision:      revSpec,
  1509  		revType:       dsess.RevisionTypeCommit,
  1510  	}}, nil
  1511  }
  1512  
  1513  func initialStateForCommit(ctx context.Context, srcDb ReadOnlyDatabase) (dsess.InitialDbState, error) {
  1514  	revSpec := srcDb.Revision()
  1515  
  1516  	spec, err := doltdb.NewCommitSpec(revSpec)
  1517  	if err != nil {
  1518  		return dsess.InitialDbState{}, err
  1519  	}
  1520  
  1521  	headRef, err := srcDb.DbData().Rsr.CWBHeadRef()
  1522  	if err != nil {
  1523  		return dsess.InitialDbState{}, err
  1524  	}
  1525  	optCmt, err := srcDb.DbData().Ddb.Resolve(ctx, spec, headRef)
  1526  	if err != nil {
  1527  		return dsess.InitialDbState{}, err
  1528  	}
  1529  	cm, ok := optCmt.ToCommit()
  1530  	if !ok {
  1531  		return dsess.InitialDbState{}, doltdb.ErrGhostCommitEncountered
  1532  	}
  1533  
  1534  	init := dsess.InitialDbState{
  1535  		Db:         srcDb,
  1536  		HeadCommit: cm,
  1537  		ReadOnly:   true,
  1538  		DbData: env.DbData{
  1539  			Ddb: srcDb.DbData().Ddb,
  1540  			Rsw: srcDb.DbData().Rsw,
  1541  			Rsr: srcDb.DbData().Rsr,
  1542  		},
  1543  		Remotes: concurrentmap.New[string, env.Remote](),
  1544  		// todo: should we initialize
  1545  		//  - Remotes
  1546  		//  - Branches
  1547  		//  - Backups
  1548  		//  - ReadReplicas
  1549  	}
  1550  
  1551  	return init, nil
  1552  }
  1553  
  1554  type staticRepoState struct {
  1555  	branch ref.DoltRef
  1556  	env.RepoStateWriter
  1557  	env.RepoStateReader
  1558  }
  1559  
  1560  func (s staticRepoState) CWBHeadRef() (ref.DoltRef, error) {
  1561  	return s.branch, nil
  1562  }
  1563  
  1564  // formatDbMapKeyName returns formatted string of database name and/or branch name. Database name is case-insensitive,
  1565  // so it's stored in lower case name. Branch name is case-sensitive, so not changed.
  1566  // TODO: branch names should be case-insensitive too
  1567  func formatDbMapKeyName(name string) string {
  1568  	if !strings.Contains(name, dsess.DbRevisionDelimiter) {
  1569  		return strings.ToLower(name)
  1570  	}
  1571  
  1572  	parts := strings.SplitN(name, dsess.DbRevisionDelimiter, 2)
  1573  	dbName, revSpec := parts[0], parts[1]
  1574  
  1575  	return strings.ToLower(dbName) + dsess.DbRevisionDelimiter + revSpec
  1576  }