Skip to content

Commit b40f7cb

Browse files
tianzhouclaude
andcommitted
fix: order REVOKE before GRANT when transitioning between table and column privileges (#324)
When changing from table-level GRANT to column-level GRANT (e.g., GRANT UPDATE to GRANT UPDATE (col)), privilege modifications (containing REVOKEs) were emitted in Phase 3 (Modify) while new column grants were emitted in Phase 2 (Create), causing GRANTs to execute before REVOKEs and leaving the user with no permissions. Move all explicit privilege operations (both creates and modifications) to the end of generateModifySQL, after object modifications/recreations. This ensures: 1. DROP+CREATE'd objects (e.g., materialized views) don't wipe out privilege changes 2. REVOKEs from modifications execute before new GRANTs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6c49aea commit b40f7cb

13 files changed

Lines changed: 154 additions & 9 deletions

File tree

internal/diff/diff.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,14 +1603,14 @@ func (d *ddlDiff) generateCreateSQL(targetSchema string, collector *diffCollecto
16031603
// See https://github.com/pgplex/pgschema/issues/253
16041604
generateDropPrivilegesSQL(d.revokedDefaultGrantsOnNewTables, targetSchema, collector)
16051605

1606-
// Create explicit object privileges
1607-
generateCreatePrivilegesSQL(d.addedPrivileges, targetSchema, collector)
1608-
1609-
// Create column-level privileges
1610-
generateCreateColumnPrivilegesSQL(d.addedColumnPrivileges, targetSchema, collector)
1611-
16121606
// Revoke default PUBLIC privileges (new revokes)
16131607
generateRevokeDefaultPrivilegesSQL(d.addedRevokedDefaultPrivs, targetSchema, collector)
1608+
1609+
// Note: Explicit privilege creates and modifications are handled in generateModifySQL
1610+
// (after object modifications/recreations) to ensure:
1611+
// 1. DROP+CREATE'd objects (e.g., materialized views) don't wipe out privilege changes
1612+
// 2. REVOKEs from modifications execute before new GRANTs
1613+
// See https://github.com/pgplex/pgschema/issues/324
16141614
}
16151615

16161616
// generateModifySQL generates ALTER statements
@@ -1653,11 +1653,15 @@ func (d *ddlDiff) generateModifySQL(targetSchema string, collector *diffCollecto
16531653
// Modify default privileges
16541654
generateModifyDefaultPrivilegesSQL(d.modifiedDefaultPrivileges, targetSchema, collector)
16551655

1656-
// Modify explicit object privileges
1656+
// All explicit privilege operations run AFTER object modifications/recreations
1657+
// to avoid DROP+CREATE'd objects (e.g., materialized views) wiping out privilege changes.
1658+
// Modifications (which contain REVOKEs) run before creates (which contain GRANTs)
1659+
// to prevent table-level REVOKEs from undoing column-level GRANTs.
1660+
// See https://github.com/pgplex/pgschema/issues/324
16571661
generateModifyPrivilegesSQL(d.modifiedPrivileges, targetSchema, collector)
1658-
1659-
// Modify column-level privileges
16601662
generateModifyColumnPrivilegesSQL(d.modifiedColumnPrivileges, targetSchema, collector)
1663+
generateCreatePrivilegesSQL(d.addedPrivileges, targetSchema, collector)
1664+
generateCreateColumnPrivilegesSQL(d.addedColumnPrivileges, targetSchema, collector)
16611665
}
16621666

16631667
// generateDropSQL generates DROP statements in reverse dependency order
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
REVOKE UPDATE ON TABLE sometable FROM app_user;
2+
3+
GRANT UPDATE (somecolumn) ON TABLE sometable TO app_user;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
DO $$
2+
BEGIN
3+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN
4+
CREATE ROLE app_user;
5+
END IF;
6+
END $$;
7+
8+
CREATE TABLE sometable (somecolumn text);
9+
10+
GRANT UPDATE (somecolumn) ON sometable TO app_user;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
DO $$
2+
BEGIN
3+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN
4+
CREATE ROLE app_user;
5+
END IF;
6+
END $$;
7+
8+
CREATE TABLE sometable (somecolumn text);
9+
10+
GRANT UPDATE ON sometable TO app_user;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"version": "1.0.0",
3+
"pgschema_version": "1.7.2",
4+
"created_at": "1970-01-01T00:00:00Z",
5+
"source_fingerprint": {
6+
"hash": "2f50f8550c48f6ac58cde417097cdd7c293b3ae0d9bd8861b197edb73e80b148"
7+
},
8+
"groups": [
9+
{
10+
"steps": [
11+
{
12+
"sql": "REVOKE UPDATE ON TABLE sometable FROM app_user;",
13+
"type": "privilege",
14+
"operation": "drop",
15+
"path": "privileges.TABLE.sometable.app_user"
16+
},
17+
{
18+
"sql": "GRANT UPDATE (somecolumn) ON TABLE sometable TO app_user;",
19+
"type": "column_privilege",
20+
"operation": "create",
21+
"path": "column_privileges.TABLE.sometable.somecolumn.app_user"
22+
}
23+
]
24+
}
25+
]
26+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
REVOKE UPDATE ON TABLE sometable FROM app_user;
2+
3+
GRANT UPDATE (somecolumn) ON TABLE sometable TO app_user;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Plan: 1 to add, 1 to drop.
2+
3+
Summary by type:
4+
privileges: 1 to drop
5+
column privileges: 1 to add
6+
7+
Privileges:
8+
- app_user
9+
10+
Column privileges:
11+
+ app_user
12+
13+
DDL to be executed:
14+
--------------------------------------------------
15+
16+
REVOKE UPDATE ON TABLE sometable FROM app_user;
17+
18+
GRANT UPDATE (somecolumn) ON TABLE sometable TO app_user;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
REVOKE UPDATE ON TABLE sometable FROM app_user;
2+
3+
GRANT UPDATE (somecolumn) ON TABLE sometable TO app_user;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
DO $$
2+
BEGIN
3+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN
4+
CREATE ROLE app_user;
5+
END IF;
6+
END $$;
7+
8+
CREATE TABLE sometable (somecolumn text);
9+
10+
GRANT SELECT ON sometable TO app_user;
11+
GRANT UPDATE (somecolumn) ON sometable TO app_user;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
DO $$
2+
BEGIN
3+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN
4+
CREATE ROLE app_user;
5+
END IF;
6+
END $$;
7+
8+
CREATE TABLE sometable (somecolumn text);
9+
10+
GRANT SELECT, UPDATE ON sometable TO app_user;

0 commit comments

Comments
 (0)