CommitFest 6842 review: Prevent constraint name conflicts in partition trees (Bug #19507)

code-review · generated from canonical Markdown

Type
code-review
Source
postgresql-master
Slug
commitfest-6842-review
Tags
code-reviewcommitfest-6842patch-reviewpostgresql
Updated
2026-10-07T21:05:25.452174+00:00
Verdict / Summaryready-for-committer
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

FileChange
src/backend/catalog/pg_constraint.cNew ChooseConstraintNameForRelation function (81 lines)
src/backend/catalog/heap.cTwo call sites in AddRelationNewConstraints converted (CHECK + NOT NULL auto-name)
src/backend/commands/tablecmds.cOne call site in ATExecSetNotNull converted
src/include/catalog/pg_constraint.hNew 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

F1

— 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).

F4

— 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.

F5

— 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).

F10

— 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

FindingSeverity/ClassStatusRepair Validation
F1NIT/maintainabilityverifiedpassed
F4NIT/correctnessrefutednot-applicable-refuted
F5NIT/maintainabilityverifiedpassed
F10NIT/testsverifiedpassed

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

FindingPatch SHA-256MethodsResult
F1c2c697e0208e6cc0aea6c4e6cc443b64f8f03b2b00d26762bc495c6d0b46858dapply-check, diff-check, compile, targeted-testpassed
F5cb100770d3973c441850da80a3f82cd887c46dcf86558196517c7aec614e6284apply-check, diff-check, compile, targeted-testpassed
F10a913f6ffbd776fdb22bfc0dea27e5f4dbeeb0b9a0f5e042a48078dfa20a07551apply-check, diff-check, targeted-testpassed

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

Raw metrics

MetricCount
Changed executable lines23
Covered changed lines23
Compiler-uncovered changed lines0
Uncovered changed lines (semantic)0
Structural uncovered lines0
Changed lines without coverage coordinates0
Changed branches12
Covered changed branches11

Touched functions

FunctionLines coveredBranches covered
ChooseConstraintNameForRelation (pg_constraint.c:611-664)21/2111/12
AddRelationNewConstraints call sites (heap.c:2618, 2709)2/2—
ATExecSetNotNull call site (tablecmds.c:8123)1/1—

Uncovered branches

  1. pg_constraint.c:633 — foreach macro branch 1 (count=0): The foreach macro's empty-list sentinel check is structurally unreachable: the preceding if (list_length(inheritors) <= 1) guard returns early when the list has 0 or 1 elements, so when foreach is reached the list always has >1 elements. Classified infeasible-with-reason — compiler macro-expansion artifact, not application logic. Test waived (not-instrumentable).

Non-instrumented changes

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

Verified-clean (non-findings)

Provenance

SeatModelHandoff report
pg-patch-reviewerglm-5.2:cloudcommitfest-6842-review-handoff-patch-reviewer
pg-security-reviewerglm-5.2:cloudcommitfest-6842-review-handoff-security-reviewer
pg-finding-verifierglm-5.2:cloudcommitfest-6842-review-handoff-verifier
coverageglm-5.2:cloud (host-disposable)inline
thread-reconciliationglm-5.2:cloudcommitfest-6842-review-thread-ledger