From 382ddd54854579eb3e0b4df2ff025d2f22fc1fc4 Mon Sep 17 00:00:00 2001 From: Paul Bardea Date: Mon, 2 Dec 2019 16:25:37 -0500 Subject: [PATCH] backupccl: allow restoration of empty db Previously, RESTORE did not support restoring empty databases. However, there are use cases in which scripts perform the backup and restore process and they have run into issues since when they go to RESTORE all of their databases, if any are empty the script will fail. Since BACKUP supports the backing up of empty databases, it makes sense to allow RESTORE to restore those databases that were backed up. Release note (enterprise change): RESTORE now supports the restoration of empty databases. --- pkg/ccl/backupccl/backup_test.go | 30 ++++++++++++++++++++++-------- pkg/ccl/backupccl/restore.go | 28 +++++++++++++--------------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/pkg/ccl/backupccl/backup_test.go b/pkg/ccl/backupccl/backup_test.go index a66d87f669eb..768fd12ffc81 100644 --- a/pkg/ccl/backupccl/backup_test.go +++ b/pkg/ccl/backupccl/backup_test.go @@ -1573,15 +1573,14 @@ func TestBackupRestoreCrossTableReferences(t *testing.T) { // Test cases where, after filtering out views that can't be restored, there are no other tables to restore - db.ExpectErr(t, `no tables to restore: DATABASE storestats`, - `RESTORE DATABASE storestats from $1 WITH OPTIONS ('skip_missing_views')`, localFoo) + db.Exec(t, `RESTORE DATABASE storestats from $1 WITH OPTIONS ('skip_missing_views')`, localFoo) + db.Exec(t, `RESTORE storestats.ordercounts from $1 WITH OPTIONS ('skip_missing_views')`, localFoo) + // Ensure that the views were not restored since they are missing the tables they reference. + db.CheckQueryResults(t, `USE storestats; SHOW TABLES;`, [][]string{}) - db.ExpectErr(t, `no tables to restore: TABLE storestats.ordercounts`, - `RESTORE storestats.ordercounts from $1 WITH OPTIONS ('skip_missing_views')`, localFoo) - - // referencing_early_customers depends only on early_customers, which can't be restored - db.ExpectErr(t, `no tables to restore: TABLE store.early_customers, store.referencing_early_customers`, - `RESTORE store.early_customers, store.referencing_early_customers from $1 WITH OPTIONS ('skip_missing_views')`, localFoo) + db.Exec(t, `RESTORE store.early_customers, store.referencing_early_customers from $1 WITH OPTIONS ('skip_missing_views')`, localFoo) + // Ensure that the views were not restored since they are missing the tables they reference. + db.CheckQueryResults(t, `SHOW TABLES;`, [][]string{}) // Test that views with valid dependencies are restored @@ -3078,6 +3077,21 @@ func TestBackupCreatedStats(t *testing.T) { }) } +// Ensure that backing up and restoring an empty database succeeds. +func TestBackupRestoreEmptyDB(t *testing.T) { + defer leaktest.AfterTest(t)() + + const numAccounts = 1 + _, _, sqlDB, _, cleanupFn := backupRestoreTestSetup(t, singleNode, numAccounts, initNone) + defer cleanupFn() + + sqlDB.Exec(t, `CREATE DATABASE empty`) + sqlDB.Exec(t, `BACKUP DATABASE empty TO $1`, localFoo) + sqlDB.Exec(t, `DROP DATABASE empty`) + sqlDB.Exec(t, `RESTORE DATABASE empty FROM $1`, localFoo) + sqlDB.CheckQueryResults(t, `USE empty; SHOW TABLES;`, [][]string{}) +} + func TestBackupRestoreSubsetCreatedStats(t *testing.T) { defer leaktest.AfterTest(t)() diff --git a/pkg/ccl/backupccl/restore.go b/pkg/ccl/backupccl/restore.go index a73726a28f59..4c2bb99477e7 100644 --- a/pkg/ccl/backupccl/restore.go +++ b/pkg/ccl/backupccl/restore.go @@ -214,15 +214,8 @@ func selectTargets( return nil, nil, err } - seenTable := false - for _, desc := range matched.descs { - if desc.Table(hlc.Timestamp{}) != nil { - seenTable = true - break - } - } - if !seenTable { - return nil, nil, errors.Errorf("no tables found: %s", tree.ErrString(&targets)) + if len(matched.descs) == 0 { + return nil, nil, errors.Errorf("no tables or databases matched the given targets: %s", tree.ErrString(&targets)) } if lastBackupDesc.FormatVersion >= BackupFormatDescriptorTrackingVersion { @@ -1546,9 +1539,6 @@ func doRestorePlan( if err != nil { return err } - if len(filteredTablesByID) == 0 { - return errors.Errorf("no tables to restore: %s", tree.ErrString(&restoreStmt.Targets)) - } tableRewrites, err := allocateTableRewrites(ctx, p, databasesByID, filteredTablesByID, restoreDBs, opts) if err != nil { return err @@ -1761,6 +1751,17 @@ func (r *restoreResumer) Resume( } r.tables = tables r.databases = databases + r.exec = p.ExecCfg().InternalExecutor + r.statsRefresher = p.ExecCfg().StatsRefresher + r.latestStats = remapRelevantStatistics(latestBackupDesc, details.TableRewrites) + + if len(r.tables) == 0 { + // We have no tables to restore (we are restoring an empty DB). + // Since we have already created any new databases that we needed, + // we can return without importing any data. + log.Warning(ctx, "no tables to restore") + return nil + } res, err := restore( ctx, @@ -1776,9 +1777,6 @@ func (r *restoreResumer) Resume( r.job, ) r.res = res - r.exec = p.ExecCfg().InternalExecutor - r.statsRefresher = p.ExecCfg().StatsRefresher - r.latestStats = remapRelevantStatistics(latestBackupDesc, details.TableRewrites) return err }