warn-set-schema
ALTER ... SET SCHEMA breaks every schema-qualified reference to the object and changes how unqualified ones resolve.
- operation
- Schemas
- lock taken
- ACCESS EXCLUSIVE
- remediation
- Multi-step plan
- category
- Data safety
- Breaks a rolling deploy
What triggers it
Any AlterObjectSchemaStmt node, with no further condition. objectType is read only to label the message (OBJECT_TABLE, OBJECT_VIEW, OBJECT_MATVIEW, OBJECT_SEQUENCE, OBJECT_FUNCTION, OBJECT_PROCEDURE, OBJECT_TYPE, OBJECT_DOMAIN, OBJECT_FOREIGN_TABLE, falling back to Object), and the name comes from relation.relname or, for non-relations, from object via List, ObjectWithArgs.objname, or String.
What does not
Nothing. There is no early return past the node check, no catalog lookup, no size or traffic gate — every SET SCHEMA in the file is reported, including a move of a table created three statements earlier in the same migration.
Where it applies
Applies to every PostgreSQL version MigrationPilot targets. It works on the SQL text alone — no database connection needed.
The lock, and what it blocks
ALTER ... SET SCHEMA takes ACCESS EXCLUSIVE on the object, but only long enough to update pg_class.relnamespace. It rewrites nothing and is over in milliseconds, so the lock is not the risk. The risk is that the old schema-qualified path stops resolving at the instant of commit, with no overlap window on either side.
Why it matters
Moving an object between schemas is a rename in every way that matters, and it takes effect all at once — every query naming it as old_schema.thing starts failing the instant the migration commits. There is no deprecation window, and old and new application versions overlap during a rolling restart. Unqualified references are the more insidious half: they resolve through search_path, which is per-role, so whether they still work depends on who is asking. Your psql session looks fine while the application role is broken.
Unsafe, and safe
Flagged
ALTER TABLE users SET SCHEMA archive; -- Every "public.users" reference breaks at commit
Safe alternative
-- Leave the table where it is and expose it at the new path, so both -- paths work while the application migrates. Move it for real in a -- later migration, once nothing references the old one. CREATE VIEW archive.users AS SELECT * FROM public.users;
Deploy and transaction boundaries
The old path and the new one are never both valid, so a rolling deploy where old and new application versions overlap has no safe instant. The compatibility view in the safe alternative is what creates the window, and removing it is a separate later migration.
What it assumes
The rule cannot see whether anything references the object by its old qualified name, so moving a brand-new table nothing has ever queried draws the same finding as moving public.users. It also cannot know any role's search_path, which is what decides whether unqualified references survive — that is per-role and per-session, so the migration user's path says nothing about the application user's, and testing the move by hand in psql can be actively misleading.
What the CLI prints
⚠ [MP098] WARNING (line 1)
Table "users" is being moved from its current schema to "archive". Every schema-qualified reference to it breaks at commit, and unqualified references now resolve only for roles whose search_path includes "archive".
Safe alternative:
-- Give callers an overlap window instead of a hard cutover. Move the
-- object, then leave a view behind at the old path:
ALTER TABLE users SET SCHEMA archive;
CREATE VIEW users AS
SELECT * FROM archive.users;
-- Ship the application change, confirm nothing reads the old path, then
-- drop the compatibility view in a later migration:
-- DROP VIEW users;
-- Check what still references the old path before cutting over:
SELECT * FROM pg_depend WHERE refobjid = 'users'::regclass;
Why: Moving an object between schemas is a rename in every way that matters. Queries that named it as old_schema.object start failing with "relation does not exist" the moment the migration commits, and there is no deprecation window: the old path stops working at the same instant the new one starts. Unqualified references are worse, because whether they still resolve depends on each role's search_path, so the migration can succeed, your psql session can look fine, and the application can still be broken. Views and functions that reference the object follow it by dependency, but the SQL your application ships does not.
Docs: https://migrationpilot.dev/rules/mp098Generated by running the CLI's own formatter over the flagged example above, so it is the text the tool actually produces. A real run also reports the other rules that fire on the same statement; those blocks are left out here.
Turning it off
For one statement, put a comment on the line before it:
-- migrationpilot-disable MP098 ALTER TABLE users SET SCHEMA archive;
For the whole project, in .migrationpilotrc.yml — by name or by id:
rules:
MP098: false
# or keep it, and downgrade it
rules:
MP098:
severity: warningTry it
Open this rule's flagged example in the playground. It runs in your browser — edit it and watch the finding appear and disappear.
Run MP098 in the playground