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

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


The following commit(s) were added to refs/heads/main by this push:
     new b60130e8 fix(relations): Ensure materialized view access method is 
restored
b60130e8 is described below

commit b60130e8062e0b2a0a031b959bc742869ceedf5d
Author: Robert Mu <[email protected]>
AuthorDate: Wed Oct 15 17:44:05 2025 +0800

    fix(relations): Ensure materialized view access method is restored
    
    On Cloudberry, Greenplum 7+, materialized views must be created with
    a specific access method (e.g., 'heap', 'ao_row') and associated storage
    options in the 'WITH' clause (e.g., 'compresstype=zstd').
    
    The prior implementation did not back up the 'USING' clause.
    During restore, the database would default to another access method
    (e.g., 'heap') which might not support the view's storage options.
    This incompatibility caused the 'CREATE MATERIALIZED VIEW' command
    to fail validation.
    
    This commit corrects the failure by:
    - Updating the view query for Cloudberry, GPDB 7+ to fetch the access 
method.
    - Modifying DDL generation to include the 'USING' clause.
    - Updating integration tests to verify the fix.
    
    Fixes #61
---
 backup/predata_relations.go                   |  8 +++--
 backup/queries_relations.go                   | 45 ++++++++++++++++++++-------
 integration/predata_relations_create_test.go  |  7 ++++-
 integration/predata_relations_queries_test.go | 11 +++++--
 4 files changed, 54 insertions(+), 17 deletions(-)

diff --git a/backup/predata_relations.go b/backup/predata_relations.go
index 8f42a8a2..ead394ab 100644
--- a/backup/predata_relations.go
+++ b/backup/predata_relations.go
@@ -444,8 +444,12 @@ func PrintCreateViewStatement(metadataFile 
*utils.FileWithByteCount, objToc *toc
        if !view.IsMaterialized {
                metadataFile.MustPrintf("\n\nCREATE VIEW %s%s AS %s\n", 
view.FQN(), view.Options, view.Definition.String)
        } else {
-               metadataFile.MustPrintf("\n\nCREATE MATERIALIZED VIEW %s%s%s AS 
%s\nWITH NO DATA\n%s;\n",
-                       view.FQN(), view.Options, tablespaceClause, 
view.Definition.String[:len(view.Definition.String)-1], view.DistPolicy.Policy)
+               accessMethodClause := ""
+               if view.AccessMethodName != "" {
+                       accessMethodClause = fmt.Sprintf(" USING %s", 
view.AccessMethodName)
+               }
+               metadataFile.MustPrintf("\n\nCREATE MATERIALIZED VIEW %s%s%s%s 
AS %s\nWITH NO DATA\n%s;\n",
+                       view.FQN(), accessMethodClause, view.Options, 
tablespaceClause, view.Definition.String[:len(view.Definition.String)-1], 
view.DistPolicy.Policy)
        }
        section, entry := view.GetMetadataEntry()
        tier := globalTierMap[view.GetUniqueID()]
diff --git a/backup/queries_relations.go b/backup/queries_relations.go
index ed16db98..34652a84 100644
--- a/backup/queries_relations.go
+++ b/backup/queries_relations.go
@@ -344,16 +344,17 @@ func GetSequenceDefinition(connectionPool *dbconn.DBConn, 
seqName string) Sequen
 }
 
 type View struct {
-       Oid            uint32
-       Schema         string
-       Name           string
-       Options        string
-       Definition     sql.NullString
-       Tablespace     string
-       IsMaterialized bool
-       DistPolicy     DistPolicy
-       NeedsDummy     bool
-       ColumnDefs     []ColumnDefinition
+       Oid              uint32
+       Schema           string
+       Name             string
+       Options          string
+       Definition       sql.NullString
+       Tablespace       string
+       IsMaterialized   bool
+       DistPolicy       DistPolicy
+       NeedsDummy       bool
+       ColumnDefs       []ColumnDefinition
+       AccessMethodName string
 }
 
 func (v View) GetMetadataEntry() (string, toc.MetadataEntry) {
@@ -412,7 +413,7 @@ func GetAllViews(connectionPool *dbconn.DBConn) []View {
 
        // Materialized views were introduced in GPDB 7 and backported to GPDB 
6.2.
        // Reloptions and tablespace added to pg_class in GPDB 6
-       atLeast6Query := fmt.Sprintf(`
+       version6Query := fmt.Sprintf(`
        SELECT
                c.oid AS oid,
                quote_ident(n.nspname) AS schema,
@@ -428,11 +429,31 @@ func GetAllViews(connectionPool *dbconn.DBConn) []View {
                AND %s
                AND %s`, relationAndSchemaFilterClause(), 
ExtensionFilterClause("c"))
 
+       atLeast7Query := fmt.Sprintf(`
+       SELECT
+               c.oid AS oid,
+               quote_ident(n.nspname) AS schema,
+               quote_ident(c.relname) AS name,
+               pg_get_viewdef(c.oid) AS definition,
+               coalesce(' WITH (' || array_to_string(c.reloptions, ', ') || 
')', '') AS options,
+               coalesce(quote_ident(t.spcname), '') AS tablespace,
+               c.relkind='m' AS ismaterialized,
+               coalesce(quote_ident(am.amname), '') AS accessmethodname
+       FROM pg_class c
+               LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+               LEFT JOIN pg_tablespace t ON t.oid = c.reltablespace
+               LEFT JOIN pg_am am ON am.oid = c.relam
+       WHERE c.relkind IN ('m', 'v')
+               AND %s
+               AND %s`, relationAndSchemaFilterClause(), 
ExtensionFilterClause("c"))
+
        query := ""
        if connectionPool.Version.IsGPDB() && 
connectionPool.Version.Before("6") {
                query = before6Query
+       } else if connectionPool.Version.IsGPDB() && 
connectionPool.Version.Is("6") {
+               query = version6Query
        } else {
-               query = atLeast6Query
+               query = atLeast7Query
        }
 
        results := make([]View, 0)
diff --git a/integration/predata_relations_create_test.go 
b/integration/predata_relations_create_test.go
index 3d825f11..5a92c391 100644
--- a/integration/predata_relations_create_test.go
+++ b/integration/predata_relations_create_test.go
@@ -576,6 +576,9 @@ SET SUBPARTITION TEMPLATE ` + `
                })
                It("creates a view with privileges, owner, security label, and 
comment", func() {
                        view := backup.View{Oid: 1, Schema: "public", Name: 
"simplemview", Definition: sql.NullString{String: " SELECT 1 AS a;", Valid: 
true}, IsMaterialized: true, DistPolicy: backup.DistPolicy{Policy: "DISTRIBUTED 
BY (a)"}}
+                       if (connectionPool.Version.IsGPDB() && 
connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() {
+                               view.AccessMethodName = "heap"
+                       }
                        viewMetadata := 
testutils.DefaultMetadata(toc.OBJ_MATERIALIZED_VIEW, true, true, true, 
includeSecurityLabels)
 
                        backup.PrintCreateViewStatement(backupfile, tocfile, 
view, viewMetadata)
@@ -594,7 +597,9 @@ SET SUBPARTITION TEMPLATE ` + `
                })
                It("creates a materialized view with options", func() {
                        view := backup.View{Oid: 1, Schema: "public", Name: 
"simplemview", Options: " WITH (fillfactor=10)", Definition: 
sql.NullString{String: " SELECT 1 AS a;", Valid: true}, IsMaterialized: true, 
DistPolicy: backup.DistPolicy{Policy: "DISTRIBUTED BY (a)"}}
-
+                       if (connectionPool.Version.IsGPDB() && 
connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() {
+                               view.AccessMethodName = "heap"
+                       }
                        backup.PrintCreateViewStatement(backupfile, tocfile, 
view, backup.ObjectMetadata{})
 
                        testhelper.AssertQueryRuns(connectionPool, 
buffer.String())
diff --git a/integration/predata_relations_queries_test.go 
b/integration/predata_relations_queries_test.go
index af97c5c9..a0fc446e 100644
--- a/integration/predata_relations_queries_test.go
+++ b/integration/predata_relations_queries_test.go
@@ -512,6 +512,9 @@ PARTITION BY LIST (gender)
 
                        results := backup.GetAllViews(connectionPool)
                        materialView := backup.View{Oid: 1, Schema: "public", 
Name: "simplematerialview", Definition: sql.NullString{String: " SELECT 1 AS 
a;", Valid: true}, IsMaterialized: true, DistPolicy: backup.DistPolicy{Policy: 
"DISTRIBUTED BY (a)"}}
+                       if (connectionPool.Version.IsGPDB() && 
connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() {
+                               materialView.AccessMethodName = "heap"
+                       }
 
                        materialView.Oid = 
testutils.OidFromObjectName(connectionPool, "public", "simplematerialview", 
backup.TYPE_RELATION)
                        Expect(results).To(HaveLen(1))
@@ -526,7 +529,9 @@ PARTITION BY LIST (gender)
 
                        results := backup.GetAllViews(connectionPool)
                        materialView := backup.View{Oid: 1, Schema: "public", 
Name: "simplematerialview", Definition: sql.NullString{String: " SELECT 1 AS 
a;", Valid: true}, Options: " WITH (fillfactor=50, autovacuum_enabled=false)", 
IsMaterialized: true, DistPolicy: backup.DistPolicy{Policy: "DISTRIBUTED BY 
(a)"}}
-
+                       if (connectionPool.Version.IsGPDB() && 
connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() {
+                               materialView.AccessMethodName = "heap"
+                       }
                        materialView.Oid = 
testutils.OidFromObjectName(connectionPool, "public", "simplematerialview", 
backup.TYPE_RELATION)
                        Expect(results).To(HaveLen(1))
                        
structmatcher.ExpectStructsToMatchExcluding(&materialView, &results[0], 
"ColumnDefs", "DistPolicy.Oid")
@@ -542,7 +547,9 @@ PARTITION BY LIST (gender)
 
                        results := backup.GetAllViews(connectionPool)
                        materialView := backup.View{Oid: 1, Schema: "public", 
Name: "simplematerialview", Definition: sql.NullString{String: " SELECT 1 AS 
a;", Valid: true}, Tablespace: "test_tablespace", IsMaterialized: true, 
DistPolicy: backup.DistPolicy{Policy: "DISTRIBUTED BY (a)"}}
-
+                       if (connectionPool.Version.IsGPDB() && 
connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() {
+                               materialView.AccessMethodName = "heap"
+                       }
                        Expect(results).To(HaveLen(1))
                        
structmatcher.ExpectStructsToMatchExcluding(&materialView, &results[0], "Oid", 
"ColumnDefs", "DistPolicy.Oid")
                })


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to