Skip to content

fix: support view options (security_invoker, security_barrier) in dump and plan#352

Merged
tianzhou merged 5 commits intomainfrom
fix/issue-350-view-options
Mar 9, 2026
Merged

fix: support view options (security_invoker, security_barrier) in dump and plan#352
tianzhou merged 5 commits intomainfrom
fix/issue-350-view-options

Conversation

@tianzhou
Copy link
Contributor

@tianzhou tianzhou commented Mar 9, 2026

Summary

  • Add support for PostgreSQL view reloptions (security_invoker, security_barrier, check_option) across the entire pipeline
  • pgschema dump now includes WITH (option=value) in CREATE VIEW output
  • pgschema plan detects option changes and generates ALTER VIEW SET/RESET migration DDL
  • Both CREATE VIEW ... WITH (security_invoker = true) and ALTER VIEW ... SET (security_invoker = true) are handled correctly

Fixes #350

Changes

File Change
ir/ir.go Added Options []string to View struct
ir/queries/queries.sql Added c.reloptions to view query
ir/queries/queries.sql.go Updated generated code to scan reloptions
ir/inspector.go Copy and sort reloptions into Options slice
internal/diff/view.go WITH clause in CREATE VIEW, ALTER VIEW SET/RESET for option changes
internal/diff/diff.go Added OptionsChanged to viewDiff struct

Test plan

  • 1 diff test covering: add option (via WITH), add option (via ALTER VIEW SET), and remove option
  • All existing view and materialized view tests pass (no regressions)
# Run new test
PGSCHEMA_TEST_FILTER="create_view/issue_350_view_options" go test -v ./internal/diff -run TestDiffFromFiles
PGSCHEMA_TEST_FILTER="create_view/issue_350_view_options" go test -v ./cmd -run TestPlanAndApply

🤖 Generated with Claude Code

…p and plan (#350)

Add support for PostgreSQL view reloptions (security_invoker, security_barrier,
check_option) across the entire pipeline: dump extracts them, plan detects
changes, and generates appropriate ALTER VIEW SET/RESET migration DDL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 9, 2026 03:03
@greptile-apps
Copy link

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR adds first-class support for PostgreSQL view reloptions (security_invoker, security_barrier, check_option) across the dump and plan/diff pipelines. The query layer now fetches pg_class.reloptions, the IR carries an Options map, CREATE [OR REPLACE] VIEW … WITH (…) is emitted in dump output, and ALTER VIEW SET/RESET DDL is generated for option changes. The feature is well-scoped and the regular-view path works correctly.

Key finding:

  • Logic regression — materialized views: viewsEqual was extended to call viewOptionsEqual (line 682, view.go). Because viewsEqual feeds structurallyDifferent in diff.go (line 783), and needsRecreate is structurallyDifferent && newView.Materialized, any option change on a materialized view (e.g. an autovacuum_enabled storage parameter) now triggers a full DROP + CREATE instead of the correct ALTER MATERIALIZED VIEW … SET (…). The OptionsChanged flag is only ever set in the non-needsRecreate branch (line 850), so this path is silently unreachable for materialized views.

Confidence Score: 2/5

  • Safe for common regular-view use case, but carries a latent logic regression for materialized views with storage parameters.
  • The regular-view pipeline (dump, plan, apply) is well-tested and correct. However, the materialized-view code path has a logic flaw introduced by including options in viewsEqual: an options-only change now incorrectly evaluates needsRecreate = true, causing an unnecessary and potentially data-destroying DROP+CREATE instead of an ALTER MATERIALIZED VIEW SET statement. While the test suite doesn't exercise this scenario (no tests for materialized views with storage parameter changes), this is a realistic use case in production databases.
  • internal/diff/diff.go (needsRecreate logic must exclude pure options changes for materialized views) and internal/diff/view.go (generateViewSQL WITH clause is correct for both view types).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[GenerateMigration] --> B{viewsEqual?}
    B -- "false (structurallyDifferent)" --> C{needsRecreate?}
    B -- "true" --> D{commentChanged or indexesChanged or triggersChanged?}
    D -- "yes" --> E[Add viewDiff with OptionsChanged]
    D -- "no" --> F[No diff generated]
    C -- "true\n(mat. view OR column mismatch)" --> G["RequiresRecreate=true\n⚠️ OptionsChanged NOT set"]
    C -- "false\n(regular view)" --> E
    E --> H[generateModifyViewsSQL]
    G --> I["DROP + CREATE OR REPLACE VIEW\n(includes WITH clause via generateViewSQL)"]
    H --> J{optionsOnlyChange?}
    J -- "yes" --> K["ALTER VIEW SET/RESET\n✓ Correct for regular views"]
    J -- "no (def changed too)" --> L["CREATE OR REPLACE VIEW WITH (...)\n✓ Both definition and options updated"]
    style G fill:#f99,stroke:#c00
    style I fill:#f99,stroke:#c00
Loading

Comments Outside Diff (1)

  1. internal/diff/diff.go, line 834-841 (link)

    Options-only change on materialized views triggers unnecessary DROP+CREATE

    viewsEqual now calls viewOptionsEqual (line 682, view.go), so any option change causes structurallyDifferent = true. For a materialized view, needsRecreate then evaluates to true && true, triggering a full DROP + CREATE instead of ALTER MATERIALIZED VIEW … SET (…).

    This means if a materialized view has storage parameters (e.g., autovacuum_enabled) stored in pg_class.reloptions and those parameters change, the migration drops and recreates the entire materialized view—including all its data—instead of emitting an ALTER MATERIALIZED VIEW … SET (…) statement.

    The fix is to exclude a pure options change from the needsRecreate condition, analogous to how comment changes were excluded before this PR:

    needsRecreate := structurallyDifferent && !optionsChanged && (newView.Materialized || viewColumnsRequireRecreate(oldView, newView))

    Alternatively, the options check can be removed from viewsEqual and kept only in the optionsChanged variable (which is already computed independently on line 785), so structurallyDifferent only reflects schema/definition/materialization changes as it did before.

Last reviewed commit: 87823cf

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds end-to-end support for PostgreSQL view reloptions (e.g., security_invoker, security_barrier) so they are preserved in dumps and correctly planned/migrated via ALTER VIEW ... SET/RESET.

Changes:

  • Extend IR + inspector to capture pg_class.reloptions for views and parse them into a structured Options map.
  • Render view options in CREATE [OR REPLACE] VIEW ... WITH (...) and emit ALTER VIEW ... SET/RESET when options change.
  • Add dump and diff test fixtures + an integration test covering issue #350 scenarios.

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated no comments.

Show a summary per file
File Description
ir/ir.go Adds View.Options map[string]string to represent view reloptions in IR.
ir/queries/queries.sql Extends view inspection query to include c.reloptions for views.
ir/queries/queries.sql.go Updates generated scanning logic to read reloptions into ViewOptions.
ir/inspector.go Parses reloptions entries (key=value) into the IR View.Options map.
internal/diff/diff.go Tracks OptionsChanged in viewDiff to drive option-only migrations.
internal/diff/view.go Emits WITH (...) in CREATE VIEW and generates ALTER VIEW ... SET/RESET for option diffs.
cmd/dump/dump_integration_test.go Adds an integration test for dumping views with security_invoker / security_barrier.
testdata/dump/issue_350_view_security_invoker/* New dump fixture validating options are preserved in dump output.
testdata/diff/create_view/issue_350_*/* New diff fixtures validating SET/RESET behavior for option add/change/remove.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Resolve conflicts: adopt []string for View.Options (from main),
update generateViewOptionChangesSQL to work with []string format,
and regenerate test fixtures for new version.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tianzhou and others added 2 commits March 8, 2026 20:26
Merge three separate test cases (add, alter, remove) into one that
covers all scenarios: add via WITH, add via ALTER VIEW SET, and remove.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The diff test already covers all view options scenarios.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +265 to +269
// Check if only options changed (e.g., security_invoker, security_barrier)
optionsOnlyChange := diff.OptionsChanged && definitionsEqual && diff.Old.Materialized == diff.New.Materialized

// Handle non-structural changes (comment-only, index-only, trigger-only, or options-only)
if commentOnlyChange || indexOnlyChange || triggerOnlyChange || optionsOnlyChange {
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optionsOnlyChange is named as if only options changed, but the condition doesn’t enforce that (e.g., comment+options changes also satisfy it). Consider renaming it to reflect what it actually checks (options changed and definitions are equal) to avoid misleading future maintainers.

Suggested change
// Check if only options changed (e.g., security_invoker, security_barrier)
optionsOnlyChange := diff.OptionsChanged && definitionsEqual && diff.Old.Materialized == diff.New.Materialized
// Handle non-structural changes (comment-only, index-only, trigger-only, or options-only)
if commentOnlyChange || indexOnlyChange || triggerOnlyChange || optionsOnlyChange {
// Check if options changed while definition and materialization are unchanged
optionsChangeWithEqualDefinitionAndMaterialization := diff.OptionsChanged && definitionsEqual && diff.Old.Materialized == diff.New.Materialized
// Handle non-structural changes (comment-only, index-only, trigger-only, or options change with equal definition/materialization)
if commentOnlyChange || indexOnlyChange || triggerOnlyChange || optionsChangeWithEqualDefinitionAndMaterialization {

Copilot uses AI. Check for mistakes.
Comment on lines +525 to +527
// generateViewOptionChangesSQL generates ALTER VIEW SET/RESET statements for option changes.
// Options are stored as sorted []string in "key=value" format (from pg_class.reloptions).
func generateViewOptionChangesSQL(diff *viewDiff, targetSchema string, collector *diffCollector) {
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states View.Options is a map[string]string, but in code View.Options is a []string (and this helper/doc comment also describes it as a slice). Please update the PR description (or the code) so reviewers/users aren’t misled about the IR shape and JSON format.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 782 to 788
if oldView, exists := oldViews[key]; exists {
structurallyDifferent := !viewsEqual(oldView, newView)
commentChanged := oldView.Comment != newView.Comment
optionsChanged := !viewOptionsEqual(oldView.Options, newView.Options)

// Check if indexes changed for materialized views
indexesChanged := false
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because structurallyDifferent is derived from !viewsEqual(oldView, newView) (and viewsEqual now includes Options), a materialized view reloptions-only change will be considered structural. Given the later needsRecreate := structurallyDifferent && newView.Materialized ... logic, this likely forces DROP+CREATE for matview option changes even though ALTER MATERIALIZED VIEW SET/RESET would suffice (and DROP may fail under RESTRICT). Consider basing the structural/recreate decision on definition/column changes only, and handle OptionsChanged separately via the ALTER SET/RESET path.

Copilot uses AI. Check for mistakes.
Comment on lines +530 to +535
diffType := DiffTypeView
viewKeyword := "VIEW"
if diff.New.Materialized {
diffType = DiffTypeMaterializedView
viewKeyword = "MATERIALIZED VIEW"
}
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There doesn’t appear to be a diff fixture covering reloptions changes on materialized views (e.g., asserting ALTER MATERIALIZED VIEW ... SET/RESET and avoiding DROP+CREATE). Adding a targeted test case would help prevent regressions, especially given the new option diff logic shared between views and matviews.

Copilot uses AI. Check for mistakes.
…CREATE

Separate definitionChanged from structurallyDifferent so that option-only
changes on materialized views emit ALTER SET/RESET rather than a full
DROP+CREATE cycle. Added materialized view (fillfactor) to the test fixture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tianzhou tianzhou merged commit e5e71ec into main Mar 9, 2026
1 check passed
tianzhou added a commit to pgschema/pgschema-mintlify that referenced this pull request Mar 9, 2026
…p and plan (pgplex#352)

* fix: support view options (security_invoker, security_barrier) in dump and plan (pgplex#350)

Add support for PostgreSQL view reloptions (security_invoker, security_barrier,
check_option) across the entire pipeline: dump extracts them, plan detects
changes, and generates appropriate ALTER VIEW SET/RESET migration DDL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: consolidate issue 350 view options into single test case

Merge three separate test cases (add, alter, remove) into one that
covers all scenarios: add via WITH, add via ALTER VIEW SET, and remove.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: remove separate dump test case for issue 350

The diff test already covers all view options scenarios.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: materialized view option-only changes use ALTER instead of DROP+CREATE

Separate definitionChanged from structurallyDifferent so that option-only
changes on materialized views emit ALTER SET/RESET rather than a full
DROP+CREATE cycle. Added materialized view (fillfactor) to the test fixture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] security_invoker does not work in ALTER VIEW

2 participants