Review: Whole-row fixes for DROP COLUMN, SET COLUMN DATA TYPE (CF 61, Patch 6055)
code-review · generated from canonical Markdown
code-review
postgresql-master
commitfest-6055-review2026-08-14T16:53:08.915599+00:00
Front matter
schema: review-report-v2 target: "CommitFest 61, Patch 6055: Whole-row fixes for DROP COLUMN, SET COLUMN DATA TYPE" head: a82e33a83d7cd39f3abd18c7b941625ca78d4c35 base: a82e33a83d7cd39f3abd18c7b941625ca78d4c35 verdict: needs-review blocker_counts: verified: 0 unverified: 0 diff_sha: d98eecf92cd4c2bf764e33cc7d788cb453be2c4c8d4193157302ccd5d6807966 model: glm-5.2:cloud date: 2026-08-14
Review: Whole-row fixes for DROP COLUMN, SET COLUMN DATA TYPE
CommitFest 61, Patch 6055 | Author: Jian He | Reviewer: pg-review-harness (glm-5.2:cloud) Thread: https://postgr.es/m/CACJufxGA6KVQy7DbHGLVw9s9KKmpGyZt5ME6C7kEfjDpr2wZCw@mail.gmail.com
Scope
The patch modifies ATExecDropColumn() in src/backend/commands/tablecmds.c to also drop CHECK constraints and indexes that contain whole-row Var references (attnum 0) when any column is dropped. The existing dependency machinery already drops objects that directly reference the dropped column; the patch closes a gap where objects referencing the whole row (e.g., CHECK (t IS NOT NULL), CREATE INDEX ON t ((t)) WHERE t IS NOT NULL) were orphaned with a stale rowtype after DROP COLUMN.
Changed files: src/backend/commands/tablecmds.c (+122 lines), src/test/regress/expected/constraints.out (+17), src/test/regress/expected/indexing.out (+25), src/test/regress/sql/constraints.sql (+11), src/test/regress/sql/indexing.sql (+9)
Changed function: ATExecDropColumn (lines 9425-9738)
Verdict: needs-review
No BLOCKERs. Three MAJOR findings (2 correctness, 1 test). Live thread objection from Matthias van de Meent (2026-08-05) regarding the scanning approach. The MAJ-1 else if bug must be fixed before commit. The MAJ-2 scope discrepancy should be clarified with the author. The TST-1 expected-output mismatch must be fixed for the test suite to pass.
Findings
— MAJOR — Correctness — else if skips indexprs whole-row check when indpred is non-null
Location: src/backend/commands/tablecmds.c:9605
Mechanism: An index can have both indpred (partial-index predicate) AND indexprs (expression columns) non-null simultaneously (e.g., CREATE INDEX ON t ((t)) WHERE a > 0). The patch's else if at line 9605 means that when indpred is non-null, indexprs is never examined. If the whole-row reference is in the expression list but not the predicate, the index will NOT be dropped.
Failure: After ALTER TABLE t DROP COLUMN <col> where t has CREATE INDEX ON t ((t)) WHERE a > 0, the expression index ((t)) survives with a now-mismatched whole-row type, causing latent data corruption.
Verification: Confirmed by manual test. Before fix: index survives. After applying repair (else-if → if): index correctly dropped.
Repair: Change else if to independent if. Add bms_free, ReleaseSysCache, and continue after finding whole-row in indpred to avoid duplicate adds. Patch SHA-256: 938f4caf325cad65e37809e86d77081b4a8779a7833b6bed86c6984f1cbde7a2. Validation: passed (apply-check, diff-check, compile, targeted-test).
— MAJOR — Correctness — Title claims SET DATA TYPE but diff only touches DROP COLUMN
Location: src/backend/commands/tablecmds.c:15395 (ATExecAlterColumnType, unmodified)
Mechanism: The patch title and v14 commit message claim to fix both DROP COLUMN and SET COLUMN DATA TYPE, but the frozen diff only modifies ATExecDropColumn. ATExecAlterColumnType is not modified. The SET DATA TYPE whole-row problem remains unfixed.
Failure: ALTER TABLE ALTER COLUMN SET DATA TYPE with a whole-row-referenced CHECK constraint silently corrupts the constraint.
Verification: Static analysis confirms no changes to ATExecAlterColumnType in the diff.
Repair: Add a whole-row check constraint error-out at the beginning of ATExecAlterColumnType. Patch SHA-256: 3aad5b13c2fd59036f97f28e977aed93d3a58bc5ac32088fc97880fe29de85a3. Validation: not-run (proposal for author).
— MAJOR — Tests — Expected output has wrong index name
Location: src/test/regress/expected/indexing.out:776
Mechanism: The expected output file has idxpart_expr_idx but PostgreSQL generates idxpart_idxpart_idx for CREATE INDEX ON idxpart((idxpart is not null)).
Failure: The indexing regression test fails with a diff.
Verification: Confirmed by running make installcheck — test fails with expected-output mismatch.
Repair: Replace idxpart_expr_idx with idxpart_idxpart_idx. Patch SHA-256: ce04af6558a009ca59166dc4868b9d491465159aab18162f92ee6a503726f01b. Validation: passed (apply-check, diff-check, targeted-test).
— MINOR — Performance — Bitmapset *expr_attrs never freed
Location: src/backend/commands/tablecmds.c:9520
Already raised by Jinbinge (2026-01-19). Add bms_free(expr_attrs) in all three blocks. Patch SHA-256: 949c591769fdc7b5a1333f00eb5a021eb31f6ff4c3ad0b2fc213f4b0e3fc49e9.
— MINOR — Performance — Redundant per-constraint pg_constraint scan
Location: src/backend/commands/tablecmds.c:9536
Already raised by author in v5 (2025-09-23), but fix lost in v14. Restructure to single scan by conrelid. Patch SHA-256: 551d04483bddf06202878e1bbd241ba4d1c344a2b0db3cafb1e00b027cf30b49.
— MINOR — Performance — Redundant index lookups
Location: src/backend/commands/tablecmds.c:9571
Already raised by Kirill Reshke (2026-01-18). Replace RelationGetIndexList + SearchSysCache1 with single systable_beginscan on pg_index by indrelid. Patch SHA-256: cd3febc12233267224d72cc45416b2f61a2fe77e8d0c6991b2a11496b0da75f0.
— NIT — 0 vs InvalidAttrNumber inconsistency (lines 9528, 9597, 9621)
— NIT — Node *expr declared at function scope (line 9438)
— NIT — Comment grammar: "references" → "reference" (line 9570)
— NIT — TupleConstr *constr fetched at function top (line 9440)
All verified by static analysis. Concrete patches provided for each.
Verification Summary
| Finding | Status | Method | Repair Validation |
|---|---|---|---|
| MAJ-1 | verified | repro | passed (apply-check, diff-check, compile, targeted-test) |
| MAJ-2 | verified | static-check | not-run (proposal for author) |
| TST-1 | verified | targeted-test | passed (apply-check, diff-check, targeted-test) |
| MIN-1 | verified | static-check | not-run (trivial fix) |
| MIN-2 | verified | static-check | not-run (refactor proposal) |
| MIN-3 | verified | static-check | not-run (refactor proposal) |
| NIT-1 | verified | static-check | not-run (trivial fix) |
| NIT-2 | verified | static-check | not-run (trivial fix) |
| NIT-3 | verified | static-check | not-run (trivial fix) |
| NIT-4 | verified | static-check | not-run (trivial fix) |
Repair Validation Summary
- MAJ-1: Repair patch applied to disposable build, compiled successfully, and tested. The
else if→ifchange withbms_freeandcontinuecorrectly drops indexes with whole-row references inindexprseven whenindpredis non-null. Manual test confirmed:CREATE INDEX ON maj1_fix_test ((maj1_fix_test)) WHERE a > 0is dropped afterALTER TABLE DROP COLUMN c. - TST-1: One-line expected-output fix applied and verified. The index name
idxpart_idxpart_idxmatches PostgreSQL's actual naming convention. - MAJ-2, MIN-1, MIN-2, MIN-3, NIT-1 to NIT-4: Concrete patches provided but not independently compiled (trivial fixes or refactor proposals for the author).
Changed-Code Coverage
Execution lane: host-disposable (passed) Counter epoch: fresh (disposable build with -fprofile-arcs -ftest-coverage)
Metrics
| Metric | Count |
|---|---|
| Changed executable lines | 54 |
| Covered changed lines | 52 |
| Uncovered changed lines | 2 |
| Changed branches | 30 |
| Covered changed branches | 26 |
| Uncovered branches | 4 |
Uncovered lines (2)
Both are defensive elog(ERROR) paths that should never execute in normal operation:
- Line 9555:
elog(ERROR, "constraint \"%s\" of relation \"%s\" does not exist", ...)— infeasible (requires corrupted catalog state) - Line 9580:
elog(ERROR, "cache lookup failed for index %u", indexoid)— infeasible (requires corrupted catalog state)
Touched function
| Function | File | Lines | Entered | Lines Covered | Branches Covered |
|---|---|---|---|---|---|
| ATExecDropColumn | src/backend/commands/tablecmds.c | 9425-9738 | yes | 74/110 | 44/90 |
Non-instrumented changes
src/test/regress/expected/constraints.out— test expected outputsrc/test/regress/expected/indexing.out— test expected outputsrc/test/regress/sql/constraints.sql— test SQLsrc/test/regress/sql/indexing.sql— test SQL
Thread Reconciliation
Thread: 621482 (17 messages, 2025-09-08 to 2026-08-05) Ledger report: commitfest-6055-review-thread-ledger
Key thread events:
- Jinbinge (2026-01-19) already raised MIN-1 (bms_free leak)
- Author himself recognized MIN-2 in v5 (2025-09-23) but fix was lost in v14
- Kirill Reshke (2026-01-18) already raised MIN-3 (single scan)
- Lakshmi (2026-07-20) tested v14 and confirmed it works
- Matthias van de Meent (2026-08-05) raised a live design objection: criticizes the scanning approach as O(total_dependent_objects), proposes pg_depend registration with own objsubid, and argues indexes should be rebuilt not dropped. This is a design-level objection the committer should weigh.
Aggregated Test Plan
- Existing tests:
constraintsandindexingregress tests (indexing fails due to TST-1) - MAJ-1 test:
CREATE INDEX ON t ((t)) WHERE a > 0; ALTER TABLE t DROP COLUMN c;— verify index is dropped - MAJ-2 test:
CREATE TABLE t (... CONSTRAINT cc CHECK (t is not null)); ALTER TABLE t ALTER COLUMN c SET DATA TYPE bigint;— verify error is raised - Coverage tests: CHECK constraint whole-row drop, index expression whole-row drop, index predicate whole-row drop
Provenance
- Patch reviewer seat: glm-5.2:cloud (handoff:
commitfest-6055-review-handoff-patch-reviewer) - Security reviewer seat: glm-5.2:cloud (handoff:
commitfest-6055-review-handoff-security-reviewer) - Thread ledger:
commitfest-6055-review-thread-ledger - Coverage: host-disposable lane, fresh gcov epoch
- Model routing: policy v1, hash 997c0355... (all seats overridden to glm-5.2:cloud)
Residual Risk
- The MAJ-1
else ifbug is a real correctness issue that causes latent index corruption. It must be fixed before commit. - The MAJ-2 scope discrepancy means the SET DATA TYPE whole-row problem remains unfixed. The author should clarify whether this is intentional.
- Matthias van de Meent's design objection (pg_depend registration vs scanning) is a valid architectural concern that may warrant a different approach in a future patch.
- The bms_free leak (MIN-1) and redundant scans (MIN-2, MIN-3) are performance issues that should be addressed but are not blockers.