Conversation
…OR REPLACE (#308) When a table gains a new column and a dependent view uses SELECT *, the view's column positions shift. PostgreSQL's CREATE OR REPLACE VIEW rejects this because it cannot rename or reorder existing columns. This fix: - Adds column tracking (via pg_attribute) to the View IR - Detects when old columns are NOT a prefix of new columns - Uses DROP VIEW + CREATE VIEW instead of CREATE OR REPLACE VIEW - Handles dependent view cascading (drop/recreate in dependency order) - Generalizes the existing materialized view recreation logic to also handle regular views Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR fixes issue #308 by detecting when view column changes are incompatible with PostgreSQL's Key Changes:
Technical Approach: The implementation handles dependent views correctly by reusing the existing dependency tracking system, ensuring views are dropped and recreated in proper dependency order. Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant Inspector as ir.Inspector
participant Diff as diff.GenerateMigration
participant ViewDiff as diff.viewColumnsRequireRecreate
User->>Inspector: buildViews(schema)
Inspector->>Inspector: getViewColumns(view)
Note over Inspector: Query pg_attribute for<br/>column names ordered by attnum
Inspector-->>User: View with Columns field populated
User->>Diff: GenerateMigration(oldIR, newIR)
Diff->>Diff: Compare old vs new views
Note over Diff: Check if structurallyDifferent
alt View definition changed
Diff->>ViewDiff: viewColumnsRequireRecreate(old, new)
ViewDiff->>ViewDiff: Compare old.Columns vs new.Columns
alt Columns prefix match
Note over ViewDiff: Safe for CREATE OR REPLACE
ViewDiff-->>Diff: false
else Columns reordered/renamed/removed
Note over ViewDiff: Requires DROP + CREATE
ViewDiff-->>Diff: true
end
alt Materialized OR columnsRequireRecreate
Diff->>Diff: Mark RequiresRecreate = true
Note over Diff: Generate DROP VIEW + CREATE VIEW
else Regular view, columns compatible
Diff->>Diff: Use CREATE OR REPLACE VIEW
end
end
Diff->>Diff: findDependentViewsForRecreatedViews()
Note over Diff: Handle dependent views,<br/>drop and recreate in order
Diff-->>User: Migration DDL
Last reviewed commit: b30275a |
There was a problem hiding this comment.
Pull request overview
This PR fixes issue #308 where adding a column to a table causes dependent views using SELECT * to fail when pgschema attempts to update them using CREATE OR REPLACE VIEW. PostgreSQL rejects this because the expanded column list reorders existing columns, which CREATE OR REPLACE VIEW doesn't allow.
Changes:
- Added view column tracking via
pg_attributeto detect incompatible column changes - Implemented
viewColumnsRequireRecreatelogic to determine when DROP+CREATE is needed instead of CREATE OR REPLACE - Extended existing materialized view recreation logic to also handle regular views with column changes
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
ir/ir.go |
Added Columns field to View struct to track ordered column names |
ir/inspector.go |
Added getViewColumns function to query pg_attribute for view columns |
internal/diff/view.go |
Added viewColumnsRequireRecreate function and updated view recreation logic to handle both materialized and regular views |
internal/diff/diff.go |
Updated view diff logic to trigger recreation for regular views with incompatible column changes |
testdata/diff/dependency/issue_308_view_select_star_column_reorder/* |
New test case demonstrating the fix for the exact scenario from issue #308 |
testdata/diff/*/plan.json |
Updated fingerprint hashes due to new Columns field in View IR |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
internal/diff/view.go
Outdated
| // dependentViewsContext tracks views that depend on materialized views being recreated | ||
| // dependentViewsContext tracks views that depend on views being recreated | ||
| type dependentViewsContext struct { | ||
| // dependents maps materialized view key (schema.name) to list of dependent regular views |
There was a problem hiding this comment.
The comment is outdated and should be updated to reflect that this field now tracks dependencies for both materialized views and regular views that require recreation (issue #308), not just materialized views. Consider updating to: "dependents maps view key (schema.name) to list of dependent views"
| // dependents maps materialized view key (schema.name) to list of dependent regular views | |
| // dependents maps view key (schema.name) to list of dependent views |
There was a problem hiding this comment.
Fixed. Updated the field comment to match the struct comment.
| // DROP the old view | ||
| var dropSQL string | ||
| if diff.New.Materialized { | ||
| dropSQL = fmt.Sprintf("DROP MATERIALIZED VIEW %s RESTRICT;", viewName) |
There was a problem hiding this comment.
For consistency and safety, consider using DROP MATERIALIZED VIEW IF EXISTS instead of DROP MATERIALIZED VIEW when recreating materialized views. Regular views use IF EXISTS at line 186, and this prevents potential errors if the view doesn't exist (e.g., in edge cases or when replaying migrations).
| dropSQL = fmt.Sprintf("DROP MATERIALIZED VIEW %s RESTRICT;", viewName) | |
| dropSQL = fmt.Sprintf("DROP MATERIALIZED VIEW IF EXISTS %s RESTRICT;", viewName) |
There was a problem hiding this comment.
The existing codebase deliberately uses DROP MATERIALIZED VIEW (without IF EXISTS) in all occurrences — see lines 466, 1428, and 1463. This is intentional: materialized views in the recreation path are known to exist (they appear in both old and new states), and pre-dropped views are explicitly checked via the preDroppedViews map and skipped. Adding IF EXISTS here would be inconsistent with the established pattern. Regular views use IF EXISTS because they can be cascade-dropped by other operations, which doesn't apply to materialized views (which always use RESTRICT).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
SELECT *, the view's column positions shift. PostgreSQL'sCREATE OR REPLACE VIEWrejects this because it cannot rename or reorder existing columns.pg_attribute) to detect incompatible column changes, and usesDROP VIEW+CREATE VIEWinstead ofCREATE OR REPLACE VIEWwhen needed.Fixes #308
Test plan
dependency/issue_308_view_select_star_column_reorderthat reproduces the exact scenario from the issuePGSCHEMA_TEST_FILTER="dependency/issue_308" go test -v ./internal/diff -run TestDiffFromFilesPGSCHEMA_TEST_FILTER="dependency/issue_308" go test -v ./cmd -run TestPlanAndApply🤖 Generated with Claude Code