| package backup_test |
| |
| import ( |
| "database/sql" |
| "fmt" |
| |
| "github.com/apache/cloudberry-backup/backup" |
| "github.com/apache/cloudberry-backup/testutils" |
| "github.com/apache/cloudberry-go-libs/testhelper" |
| |
| . "github.com/onsi/ginkgo/v2" |
| . "github.com/onsi/gomega" |
| ) |
| |
| var _ = Describe("backup/dependencies tests", func() { |
| var ( |
| relation1 backup.Relation |
| relation2 backup.Relation |
| relation3 backup.Relation |
| depMap map[backup.UniqueID]map[backup.UniqueID]bool |
| ) |
| |
| BeforeEach(func() { |
| relation1 = backup.Relation{Schema: "public", Name: "relation1", Oid: 1} |
| relation2 = backup.Relation{Schema: "public", Name: "relation2", Oid: 2} |
| relation3 = backup.Relation{Schema: "public", Name: "relation3", Oid: 3} |
| depMap = make(map[backup.UniqueID]map[backup.UniqueID]bool) |
| tocfile, backupfile = testutils.InitializeTestTOC(buffer, "predata") |
| }) |
| Describe("TopologicalSort", func() { |
| It("returns the original slice if there are no dependencies among objects", func() { |
| relations := []backup.Sortable{relation1, relation2, relation3} |
| |
| relations, _ = backup.TopologicalSort(relations, depMap) |
| |
| Expect(relations[0].FQN()).To(Equal("public.relation1")) |
| Expect(relations[1].FQN()).To(Equal("public.relation2")) |
| Expect(relations[2].FQN()).To(Equal("public.relation3")) |
| }) |
| It("sorts the slice correctly if there is an object dependent on one other object", func() { |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 1}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CLASS_OID, Oid: 3}: true} |
| relations := []backup.Sortable{relation1, relation2, relation3} |
| |
| relations, _ = backup.TopologicalSort(relations, depMap) |
| |
| Expect(relations[0].FQN()).To(Equal("public.relation2")) |
| Expect(relations[1].FQN()).To(Equal("public.relation3")) |
| Expect(relations[2].FQN()).To(Equal("public.relation1")) |
| }) |
| It("sorts the slice correctly if there are two objects dependent on one other object", func() { |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 1}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CLASS_OID, Oid: 2}: true} |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 3}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CLASS_OID, Oid: 2}: true} |
| relations := []backup.Sortable{relation1, relation2, relation3} |
| |
| relations, _ = backup.TopologicalSort(relations, depMap) |
| |
| Expect(relations[0].FQN()).To(Equal("public.relation2")) |
| Expect(relations[1].FQN()).To(Equal("public.relation1")) |
| Expect(relations[2].FQN()).To(Equal("public.relation3")) |
| }) |
| It("sorts the slice correctly if there is one object dependent on two other objects", func() { |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 2}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CLASS_OID, Oid: 1}: true, {ClassID: backup.PG_CLASS_OID, Oid: 1}: true} |
| relations := []backup.Sortable{relation1, relation2, relation3} |
| |
| relations, _ = backup.TopologicalSort(relations, depMap) |
| |
| Expect(relations[0].FQN()).To(Equal("public.relation1")) |
| Expect(relations[1].FQN()).To(Equal("public.relation3")) |
| Expect(relations[2].FQN()).To(Equal("public.relation2")) |
| }) |
| It("aborts if dependency loop (this shouldn't be possible)", func() { |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 1}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CLASS_OID, Oid: 3}: true} |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 2}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CLASS_OID, Oid: 1}: true} |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 3}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CLASS_OID, Oid: 2}: true} |
| |
| sortable := []backup.Sortable{relation1, relation2, relation3} |
| defer func() { |
| testhelper.ExpectRegexp(logfile, "Object: public.relation1 {ClassID:1259 Oid:1}") |
| testhelper.ExpectRegexp(logfile, "Dependencies:") |
| testhelper.ExpectRegexp(logfile, "\tpublic.relation3 {ClassID:1259 Oid:3}") |
| testhelper.ExpectRegexp(logfile, "Object: public.relation2 {ClassID:1259 Oid:2}") |
| testhelper.ExpectRegexp(logfile, "Dependencies:") |
| testhelper.ExpectRegexp(logfile, "\tpublic.relation1 {ClassID:1259 Oid:1}") |
| testhelper.ExpectRegexp(logfile, "Object: public.relation3 {ClassID:1259 Oid:3}") |
| testhelper.ExpectRegexp(logfile, "Dependencies:") |
| testhelper.ExpectRegexp(logfile, "\tpublic.relation2 {ClassID:1259 Oid:2}") |
| }() |
| defer testhelper.ShouldPanicWithMessage("Dependency resolution failed; see log file gbytes.Buffer for details. This is a bug, please report.") |
| sortable, _ = backup.TopologicalSort(sortable, depMap) |
| }) |
| It("aborts if dependencies are not met", func() { |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 1}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CLASS_OID, Oid: 2}: true} |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 1}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CLASS_OID, Oid: 4}: true} |
| sortable := []backup.Sortable{relation1, relation2} |
| |
| defer testhelper.ShouldPanicWithMessage("Dependency resolution failed; see log file gbytes.Buffer for details. This is a bug, please report.") |
| sortable, _ = backup.TopologicalSort(sortable, depMap) |
| }) |
| }) |
| Describe("PrintDependentObjectStatements", func() { |
| var ( |
| objects []backup.Sortable |
| metadataMap backup.MetadataMap |
| funcInfoMap map[uint32]backup.FunctionInfo |
| plannerSupportValue string |
| parallelValue string |
| default_parallel string |
| ) |
| BeforeEach(func() { |
| plannerSupportValue = "" |
| parallelValue = "" |
| default_parallel = "" |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() { |
| plannerSupportValue = "-" |
| parallelValue = "u" |
| default_parallel = " PARALLEL UNSAFE" |
| } |
| funcInfoMap = map[uint32]backup.FunctionInfo{ |
| 1: {QualifiedName: "public.write_to_s3", Arguments: sql.NullString{String: "", Valid: true}, IsInternal: false}, |
| 2: {QualifiedName: "public.read_from_s3", Arguments: sql.NullString{String: "", Valid: true}, IsInternal: false}, |
| } |
| objects = []backup.Sortable{ |
| backup.Function{Oid: 1, Schema: "public", Name: "function", FunctionBody: "SELECT $1 + $2", |
| Arguments: sql.NullString{String: "integer, integer", Valid: true}, |
| IdentArgs: sql.NullString{String: "integer, integer", Valid: true}, |
| ResultType: sql.NullString{String: "integer", Valid: true}, |
| Language: "sql", PlannerSupport: plannerSupportValue, Parallel: parallelValue}, |
| backup.BaseType{Oid: 2, Schema: "public", Name: "base", Input: "typin", Output: "typout", Category: "U"}, |
| backup.CompositeType{Oid: 3, Schema: "public", Name: "composite", Attributes: []backup.Attribute{{Name: "foo", Type: "integer"}}}, |
| backup.Domain{Oid: 4, Schema: "public", Name: "domain", BaseType: "numeric"}, |
| backup.Table{ |
| Relation: backup.Relation{Oid: 5, Schema: "public", Name: "relation"}, |
| TableDefinition: backup.TableDefinition{DistPolicy: backup.DistPolicy{Policy: "DISTRIBUTED RANDOMLY"}, ColumnDefs: []backup.ColumnDefinition{}}, |
| }, |
| backup.ExternalProtocol{Oid: 6, Name: "ext_protocol", Trusted: true, ReadFunction: 2, WriteFunction: 1, Validator: 0}, |
| backup.RangeType{Oid: 7, Schema: "public", Name: "rangetype1"}, |
| } |
| metadataMap = backup.MetadataMap{ |
| backup.UniqueID{ClassID: backup.PG_PROC_OID, Oid: 1}: backup.ObjectMetadata{Comment: "function"}, |
| backup.UniqueID{ClassID: backup.PG_TYPE_OID, Oid: 2}: backup.ObjectMetadata{Comment: "base type"}, |
| backup.UniqueID{ClassID: backup.PG_TYPE_OID, Oid: 3}: backup.ObjectMetadata{Comment: "composite type"}, |
| backup.UniqueID{ClassID: backup.PG_TYPE_OID, Oid: 4}: backup.ObjectMetadata{Comment: "domain"}, |
| backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 5}: backup.ObjectMetadata{Comment: "relation"}, |
| backup.UniqueID{ClassID: backup.PG_EXTPROTOCOL_OID, Oid: 6}: backup.ObjectMetadata{Comment: "protocol"}, |
| backup.UniqueID{ClassID: backup.PG_TYPE_OID, Oid: 7}: backup.ObjectMetadata{Comment: "range type"}, |
| } |
| }) |
| It("prints create statements for dependent types, functions, protocols, and tables (domain has a constraint)", func() { |
| constraints := []backup.Constraint{ |
| {Name: "check_constraint", Def: sql.NullString{String: "CHECK (VALUE > 2)", Valid: true}, OwningObject: "public.domain"}, |
| } |
| backup.PrintDependentObjectStatements(backupfile, tocfile, objects, metadataMap, constraints, funcInfoMap) |
| testhelper.ExpectRegexp(buffer, fmt.Sprintf(` |
| CREATE FUNCTION public.function(integer, integer) RETURNS integer AS |
| $_$SELECT $1 + $2$_$ |
| LANGUAGE sql%s; |
| |
| |
| COMMENT ON FUNCTION public.function(integer, integer) IS 'function'; |
| |
| |
| CREATE TYPE public.base ( |
| INPUT = typin, |
| OUTPUT = typout |
| ); |
| |
| |
| COMMENT ON TYPE public.base IS 'base type'; |
| |
| |
| CREATE TYPE public.composite AS ( |
| foo integer |
| ); |
| |
| COMMENT ON TYPE public.composite IS 'composite type'; |
| |
| CREATE DOMAIN public.domain AS numeric |
| CONSTRAINT check_constraint CHECK (VALUE > 2); |
| |
| |
| COMMENT ON DOMAIN public.domain IS 'domain'; |
| |
| |
| CREATE TABLE public.relation ( |
| ) DISTRIBUTED RANDOMLY; |
| |
| |
| COMMENT ON TABLE public.relation IS 'relation'; |
| |
| |
| CREATE TRUSTED PROTOCOL ext_protocol (readfunc = public.read_from_s3, writefunc = public.write_to_s3); |
| |
| |
| COMMENT ON PROTOCOL ext_protocol IS 'protocol'; |
| `, default_parallel)) |
| }) |
| It("prints create statements for dependent types, functions, protocols, and tables (no domain constraint)", func() { |
| constraints := make([]backup.Constraint, 0) |
| backup.PrintDependentObjectStatements(backupfile, tocfile, objects, metadataMap, constraints, funcInfoMap) |
| testhelper.ExpectRegexp(buffer, fmt.Sprintf(` |
| CREATE FUNCTION public.function(integer, integer) RETURNS integer AS |
| $_$SELECT $1 + $2$_$ |
| LANGUAGE sql%s; |
| |
| |
| COMMENT ON FUNCTION public.function(integer, integer) IS 'function'; |
| |
| |
| CREATE TYPE public.base ( |
| INPUT = typin, |
| OUTPUT = typout |
| ); |
| |
| |
| COMMENT ON TYPE public.base IS 'base type'; |
| |
| |
| CREATE TYPE public.composite AS ( |
| foo integer |
| ); |
| |
| COMMENT ON TYPE public.composite IS 'composite type'; |
| |
| CREATE DOMAIN public.domain AS numeric; |
| |
| |
| COMMENT ON DOMAIN public.domain IS 'domain'; |
| |
| |
| CREATE TABLE public.relation ( |
| ) DISTRIBUTED RANDOMLY; |
| |
| |
| COMMENT ON TABLE public.relation IS 'relation'; |
| |
| |
| CREATE TRUSTED PROTOCOL ext_protocol (readfunc = public.read_from_s3, writefunc = public.write_to_s3); |
| |
| |
| COMMENT ON PROTOCOL ext_protocol IS 'protocol'; |
| `, default_parallel)) |
| }) |
| }) |
| Describe("MarkViewsDependingOnConstraints", func() { |
| It("marks views that depend on constraints", func() { |
| view1 := backup.View{Schema: "public", Name: "view1", Oid: 1} |
| view2 := backup.View{Schema: "public", Name: "view2", Oid: 2} |
| view3 := backup.View{Schema: "public", Name: "view3", Oid: 3} |
| sortableObjs := []backup.Sortable{view1, view2, view3} |
| |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 1}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CONSTRAINT_OID, Oid: 4}: true} |
| |
| resultViews := backup.MarkViewsDependingOnConstraints(sortableObjs, depMap) |
| Expect(resultViews).To(HaveLen(1)) |
| Expect(resultViews[0].FQN()).To(Equal("public.view1")) |
| }) |
| It("marks no views if none depend on constraints", func() { |
| view1 := backup.View{Schema: "public", Name: "view1", Oid: 1} |
| view2 := backup.View{Schema: "public", Name: "view2", Oid: 2} |
| view3 := backup.View{Schema: "public", Name: "view3", Oid: 3} |
| sortableObjs := []backup.Sortable{view1, view2, view3} |
| |
| resultViews := backup.MarkViewsDependingOnConstraints(sortableObjs, depMap) |
| Expect(resultViews).To(HaveLen(0)) |
| |
| }) |
| It("does not marks any object that is not a view", func() { |
| view1 := backup.View{Schema: "public", Name: "view1", Oid: 1} |
| view2 := backup.View{Schema: "public", Name: "view2", Oid: 2} |
| view3 := backup.View{Schema: "public", Name: "view3", Oid: 3} |
| relation1 := backup.Relation{Schema: "public", Name: "relation1", Oid: 4} |
| sortableObjs := []backup.Sortable{view1, view2, view3, relation1} |
| |
| depMap[backup.UniqueID{ClassID: backup.PG_CLASS_OID, Oid: 1}] = map[backup.UniqueID]bool{{ClassID: backup.PG_CONSTRAINT_OID, Oid: 4}: true} |
| |
| resultViews := backup.MarkViewsDependingOnConstraints(sortableObjs, depMap) |
| Expect(resultViews).To(HaveLen(1)) |
| Expect(resultViews[0].FQN()).To(Equal("public.view1")) |
| }) |
| }) |
| }) |