github.com/cloudberrydb/gpbackup@v1.0.3-0.20240118031043-5410fd45eed6/restore/validate_test.go (about)

     1  package restore_test
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/DATA-DOG/go-sqlmock"
     7  	"github.com/cloudberrydb/gp-common-go-libs/testhelper"
     8  	"github.com/cloudberrydb/gpbackup/history"
     9  	"github.com/cloudberrydb/gpbackup/options"
    10  	"github.com/cloudberrydb/gpbackup/restore"
    11  	"github.com/cloudberrydb/gpbackup/testutils"
    12  	"github.com/cloudberrydb/gpbackup/toc"
    13  	"github.com/cloudberrydb/gpbackup/utils"
    14  	"github.com/spf13/cobra"
    15  
    16  	. "github.com/onsi/ginkgo/v2"
    17  	. "github.com/onsi/gomega"
    18  )
    19  
    20  var _ = Describe("restore/validate tests", func() {
    21  	var filterList []string
    22  	var tocfile *toc.TOC
    23  	var backupfile *utils.FileWithByteCount
    24  	AfterEach(func() {
    25  		filterList = []string{}
    26  	})
    27  	Describe("ValidateSchemasInBackupSet", func() {
    28  		sequence := toc.StatementWithType{ObjectType: "SEQUENCE", Statement: "CREATE SEQUENCE schema.somesequence"}
    29  		sequenceLen := uint64(len(sequence.Statement))
    30  		table1 := toc.StatementWithType{ObjectType: "TABLE", Statement: "CREATE TABLE schema1.table1"}
    31  		table1Len := uint64(len(table1.Statement))
    32  		table2 := toc.StatementWithType{ObjectType: "TABLE", Statement: "CREATE TABLE schema2.table2"}
    33  		table2Len := uint64(len(table2.Statement))
    34  		BeforeEach(func() {
    35  			tocfile, backupfile = testutils.InitializeTestTOC(buffer, "predata")
    36  			backupfile.ByteCount = table1Len
    37  			tocfile.AddMetadataEntry("predata", toc.MetadataEntry{Schema: "schema1", Name: "table1", ObjectType: "TABLE"}, 0, backupfile.ByteCount)
    38  			tocfile.AddCoordinatorDataEntry("schema1", "table1", 1, "(i)", 0, "", "")
    39  			backupfile.ByteCount += table2Len
    40  			tocfile.AddMetadataEntry("predata", toc.MetadataEntry{Schema: "schema2", Name: "table2", ObjectType: "TABLE"}, table1Len, backupfile.ByteCount)
    41  			tocfile.AddCoordinatorDataEntry("schema2", "table2", 2, "(j)", 0, "", "")
    42  			backupfile.ByteCount += sequenceLen
    43  			tocfile.AddMetadataEntry("predata", toc.MetadataEntry{Schema: "schema", Name: "somesequence", ObjectType: "SEQUENCE"}, table1Len+table2Len, backupfile.ByteCount)
    44  			restore.SetTOC(tocfile)
    45  		})
    46  		It("passes when schema exists in normal backup", func() {
    47  			restore.SetBackupConfig(&history.BackupConfig{})
    48  			filterList = []string{"schema1"}
    49  			restore.ValidateIncludeSchemasInBackupSet(filterList)
    50  		})
    51  		It("panics when schema does not exist in normal backup", func() {
    52  			restore.SetBackupConfig(&history.BackupConfig{})
    53  			filterList = []string{"schema3"}
    54  			defer testhelper.ShouldPanicWithMessage("Could not find the following schema(s) in the backup set: schema3")
    55  			restore.ValidateIncludeSchemasInBackupSet(filterList)
    56  		})
    57  		It("passes when schema exists in data-only backup", func() {
    58  			restore.SetBackupConfig(&history.BackupConfig{DataOnly: true})
    59  			filterList = []string{"schema1"}
    60  			restore.ValidateIncludeSchemasInBackupSet(filterList)
    61  		})
    62  		It("panics when schema does not exist in data-only backup", func() {
    63  			restore.SetBackupConfig(&history.BackupConfig{DataOnly: true})
    64  			filterList = []string{"schema3"}
    65  			defer testhelper.ShouldPanicWithMessage("Could not find the following schema(s) in the backup set: schema3")
    66  			restore.ValidateIncludeSchemasInBackupSet(filterList)
    67  		})
    68  		It("generates warning when exclude-schema does not exist in backup and noFatal is true", func() {
    69  			_, _, logfile = testhelper.SetupTestLogger()
    70  			restore.SetBackupConfig(&history.BackupConfig{})
    71  			filterList = []string{"schema3"}
    72  			restore.ValidateExcludeSchemasInBackupSet(filterList)
    73  			testhelper.ExpectRegexp(logfile, "[WARNING]:-Could not find the following excluded schema(s) in the backup set: schema3")
    74  		})
    75  	})
    76  	Describe("GenerateRestoreRelationList", func() {
    77  		var opts *options.Options
    78  		BeforeEach(func() {
    79  			tocfile, _ = testutils.InitializeTestTOC(buffer, "metadata")
    80  			tocfile.AddCoordinatorDataEntry("s1", "table1", 1, "(j)", 0, "", "")
    81  			tocfile.AddCoordinatorDataEntry("s1", "table2", 2, "(j)", 0, "", "")
    82  			tocfile.AddCoordinatorDataEntry("s2", "table1", 3, "(j)", 0, "", "")
    83  			tocfile.AddCoordinatorDataEntry("s2", "table2", 4, "(j)", 0, "", "")
    84  			restore.SetTOC(tocfile)
    85  
    86  			opts = &options.Options{}
    87  		})
    88  		It("returns all tables if no filtering is used", func() {
    89  			opts.IncludedRelations = []string{"s1.table1", "s1.table2", "s2.table1", "s2.table2"}
    90  
    91  			resultRelations := restore.GenerateRestoreRelationList(*opts)
    92  
    93  			expectedRelations := []string{"s1.table1", "s1.table2", "s2.table1", "s2.table2"}
    94  			Expect(resultRelations).To(ConsistOf(expectedRelations))
    95  		})
    96  		It("filters on include relations", func() {
    97  			opts.IncludedRelations = []string{"s1.table1", "s1.table2"}
    98  
    99  			resultRelations := restore.GenerateRestoreRelationList(*opts)
   100  
   101  			expectedRelations := []string{"s1.table1", "s1.table2"}
   102  			Expect(resultRelations).To(ConsistOf(expectedRelations))
   103  		})
   104  		It("filters on exclude relations", func() {
   105  			opts.ExcludedRelations = []string{"s1.table2", "s2.table1"}
   106  
   107  			resultRelations := restore.GenerateRestoreRelationList(*opts)
   108  
   109  			expectedRelations := []string{"s1.table1", "s2.table2"}
   110  			Expect(resultRelations).To(ConsistOf(expectedRelations))
   111  		})
   112  		It("filters on include schema", func() {
   113  			opts.IncludedSchemas = []string{"s1"}
   114  
   115  			resultRelations := restore.GenerateRestoreRelationList(*opts)
   116  
   117  			expectedRelations := []string{"s1.table1", "s1.table2"}
   118  			Expect(resultRelations).To(ConsistOf(expectedRelations))
   119  		})
   120  		It("filters on exclude schema", func() {
   121  			opts.ExcludedSchemas = []string{"s2"}
   122  
   123  			resultRelations := restore.GenerateRestoreRelationList(*opts)
   124  
   125  			expectedRelations := []string{"s1.table1", "s1.table2"}
   126  			Expect(resultRelations).To(ConsistOf(expectedRelations))
   127  		})
   128  		It("filters on include schema with exclude relation", func() {
   129  			opts.IncludedSchemas = []string{"s1"}
   130  			opts.ExcludedRelations = []string{"s1.table1"}
   131  
   132  			resultRelations := restore.GenerateRestoreRelationList(*opts)
   133  
   134  			expectedRelations := []string{"s1.table2"}
   135  			Expect(resultRelations).To(ConsistOf(expectedRelations))
   136  		})
   137  	})
   138  	Describe("ValidateRelationsInRestoreDatabase", func() {
   139  		BeforeEach(func() {
   140  			restore.SetBackupConfig(&history.BackupConfig{DataOnly: false})
   141  			_ = cmdFlags.Set(options.DATA_ONLY, "false")
   142  		})
   143  		Context("data-only restore", func() {
   144  			BeforeEach(func() {
   145  				_ = cmdFlags.Set(options.DATA_ONLY, "true")
   146  			})
   147  			It("panics if all tables missing from database", func() {
   148  				noTableRows := sqlmock.NewRows([]string{"string"})
   149  				mock.ExpectQuery("SELECT (.*)").WillReturnRows(noTableRows)
   150  				filterList = []string{"public.table2"}
   151  				defer testhelper.ShouldPanicWithMessage("Relation public.table2 must exist for data-only restore")
   152  				restore.ValidateRelationsInRestoreDatabase(connectionPool, filterList)
   153  			})
   154  			It("panics if some tables missing from database", func() {
   155  				singleTableRow := sqlmock.NewRows([]string{"string"}).
   156  					AddRow("public.table1")
   157  				mock.ExpectQuery("SELECT (.*)").WillReturnRows(singleTableRow)
   158  				filterList = []string{"public.table1", "public.table2"}
   159  				defer testhelper.ShouldPanicWithMessage("Relation public.table2 must exist for data-only restore")
   160  				restore.ValidateRelationsInRestoreDatabase(connectionPool, filterList)
   161  			})
   162  			It("passes if all tables are present in database", func() {
   163  				twoTableRows := sqlmock.NewRows([]string{"string"}).
   164  					AddRow("public.table1").AddRow("public.table2")
   165  				mock.ExpectQuery("SELECT (.*)").WillReturnRows(twoTableRows)
   166  				filterList = []string{"public.table1", "public.table2"}
   167  				restore.ValidateRelationsInRestoreDatabase(connectionPool, filterList)
   168  			})
   169  		})
   170  		Context("restore includes metadata", func() {
   171  			It("passes if table is not present in database", func() {
   172  				noTableRows := sqlmock.NewRows([]string{"string"})
   173  				mock.ExpectQuery("SELECT (.*)").WillReturnRows(noTableRows)
   174  				filterList = []string{"public.table2"}
   175  				restore.ValidateRelationsInRestoreDatabase(connectionPool, filterList)
   176  			})
   177  			It("panics if single table is present in database", func() {
   178  				singleTableRow := sqlmock.NewRows([]string{"string"}).
   179  					AddRow("public.table1")
   180  				mock.ExpectQuery("SELECT (.*)").WillReturnRows(singleTableRow)
   181  				filterList = []string{"public.table1", "public.table2"}
   182  				defer testhelper.ShouldPanicWithMessage("Relation public.table1 already exists")
   183  				restore.ValidateRelationsInRestoreDatabase(connectionPool, filterList)
   184  			})
   185  			It("panics if multiple tables are present in database", func() {
   186  				twoTableRows := sqlmock.NewRows([]string{"string"}).
   187  					AddRow("public.table1").AddRow("public.view1")
   188  				mock.ExpectQuery("SELECT (.*)").WillReturnRows(twoTableRows)
   189  				filterList = []string{"public.table1", "public.view1"}
   190  				defer testhelper.ShouldPanicWithMessage("Relation public.table1 already exists")
   191  				restore.ValidateRelationsInRestoreDatabase(connectionPool, filterList)
   192  			})
   193  		})
   194  	})
   195  	Describe("ValidateRelationsInBackupSet", func() {
   196  		var tocfile *toc.TOC
   197  		var backupfile *utils.FileWithByteCount
   198  		BeforeEach(func() {
   199  			tocfile, backupfile = testutils.InitializeTestTOC(buffer, "predata")
   200  			tocfile.AddMetadataEntry("predata", toc.MetadataEntry{Schema: "schema1", Name: "table1", ObjectType: "TABLE"}, 0, backupfile.ByteCount)
   201  			tocfile.AddCoordinatorDataEntry("schema1", "table1", 1, "(i)", 0, "", "")
   202  
   203  			tocfile.AddMetadataEntry("predata", toc.MetadataEntry{Schema: "schema2", Name: "table2", ObjectType: "TABLE"}, 0, backupfile.ByteCount)
   204  			tocfile.AddCoordinatorDataEntry("schema2", "table2", 2, "(j)", 0, "", "")
   205  
   206  			tocfile.AddMetadataEntry("predata", toc.MetadataEntry{Schema: "schema1", Name: "somesequence", ObjectType: "SEQUENCE"}, 0, backupfile.ByteCount)
   207  			tocfile.AddMetadataEntry("predata", toc.MetadataEntry{Schema: "schema1", Name: "someview", ObjectType: "VIEW"}, 0, backupfile.ByteCount)
   208  			tocfile.AddMetadataEntry("predata", toc.MetadataEntry{Schema: "schema1", Name: "somefunction", ObjectType: "FUNCTION"}, 0, backupfile.ByteCount)
   209  
   210  			restore.SetTOC(tocfile)
   211  		})
   212  		It("passes when table exists in normal backup", func() {
   213  			restore.SetBackupConfig(&history.BackupConfig{})
   214  			filterList = []string{"schema1.table1"}
   215  			restore.ValidateIncludeRelationsInBackupSet(filterList)
   216  		})
   217  		It("panics when table does not exist in normal backup", func() {
   218  			restore.SetBackupConfig(&history.BackupConfig{})
   219  			filterList = []string{"schema1.table3"}
   220  			defer testhelper.ShouldPanicWithMessage("Could not find the following relation(s) in the backup set: schema1.table3")
   221  			restore.ValidateIncludeRelationsInBackupSet(filterList)
   222  		})
   223  		It("passes when sequence exists in normal backup", func() {
   224  			restore.SetBackupConfig(&history.BackupConfig{})
   225  			filterList = []string{"schema1.somesequence"}
   226  			restore.ValidateIncludeRelationsInBackupSet(filterList)
   227  		})
   228  		It("generates a warning if the exclude-schema is not in the backup set and noFatal is true", func() {
   229  			_, _, logfile = testhelper.SetupTestLogger()
   230  			restore.SetBackupConfig(&history.BackupConfig{})
   231  			filterList = []string{"schema1.table3"}
   232  			restore.ValidateExcludeRelationsInBackupSet(filterList)
   233  			testhelper.ExpectRegexp(logfile, "[WARNING]:-Could not find the following excluded relation(s) in the backup set: schema1.table3")
   234  		})
   235  		It("passes when view exists in normal backup", func() {
   236  			restore.SetBackupConfig(&history.BackupConfig{})
   237  			filterList = []string{"schema1.someview"}
   238  			restore.ValidateIncludeRelationsInBackupSet(filterList)
   239  		})
   240  		It("passes when table exists in data-only backup", func() {
   241  			restore.SetBackupConfig(&history.BackupConfig{DataOnly: true})
   242  			filterList = []string{"schema1.table1"}
   243  			restore.ValidateIncludeRelationsInBackupSet(filterList)
   244  		})
   245  		It("panics when relation does not exist in backup but function with same name does", func() {
   246  			restore.SetBackupConfig(&history.BackupConfig{})
   247  			filterList = []string{"schema1.somefunction"}
   248  			defer testhelper.ShouldPanicWithMessage("Could not find the following relation(s) in the backup set: schema1.somefunction")
   249  			restore.ValidateIncludeRelationsInBackupSet(filterList)
   250  		})
   251  		It("table does not exist in data-only backup", func() {
   252  			restore.SetBackupConfig(&history.BackupConfig{DataOnly: true})
   253  			filterList = []string{"schema1.table3"}
   254  			defer testhelper.ShouldPanicWithMessage("Could not find the following relation(s) in the backup set: schema1.table3")
   255  			restore.ValidateIncludeRelationsInBackupSet(filterList)
   256  		})
   257  		It("passes when table exists in most recent restore plan entry", func() {
   258  			restore.SetBackupConfig(&history.BackupConfig{RestorePlan: []history.RestorePlanEntry{{TableFQNs: []string{"schema1.table1_part_1"}}}})
   259  			filterList = []string{"schema1.table1_part_1"}
   260  			restore.ValidateIncludeRelationsInBackupSet(filterList)
   261  		})
   262  		It("passes when table exists in previous restore plan entry", func() {
   263  			restore.SetBackupConfig(&history.BackupConfig{RestorePlan: []history.RestorePlanEntry{{TableFQNs: []string{"schema1.random_table"}}, {TableFQNs: []string{"schema1.table1_part_1"}}}})
   264  			filterList = []string{"schema1.table1_part_1"}
   265  			restore.ValidateIncludeRelationsInBackupSet(filterList)
   266  		})
   267  	})
   268  	Describe("ValidateDatabaseExistence", func() {
   269  		It("panics if createdb passed when db exists", func() {
   270  			dbExists := sqlmock.NewRows([]string{"string"}).
   271  				AddRow("true")
   272  			mock.ExpectQuery("SELECT (.*)").WillReturnRows(dbExists)
   273  			defer testhelper.ShouldPanicWithMessage(`Database "testdb" already exists.`)
   274  			restore.ValidateDatabaseExistence("testdb", true, false)
   275  		})
   276  		It("passes if db exists and --create-db not passed", func() {
   277  			dbExists := sqlmock.NewRows([]string{"string"}).
   278  				AddRow("true")
   279  			mock.ExpectQuery("SELECT (.*)").WillReturnRows(dbExists)
   280  			restore.ValidateDatabaseExistence("testdb", false, false)
   281  		})
   282  		It("panics and tells user to manually create db when db does not exist and filtered", func() {
   283  			dbExists := sqlmock.NewRows([]string{"string"}).
   284  				AddRow("false")
   285  			mock.ExpectQuery("SELECT (.*)").WillReturnRows(dbExists)
   286  			defer testhelper.ShouldPanicWithMessage(`Database "testdb" must be created manually`)
   287  			restore.ValidateDatabaseExistence("testdb", true, true)
   288  		})
   289  		It("panics and tells user to pass --create-db when db does not exist, not filtered, and no --create-db", func() {
   290  			dbExists := sqlmock.NewRows([]string{"string"}).
   291  				AddRow("false")
   292  			mock.ExpectQuery("SELECT (.*)").WillReturnRows(dbExists)
   293  			defer testhelper.ShouldPanicWithMessage(`Database "testdb" does not exist. Use the --create-db flag`)
   294  			restore.ValidateDatabaseExistence("testdb", false, false)
   295  		})
   296  	})
   297  	Describe("Validate various flag combinations that are required or exclusive", func() {
   298  		DescribeTable("Validate various flag combinations that are required or exclusive",
   299  			func(argString string, valid bool) {
   300  				testCmd := &cobra.Command{
   301  					Use:  "flag validation",
   302  					Args: cobra.NoArgs,
   303  					Run: func(cmd *cobra.Command, args []string) {
   304  						restore.ValidateFlagCombinations(cmd.Flags())
   305  					}}
   306  				testCmd.SetArgs(strings.Split(argString, " "))
   307  				restore.SetCmdFlags(testCmd.Flags())
   308  
   309  				if !valid {
   310  					defer testhelper.ShouldPanicWithMessage("CRITICAL")
   311  				}
   312  
   313  				err := testCmd.Execute()
   314  				if err != nil && valid {
   315  					Fail("Valid flag combination failed validation check")
   316  				}
   317  			},
   318  			Entry("--backup-dir combo", "--backup-dir /tmp --plugin-config /tmp/config", false),
   319  
   320  			/*
   321  			 * Below are all the different filter combinations
   322  			 */
   323  			// --exclude-schema combinations with other filters
   324  			Entry("--exclude-schema combos", "--exclude-schema schema1 --include-table schema.table2", false),
   325  			Entry("--exclude-schema combos", "--exclude-schema schema1 --include-table-file /tmp/file2", false),
   326  			Entry("--exclude-schema combos", "--exclude-schema schema1 --include-schema schema2", false),
   327  			Entry("--exclude-schema combos", "--exclude-schema schema1 --include-schema-file /tmp/file2", true), // TODO: Verify this.
   328  			Entry("--exclude-schema combos", "--exclude-schema schema1 --exclude-table schema.table2", false),
   329  			Entry("--exclude-schema combos", "--exclude-schema schema1 --exclude-table-file /tmp/file2", false),
   330  			Entry("--exclude-schema combos", "--exclude-schema schema1 --exclude-schema schema2", true),
   331  			Entry("--exclude-schema combos", "--exclude-schema schema1 --exclude-schema-file /tmp/file2", true), // TODO: Verify this.
   332  
   333  			// --exclude-schema-file combinations with other filters
   334  			Entry("--exclude-schema-file combos", "--exclude-schema-file /tmp/file --include-table schema.table2", true),    // TODO: Verify this.
   335  			Entry("--exclude-schema-file combos", "--exclude-schema-file /tmp/file --include-table-file /tmp/file2", true),  // TODO: Verify this.
   336  			Entry("--exclude-schema-file combos", "--exclude-schema-file /tmp/file --include-schema schema2", true),         // TODO: Verify this.
   337  			Entry("--exclude-schema-file combos", "--exclude-schema-file /tmp/file --include-schema-file /tmp/file2", true), // TODO: Verify this.
   338  			Entry("--exclude-schema-file combos", "--exclude-schema-file /tmp/file --exclude-table schema.table2", true),    // TODO: Verify this.
   339  			Entry("--exclude-schema-file combos", "--exclude-schema-file /tmp/file --exclude-table-file /tmp/file2", true),  // TODO: Verify this.
   340  
   341  			// --exclude-table combinations with other filters
   342  			Entry("--exclude-table combos", "--exclude-table schema.table --include-table schema.table2", false),
   343  			Entry("--exclude-table combos", "--exclude-table schema.table --include-table-file /tmp/file2", false),
   344  			Entry("--exclude-table combos", "--exclude-table schema.table --include-schema schema2", true),
   345  			Entry("--exclude-table combos", "--exclude-table schema.table --include-schema-file /tmp/file2", true),
   346  			Entry("--exclude-table combos", "--exclude-table schema.table --exclude-table schema.table2", true),
   347  			Entry("--exclude-table combos", "--exclude-table schema.table --exclude-table-file /tmp/file2", false),
   348  
   349  			// --exclude-table-file combinations with other filters
   350  			Entry("--exclude-table-file combos", "--exclude-table-file /tmp/file --include-table schema.table2", false),
   351  			Entry("--exclude-table-file combos", "--exclude-table-file /tmp/file --include-table-file /tmp/file2", false),
   352  			Entry("--exclude-table-file combos", "--exclude-table-file /tmp/file --include-schema schema2", true),
   353  			Entry("--exclude-table-file combos", "--exclude-table-file /tmp/file --include-schema-file /tmp/file2", true),
   354  
   355  			// --include-schema combinations with other filters
   356  			Entry("--include-schema combos", "--include-schema schema1 --include-table schema.table2", false),
   357  			Entry("--include-schema combos", "--include-schema schema1 --include-table-file /tmp/file2", false),
   358  			Entry("--include-schema combos", "--include-schema schema1 --include-schema schema2", true),
   359  			Entry("--include-schema combos", "--include-schema schema1 --include-schema-file /tmp/file2", true), // TODO: Verify this.
   360  
   361  			// --include-schema-file combinations with other filters
   362  			Entry("--include-schema-file combos", "--include-schema-file /tmp/file --include-table schema.table2", true),   // TODO: Verify this.
   363  			Entry("--include-schema-file combos", "--include-schema-file /tmp/file --include-table-file /tmp/file2", true), // TODO: Verify this.
   364  
   365  			// --include-table combinations with other filters
   366  			Entry("--include-table combos", "--include-table schema.table --include-table schema.table2", true),
   367  			Entry("--include-table combos", "--include-table schema.table --include-table-file /tmp/file2", false),
   368  
   369  			/*
   370  			 * Below are various different incremental combinations
   371  			 */
   372  			Entry("incremental combos", "--incremental", false),
   373  			Entry("incremental combos", "--incremental --data-only", true),
   374  
   375  			/*
   376  			 * Below are various different truncate combinations
   377  			 */
   378  			Entry("truncate combos", "--truncate-table", false),
   379  			Entry("truncate combos", "--truncate-table --include-table schema.table2", true),
   380  			Entry("truncate combos", "--truncate-table --include-table-file /tmp/file2", true),
   381  			Entry("truncate combos", "--truncate-table --include-table schema.table2 --redirect-db foodb", true),
   382  			Entry("truncate combos", "--truncate-table --include-table schema.table2 --redirect-schema schema2", false),
   383  
   384  			/*
   385  			 * Below are various different redirect-schema combinations
   386  			 */
   387  			Entry("--redirect-schema combos", "--redirect-schema schema1", false),
   388  			Entry("--redirect-schema combos", "--redirect-schema schema1 --include-table schema.table2", true),
   389  			Entry("--redirect-schema combos", "--redirect-schema schema1 --include-table-file /tmp/file2", true),
   390  			Entry("--redirect-schema combos", "--redirect-schema schema1 --include-schema schema2", true),
   391  			Entry("--redirect-schema combos", "--redirect-schema schema1 --include-schema-file /tmp/file2", true),
   392  			Entry("--redirect-schema combos", "--redirect-schema schema1 --exclude-table schema.table2", false),
   393  			Entry("--redirect-schema combos", "--redirect-schema schema1 --exclude-table-file /tmp/file2", false),
   394  			Entry("--redirect-schema combos", "--redirect-schema schema1 --exclude-schema schema2", false),
   395  			Entry("--redirect-schema combos", "--redirect-schema schema1 --exclude-schema-file /tmp/file2", false),
   396  			Entry("--redirect-schema combos", "--redirect-schema schema1 --include-table schema.table2 --metadata-only", true),
   397  			Entry("--redirect-schema combos", "--redirect-schema schema1 --include-table schema.table2 --data-only", true),
   398  		)
   399  	})
   400  	Describe("ValidateBackupFlagCombinations", func() {
   401  		It("restore with copy-queue-size should fatal if backup was not taken with single-data-file", func() {
   402  			restore.SetBackupConfig(&history.BackupConfig{SingleDataFile: false})
   403  			testCmd := &cobra.Command{
   404  				Use:  "flag validation",
   405  				Args: cobra.NoArgs,
   406  				Run: func(cmd *cobra.Command, args []string) {
   407  					restore.ValidateBackupFlagCombinations()
   408  				}}
   409  			testCmd.SetArgs([]string{"--copy-queue-size", "4"})
   410  			restore.SetCmdFlags(testCmd.Flags())
   411  
   412  			defer testhelper.ShouldPanicWithMessage("CRITICAL")
   413  			err := testCmd.Execute()
   414  			if err == nil {
   415  				Fail("invalid flag combination passed validation check")
   416  			}
   417  		})
   418  	})
   419  })