github.com/tuhaihe/gpbackup@v1.0.3/end_to_end/incremental_test.go (about)

     1  package end_to_end_test
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"strconv"
     8  
     9  	"github.com/tuhaihe/gp-common-go-libs/dbconn"
    10  	"github.com/tuhaihe/gp-common-go-libs/testhelper"
    11  
    12  	. "github.com/onsi/ginkgo/v2"
    13  	. "github.com/onsi/gomega"
    14  )
    15  
    16  var _ = Describe("End to End incremental tests", func() {
    17  	BeforeEach(func() {
    18  		end_to_end_setup()
    19  	})
    20  	AfterEach(func() {
    21  		end_to_end_teardown()
    22  	})
    23  
    24  	Describe("Incremental backup", func() {
    25  		BeforeEach(func() {
    26  			skipIfOldBackupVersionBefore("1.7.0")
    27  		})
    28  		It("restores from an incremental backup specified with a timestamp", func() {
    29  			fullBackupTimestamp := gpbackup(gpbackupPath, backupHelperPath,
    30  				"--leaf-partition-data")
    31  
    32  			testhelper.AssertQueryRuns(backupConn,
    33  				"INSERT into schema2.ao1 values(1001)")
    34  			defer testhelper.AssertQueryRuns(backupConn,
    35  				"DELETE from schema2.ao1 where i=1001")
    36  			incremental1Timestamp := gpbackup(gpbackupPath, backupHelperPath,
    37  				"--incremental",
    38  				"--leaf-partition-data",
    39  				"--from-timestamp", fullBackupTimestamp)
    40  
    41  			testhelper.AssertQueryRuns(backupConn,
    42  				"INSERT into schema2.ao1 values(1002)")
    43  			defer testhelper.AssertQueryRuns(backupConn,
    44  				"DELETE from schema2.ao1 where i=1002")
    45  			incremental2Timestamp := gpbackup(gpbackupPath, backupHelperPath,
    46  				"--incremental",
    47  				"--leaf-partition-data",
    48  				"--from-timestamp", incremental1Timestamp)
    49  
    50  			gprestore(gprestorePath, restoreHelperPath, incremental2Timestamp,
    51  				"--redirect-db", "restoredb")
    52  
    53  			assertRelationsCreated(restoreConn, TOTAL_RELATIONS)
    54  			assertDataRestored(restoreConn, publicSchemaTupleCounts)
    55  			schema2TupleCounts["schema2.ao1"] = 1002
    56  			assertDataRestored(restoreConn, schema2TupleCounts)
    57  		})
    58  		It("restores from an incremental backup with AO Table consisting of multiple segment files", func() {
    59  			// Versions before 1.13.0 incorrectly handle AO table inserts involving multiple seg files
    60  			skipIfOldBackupVersionBefore("1.13.0")
    61  
    62  			testhelper.AssertQueryRuns(backupConn,
    63  				"CREATE TABLE foobar WITH (appendonly=true) AS SELECT i FROM generate_series(1,5) i")
    64  			defer testhelper.AssertQueryRuns(backupConn,
    65  				"DROP TABLE foobar")
    66  			testhelper.AssertQueryRuns(backupConn, "VACUUM foobar")
    67  			entriesInTable := dbconn.MustSelectString(backupConn,
    68  				"SELECT count(*) FROM foobar")
    69  			Expect(entriesInTable).To(Equal(strconv.Itoa(5)))
    70  
    71  			fullBackupTimestamp := gpbackup(gpbackupPath, backupHelperPath,
    72  				"--leaf-partition-data")
    73  
    74  			testhelper.AssertQueryRuns(backupConn,
    75  				"INSERT INTO foobar VALUES (1)")
    76  
    77  			// Ensure two distinct aoseg entries contain 'foobar' data
    78  			var numRows string
    79  			// For GPDB 7+, the gp_toolkit function returns the aoseg entries from the segments
    80  			numRows = dbconn.MustSelectString(backupConn,
    81  				"SELECT count(distinct(segno)) FROM gp_toolkit.__gp_aoseg('foobar'::regclass)")
    82  			Expect(numRows).To(Equal(strconv.Itoa(2)))
    83  
    84  			entriesInTable = dbconn.MustSelectString(backupConn,
    85  				"SELECT count(*) FROM foobar")
    86  			Expect(entriesInTable).To(Equal(strconv.Itoa(6)))
    87  
    88  			incremental1Timestamp := gpbackup(gpbackupPath, backupHelperPath,
    89  				"--incremental",
    90  				"--leaf-partition-data",
    91  				"--from-timestamp", fullBackupTimestamp)
    92  
    93  			gprestore(gprestorePath, restoreHelperPath, incremental1Timestamp,
    94  				"--redirect-db", "restoredb")
    95  
    96  			// The insertion should have been recorded in the incremental backup
    97  			entriesInTable = dbconn.MustSelectString(restoreConn,
    98  				"SELECT count(*) FROM foobar")
    99  			Expect(entriesInTable).To(Equal(strconv.Itoa(6)))
   100  		})
   101  		It("can restore from an old backup with an incremental taken from new binaries with --include-table", func() {
   102  			if !useOldBackupVersion {
   103  				Skip("This test is only needed for old backup versions")
   104  			}
   105  			_ = gpbackup(gpbackupPath, backupHelperPath,
   106  				"--leaf-partition-data",
   107  				"--include-table=public.sales")
   108  			testhelper.AssertQueryRuns(backupConn,
   109  				"INSERT into sales values(1, '2017-01-01', 99.99)")
   110  			defer testhelper.AssertQueryRuns(backupConn,
   111  				"DELETE from sales where amt=99.99")
   112  			_ = gpbackup(gpbackupPath, backupHelperPath,
   113  				"--incremental",
   114  				"--leaf-partition-data",
   115  				"--include-table=public.sales")
   116  
   117  			gpbackupPathOld, backupHelperPathOld := gpbackupPath, backupHelperPath
   118  			gpbackupPath, backupHelperPath, _ = buildAndInstallBinaries()
   119  
   120  			testhelper.AssertQueryRuns(backupConn,
   121  				"INSERT into sales values(2, '2017-02-01', 88.88)")
   122  			defer testhelper.AssertQueryRuns(backupConn,
   123  				"DELETE from sales where amt=88.88")
   124  			incremental2Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   125  				"--incremental",
   126  				"--leaf-partition-data",
   127  				"--include-table=public.sales")
   128  			gpbackupPath, backupHelperPath = gpbackupPathOld, backupHelperPathOld
   129  
   130  			gprestore(gprestorePath, restoreHelperPath, incremental2Timestamp,
   131  				"--redirect-db", "restoredb")
   132  
   133  			localTupleCounts := map[string]int{
   134  				"public.sales": 15,
   135  			}
   136  			assertRelationsCreated(restoreConn, 13)
   137  			assertDataRestored(restoreConn, localTupleCounts)
   138  		})
   139  		Context("Without a timestamp", func() {
   140  			It("restores from a incremental backup specified with a backup directory", func() {
   141  				_ = gpbackup(gpbackupPath, backupHelperPath,
   142  					"--leaf-partition-data",
   143  					"--backup-dir", backupDir)
   144  
   145  				testhelper.AssertQueryRuns(backupConn,
   146  					"INSERT into schema2.ao1 values(1001)")
   147  				defer testhelper.AssertQueryRuns(backupConn,
   148  					"DELETE from schema2.ao1 where i=1001")
   149  				_ = gpbackup(gpbackupPath, backupHelperPath,
   150  					"--incremental",
   151  					"--leaf-partition-data",
   152  					"--backup-dir", backupDir)
   153  
   154  				testhelper.AssertQueryRuns(backupConn,
   155  					"INSERT into schema2.ao1 values(1002)")
   156  				defer testhelper.AssertQueryRuns(backupConn,
   157  					"DELETE from schema2.ao1 where i=1002")
   158  				incremental2Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   159  					"--incremental",
   160  					"--leaf-partition-data",
   161  					"--backup-dir", backupDir)
   162  
   163  				gprestore(gprestorePath, restoreHelperPath, incremental2Timestamp,
   164  					"--redirect-db", "restoredb",
   165  					"--backup-dir", backupDir)
   166  
   167  				assertRelationsCreated(restoreConn, TOTAL_RELATIONS)
   168  				assertDataRestored(restoreConn, publicSchemaTupleCounts)
   169  				schema2TupleCounts["schema2.ao1"] = 1002
   170  				assertDataRestored(restoreConn, schema2TupleCounts)
   171  
   172  				_ = os.Remove(backupDir)
   173  			})
   174  			It("restores from --include filtered incremental backup with partition tables", func() {
   175  				_ = gpbackup(gpbackupPath, backupHelperPath,
   176  					"--leaf-partition-data",
   177  					"--include-table", "public.sales")
   178  
   179  				testhelper.AssertQueryRuns(backupConn,
   180  					"INSERT into sales VALUES(19, '2017-02-15'::date, 100)")
   181  				defer testhelper.AssertQueryRuns(backupConn,
   182  					"DELETE from sales where id=19")
   183  				_ = gpbackup(gpbackupPath, backupHelperPath,
   184  					"--incremental",
   185  					"--leaf-partition-data",
   186  					"--include-table", "public.sales")
   187  
   188  				testhelper.AssertQueryRuns(backupConn,
   189  					"INSERT into sales VALUES(20, '2017-03-15'::date, 100)")
   190  				defer testhelper.AssertQueryRuns(backupConn,
   191  					"DELETE from sales where id=20")
   192  				incremental2Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   193  					"--incremental",
   194  					"--leaf-partition-data",
   195  					"--include-table", "public.sales")
   196  				gprestore(gprestorePath, restoreHelperPath, incremental2Timestamp,
   197  					"--redirect-db", "restoredb")
   198  
   199  				assertDataRestored(restoreConn, map[string]int{
   200  					"public.sales":             15,
   201  					"public.sales_1_prt_feb17": 2,
   202  					"public.sales_1_prt_mar17": 2,
   203  				})
   204  			})
   205  			It("restores from --exclude filtered incremental backup with partition tables", func() {
   206  				skipIfOldBackupVersionBefore("1.18.0")
   207  				publicSchemaTupleCountsWithExclude := map[string]int{
   208  					"public.foo":   40000, // holds is excluded and doesn't exist
   209  					"public.sales": 12,    // 13 original - 1 for excluded partition
   210  				}
   211  				schema2TupleCountsWithExclude := map[string]int{
   212  					"schema2.returns": 6,
   213  					"schema2.foo2":    0,
   214  					"schema2.foo3":    100,
   215  					"schema2.ao2":     1001, // +1 for new row, ao1 is excluded and doesn't exist
   216  				}
   217  
   218  				_ = gpbackup(gpbackupPath, backupHelperPath,
   219  					"--leaf-partition-data",
   220  					"--exclude-table", "public.holds",
   221  					"--exclude-table", "public.sales_1_prt_mar17",
   222  					"--exclude-table", "schema2.ao1")
   223  
   224  				testhelper.AssertQueryRuns(backupConn,
   225  					"INSERT into sales VALUES(20, '2017-03-15'::date, 100)")
   226  				defer testhelper.AssertQueryRuns(backupConn,
   227  					"DELETE from sales where id=20")
   228  				testhelper.AssertQueryRuns(backupConn,
   229  					"INSERT into schema2.ao1 values(1001)")
   230  				defer testhelper.AssertQueryRuns(backupConn,
   231  					"DELETE from schema2.ao1 where i=1001")
   232  				testhelper.AssertQueryRuns(backupConn,
   233  					"INSERT into schema2.ao2 values(1002)")
   234  				defer testhelper.AssertQueryRuns(backupConn,
   235  					"DELETE from schema2.ao2 where i=1002")
   236  
   237  				incremental1Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   238  					"--incremental",
   239  					"--leaf-partition-data",
   240  					"--exclude-table", "public.holds",
   241  					"--exclude-table", "public.sales_1_prt_mar17",
   242  					"--exclude-table", "schema2.ao1")
   243  
   244  				gprestore(gprestorePath, restoreHelperPath, incremental1Timestamp,
   245  					"--redirect-db", "restoredb")
   246  
   247  				if false {
   248  					// -2 for public.holds and schema2.ao1, excluded partition will be included anyway but it's data - will not
   249  					assertRelationsCreated(restoreConn, TOTAL_RELATIONS-2)
   250  				} else {
   251  					// In GPDB 7+, the sales_1_prt_mar17 partition will not be created.
   252  					// TODO: Should the leaf partition actually be created when it has
   253  					// been excluded with the new GPDB partitioning logic?
   254  					assertRelationsCreated(restoreConn, TOTAL_RELATIONS-3)
   255  				}
   256  				assertDataRestored(restoreConn, publicSchemaTupleCountsWithExclude)
   257  				assertDataRestored(restoreConn, schema2TupleCountsWithExclude)
   258  			})
   259  			It("restores from full incremental backup with partition tables with restore table filtering", func() {
   260  				skipIfOldBackupVersionBefore("1.7.2")
   261  				testhelper.AssertQueryRuns(backupConn,
   262  					"INSERT into sales VALUES(19, '2017-02-15'::date, 100)")
   263  				defer testhelper.AssertQueryRuns(backupConn,
   264  					"DELETE from sales where id=19")
   265  				_ = gpbackup(gpbackupPath, backupHelperPath,
   266  					"--leaf-partition-data")
   267  
   268  				incremental1Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   269  					"--incremental", "--leaf-partition-data")
   270  				gprestore(gprestorePath, restoreHelperPath, incremental1Timestamp,
   271  					"--redirect-db", "restoredb",
   272  					"--include-table", "public.sales_1_prt_feb17")
   273  
   274  				assertDataRestored(restoreConn, map[string]int{
   275  					"public.sales":             2,
   276  					"public.sales_1_prt_feb17": 2,
   277  				})
   278  			})
   279  			Context("old binaries", func() {
   280  				It("can restore from a backup with an incremental taken from new binaries", func() {
   281  					if !useOldBackupVersion {
   282  						Skip("This test is only needed for old backup versions")
   283  					}
   284  					_ = gpbackup(gpbackupPath, backupHelperPath,
   285  						"--leaf-partition-data")
   286  
   287  					testhelper.AssertQueryRuns(backupConn,
   288  						"INSERT into schema2.ao1 values(1001)")
   289  					defer testhelper.AssertQueryRuns(backupConn,
   290  						"DELETE from schema2.ao1 where i=1001")
   291  					_ = gpbackup(gpbackupPath, backupHelperPath,
   292  						"--incremental",
   293  						"--leaf-partition-data")
   294  
   295  					gpbackupPathOld, backupHelperPathOld := gpbackupPath, backupHelperPath
   296  					gpbackupPath, backupHelperPath, _ = buildAndInstallBinaries()
   297  
   298  					testhelper.AssertQueryRuns(backupConn,
   299  						"INSERT into schema2.ao1 values(1002)")
   300  					defer testhelper.AssertQueryRuns(backupConn,
   301  						"DELETE from schema2.ao1 where i=1002")
   302  					incremental2Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   303  						"--incremental",
   304  						"--leaf-partition-data")
   305  					gpbackupPath, backupHelperPath = gpbackupPathOld, backupHelperPathOld
   306  					gprestore(gprestorePath, restoreHelperPath, incremental2Timestamp,
   307  						"--redirect-db", "restoredb")
   308  
   309  					assertRelationsCreated(restoreConn, TOTAL_RELATIONS)
   310  					assertDataRestored(restoreConn, publicSchemaTupleCounts)
   311  					schema2TupleCounts["schema2.ao1"] = 1002
   312  					assertDataRestored(restoreConn, schema2TupleCounts)
   313  				})
   314  			})
   315  		})
   316  		Context("With a plugin", func() {
   317  			BeforeEach(func() {
   318  				// FIXME: we are temporarily disabling these tests because we will be altering our backwards compatibility logic.
   319  				if useOldBackupVersion {
   320  					Skip("This test is only needed for the most recent backup versions")
   321  				}
   322  			//	pluginExecutablePath := fmt.Sprintf("%s/src/github.com/greenplum-db/gpbackup/plugins/example_plugin.bash", os.Getenv("GOPATH"))
   323  			//	copyPluginToAllHosts(backupConn, pluginExecutablePath)
   324  			})
   325  			It("Restores from an incremental backup based on a from-timestamp incremental", func() {
   326  				Skip("Cloudberry skip")
   327  				fullBackupTimestamp := gpbackup(gpbackupPath, backupHelperPath,
   328  					"--leaf-partition-data",
   329  					"--single-data-file",
   330  					"--plugin-config", pluginConfigPath)
   331  				forceMetadataFileDownloadFromPlugin(backupConn, fullBackupTimestamp)
   332  				testhelper.AssertQueryRuns(backupConn,
   333  					"INSERT into schema2.ao1 values(1001)")
   334  				defer testhelper.AssertQueryRuns(backupConn,
   335  					"DELETE from schema2.ao1 where i=1001")
   336  				incremental1Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   337  					"--incremental",
   338  					"--leaf-partition-data",
   339  					"--single-data-file",
   340  					"--from-timestamp", fullBackupTimestamp,
   341  					"--plugin-config", pluginConfigPath)
   342  				forceMetadataFileDownloadFromPlugin(backupConn, incremental1Timestamp)
   343  
   344  				testhelper.AssertQueryRuns(backupConn,
   345  					"INSERT into schema2.ao1 values(1002)")
   346  				defer testhelper.AssertQueryRuns(backupConn,
   347  					"DELETE from schema2.ao1 where i=1002")
   348  				incremental2Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   349  					"--incremental",
   350  					"--leaf-partition-data",
   351  					"--single-data-file",
   352  					"--plugin-config", pluginConfigPath)
   353  				forceMetadataFileDownloadFromPlugin(backupConn, incremental2Timestamp)
   354  
   355  				gprestore(gprestorePath, restoreHelperPath, incremental2Timestamp,
   356  					"--redirect-db", "restoredb",
   357  					"--plugin-config", pluginConfigPath)
   358  
   359  				assertRelationsCreated(restoreConn, TOTAL_RELATIONS)
   360  				assertDataRestored(restoreConn, publicSchemaTupleCounts)
   361  				schema2TupleCounts["schema2.ao1"] = 1002
   362  				assertDataRestored(restoreConn, schema2TupleCounts)
   363  				assertArtifactsCleaned(restoreConn, fullBackupTimestamp)
   364  				assertArtifactsCleaned(restoreConn, incremental1Timestamp)
   365  				assertArtifactsCleaned(restoreConn, incremental2Timestamp)
   366  			})
   367  			It("Restores from an incremental backup based on a from-timestamp incremental with --copy-queue-size", func() {
   368  				Skip("Cloudberry skip")
   369  				fullBackupTimestamp := gpbackup(gpbackupPath, backupHelperPath,
   370  					"--leaf-partition-data",
   371  					"--single-data-file",
   372  					"--copy-queue-size", "4",
   373  					"--plugin-config", pluginConfigPath)
   374  				forceMetadataFileDownloadFromPlugin(backupConn, fullBackupTimestamp)
   375  				testhelper.AssertQueryRuns(backupConn,
   376  					"INSERT into schema2.ao1 values(1001)")
   377  				defer testhelper.AssertQueryRuns(backupConn,
   378  					"DELETE from schema2.ao1 where i=1001")
   379  				incremental1Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   380  					"--incremental",
   381  					"--leaf-partition-data",
   382  					"--single-data-file",
   383  					"--copy-queue-size", "4",
   384  					"--from-timestamp", fullBackupTimestamp,
   385  					"--plugin-config", pluginConfigPath)
   386  				forceMetadataFileDownloadFromPlugin(backupConn, incremental1Timestamp)
   387  
   388  				testhelper.AssertQueryRuns(backupConn,
   389  					"INSERT into schema2.ao1 values(1002)")
   390  				defer testhelper.AssertQueryRuns(backupConn,
   391  					"DELETE from schema2.ao1 where i=1002")
   392  				incremental2Timestamp := gpbackup(gpbackupPath, backupHelperPath,
   393  					"--incremental",
   394  					"--leaf-partition-data",
   395  					"--single-data-file",
   396  					"--copy-queue-size", "4",
   397  					"--plugin-config", pluginConfigPath)
   398  				forceMetadataFileDownloadFromPlugin(backupConn, incremental2Timestamp)
   399  
   400  				gprestore(gprestorePath, restoreHelperPath, incremental2Timestamp,
   401  					"--redirect-db", "restoredb",
   402  					"--copy-queue-size", "4",
   403  					"--plugin-config", pluginConfigPath)
   404  
   405  				assertRelationsCreated(restoreConn, TOTAL_RELATIONS)
   406  				assertDataRestored(restoreConn, publicSchemaTupleCounts)
   407  				schema2TupleCounts["schema2.ao1"] = 1002
   408  				assertDataRestored(restoreConn, schema2TupleCounts)
   409  				assertArtifactsCleaned(restoreConn, fullBackupTimestamp)
   410  				assertArtifactsCleaned(restoreConn, incremental1Timestamp)
   411  				assertArtifactsCleaned(restoreConn, incremental2Timestamp)
   412  			})
   413  			It("Runs backup and restore if plugin location changed", func() {
   414  				Skip("Cloudberry skip src/github.com/tuhaihe/gpbackup/plugins/example_plugin.bash");
   415  				pluginExecutablePath := fmt.Sprintf("%s/src/github.com/tuhaihe/gpbackup/plugins/example_plugin.bash", os.Getenv("GOPATH"))
   416  				fullBackupTimestamp := gpbackup(gpbackupPath, backupHelperPath,
   417  					"--leaf-partition-data",
   418  					"--plugin-config", pluginConfigPath)
   419  
   420  				otherPluginExecutablePath := fmt.Sprintf("%s/other_plugin_location/example_plugin.bash", backupDir)
   421  				command := exec.Command("bash", "-c", fmt.Sprintf("mkdir %s/other_plugin_location && cp %s %s/other_plugin_location", backupDir, pluginExecutablePath, backupDir))
   422  				mustRunCommand(command)
   423  				newCongig := fmt.Sprintf(`EOF1
   424  executablepath: %s/other_plugin_location/example_plugin.bash
   425  options:
   426   password: unknown
   427  EOF1`, backupDir)
   428  				otherPluginConfig := fmt.Sprintf("%s/other_plugin_location/example_plugin_config.yml", backupDir)
   429  				command = exec.Command("bash", "-c", fmt.Sprintf("cat > %s << %s", otherPluginConfig, newCongig))
   430  				mustRunCommand(command)
   431  
   432  				copyPluginToAllHosts(backupConn, otherPluginExecutablePath)
   433  
   434  				incrementalBackupTimestamp := gpbackup(gpbackupPath, backupHelperPath,
   435  					"--leaf-partition-data",
   436  					"--incremental",
   437  					"--plugin-config", otherPluginConfig)
   438  
   439  				Expect(incrementalBackupTimestamp).NotTo(BeNil())
   440  
   441  				gprestore(gprestorePath, restoreHelperPath, incrementalBackupTimestamp,
   442  					"--redirect-db", "restoredb",
   443  					"--plugin-config", otherPluginConfig)
   444  
   445  				assertRelationsCreated(restoreConn, TOTAL_RELATIONS)
   446  				assertDataRestored(restoreConn, publicSchemaTupleCounts)
   447  				assertArtifactsCleaned(restoreConn, fullBackupTimestamp)
   448  				assertArtifactsCleaned(restoreConn, incrementalBackupTimestamp)
   449  			})
   450  		})
   451  	})
   452  	Describe("Incremental restore", func() {
   453  		var oldSchemaTupleCounts, newSchemaTupleCounts map[string]int
   454  		BeforeEach(func() {
   455  			skipIfOldBackupVersionBefore("1.16.0")
   456  		})
   457  		Context("Simple incremental restore", func() {
   458  			It("Existing tables should be excluded from metadata restore", func() {
   459  				// Create a heap, ao, co, and external table and create a backup
   460  				testhelper.AssertQueryRuns(backupConn,
   461  					"DROP SCHEMA IF EXISTS testschema CASCADE; CREATE SCHEMA testschema;")
   462  				testhelper.AssertQueryRuns(backupConn,
   463  					"CREATE TABLE testschema.heap_table (a int);")
   464  				testhelper.AssertQueryRuns(backupConn,
   465  					"CREATE TABLE testschema.ao_table (a int) WITH (appendonly=true);")
   466  				testhelper.AssertQueryRuns(backupConn,
   467  					"CREATE TABLE testschema.co_table (a int) WITH (appendonly=true, orientation=column);")
   468  				testhelper.AssertQueryRuns(backupConn,
   469  					"CREATE EXTERNAL WEB TABLE testschema.external_table (a text) EXECUTE E'echo hi' FORMAT 'csv';")
   470  				backupTimestamp := gpbackup(gpbackupPath, backupHelperPath, "--leaf-partition-data")
   471  
   472  				// Restore the backup to a different database
   473  				testhelper.AssertQueryRuns(restoreConn,
   474  					"DROP SCHEMA IF EXISTS testschema CASCADE;")
   475  				gprestore(gprestorePath, restoreHelperPath, backupTimestamp, "--redirect-db", "restoredb")
   476  
   477  				// Trigger an incremental backup
   478  				testhelper.AssertQueryRuns(backupConn,
   479  					"INSERT INTO testschema.ao_table VALUES (1);")
   480  				incrementalBackupTimestamp := gpbackup(gpbackupPath, backupHelperPath, "--leaf-partition-data", "--incremental")
   481  
   482  				// Restore the incremental backup. We should see gprestore
   483  				// not error out due to already existing tables.
   484  				gprestore(gprestorePath, restoreHelperPath, incrementalBackupTimestamp, "--redirect-db", "restoredb", "--incremental", "--data-only")
   485  
   486  				// Cleanup
   487  				testhelper.AssertQueryRuns(backupConn,
   488  					"DROP SCHEMA IF EXISTS testschema CASCADE;")
   489  				testhelper.AssertQueryRuns(restoreConn,
   490  					"DROP SCHEMA IF EXISTS testschema CASCADE;")
   491  			})
   492  			It("Does not try to restore postdata", func() {
   493  				testhelper.AssertQueryRuns(backupConn,
   494  					"CREATE TABLE zoo (a int) WITH (appendonly=true);")
   495  				testhelper.AssertQueryRuns(backupConn,
   496  					"CREATE  INDEX fooidx ON zoo USING btree(a);")
   497  				backupTimestamp := gpbackup(gpbackupPath, backupHelperPath, "--leaf-partition-data")
   498  				testhelper.AssertQueryRuns(backupConn,
   499  					"INSERT INTO zoo VALUES (1);")
   500  				incrementalBackupTimestamp := gpbackup(gpbackupPath, backupHelperPath, "--leaf-partition-data", "--incremental")
   501  				gprestore(gprestorePath, restoreHelperPath, backupTimestamp, "--redirect-db", "restoredb")
   502  				gprestore(gprestorePath, restoreHelperPath, incrementalBackupTimestamp, "--redirect-db", "restoredb", "--incremental", "--data-only")
   503  
   504  				// Cleanup
   505  				testhelper.AssertQueryRuns(backupConn,
   506  					"DROP TABLE IF EXISTS zoo;")
   507  				testhelper.AssertQueryRuns(restoreConn,
   508  					"DROP TABLE IF EXISTS zoo;")
   509  			})
   510  			It("Does not incremental restore without --data-only", func() {
   511  				args := []string{
   512  					"--timestamp", "23432432",
   513  					"--incremental",
   514  					"--redirect-db", "restoredb"}
   515  				cmd := exec.Command(gprestorePath, args...)
   516  				output, err := cmd.CombinedOutput()
   517  				Expect(err).To(HaveOccurred())
   518  				Expect(string(output)).To(ContainSubstring("Cannot use --incremental without --data-only"))
   519  			})
   520  			It("Does not incremental restore with --metadata-only", func() {
   521  				args := []string{
   522  					"--timestamp", "23432432",
   523  					"--incremental", "--metadata-only",
   524  					"--redirect-db", "restoredb"}
   525  				cmd := exec.Command(gprestorePath, args...)
   526  				output, err := cmd.CombinedOutput()
   527  				Expect(err).To(HaveOccurred())
   528  				Expect(string(output)).To(ContainSubstring(
   529  					"The following flags may not be specified together: truncate-table, metadata-only, incremental"))
   530  			})
   531  		})
   532  		Context("No DDL no partitioning", func() {
   533  			BeforeEach(func() {
   534  				testhelper.AssertQueryRuns(backupConn,
   535  					"DROP SCHEMA IF EXISTS new_schema CASCADE; DROP SCHEMA IF EXISTS old_schema CASCADE; CREATE SCHEMA old_schema;")
   536  				testhelper.AssertQueryRuns(backupConn,
   537  					"CREATE TABLE old_schema.old_table0 (mydata int) WITH (appendonly=true) DISTRIBUTED BY (mydata);")
   538  				testhelper.AssertQueryRuns(backupConn,
   539  					"CREATE TABLE old_schema.old_table1 (mydata int) WITH (appendonly=true) DISTRIBUTED BY (mydata);")
   540  				testhelper.AssertQueryRuns(backupConn,
   541  					"CREATE TABLE old_schema.old_table2 (mydata int) WITH (appendonly=true) DISTRIBUTED BY (mydata);")
   542  				testhelper.AssertQueryRuns(backupConn,
   543  					"INSERT INTO old_schema.old_table0 SELECT generate_series(1, 5);")
   544  				testhelper.AssertQueryRuns(backupConn,
   545  					"INSERT INTO old_schema.old_table1 SELECT generate_series(1, 10);")
   546  				testhelper.AssertQueryRuns(backupConn,
   547  					"INSERT INTO old_schema.old_table2 SELECT generate_series(1, 15);")
   548  
   549  				oldSchemaTupleCounts = map[string]int{
   550  					"old_schema.old_table0": 5,
   551  					"old_schema.old_table1": 10,
   552  					"old_schema.old_table2": 15,
   553  				}
   554  				newSchemaTupleCounts = map[string]int{}
   555  				baseTimestamp := gpbackup(gpbackupPath, backupHelperPath,
   556  					"--leaf-partition-data")
   557  
   558  				testhelper.AssertQueryRuns(restoreConn,
   559  					"DROP SCHEMA IF EXISTS new_schema CASCADE; DROP SCHEMA IF EXISTS old_schema CASCADE;")
   560  				gprestore(gprestorePath, restoreHelperPath, baseTimestamp,
   561  					"--redirect-db", "restoredb")
   562  			})
   563  			AfterEach(func() {
   564  				testhelper.AssertQueryRuns(backupConn,
   565  					"DROP SCHEMA IF EXISTS new_schema CASCADE; DROP SCHEMA IF EXISTS old_schema CASCADE;")
   566  				testhelper.AssertQueryRuns(restoreConn,
   567  					"DROP SCHEMA IF EXISTS new_schema CASCADE; DROP SCHEMA IF EXISTS old_schema CASCADE;")
   568  			})
   569  			Context("Include/Exclude schemas and tables", func() {
   570  				var timestamp string
   571  				BeforeEach(func() {
   572  					testhelper.AssertQueryRuns(backupConn,
   573  						"CREATE SCHEMA new_schema;")
   574  					testhelper.AssertQueryRuns(backupConn,
   575  						"CREATE TABLE new_schema.new_table1 (mydata int) WITH (appendonly=true) DISTRIBUTED BY (mydata);")
   576  					testhelper.AssertQueryRuns(backupConn,
   577  						"CREATE TABLE new_schema.new_table2 (mydata int) WITH (appendonly=true) DISTRIBUTED BY (mydata);")
   578  					testhelper.AssertQueryRuns(backupConn,
   579  						"INSERT INTO new_schema.new_table1 SELECT generate_series(1, 30);")
   580  					testhelper.AssertQueryRuns(backupConn,
   581  						"INSERT INTO new_schema.new_table2 SELECT generate_series(1, 35);")
   582  					testhelper.AssertQueryRuns(backupConn,
   583  						"INSERT INTO old_schema.old_table1 SELECT generate_series(11, 20);")
   584  					timestamp = gpbackup(gpbackupPath, backupHelperPath,
   585  						"--leaf-partition-data", "--incremental")
   586  				})
   587  				AfterEach(func() {
   588  					testhelper.AssertQueryRuns(backupConn,
   589  						"DROP SCHEMA IF EXISTS new_schema CASCADE;")
   590  					testhelper.AssertQueryRuns(restoreConn,
   591  						"DROP SCHEMA IF EXISTS new_schema CASCADE;")
   592  					testhelper.AssertQueryRuns(backupConn,
   593  						"DELETE FROM old_schema.old_table1 where mydata>10;")
   594  					oldSchemaTupleCounts = map[string]int{}
   595  					newSchemaTupleCounts = map[string]int{}
   596  					assertArtifactsCleaned(restoreConn, timestamp)
   597  				})
   598  				It("Restores only tables included by use if user input is provided", func() {
   599  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   600  						"--incremental", "--data-only",
   601  						"--include-table", "old_schema.old_table1",
   602  						"--redirect-db", "restoredb")
   603  					oldSchemaTupleCounts["old_schema.old_table1"] = 20
   604  					// new_schema should not be present
   605  					assertSchemasExist(restoreConn, 4)
   606  					assertRelationsExistForIncremental(restoreConn, 3)
   607  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   608  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   609  				})
   610  				It("Does not restore tables excluded by user if user input is provided", func() {
   611  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   612  						"--incremental", "--data-only",
   613  						"--exclude-table", "new_schema.new_table1",
   614  						"--exclude-table", "new_schema.new_table2",
   615  						"--redirect-db", "restoredb")
   616  					oldSchemaTupleCounts["old_schema.old_table1"] = 20
   617  					// new_schema should not be present
   618  					assertSchemasExist(restoreConn, 4)
   619  					assertRelationsExistForIncremental(restoreConn, 3)
   620  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   621  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   622  				})
   623  				It("Restores only schemas included by user if user input is provided", func() {
   624  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   625  						"--incremental", "--data-only",
   626  						"--include-schema", "old_schema",
   627  						"--redirect-db", "restoredb")
   628  					oldSchemaTupleCounts["old_schema.old_table1"] = 20
   629  					assertSchemasExist(restoreConn, 4)
   630  					assertRelationsExistForIncremental(restoreConn, 3)
   631  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   632  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   633  				})
   634  				It("Does not restore schemas excluded by user if user input is provided", func() {
   635  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   636  						"--incremental", "--data-only",
   637  						"--exclude-schema", "new_schema",
   638  						"--redirect-db", "restoredb")
   639  					oldSchemaTupleCounts["old_schema.old_table1"] = 20
   640  					assertSchemasExist(restoreConn, 4)
   641  					assertRelationsExistForIncremental(restoreConn, 3)
   642  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   643  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   644  				})
   645  			})
   646  			Context("New tables and schemas", func() {
   647  				var timestamp string
   648  				BeforeEach(func() {
   649  					testhelper.AssertQueryRuns(backupConn,
   650  						"CREATE SCHEMA new_schema;")
   651  					testhelper.AssertQueryRuns(backupConn,
   652  						"CREATE TABLE new_schema.new_table1 (mydata int) WITH (appendonly=true) DISTRIBUTED BY (mydata);")
   653  					testhelper.AssertQueryRuns(backupConn,
   654  						"INSERT INTO new_schema.new_table1 SELECT generate_series(1, 30);")
   655  					testhelper.AssertQueryRuns(backupConn,
   656  						"CREATE TABLE old_schema.new_table1 (mydata int) WITH (appendonly=true) DISTRIBUTED BY (mydata);")
   657  					testhelper.AssertQueryRuns(backupConn,
   658  						"INSERT INTO old_schema.new_table1 SELECT generate_series(1, 20);")
   659  					testhelper.AssertQueryRuns(backupConn,
   660  						"INSERT INTO new_schema.new_table1 SELECT generate_series(1, 25);")
   661  					testhelper.AssertQueryRuns(backupConn,
   662  						"INSERT INTO old_schema.old_table1 SELECT generate_series(11, 20);")
   663  					timestamp = gpbackup(gpbackupPath, backupHelperPath,
   664  						"--leaf-partition-data", "--incremental")
   665  				})
   666  				AfterEach(func() {
   667  					testhelper.AssertQueryRuns(backupConn,
   668  						"DROP TABLE IF EXISTS old_schema.new_table1 CASCADE;")
   669  					testhelper.AssertQueryRuns(backupConn,
   670  						"DROP TABLE IF EXISTS old_schema.new_table2 CASCADE;")
   671  					testhelper.AssertQueryRuns(restoreConn,
   672  						"DROP TABLE IF EXISTS old_schema.new_table1 CASCADE;")
   673  					testhelper.AssertQueryRuns(restoreConn,
   674  						"DROP TABLE IF EXISTS old_schema.new_table2 CASCADE;")
   675  					testhelper.AssertQueryRuns(backupConn,
   676  						"DELETE FROM old_schema.old_table1 where mydata>10;")
   677  					testhelper.AssertQueryRuns(backupConn,
   678  						"DROP SCHEMA IF EXISTS new_schema CASCADE;")
   679  					testhelper.AssertQueryRuns(restoreConn,
   680  						"DROP SCHEMA IF EXISTS new_schema CASCADE;")
   681  					oldSchemaTupleCounts = map[string]int{}
   682  					newSchemaTupleCounts = map[string]int{}
   683  					assertArtifactsCleaned(restoreConn, timestamp)
   684  				})
   685  				It("Does not restore old/new tables and exits gracefully", func() {
   686  					args := []string{
   687  						"--timestamp", timestamp,
   688  						"--incremental", "--data-only",
   689  						"--redirect-db", "restoredb"}
   690  					cmd := exec.Command(gprestorePath, args...)
   691  					output, err := cmd.CombinedOutput()
   692  					Expect(err).To(HaveOccurred())
   693  					Expect(string(output)).To(ContainSubstring(
   694  						"objects are missing from the target database: " +
   695  							"[new_schema new_schema.new_table1 old_schema.new_table1]"))
   696  					assertRelationsExistForIncremental(restoreConn, 3)
   697  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   698  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   699  				})
   700  				It("Only restores existing tables if --on-error-continue is specified", func() {
   701  					args := []string{
   702  						"--timestamp", timestamp,
   703  						"--incremental", "--data-only",
   704  						"--on-error-continue",
   705  						"--redirect-db", "restoredb"}
   706  					cmd := exec.Command(gprestorePath, args...)
   707  					_, err := cmd.CombinedOutput()
   708  					Expect(err).To(HaveOccurred())
   709  					oldSchemaTupleCounts["old_schema.old_table1"] = 20
   710  					assertRelationsExistForIncremental(restoreConn, 3)
   711  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   712  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   713  				})
   714  			})
   715  			Context("Existing tables in existing schemas were updated", func() {
   716  				var timestamp string
   717  				BeforeEach(func() {
   718  					testhelper.AssertQueryRuns(backupConn,
   719  						"INSERT INTO old_schema.old_table1 SELECT generate_series(11, 20);")
   720  					testhelper.AssertQueryRuns(backupConn,
   721  						"INSERT INTO old_schema.old_table2 SELECT generate_series(16, 30);")
   722  					timestamp = gpbackup(gpbackupPath, backupHelperPath,
   723  						"--leaf-partition-data", "--incremental")
   724  				})
   725  				AfterEach(func() {
   726  					testhelper.AssertQueryRuns(backupConn,
   727  						"DELETE FROM old_schema.old_table1 where mydata>10;")
   728  					testhelper.AssertQueryRuns(backupConn,
   729  						"DELETE FROM old_schema.old_table2 where mydata>15;")
   730  					oldSchemaTupleCounts["old_schema.old_table1"] = 10
   731  					oldSchemaTupleCounts["old_schema.old_table2"] = 15
   732  					testhelper.AssertQueryRuns(restoreConn,
   733  						"DELETE FROM old_schema.old_table1 where mydata>10;")
   734  					testhelper.AssertQueryRuns(restoreConn,
   735  						"DELETE FROM old_schema.old_table2 where mydata>15;")
   736  					assertArtifactsCleaned(restoreConn, timestamp)
   737  				})
   738  				It("Updates data in existing tables", func() {
   739  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   740  						"--incremental", "--data-only",
   741  						"--redirect-db", "restoredb")
   742  					oldSchemaTupleCounts["old_schema.old_table1"] = 20
   743  					oldSchemaTupleCounts["old_schema.old_table2"] = 30
   744  					assertSchemasExist(restoreConn, 4)
   745  					assertRelationsExistForIncremental(restoreConn, 3)
   746  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   747  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   748  				})
   749  				It("Updates only tables included by user if user input is provided", func() {
   750  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   751  						"--incremental", "--data-only",
   752  						"--include-table", "old_schema.old_table1",
   753  						"--redirect-db", "restoredb")
   754  					oldSchemaTupleCounts["old_schema.old_table1"] = 20
   755  					assertSchemasExist(restoreConn, 4)
   756  					assertRelationsExistForIncremental(restoreConn, 3)
   757  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   758  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   759  				})
   760  				It("Does not update tables excluded by user if user input is provided", func() {
   761  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   762  						"--incremental", "--data-only",
   763  						"--exclude-table", "old_schema.old_table1",
   764  						"--redirect-db", "restoredb")
   765  					oldSchemaTupleCounts["old_schema.old_table2"] = 30
   766  					assertSchemasExist(restoreConn, 4)
   767  					assertRelationsExistForIncremental(restoreConn, 3)
   768  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   769  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   770  				})
   771  				It("Does not update anything if user excluded all tables", func() {
   772  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   773  						"--incremental", "--data-only",
   774  						"--exclude-table", "old_schema.old_table1",
   775  						"--exclude-table", "old_schema.old_table2",
   776  						"--redirect-db", "restoredb")
   777  					assertSchemasExist(restoreConn, 4)
   778  					assertRelationsExistForIncremental(restoreConn, 3)
   779  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   780  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   781  				})
   782  				It("Does not update tables if user input is provided and schema is not included", func() {
   783  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   784  						"--incremental", "--data-only",
   785  						"--include-schema", "public",
   786  						"--redirect-db", "restoredb")
   787  					assertSchemasExist(restoreConn, 4)
   788  					assertRelationsExistForIncremental(restoreConn, 3)
   789  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   790  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   791  				})
   792  				It("Does not restore tables if user input is provide and schema is excluded by user", func() {
   793  					gprestore(gprestorePath, restoreHelperPath, timestamp,
   794  						"--incremental", "--data-only",
   795  						"--exclude-schema", "old_schema",
   796  						"--redirect-db", "restoredb")
   797  					assertSchemasExist(restoreConn, 4)
   798  					assertRelationsExistForIncremental(restoreConn, 3)
   799  					assertDataRestored(restoreConn, oldSchemaTupleCounts)
   800  					assertDataRestored(restoreConn, newSchemaTupleCounts)
   801  				})
   802  			})
   803  		})
   804  	})
   805  })