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:41 UTC

(cloudberry-backup) branch main updated (5db21713 -> 8b045de5)

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

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


    from 5db21713 Enable GitHub auto-merge for pull requests
     new ee59c2cd Adapt tests to the PG16 kernel of Cloudberry main
     new 5d90463f CI: test against both Cloudberry REL_2_STABLE and main
     new 8b045de5 Fix nondeterministic order of foreign data wrappers and servers

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/cloudberry-backup-ci.yml   | 57 ++++++++++++++++++++--------
 backup/queries_functions.go                  |  6 ++-
 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 ++++++++++
 7 files changed, 106 insertions(+), 26 deletions(-)


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


(cloudberry-backup) 02/03: CI: test against both Cloudberry REL_2_STABLE and main

Posted by dj...@apache.org.
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 5d90463f8bd0f7bec1650c7f4faa4c20fd96c4a6
Author: Dianjin Wang <wa...@gmail.com>
AuthorDate: Tue Oct 20 17:08:53 2026 +0800

    CI: test against both Cloudberry REL_2_STABLE and main
    
    CI was pinned to Cloudberry REL_2_STABLE in deeb0946 because main moved
    to the PG16 kernel and broke some tests. That left Cloudberry main
    (the future 3.x) without any coverage.
    
    Turn the Cloudberry branch into a matrix dimension, so that every test
    target runs against both REL_2_STABLE and main. Artifact names and job
    names now include the branch. The test job runs with !cancelled(), so a
    failed build on one branch does not skip the test jobs of the other.
    
    Also fix the regression target on main. Its minimal_schedule tests now
    expect the shared tables (char_tbl, int2_tbl, ...) to exist already,
    and those tables are created by test_setup. Without test_setup, 5 of
    the 7 tests failed and the regression database being backed up was
    almost empty. Run test_setup first wherever it exists. On
    REL_2_STABLE all tests still pass and the schema diff is unchanged.
    
    Tested by running every target on a demo cluster on Cloudberry main
    in the CI build image.
    
    Assisted-by: Claude Code
---
 .github/workflows/cloudberry-backup-ci.yml | 57 ++++++++++++++++++++++--------
 1 file changed, 42 insertions(+), 15 deletions(-)

diff --git a/.github/workflows/cloudberry-backup-ci.yml b/.github/workflows/cloudberry-backup-ci.yml
index e1682155..cd49096e 100644
--- a/.github/workflows/cloudberry-backup-ci.yml
+++ b/.github/workflows/cloudberry-backup-ci.yml
@@ -25,13 +25,16 @@
 #   and runs Cloudberry-backup tests against a demo Cloudberry cluster.
 #
 # Workflow Overview:
-# 1. Build Cloudberry from source and upload the installation as an artifact.
-# 2. For each test target (unit, integration, end_to_end), restore the
-#    Cloudberry installation, create a demo cluster, and run tests.
+# 1. For each Cloudberry branch (REL_2_STABLE, main), build Cloudberry
+#    from source and upload the installation as an artifact.
+# 2. For each Cloudberry branch and test target (unit, integration,
+#    end_to_end, ...), restore the matching Cloudberry installation,
+#    create a demo cluster, and run tests.
 #
 # Notes:
 # - Each test job runs in an isolated environment and creates its own demo cluster.
 # - Artifacts are used to avoid rebuilding Cloudberry for every test target.
+# - Artifact names carry the Cloudberry branch so the two builds never mix.
 # --------------------------------------------------------------------
 
 name: cloudberry-backup-ci
@@ -53,15 +56,16 @@ permissions:
 
 env:
   CLOUDBERRY_REPO: apache/cloudberry
-  # Currently targeting REL_2_STABLE (2.x) branch only.
-  # TODO: Switch back to "main" once Cloudberry 3.x / main branch stabilizes with PG16 support.
-  CLOUDBERRY_REF: REL_2_STABLE
+  # The Cloudberry branches to test against are set by the
+  # `cloudberry_ref` matrix of each job:
+  #   - REL_2_STABLE: Cloudberry 2.x (PG14 kernel), the current stable line.
+  #   - main:         the future Cloudberry 3.x (PG16 kernel).
   CLOUDBERRY_DIR: cloudberry
   CLOUDBERRY_BACKUP_DIR: cloudberry-backup
 
 jobs:
   build_cloudberry:
-    name: Build Cloudberry From Source
+    name: Build Cloudberry From Source (${{ matrix.cloudberry_ref }})
     runs-on: ubuntu-22.04
     timeout-minutes: 180
     container:
@@ -73,6 +77,11 @@ jobs:
         -v /usr/local:/host_usr_local
         -v /opt:/host_opt
 
+    strategy:
+      fail-fast: false
+      matrix:
+        cloudberry_ref: [REL_2_STABLE, main]
+
     steps:
       - name: Free Disk Space
         run: |
@@ -99,7 +108,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           repository: ${{ env.CLOUDBERRY_REPO }}
-          ref: ${{ env.CLOUDBERRY_REF }}
+          ref: ${{ matrix.cloudberry_ref }}
           fetch-depth: 1
           submodules: true
           path: ${{ env.CLOUDBERRY_DIR }}
@@ -159,7 +168,7 @@ jobs:
       - name: Upload Cloudberry Installation
         uses: actions/upload-artifact@v4
         with:
-          name: cloudberry-db-install
+          name: cloudberry-db-install-${{ matrix.cloudberry_ref }}
           path: cloudberry-db.tgz
           if-no-files-found: error
           retention-days: 7
@@ -167,14 +176,19 @@ jobs:
       - name: Upload Cloudberry Source
         uses: actions/upload-artifact@v4
         with:
-          name: cloudberry-source
+          name: cloudberry-source-${{ matrix.cloudberry_ref }}
           path: cloudberry-src.tgz
           if-no-files-found: error
           retention-days: 7
 
   test_cloudberry_backup:
-    name: Cloudberry-backup Tests (${{ matrix.test_target }})
+    name: Cloudberry-backup Tests (${{ matrix.cloudberry_ref }}, ${{ matrix.test_target }})
     needs: [build_cloudberry]
+    # `needs` on a matrix job fails as soon as any one of its legs fails, so
+    # run anyway and let each leg fail on its own if the Cloudberry build it
+    # downloads is missing.  That keeps a broken main build from hiding the
+    # REL_2_STABLE results.
+    if: ${{ !cancelled() }}
     runs-on: ubuntu-22.04
     timeout-minutes: 180
     container:
@@ -189,6 +203,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
+        cloudberry_ref: [REL_2_STABLE, main]
         test_target: [smoke, unit, integration, end_to_end, s3_plugin_e2e, regression, scale, package_install]
 
     steps:
@@ -222,12 +237,12 @@ jobs:
       - name: Download Cloudberry Installation
         uses: actions/download-artifact@v4
         with:
-          name: cloudberry-db-install
+          name: cloudberry-db-install-${{ matrix.cloudberry_ref }}
 
       - name: Download Cloudberry Source
         uses: actions/download-artifact@v4
         with:
-          name: cloudberry-source
+          name: cloudberry-source-${{ matrix.cloudberry_ref }}
 
       - name: Restore Cloudberry Installation
         run: |
@@ -281,6 +296,7 @@ jobs:
           CLOUDBERRY_BACKUP_SRC: ${{ github.workspace }}/${{ env.CLOUDBERRY_BACKUP_DIR }}
           CLOUDBERRY_SRC: ${{ github.workspace }}/${{ env.CLOUDBERRY_DIR }}
           TEST_TARGET: ${{ matrix.test_target }}
+          CLOUDBERRY_REF: ${{ matrix.cloudberry_ref }}
         run: |
           set -euo pipefail
           TEST_LOG_ROOT="${GITHUB_WORKSPACE}/test-logs/${TEST_TARGET}"
@@ -434,7 +450,17 @@ jobs:
                 ;;
               regression)
                 pushd ${CLOUDBERRY_SRC}/src/test/regress
-                ./pg_regress --dbname=regression --host=localhost --port=7000 --init-file=init_file --schedule=./minimal_schedule || true
+                # On Cloudberry main (PG16 kernel) the shared test tables
+                # (char_tbl, int2_tbl, ...) are created by test_setup, which
+                # minimal_schedule does not list; without it most tests fail
+                # and the regression database ends up nearly empty.  Run it
+                # first wherever it exists; on REL_2_STABLE it is harmless.
+                regress_schedule=./minimal_schedule
+                if [ -f sql/test_setup.sql ]; then
+                  regress_schedule=/tmp/regression_minimal_schedule
+                  { echo "test: test_setup"; cat ./minimal_schedule; } > "${regress_schedule}"
+                fi
+                ./pg_regress --dbname=regression --host=localhost --port=7000 --init-file=init_file --schedule="${regress_schedule}" || true
                 cat regression.diffs 2>/dev/null || true
                 popd
 
@@ -569,6 +595,7 @@ jobs:
 
           {
             echo "## Cloudberry-backup Test Summary"
+            echo "- Cloudberry: ${CLOUDBERRY_REF}"
             echo "- Target: ${TEST_TARGET}"
             if [ ${status} -eq 0 ]; then
               echo "- Result: PASS"
@@ -584,7 +611,7 @@ jobs:
         if: failure()
         uses: actions/upload-artifact@v4
         with:
-          name: cloudberry-backup-logs-${{ matrix.test_target }}
+          name: cloudberry-backup-logs-${{ matrix.cloudberry_ref }}-${{ matrix.test_target }}
           path: test-logs/${{ matrix.test_target }}
           if-no-files-found: warn
           retention-days: 7


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


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

Posted by dj...@apache.org.
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


(cloudberry-backup) 03/03: Fix nondeterministic order of foreign data wrappers and servers

Posted by dj...@apache.org.
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 8b045de59b4ea0014cc0eabc2a7963759e758cc2
Author: Dianjin Wang <wa...@gmail.com>
AuthorDate: Thu Oct 22 11:54:15 2026 +0800

    Fix nondeterministic order of foreign data wrappers and servers
    
    GetForeignDataWrappers() and GetForeignServers() have no ORDER BY, so
    the database may return their rows in any order. The integration test
    "creates foreign data wrappers with a validator and options" indexes
    the result as [0] and [1], and it failed on the Cloudberry main leg of
    CI with the two wrappers swapped. The same run had passed earlier on a
    local main cluster only by chance.
    
    Order both queries by oid. That makes the tests deterministic, and the
    backup now writes these objects in a stable order too.
    
    This change is taken from Jinbao Chen's PR #106.
    
    See: https://github.com/apache/cloudberry-backup/pull/106
    
    Co-authored-by: Jinbao Chen <ch...@gmail.com>
    Assisted-by: Claude Code
---
 backup/queries_functions.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/backup/queries_functions.go b/backup/queries_functions.go
index 8fb04069..98c90461 100644
--- a/backup/queries_functions.go
+++ b/backup/queries_functions.go
@@ -840,7 +840,8 @@ func GetForeignDataWrappers(connectionPool *dbconn.DBConn) []ForeignDataWrapper
 			SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value)
 			FROM pg_options_to_table(fdwoptions) ORDER BY option_name), ', ') AS options
 	FROM pg_foreign_data_wrapper
-	WHERE oid >= %d AND %s`, FIRST_NORMAL_OBJECT_ID, ExtensionFilterClause(""))
+	WHERE oid >= %d AND %s
+	ORDER BY oid`, FIRST_NORMAL_OBJECT_ID, ExtensionFilterClause(""))
 
 	err := connectionPool.Select(&results, query)
 	gplog.FatalOnError(err)
@@ -889,7 +890,8 @@ func GetForeignServers(connectionPool *dbconn.DBConn) []ForeignServer {
 			FROM pg_options_to_table(fs.srvoptions) ORDER BY option_name), ', ') AS options
 	FROM pg_foreign_server fs
 		LEFT JOIN pg_foreign_data_wrapper fdw ON fdw.oid = srvfdw
-	WHERE fs.oid >= %d AND %s`, FIRST_NORMAL_OBJECT_ID, ExtensionFilterClause("fs"))
+	WHERE fs.oid >= %d AND %s
+	ORDER BY fs.oid`, FIRST_NORMAL_OBJECT_ID, ExtensionFilterClause("fs"))
 
 	err := connectionPool.Select(&results, query)
 	gplog.FatalOnError(err)


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