Adding support for the restore subcommand in the dolt_backup stored procedure

This commit is contained in:
Jason Fulghum
2024-06-11 16:08:58 -07:00
parent add3ceac96
commit 057e3c8786
2 changed files with 159 additions and 13 deletions
@@ -21,6 +21,8 @@ import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/doltversion"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
"github.com/dolthub/dolt/go/libraries/doltcore/branch_control"
"github.com/dolthub/dolt/go/libraries/doltcore/dbfactory"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
@@ -29,6 +31,7 @@ import (
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/libraries/utils/filesys"
"github.com/dolthub/dolt/go/store/datas/pull"
"github.com/dolthub/dolt/go/store/types"
)
const (
@@ -90,7 +93,9 @@ func doDoltBackup(ctx *sql.Context, args []string) (int, error) {
return statusErr, fmt.Errorf("error removing backup: %w", err)
}
case cli.RestoreBackupId:
return statusErr, fmt.Errorf("restoring backup endpoint in sql is unimplemented.")
if err = restoreBackup(ctx, dbData, apr); err != nil {
return statusErr, fmt.Errorf("error restoring backup: %w", err)
}
case cli.SyncBackupUrlId:
err = syncBackupViaUrl(ctx, dbData, sess, apr)
if err != nil {
@@ -147,9 +152,64 @@ func addBackup(ctx *sql.Context, dbData env.DbData, apr *argparser.ArgParseResul
}
}
func restoreBackup(ctx *sql.Context, _ env.DbData, apr *argparser.ArgParseResults) error {
if apr.NArg() != 3 {
return fmt.Errorf("usage: dolt_backup('restore', 'backup_url', 'database_name')")
}
backupUrl := strings.TrimSpace(apr.Arg(1))
dbName := strings.TrimSpace(apr.Arg(2))
force := apr.Contains(cli.ForceFlag)
remoteParams := map[string]string{}
r := env.NewRemote("", backupUrl, remoteParams)
srcDb, err := r.GetRemoteDB(ctx, types.Format_Default, nil)
if err != nil {
return err
}
sess := dsess.DSessFromSess(ctx.Session)
existingDbData, restoringExistingDb := sess.GetDbData(ctx, dbName)
if restoringExistingDb {
if !force {
return fmt.Errorf("error: cannot restore backup into %s. "+
"A database with that name already exists. Did you mean to supply --force?", dbName)
}
return syncRootsFromBackup(ctx, existingDbData, sess, r)
} else {
// Track whether the db directory existed before we tried to create it, so we can clean up on errors
userDirExisted, _ := sess.Provider().FileSystem().Exists(dbName)
// Create a new Dolt env for the clone; use env.NoRemote to avoid origin upstream
clonedEnv, err := actions.EnvForClone(ctx, srcDb.ValueReadWriter().Format(), env.NoRemote, dbName,
sess.Provider().FileSystem(), doltversion.Version, env.GetCurrentUserHomeDir)
if err != nil {
return errhand.VerboseErrorFromError(err)
}
// make empty repo state
_, err = env.CreateRepoState(clonedEnv.FS, env.DefaultInitBranch)
if err != nil {
return err
}
if err = syncRootsFromBackup(ctx, clonedEnv.DbData(), sess, r); err != nil {
// If we're cloning into a directory that already exists do not erase it.
// Otherwise, make a best effort to delete any directory we created.
if userDirExisted {
_ = clonedEnv.FS.Delete(dbfactory.DoltDir, true)
} else {
_ = clonedEnv.FS.Delete(".", true)
}
}
return err
}
}
func removeBackup(ctx *sql.Context, dbData env.DbData, apr *argparser.ArgParseResults) error {
if apr.NArg() != 2 {
return fmt.Errorf("usage: dolt_backup('remove', 'backup_name'")
return fmt.Errorf("usage: dolt_backup('remove', 'backup_name')")
}
backupName := strings.TrimSpace(apr.Arg(1))
@@ -210,7 +270,7 @@ func syncBackupViaUrl(ctx *sql.Context, dbData env.DbData, sess *dsess.DoltSessi
b := env.NewRemote("__temp__", backupUrl, params)
return syncRoots(ctx, dbData, sess, b)
return syncRootsToBackup(ctx, dbData, sess, b)
}
func syncBackupViaName(ctx *sql.Context, dbData env.DbData, sess *dsess.DoltSession, apr *argparser.ArgParseResults) error {
@@ -229,10 +289,11 @@ func syncBackupViaName(ctx *sql.Context, dbData env.DbData, sess *dsess.DoltSess
return fmt.Errorf("error: unknown backup: '%s'; %v", backupName, backups)
}
return syncRoots(ctx, dbData, sess, b)
return syncRootsToBackup(ctx, dbData, sess, b)
}
func syncRoots(ctx *sql.Context, dbData env.DbData, sess *dsess.DoltSession, backup env.Remote) error {
// syncRootsToBackup syncs the roots from |dbData| to the backup specified by |backup|.
func syncRootsToBackup(ctx *sql.Context, dbData env.DbData, sess *dsess.DoltSession, backup env.Remote) error {
destDb, err := sess.Provider().GetRemoteDB(ctx, dbData.Ddb.ValueReadWriter().Format(), backup, true)
if err != nil {
return fmt.Errorf("error loading backup destination: %w", err)
@@ -250,3 +311,23 @@ func syncRoots(ctx *sql.Context, dbData env.DbData, sess *dsess.DoltSession, bac
return nil
}
// syncRootsFromBackup syncs the roots from the backup specified by |backup| to |dbData|.
func syncRootsFromBackup(ctx *sql.Context, dbData env.DbData, sess *dsess.DoltSession, backup env.Remote) error {
destDb, err := sess.Provider().GetRemoteDB(ctx, dbData.Ddb.ValueReadWriter().Format(), backup, true)
if err != nil {
return fmt.Errorf("error loading backup destination: %w", err)
}
tmpDir, err := dbData.Rsw.TempTableFilesDir()
if err != nil {
return err
}
err = actions.SyncRoots(ctx, destDb, dbData.Ddb, tmpDir, runProgFuncs, stopProgFuncs)
if err != nil && err != pull.ErrDBUpToDate {
return fmt.Errorf("error syncing backup: %w", err)
}
return nil
}
+73 -8
View File
@@ -80,15 +80,80 @@ teardown() {
[[ ! "$output" =~ "bac1" ]] || false
}
@test "sql-backup: dolt_backup restore invalid arguments" {
# Not enough arguments
run dolt sql -q "call dolt_backup('restore')"
[ "$status" -eq 1 ]
[[ "$output" =~ "usage: dolt_backup('restore', 'backup_url', 'database_name')" ]] || false
# Not enough arguments
run dolt sql -q "call dolt_backup('restore', 'file:///some_directory')"
[ "$status" -eq 1 ]
[[ "$output" =~ "usage: dolt_backup('restore', 'backup_url', 'database_name')" ]] || false
# Too many arguments
run dolt sql -q "call dolt_backup('restore', 'hostedapidb-0', 'file:///some_directory', 'too many')"
[ "$status" -eq 1 ]
[[ "$output" =~ "usage: dolt_backup('restore', 'backup_url', 'database_name')" ]] || false
}
@test "sql-backup: dolt_backup restore" {
run dolt sql -q "call dolt_backup('restore', 'hostedapidb-0', 'file:///some_directory')"
[ "$status" -ne 0 ]
run dolt sql -q "CALL dolt_backup('restore', 'hostedapidb-0', 'file:///some_directory')"
[ "$status" -ne 0 ]
run dolt sql -q "call dolt_backup('restore', 'hostedapidb-0', 'file:///some_directory')"
[ "$status" -ne 0 ]
run dolt sql -q "CALL dolt_backup('restore', 'hostedapidb-0', 'file:///some_directory')"
[ "$status" -ne 0 ]
backupsDir="$PWD/backups"
mkdir backupsDir
# Created a nested database, back it up, drop it, then restore it with a new name
dolt sql -q "create database db1;"
cd db1
dolt sql -q "create table t1 (pk int primary key); insert into t1 values (42); call dolt_commit('-Am', 'creating table t1');"
dolt sql -q "call dolt_backup('add', 'backups', 'file://$backupsDir');"
dolt sql -q "call dolt_backup('sync', 'backups');"
cd ..
dolt sql -q "drop database db1;"
dolt sql -q "call dolt_backup('restore', 'file://$backupsDir', 'db2');"
# Assert that db2 is present, and db1 is not
run dolt sql -q "show databases;"
[ "$status" -eq 0 ]
[[ ! "$output" =~ "db1" ]] || false
[[ "$output" =~ "db2" ]] || false
# Sanity check some data in the database
run dolt sql -q "use db2; select * from t1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "42" ]] || false
# Assert that db2 doesn't have any remotes
cd db2
run dolt remote -v
[ "$status" -eq 0 ]
[ "${#lines[@]}" -eq 0 ]
}
@test "sql-backup: dolt_backup restore --force" {
backupsDir="$PWD/backups"
mkdir backupsDir
# Created a nested database, and back it up
dolt sql -q "create database db1;"
cd db1
dolt sql -q "create table t1 (pk int primary key); insert into t1 values (42); call dolt_commit('-Am', 'creating table t1');"
dolt sql -q "call dolt_backup('add', 'backups', 'file://$backupsDir');"
dolt sql -q "call dolt_backup('sync', 'backups');"
# Make a new commit in db1, but don't push it to the backup
dolt sql -q "update t1 set pk=100; call dolt_commit('-Am', 'updating table t1');"
# Assert that without --force, we can't update an existing db from a backup
run dolt sql -q "call dolt_backup('restore', 'file://$backupsDir', 'db1');"
[ "$status" -eq 1 ]
[[ "$output" =~ "cannot restore backup into db1. A database with that name already exists." ]] || false
# Use --force to overwrite the existing database and sanity check the data
run dolt sql -q "call dolt_backup('restore', '--force', 'file://$backupsDir', 'db1');"
[ "$status" -eq 0 ]
run dolt sql -q "use db1; select * from t1;"
[ "$status" -eq 0 ]
[[ "$output" =~ "42" ]] || false
}
@test "sql-backup: dolt_backup unrecognized" {