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 }