fix: clean up leaked cluster-level roles in TestIgnorePrivileges#346
fix: clean up leaked cluster-level roles in TestIgnorePrivileges#346
Conversation
Triggers with column-specific UPDATE events (e.g., UPDATE OF email) were losing the column specification during inspection, causing incorrect migration plans that would fire triggers on all updates instead of only on specified column changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only extract UPDATE OF columns when the trigger actually has an UPDATE event, preventing false positives if the substring appears elsewhere. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TestIgnorePrivileges creates cluster-level PostgreSQL roles (app_reader, deploy_bot, admin_role) and ALTER DEFAULT PRIVILEGES rules in the shared embedded PG instance. Since Go runs tests alphabetically, these persist and contaminate TestPlanAndApply, causing unexpected GRANT statements in plan output for all table/view/index tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses two related concerns: it fixes a test isolation bug where TestIgnorePrivileges leaked cluster-level PostgreSQL roles into the shared embedded PG instance (contaminating subsequent tests like TestPlanAndApply), and it also bundles in support for rendering UPDATE OF column lists in trigger DDL (both detection from pg_get_triggerdef() output and SQL generation).
Changes:
- Added
cleanupSharedEmbeddedPG()helper that explicitly revokes default privileges and drops leaked roles (app_reader,deploy_bot,admin_role) afterTestIgnorePrivilegescompletes - Added
UpdateColumns []stringfield to theTriggerIR struct, with extraction frompg_get_triggerdef()output viaextractUpdateColumnsFromTriggerDef, equality comparison intriggersEqual, and correct rendering ingenerateTriggerSQLWithMode - Updated
testdata/diff/create_trigger/fixtures to include a newemployees_salary_update_trigger(BEFORE UPDATE OF salary) test case and update thealter_triggertest to exercise theINSERT OR UPDATE OF salaryevent list
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
cmd/ignore_integration_test.go |
Adds cleanupSharedEmbeddedPG() and calls it after the privilege subtests to remove leaked cluster-level roles |
ir/ir.go |
Adds UpdateColumns field to the Trigger struct |
ir/inspector.go |
Adds extractUpdateColumnsFromTriggerDef helper and populates UpdateColumns in buildTriggers |
internal/diff/trigger.go |
Adds UpdateColumns equality check and UPDATE OF col1, col2 rendering in SQL generation |
testdata/diff/create_trigger/add_trigger/* |
Adds employees_salary_update_trigger fixture for the new UPDATE OF trigger feature |
testdata/diff/create_trigger/alter_trigger/* |
Updates alter_trigger fixtures to reflect INSERT OR UPDATE OF salary event list |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Greptile SummaryThis PR fixes a test isolation bug where Key changes:
Confidence Score: 4/5
Sequence DiagramsequenceDiagram
participant Test as TestIgnorePrivileges
participant PlanCmd as GeneratePlan
participant EmbPG as sharedEmbeddedPG
participant Cleanup as cleanupSharedEmbeddedPG
Test->>PlanCmd: GeneratePlan(config, sharedEmbeddedPG)
PlanCmd->>EmbPG: ApplySchema(ctx, "public", desiredState SQL)
Note over EmbPG: CREATE ROLE app_reader/deploy_bot/admin_role<br/>ALTER DEFAULT PRIVILEGES GRANT … TO app_reader<br/>ALTER DEFAULT PRIVILEGES GRANT … TO deploy_bot<br/>(cluster-level — survive schema DROP)
PlanCmd-->>Test: migrationPlan
Test->>Cleanup: cleanupSharedEmbeddedPG(t)
Cleanup->>EmbPG: ALTER DEFAULT PRIVILEGES REVOKE ALL ON TABLES FROM app_reader
Cleanup->>EmbPG: ALTER DEFAULT PRIVILEGES REVOKE ALL ON TABLES FROM deploy_bot
Cleanup->>EmbPG: REASSIGN OWNED BY app_reader/deploy_bot/admin_role TO testuser
Cleanup->>EmbPG: DROP OWNED BY app_reader/deploy_bot/admin_role
Cleanup->>EmbPG: DROP ROLE IF EXISTS app_reader/deploy_bot/admin_role
Note over EmbPG: Cluster-level objects removed<br/>sharedEmbeddedPG now clean
Note right of Test: TestPlanAndApply runs next<br/>No more spurious GRANT statements
Last reviewed commit: d51911b |
| // Compare update columns | ||
| if len(old.UpdateColumns) != len(new.UpdateColumns) { | ||
| return false | ||
| } | ||
| for i, col := range old.UpdateColumns { | ||
| if col != new.UpdateColumns[i] { | ||
| return false | ||
| } | ||
| } |
There was a problem hiding this comment.
Order-sensitive UpdateColumns comparison may cause spurious migrations
UpdateColumns are compared element-by-element in the order they appear. PostgreSQL treats UPDATE OF a, b and UPDATE OF b, a as semantically identical (the trigger fires when any listed column is updated), but this comparison would treat them as different and generate an unnecessary CREATE OR REPLACE TRIGGER. If a user ever reorders columns in their schema file, they'll see unexpected plan output.
Consider sorting both slices before comparing:
| // Compare update columns | |
| if len(old.UpdateColumns) != len(new.UpdateColumns) { | |
| return false | |
| } | |
| for i, col := range old.UpdateColumns { | |
| if col != new.UpdateColumns[i] { | |
| return false | |
| } | |
| } | |
| // Compare update columns (order-independent, as PostgreSQL treats them as a set) | |
| if len(old.UpdateColumns) != len(new.UpdateColumns) { | |
| return false | |
| } | |
| oldCols := append([]string(nil), old.UpdateColumns...) | |
| newCols := append([]string(nil), new.UpdateColumns...) | |
| sort.Strings(oldCols) | |
| sort.Strings(newCols) | |
| for i, col := range oldCols { | |
| if col != newCols[i] { | |
| return false | |
| } | |
| } |
There was a problem hiding this comment.
Not an issue here. Both desired and current state go through database inspection (pg_get_triggerdef()), which normalizes column order by attnum. pgschema doesn't parse raw SQL — both sides are always inspector-derived, so the column order is deterministic and consistent.
Adding a sort would be defensive but unnecessary given the architecture.
Summary
TestIgnorePrivileges(added in PR feat: add support for privileges in .pgschemaignore (#339) #340) creates cluster-level PostgreSQL roles (app_reader,deploy_bot,admin_role) andALTER DEFAULT PRIVILEGESrules in thesharedEmbeddedPGinstance via the plan commandTestPlanAndApply, causing unexpected GRANT statements in plan output for ~100 table/view/index/trigger testscleanupSharedEmbeddedPG()that revokes default privileges, drops owned objects, and drops the leaked roles after the privilege test completesTest plan
TestIgnorePrivilegesstill passesTestPlanAndApplypasses when run afterTestIgnorePrivileges(previously failed)go test -v ./cmdpasses (all 150+ integration tests)go test -v ./internal/diffpasses🤖 Generated with Claude Code