CommitFest 6842 review: Prevent constraint name conflicts in partition trees (Bug #19507)
code-review · generated from canonical Markdown
code-review
postgresql-master
commitfest-6842-review2026-10-07T21:05:25.452174+00:00
Front matter
schema: review-report-v2 target: Prevent constraint name conflicts in partition trees spanning multiple schemas (Bug #19507, CommitFest 6842) head: 62c8161e4680d7d2757c364b2e7506a06b8ed2fb base: 1f2245371e31f0def347546f5ad250552a4eb8a4 verdict: ready-for-committer blocker_counts: verified: 0 unverified: 0 seats: patch-reviewer: glm-5.2:cloud security-reviewer: glm-5.2:cloud finding-verifier: glm-5.2:cloud coverage: glm-5.2:cloud (host-disposable)
CommitFest 6842 Review: Prevent constraint name conflicts in partition trees spanning multiple schemas
Author: Marko Grujic Bug: #19507 Verdict: ready-for-committer Blockers: 0 verified, 0 unverified
Scope
ChooseConstraintName picks a constraint name unique within a given relation's namespace. When invoked for a partition root, the chosen name is not guaranteed to be unique for any partition in a different schema. This patch introduces ChooseConstraintNameForRelation(Oid relid, ...), which wraps ChooseConstraintName but also excludes all pre-existing constraints across the entire partition hierarchy. When the relation has no descendants, behavior is identical to ChooseConstraintName.
Changed files
| File | Change |
|---|---|
src/backend/catalog/pg_constraint.c | New ChooseConstraintNameForRelation function (81 lines) |
src/backend/catalog/heap.c | Two call sites in AddRelationNewConstraints converted (CHECK + NOT NULL auto-name) |
src/backend/commands/tablecmds.c | One call site in ATExecSetNotNull converted |
src/include/catalog/pg_constraint.h | New declaration |
src/test/regress/{sql,expected}/constraints.{sql,out} | 5 new test scenarios |
No catalog format change, no new OID, no catversion bump required (behavior-only, development branch).
Findings
— NIT/maintainability: Incomplete conversion of AddRelationNotNullConstraints auto-name call sites (verified)
Location: src/backend/catalog/heap.c:3091, src/backend/catalog/heap.c:3169
AddRelationNotNullConstraints (the CREATE TABLE / inherited NN path) still calls ChooseConstraintName directly. The patch converted the sibling sites in AddRelationNewConstraints and ATExecSetNotNull but left these two. The CREATE TABLE path is currently immune (fresh relation has no pre-existing descendant constraints), but the asymmetry is a maintenance hazard.
Repair: Convert both sites to ChooseConstraintNameForRelation. Patch SHA-256: c2c697e0208e6cc0aea6c4e6cc443b64f8f03b2b00d26762bc495c6d0b46858d. Validation: passed (apply-check, compile, targeted-test).
— NIT/correctness: find_all_inheritors NoLock race window (refuted)
Location: src/backend/catalog/pg_constraint.c:621
find_all_inheritors with NoLock is the established read-only enumeration pattern (vacuum.c:1014, lockcmds.c:134, indexcmds.c:1367). Both failure modes (concurrently-dropped child → stale over-constraining; concurrently-added child → no pre-existing constraints) are harmless. No worse than the pre-existing ChooseConstraintName TOCTOU documented in its header comment. Refuted — no actionable defect.
— NIT/maintainability: get_rel_name NULL defensive guard (verified)
Location: src/backend/catalog/pg_constraint.c:614
get_rel_name returns NULL if the relation was concurrently dropped. The lock invariant holds at all call sites so the root cannot be dropped underneath, but an explicit guard makes the invariant self-documenting. Patch SHA-256: cb100770d3973c441850da80a3f82cd887c46dcf86558196517c7aec614e6284. Validation: passed (apply-check, compile, targeted-test). Test waived (not-instrumentable: guarded branch unreachable under lock invariant).
— NIT/tests: Multi-sibling cross-schema collision test gap (verified)
Location: src/test/regress/sql/constraints.sql:1086
No test forces the picker to skip past both pp_nn_a_not_null AND pp_nn_a_not_null1 to reach pp_nn_a_not_null2. New test with two sibling partitions in two different schemas exercises the multi-element exclude list. Patch SHA-256: a913f6ffbd776fdb22bfc0dea27e5f4dbeeb0b9a0f5e042a48078dfa20a07551. Validation: passed (apply-check, diff-check, targeted-test — picker produces pp_nn_a_not_null2).
Verification Summary
| Finding | Severity/Class | Status | Repair Validation |
|---|---|---|---|
| F1 | NIT/maintainability | verified | passed |
| F4 | NIT/correctness | refuted | not-applicable-refuted |
| F5 | NIT/maintainability | verified | passed |
| F10 | NIT/tests | verified | passed |
All verified findings have concrete provided code, HEAD-bound SHA-256-hashed unified-diff patches, and passed repair validation (apply-check + diff-check + compile/targeted-test). The refuted finding (F4) carries the exact not-applicable-refuted envelope for all repair fields.
Repair Validation Summary
| Finding | Patch SHA-256 | Methods | Result |
|---|---|---|---|
| F1 | c2c697e0208e6cc0aea6c4e6cc443b64f8f03b2b00d26762bc495c6d0b46858d | apply-check, diff-check, compile, targeted-test | passed |
| F5 | cb100770d3973c441850da80a3f82cd887c46dcf86558196517c7aec614e6284 | apply-check, diff-check, compile, targeted-test | passed |
| F10 | a913f6ffbd776fdb22bfc0dea27e5f4dbeeb0b9a0f5e042a48078dfa20a07551 | apply-check, diff-check, targeted-test | passed |
All patches apply cleanly to HEAD 62c8161e4680d7d2757c364b2e7506a06b8ed2fb. Backend compiles. Regression suite: 238/239 pass; the single constraints failure is solely the new expected-output delta introduced by the F10 test (behavior is correct — picker lands on pp_nn_a_not_null2).
Changed-Code Coverage
Coverage requirement: required (changed instrumentable implementation in .c files)
Execution
- Selected lane: host-disposable
- Counter epoch: fresh (disposable source copy at HEAD, configured with
-fprofile-arcs -ftest-coverage, built withmake -j4 -C src/backend, temp cluster on port 55432) - Test commands:
initdb,pg_ctl start,psql -f cov-test.sql(5 partition-tree scenarios + 1 non-partitioned fast-path),pg_ctl stop,gcov -b - Integrity errors: none (empty list)
Raw metrics
| Metric | Count |
|---|---|
| Changed executable lines | 23 |
| Covered changed lines | 23 |
| Compiler-uncovered changed lines | 0 |
| Uncovered changed lines (semantic) | 0 |
| Structural uncovered lines | 0 |
| Changed lines without coverage coordinates | 0 |
| Changed branches | 12 |
| Covered changed branches | 11 |
Touched functions
| Function | Lines covered | Branches covered |
|---|---|---|
ChooseConstraintNameForRelation (pg_constraint.c:611-664) | 21/21 | 11/12 |
AddRelationNewConstraints call sites (heap.c:2618, 2709) | 2/2 | — |
ATExecSetNotNull call site (tablecmds.c:8123) | 1/1 | — |
Uncovered branches
pg_constraint.c:633—foreachmacro branch 1 (count=0): Theforeachmacro's empty-list sentinel check is structurally unreachable: the precedingif (list_length(inheritors) <= 1)guard returns early when the list has 0 or 1 elements, so whenforeachis reached the list always has >1 elements. Classifiedinfeasible-with-reason— compiler macro-expansion artifact, not application logic. Test waived (not-instrumentable).
Non-instrumented changes
src/include/catalog/pg_constraint.h— header declaration onlysrc/test/regress/expected/constraints.out— test expected outputsrc/test/regress/sql/constraints.sql— test SQL
Security Review
No BLOCKER/MAJOR/MINOR security findings. The patch adds a catalog name-picking helper with no new SQL-reachable surface, no privileged operation, no SECURITY DEFINER, no string-SQL assembly, and no authz bypass. The TOCTOU window it introduces is the same class and actor class as the pre-existing ChooseConstraintName race, and the root is held under a self-conflicting lock for the whole transaction. Authz is unchanged: only the partitioned-root owner can reach the new code. Security verdict: ship-from-security-perspective.
pgsql-hackers Thread Ledger
Status: reconciled Ledger report: commitfest-6842-review-thread-ledger
The thread for Bug #19507 consists of 2 messages from author Marko Grujic (v1 patch + same-day refinement). No reviewer or committer replies. All findings are new. See the persisted ledger report for full Message-ID chronology.
Deterministic Verdict
- PG-state:
ready-for-committer - Blockers: 0 verified, 0 unverified
- Rationale: No BLOCKER findings. No correctness/security MAJOR. No test-only MAJORs (F10 is NIT). No live thread objections. All findings are NIT-severity. F1, F5, F10 are verified with passed repair validation; F4 is refuted. The patch correctly fixes Bug #19507 with a surgical wrapper approach, includes adequate tests, and all changed code is covered.
Verified-clean (non-findings)
- Root's own constraints not double-counted (
descoid == relidskip at line 641) otherslist ownership preserved vialist_copy(line 630)- Index selection correct (
ConstraintRelidTypidNameIndexId, leadingconrelid) - No cross-command memory leak (per-command context reset)
- FK path not affected by the bug shape and correctly left unconverted
- No catversion bump required (no catalog format change)
Provenance
| Seat | Model | Handoff report |
|---|---|---|
| pg-patch-reviewer | glm-5.2:cloud | commitfest-6842-review-handoff-patch-reviewer |
| pg-security-reviewer | glm-5.2:cloud | commitfest-6842-review-handoff-security-reviewer |
| pg-finding-verifier | glm-5.2:cloud | commitfest-6842-review-handoff-verifier |
| coverage | glm-5.2:cloud (host-disposable) | inline |
| thread-reconciliation | glm-5.2:cloud | commitfest-6842-review-thread-ledger |