diff --git a/docs/generated/sql/bnf/restore.bnf b/docs/generated/sql/bnf/restore.bnf index 60310a48178c..171bffe4cfe2 100644 --- a/docs/generated/sql/bnf/restore.bnf +++ b/docs/generated/sql/bnf/restore.bnf @@ -1,4 +1,5 @@ restore_stmt ::= 'RESTORE' 'FROM' list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options + | 'RESTORE' 'FROM' string_or_placeholder 'IN' list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options | 'RESTORE' ( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* ) 'FROM' list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options | 'RESTORE' ( ( 'TABLE' | ) table_pattern ( ( ',' table_pattern ) )* | 'DATABASE' database_name ( ( ',' database_name ) )* ) 'FROM' string_or_placeholder 'IN' list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index 771937bfe8e6..d89121b942d6 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -167,6 +167,7 @@ reset_stmt ::= restore_stmt ::= 'RESTORE' 'FROM' list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options + | 'RESTORE' 'FROM' string_or_placeholder 'IN' list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options | 'RESTORE' targets 'FROM' list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options | 'RESTORE' targets 'FROM' string_or_placeholder 'IN' list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options diff --git a/pkg/ccl/backupccl/backup_test.go b/pkg/ccl/backupccl/backup_test.go index 3cccfd0b7188..f5ad38dce2aa 100644 --- a/pkg/ccl/backupccl/backup_test.go +++ b/pkg/ccl/backupccl/backup_test.go @@ -590,6 +590,16 @@ func TestBackupRestoreAppend(t *testing.T) { sqlDB.Exec(t, "DROP DATABASE data CASCADE") sqlDB.Exec(t, "RESTORE DATABASE data FROM $4 IN ($1, $2, $3) AS OF SYSTEM TIME "+ts2, append(collections, fullBackup2)...) + + if test.name != "userfile" { + // Cluster restores from userfile are not supported yet since the + // restoring cluster needs to be empty, which means it can't contain any + // userfile tables. + + _, _, sqlDBRestore, cleanupEmptyCluster := backupRestoreTestSetupEmpty(t, MultiNode, tmpDir, InitNone) + defer cleanupEmptyCluster() + sqlDBRestore.Exec(t, "RESTORE FROM $4 IN ($1, $2, $3) AS OF SYSTEM TIME "+ts2, append(collections, fullBackup2)...) + } } if test.name == "userfile" { diff --git a/pkg/sql/parser/parse_test.go b/pkg/sql/parser/parse_test.go index 31c1b2f7ea03..1052082cd448 100644 --- a/pkg/sql/parser/parse_test.go +++ b/pkg/sql/parser/parse_test.go @@ -1614,6 +1614,14 @@ func TestParse(t *testing.T) { {`RESTORE DATABASE foo FROM ($1, $2), ($3, $4)`}, {`RESTORE DATABASE foo FROM ($1, $2), ($3, $4) AS OF SYSTEM TIME '1'`}, + {`RESTORE FROM ($1, $2)`}, + {`RESTORE FROM ($1, $2), $3`}, + {`RESTORE FROM $1, ($2, $3)`}, + {`RESTORE FROM ($1, $2), ($3, $4)`}, + {`RESTORE FROM ($1, $2), ($3, $4) AS OF SYSTEM TIME '1'`}, + {`RESTORE FROM $4 IN $1, $2, 'bar'`}, + {`RESTORE FROM $4 IN $1, $2, 'bar' AS OF SYSTEM TIME '1' WITH skip_missing_foreign_keys`}, + {`RESTORE TENANT 36 FROM ($1, $2) AS OF SYSTEM TIME '1'`}, {`BACKUP TABLE foo TO 'bar' WITH revision_history, detached`}, diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index ee81ab213e8c..97a85bdc5429 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -2447,6 +2447,16 @@ restore_stmt: Options: *($5.restoreOptions()), } } +| RESTORE FROM string_or_placeholder IN list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options + { + $$.val = &tree.Restore{ + DescriptorCoverage: tree.AllDescriptors, + Subdir: $3.expr(), + From: $5.listOfStringOrPlaceholderOptList(), + AsOf: $6.asOfClause(), + Options: *($7.restoreOptions()), + } + } | RESTORE targets FROM list_of_string_or_placeholder_opt_list opt_as_of_clause opt_with_restore_options { $$.val = &tree.Restore{