blob: e6e455906b0d8edd607ed5745ebd8dda40c8d409 [file]
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');`)
})
})
})