github.com/tuhaihe/gpbackup@v1.0.3/backup/queries_functions_test.go (about) 1 package backup_test 2 3 import ( 4 "database/sql" 5 "database/sql/driver" 6 7 "github.com/DATA-DOG/go-sqlmock" 8 "github.com/tuhaihe/gp-common-go-libs/structmatcher" 9 "github.com/tuhaihe/gpbackup/backup" 10 11 . "github.com/onsi/ginkgo/v2" 12 . "github.com/onsi/gomega" 13 ) 14 15 var _ = Describe("backup/queries_acl tests", func() { 16 Describe("PostProcessFunctionConfigs", func() { 17 It("returns correct value for search_path", func() { 18 allFunctions := []backup.Function{ 19 {Config: "SET SEARCH_PATH TO bar"}, 20 } 21 err := backup.PostProcessFunctionConfigs(allFunctions) 22 Expect(err).ToNot(HaveOccurred()) 23 Expect(allFunctions[0].Config).To(Equal(`SET search_path TO 'bar'`)) 24 }) 25 It("returns error when function config does not parse", func() { 26 allFunctions := []backup.Function{ 27 {Config: "SET foo blah blah blah"}, 28 } 29 err := backup.PostProcessFunctionConfigs(allFunctions) 30 Expect(err).To(HaveOccurred()) 31 }) 32 // known bug https://www.pivotaltracker.com/story/show/164575992 33 PIt("returns correct value for multiple GUCs in one function", func() { 34 allFunctions := []backup.Function{ 35 // not clear how the native pg_proc.proconfig field will translate into our Config attribute: assuming we get 2 separate strings 36 {Config: `SET search_path TO bar, blah 37 SET BAZ TO abc`}, 38 } 39 err := backup.PostProcessFunctionConfigs(allFunctions) 40 Expect(err).ToNot(HaveOccurred()) 41 // expecting separate lines stored in the Config attribute; 42 // this may not be the perfect solution, TBD: may want to have it become a slice of strings 43 Expect(allFunctions[0].Config).To(Equal(`SET search_path TO 'bar', blah 44 SET baz to abc`)) 45 }) 46 }) 47 Describe("QuoteGUCValue", func() { 48 It("returns correct value for a name/value pair", func() { 49 result := backup.QuoteGUCValue("foo", `bar`) 50 Expect(result).To(Equal(`'bar'`)) 51 }) 52 It("returns correct value for SEARCH_PATH", func() { 53 result := backup.QuoteGUCValue("search_path", `"$user",public`) 54 Expect(result).To(Equal(`'$user', 'public'`)) 55 }) 56 It("returns correct value for temp_tablespaces", func() { 57 result := backup.QuoteGUCValue("temp_tablespaces", `"tables""pace1%", tablespace2`) 58 Expect(result).To(Equal(`'tables"pace1%', 'tablespace2'`)) 59 }) 60 }) 61 Describe("UnescapeDoubleQuote", func() { 62 It("removes outside quotes", func() { 63 result := backup.UnescapeDoubleQuote(`"foo"`) 64 Expect(result).To(Equal(`foo`)) 65 }) 66 It("does nothing if string has no quotes surrounding it", func() { 67 result := backup.UnescapeDoubleQuote(`foo`) 68 Expect(result).To(Equal(`foo`)) 69 }) 70 It("removes outside quotes and unescapes embedded quote", func() { 71 result := backup.UnescapeDoubleQuote(`"foo"""`) 72 Expect(result).To(Equal(`foo"`)) 73 }) 74 It("removes outside quotes and unescapes multiple embedded quotes", func() { 75 result := backup.UnescapeDoubleQuote(`"""foo"""`) 76 Expect(result).To(Equal(`"foo"`)) 77 }) 78 }) 79 Describe("GetFunctions", func() { 80 It("GetFunctions properly handles NULL function arguments, NULL function identity arguments, or NULL function result types", func() { 81 if false { 82 Skip("Test does not apply for GPDB versions before 5") 83 } 84 85 header := []string{"oid", "schema", "name", "proretset", "functionbody", "binarypath", "arguments", "identargs", "resulttype", 86 "provolatile", "proisstrict", "prosecdef", "proconfig", "procost", "prorows", "prodataaccess", "language"} 87 rowGood := []driver.Value{"1", "mock_schema", "mock_table", false, "mock_funcbody", "mock_path", 88 sql.NullString{String: "mock_args", Valid: true}, sql.NullString{String: "mock_identargs", Valid: true}, 89 sql.NullString{String: "mock_resulttype", Valid: true}, "mock_volatility", false, false, "", 0, 0, 90 "mock_dataaccess", "mock_language"} 91 rowNullArg := []driver.Value{"2", "mock_schema2", "mock_table2", false, "mock_funcbody2", "mock_path2", nil, 92 sql.NullString{String: "mock_identargs2", Valid: true}, sql.NullString{String: "mock_resulttype2", Valid: true}, "mock_volatility2", 93 false, false, "", 0, 0, "mock_dataaccess2", "mock_language2"} 94 rowNullIdentArg := []driver.Value{"3", "mock_schema3", "mock_table3", false, "mock_funcbody3", "mock_path3", 95 sql.NullString{String: "mock_args3", Valid: true}, nil, sql.NullString{String: "mock_resulttype3", Valid: true}, "mock_volatility3", 96 false, false, "", 0, 0, "mock_dataaccess3", "mock_language3"} 97 rowNullResultType := []driver.Value{"4", "mock_schema4", "mock_table4", false, "mock_funcbody4", "mock_path4", 98 sql.NullString{String: "mock_args4", Valid: true}, sql.NullString{String: "mock_identargs4", Valid: true}, nil, "mock_volatility4", 99 false, false, "", 0, 0, "mock_dataaccess4", "mock_language4"} 100 fakeRows := sqlmock.NewRows(header).AddRow(rowGood...).AddRow(rowNullArg...).AddRow(rowNullIdentArg...).AddRow(rowNullResultType...) 101 mock.ExpectQuery(`SELECT (.*)`).WillReturnRows(fakeRows) 102 result := backup.GetFunctions(connectionPool) 103 104 // Expect the GetFunctions function to return only the 1st row since all other rows have invalid NULL strings 105 expectedResult := []backup.Function{{Oid: 1, Schema: "mock_schema", Name: "mock_table", ReturnsSet: false, FunctionBody: "mock_funcbody", 106 BinaryPath: "mock_path", Arguments: sql.NullString{String: "mock_args", Valid: true}, 107 IdentArgs: sql.NullString{String: "mock_identargs", Valid: true}, ResultType: sql.NullString{String: "mock_resulttype", Valid: true}, 108 Volatility: "mock_volatility", IsStrict: false, IsSecurityDefiner: false, Config: "", Cost: 0, 109 NumRows: 0, DataAccess: "mock_dataaccess", Language: "mock_language"}} 110 111 Expect(result).To(HaveLen(1)) 112 structmatcher.ExpectStructsToMatch(&expectedResult[0], &result[0]) 113 }) 114 }) 115 Describe("GetAggregates", func() { 116 It("GetAggregates properly handles NULL aggregate arguments or NULL aggregate identity arguments", func() { 117 if false { 118 Skip("Test does not apply for GPDB versions before 5") 119 } 120 121 header := []string{"oid", "schema", "name", "arguments", "identargs", "aggtransfn", "aggprelimfn", "aggfinalfn", "sortoperator", 122 "sortoperatorschema", "transitiondatatype", "initialvalue", "initvalisnull", "minitvalisnull", "aggordered"} 123 rowGood := []driver.Value{"1", "mock_schema", "mock_table", sql.NullString{String: "mock_args", Valid: true}, 124 sql.NullString{String: "mock_identargs", Valid: true}, 0, 0, 0, "mock_operator", "mock_operatorschema", 125 "mock_transdatatype", "mock_initvalue", false, false, false} 126 rowNullArg := []driver.Value{"2", "mock_schema2", "mock_table2", nil, sql.NullString{String: "mock_identargs2", Valid: true}, 0, 0, 0, 127 "mock_operator2", "mock_operatorschema2", "mock_transdatatype2", "mock_initvalue2", false, false, false} 128 rowNullIdentArg := []driver.Value{"3", "mock_schema3", "mock_table3", sql.NullString{String: "mock_args3", Valid: true}, nil, 0, 0, 0, 129 "mock_operator3", "mock_operatorschema3", "mock_transdatatype3", "mock_initvalue3", false, false, false} 130 fakeRows := sqlmock.NewRows(header).AddRow(rowGood...).AddRow(rowNullArg...).AddRow(rowNullIdentArg...) 131 mock.ExpectQuery(`SELECT (.*)`).WillReturnRows(fakeRows) 132 result := backup.GetAggregates(connectionPool) 133 134 // Expect the GetAggregates function to return only the 1st row since all other rows have invalid NULL strings 135 expectedResult := []backup.Aggregate{{Oid: 1, Schema: "mock_schema", Name: "mock_table", 136 Arguments: sql.NullString{String: "mock_args", Valid: true}, IdentArgs: sql.NullString{String: "mock_identargs", Valid: true}, 137 TransitionFunction: 0, PreliminaryFunction: 0, FinalFunction: 0, SortOperator: "mock_operator", 138 SortOperatorSchema: "mock_operatorschema", TransitionDataType: "mock_transdatatype", InitialValue: "mock_initvalue", 139 InitValIsNull: false, MInitValIsNull: false, IsOrdered: false}} 140 Expect(result).To(HaveLen(1)) 141 structmatcher.ExpectStructsToMatch(&expectedResult[0], &result[0]) 142 }) 143 }) 144 })