blob: fef855685b803fb0a2cf36f8042d1b46bb800238 [file]
package options_test
import (
"os"
"github.com/DATA-DOG/go-sqlmock"
"github.com/apache/cloudberry-backup/options"
"github.com/apache/cloudberry-go-libs/dbconn"
"github.com/apache/cloudberry-go-libs/testhelper"
"github.com/spf13/pflag"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("options", func() {
var (
myflags *pflag.FlagSet
)
BeforeEach(func() {
myflags = &pflag.FlagSet{}
options.SetBackupFlagDefaults(myflags)
})
Describe("Options initialization", func() {
It("returns no included tables when none specified", func() {
subject, err := options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
includedTables := subject.GetIncludedTables()
Expect(includedTables).To(BeEmpty())
originalIncludedTables := subject.GetOriginalIncludedTables()
Expect(originalIncludedTables).To(BeEmpty())
})
It("returns the include tables when one table in flag", func() {
err := myflags.Set(options.INCLUDE_RELATION, "foo.bar")
Expect(err).ToNot(HaveOccurred())
subject, err := options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
includedTables := subject.GetIncludedTables()
Expect(includedTables).To(HaveLen(1))
Expect(includedTables[0]).To(Equal("foo.bar"))
originalIncludedTables := subject.GetOriginalIncludedTables()
Expect(originalIncludedTables[0]).To(Equal("foo.bar"))
})
It("returns an include with special characters besides quote and dot", func() {
err := myflags.Set(options.INCLUDE_RELATION, `foo '~#$%^&*()_-+[]{}><\|;:/?!\t\n,.bar`)
Expect(err).ToNot(HaveOccurred())
subject, err := options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
includedTables := subject.GetIncludedTables()
Expect(includedTables).To(HaveLen(1))
Expect(includedTables[0]).To(Equal(`foo '~#$%^&*()_-+[]{}><\|;:/?!\t\n,.bar`))
})
It("returns all included tables when multiple individual flags provided", func() {
err := myflags.Set(options.INCLUDE_RELATION, "foo.bar")
Expect(err).ToNot(HaveOccurred())
err = myflags.Set(options.INCLUDE_RELATION, "bar.baz")
Expect(err).ToNot(HaveOccurred())
subject, err := options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
includedTables := subject.GetIncludedTables()
Expect(includedTables).To(HaveLen(2))
Expect(includedTables[0]).To(Equal("foo.bar"))
Expect(includedTables[1]).To(Equal("bar.baz"))
})
It("returns the text-file tables when specified", func() {
file, err := os.CreateTemp("/tmp", "gpbackup_test_options*.txt")
Expect(err).To(Not(HaveOccurred()))
defer func() {
_ = os.Remove(file.Name())
}()
_, err = file.WriteString("myschema.mytable\n")
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("myschema.mytable2\n")
Expect(err).To(Not(HaveOccurred()))
err = file.Close()
Expect(err).To(Not(HaveOccurred()))
err = myflags.Set(options.INCLUDE_RELATION_FILE, file.Name())
Expect(err).ToNot(HaveOccurred())
subject, err := options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
includedTables := subject.GetIncludedTables()
Expect(includedTables).To(HaveLen(2))
Expect(includedTables[0]).To(Equal("myschema.mytable"))
Expect(includedTables[1]).To(Equal("myschema.mytable2"))
})
It("sets the INCLUDE_RELATIONS flag from file", func() {
file, err := os.CreateTemp("/tmp", "gpbackup_test_options*.txt")
Expect(err).To(Not(HaveOccurred()))
defer func() {
_ = os.Remove(file.Name())
}()
_, err = file.WriteString("myschema.mytable\n")
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("myschema.mytable2\n")
Expect(err).To(Not(HaveOccurred()))
err = file.Close()
Expect(err).To(Not(HaveOccurred()))
err = myflags.Set(options.INCLUDE_RELATION_FILE, file.Name())
Expect(err).ToNot(HaveOccurred())
_, err = options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
includedTables, err := myflags.GetStringArray(options.INCLUDE_RELATION)
Expect(err).ToNot(HaveOccurred())
Expect(includedTables).To(HaveLen(2))
Expect(includedTables[0]).To(Equal("myschema.mytable"))
Expect(includedTables[1]).To(Equal("myschema.mytable2"))
})
It("skips empty lines in files provided for filtering tables", func() {
file, err := os.CreateTemp("/tmp", "gpbackup_test_options*.txt")
Expect(err).To(Not(HaveOccurred()))
defer func() {
_ = os.Remove(file.Name())
}()
_, err = file.WriteString("myschema.mytable\n")
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("\n")
Expect(err).To(Not(HaveOccurred()))
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("\n")
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("myschema.mytable2\n")
Expect(err).To(Not(HaveOccurred()))
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("\n")
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("\n")
err = file.Close()
Expect(err).To(Not(HaveOccurred()))
err = myflags.Set(options.EXCLUDE_RELATION_FILE, file.Name())
Expect(err).ToNot(HaveOccurred())
_, err = options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
excludedTables, err := myflags.GetStringArray(options.EXCLUDE_RELATION)
Expect(err).ToNot(HaveOccurred())
Expect(excludedTables).To(HaveLen(2))
Expect(excludedTables[0]).To(Equal("myschema.mytable"))
Expect(excludedTables[1]).To(Equal("myschema.mytable2"))
})
It("skips empty lines in files provided for filtering schemas", func() {
file, err := os.CreateTemp("/tmp", "gpbackup_test_options*.txt")
Expect(err).To(Not(HaveOccurred()))
defer func() {
_ = os.Remove(file.Name())
}()
_, err = file.WriteString("myschema1\n")
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("\n")
Expect(err).To(Not(HaveOccurred()))
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("\n")
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("myschema2\n")
Expect(err).To(Not(HaveOccurred()))
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("\n")
Expect(err).To(Not(HaveOccurred()))
_, err = file.WriteString("\n")
err = file.Close()
Expect(err).To(Not(HaveOccurred()))
err = myflags.Set(options.INCLUDE_SCHEMA_FILE, file.Name())
Expect(err).ToNot(HaveOccurred())
_, err = options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
includedSchemas, err := myflags.GetStringArray(options.INCLUDE_SCHEMA)
Expect(err).ToNot(HaveOccurred())
Expect(includedSchemas).To(HaveLen(2))
Expect(includedSchemas[0]).To(Equal("myschema1"))
Expect(includedSchemas[1]).To(Equal("myschema2"))
})
It("it remembers flag values for INCLUDE_SCHEMA, EXCLUDE*, LEAF_PARTITION_DATA", func() {
err := myflags.Set(options.INCLUDE_SCHEMA, "my include schema")
Expect(err).ToNot(HaveOccurred())
err = myflags.Set(options.EXCLUDE_SCHEMA, "my exclude schema")
Expect(err).ToNot(HaveOccurred())
err = myflags.Set(options.LEAF_PARTITION_DATA, "true")
Expect(err).ToNot(HaveOccurred())
subject, err := options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
Expect(subject.GetIncludedSchemas()[0]).To(Equal("my include schema"))
Expect(subject.GetExcludedSchemas()[0]).To(Equal("my exclude schema"))
})
It("returns an error upon invalid inclusions", func() {
err := myflags.Set(options.INCLUDE_RELATION, "foo")
Expect(err).ToNot(HaveOccurred())
_, err = options.NewOptions(myflags)
Expect(err).To(HaveOccurred())
})
Describe("AddIncludeRelation", func() {
It("it adds a relation", func() {
subject, err := options.NewOptions(myflags)
Expect(err).To(Not(HaveOccurred()))
subject.AddIncludedRelation("public.foobar")
Expect(subject.GetIncludedTables()).To(Equal([]string{"public.foobar"}))
Expect(subject.GetOriginalIncludedTables()).To(BeEmpty())
})
})
})
Describe("SeparateSchemaAndTable", func() {
It("properly splits the strings", func() {
tableList := []string{
"foo.Bar",
"FOO.Bar",
"FO!@#.BAR",
`ezschema."myt.able"`, // dots in one part
`"mys.chema"."othert.able"`, // dots in both parts
`easy."tryme""`, // quotes with escaping
`"mys.chema"."anoth"ert.able"`, // quotes without escaping
`easy.no"quotes`, // quotes without wrapping or escaping
}
expectedFqn := []options.Relation{
{Schema: `foo`, Name: `Bar`},
{Schema: `FOO`, Name: `Bar`},
{Schema: `FO!@#`, Name: `BAR`},
{Schema: "ezschema", Name: "myt.able"},
{Schema: "mys.chema", Name: "othert.able"},
{Schema: "easy", Name: "tryme\""},
{Schema: "mys.chema", Name: "anoth\"ert.able"},
{Schema: "easy", Name: "no\"quotes"},
}
resultFqn, err := options.SeparateSchemaAndTable(tableList)
Expect(err).ToNot(HaveOccurred())
Expect(resultFqn).To(Equal(expectedFqn))
})
It("fails to split TableName", func() {
tableList := []string{"foo."}
_, err := options.SeparateSchemaAndTable(tableList)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("foo."))
})
It("fails to split SchemaName", func() {
tableList := []string{".bar"}
_, err := options.SeparateSchemaAndTable(tableList)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(".bar"))
})
It("fails to split SchemaName or tableName (no '.')", func() {
tableList := []string{"foobar"}
_, err := options.SeparateSchemaAndTable(tableList)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("foobar"))
})
It("fails when there is more than one dot and no correct quoting", func() {
// todo in a future story, establish a way for users to escape dots to show us which one is *in* the name versus the dot that divides schemaname from tablename
tableList := []string{"foobar.baz.bam"}
_, err := options.SeparateSchemaAndTable(tableList)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("foobar.baz.bam"))
})
})
Describe("QuoteTableNames", func() {
var (
conn *dbconn.DBConn
mockdb sqlmock.Sqlmock
)
BeforeEach(func() {
conn, mockdb, _, _, _ = testhelper.SetupTestEnvironment()
})
It("returns empty result if given empty list", func() {
tablenames := make([]string, 0)
quotedTableNames, err := options.QuoteTableNames(conn, tablenames)
Expect(err).To(Not(HaveOccurred()))
Expect(tablenames).To(Equal(quotedTableNames))
})
It("returns a single result when given a single fqn", func() {
tablenames := []string{"public.foo"}
queryMock := mockdb.ExpectQuery("SELECT quote_")
resultRows := sqlmock.NewRows([]string{"schemaname", "tablename"}).
AddRow("public", "foo")
queryMock.WillReturnRows(resultRows)
quotedTableNames, err := options.QuoteTableNames(conn, tablenames)
Expect(err).To(Not(HaveOccurred()))
Expect(tablenames).To(Equal(quotedTableNames))
})
It("returns an array of correctly formatted fqn's", func() {
tablenames := []string{"public.one", "public.two", "public.three"}
queryMock := mockdb.ExpectQuery("SELECT quote_")
resultRows := sqlmock.NewRows([]string{"schemaname", "tablename"}).
AddRow("public", "one")
queryMock.WillReturnRows(resultRows)
queryMock = mockdb.ExpectQuery("SELECT quote_")
resultRows = sqlmock.NewRows([]string{"schemaname", "tablename"}).
AddRow("public", "two")
queryMock.WillReturnRows(resultRows)
queryMock = mockdb.ExpectQuery("SELECT quote_")
resultRows = sqlmock.NewRows([]string{"schemaname", "tablename"}).
AddRow("public", "three")
queryMock.WillReturnRows(resultRows)
quotedTableNames, err := options.QuoteTableNames(conn, tablenames)
Expect(err).To(Not(HaveOccurred()))
Expect(tablenames).To(Equal(quotedTableNames))
})
})
})