Skip to content

feat: --omit-schema flag to drop the sole schema's qualifier from output - #69

Open
Apakottur wants to merge 3 commits into
mainfrom
feat/omit-schema
Open

feat: --omit-schema flag to drop the sole schema's qualifier from output#69
Apakottur wants to merge 3 commits into
mainfrom
feat/omit-schema

Conversation

@Apakottur

@Apakottur Apakottur commented Jul 12, 2026

Copy link
Copy Markdown
Owner

What

Adds --omit-schema NAME to pgmig generate (and omit_schema= to pgmig.generate): omits that schema's qualifier from the emitted SQL, for a readable diff when a database uses a single schema (typically public). Guarded by validation that NAME is the only user schema in both databases — otherwise a clean PgmigError — so an unqualified name can never be ambiguous.

Behavior

With --omit-schema public (single-schema databases):

source target emitted SQL
absent person table CREATE TABLE "person" ("id" integer);
absent index on person CREATE INDEX person_id_idx ON person USING btree (id);
absent FK to team ALTER TABLE "person" ADD CONSTRAINT "person_team_fk" FOREIGN KEY (team_id) REFERENCES team(id);
absent function CREATE OR REPLACE FUNCTION bump(n integer) ...

Without the flag, output is unchanged (fully qualified, as today). With the flag but a second user schema present (or the name wrong): omit_schema 'public' requires the source database to contain exactly that one user schema, but found: [...].

Applies to every emitted object path: tables (incl. OWNER TO), columns, indexes, constraints, triggers, functions, sequences, enums, views, materialized views, domains and composite types.

Notes

The rendering policy is a field on the diff context introduced in #80: context_scope(..., omit_schema=...) carries it for the duration of one generate call, and the rendering helpers in _sql.py read context.omit_schema (lenient — None outside any diff scope, so they stay usable as plain functions). Emit sites call schema_qualified(schema, *rest) with no parameter threading through the diff helpers.

No single mechanism covers all emitted SQL, so this works in three legs:

  • pgmig-built paths go through schema_qualified, which drops the matching schema segment.
  • Server-generated definitions (pg_get_indexdef/functiondef/triggerdef/constraintdef/viewdef) are introspected with the schema on the search_path, so deparse drops the qualifiers it resolves semantically (FK REFERENCES targets, trigger EXECUTE FUNCTION, view bodies) — without ever touching string literals. This intentionally trades the empty-search_path portability property from fix: introspect with empty search_path for deterministic, portable SQL #58 when the flag is used; the default (omit_schema=None) keeps the empty search_path.
  • Spots deparse always qualifies regardless of search_path — the index/trigger ON schema.table clause and a routine's own header name — are edited textually by anchored helpers (strip_on_clause_qualifier, strip_routine_name_qualifier) that try each identifier in its quoted and unquoted deparse form. If no form matches, the definition is returned unchanged (valid SQL, just qualified) rather than risking a wrong edit.

Deliberately untouched: CREATE SCHEMA / COMMENT ON SCHEMA and the extension SCHEMA x clause name the schema as their subject, not as a qualifier. Error diagnostics also honor the omission (unlike an earlier revision of this PR): validation guarantees a sole user schema, so an unqualified diagnostic stays unambiguous.

Existing tests are untouched (default path unchanged); new coverage: unit tests for schema_qualified, both strip helpers (quoted/unquoted/fallback forms) and the context-scope reset (incl. on exception), plus integration tests proving no public token survives anywhere in the output across every supported object kind — and that the migration still converges on apply.

Adds --omit-schema NAME to `pgmig generate` (and omit_schema= to the
library API): omits that schema's qualifier from the emitted SQL, for a
readable diff when a database uses a single schema (typically public).
Validated to be the only user schema in both databases.

The rendering policy lives in a ContextVar in _sql.py, set for the
duration of one generate call via omit_schema_context(); emit sites
call schema_qualified() with no parameter threading through the diff
helpers. Server-generated definitions are unqualified by introspecting
with the schema on the search_path; the two spots deparse always
qualifies (index/trigger ON clause, routine header name) are edited by
anchored textual helpers that fall back to unchanged output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017CH61Q77pT875X1qYZXfTA
The omit-schema rendering policy now lives as a field on _ContextData
(set via context_scope) instead of a separate ContextVar in _sql.py.
The rendering helpers read context.omit_schema, which is lenient (None
outside any diff scope) so they stay usable as plain functions.
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.

1 participant