You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudberry.apache.org by dj...@apache.org on 2026/10/23 02:54:42 UTC

(cloudberry-backup) 01/03: Adapt tests to the PG16 kernel of Cloudberry main

This is an automated email from the ASF dual-hosted git repository.

tuhaihe pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudberry-backup.git

commit ee59c2cdae4e8943250a3730259593d50da48198
Author: Dianjin Wang <wa...@gmail.com>
AuthorDate: Tue Oct 20 17:08:53 2026 +0800

    Adapt tests to the PG16 kernel of Cloudberry main
    
    Cloudberry main (the future 3.x) is now on the PG16 kernel. Running the
    suites against it gives 8 integration failures and 1 end_to_end
    failure. Both suites pass on REL_2_STABLE. All of the failures come
    from PG16 behavior changes, not from gpbackup bugs:
    
    - A superuser that grants a membership without GRANTED BY is now
      recorded as the bootstrap superuser, not as itself.
    - A role named in GRANTED BY must hold ADMIN OPTION on the granted
      role, even when it is a superuser.
    - A role that granted a membership can no longer be dropped while
      that membership exists, so the "dropped granter" case cannot be
      set up at all.
    - pg_get_viewdef() no longer qualifies the columns of a single-table
      query.
    
    Add testutils.IsCBDB3OrLater() and use it to set up and check these
    cases per version. It compares SemVer.Major directly because a main
    build reports "3.0.0-devel", which sorts before "3.0.0" under semver,
    so Version.AtLeast("3") returns false.
    
    The membership tests now also match on the member, since granting
    ADMIN OPTION to testrole first adds a second row for usergroup.
    
    Tested on a demo cluster on Cloudberry main (PG 16.9) and on
    REL_2_STABLE (PG 14.10): integration and end_to_end pass on both.
    
    See: https://github.com/apache/cloudberry-backup/issues/103
    
    Assisted-by: Claude Code
---
 end_to_end/end_to_end_suite_test.go          | 10 ++++++++--
 integration/metadata_globals_create_test.go  |  6 ++++--
 integration/metadata_globals_queries_test.go | 26 ++++++++++++++++++++++----
 integration/postdata_create_test.go          |  7 ++++++-
 testutils/functions.go                       | 20 ++++++++++++++++++++
 5 files changed, 60 insertions(+), 9 deletions(-)

diff --git a/end_to_end/end_to_end_suite_test.go b/end_to_end/end_to_end_suite_test.go
index 585c2057..468ee686 100644
--- a/end_to_end/end_to_end_suite_test.go
+++ b/end_to_end/end_to_end_suite_test.go
@@ -2158,8 +2158,14 @@ LANGUAGE plpgsql NO SQL;`)
 				Expect(contents).To(ContainSubstring("CREATE VIEW public.key_dependent_view AS \nSELECT\n\tNULL::integer AS key,\n\tNULL::character varying(20) COLLATE pg_catalog.\"C\" AS data;"))
 				Expect(contents).To(ContainSubstring("CREATE VIEW public.key_dependent_view_no_cols AS \nSELECT;"))
 				Expect(contents).To(ContainSubstring("ALTER TABLE ONLY public.view_base_table ADD CONSTRAINT view_base_table_pkey PRIMARY KEY (key);"))
-				Expect(contents).To(ContainSubstring("CREATE OR REPLACE VIEW public.key_dependent_view AS  SELECT view_base_table.key,\n    (view_base_table.data COLLATE \"C\") AS data\n   FROM public.view_base_table\n  GROUP BY view_base_table.key;"))
-				Expect(contents).To(ContainSubstring("CREATE OR REPLACE VIEW public.key_dependent_view_no_cols AS  SELECT\n   FROM public.view_base_table\n  GROUP BY view_base_table.key\n HAVING (length((view_base_table.data)::text) > 0);"))
+				if testutils.IsCBDB3OrLater(backupConn) {
+					// PG16 no longer qualifies columns of a single-table query.
+					Expect(contents).To(ContainSubstring("CREATE OR REPLACE VIEW public.key_dependent_view AS  SELECT key,\n    (data COLLATE \"C\") AS data\n   FROM public.view_base_table\n  GROUP BY key;"))
+					Expect(contents).To(ContainSubstring("CREATE OR REPLACE VIEW public.key_dependent_view_no_cols AS  SELECT\n   FROM public.view_base_table\n  GROUP BY key\n HAVING (length((data)::text) > 0);"))
+				} else {
+					Expect(contents).To(ContainSubstring("CREATE OR REPLACE VIEW public.key_dependent_view AS  SELECT view_base_table.key,\n    (view_base_table.data COLLATE \"C\") AS data\n   FROM public.view_base_table\n  GROUP BY view_base_table.key;"))
+					Expect(contents).To(ContainSubstring("CREATE OR REPLACE VIEW public.key_dependent_view_no_cols AS  SELECT\n   FROM public.view_base_table\n  GROUP BY view_base_table.key\n HAVING (length((view_base_table.data)::text) > 0);"))
+				}
 
 				gprestore(gprestorePath, restoreHelperPath, timestamp,
 					"--redirect-db", "restoredb",
diff --git a/integration/metadata_globals_create_test.go b/integration/metadata_globals_create_test.go
index 2063c3a1..15551390 100644
--- a/integration/metadata_globals_create_test.go
+++ b/integration/metadata_globals_create_test.go
@@ -416,6 +416,7 @@ var _ = Describe("backup integration create statement tests", func() {
 			defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE testuser`)
 		})
 		It("grants a role without ADMIN OPTION", func() {
+			testutils.GrantAdminToGrantorIfRequired(connectionPool, "usergroup", "testrole")
 			numRoleMembers := len(backup.GetRoleMembers(connectionPool))
 			expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: "testrole", IsAdmin: false}
 			backup.PrintRoleMembershipStatements(backupfile, tocfile, []backup.RoleMember{expectedRoleMember})
@@ -425,7 +426,7 @@ var _ = Describe("backup integration create statement tests", func() {
 			resultRoleMembers := backup.GetRoleMembers(connectionPool)
 			Expect(resultRoleMembers).To(HaveLen(numRoleMembers + 1))
 			for _, roleMember := range resultRoleMembers {
-				if roleMember.Role == "usergroup" {
+				if roleMember.Role == "usergroup" && roleMember.Member == "testuser" {
 					structmatcher.ExpectStructsToMatch(&expectedRoleMember, &roleMember)
 					return
 				}
@@ -433,6 +434,7 @@ var _ = Describe("backup integration create statement tests", func() {
 			Fail("Role 'testuser' is not a member of role 'usergroup'")
 		})
 		It("grants a role WITH ADMIN OPTION", func() {
+			testutils.GrantAdminToGrantorIfRequired(connectionPool, "usergroup", "testrole")
 			numRoleMembers := len(backup.GetRoleMembers(connectionPool))
 			expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: "testrole", IsAdmin: true}
 			backup.PrintRoleMembershipStatements(backupfile, tocfile, []backup.RoleMember{expectedRoleMember})
@@ -442,7 +444,7 @@ var _ = Describe("backup integration create statement tests", func() {
 			resultRoleMembers := backup.GetRoleMembers(connectionPool)
 			Expect(resultRoleMembers).To(HaveLen(numRoleMembers + 1))
 			for _, roleMember := range resultRoleMembers {
-				if roleMember.Role == "usergroup" {
+				if roleMember.Role == "usergroup" && roleMember.Member == "testuser" {
 					structmatcher.ExpectStructsToMatch(&expectedRoleMember, &roleMember)
 					return
 				}
diff --git a/integration/metadata_globals_queries_test.go b/integration/metadata_globals_queries_test.go
index d6182e69..650fef14 100644
--- a/integration/metadata_globals_queries_test.go
+++ b/integration/metadata_globals_queries_test.go
@@ -506,6 +506,17 @@ CREATEEXTTABLE (protocol='gphdfs', type='writable')`
 		})
 	})
 	Describe("GetRoleMembers", func() {
+		/*
+		 * Since PG16 (Cloudberry 3.x) a superuser that grants a membership
+		 * without GRANTED BY is recorded as the bootstrap superuser rather
+		 * than as itself.
+		 */
+		implicitGrantor := func(currentRole string) string {
+			if testutils.IsCBDB3OrLater(connectionPool) {
+				return testutils.GetUserByID(connectionPool, 10)
+			}
+			return currentRole
+		}
 		BeforeEach(func() {
 			testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE usergroup`)
 			testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE testuser`)
@@ -516,7 +527,7 @@ CREATEEXTTABLE (protocol='gphdfs', type='writable')`
 		})
 		It("returns a role without ADMIN OPTION", func() {
 			testhelper.AssertQueryRuns(connectionPool, "GRANT usergroup TO testuser")
-			expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: "testrole", IsAdmin: false}
+			expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: implicitGrantor("testrole"), IsAdmin: false}
 
 			roleMembers := backup.GetRoleMembers(connectionPool)
 
@@ -529,13 +540,14 @@ CREATEEXTTABLE (protocol='gphdfs', type='writable')`
 			Fail("Role 'testuser' is not a member of role 'usergroup'")
 		})
 		It("returns a role WITH ADMIN OPTION", func() {
+			testutils.GrantAdminToGrantorIfRequired(connectionPool, "usergroup", "testrole")
 			testhelper.AssertQueryRuns(connectionPool, "GRANT usergroup TO testuser WITH ADMIN OPTION GRANTED BY testrole")
 			expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: "testrole", IsAdmin: true}
 
 			roleMembers := backup.GetRoleMembers(connectionPool)
 
 			for _, roleMember := range roleMembers {
-				if roleMember.Role == "usergroup" {
+				if roleMember.Role == "usergroup" && roleMember.Member == "testuser" {
 					structmatcher.ExpectStructsToMatch(&expectedRoleMember, &roleMember)
 					return
 				}
@@ -552,7 +564,7 @@ CREATEEXTTABLE (protocol='gphdfs', type='writable')`
 			testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE "1testuser"`)
 			defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE "1testuser"`)
 			testhelper.AssertQueryRuns(connectionPool, `GRANT "1usergroup" TO "1testuser"`)
-			expectedRoleMember := backup.RoleMember{Role: `"1usergroup"`, Member: `"1testuser"`, Grantor: `"1testrole"`, IsAdmin: false}
+			expectedRoleMember := backup.RoleMember{Role: `"1usergroup"`, Member: `"1testuser"`, Grantor: implicitGrantor(`"1testrole"`), IsAdmin: false}
 
 			roleMembers := backup.GetRoleMembers(connectionPool)
 
@@ -565,6 +577,12 @@ CREATEEXTTABLE (protocol='gphdfs', type='writable')`
 			Fail(`Role "1testuser" is not a member of role "1usergroup"`)
 		})
 		It("handles dropped granter", func() {
+			if testutils.IsCBDB3OrLater(connectionPool) {
+				// Since PG16 a role that granted a membership cannot be
+				// dropped while that membership exists, so a membership
+				// with a dropped grantor can no longer be produced.
+				Skip("Test not applicable to Cloudberry 3.x and later")
+			}
 			testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE testdropgranter_role`)
 			defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE testdropgranter_role`)
 			testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE testdropgranter_member`)
@@ -583,7 +601,7 @@ CREATEEXTTABLE (protocol='gphdfs', type='writable')`
 			testhelper.AssertQueryRuns(connectionPool, "CREATE CAST (oid AS text) WITH FUNCTION pg_catalog.text(oid) AS IMPLICIT;")
 			defer testhelper.AssertQueryRuns(connectionPool, "DROP FUNCTION pg_catalog.text(oid) CASCADE;")
 			testhelper.AssertQueryRuns(connectionPool, "GRANT usergroup TO testuser")
-			expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: "testrole", IsAdmin: false}
+			expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: implicitGrantor("testrole"), IsAdmin: false}
 
 			roleMembers := backup.GetRoleMembers(connectionPool)
 
diff --git a/integration/postdata_create_test.go b/integration/postdata_create_test.go
index aba3cfb1..1e2ba5a9 100644
--- a/integration/postdata_create_test.go
+++ b/integration/postdata_create_test.go
@@ -402,11 +402,16 @@ AS $$ BEGIN RAISE EXCEPTION 'exception'; END; $$;`)
 				DROP TABLE public.view_base_table CASCADE;
 			`)
 
+			viewDef := " SELECT view_base_table.key,\n    (view_base_table.data COLLATE \"C\") AS data\n   FROM public.view_base_table\n  GROUP BY view_base_table.key;"
+			if testutils.IsCBDB3OrLater(connectionPool) {
+				// PG16 no longer qualifies columns of a single-table query.
+				viewDef = " SELECT key,\n    (data COLLATE \"C\") AS data\n   FROM public.view_base_table\n  GROUP BY key;"
+			}
 			view1 := backup.View{
 				Schema: "public",
 				Name:   "key_dependent_view",
 				Definition: sql.NullString{
-					String: " SELECT view_base_table.key,\n    (view_base_table.data COLLATE \"C\") AS data\n   FROM public.view_base_table\n  GROUP BY view_base_table.key;",
+					String: viewDef,
 					Valid:  true,
 				},
 				ColumnDefs: []backup.ColumnDefinition{
diff --git a/testutils/functions.go b/testutils/functions.go
index da86eb0d..7245ce94 100644
--- a/testutils/functions.go
+++ b/testutils/functions.go
@@ -559,6 +559,26 @@ func SkipIfBefore7(connectionPool *dbconn.DBConn) {
 	}
 }
 
+/*
+ * Cloudberry 3.x is built on the PostgreSQL 16 kernel.  Compare the major
+ * version directly: a development build reports a version such as
+ * "3.0.0-devel", which sorts before "3.0.0" under semver and so fails
+ * Version.AtLeast("3").
+ */
+func IsCBDB3OrLater(connectionPool *dbconn.DBConn) bool {
+	return connectionPool.Version.IsCBDB() && connectionPool.Version.SemVer.Major >= 3
+}
+
+/*
+ * Since PG16 (Cloudberry 3.x) the role named in GRANTED BY must hold ADMIN
+ * OPTION on the granted role, even when that role is a superuser.
+ */
+func GrantAdminToGrantorIfRequired(connectionPool *dbconn.DBConn, role string, grantor string) {
+	if IsCBDB3OrLater(connectionPool) {
+		testhelper.AssertQueryRuns(connectionPool, fmt.Sprintf("GRANT %s TO %s WITH ADMIN OPTION", role, grantor))
+	}
+}
+
 func InitializeTestTOC(buffer io.Writer, which string) (*toc.TOC, *utils.FileWithByteCount) {
 	tocfile := &toc.TOC{}
 	tocfile.InitializeMetadataEntryMap()


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org