Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions backup/predata_relations.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,12 +444,20 @@ 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 {
// Cloudberry incremental materialized views (relisivm) must be recreated
// as INCREMENTAL so the kernel re-establishes their maintenance triggers;
// restoring them as a plain MATERIALIZED VIEW would silently drop the
// incremental behaviour.
matviewKeyword := "MATERIALIZED VIEW"
if view.IsIncremental {
matviewKeyword = "INCREMENTAL MATERIALIZED VIEW"
}
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)
metadataFile.MustPrintf("\n\nCREATE %s %s%s%s%s AS %s\nWITH NO DATA\n%s;\n",
matviewKeyword, 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()]
Expand Down
12 changes: 12 additions & 0 deletions backup/predata_relations_other_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,18 @@ GRANT ALL ON TABLE schema1.mview1 TO testrole;`}
testutils.AssertBufferContents(tocfile.PredataEntries, buffer,
`CREATE MATERIALIZED VIEW schema1.mview1 WITH (security_barrier=true) TABLESPACE myTablespace AS SELECT count(*) FROM pg_tables
WITH NO DATA
DISTRIBUTED BY (tablename);`)
})
It("can print an incremental materialized view", func() {
// A Cloudberry incremental materialized view must be recreated with
// the INCREMENTAL keyword so the kernel re-establishes its maintenance
// triggers; otherwise it would be restored as a plain matview.
mview.IsIncremental = true
backup.PrintCreateViewStatement(backupfile, tocfile, mview, emptyMetadata)
testutils.ExpectEntry(tocfile.PredataEntries, 0, "schema1", "", "mview1", toc.OBJ_MATERIALIZED_VIEW)
testutils.AssertBufferContents(tocfile.PredataEntries, buffer,
`CREATE INCREMENTAL MATERIALIZED VIEW schema1.mview1 AS SELECT count(*) FROM pg_tables
WITH NO DATA
DISTRIBUTED BY (tablename);`)
})
})
Expand Down
15 changes: 14 additions & 1 deletion backup/queries_postdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,18 @@ func GetTriggers(connectionPool *dbconn.DBConn) []TriggerDefinition {
if connectionPool.Version.IsGPDB() && connectionPool.Version.Before("6") {
constraintClause = "tgisconstraint = 'f'"
}
// Cloudberry incremental materialized views own maintenance triggers on their
// base tables that call pg_catalog.ivm_immediate_before /
// ivm_immediate_maintenance. They are re-created automatically when the
// incremental matview is repopulated (REFRESH) after restore, and their names
// and function arguments embed the matview's pre-restore OID, so backing them
// up would duplicate them with stale OIDs. Exclude them. The subquery is empty
// on systems without IVM, so this is a no-op there (not just Cloudberry).
ivmTriggerClause := `t.tgfoid NOT IN (
SELECT p.oid FROM pg_proc p
JOIN pg_namespace pn ON pn.oid = p.pronamespace
WHERE pn.nspname = 'pg_catalog'
AND p.proname IN ('ivm_immediate_before', 'ivm_immediate_maintenance'))`
query := fmt.Sprintf(`
SELECT t.oid AS oid,
quote_ident(t.tgname) AS name,
Expand All @@ -452,8 +464,9 @@ func GetTriggers(connectionPool *dbconn.DBConn) []TriggerDefinition {
AND tgname NOT LIKE 'pg_%%'
AND %s
AND %s
AND %s
ORDER BY tgname`,
relationAndSchemaFilterClause(), constraintClause, ExtensionFilterClause("c"))
relationAndSchemaFilterClause(), constraintClause, ExtensionFilterClause("c"), ivmTriggerClause)

results := make([]TriggerDefinition, 0)
err := connectionPool.Select(&results, query)
Expand Down
11 changes: 10 additions & 1 deletion backup/queries_relations.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ type View struct {
Definition sql.NullString
Tablespace string
IsMaterialized bool
IsIncremental bool
DistPolicy DistPolicy
NeedsDummy bool
ColumnDefs []ColumnDefinition
Expand Down Expand Up @@ -429,6 +430,13 @@ func GetAllViews(connectionPool *dbconn.DBConn) []View {
AND %s
AND %s`, relationAndSchemaFilterClause(), ExtensionFilterClause("c"))

// relisivm (incremental materialized view flag) is a Cloudberry-only column;
// GPDB does not have it, so only reference it on Cloudberry.
incrementalExpr := "false"
if connectionPool.Version.IsCBDB() {
incrementalExpr = "c.relisivm"
}

atLeast7Query := fmt.Sprintf(`
SELECT
c.oid AS oid,
Expand All @@ -438,14 +446,15 @@ func GetAllViews(connectionPool *dbconn.DBConn) []View {
coalesce(' WITH (' || array_to_string(c.reloptions, ', ') || ')', '') AS options,
coalesce(quote_ident(t.spcname), '') AS tablespace,
c.relkind='m' AS ismaterialized,
%s AS isincremental,
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"))
AND %s`, incrementalExpr, relationAndSchemaFilterClause(), ExtensionFilterClause("c"))

query := ""
if connectionPool.Version.IsGPDB() && connectionPool.Version.Before("6") {
Expand Down
17 changes: 17 additions & 0 deletions integration/postdata_queries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,23 @@ PARTITION BY RANGE (date)

Expect(results).To(BeEmpty())
})
It("does not include IVM maintenance triggers of an incremental materialized view", func() {
if !connectionPool.Version.IsCBDB() {
Skip("Incremental materialized views are only available on Cloudberry")
}
testhelper.AssertQueryRuns(connectionPool, "CREATE TABLE public.ivm_base(id int, val int) DISTRIBUTED BY (id)")
defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.ivm_base CASCADE")
// A populated incremental matview installs IVM_trigger_* maintenance
// triggers on the base table (calling pg_catalog.ivm_immediate_*). They
// are re-created automatically when the matview is repopulated after
// restore, so GetTriggers must not back them up.
testhelper.AssertQueryRuns(connectionPool, "CREATE INCREMENTAL MATERIALIZED VIEW public.ivm_mv AS SELECT id, val FROM public.ivm_base DISTRIBUTED BY (id)")
defer testhelper.AssertQueryRuns(connectionPool, "DROP MATERIALIZED VIEW public.ivm_mv")

results := backup.GetTriggers(connectionPool)

Expect(results).To(BeEmpty())
})
It("returns a slice of triggers for a specific schema", func() {
triggerString1 := `CREATE TRIGGER sync_trigger_table1 AFTER INSERT OR DELETE OR UPDATE ON public.trigger_table1 FOR EACH STATEMENT EXECUTE PROCEDURE "RI_FKey_check_ins"()`
triggerString2 := `CREATE TRIGGER sync_trigger_table1 AFTER INSERT OR DELETE OR UPDATE ON testschema.trigger_table1 FOR EACH STATEMENT EXECUTE PROCEDURE "RI_FKey_check_ins"()`
Expand Down
29 changes: 29 additions & 0 deletions integration/predata_relations_queries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,5 +553,34 @@ PARTITION BY LIST (gender)
Expect(results).To(HaveLen(1))
structmatcher.ExpectStructsToMatchExcluding(&materialView, &results[0], "Oid", "ColumnDefs", "DistPolicy.Oid")
})
It("sets IsIncremental for an incremental materialized view", func() {
// Cloudberry incremental materialized views (relisivm) must be flagged
// so they are recreated with CREATE INCREMENTAL MATERIALIZED VIEW; a
// plain materialized view must not be flagged.
if !connectionPool.Version.IsCBDB() {
Skip("Incremental materialized views are only available on Cloudberry")
}
testhelper.AssertQueryRuns(connectionPool, "CREATE TABLE public.ivm_base(id int, val int) DISTRIBUTED BY (id)")
defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLE public.ivm_base CASCADE")
testhelper.AssertQueryRuns(connectionPool, "CREATE INCREMENTAL MATERIALIZED VIEW public.ivm_mv AS SELECT id, val FROM public.ivm_base DISTRIBUTED BY (id)")
defer testhelper.AssertQueryRuns(connectionPool, "DROP MATERIALIZED VIEW public.ivm_mv")
testhelper.AssertQueryRuns(connectionPool, "CREATE MATERIALIZED VIEW public.plain_mv AS SELECT id, val FROM public.ivm_base DISTRIBUTED BY (id)")
defer testhelper.AssertQueryRuns(connectionPool, "DROP MATERIALIZED VIEW public.plain_mv")

results := backup.GetAllViews(connectionPool)
var ivm, plain backup.View
for _, v := range results {
switch v.Name {
case "ivm_mv":
ivm = v
case "plain_mv":
plain = v
}
}
Expect(ivm.IsMaterialized).To(BeTrue())
Expect(ivm.IsIncremental).To(BeTrue())
Expect(plain.IsMaterialized).To(BeTrue())
Expect(plain.IsIncremental).To(BeFalse())
})
})
})