feat(dump): add --qualify-schema to force full schema qualification (#321)#492
Conversation
…WIP] Foundation for `dump --qualify-schema` (issue #321), default-false so plan/apply and existing dump output are unchanged: - cmd/dump: `--qualify-schema` flag + `DumpConfig.QualifySchema`; dump uses `diff.GenerateMigrationWithOptions(...)` and passes the option to the formatter. - internal/diff: `GenerateMigrationWithOptions` carries the option on `diffCollector` (the seam reaching every generator). - ir/quote.go, internal/diff: mode variants of the three qualify helpers (`QualifyEntityNameWithQuotesMode`, `qualifyEntityNameMode`, `stripSchemaPrefixMode`) that always qualify / preserve the prefix when enabled. - internal/dump/formatter: comment headers keep the target schema name (not "-") when qualifySchema is on. TODO (next commit): thread collector.qualifySchema from the create-path generators into the leaf builders and swap their qualify-helper calls to the *Mode variants so the flag actually forces qualification; then tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
generateCreateTablesSQL now passes collector.qualifySchema into generateTableSQL, which threads it to writeColumnDefinitionToBuilder. Table names, LIKE-clause source tables, and column type names now stay schema-qualified when --qualify-schema is set (via the *Mode helper variants). Default (false) output is unchanged. Still TODO: thread the remaining create generators (constraints/FK refs, functions, sequences, views, types, indexes, triggers, policies, privileges) + tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
generateFunctionSQL / formatFunctionParameter take qualifySchema (threaded from collector at all call sites); generateFunctionComment reads collector.qualifySchema. Function name, parameter types/defaults, and return type now stay schema-qualified under --qualify-schema. Covers issue #321's CREATE FUNCTION example. Default unchanged. Remaining create generators: sequence, type, view, constraint(FK refs), index, trigger, policy, privilege, aggregate. Then tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e paths [WIP] - generateSequenceSQL: sequence name and OWNED BY owner table qualify under the flag. - generateConstraintSQL: FK REFERENCES target table qualifies under the flag (threaded from generateTableSQL's inline-constraint loop). - Updated test callers to pass qualifySchema=false (default behavior). Remaining: type/domain attrs, aggregate state types, views, indexes, triggers, policies, privileges. Then tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hs [WIP] - generateTypeSQL: type/domain/composite name and composite attribute types qualify. - generateAggregateSQL: aggregate name and STYPE/MSTYPE state types qualify. - generateAggregateComment: name qualifies (reads collector.qualifySchema). Covered now: tables/columns, functions, sequences, constraints(FK), types, aggregates. Remaining: views, indexes, triggers, policies, privileges; then tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…reate paths [WIP] - generateViewSQL: view name qualifies under the flag (the targetSchema!="" branch now honors qualifySchema; the targetSchema=="" branch already always-qualified). - index/view COMMENT ON statements qualify the object name. - generateTriggerSQLWithMode: CREATE TRIGGER ON <table> qualifies the table. - generateRLSChangesSQL: ALTER TABLE ... ROW LEVEL SECURITY qualifies the table. - Updated all builder callers to pass collector.qualifySchema; test callers pass false. Structural entity names/types now qualify consistently across object types. Per design, free-form expression text (view bodies, policy USING/CHECK, trigger function bodies) is left as-is. Privilege generators don't use the qualify helpers (out of scope). Next: positive + negative byte-identical tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fast builder-level tests for --qualify-schema: table name + column type, FK referenced table, enum type, and sequence name + OWNED BY owner. Each asserts the forced-qualification output AND that the default (qualifySchema=false) output is unchanged — the negative cases guard against any default-behavior drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Greptile SummaryThis PR adds an opt-in dump mode for fully schema-qualified SQL. The main changes are:
Confidence Score: 4/5Forced qualification is incomplete across several dump SQL paths.
internal/diff/procedure.go, internal/diff/type.go, internal/diff/table.go, internal/diff/index.go Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[dump --qualify-schema] --> B[DumpConfig.QualifySchema]
B --> C[GenerateMigrationWithOptions]
C --> D[diffCollector.qualifySchema]
D --> E[Updated create-path generators]
D -. not fully threaded .-> F[Procedures]
D -. not fully threaded .-> G[ALTER/deferred table paths]
D -. partial .-> H[Index DDL]
E --> I[Fully qualified SQL]
F --> J[Unqualified SQL under the flag]
G --> J
H --> J
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[dump --qualify-schema] --> B[DumpConfig.QualifySchema]
B --> C[GenerateMigrationWithOptions]
C --> D[diffCollector.qualifySchema]
D --> E[Updated create-path generators]
D -. not fully threaded .-> F[Procedures]
D -. not fully threaded .-> G[ALTER/deferred table paths]
D -. partial .-> H[Index DDL]
E --> I[Fully qualified SQL]
F --> J[Unqualified SQL under the flag]
G --> J
H --> J
Reviews (1): Last reviewed commit: "style(dump): gofmt struct alignment for ..." | Re-trigger Greptile |
There was a problem hiding this comment.
Pull request overview
This PR adds an opt-in pgschema dump --qualify-schema flag intended to force fully schema-qualified DDL output (instead of the default “smart qualification” that omits the target-schema prefix), by threading a qualifySchema option through the shared internal/diff SQL generators and adjusting dump formatting to keep schema names in comment headers.
Changes:
- Adds
diff.GenerateMigrationWithOptions(..., qualifySchema)and stores the option ondiffCollectorso generators can opt into forced qualification without changing default plan/apply behavior. - Introduces mode-aware qualification helpers (e.g.,
QualifyEntityNameWithQuotesMode,qualifyEntityNameMode,stripSchemaPrefixMode) and threads the option through multiple SQL builders. - Adds dump CLI/config plumbing for
--qualify-schema, updates dump formatter behavior, and adds targeted builder-level tests for qualification behavior.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| ir/quote.go | Adds QualifyEntityNameWithQuotesMode to support forced qualification with proper quoting. |
| internal/dump/formatter.go | Adds qualifySchema to formatter and preserves schema name in comment headers when forced qualification is enabled. |
| internal/diff/view.go | Threads qualifySchema into view SQL generation and qualification for view comments/triggers. |
| internal/diff/type.go | Threads qualifySchema into type SQL generation and type-attribute datatype qualification. |
| internal/diff/trigger.go | Threads qualifySchema into trigger SQL generation (target table qualification). |
| internal/diff/table.go | Adds stripSchemaPrefixMode and threads qualifySchema through CREATE TABLE + column/type handling. |
| internal/diff/sequence.go | Threads qualifySchema into sequence naming and OWNED BY qualification. |
| internal/diff/qualify_schema_test.go | Adds new builder-level tests for forced qualification vs default behavior. |
| internal/diff/policy.go | Applies forced qualification to RLS ALTER TABLE statements. |
| internal/diff/index.go | Applies forced qualification to index comment targets. |
| internal/diff/identifier_quote_test.go | Updates tests to reflect new builder signatures/options plumbing. |
| internal/diff/function.go | Threads qualifySchema into function SQL generation including params/return types/defaults and comments. |
| internal/diff/diff.go | Adds GenerateMigrationWithOptions and qualifyEntityNameMode; stores the option on the collector. |
| internal/diff/constraint.go | Threads qualifySchema into FK referenced-table qualification. |
| internal/diff/collector.go | Stores qualifySchema on the collector as shared generator configuration. |
| internal/diff/aggregate.go | Threads qualifySchema into aggregate naming and state-type qualification. |
| cmd/dump/multifile_test.go | Updates formatter construction for the new NewDumpFormatter signature. |
| cmd/dump/dump.go | Adds --qualify-schema flag, wires it into config, calls GenerateMigrationWithOptions, and passes through to formatter. |
| cmd/dump/dump_test.go | Updates formatter construction for the new NewDumpFormatter signature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
# Conflicts: # internal/diff/table.go # internal/diff/view.go
pg-codex local review found create-path qualification sites outside the original three-helper inventory. A full re-audit of all four qualify helpers (getTableNameWithSchema, qualifyEntityName, stripSchemaPrefix, QualifyEntityNameWithQuotes) against the empty->target dump path turned up these unthreaded create-path emitters: - CREATE INDEX ... ON <table> (generateIndexSQLWithName) - CREATE POLICY ... ON <table> (generatePolicySQL) - CREATE PROCEDURE name + param type (generateProcedureSQL / formatParameterString) - COMMENT ON PROCEDURE/SEQUENCE/TABLE/COLUMN - deferred FK ALTER TABLE + REFERENCES (generateDeferredConstraintsSQL / generateForeignKeyClause) Adds getTableNameWithSchemaMode and Mode variants for the index/policy/ FK-clause helpers; threads qualifySchema through the procedure path mirroring the function path. Modify/drop callers keep the delegating defaults, so plan/apply output is unchanged. Tests: extends qualify_schema_test.go with index ON-table, policy ON-table, procedure (name + same-schema param type + DEFAULT), and deferred FK REFERENCES coverage (positive + bare-default negative). Full ./internal/diff/... suite passes with all existing fixtures unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses greptile P1: the domain BaseType branch emitted typeObj.BaseType directly while composite attribute types already went through stripSchemaPrefixMode. Route BaseType through the same helper so a cross-schema (or target-schema-prefixed) base type is preserved under forced qualification and the target-schema prefix is stripped otherwise. Default output is unchanged (full ./internal/diff/... suite passes with all fixtures intact). Note: same-schema user-defined base types are stored bare by the inspector, so this cannot add a prefix to them — that limitation is shared by all type-reference positions and is tracked separately from object-identifier qualification. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clarify that the flag forces qualification of object identifiers and notes the known limitation that same-schema type references are not affected (the IR stores them without schema identity). Tracked for a follow-up inspector/IR change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- procedure.go: the recreate (DROP+CREATE) path now passes collector.qualifySchema to the CREATE half for coherence — a CREATE statement should honor forced qualification. Byte-identical in practice (only `dump` sets the flag, and dump is empty->target / create-only, so the modify path never runs with qualifySchema=true). - qualify_schema_test.go: the table-column and procedure-parameter tests used pre-qualified type strings (public.user_kind / public.currency) that the inspector never produces for same-schema types. Switched to bare type refs and inverted the assertions to guard the #493 boundary: the object *name* is qualified under the flag, while the same-schema type *reference* stays bare (forced qualification cannot invent a schema for a bare ref). - formatter_test.go (new): table-driven coverage for getCommentSchemaName, exercising qualifySchema=true (target schema preserved vs collapsed to "-"), cross-schema, and unqualified paths. go build/vet, full ./internal/diff/... + ./internal/dump/... suites pass; gofmt clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…f limitation The file-level comment still claimed names/types are always qualified; the tests now document that bare same-schema type references stay bare under forced qualification (#493). Comment-only change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses Copilot: the docstring claimed qualifySchema=true makes the emitted DDL "always" schema-qualify entity names, but DROP/ALTER-in-place paths keep smart qualification. Reword to scope the guarantee to the create-path (empty->target) object identifiers that dump actually emits, and note the #493 same-schema type-reference limitation. Comment-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Fixes #321.
What
Adds a
pgschema dump --qualify-schemaflag. By default, dump uses "smart qualification" — it omits the schema prefix for objects in the target schema. With--qualify-schema, dump always emits fully schema-qualified names (e.g.public.users,account public.user_kind), which:user), per Respect quoted identifiers in plpgsql functions when dumping schema #320, andHow
The dump builds its SQL via
diff.GenerateMigration(empty → target), so qualification lives in the sharedinternal/diffpackage (also used by plan/apply). To keep this dump-scoped and opt-in:GenerateMigrationWithOptions(old, new, targetSchema, qualifySchema)carries the option ondiffCollector(the seam every generator already receives).GenerateMigrationremains a thin wrapper passingfalse, so plan/apply behavior is unchanged.QualifyEntityNameWithQuotesMode,qualifyEntityNameMode,stripSchemaPrefixMode— with the originals delegating withfalse.qualifySchemafrom the collector down to those helpers.dumpadds the--qualify-schemaflag →DumpConfig.QualifySchema, callsGenerateMigrationWithOptions, and passes it toDumpFormatterso comment headers keep the schema name (instead of-) when forced.Coverage
Forced qualification applies to the structural names/types pgschema generates: tables/columns (+LIKE), functions (name/params/return), sequences (+OWNED BY), FK references, types/domains/composite attrs, aggregates (+STYPE/MSTYPE), views, index/view
COMMENTs,CREATE TRIGGERtarget table, andALTER TABLE … ROW LEVEL SECURITY.Out of scope (intentional)
Free-form expression text — view bodies, policy
USING/CHECK, trigger function bodies — is left as-is (that SQL may already contain qualified identifiers; rewriting it would require full parsing). Privilege generators don't use the qualify helpers.Tests
internal/diff/qualify_schema_test.go: builder-level tests for table+column type, FK reference, enum type, and sequence (+OWNED BY) — each asserting forced-qualification output and that the default (false) output is unchanged.go test ./internal/diff/...passes — existing fixtures unchanged, confirming no default-behavior drift.🤖 Generated with Claude Code
Scope & known limitation
--qualify-schemaforces full schema qualification for object identifiers — table / view / index / sequence / type / function / procedure / policy names, andON/REFERENCES/OWNED BY/COMMENTtargets. This covers the reserved-word / ambiguous-identifier class from #320.It does not qualify same-schema type references (column types, composite attribute types, function/procedure params + returns, aggregate state types, domain base types). The IR stores same-schema user-defined types as bare strings, so the render-side helper can only strip a target-schema prefix, not add one. Cross-schema type references already arrive qualified and are preserved. Inspector/IR work to enable type-reference qualification is tracked in #493.
Free-form expression text (view bodies, policy
USING/CHECK, trigger function bodies) is intentionally left as-is.