MODIFY COLUMN accepts foreign-key child column states that CREATE/ADD FOREIGN KEY rejects (SET NULL NOT NULL, signed unsigned)
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
### 1. Minimal reproduce step (Required)
`ALTER TABLE ... MODIFY COLUMN` can move a foreign-key child column into a state that `CREATE TABLE` / `ADD FOREIGN KEY` would reject, because the FK compatibility check runs on an incomplete target column and returns early when `type/flen/decimal` are unchanged. Two dimensions reproduce it:
**(A) A `SET NULL` child column made `NOT NULL`:**
```sql
SET foreign_key_checks = 1;
CREATE TABLE p(id INT PRIMARY KEY);
CREATE TABLE c(id INT PRIMARY KEY, pid INT, INDEX(pid),
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id) ON DELETE SET NULL);
ALTER TABLE c MODIFY COLUMN pid INT NOT NULL; -- succeeds, no warning
INSERT INTO p VALUES (1); INSERT INTO c VALUES (1,1);
DELETE FROM p WHERE id = 1; -- ERROR 1048: Column 'pid' cannot be null
```
**(B) A signed FK child column made `UNSIGNED` while referencing a signed parent:**
```sql
SET foreign_key_checks = 1;
CREATE TABLE p(a INT PRIMARY KEY);
CREATE TABLE c(a INT, INDEX(a),
CONSTRAINT fk FOREIGN KEY (a) REFERENCES p(a) ON UPDATE CASCADE);
INSERT INTO p VALUES (1); INSERT INTO c VALUES (1);
ALTER TABLE c MODIFY COLUMN a INT UNSIGNED; -- succeeds, no warning
UPDATE p SET a = -1 WHERE a = 1; -- ERROR 1264: Out of range value for column 'a'
```
### 2. What did you expect to see? (Required)
`MODIFY COLUMN` should reject these transitions, because the resulting schema is one that TiDB's own `CREATE` / `ADD FOREIGN KEY` validator already refuses:
```sql
-- (A) direct target state is rejected:
CREATE TABLE c(id INT PRIMARY KEY, pid INT NOT NULL, INDEX(pid),
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id) ON DELETE SET NULL);
-- ERROR 1830: Column 'pid' cannot be NOT NULL: needed in a foreign key constraint 'fk' SET NULL
-- (B) direct target state is rejected:
CREATE TABLE c(a INT UNSIGNED, INDEX(a),
CONSTRAINT fk FOREIGN KEY (a) REFERENCES p(a) ON UPDATE CASCADE);
-- ERROR 3780: Referencing column 'a' and referenced column 'a' ... are incompatible.
```
### 3. What did you see instead (Required)
Both `ALTER TABLE ... MODIFY COLUMN` statements succeed with no warning and publish the invalid FK schema (`SHOW CREATE TABLE` shows `pid int NOT NULL ... ON DELETE SET NULL`, and `a int unsigned ... REFERENCES p(a)` against a signed parent). The invalidity surfaces later as fail-stop DML: the parent `DELETE`/`UPDATE` that must run the FK action fails at runtime (`ERROR 1048` / `ERROR 1264`), so the referential action can no longer be performed. A round-trip `DROP FOREIGN KEY` + `ADD FOREIGN KEY` on the modified schema is correctly rejected with `ERROR 3780`, confirming the published state is the one the validator considers invalid.
### 4. What is your TiDB version? (Required)
```
Release Version: v9.0.0-beta.2.pre-1774-g81ec977cb8
Git Commit Hash: 81ec977cb8bf97e0c9805dfc0be8ffcbd4b0bbeb
UTC Build Time: 2026-05-28 03:35:30
Edition: Community
Store: tikv
```
Likely root cause — FK modify validator runs on an incomplete target column and returns early
In `pkg/ddl/foreign_key.go`, `checkModifyColumnWithForeignKeyConstraint` returns `nil` early when `newCol` and `originalCol` share the same `type`, `flen`, and `decimal`. But `INT -> INT UNSIGNED` keeps that tuple equal while flipping the unsigned flag, and the `NOT NULL` flag is applied *after* this check (via `ProcessModifyColumnOptions`), so the validator inspects a column that is not yet the final target. Meanwhile the `CREATE` / `ADD FOREIGN KEY` path checks the full set — type, unsigned flag, charset, collation, and the `SET NULL`-vs-`NOT NULL` requirement. The fix is to validate FK compatibility against the fully materialized target column (options applied) and compare the same dimensions the create/add path does, rather than short-circuiting on `type/flen/decimal` equality.
Contributor guide
Research direction
Start in pkg/ddl/foreign_key.go at checkModifyColumnWithForeignKeyConstraint and trace how ProcessModifyColumnOptions materializes the target column. Compare the MODIFY COLUMN checks with the CREATE/ADD FOREIGN KEY validation for unsigned and SET NULL versus NOT NULL cases. Done means both reproduced ALTER statements are rejected before publishing an invalid schema, while valid modifications continue to work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100