| package backup_test |
| |
| import ( |
| "database/sql" |
| "fmt" |
| |
| "github.com/apache/cloudberry-backup/backup" |
| "github.com/apache/cloudberry-backup/testutils" |
| "github.com/apache/cloudberry-backup/toc" |
| "github.com/apache/cloudberry-go-libs/testhelper" |
| |
| . "github.com/onsi/ginkgo/v2" |
| . "github.com/onsi/gomega" |
| ) |
| |
| var _ = Describe("backup/predata_functions tests", func() { |
| BeforeEach(func() { |
| tocfile, backupfile = testutils.InitializeTestTOC(buffer, "predata") |
| }) |
| Describe("Functions involved in printing CREATE FUNCTION statements", func() { |
| var funcDef backup.Function |
| var DEFAULT_PARALLEL string |
| BeforeEach(func() { |
| funcDef = backup.Function{Oid: 1, Schema: "public", Name: "func_name", ReturnsSet: false, FunctionBody: "add_two_ints", BinaryPath: "", Arguments: sql.NullString{String: "integer, integer", Valid: true}, IdentArgs: sql.NullString{String: "integer, integer", Valid: true}, ResultType: sql.NullString{String: "integer", Valid: true}, Volatility: "v", IsStrict: false, IsSecurityDefiner: false, Config: "", Cost: float32(1), NumRows: float32(0), DataAccess: "", Language: "internal", ExecLocation: "a"} |
| funcDef.Parallel = "" |
| funcDef.PlannerSupport = "" |
| DEFAULT_PARALLEL = "" |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() { |
| funcDef.Parallel = "u" |
| funcDef.PlannerSupport = "-" |
| DEFAULT_PARALLEL = " PARALLEL UNSAFE" |
| } |
| }) |
| |
| Describe("PrintCreateFunctionStatement", func() { |
| var ( |
| funcMetadata backup.ObjectMetadata |
| ) |
| BeforeEach(func() { |
| funcMetadata = backup.ObjectMetadata{} |
| }) |
| It("prints a function definition for an internal function without a binary path", func() { |
| backup.PrintCreateFunctionStatement(backupfile, tocfile, funcDef, funcMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "func_name(integer, integer)", toc.OBJ_FUNCTION) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, fmt.Sprintf(`CREATE FUNCTION public.func_name(integer, integer) RETURNS integer AS |
| $$add_two_ints$$ |
| LANGUAGE internal%s;`, DEFAULT_PARALLEL)) |
| }) |
| It("prints a function definition for a function that returns a set", func() { |
| funcDef.ReturnsSet = true |
| funcDef.ResultType = sql.NullString{String: "SETOF integer", Valid: true} |
| backup.PrintCreateFunctionStatement(backupfile, tocfile, funcDef, funcMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, fmt.Sprintf(`CREATE FUNCTION public.func_name(integer, integer) RETURNS SETOF integer AS |
| $$add_two_ints$$ |
| LANGUAGE internal%s;`, DEFAULT_PARALLEL)) |
| }) |
| It("prints a function definition for a function with permissions, an owner, security label, and a comment", func() { |
| funcMetadata := testutils.DefaultMetadata(toc.OBJ_FUNCTION, true, true, true, true) |
| backup.PrintCreateFunctionStatement(backupfile, tocfile, funcDef, funcMetadata) |
| expectedStatements := []string{fmt.Sprintf(`CREATE FUNCTION public.func_name(integer, integer) RETURNS integer AS |
| $$add_two_ints$$ |
| LANGUAGE internal%s;`, DEFAULT_PARALLEL), |
| "COMMENT ON FUNCTION public.func_name(integer, integer) IS 'This is a function comment.';", |
| "ALTER FUNCTION public.func_name(integer, integer) OWNER TO testrole;", |
| `REVOKE ALL ON FUNCTION public.func_name(integer, integer) FROM PUBLIC; |
| REVOKE ALL ON FUNCTION public.func_name(integer, integer) FROM testrole; |
| GRANT ALL ON FUNCTION public.func_name(integer, integer) TO testrole;`, |
| "SECURITY LABEL FOR dummy ON FUNCTION public.func_name(integer, integer) IS 'unclassified';"} |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...) |
| |
| }) |
| It("prints a function definition for a stored procedure", func() { |
| testutils.SkipIfBefore7(connectionPool) |
| procDef := backup.Function{Oid: 1, Schema: "public", Name: "my_procedure", Kind: "p", ReturnsSet: false, FunctionBody: "do_something", BinaryPath: "", Arguments: sql.NullString{String: "", Valid: true}, IdentArgs: sql.NullString{String: "", Valid: true}, ResultType: sql.NullString{String: "", Valid: false}, Volatility: "", IsStrict: false, IsSecurityDefiner: false, Config: "", NumRows: float32(0), DataAccess: "", Language: "SQL", ExecLocation: "a"} |
| procDef.PlannerSupport = "-" |
| backup.PrintCreateFunctionStatement(backupfile, tocfile, procDef, funcMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "my_procedure()", toc.OBJ_FUNCTION) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE PROCEDURE public.my_procedure() AS |
| $$do_something$$ |
| LANGUAGE SQL;`) |
| }) |
| }) |
| Describe("PrintFunctionBodyOrPath", func() { |
| It("prints a function definition for an internal function with 'NULL' binary path using '-'", func() { |
| funcDef.BinaryPath = "-" |
| backup.PrintFunctionBodyOrPath(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, ` |
| $$add_two_ints$$ |
| `) |
| }) |
| It("prints a function definition for an internal function with a binary path", func() { |
| funcDef.BinaryPath = "$libdir/binary" |
| backup.PrintFunctionBodyOrPath(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, ` |
| '$libdir/binary', 'add_two_ints' |
| `) |
| }) |
| It("prints a function definition for a function with a one-line function definition", func() { |
| funcDef.FunctionBody = "SELECT $1+$2" |
| funcDef.Language = "sql" |
| backup.PrintFunctionBodyOrPath(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, `$_$SELECT $1+$2$_$`) |
| }) |
| It("prints a function definition for a function with a multi-line function definition", func() { |
| funcDef.FunctionBody = ` |
| BEGIN |
| SELECT $1 + $2 |
| END |
| ` |
| funcDef.Language = "sql" |
| backup.PrintFunctionBodyOrPath(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, `$_$ |
| BEGIN |
| SELECT $1 + $2 |
| END |
| $_$`) |
| }) |
| }) |
| Describe("PrintFunctionModifiers", func() { |
| Context("SqlUsage cases", func() { |
| BeforeEach(func() { |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() { |
| Skip("Test not applicable to GP7 and above") |
| } |
| }) |
| It("prints 'c' as CONTAINS SQL", func() { |
| funcDef.DataAccess = "c" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "CONTAINS SQL") |
| }) |
| It("prints 'm' as MODIFIES SQL DATA", func() { |
| funcDef.DataAccess = "m" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "MODIFIES SQL DATA") |
| }) |
| It("prints 'n' as NO SQL", func() { |
| funcDef.DataAccess = "n" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "NO SQL") |
| }) |
| It("prints 'r' as READS SQL DATA", func() { |
| funcDef.DataAccess = "r" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "READS SQL DATA") |
| }) |
| }) |
| Context("Volatility cases", func() { |
| It("does not print anything for 'v'", func() { |
| funcDef.Volatility = "v" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.NotExpectRegexp(buffer, "STABLE") |
| testhelper.NotExpectRegexp(buffer, "IMMUTABLE") |
| }) |
| It("prints 's' as STABLE", func() { |
| funcDef.Volatility = "s" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "STABLE") |
| }) |
| It("prints 'i' as IMMUTABLE", func() { |
| funcDef.Volatility = "i" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "IMMUTABLE") |
| }) |
| }) |
| It("prints 'LEAKPROOF' if IsLeakProof is set", func() { |
| funcDef.IsLeakProof = true |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "LEAKPROOF") |
| }) |
| It("prints 'STRICT' if IsStrict is set", func() { |
| funcDef.IsStrict = true |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "STRICT") |
| }) |
| It("prints 'SECURITY DEFINER' if IsSecurityDefiner is set", func() { |
| funcDef.IsSecurityDefiner = true |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "SECURITY DEFINER") |
| }) |
| It("print 'WINDOW' if IsWindow is set", func() { |
| funcDef.IsWindow = true |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "WINDOW") |
| }) |
| It("print 'WINDOW' if Kind is 'w'", func() { |
| funcDef.Kind = "w" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "WINDOW") |
| }) |
| It("print 'TRANSFORM' block if transforms are present", func() { |
| testutils.SkipIfBefore7(connectionPool) |
| funcDef.TransformTypes = "FOR TYPE public.hstore, FOR TYPE pg_catalog.jsonb" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| Expect(string(buffer.Contents())).To(ContainSubstring("TRANSFORM FOR TYPE public.hstore, FOR TYPE pg_catalog.jsonb")) |
| }) |
| It("print 'SUPPORT' if PlannerSupport is set", func() { |
| testutils.SkipIfBefore7(connectionPool) |
| funcDef.PlannerSupport = "my_planner_support" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "SUPPORT my_planner_support") |
| }) |
| Context("Execlocation cases", func() { |
| It("Default", func() { |
| funcDef.ExecLocation = "a" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.NotExpectRegexp(buffer, "EXECUTE") |
| }) |
| It("print 'm' as EXECUTE ON MASTER", func() { |
| funcDef.ExecLocation = "m" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "EXECUTE ON MASTER") |
| }) |
| It("print 'c' as EXECUTE ON COORDINATOR", func() { |
| funcDef.ExecLocation = "c" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "EXECUTE ON COORDINATOR") |
| }) |
| It("print 's' as EXECUTE ON ALL SEGMENTS", func() { |
| funcDef.ExecLocation = "s" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "EXECUTE ON ALL SEGMENTS") |
| }) |
| It("print 'i' as EXECUTE ON INITPLAN", func() { |
| funcDef.ExecLocation = "i" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "EXECUTE ON INITPLAN") |
| }) |
| }) |
| Context("Cost cases", func() { |
| /* |
| * The default COST values are 1 for C and internal functions and |
| * 100 for any other language, so it should not print COST clauses |
| * for those values but print any other COST. |
| */ |
| It("prints 'COST 5' if Cost is set to 5", func() { |
| funcDef.Cost = 5 |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "COST 5") |
| }) |
| It("prints 'COST 1' if Cost is set to 1 and language is not c or internal", func() { |
| funcDef.Cost = 1 |
| funcDef.Language = "sql" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "COST 1") |
| }) |
| It("does not print 'COST 1' if Cost is set to 1 and language is c", func() { |
| funcDef.Cost = 1 |
| funcDef.Language = "c" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.NotExpectRegexp(buffer, "COST") |
| }) |
| It("does not print 'COST 1' if Cost is set to 1 and language is internal", func() { |
| funcDef.Cost = 1 |
| funcDef.Language = "internal" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.NotExpectRegexp(buffer, "COST") |
| }) |
| It("prints 'COST 100' if Cost is set to 100 and language is c", func() { |
| funcDef.Cost = 100 |
| funcDef.Language = "c" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "COST 100") |
| }) |
| It("prints 'COST 100' if Cost is set to 100 and language is internal", func() { |
| funcDef.Cost = 100 |
| funcDef.Language = "internal" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "COST 100") |
| }) |
| It("does not print 'COST 100' if Cost is set to 100 and language is not c or internal", func() { |
| funcDef.Cost = 100 |
| funcDef.Language = "sql" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.NotExpectRegexp(buffer, "COST 100") |
| }) |
| }) |
| Context("NumRows cases", func() { |
| /* |
| * A ROWS value of 0 means "no estimate" and 1000 means "too high |
| * to estimate", so those should not be printed but any other ROWS |
| * value should be. |
| */ |
| It("prints 'ROWS 5' if Rows is set to 5", func() { |
| funcDef.NumRows = 5 |
| funcDef.ReturnsSet = true |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "ROWS 5") |
| }) |
| It("does not print 'ROWS' if Rows is set but ReturnsSet is false", func() { |
| funcDef.NumRows = 100 |
| funcDef.ReturnsSet = false |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.NotExpectRegexp(buffer, "ROWS") |
| }) |
| It("does not print 'ROWS' if Rows is set to 0", func() { |
| funcDef.NumRows = 0 |
| funcDef.ReturnsSet = true |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.NotExpectRegexp(buffer, "ROWS") |
| }) |
| It("does not print 'ROWS' if Rows is set to 1000", func() { |
| funcDef.NumRows = 1000 |
| funcDef.ReturnsSet = true |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.NotExpectRegexp(buffer, "ROWS") |
| }) |
| }) |
| It("prints config statements if any are set", func() { |
| funcDef.Config = "SET client_min_messages TO error" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "SET client_min_messages TO error") |
| }) |
| Context("Parallel cases", func() { |
| It("prints 'u' as 'PARALLEL UNSAFE'", func() { |
| testutils.SkipIfBefore7(connectionPool) |
| funcDef.Parallel = "u" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "PARALLEL UNSAFE") |
| }) |
| It("prints 's' as 'PARALLEL SAFE'", func() { |
| testutils.SkipIfBefore7(connectionPool) |
| funcDef.Parallel = "s" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "PARALLEL SAFE") |
| }) |
| It("prints 'r' as 'PARALLEL RESTRICTED'", func() { |
| testutils.SkipIfBefore7(connectionPool) |
| funcDef.Parallel = "r" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| testhelper.ExpectRegexp(buffer, "PARALLEL RESTRICTED") |
| }) |
| It("panics is there is an unrecognized parallel value", func() { |
| testutils.SkipIfBefore7(connectionPool) |
| defer testhelper.ShouldPanicWithMessage("unrecognized proparallel value for function public.func_name") |
| funcDef.Parallel = "unknown_value" |
| backup.PrintFunctionModifiers(backupfile, funcDef) |
| }) |
| }) |
| |
| }) |
| |
| }) |
| Describe("PrintCreateAggregateStatement", func() { |
| var ( |
| aggDefinition backup.Aggregate |
| emptyMetadata backup.ObjectMetadata |
| aggMetadata backup.ObjectMetadata |
| ) |
| funcInfoMap := map[uint32]backup.FunctionInfo{ |
| 1: {QualifiedName: "public.mysfunc", Arguments: sql.NullString{String: "integer", Valid: true}}, |
| 2: {QualifiedName: "public.mypfunc", Arguments: sql.NullString{String: "numeric, numeric", Valid: true}}, |
| 3: {QualifiedName: "public.myffunc", Arguments: sql.NullString{String: "text", Valid: true}}, |
| 4: {QualifiedName: "pg_catalog.ordered_set_transition_multi", Arguments: sql.NullString{String: `internal, VARIADIC "any"`, Valid: true}}, |
| 5: {QualifiedName: "pg_catalog.rank_final", Arguments: sql.NullString{String: `internal, VARIADIC "any"`, Valid: true}}, |
| } |
| BeforeEach(func() { |
| aggDefinition = backup.Aggregate{Oid: 1, Schema: "public", Name: "agg_name", Arguments: sql.NullString{String: "integer, integer", Valid: true}, IdentArgs: sql.NullString{String: "integer, integer", Valid: true}, TransitionFunction: 1, TransitionDataType: "integer", InitValIsNull: true, MInitValIsNull: true} |
| emptyMetadata = backup.ObjectMetadata{} |
| aggMetadata = testutils.DefaultMetadata(toc.OBJ_AGGREGATE, true, true, true, true) |
| }) |
| |
| It("prints an aggregate definition for an unordered aggregate with no optional specifications", func() { |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "agg_name(integer, integer)", toc.OBJ_AGGREGATE) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer |
| );`) |
| }) |
| It("prints an aggregate definition for an ordered aggregate with no optional specifications", func() { |
| aggDefinition.IsOrdered = true |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE ORDERED AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer |
| );`) |
| }) |
| It("prints an aggregate definition for an unordered aggregate with no arguments", func() { |
| aggDefinition.Arguments = sql.NullString{String: "", Valid: true} |
| aggDefinition.IdentArgs = sql.NullString{String: "", Valid: true} |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(*) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer |
| );`) |
| }) |
| It("prints an aggregate with a preliminary function", func() { |
| aggDefinition.PreliminaryFunction = 2 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| PREFUNC = public.mypfunc |
| );`) |
| }) |
| It("prints an aggregate with a combine function", func() { |
| aggDefinition.CombineFunction = 2 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| COMBINEFUNC = public.mypfunc |
| );`) |
| }) |
| It("prints an aggregate with a serial function", func() { |
| aggDefinition.SerialFunction = 2 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| SERIALFUNC = public.mypfunc |
| );`) |
| }) |
| It("prints an aggregate with a deserial function", func() { |
| aggDefinition.DeserialFunction = 2 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| DESERIALFUNC = public.mypfunc |
| );`) |
| }) |
| It("prints an aggregate with a final function", func() { |
| aggDefinition.FinalFunction = 3 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| FINALFUNC = public.myffunc |
| );`) |
| }) |
| It("prints an aggregate with a final function extra attribute", func() { |
| aggDefinition.FinalFuncExtra = true |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| FINALFUNC_EXTRA |
| );`) |
| }) |
| It("prints an aggregate with an initial condition", func() { |
| aggDefinition.InitialValue = "0" |
| aggDefinition.InitValIsNull = false |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| INITCOND = '0' |
| );`) |
| }) |
| It("prints an aggregate with a sort operator", func() { |
| aggDefinition.SortOperator = "+" |
| aggDefinition.SortOperatorSchema = "myschema" |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| SORTOP = myschema."+" |
| );`) |
| }) |
| It("prints an aggregate with a specified transition data size", func() { |
| aggDefinition.TransitionDataSize = 1000 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "agg_name(integer, integer)", toc.OBJ_AGGREGATE) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| SSPACE = 1000 |
| );`) |
| }) |
| It("prints an aggregate with a specified moving transition function", func() { |
| aggDefinition.MTransitionFunction = 1 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "agg_name(integer, integer)", toc.OBJ_AGGREGATE) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| MSFUNC = public.mysfunc |
| );`) |
| }) |
| It("prints an aggregate with a specified moving inverse transition function", func() { |
| aggDefinition.MInverseTransitionFunction = 1 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "agg_name(integer, integer)", toc.OBJ_AGGREGATE) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| MINVFUNC = public.mysfunc |
| );`) |
| }) |
| It("prints an aggregate with a specified moving state type", func() { |
| aggDefinition.MTransitionDataType = "numeric" |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "agg_name(integer, integer)", toc.OBJ_AGGREGATE) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| MSTYPE = numeric |
| );`) |
| }) |
| It("prints an aggregate with a specified moving transition size", func() { |
| aggDefinition.MTransitionDataSize = 100 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "agg_name(integer, integer)", toc.OBJ_AGGREGATE) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| MSSPACE = 100 |
| );`) |
| }) |
| It("prints an aggregate with a specified moving final function", func() { |
| aggDefinition.MFinalFunction = 3 |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "agg_name(integer, integer)", toc.OBJ_AGGREGATE) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| MFINALFUNC = public.myffunc |
| );`) |
| }) |
| It("prints an aggregate with a moving final function extra attribute", func() { |
| aggDefinition.MFinalFuncExtra = true |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "agg_name(integer, integer)", toc.OBJ_AGGREGATE) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| MFINALFUNC_EXTRA |
| );`) |
| }) |
| It("prints an aggregate with a moving initial condition", func() { |
| aggDefinition.MInitialValue = "0" |
| aggDefinition.MInitValIsNull = false |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| MINITCOND = '0' |
| );`) |
| }) |
| It("prints an aggregate with multiple specifications", func() { |
| aggDefinition.FinalFunction = 3 |
| aggDefinition.SortOperator = "~>~" |
| aggDefinition.SortOperatorSchema = "myschema" |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| FINALFUNC = public.myffunc, |
| SORTOP = myschema."~>~" |
| );`) |
| }) |
| It("prints a hypothetical ordered-set aggregate", func() { |
| complexAggDefinition := backup.Aggregate{ |
| Schema: "public", Name: "agg_hypo_ord", Arguments: sql.NullString{String: `VARIADIC "any" ORDER BY VARIADIC "any"`, Valid: true}, |
| IdentArgs: sql.NullString{String: `VARIADIC "any" ORDER BY VARIADIC "any"`, Valid: true}, TransitionFunction: 4, FinalFunction: 5, |
| TransitionDataType: "internal", InitValIsNull: true, MInitValIsNull: true, FinalFuncExtra: true, |
| } |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() { |
| complexAggDefinition.Kind = "h" |
| } else { |
| complexAggDefinition.Hypothetical = true |
| } |
| aggDefinition = complexAggDefinition |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_hypo_ord(VARIADIC "any" ORDER BY VARIADIC "any") ( |
| SFUNC = pg_catalog.ordered_set_transition_multi, |
| STYPE = internal, |
| FINALFUNC = pg_catalog.rank_final, |
| FINALFUNC_EXTRA, |
| HYPOTHETICAL |
| );`) |
| }) |
| It("prints an aggregate with owner, security label and comment", func() { |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, aggMetadata) |
| expectedStatements := []string{ |
| `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer |
| );`, "COMMENT ON AGGREGATE public.agg_name(integer, integer) IS 'This is an aggregate comment.';", |
| "ALTER AGGREGATE public.agg_name(integer, integer) OWNER TO testrole;", |
| `REVOKE ALL ON FUNCTION public.agg_name(integer, integer) FROM PUBLIC; |
| REVOKE ALL ON FUNCTION public.agg_name(integer, integer) FROM testrole; |
| GRANT ALL ON FUNCTION public.agg_name(integer, integer) TO testrole;`, |
| "SECURITY LABEL FOR dummy ON AGGREGATE public.agg_name(integer, integer) IS 'unclassified';"} |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...) |
| }) |
| It("prints an aggregate with owner, comment, and no arguments", func() { |
| aggDefinition.Arguments = sql.NullString{String: "", Valid: true} |
| aggDefinition.IdentArgs = sql.NullString{String: "", Valid: true} |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, aggMetadata) |
| expectedStatements := []string{ |
| `CREATE AGGREGATE public.agg_name(*) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer |
| );`, |
| "COMMENT ON AGGREGATE public.agg_name(*) IS 'This is an aggregate comment.';", |
| "ALTER AGGREGATE public.agg_name(*) OWNER TO testrole;", |
| `REVOKE ALL ON FUNCTION public.agg_name(*) FROM PUBLIC; |
| REVOKE ALL ON FUNCTION public.agg_name(*) FROM testrole; |
| GRANT ALL ON FUNCTION public.agg_name(*) TO testrole;`, |
| "SECURITY LABEL FOR dummy ON AGGREGATE public.agg_name(*) IS 'unclassified';"} |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...) |
| }) |
| It("prints an aggregate definition with parallel safe modifier", func() { |
| aggDefinition.Parallel = "s" |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "agg_name(integer, integer)", toc.OBJ_AGGREGATE) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE AGGREGATE public.agg_name(integer, integer) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer, |
| PARALLEL = SAFE |
| );`) |
| }) |
| DescribeTable("prints aggregate with aggfinalmodify or aggmfinalmodify", |
| func(kind string, finalMod string, mfinalMod string, expected string) { |
| testutils.SkipIfBefore7(connectionPool) |
| aggDefinition = backup.Aggregate{Oid: 1, Schema: "public", Name: "agg_name", Arguments: sql.NullString{String: "", Valid: true}, IdentArgs: sql.NullString{String: "", Valid: true}, TransitionFunction: 1, TransitionDataType: "integer", InitValIsNull: true, MInitValIsNull: true} |
| aggDefinition.Kind = kind |
| aggDefinition.Finalmodify = finalMod |
| aggDefinition.Mfinalmodify = mfinalMod |
| backup.PrintCreateAggregateStatement(backupfile, tocfile, aggDefinition, funcInfoMap, aggMetadata) |
| expectedStatements := []string{ |
| fmt.Sprintf(`CREATE AGGREGATE public.agg_name(*) ( |
| SFUNC = public.mysfunc, |
| STYPE = integer%s |
| );`, expected), |
| "COMMENT ON AGGREGATE public.agg_name(*) IS 'This is an aggregate comment.';", |
| "ALTER AGGREGATE public.agg_name(*) OWNER TO testrole;", |
| "REVOKE ALL ON FUNCTION public.agg_name(*) FROM PUBLIC;\nREVOKE ALL ON FUNCTION public.agg_name(*) FROM testrole;\nGRANT ALL ON FUNCTION public.agg_name(*) TO testrole;", |
| "SECURITY LABEL FOR dummy ON AGGREGATE public.agg_name(*) IS 'unclassified';"} |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...) |
| }, |
| Entry("kind: n, aggfinalmodify: r", "n", "r", "", ""), // default, don't print |
| Entry("kind: n, aggfinalmodify: s", "n", "s", "", ",\n\tFINALFUNC_MODIFY = SHAREABLE"), |
| Entry("kind: n, aggfinalmodify: w", "n", "w", "", ",\n\tFINALFUNC_MODIFY = READ_WRITE"), |
| Entry("kind: o or h, aggfinalmodify: r", "o", "r", "", ",\n\tFINALFUNC_MODIFY = READ_ONLY"), |
| Entry("kind: o or h, aggfinalmodify: s", "o", "s", "", ",\n\tFINALFUNC_MODIFY = SHAREABLE"), |
| Entry("kind: o or h, aggfinalmodify: w", "o", "w", "", ""), // default, don't print |
| |
| Entry("kind: n, aggmfinalmodify: r", "n", "", "r", ""), // default, don't print |
| Entry("kind: n, aggmfinalmodify: s", "n", "", "s", ",\n\tMFINALFUNC_MODIFY = SHAREABLE"), |
| Entry("kind: n, aggmfinalmodify: w", "n", "", "w", ",\n\tMFINALFUNC_MODIFY = READ_WRITE"), |
| Entry("kind: o or h, aggmfinalmodify: r", "o", "", "r", ",\n\tMFINALFUNC_MODIFY = READ_ONLY"), |
| Entry("kind: o or h, aggmfinalmodify: s", "o", "", "s", ",\n\tMFINALFUNC_MODIFY = SHAREABLE"), |
| Entry("kind: o or h, aggmfinalmodify: w", "o", "", "w", ""), // default, don't print |
| ) |
| }) |
| Describe("PrintCreateCastStatement", func() { |
| emptyMetadata := backup.ObjectMetadata{} |
| It("prints an explicit cast with a function", func() { |
| castDef := backup.Cast{Oid: 1, SourceTypeFQN: "src", TargetTypeFQN: "dst", FunctionSchema: "public", FunctionName: "cast_func", FunctionArgs: "integer, integer", CastContext: "e", CastMethod: "f"} |
| backup.PrintCreateCastStatement(backupfile, tocfile, castDef, emptyMetadata) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "(src AS dst)", toc.OBJ_CAST) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE CAST (src AS dst) |
| WITH FUNCTION public.cast_func(integer, integer);`) |
| }) |
| It("prints an implicit cast with a function", func() { |
| castDef := backup.Cast{Oid: 1, SourceTypeFQN: "src", TargetTypeFQN: "dst", FunctionSchema: "public", FunctionName: "cast_func", FunctionArgs: "integer, integer", CastContext: "i", CastMethod: "f"} |
| backup.PrintCreateCastStatement(backupfile, tocfile, castDef, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE CAST (src AS dst) |
| WITH FUNCTION public.cast_func(integer, integer) |
| AS IMPLICIT;`) |
| }) |
| It("prints an assignment cast with a function", func() { |
| castDef := backup.Cast{Oid: 1, SourceTypeFQN: "src", TargetTypeFQN: "dst", FunctionSchema: "public", FunctionName: "cast_func", FunctionArgs: "integer, integer", CastContext: "a", CastMethod: "f"} |
| backup.PrintCreateCastStatement(backupfile, tocfile, castDef, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE CAST (src AS dst) |
| WITH FUNCTION public.cast_func(integer, integer) |
| AS ASSIGNMENT;`) |
| }) |
| It("prints an explicit cast without a function", func() { |
| castDef := backup.Cast{Oid: 1, SourceTypeFQN: "src", TargetTypeFQN: "dst", FunctionSchema: "", FunctionName: "", FunctionArgs: "", CastContext: "e", CastMethod: "b"} |
| backup.PrintCreateCastStatement(backupfile, tocfile, castDef, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE CAST (src AS dst) |
| WITHOUT FUNCTION;`) |
| }) |
| It("prints an implicit cast without a function", func() { |
| castDef := backup.Cast{Oid: 1, SourceTypeFQN: "src", TargetTypeFQN: "dst", FunctionSchema: "", FunctionName: "", FunctionArgs: "", CastContext: "i", CastMethod: "b"} |
| backup.PrintCreateCastStatement(backupfile, tocfile, castDef, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE CAST (src AS dst) |
| WITHOUT FUNCTION |
| AS IMPLICIT;`) |
| }) |
| It("prints an assignment cast without a function", func() { |
| castDef := backup.Cast{Oid: 1, SourceTypeFQN: "src", TargetTypeFQN: "dst", FunctionSchema: "", FunctionName: "", FunctionArgs: "", CastContext: "a", CastMethod: "b"} |
| backup.PrintCreateCastStatement(backupfile, tocfile, castDef, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE CAST (src AS dst) |
| WITHOUT FUNCTION |
| AS ASSIGNMENT;`) |
| }) |
| It("prints an inout cast", func() { |
| castDef := backup.Cast{Oid: 1, SourceTypeFQN: "src", TargetTypeFQN: "dst", FunctionSchema: "", FunctionName: "", FunctionArgs: "", CastContext: "e", CastMethod: "i"} |
| backup.PrintCreateCastStatement(backupfile, tocfile, castDef, emptyMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE CAST (src AS dst) |
| WITH INOUT;`) |
| }) |
| It("prints a cast with a comment", func() { |
| castDef := backup.Cast{Oid: 1, SourceTypeFQN: "src", TargetTypeFQN: "dst", FunctionSchema: "", FunctionName: "", FunctionArgs: "", CastContext: "e", CastMethod: "b"} |
| castMetadata := testutils.DefaultMetadata(toc.OBJ_CAST, false, false, true, false) |
| backup.PrintCreateCastStatement(backupfile, tocfile, castDef, castMetadata) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE CAST (src AS dst) |
| WITHOUT FUNCTION;`, "COMMENT ON CAST (src AS dst) IS 'This is a cast comment.';") |
| }) |
| }) |
| Describe("PrintCreateExtensionStatement", func() { |
| emptyMetadataMap := backup.MetadataMap{} |
| It("prints a create extension statement", func() { |
| extensionDef := backup.Extension{Oid: 1, Name: "extension1", Schema: "schema1"} |
| backup.PrintCreateExtensionStatements(backupfile, tocfile, []backup.Extension{extensionDef}, emptyMetadataMap) |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() { |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE SCHEMA IF NOT EXISTS schema1; |
| SET search_path=schema1,pg_catalog; |
| CREATE EXTENSION IF NOT EXISTS extension1 WITH SCHEMA schema1; |
| SET search_path=pg_catalog;`) |
| } else { |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `SET search_path=schema1,pg_catalog; |
| CREATE EXTENSION IF NOT EXISTS extension1 WITH SCHEMA schema1; |
| SET search_path=pg_catalog;`) |
| } |
| }) |
| It("prints a create extension statement with a comment", func() { |
| extensionDef := backup.Extension{Oid: 1, Name: "extension1", Schema: "schema1"} |
| extensionMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_EXTENSION, false, false, true, false) |
| backup.PrintCreateExtensionStatements(backupfile, tocfile, []backup.Extension{extensionDef}, extensionMetadataMap) |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() { |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE SCHEMA IF NOT EXISTS schema1; |
| SET search_path=schema1,pg_catalog; |
| CREATE EXTENSION IF NOT EXISTS extension1 WITH SCHEMA schema1; |
| SET search_path=pg_catalog;`, "COMMENT ON EXTENSION extension1 IS 'This is an extension comment.';") |
| } else { |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `SET search_path=schema1,pg_catalog; |
| CREATE EXTENSION IF NOT EXISTS extension1 WITH SCHEMA schema1; |
| SET search_path=pg_catalog;`, "COMMENT ON EXTENSION extension1 IS 'This is an extension comment.';") |
| } |
| }) |
| }) |
| Describe("ExtractLanguageFunctions", func() { |
| customLang1 := backup.ProceduralLanguage{Oid: 1, Name: "custom_language", Owner: "testrole", IsPl: true, PlTrusted: true, Handler: 3, Inline: 4, Validator: 5} |
| customLang2 := backup.ProceduralLanguage{Oid: 2, Name: "custom_language2", Owner: "testrole", IsPl: true, PlTrusted: true, Handler: 5, Inline: 6, Validator: 7} |
| procLangs := []backup.ProceduralLanguage{customLang1, customLang2} |
| langFunc := backup.Function{Oid: 3, Name: "custom_handler"} |
| nonLangFunc := backup.Function{Oid: 2, Name: "random_function"} |
| It("handles a case where all functions are language-associated functions", func() { |
| funcDefs := []backup.Function{langFunc} |
| langFuncs, otherFuncs := backup.ExtractLanguageFunctions(funcDefs, procLangs) |
| Expect(langFuncs).To(HaveLen(1)) |
| Expect(otherFuncs).To(BeEmpty()) |
| Expect(langFuncs[0].Name).To(Equal("custom_handler")) |
| }) |
| It("handles a case where no functions are language-associated functions", func() { |
| funcDefs := []backup.Function{nonLangFunc} |
| langFuncs, otherFuncs := backup.ExtractLanguageFunctions(funcDefs, procLangs) |
| Expect(langFuncs).To(BeEmpty()) |
| Expect(otherFuncs).To(HaveLen(1)) |
| Expect(otherFuncs[0].Name).To(Equal("random_function")) |
| }) |
| It("handles a case where some functions are language-associated functions", func() { |
| funcDefs := []backup.Function{langFunc, nonLangFunc} |
| langFuncs, otherFuncs := backup.ExtractLanguageFunctions(funcDefs, procLangs) |
| Expect(langFuncs).To(HaveLen(1)) |
| Expect(otherFuncs).To(HaveLen(1)) |
| Expect(langFuncs[0].Name).To(Equal("custom_handler")) |
| Expect(otherFuncs[0].Name).To(Equal("random_function")) |
| }) |
| }) |
| Describe("PrintCreateLanguageStatements", func() { |
| plUntrustedHandlerOnly := backup.ProceduralLanguage{Oid: 1, Name: "plpythonu", Owner: "testrole", IsPl: true, PlTrusted: false, Handler: 4, Inline: 0, Validator: 0} |
| plAllFields := backup.ProceduralLanguage{Oid: 1, Name: "plperl", Owner: "testrole", IsPl: true, PlTrusted: true, Handler: 1, Inline: 2, Validator: 3} |
| plComment := backup.ProceduralLanguage{Oid: 1, Name: "plpythonu", Owner: "testrole", IsPl: true, PlTrusted: false, Handler: 4, Inline: 0, Validator: 0} |
| funcInfoMap := map[uint32]backup.FunctionInfo{ |
| 1: {QualifiedName: "pg_catalog.plperl_call_handler", Arguments: sql.NullString{String: "", Valid: true}, IsInternal: true}, |
| 2: {QualifiedName: "pg_catalog.plperl_inline_handler", Arguments: sql.NullString{String: "internal", Valid: true}, IsInternal: true}, |
| 3: {QualifiedName: "pg_catalog.plperl_validator", Arguments: sql.NullString{String: "oid", Valid: true}, IsInternal: true}, |
| 4: {QualifiedName: "pg_catalog.plpython_call_handler", Arguments: sql.NullString{String: "", Valid: true}, IsInternal: true}, |
| } |
| emptyMetadataMap := backup.MetadataMap{} |
| |
| It("prints untrusted language with a handler only", func() { |
| langs := []backup.ProceduralLanguage{plUntrustedHandlerOnly} |
| |
| backup.PrintCreateLanguageStatements(backupfile, tocfile, langs, funcInfoMap, emptyMetadataMap) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "plpythonu", toc.OBJ_LANGUAGE) |
| |
| createStatement1 := "CREATE PROCEDURAL LANGUAGE plpythonu HANDLER pg_catalog.plpython_call_handler;" |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("6")) || connectionPool.Version.IsCBDB() { |
| createStatement1 = "CREATE OR REPLACE PROCEDURAL LANGUAGE plpythonu HANDLER pg_catalog.plpython_call_handler;" |
| } |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, createStatement1, "ALTER FUNCTION pg_catalog.plpython_call_handler() OWNER TO testrole;") |
| }) |
| It("prints trusted language with handler, inline, and validator", func() { |
| langs := []backup.ProceduralLanguage{plAllFields} |
| |
| backup.PrintCreateLanguageStatements(backupfile, tocfile, langs, funcInfoMap, emptyMetadataMap) |
| |
| createStatement1 := "CREATE TRUSTED PROCEDURAL LANGUAGE plperl HANDLER pg_catalog.plperl_call_handler INLINE pg_catalog.plperl_inline_handler VALIDATOR pg_catalog.plperl_validator;" |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("6")) || connectionPool.Version.IsCBDB() { |
| createStatement1 = "CREATE OR REPLACE TRUSTED PROCEDURAL LANGUAGE plperl HANDLER pg_catalog.plperl_call_handler INLINE pg_catalog.plperl_inline_handler VALIDATOR pg_catalog.plperl_validator;" |
| } |
| |
| expectedStatements := []string{ |
| createStatement1, |
| `ALTER FUNCTION pg_catalog.plperl_call_handler() OWNER TO testrole; |
| ALTER FUNCTION pg_catalog.plperl_inline_handler(internal) OWNER TO testrole; |
| ALTER FUNCTION pg_catalog.plperl_validator(oid) OWNER TO testrole;`, |
| } |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...) |
| }) |
| It("prints multiple create language statements", func() { |
| langs := []backup.ProceduralLanguage{plUntrustedHandlerOnly, plAllFields} |
| |
| backup.PrintCreateLanguageStatements(backupfile, tocfile, langs, funcInfoMap, emptyMetadataMap) |
| |
| createStatement1 := "CREATE PROCEDURAL LANGUAGE plpythonu HANDLER pg_catalog.plpython_call_handler;" |
| createStatement2 := "CREATE TRUSTED PROCEDURAL LANGUAGE plperl HANDLER pg_catalog.plperl_call_handler INLINE pg_catalog.plperl_inline_handler VALIDATOR pg_catalog.plperl_validator;" |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("6")) || connectionPool.Version.IsCBDB() { |
| createStatement1 = "CREATE OR REPLACE PROCEDURAL LANGUAGE plpythonu HANDLER pg_catalog.plpython_call_handler;" |
| createStatement2 = "CREATE OR REPLACE TRUSTED PROCEDURAL LANGUAGE plperl HANDLER pg_catalog.plperl_call_handler INLINE pg_catalog.plperl_inline_handler VALIDATOR pg_catalog.plperl_validator;" |
| } |
| expectedStatements := []string{ |
| createStatement1, |
| "ALTER FUNCTION pg_catalog.plpython_call_handler() OWNER TO testrole;", |
| createStatement2, |
| `ALTER FUNCTION pg_catalog.plperl_call_handler() OWNER TO testrole; |
| ALTER FUNCTION pg_catalog.plperl_inline_handler(internal) OWNER TO testrole; |
| ALTER FUNCTION pg_catalog.plperl_validator(oid) OWNER TO testrole;`, |
| } |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...) |
| }) |
| It("prints a language with privileges, an owner, security label, and a comment", func() { |
| langs := []backup.ProceduralLanguage{plComment} |
| langMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_LANGUAGE, true, true, true, true) |
| |
| backup.PrintCreateLanguageStatements(backupfile, tocfile, langs, funcInfoMap, langMetadataMap) |
| |
| createStatement1 := "CREATE PROCEDURAL LANGUAGE plpythonu HANDLER pg_catalog.plpython_call_handler;" |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("6")) || connectionPool.Version.IsCBDB() { |
| createStatement1 = "CREATE OR REPLACE PROCEDURAL LANGUAGE plpythonu HANDLER pg_catalog.plpython_call_handler;" |
| } |
| |
| expectedStatements := []string{ |
| createStatement1, |
| "ALTER FUNCTION pg_catalog.plpython_call_handler() OWNER TO testrole;", |
| "COMMENT ON LANGUAGE plpythonu IS 'This is a language comment.';", |
| } |
| |
| expectedStatements = append(expectedStatements, "ALTER LANGUAGE plpythonu OWNER TO testrole;") |
| expectedStatements = append(expectedStatements, `REVOKE ALL ON LANGUAGE plpythonu FROM PUBLIC; |
| REVOKE ALL ON LANGUAGE plpythonu FROM testrole; |
| GRANT ALL ON LANGUAGE plpythonu TO testrole;`, |
| "SECURITY LABEL FOR dummy ON LANGUAGE plpythonu IS 'unclassified';") |
| |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...) |
| }) |
| It("prints a language using a role with % in its name", func() { |
| langWithValidatorAndPercentOwner := backup.ProceduralLanguage{Oid: 1, Name: "plperl", Owner: "owner%percentage", IsPl: true, PlTrusted: true, Handler: 1, Inline: 2, Validator: 3} |
| langs := []backup.ProceduralLanguage{langWithValidatorAndPercentOwner} |
| |
| langMetadataMap := testutils.DefaultMetadataMap(toc.OBJ_LANGUAGE, true, true, true, true) |
| |
| backup.PrintCreateLanguageStatements(backupfile, tocfile, langs, funcInfoMap, langMetadataMap) |
| |
| createStatement1 := "CREATE TRUSTED PROCEDURAL LANGUAGE plperl HANDLER pg_catalog.plperl_call_handler INLINE pg_catalog.plperl_inline_handler VALIDATOR pg_catalog.plperl_validator;" |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("6")) || connectionPool.Version.IsCBDB() { |
| createStatement1 = "CREATE OR REPLACE TRUSTED PROCEDURAL LANGUAGE plperl HANDLER pg_catalog.plperl_call_handler INLINE pg_catalog.plperl_inline_handler VALIDATOR pg_catalog.plperl_validator;" |
| } |
| expectedStatements := []string{ |
| createStatement1, |
| "ALTER FUNCTION pg_catalog.plperl_call_handler() OWNER TO owner%percentage;\nALTER FUNCTION pg_catalog.plperl_inline_handler(internal) OWNER TO owner%percentage;\nALTER FUNCTION pg_catalog.plperl_validator(oid) OWNER TO owner%percentage;", |
| `COMMENT ON LANGUAGE plperl IS 'This is a language comment.';`, |
| } |
| |
| expectedStatements = append(expectedStatements, `ALTER LANGUAGE plperl OWNER TO testrole;`) |
| expectedStatements = append(expectedStatements, `REVOKE ALL ON LANGUAGE plperl FROM PUBLIC; |
| REVOKE ALL ON LANGUAGE plperl FROM testrole; |
| GRANT ALL ON LANGUAGE plperl TO testrole;`, |
| "SECURITY LABEL FOR dummy ON LANGUAGE plperl IS 'unclassified';") |
| |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...) |
| }) |
| }) |
| Describe("PrintCreateTransformStatement", func() { |
| funcInfoMap := map[uint32]backup.FunctionInfo{ |
| 1: {QualifiedName: "somenamespace.from_sql_f", IdentArgs: sql.NullString{String: "internal", Valid: true}}, |
| 2: {QualifiedName: "somenamespace.to_sql_f", IdentArgs: sql.NullString{String: "internal", Valid: true}}, |
| } |
| |
| DescribeTable("prints transform statements with at least one transform function", func(fromSql uint32, toSql uint32, expected string) { |
| testutils.SkipIfBefore7(connectionPool) |
| transform := backup.Transform{Oid: 1, TypeNamespace: "mynamespace", TypeName: "mytype", LanguageName: "somelang", FromSQLFunc: fromSql, ToSQLFunc: toSql} |
| transMetadata := testutils.DefaultMetadata(toc.OBJ_TRANSFORM, false, false, false, false) |
| backup.PrintCreateTransformStatement(backupfile, tocfile, transform, funcInfoMap, transMetadata) |
| expectedStatements := []string{fmt.Sprintf(`CREATE TRANSFORM FOR mynamespace.mytype LANGUAGE somelang %s;`, expected)} |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, expectedStatements...) |
| }, |
| Entry("both functions are specified", uint32(1), uint32(2), "(FROM SQL WITH FUNCTION somenamespace.from_sql_f(internal), TO SQL WITH FUNCTION somenamespace.to_sql_f(internal))"), |
| Entry("only fromSQL function is specified", uint32(1), uint32(0), "(FROM SQL WITH FUNCTION somenamespace.from_sql_f(internal))"), |
| Entry("only toSql function is specified", uint32(0), uint32(2), "(TO SQL WITH FUNCTION somenamespace.to_sql_f(internal))"), |
| ) |
| It("prints a warning if there are no transform functions specified", func() { |
| testutils.SkipIfBefore7(connectionPool) |
| _, _, logfile = testhelper.SetupTestLogger() |
| transform := backup.Transform{Oid: 1, TypeNamespace: "mynamespace", TypeName: "mycustomtype", LanguageName: "someproclanguage", FromSQLFunc: 0, ToSQLFunc: 0} |
| transMetadata := testutils.DefaultMetadata(toc.OBJ_TRANSFORM, false, false, false, false) |
| backup.PrintCreateTransformStatement(backupfile, tocfile, transform, funcInfoMap, transMetadata) |
| testhelper.ExpectRegexp(logfile, "[WARNING]:-Skipping invalid transform object for type mynamespace.mycustomtype and language someproclanguage; At least one of FROM and TO functions should be specified") |
| }) |
| }) |
| |
| Describe("PrintCreateConversionStatements", func() { |
| var ( |
| convOne backup.Conversion |
| convTwo backup.Conversion |
| metadataMap backup.MetadataMap |
| ) |
| BeforeEach(func() { |
| convOne = backup.Conversion{Oid: 1, Schema: "public", Name: "conv_one", ForEncoding: "UTF8", ToEncoding: "LATIN1", ConversionFunction: "public.converter", IsDefault: false} |
| convTwo = backup.Conversion{Oid: 0, Schema: "public", Name: "conv_two", ForEncoding: "UTF8", ToEncoding: "LATIN1", ConversionFunction: "public.converter", IsDefault: true} |
| metadataMap = backup.MetadataMap{} |
| }) |
| |
| It("prints a non-default conversion", func() { |
| conversions := []backup.Conversion{convOne} |
| backup.PrintCreateConversionStatements(backupfile, tocfile, conversions, metadataMap) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "public", "", "conv_one", toc.OBJ_CONVERSION) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE CONVERSION public.conv_one FOR 'UTF8' TO 'LATIN1' FROM public.converter;`) |
| }) |
| It("prints a default conversion", func() { |
| conversions := []backup.Conversion{convTwo} |
| backup.PrintCreateConversionStatements(backupfile, tocfile, conversions, metadataMap) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE DEFAULT CONVERSION public.conv_two FOR 'UTF8' TO 'LATIN1' FROM public.converter;`) |
| }) |
| It("prints multiple create conversion statements", func() { |
| conversions := []backup.Conversion{convOne, convTwo} |
| backup.PrintCreateConversionStatements(backupfile, tocfile, conversions, metadataMap) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, |
| `CREATE CONVERSION public.conv_one FOR 'UTF8' TO 'LATIN1' FROM public.converter;`, |
| `CREATE DEFAULT CONVERSION public.conv_two FOR 'UTF8' TO 'LATIN1' FROM public.converter;`) |
| }) |
| It("prints a conversion with an owner and a comment", func() { |
| conversions := []backup.Conversion{convOne} |
| metadataMap = testutils.DefaultMetadataMap(toc.OBJ_CONVERSION, false, true, true, false) |
| backup.PrintCreateConversionStatements(backupfile, tocfile, conversions, metadataMap) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, "CREATE CONVERSION public.conv_one FOR 'UTF8' TO 'LATIN1' FROM public.converter;", |
| "COMMENT ON CONVERSION public.conv_one IS 'This is a conversion comment.';", |
| "ALTER CONVERSION public.conv_one OWNER TO testrole;") |
| }) |
| }) |
| Describe("PrintCreateForeignDataWrapperStatement", func() { |
| funcInfoMap := map[uint32]backup.FunctionInfo{ |
| 1: {QualifiedName: "pg_catalog.postgresql_fdw_handler", Arguments: sql.NullString{String: "", Valid: true}, IsInternal: true}, |
| 2: {QualifiedName: "pg_catalog.postgresql_fdw_validator", Arguments: sql.NullString{String: "", Valid: true}, IsInternal: true}, |
| } |
| It("prints a basic foreign data wrapper", func() { |
| foreignDataWrapper := backup.ForeignDataWrapper{Oid: 1, Name: "foreigndata"} |
| backup.PrintCreateForeignDataWrapperStatement(backupfile, tocfile, foreignDataWrapper, funcInfoMap, backup.ObjectMetadata{}) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "foreigndata", toc.OBJ_FOREIGN_DATA_WRAPPER) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE FOREIGN DATA WRAPPER foreigndata;`) |
| }) |
| It("prints a foreign data wrapper with a handler", func() { |
| foreignDataWrapper := backup.ForeignDataWrapper{Name: "foreigndata", Handler: 1} |
| backup.PrintCreateForeignDataWrapperStatement(backupfile, tocfile, foreignDataWrapper, funcInfoMap, backup.ObjectMetadata{}) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "foreigndata", toc.OBJ_FOREIGN_DATA_WRAPPER) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE FOREIGN DATA WRAPPER foreigndata |
| HANDLER pg_catalog.postgresql_fdw_handler;`) |
| }) |
| It("prints a foreign data wrapper with a validator", func() { |
| foreignDataWrapper := backup.ForeignDataWrapper{Name: "foreigndata", Validator: 2} |
| backup.PrintCreateForeignDataWrapperStatement(backupfile, tocfile, foreignDataWrapper, funcInfoMap, backup.ObjectMetadata{}) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "foreigndata", toc.OBJ_FOREIGN_DATA_WRAPPER) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE FOREIGN DATA WRAPPER foreigndata |
| VALIDATOR pg_catalog.postgresql_fdw_validator;`) |
| }) |
| It("prints a foreign data wrapper with one option", func() { |
| foreignDataWrapper := backup.ForeignDataWrapper{Name: "foreigndata", Options: "debug 'true'"} |
| backup.PrintCreateForeignDataWrapperStatement(backupfile, tocfile, foreignDataWrapper, funcInfoMap, backup.ObjectMetadata{}) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "foreigndata", toc.OBJ_FOREIGN_DATA_WRAPPER) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE FOREIGN DATA WRAPPER foreigndata |
| OPTIONS (debug 'true');`) |
| }) |
| It("prints a foreign data wrapper with two options", func() { |
| foreignDataWrapper := backup.ForeignDataWrapper{Name: "foreigndata", Options: "debug 'true', host 'localhost'"} |
| backup.PrintCreateForeignDataWrapperStatement(backupfile, tocfile, foreignDataWrapper, funcInfoMap, backup.ObjectMetadata{}) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "foreigndata", toc.OBJ_FOREIGN_DATA_WRAPPER) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE FOREIGN DATA WRAPPER foreigndata |
| OPTIONS (debug 'true', host 'localhost');`) |
| }) |
| }) |
| Describe("PrintCreateServerStatement", func() { |
| It("prints a basic foreign server", func() { |
| foreignServer := backup.ForeignServer{Oid: 1, Name: "foreignserver", ForeignDataWrapper: "foreignwrapper"} |
| backup.PrintCreateServerStatement(backupfile, tocfile, foreignServer, backup.ObjectMetadata{}) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "foreignserver", toc.OBJ_FOREIGN_SERVER) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE SERVER foreignserver |
| FOREIGN DATA WRAPPER foreignwrapper;`) |
| }) |
| It("prints a foreign server with one option", func() { |
| foreignServer := backup.ForeignServer{Oid: 1, Name: "foreignserver", ForeignDataWrapper: "foreignwrapper", Options: "host 'localhost'"} |
| backup.PrintCreateServerStatement(backupfile, tocfile, foreignServer, backup.ObjectMetadata{}) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "foreignserver", toc.OBJ_FOREIGN_SERVER) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE SERVER foreignserver |
| FOREIGN DATA WRAPPER foreignwrapper |
| OPTIONS (host 'localhost');`) |
| }) |
| It("prints a foreign server with two options", func() { |
| foreignServer := backup.ForeignServer{Oid: 1, Name: "foreignserver", ForeignDataWrapper: "foreignwrapper", Options: "host 'localhost', dbname 'testdb'"} |
| backup.PrintCreateServerStatement(backupfile, tocfile, foreignServer, backup.ObjectMetadata{}) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "foreignserver", toc.OBJ_FOREIGN_SERVER) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE SERVER foreignserver |
| FOREIGN DATA WRAPPER foreignwrapper |
| OPTIONS (host 'localhost', dbname 'testdb');`) |
| }) |
| It("prints a foreign server with type and version", func() { |
| foreignServer := backup.ForeignServer{Oid: 1, Name: "foreignserver", Type: "server type", Version: "server version", ForeignDataWrapper: "foreignwrapper"} |
| backup.PrintCreateServerStatement(backupfile, tocfile, foreignServer, backup.ObjectMetadata{}) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "foreignserver", toc.OBJ_FOREIGN_SERVER) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE SERVER foreignserver |
| TYPE 'server type' |
| VERSION 'server version' |
| FOREIGN DATA WRAPPER foreignwrapper;`) |
| }) |
| }) |
| Describe("PrintCreateuserMappingtatement", func() { |
| It("prints a basic user mapping", func() { |
| userMapping := backup.UserMapping{Oid: 1, User: "testrole", Server: "foreignserver"} |
| backup.PrintCreateUserMappingStatement(backupfile, tocfile, userMapping) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "testrole ON foreignserver", toc.OBJ_USER_MAPPING) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE USER MAPPING FOR testrole |
| SERVER foreignserver;`) |
| }) |
| It("prints a user mapping with one option", func() { |
| userMapping := backup.UserMapping{Oid: 1, User: "testrole", Server: "foreignserver", Options: "host 'localhost'"} |
| backup.PrintCreateUserMappingStatement(backupfile, tocfile, userMapping) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "testrole ON foreignserver", toc.OBJ_USER_MAPPING) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE USER MAPPING FOR testrole |
| SERVER foreignserver |
| OPTIONS (host 'localhost');`) |
| }) |
| It("prints a user mapping with two options", func() { |
| userMapping := backup.UserMapping{Oid: 1, User: "testrole", Server: "foreignserver", Options: "host 'localhost', dbname 'testdb'"} |
| backup.PrintCreateUserMappingStatement(backupfile, tocfile, userMapping) |
| testutils.ExpectEntry(tocfile.PredataEntries, 0, "", "", "testrole ON foreignserver", toc.OBJ_USER_MAPPING) |
| testutils.AssertBufferContents(tocfile.PredataEntries, buffer, `CREATE USER MAPPING FOR testrole |
| SERVER foreignserver |
| OPTIONS (host 'localhost', dbname 'testdb');`) |
| }) |
| }) |
| }) |