ban-char-field
CHAR(n) pads with spaces, wastes storage, and causes comparison bugs.
- operation
- Columns
- lock taken
- no table lock
- remediation
- Fixed by --fix
- category
- Types & schema style
- Can lose data
What triggers it
The same two entry points as MP040 — CreateStmt.tableElts[].ColumnDef, and AlterTableStmt commands with subtype AT_AddColumn. A column matches when typeName.names[].String.sval contains bpchar or char and does not contain varying. CHAR(2) and CHARACTER(2) both lower to [pg_catalog, bpchar].
What does not
VARCHAR(n) and CHARACTER VARYING(n), which lower to [pg_catalog, varchar] and so fail the bpchar/char test outright — MP037 owns those. TEXT. Any statement that is not a CreateStmt or an ADD COLUMN. Note that the !typeNames.some(n => n === "varying") guard is vestigial: the parser never emits varying as a name component, so that clause never changes the outcome.
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
No lock consequence. This is a storage and comparison-semantics rule. bpchar blank-pads every value out to the declared width and strips trailing blanks again on comparison and on cast, so a value whose trailing whitespace was meaningful does not come back the way it went in, and LIKE against a padded value behaves differently from =.
Why it matters
CHAR(n) right-pads values with spaces to the declared length, wasting storage and causing subtle comparison bugs. Use TEXT or VARCHAR instead.
Unsafe, and safe
Flagged
ALTER TABLE users ADD COLUMN country_code CHAR(2);
Safe alternative
ALTER TABLE users ADD COLUMN country_code TEXT;
What it assumes
The type name char on its own is PostgreSQL internal "char" — a one-byte type that is not bpchar and does not blank-pad — and the rule flags it anyway, because it matches the name list. That is a false positive the fixer cannot even act on, since fixMP041 only rewrites CHAR(n) / CHARACTER(n) forms with an explicit width. As with MP040, it returns on the first matching column, and a domain built over char(n) is invisible.
What the CLI prints
⚠ [MP041] WARNING (line 1) Column "country_code" on "users" uses CHAR(n), which blank-pads values and wastes storage. Use TEXT instead. Safe alternative: -- Use TEXT instead of CHAR(n): -- "country_code" TEXT Why: CHAR(n) blank-pads values to the specified length, wasting storage and causing confusing behavior in string comparisons and LIKE queries. It offers no performance advantage over TEXT in PostgreSQL. Use TEXT (or VARCHAR if you must have a length limit). Docs: https://migrationpilot.dev/rules/mp041
Generated 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 MP041 ALTER TABLE users ADD COLUMN country_code CHAR(2);
For the whole project, in .migrationpilotrc.yml — by name or by id:
rules:
MP041: false
# or keep it, and downgrade it
rules:
MP041:
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 MP041 in the playground