blob: d93ef5ed8c38fd7cace76c771aaa887937d17aef [file] [log] [blame]
package backup_test
import (
"database/sql"
"database/sql/driver"
"github.com/DATA-DOG/go-sqlmock"
"github.com/greenplum-db/gp-common-go-libs/structmatcher"
"github.com/greenplum-db/gpbackup/backup"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("backup/queries_acl tests", func() {
Describe("PostProcessFunctionConfigs", func() {
It("returns correct value for search_path", func() {
allFunctions := []backup.Function{
{Config: "SET SEARCH_PATH TO bar"},
}
err := backup.PostProcessFunctionConfigs(allFunctions)
Expect(err).ToNot(HaveOccurred())
Expect(allFunctions[0].Config).To(Equal(`SET search_path TO 'bar'`))
})
It("returns error when function config does not parse", func() {
allFunctions := []backup.Function{
{Config: "SET foo blah blah blah"},
}
err := backup.PostProcessFunctionConfigs(allFunctions)
Expect(err).To(HaveOccurred())
})
// known bug https://www.pivotaltracker.com/story/show/164575992
PIt("returns correct value for multiple GUCs in one function", func() {
allFunctions := []backup.Function{
// not clear how the native pg_proc.proconfig field will translate into our Config attribute: assuming we get 2 separate strings
{Config: `SET search_path TO bar, blah
SET BAZ TO abc`},
}
err := backup.PostProcessFunctionConfigs(allFunctions)
Expect(err).ToNot(HaveOccurred())
// expecting separate lines stored in the Config attribute;
// this may not be the perfect solution, TBD: may want to have it become a slice of strings
Expect(allFunctions[0].Config).To(Equal(`SET search_path TO 'bar', blah
SET baz to abc`))
})
})
Describe("QuoteGUCValue", func() {
It("returns correct value for a name/value pair", func() {
result := backup.QuoteGUCValue("foo", `bar`)
Expect(result).To(Equal(`'bar'`))
})
It("returns correct value for SEARCH_PATH", func() {
result := backup.QuoteGUCValue("search_path", `"$user",public`)
Expect(result).To(Equal(`'$user', 'public'`))
})
It("returns correct value for temp_tablespaces", func() {
result := backup.QuoteGUCValue("temp_tablespaces", `"tables""pace1%", tablespace2`)
Expect(result).To(Equal(`'tables"pace1%', 'tablespace2'`))
})
})
Describe("UnescapeDoubleQuote", func() {
It("removes outside quotes", func() {
result := backup.UnescapeDoubleQuote(`"foo"`)
Expect(result).To(Equal(`foo`))
})
It("does nothing if string has no quotes surrounding it", func() {
result := backup.UnescapeDoubleQuote(`foo`)
Expect(result).To(Equal(`foo`))
})
It("removes outside quotes and unescapes embedded quote", func() {
result := backup.UnescapeDoubleQuote(`"foo"""`)
Expect(result).To(Equal(`foo"`))
})
It("removes outside quotes and unescapes multiple embedded quotes", func() {
result := backup.UnescapeDoubleQuote(`"""foo"""`)
Expect(result).To(Equal(`"foo"`))
})
})
Describe("GetFunctions", func() {
It("GetFunctions properly handles NULL function arguments, NULL function identity arguments, or NULL function result types", func() {
if false {
Skip("Test does not apply for GPDB versions before 5")
}
header := []string{"oid", "schema", "name", "proretset", "functionbody", "binarypath", "arguments", "identargs", "resulttype",
"provolatile", "proisstrict", "prosecdef", "proconfig", "procost", "prorows", "prodataaccess", "language"}
rowGood := []driver.Value{"1", "mock_schema", "mock_table", false, "mock_funcbody", "mock_path",
sql.NullString{String: "mock_args", Valid: true}, sql.NullString{String: "mock_identargs", Valid: true},
sql.NullString{String: "mock_resulttype", Valid: true}, "mock_volatility", false, false, "", 0, 0,
"mock_dataaccess", "mock_language"}
rowNullArg := []driver.Value{"2", "mock_schema2", "mock_table2", false, "mock_funcbody2", "mock_path2", nil,
sql.NullString{String: "mock_identargs2", Valid: true}, sql.NullString{String: "mock_resulttype2", Valid: true}, "mock_volatility2",
false, false, "", 0, 0, "mock_dataaccess2", "mock_language2"}
rowNullIdentArg := []driver.Value{"3", "mock_schema3", "mock_table3", false, "mock_funcbody3", "mock_path3",
sql.NullString{String: "mock_args3", Valid: true}, nil, sql.NullString{String: "mock_resulttype3", Valid: true}, "mock_volatility3",
false, false, "", 0, 0, "mock_dataaccess3", "mock_language3"}
rowNullResultType := []driver.Value{"4", "mock_schema4", "mock_table4", false, "mock_funcbody4", "mock_path4",
sql.NullString{String: "mock_args4", Valid: true}, sql.NullString{String: "mock_identargs4", Valid: true}, nil, "mock_volatility4",
false, false, "", 0, 0, "mock_dataaccess4", "mock_language4"}
fakeRows := sqlmock.NewRows(header).AddRow(rowGood...).AddRow(rowNullArg...).AddRow(rowNullIdentArg...).AddRow(rowNullResultType...)
mock.ExpectQuery(`SELECT (.*)`).WillReturnRows(fakeRows)
result := backup.GetFunctions(connectionPool)
// Expect the GetFunctions function to return only the 1st row since all other rows have invalid NULL strings
expectedResult := []backup.Function{{Oid: 1, Schema: "mock_schema", Name: "mock_table", ReturnsSet: false, FunctionBody: "mock_funcbody",
BinaryPath: "mock_path", Arguments: sql.NullString{String: "mock_args", Valid: true},
IdentArgs: sql.NullString{String: "mock_identargs", Valid: true}, ResultType: sql.NullString{String: "mock_resulttype", Valid: true},
Volatility: "mock_volatility", IsStrict: false, IsSecurityDefiner: false, Config: "", Cost: 0,
NumRows: 0, DataAccess: "mock_dataaccess", Language: "mock_language"}}
Expect(result).To(HaveLen(1))
structmatcher.ExpectStructsToMatch(&expectedResult[0], &result[0])
})
})
Describe("GetAggregates", func() {
It("GetAggregates properly handles NULL aggregate arguments or NULL aggregate identity arguments", func() {
if false {
Skip("Test does not apply for GPDB versions before 5")
}
header := []string{"oid", "schema", "name", "arguments", "identargs", "aggtransfn", "aggprelimfn", "aggfinalfn", "sortoperator",
"sortoperatorschema", "transitiondatatype", "initialvalue", "initvalisnull", "minitvalisnull", "aggordered"}
rowGood := []driver.Value{"1", "mock_schema", "mock_table", sql.NullString{String: "mock_args", Valid: true},
sql.NullString{String: "mock_identargs", Valid: true}, 0, 0, 0, "mock_operator", "mock_operatorschema",
"mock_transdatatype", "mock_initvalue", false, false, false}
rowNullArg := []driver.Value{"2", "mock_schema2", "mock_table2", nil, sql.NullString{String: "mock_identargs2", Valid: true}, 0, 0, 0,
"mock_operator2", "mock_operatorschema2", "mock_transdatatype2", "mock_initvalue2", false, false, false}
rowNullIdentArg := []driver.Value{"3", "mock_schema3", "mock_table3", sql.NullString{String: "mock_args3", Valid: true}, nil, 0, 0, 0,
"mock_operator3", "mock_operatorschema3", "mock_transdatatype3", "mock_initvalue3", false, false, false}
fakeRows := sqlmock.NewRows(header).AddRow(rowGood...).AddRow(rowNullArg...).AddRow(rowNullIdentArg...)
mock.ExpectQuery(`SELECT (.*)`).WillReturnRows(fakeRows)
result := backup.GetAggregates(connectionPool)
// Expect the GetAggregates function to return only the 1st row since all other rows have invalid NULL strings
expectedResult := []backup.Aggregate{{Oid: 1, Schema: "mock_schema", Name: "mock_table",
Arguments: sql.NullString{String: "mock_args", Valid: true}, IdentArgs: sql.NullString{String: "mock_identargs", Valid: true},
TransitionFunction: 0, PreliminaryFunction: 0, FinalFunction: 0, SortOperator: "mock_operator",
SortOperatorSchema: "mock_operatorschema", TransitionDataType: "mock_transdatatype", InitialValue: "mock_initvalue",
InitValIsNull: false, MInitValIsNull: false, IsOrdered: false}}
Expect(result).To(HaveLen(1))
structmatcher.ExpectStructsToMatch(&expectedResult[0], &result[0])
})
})
})