Conversation
Greptile SummaryThis PR fixes PostgreSQL compatibility when modifying functions whose return type or parameter names change. PostgreSQL does not allow Key Changes:
Test Coverage:
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Function Modification Detected] --> B{Only Comment Changed?}
B -->|Yes| C[Generate COMMENT ON FUNCTION]
B -->|No| D{Only Attributes Changed?<br/>LEAKPROOF/PARALLEL}
D -->|Yes| E[Generate ALTER FUNCTION for attributes]
D -->|No| F{Requires Recreate?<br/>Return type or param names changed}
F -->|Yes| G[Generate DROP FUNCTION]
G --> H[Generate CREATE FUNCTION]
H --> I[Check if comment also changed]
F -->|No| J[Generate CREATE OR REPLACE FUNCTION]
J --> K[Check if comment also changed]
E --> L[Check if comment also changed]
I -->|Changed| M[Generate COMMENT]
K -->|Changed| M
L -->|Changed| M
Last reviewed commit: ccb9399 |
There was a problem hiding this comment.
Pull request overview
Updates function diffing/migration generation to handle PostgreSQL’s limitation where CREATE OR REPLACE FUNCTION cannot change certain function signature properties (e.g., return type, parameter names), by emitting DROP FUNCTION followed by CREATE FUNCTION when needed.
Changes:
- Add
functionRequiresRecreate()and use it in function modification planning to emitDROP FUNCTION IF EXISTS+CREATE OR REPLACE FUNCTION. - Refactor function drop SQL generation into shared
generateDropFunctionSQL()helper. - Add new regression fixtures for issue #326 covering return type change, parameter type change, and parameter name change.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/diff/function.go | Adds recreate detection and emits DROP+CREATE for incompatible function modifications; refactors DROP SQL generation into a helper. |
| testdata/diff/create_function/issue_326_return_type_change/plan.txt | New expected human plan output for return type change case. |
| testdata/diff/create_function/issue_326_return_type_change/plan.sql | New expected SQL plan for return type change case. |
| testdata/diff/create_function/issue_326_return_type_change/plan.json | New expected JSON plan for return type change case. |
| testdata/diff/create_function/issue_326_return_type_change/old.sql | Old schema fixture for return type change. |
| testdata/diff/create_function/issue_326_return_type_change/new.sql | New schema fixture for return type change. |
| testdata/diff/create_function/issue_326_return_type_change/diff.sql | Expected migration SQL for return type change. |
| testdata/diff/create_function/issue_326_param_type_change/plan.txt | New expected human plan output for parameter type change case. |
| testdata/diff/create_function/issue_326_param_type_change/plan.sql | New expected SQL plan for parameter type change case. |
| testdata/diff/create_function/issue_326_param_type_change/plan.json | New expected JSON plan for parameter type change case. |
| testdata/diff/create_function/issue_326_param_type_change/old.sql | Old schema fixture for parameter type change. |
| testdata/diff/create_function/issue_326_param_type_change/new.sql | New schema fixture for parameter type change. |
| testdata/diff/create_function/issue_326_param_type_change/diff.sql | Expected migration SQL for parameter type change. |
| testdata/diff/create_function/issue_326_param_name_change/plan.txt | New expected human plan output for parameter name change case. |
| testdata/diff/create_function/issue_326_param_name_change/plan.sql | New expected SQL plan for parameter name change case. |
| testdata/diff/create_function/issue_326_param_name_change/plan.json | New expected JSON plan for parameter name change case. |
| testdata/diff/create_function/issue_326_param_name_change/old.sql | Old schema fixture for parameter name change. |
| testdata/diff/create_function/issue_326_param_name_change/new.sql | New schema fixture for parameter name change. |
| testdata/diff/create_function/issue_326_param_name_change/diff.sql | Expected migration SQL for parameter name change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for i := range oldParams { | ||
| if oldParams[i].Name != newParams[i].Name { | ||
| return true | ||
| } |
There was a problem hiding this comment.
functionRequiresRecreate currently only compares parameter names (and count) after filtering out TABLE params. If an OUT parameter’s type or mode changes (which does not affect Function.GetArguments() and therefore can still show up as a “modify”), this will fall through to CREATE OR REPLACE FUNCTION and PostgreSQL will reject it. Consider extending this check to also compare OUT/INOUT parameter mode and data type (at least for non-input parameters), not just .Name.
| for i := range oldParams { | |
| if oldParams[i].Name != newParams[i].Name { | |
| return true | |
| } | |
| for i := range oldParams { | |
| // Name changes always require a DROP+CREATE | |
| if oldParams[i].Name != newParams[i].Name { | |
| return true | |
| } | |
| // For OUT/INOUT parameters, a change in mode or data type (or other | |
| // signature-relevant attributes) also requires DROP+CREATE, since | |
| // PostgreSQL does not allow CREATE OR REPLACE FUNCTION to alter them. | |
| if (oldParams[i].Mode == "OUT" || oldParams[i].Mode == "INOUT") || | |
| (newParams[i].Mode == "OUT" || newParams[i].Mode == "INOUT") { | |
| if !parameterEqual(oldParams[i], newParams[i]) { | |
| return true | |
| } | |
| } |
| dropSQL := generateDropFunctionSQL(oldFunc, targetSchema) | ||
| createSQL := generateFunctionSQL(newFunc, targetSchema) | ||
|
|
||
| alterContext := &diffContext{ | ||
| Type: DiffTypeFunction, | ||
| Operation: DiffOperationAlter, |
There was a problem hiding this comment.
Dropping and recreating a function inside the modify phase can fail if any existing objects depend on it (e.g., views or triggers), since PostgreSQL will block DROP FUNCTION ... under RESTRICT (the default). There doesn’t appear to be corresponding logic here to pre-drop/recreate dependent objects for this “recreate” path (unlike the drop phase which handles triggers for dropped functions). Consider extending dependency handling (or explicitly using CASCADE with controlled recreation) so this migration path is reliable when dependencies exist.
…ge (#326) PostgreSQL does not allow CREATE OR REPLACE FUNCTION to change the return type, parameter names, or OUT/INOUT parameter types of an existing function. When these change (but input parameter types stay the same, keeping the same function signature key), pgschema now generates DROP FUNCTION followed by CREATE FUNCTION instead of just CREATE OR REPLACE FUNCTION. Also extracts generateDropFunctionSQL helper to share DROP statement generation between the recreate path and the existing drop path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ccb9399 to
dd5122c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } else if functionRequiresRecreate(oldFunc, newFunc) { | ||
| // Return type, OUT parameters, or parameter names changed - must DROP then CREATE | ||
| // PostgreSQL does not allow CREATE OR REPLACE to change these. | ||
| // See https://github.com/pgplex/pgschema/issues/326 | ||
| dropSQL := generateDropFunctionSQL(oldFunc, targetSchema) |
There was a problem hiding this comment.
The DROP+CREATE path for incompatible function changes can still fail at runtime if any existing objects depend on the function (e.g., views, policies, triggers on unchanged tables). PostgreSQL will reject DROP FUNCTION with dependent objects present, and this branch runs during the modify phase without any dependency pre-drop/recreate handling. Consider adding dependency handling similar to view recreation (drop/recreate dependents around the function recreation), or reordering so dependents are updated/dropped before the function is dropped; adding a regression test with a view depending on the function would help prevent plan failures.
Summary
CREATE OR REPLACE FUNCTIONbecause it cannot alter these propertiesfunctionRequiresRecreate()and generatesDROP FUNCTIONfollowed byCREATE FUNCTIONinsteadgenerateDropFunctionSQLinto a shared helper to eliminate duplicationFixes #326
Test plan
PGSCHEMA_TEST_FILTER="create_function/issue_326" go test -v ./internal/diff -run TestDiffFromFiles— 3 new test cases (return type change, param type change, param name change)PGSCHEMA_TEST_FILTER="create_function/" go test -v ./internal/diff -run TestDiffFromFiles— all 8 function diff tests passPGSCHEMA_TEST_FILTER="create_function/" go test -v ./cmd -run TestPlanAndApply— all 8 function integration tests passPGSCHEMA_TEST_FILTER="dependency/" go test -v ./internal/diff -run TestDiffFromFiles— all 13 dependency tests pass🤖 Generated with Claude Code