blob: 94c9891bd6b6f9e7434dc3862d05beba18c33107 [file]
package backup_test
import (
"fmt"
"regexp"
"github.com/DATA-DOG/go-sqlmock"
"github.com/apache/cloudberry-backup/backup"
"github.com/apache/cloudberry-backup/history"
"github.com/apache/cloudberry-backup/options"
"github.com/apache/cloudberry-backup/report"
"github.com/apache/cloudberry-backup/toc"
"github.com/apache/cloudberry-backup/utils"
"gopkg.in/cheggaaa/pb.v1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("backup/data tests", func() {
Describe("ConstructTableAttributesList", func() {
It("creates an attribute list for a table with one column", func() {
columnDefs := []backup.ColumnDefinition{{Name: "a"}}
atts := backup.ConstructTableAttributesList(columnDefs)
Expect(atts).To(Equal("(a)"))
})
It("creates an attribute list for a table with multiple columns", func() {
columnDefs := []backup.ColumnDefinition{{Name: "a"}, {Name: "b"}}
atts := backup.ConstructTableAttributesList(columnDefs)
Expect(atts).To(Equal("(a,b)"))
})
It("creates an attribute list for a table with no columns", func() {
columnDefs := make([]backup.ColumnDefinition, 0)
atts := backup.ConstructTableAttributesList(columnDefs)
Expect(atts).To(Equal(""))
})
})
Describe("AddTableDataEntriesToTOC", func() {
var (
tocfile *toc.TOC
rowsCopiedMaps []map[uint32]int64
table backup.Table
)
BeforeEach(func() {
tocfile = &toc.TOC{}
backup.SetTOC(tocfile)
rowsCopiedMaps = make([]map[uint32]int64, connectionPool.NumConns)
columnDefs := []backup.ColumnDefinition{{Oid: 1, Name: "a"}}
table = backup.Table{
Relation: backup.Relation{Oid: 1, Schema: "public", Name: "table"},
TableDefinition: backup.TableDefinition{ColumnDefs: columnDefs},
}
})
It("adds an entry for a regular table to the TOC", func() {
tables := []backup.Table{table}
backup.AddTableDataEntriesToTOC(tables, rowsCopiedMaps)
expectedDataEntries := []toc.CoordinatorDataEntry{{Schema: "public", Name: "table", Oid: 1, AttributeString: "(a)"}}
Expect(tocfile.DataEntries).To(Equal(expectedDataEntries))
})
It("does not add an entry for an external table to the TOC", func() {
table.IsExternal = true
tables := []backup.Table{table}
backup.AddTableDataEntriesToTOC(tables, rowsCopiedMaps)
Expect(tocfile.DataEntries).To(BeNil())
})
It("does not add an entry for a foreign table to the TOC", func() {
foreignDef := backup.ForeignTableDefinition{Oid: 23, Options: "", Server: "fs"}
table.ForeignDef = foreignDef
tables := []backup.Table{table}
backup.AddTableDataEntriesToTOC(tables, rowsCopiedMaps)
Expect(tocfile.DataEntries).To(BeNil())
})
})
Describe("CopyTableOut", func() {
testTable := backup.Table{Relation: backup.Relation{SchemaOid: 2345, Oid: 3456, Schema: "public", Name: "foo"}}
It("will back up a table to its own file with gzip compression", func() {
utils.SetPipeThroughProgram(utils.PipeThroughProgram{Name: "gzip", OutputCommand: "gzip -c -8", InputCommand: "gzip -d -c", Extension: ".gz"})
execStr := regexp.QuoteMeta("COPY public.foo TO PROGRAM 'gzip -c -8 > <SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456.gz' WITH CSV DELIMITER ',' ON SEGMENT IGNORE EXTERNAL PARTITIONS;")
mock.ExpectExec(execStr).WillReturnResult(sqlmock.NewResult(10, 0))
filename := "<SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456.gz"
_, err := backup.CopyTableOut(connectionPool, testTable, filename, defaultConnNum)
Expect(err).ShouldNot(HaveOccurred())
})
It("will back up a table to its own file with gzip compression using a plugin", func() {
_ = cmdFlags.Set(options.PLUGIN_CONFIG, "/tmp/plugin_config")
pluginConfig := utils.PluginConfig{ExecutablePath: "/tmp/fake-plugin.sh", ConfigPath: "/tmp/plugin_config"}
backup.SetPluginConfig(&pluginConfig)
utils.SetPipeThroughProgram(utils.PipeThroughProgram{Name: "gzip", OutputCommand: "gzip -c -8", InputCommand: "gzip -d -c", Extension: ".gz"})
execStr := regexp.QuoteMeta("COPY public.foo TO PROGRAM 'gzip -c -8 | /tmp/fake-plugin.sh backup_data /tmp/plugin_config <SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456' WITH CSV DELIMITER ',' ON SEGMENT IGNORE EXTERNAL PARTITIONS;")
mock.ExpectExec(execStr).WillReturnResult(sqlmock.NewResult(10, 0))
filename := "<SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456"
_, err := backup.CopyTableOut(connectionPool, testTable, filename, defaultConnNum)
Expect(err).ShouldNot(HaveOccurred())
})
It("will back up a table to its own file with zstd compression", func() {
utils.SetPipeThroughProgram(utils.PipeThroughProgram{Name: "zstd", OutputCommand: "zstd --compress -3 -c", InputCommand: "zstd --decompress -c", Extension: ".zst"})
execStr := regexp.QuoteMeta("COPY public.foo TO PROGRAM 'zstd --compress -3 -c > <SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456.zst' WITH CSV DELIMITER ',' ON SEGMENT IGNORE EXTERNAL PARTITIONS;")
mock.ExpectExec(execStr).WillReturnResult(sqlmock.NewResult(10, 0))
filename := "<SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456.zst"
_, err := backup.CopyTableOut(connectionPool, testTable, filename, defaultConnNum)
Expect(err).ShouldNot(HaveOccurred())
})
It("will back up a table to its own file with zstd compression using a plugin", func() {
_ = cmdFlags.Set(options.PLUGIN_CONFIG, "/tmp/plugin_config")
pluginConfig := utils.PluginConfig{ExecutablePath: "/tmp/fake-plugin.sh", ConfigPath: "/tmp/plugin_config"}
backup.SetPluginConfig(&pluginConfig)
utils.SetPipeThroughProgram(utils.PipeThroughProgram{Name: "zstd", OutputCommand: "zstd --compress -3 -c", InputCommand: "zstd --decompress -c", Extension: ".zst"})
execStr := regexp.QuoteMeta("COPY public.foo TO PROGRAM 'zstd --compress -3 -c | /tmp/fake-plugin.sh backup_data /tmp/plugin_config <SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456' WITH CSV DELIMITER ',' ON SEGMENT IGNORE EXTERNAL PARTITIONS;")
mock.ExpectExec(execStr).WillReturnResult(sqlmock.NewResult(10, 0))
filename := "<SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456"
_, err := backup.CopyTableOut(connectionPool, testTable, filename, defaultConnNum)
Expect(err).ShouldNot(HaveOccurred())
})
It("will back up a table to its own file without compression", func() {
utils.SetPipeThroughProgram(utils.PipeThroughProgram{Name: "cat", OutputCommand: "cat -", InputCommand: "cat -", Extension: ""})
execStr := regexp.QuoteMeta("COPY public.foo TO PROGRAM 'cat - > <SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456' WITH CSV DELIMITER ',' ON SEGMENT IGNORE EXTERNAL PARTITIONS;")
mock.ExpectExec(execStr).WillReturnResult(sqlmock.NewResult(10, 0))
filename := "<SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456"
_, err := backup.CopyTableOut(connectionPool, testTable, filename, defaultConnNum)
Expect(err).ShouldNot(HaveOccurred())
})
It("will back up a table to its own file without compression using a plugin", func() {
_ = cmdFlags.Set(options.PLUGIN_CONFIG, "/tmp/plugin_config")
pluginConfig := utils.PluginConfig{ExecutablePath: "/tmp/fake-plugin.sh", ConfigPath: "/tmp/plugin_config"}
backup.SetPluginConfig(&pluginConfig)
utils.SetPipeThroughProgram(utils.PipeThroughProgram{Name: "cat", OutputCommand: "cat -", InputCommand: "cat -", Extension: ""})
execStr := regexp.QuoteMeta("COPY public.foo TO PROGRAM 'cat - | /tmp/fake-plugin.sh backup_data /tmp/plugin_config <SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456' WITH CSV DELIMITER ',' ON SEGMENT IGNORE EXTERNAL PARTITIONS;")
mock.ExpectExec(execStr).WillReturnResult(sqlmock.NewResult(10, 0))
filename := "<SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456"
_, err := backup.CopyTableOut(connectionPool, testTable, filename, defaultConnNum)
Expect(err).ShouldNot(HaveOccurred())
})
It("will back up a table to a single file", func() {
_ = cmdFlags.Set(options.SINGLE_DATA_FILE, "true")
execStr := regexp.QuoteMeta(`COPY public.foo TO PROGRAM '(test -p "<SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456" || (echo "Pipe not found <SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456">&2; exit 1)) && cat - > <SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456' WITH CSV DELIMITER ',' ON SEGMENT IGNORE EXTERNAL PARTITIONS;`)
mock.ExpectExec(execStr).WillReturnResult(sqlmock.NewResult(10, 0))
filename := "<SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_3456"
_, err := backup.CopyTableOut(connectionPool, testTable, filename, defaultConnNum)
Expect(err).ShouldNot(HaveOccurred())
})
})
Describe("BackupSingleTableData", func() {
var (
testTable backup.Table
rowsCopiedMap map[uint32]int64
counters backup.BackupProgressCounters
copyFmtStr = "COPY(.*)%s(.*)"
)
BeforeEach(func() {
testTable = backup.Table{
Relation: backup.Relation{Oid: 0, Schema: "public", Name: "testtable"},
TableDefinition: backup.TableDefinition{IsExternal: false},
}
_ = cmdFlags.Set(options.SINGLE_DATA_FILE, "false")
rowsCopiedMap = make(map[uint32]int64)
counters = backup.BackupProgressCounters{NumRegTables: 0, TotalRegTables: 1}
counters.ProgressBar = utils.NewProgressBar(int(counters.TotalRegTables), "Tables backed up: ", utils.PB_INFO)
counters.ProgressBar.(*pb.ProgressBar).NotPrint = true
counters.ProgressBar.Start()
})
It("backs up a single regular table with single data file", func() {
_ = cmdFlags.Set(options.SINGLE_DATA_FILE, "true")
backupFile := fmt.Sprintf("<SEG_DATA_DIR>/gpbackup_<SEGID>_20170101010101_pipe_(.*)_%d", testTable.Oid)
copyCmd := fmt.Sprintf(copyFmtStr, backupFile)
mock.ExpectExec(copyCmd).WillReturnResult(sqlmock.NewResult(0, 10))
err := backup.BackupSingleTableData(testTable, rowsCopiedMap, &counters, 0)
Expect(err).ShouldNot(HaveOccurred())
Expect(rowsCopiedMap[0]).To(Equal(int64(10)))
Expect(counters.NumRegTables).To(Equal(int64(1)))
})
It("backs up a single regular table without a single data file", func() {
_ = cmdFlags.Set(options.SINGLE_DATA_FILE, "false")
backupFile := fmt.Sprintf("<SEG_DATA_DIR>/backups/20170101/20170101010101/gpbackup_<SEGID>_20170101010101_%d", testTable.Oid)
copyCmd := fmt.Sprintf(copyFmtStr, backupFile)
mock.ExpectExec(copyCmd).WillReturnResult(sqlmock.NewResult(0, 10))
err := backup.BackupSingleTableData(testTable, rowsCopiedMap, &counters, 0)
Expect(err).ShouldNot(HaveOccurred())
Expect(rowsCopiedMap[0]).To(Equal(int64(10)))
Expect(counters.NumRegTables).To(Equal(int64(1)))
})
})
Describe("GetBackupDataSet", func() {
config := history.BackupConfig{}
var testTable backup.Table
BeforeEach(func() {
config.MetadataOnly = false
backup.SetReport(&report.Report{BackupConfig: config})
testTable = backup.Table{
Relation: backup.Relation{Schema: "public", Name: "testtable"},
TableDefinition: backup.TableDefinition{},
}
})
It("excludes a foreign table", func() {
foreignDef := backup.ForeignTableDefinition{Oid: 23, Options: "", Server: "fs"}
testTable.ForeignDef = foreignDef
tables := []backup.Table{testTable}
dataTables, numExtOrForeignTables := backup.GetBackupDataSet(tables)
Expect(len(dataTables)).To(Equal(0))
Expect(numExtOrForeignTables).To(Equal(int64(1)))
})
It("excludes an external table", func() {
testTable.IsExternal = true
tables := []backup.Table{testTable}
dataTables, numExtOrForeignTables := backup.GetBackupDataSet(tables)
Expect(len(dataTables)).To(Equal(0))
Expect(numExtOrForeignTables).To(Equal(int64(1)))
})
It("doesn't exclude regular table", func() {
tables := []backup.Table{testTable}
dataTables, numExtOrForeignTables := backup.GetBackupDataSet(tables)
Expect(len(dataTables)).To(Equal(1))
Expect(numExtOrForeignTables).To(Equal(int64(0)))
})
It("returns an empty set, if --metadata-only flag is set and a regular table is given", func() {
config.MetadataOnly = true
backup.SetReport(&report.Report{BackupConfig: config})
tables := []backup.Table{testTable}
dataTables, numExtOrForeignTables := backup.GetBackupDataSet(tables)
Expect(len(dataTables)).To(Equal(0))
Expect(numExtOrForeignTables).To(Equal(int64(0)))
})
})
})