Skip to content

fix: strip temp schema prefix from GRANT/REVOKE function signatures (#376)#379

Merged
tianzhou merged 2 commits intomainfrom
fix/issue-376-grant-revoke-temp-schema-leak
Mar 27, 2026
Merged

fix: strip temp schema prefix from GRANT/REVOKE function signatures (#376)#379
tianzhou merged 2 commits intomainfrom
fix/issue-376-grant-revoke-temp-schema-leak

Conversation

@tianzhou
Copy link
Copy Markdown
Contributor

Summary

  • When using an external database (or embedded postgres with temp schemas) for plan generation, pg_get_function_identity_arguments() qualifies user-defined argument types with the temporary schema name (e.g., pgschema_tmp_20260326_161823_31f3dbda.my_input[])
  • This leaked into GRANT/REVOKE statements, causing apply to fail with ERROR: schema "pgschema_tmp_..." does not exist
  • Fix: normalize Privilege.ObjectName and RevokedDefaultPrivilege.ObjectName to strip schema prefixes from function/procedure argument types during IR normalization

Fixes #376

Test plan

  • Added testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/ test case
  • Test fails before fix (temp schema leaks into plan.sql)
  • Test passes after fix
  • All 14 privilege diff tests pass (no regressions)
  • All function diff tests pass (no regressions)
  • Run: PGSCHEMA_TEST_FILTER="privilege/issue_376" go test -v ./internal/diff -run TestDiffFromFiles
  • Run: PGSCHEMA_TEST_FILTER="privilege/" go test -v ./cmd -run TestPlanAndApply

🤖 Generated with Claude Code

…376)

When using an external database for plan generation, pg_get_function_identity_arguments()
qualifies user-defined argument types with the temporary schema name (e.g.,
pgschema_tmp_20260326_161823_31f3dbda.my_input[]). This leaked into GRANT/REVOKE
statements, causing apply to fail with "schema does not exist".

Fix: normalize privilege ObjectName to strip the schema prefix from function/procedure
argument types, consistent with how other schema objects are normalized.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 27, 2026 11:10
Copy link
Copy Markdown
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

Fixes a planning/apply failure where temporary desired-state schema qualifiers (e.g., pgschema_tmp_...) leak into GRANT/REVOKE ... ON FUNCTION/PROCEDURE signatures, producing invalid SQL at apply time.

Changes:

  • Normalize Privilege.ObjectName and RevokedDefaultPrivilege.ObjectName for FUNCTION/PROCEDURE objects by stripping same-schema qualifiers from argument type references during IR normalization.
  • Add a regression fixture for issue #376 validating the generated plan SQL no longer references the temporary schema.

Reviewed changes

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

Show a summary per file
File Description
ir/normalize.go Adds privilege/revoked-default-privilege signature normalization to remove temp schema qualifiers from function/procedure argument types.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/plan.txt New expected plan output for the regression case.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/plan.sql New expected DDL showing unqualified argument type usage.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/plan.json New expected plan JSON with corrected object paths/SQL.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/old.sql Old-state SQL fixture for the regression test.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/new.sql New-state SQL fixture adding the GRANT/REVOKE statements that previously failed.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/diff.sql Expected diff SQL for the regression case.

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 27, 2026

Greptile Summary

This PR fixes a bug where temporary schema names (e.g. pgschema_tmp_20260326_161823_31f3dbda) leaked into GRANT/REVOKE SQL statements, causing apply to fail with ERROR: schema \"pgschema_tmp_...\" does not exist. The fix normalizes Privilege.ObjectName and RevokedDefaultPrivilege.ObjectName during IR normalization by stripping the schema prefix from function/procedure argument types.\n\nKey changes:\n- ir/normalize.go: New normalizePrivilegeObjectName helper strips schemaName + \".\" from argument type tokens in a function/procedure signature; called from normalizeSchema for all FUNCTION and PROCEDURE privilege entries.\n- testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/: New golden-file test case covering the REVOKE + GRANT round-trip with a user-defined composite type array argument.\n\nNotes:\n- Pointer semantics are correct — schema.Privileges is []*Privilege, so field assignment modifies the original struct.\n- The strings.Split(args, \", \") / strings.Join round-trip is safe even when type names contain \", \" (e.g. numeric(10, 2)), because the same delimiter is used in both directions — though it is inconsistent with the splitTableColumns helper used elsewhere in the same file.\n- No unit tests were added for the new helper function; coverage comes entirely from the integration test fixture.

Confidence Score: 5/5

Safe to merge — the fix is narrowly scoped, pointer semantics are correct, and all remaining findings are minor style/test suggestions.

The only open findings are P2 style observations (use splitTableColumns for consistency; add a unit test for the new helper). Neither blocks correctness or introduces data risk. The core logic is sound, the test passes, and no regressions were introduced in the 14 existing privilege diff tests.

ir/normalize.go — minor style point around argument-splitting approach and lack of unit test for the new helper.

Important Files Changed

Filename Overview
ir/normalize.go Adds normalizePrivilegeObjectName and calls it for FUNCTION/PROCEDURE privileges during schema normalization. Logic is correct: pointer semantics are safe ([]*Privilege), prefix detection/stripping is sound, and guard conditions are thorough.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/new.sql Desired state: creates a composite type, a function taking that type as an array argument, and grants; models the exact scenario that caused the temp-schema leak.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/plan.json Plan JSON fixture confirms schema-prefix-free function signatures in path and sql fields.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/diff.sql Expected diff output showing REVOKE/GRANT without any temp-schema prefix — correctly captures the desired post-fix behavior.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/plan.sql Generated SQL fixture; identical to diff.sql — no temp-schema prefix present, confirming the fix works end-to-end.
testdata/diff/privilege/issue_376_grant_revoke_temp_schema_leak/plan.txt Human-readable plan text fixture; correctly shows clean function signatures in the summary output.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["normalizeSchema\nschema.Name = pgschema_tmp_xxx"] --> B["Iterate schema.Privileges"]
    B --> C{"ObjectType ==\nFUNCTION or PROCEDURE?"}
    C -- No --> D["Skip"]
    C -- Yes --> E["normalizePrivilegeObjectName(objectName, schemaName)"]
    E --> F{"Has parens?"}
    F -- No --> G["Return unchanged"]
    F -- Yes --> H["Extract funcName + args"]
    H --> I{"args empty?"}
    I -- Yes --> G
    I -- No --> J["Split args by ', '"]
    J --> K["For each part: strip schema prefix"]
    K --> L{"Any part changed?"}
    L -- No --> G
    L -- Yes --> M["Reconstruct: funcName + cleaned args"]
    M --> N["priv.ObjectName updated\ne.g. f_test(p_items my_input[])"]
    A --> O["Iterate schema.RevokedDefaultPrivileges"]
    O --> P{"ObjectType ==\nFUNCTION or PROCEDURE?"}
    P -- No --> D
    P -- Yes --> E
Loading

Reviews (1): Last reviewed commit: "fix: strip temp schema prefix from GRANT..." | Re-trigger Greptile

ir/normalize.go Outdated
Comment on lines +674 to +681
parts := strings.Split(args, ", ")
changed := false
for i, part := range parts {
if strings.Contains(part, prefix) {
parts[i] = strings.ReplaceAll(part, prefix, "")
changed = true
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Argument splitting doesn't use the existing splitTableColumns helper

The rest of normalize.go already has a splitTableColumns helper that splits argument lists by top-level commas while respecting nested parentheses (e.g. numeric(10, 2)). This function splits with a plain strings.Split(args, ", ") instead.

In practice the round-trip is safe (split and join with the same ", " delimiter preserves the original string), and pg_get_function_identity_arguments() rarely returns precision-modified types. But it is an inconsistency with the established pattern in this file, and could produce subtly wrong intermediate parts slices if the type list ever contains ", " inside type modifiers—even though the final joined string remains correct.

Consider using splitTableColumns for consistency and robustness:

parts := splitTableColumns(args)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +649 to +688
// normalizePrivilegeObjectName strips schema qualifiers from argument types
// in function/procedure signatures used in GRANT/REVOKE statements.
// e.g., "f_test(p_items pgschema_tmp_20260326_161823_31f3dbda.my_input[])" → "f_test(p_items my_input[])"
func normalizePrivilegeObjectName(objectName, schemaName string) string {
if objectName == "" || schemaName == "" {
return objectName
}

// Find the argument list in the signature: name(args)
parenOpen := strings.Index(objectName, "(")
parenClose := strings.LastIndex(objectName, ")")
if parenOpen < 0 || parenClose < 0 || parenClose <= parenOpen {
return objectName
}

funcName := objectName[:parenOpen]
args := objectName[parenOpen+1 : parenClose]

if args == "" {
return objectName
}

prefix := schemaName + "."

// Split args by comma, strip schema prefix from each type reference, rejoin
parts := strings.Split(args, ", ")
changed := false
for i, part := range parts {
if strings.Contains(part, prefix) {
parts[i] = strings.ReplaceAll(part, prefix, "")
changed = true
}
}

if !changed {
return objectName
}

return funcName + "(" + strings.Join(parts, ", ") + ")"
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 No unit tests for normalizePrivilegeObjectName

Every other normalization helper in this package has a corresponding table-driven unit test in normalize_test.go (e.g. TestStripSchemaPrefixFromBody). The new normalizePrivilegeObjectName function is exercised only through the integration test data file, which makes it harder to catch regressions and edge-case inputs (empty args, no-arg function, array types, multi-arg, prefix not present) without spinning up the full diff pipeline.

Consider adding a TestNormalizePrivilegeObjectName table-driven test alongside the existing helpers in normalize_test.go.

…legeObjectName

Address review feedback:
- Use splitTableColumns helper for arg splitting to correctly handle nested
  parentheses in type modifiers (e.g., numeric(10, 2))
- Add table-driven unit tests covering edge cases: empty inputs, no-arg
  functions, array types, multi-arg, temp schema prefix, precision types

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tianzhou tianzhou requested a review from Copilot March 27, 2026 11:36
@tianzhou tianzhou merged commit cbecb2a into main Mar 27, 2026
3 checks passed
Copy link
Copy Markdown
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 no new comments.


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

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.

pgschema leaks temporary desired-state schema into GRANT/REVOKE ON FUNCTION for user-defined argument types

2 participants