fix: preserve schema qualifiers in function/procedure bodies (#354)#358
fix: preserve schema qualifiers in function/procedure bodies (#354)#358
Conversation
Previously, schema qualifiers (e.g., public.test) were unconditionally stripped from function/procedure bodies during normalization. This broke functions with restrictive search_path settings (SET search_path = '', pg_temp, etc.) where qualified references are required. Changes: - Move schema stripping from normalization time to comparison time - Make stripSchemaQualifications skip dollar-quoted blocks so function bodies applied to embedded postgres preserve their qualifiers - Export StripSchemaPrefixFromBody for use by the diff package - Fix dollar-quote regex to match PostgreSQL grammar (no $1$ false positives) - Add unit tests for splitDollarQuotedSegments Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
f5a0869 to
4708a17
Compare
Greptile SummaryThis PR fixes a bug (#354) where schema qualifiers (e.g.,
Key changes by file:
Confidence Score: 4/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User SQL / pgschema dump] --> B[stripSchemaQualifications]
B --> C{splitDollarQuotedSegments}
C -->|Non-dollar-quoted segments| D[stripSchemaQualificationsFromText\nStrips schema.object patterns]
C -->|Dollar-quoted blocks $$...$$| E[Preserved verbatim\nfunction body kept intact]
D --> F[Assembled SQL for embedded Postgres]
E --> F
F --> G[Embedded Postgres inspection\nnormalizeIR]
G --> H[normalizeFunction / normalizeProcedure\nNO schema stripping at normalization]
H --> I[IR with original qualifiers preserved\ne.g., public.test in body]
I --> J{Diff comparison\ndefinitionsEqualIgnoringSchema}
J --> K[StripSchemaPrefixFromBody on both sides\nCompare stripped versions]
K -->|Equal| L[No diff generated]
K -->|Different| M[Generate CREATE OR REPLACE FUNCTION\nUsing IR body with qualifiers preserved]
style E fill:#90EE90
style I fill:#90EE90
style K fill:#ADD8E6
|
There was a problem hiding this comment.
Pull request overview
This PR addresses incorrect handling of schema-qualified references inside function/procedure bodies (notably when SET search_path = ''), by preserving qualifiers in the IR/DDL output while still allowing schema-insensitive comparisons during diffing.
Changes:
- Stop stripping same-schema qualifiers from function/procedure bodies during IR normalization; instead, ignore qualifier differences only at diff comparison time.
- Update function SQL generation to correctly render empty
search_path(""stored in catalog) asSET search_path = ''. - Update desired-state SQL preprocessing to avoid stripping schema qualifiers inside dollar-quoted blocks, and add unit tests for the dollar-quote segmentation logic; update golden fixtures accordingly.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
internal/postgres/desired_state.go |
Skip schema-qualification stripping inside dollar-quoted blocks when preparing desired-state SQL for temp-schema application. |
internal/postgres/desired_state_test.go |
Add unit tests for splitting SQL into dollar-quoted vs non-quoted segments. |
ir/normalize.go |
Export StripSchemaPrefixFromBody; stop stripping qualifiers in function/procedure normalization while keeping view normalization behavior. |
ir/normalize_test.go |
Update tests to call exported StripSchemaPrefixFromBody. |
internal/diff/function.go |
Render empty search_path correctly; compare definitions via definitionsEqualIgnoringSchema instead of raw string equality. |
internal/diff/procedure.go |
Compare procedure definitions via definitionsEqualIgnoringSchema instead of raw string equality. |
testdata/diff/create_function/issue_354_empty_search_path/plan.txt |
Add golden output for issue #354 plan (human format). |
testdata/diff/create_function/issue_354_empty_search_path/plan.sql |
Add golden output for issue #354 plan (SQL format). |
testdata/diff/create_function/issue_354_empty_search_path/plan.json |
Add golden output for issue #354 plan (JSON format). |
testdata/diff/create_function/issue_354_empty_search_path/old.sql |
Add fixture for “old” state (table only). |
testdata/diff/create_function/issue_354_empty_search_path/new.sql |
Add fixture for “new” state (table + function with empty search_path). |
testdata/diff/create_function/issue_354_empty_search_path/diff.sql |
Add expected migration SQL for issue #354. |
testdata/diff/dependency/table_to_function/plan.txt |
Update fixture to preserve schema-qualified table reference in function body. |
testdata/diff/dependency/table_to_function/plan.sql |
Update fixture to preserve schema-qualified table reference in function body. |
testdata/diff/dependency/table_to_function/plan.json |
Update fixture to preserve schema-qualified table reference in function body. |
testdata/diff/dependency/table_to_function/diff.sql |
Update expected migration SQL to preserve schema-qualified table reference. |
testdata/dump/issue_252_function_schema_qualifier/pgschema.sql |
Update dump fixture to expect preserved schema-qualified references inside bodies. |
testdata/dump/issue_320_plpgsql_reserved_keyword_type/pgschema.sql |
Update dump fixture to expect preserved schema-qualified references inside bodies. |
testdata/diff/create_trigger/add_trigger/plan.json |
Update fingerprint hash due to fixture changes elsewhere. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| // Split SQL into dollar-quoted and non-dollar-quoted segments. | ||
| // Schema qualifiers inside function/procedure bodies (dollar-quoted blocks) | ||
| // must be preserved — the user may need them when search_path doesn't include | ||
| // the function's schema (e.g., SET search_path = ''). (Issue #354) | ||
| segments := splitDollarQuotedSegments(sql) | ||
| var result strings.Builder | ||
| result.Grow(len(sql)) | ||
| for _, seg := range segments { | ||
| if seg.quoted { | ||
| // Preserve dollar-quoted content as-is | ||
| result.WriteString(seg.text) | ||
| } else { | ||
| result.WriteString(stripSchemaQualificationsFromText(seg.text, schemaName)) | ||
| } | ||
| } | ||
| return result.String() |
| pattern4 := fmt.Sprintf(`(?:^|[^"])%s\.([a-zA-Z_][a-zA-Z0-9_$]*)`, escapedSchema) | ||
| re4 := regexp.MustCompile(pattern4) | ||
|
|
||
| result := sql | ||
| result := text | ||
| // Apply in order: quoted schema first to avoid double-matching | ||
| result = re1.ReplaceAllString(result, "$1") | ||
| result = re2.ReplaceAllString(result, "$1") |
Summary
public.test) were unconditionally stripped from function/procedure bodies during normalization. This broke functions with restrictivesearch_pathsettings (SET search_path = '',pg_temp, etc.) where qualified references are required.definitionsEqualIgnoringSchemain diff package), so the IR preserves the original body for correct DDL generationstripSchemaQualificationsskip dollar-quoted blocks so function bodies applied to embedded postgres preserve their qualifiers$1$false positives on parameter references)Fixes #354 (Bug 2: schema qualifiers stripped from function bodies)
Depends on #357 (Bug 1: empty search_path rendering)
Test plan
create_function/issue_354_empty_search_path— now expectspublic.testpreserved in bodysplitDollarQuotedSegments(parameter references, nested tags, unterminated quotes)dependency/table_to_function,issue_252,issue_320)🤖 Generated with Claude Code