Skip to content
Merged
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
9 changes: 8 additions & 1 deletion pkg/diff/materialized_view_sql_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ func (mvsg *materializedViewSQLGenerator) Add(mv schema.MaterializedView) (parti
materializedViewSb.WriteString(fmt.Sprintf(" TABLESPACE %s", schema.EscapeIdentifier(mv.Tablespace)))
}
materializedViewSb.WriteString(" AS\n")
materializedViewSb.WriteString(mv.ViewDefinition)
// pg_get_viewdef() may include a trailing semicolon in the view definition. Strip it so
// that WITH NO DATA is part of the same CREATE MATERIALIZED VIEW statement.
materializedViewSb.WriteString(strings.TrimRight(mv.ViewDefinition, "; \n\t"))
// Prevent the materialized view's stored query from being executed during schema
// reconstruction. Without WITH NO DATA, Postgres defaults to WITH DATA, which populates
// the view by running the query with the current connection's privileges. A low-privileged
// user could exploit this by planting a materialized view whose body escalates privileges.
materializedViewSb.WriteString("\nWITH NO DATA")

addVertexId := buildMaterializedViewVertexId(mv.SchemaQualifiedName, diffTypeAddAlter)

Expand Down
82 changes: 82 additions & 0 deletions pkg/diff/schema_migration_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,88 @@ var (
},
expectedDiffErrContains: "loop detected",
},
{
name: "Add materialized view generates WITH NO DATA",
oldSchema: schema.Schema{},
newSchema: schema.Schema{
MaterializedViews: []schema.MaterializedView{
{
SchemaQualifiedName: schema.SchemaQualifiedName{
SchemaName: "public",
EscapedName: schema.EscapeIdentifier("test_mv"),
},
ViewDefinition: " SELECT 1 AS x",
},
},
},
expectedStatements: []Statement{
{
DDL: "CREATE MATERIALIZED VIEW \"public\".\"test_mv\" AS\n SELECT 1 AS x\nWITH NO DATA",
Timeout: statementTimeoutDefault,
LockTimeout: lockTimeoutDefault,
},
},
},
{
name: "Alter materialized view (recreation) generates WITH NO DATA",
oldSchema: schema.Schema{
MaterializedViews: []schema.MaterializedView{
{
SchemaQualifiedName: schema.SchemaQualifiedName{
SchemaName: "public",
EscapedName: schema.EscapeIdentifier("test_mv"),
},
ViewDefinition: " SELECT 1 AS x",
},
},
},
newSchema: schema.Schema{
MaterializedViews: []schema.MaterializedView{
{
SchemaQualifiedName: schema.SchemaQualifiedName{
SchemaName: "public",
EscapedName: schema.EscapeIdentifier("test_mv"),
},
ViewDefinition: " SELECT 2 AS x",
},
},
},
expectedStatements: []Statement{
{
DDL: "DROP MATERIALIZED VIEW \"public\".\"test_mv\"",
Timeout: statementTimeoutDefault,
LockTimeout: lockTimeoutDefault,
},
{
DDL: "CREATE MATERIALIZED VIEW \"public\".\"test_mv\" AS\n SELECT 2 AS x\nWITH NO DATA",
Timeout: statementTimeoutDefault,
LockTimeout: lockTimeoutDefault,
},
},
},
{
name: "Add materialized view with options generates WITH NO DATA",
oldSchema: schema.Schema{},
newSchema: schema.Schema{
MaterializedViews: []schema.MaterializedView{
{
SchemaQualifiedName: schema.SchemaQualifiedName{
SchemaName: "public",
EscapedName: schema.EscapeIdentifier("test_mv"),
},
ViewDefinition: " SELECT 1 AS x",
Options: map[string]string{"fillfactor": "70"},
},
},
},
expectedStatements: []Statement{
{
DDL: "CREATE MATERIALIZED VIEW \"public\".\"test_mv\" WITH (fillfactor=70) AS\n SELECT 1 AS x\nWITH NO DATA",
Timeout: statementTimeoutDefault,
LockTimeout: lockTimeoutDefault,
},
},
},
}
)

Expand Down
Loading