pingcap / pingcap/tidb

ALTER TABLE ADD COLUMN silently drops an inline column-level CHECK constraint

Open
#69,651 1 comment 0 reactions 0 assignees View on GitHub
affects-9.0 component/ddl found-by-ai severity/moderate sig/sql-infra type/bug
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 ... ADD COLUMN` with an inline column-level `CHECK` constraint succeeds and emits no warning, but the `CHECK` constraint is never published or enforced. A later row that should be rejected is silently accepted.

```sql
-- CHECK constraints must be enabled; a session reads this at connect time, so reconnect after:
SET GLOBAL tidb_enable_check_constraint = 1;

CREATE TABLE t(a INT);
INSERT INTO t VALUES (1),(2);

ALTER TABLE t ADD COLUMN b INT DEFAULT 1 CHECK (b > 0); -- succeeds, @@warning_count = 0
SHOW CREATE TABLE t; -- no CONSTRAINT ... CHECK on b
SELECT COUNT(*) FROM information_schema.check_constraints
WHERE constraint_schema = DATABASE(); -- 0

INSERT INTO t(a,b) VALUES (3,0); -- accepted, though b > 0 should reject it
SELECT a,b FROM t; -- (1,1),(2,1),(3,0)
```

A **named** inline constraint (`ADD COLUMN b INT DEFAULT 1 CONSTRAINT ck CHECK (b > 0)`) behaves identically: no constraint published, `b = 0` accepted.

### 2. What did you expect to see? (Required)

The inline `CHECK` should be enforced, exactly as it is on the two sibling paths:

```sql
-- direct CREATE TABLE publishes and enforces the same inline CHECK:
CREATE TABLE d(a INT, b INT DEFAULT 1 CHECK (b > 0));
INSERT INTO d(a,b) VALUES (1,0); -- ERROR 3819: Check constraint 'd_chk_1' is violated.

-- sequential ADD COLUMN + ADD CONSTRAINT also enforces it:
CREATE TABLE s(a INT);
ALTER TABLE s ADD COLUMN b INT DEFAULT 1;
ALTER TABLE s ADD CONSTRAINT ck CHECK (b > 0);
INSERT INTO s(a,b) VALUES (1,0); -- ERROR 3819: Check constraint 'ck' is violated.
```

So the expected outcome is either the constraint is published and enforced, or the statement is rejected — not silently dropped.

### 3. What did you see instead (Required)

`ALTER TABLE ... ADD COLUMN ... CHECK (...)` reports success with `@@warning_count = 0`, `SHOW CREATE TABLE` shows no `CHECK` on the new column, `information_schema.check_constraints` has no row, and a subsequent `INSERT` that violates the requested predicate is accepted. The user asked for an enforced integrity constraint and got a silently unconstrained column.

### 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 — ADD COLUMN discards the column-level constraint it extracts

In `pkg/ddl/add_column.go`, `CreateNewColumn` calls `buildColumnAndConstraint(...)` as `col, _, err := ...`, discarding the second return value — the column-level constraints extracted from `ColumnOptionCheck`. `AddColumn` then submits only an `ActionAddColumn` job with `TableColumnArgs`, so the extracted `CHECK` never reaches an owner. By contrast, `CREATE TABLE` consumes `ast.ConstraintCheck` into the table metadata, and table-level `ALTER TABLE ADD CHECK` is a separate `ActionAddCheckConstraint` path that validates existing rows before publishing. The fix is to carry the column-level `CHECK` returned by `buildColumnAndConstraint` into an `ActionAddCheckConstraint` sub-job after the new column is public (including existing-row validation), or to reject inline column `CHECK` in `ADD COLUMN` explicitly instead of dropping it.

Contributor guide

Open the contributing guide

Research direction

Start in pkg/ddl/add_column.go at CreateNewColumn and trace buildColumnAndConstraint through AddColumn, including the discarded constraint result and the ActionAddColumn/TableColumnArgs path. Compare this with the ActionAddCheckConstraint path used by ALTER TABLE ADD CHECK. Done means the inline CHECK is either published and enforced with existing-row validation, or the ADD COLUMN statement rejects it instead of silently dropping it.

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
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.